summaryrefslogtreecommitdiff
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
downloadflyspray-8df3db566a3a937b45ebf11adb90d265e6f5e2d4.tar.xz
initial checking of customized version 1.0rc9
-rw-r--r--.htaccess142
-rw-r--r--archnav32.pngbin0 -> 5508 bytes
-rw-r--r--attachments/.htaccess1
-rw-r--r--attachments/index.html0
-rw-r--r--avatars/1808f8a929.jpgbin0 -> 1334 bytes
-rw-r--r--avatars/index.html1
-rw-r--r--cache/index.html0
-rw-r--r--favicon.icobin0 -> 894 bytes
-rw-r--r--feed.php160
-rw-r--r--flyspray.conf.php48
-rw-r--r--flyspray.pngbin0 -> 67461 bytes
-rw-r--r--flyspray_small.pngbin0 -> 15645 bytes
-rw-r--r--fonts/index.html1
-rw-r--r--header.php75
-rw-r--r--includes/.htaccess1
-rw-r--r--includes/GithubProvider.php60
-rw-r--r--includes/class.backend.php1869
-rw-r--r--includes/class.csp.php106
-rw-r--r--includes/class.database.php434
-rw-r--r--includes/class.effort.php302
-rw-r--r--includes/class.flyspray.php1471
-rw-r--r--includes/class.gpc.php257
-rw-r--r--includes/class.jabber2.php943
-rw-r--r--includes/class.notify.php1114
-rw-r--r--includes/class.project.php474
-rw-r--r--includes/class.recaptcha.php33
-rw-r--r--includes/class.tpl.php1525
-rw-r--r--includes/class.user.php555
-rw-r--r--includes/constants.inc.php96
-rw-r--r--includes/events.inc.php308
-rw-r--r--includes/fix.inc.php205
-rw-r--r--includes/i18n.inc.php166
-rw-r--r--includes/modify.inc.php3053
-rw-r--r--includes/password_compat.php319
-rw-r--r--includes/utf8.inc.php118
-rw-r--r--index.php275
-rw-r--r--js/callbacks/checkrelated.php20
-rw-r--r--js/callbacks/checksave.php16
-rw-r--r--js/callbacks/deletesearches.php30
-rw-r--r--js/callbacks/gethistory.php69
-rw-r--r--js/callbacks/getpreview.php21
-rw-r--r--js/callbacks/getsearches.php30
-rw-r--r--js/callbacks/quickedit.php170
-rw-r--r--js/callbacks/savesearches.php27
-rw-r--r--js/callbacks/searchnames.php55
-rw-r--r--js/callbacks/searchtask.php43
-rw-r--r--js/callbacks/testemail.php44
-rw-r--r--js/callbacks/usersearch.php45
-rw-r--r--js/ckeditor/CHANGES.md720
-rw-r--r--js/ckeditor/LICENSE.md1264
-rw-r--r--js/ckeditor/README.md39
-rw-r--r--js/ckeditor/adapters/jquery.js10
-rw-r--r--js/ckeditor/build-config.js157
-rw-r--r--js/ckeditor/ckeditor.js946
-rw-r--r--js/ckeditor/config.js38
-rw-r--r--js/ckeditor/contents.css134
-rw-r--r--js/ckeditor/lang/af.js5
-rw-r--r--js/ckeditor/lang/ar.js5
-rw-r--r--js/ckeditor/lang/bg.js5
-rw-r--r--js/ckeditor/lang/bn.js5
-rw-r--r--js/ckeditor/lang/bs.js5
-rw-r--r--js/ckeditor/lang/ca.js5
-rw-r--r--js/ckeditor/lang/cs.js5
-rw-r--r--js/ckeditor/lang/cy.js5
-rw-r--r--js/ckeditor/lang/da.js5
-rw-r--r--js/ckeditor/lang/de.js5
-rw-r--r--js/ckeditor/lang/el.js5
-rw-r--r--js/ckeditor/lang/en-au.js5
-rw-r--r--js/ckeditor/lang/en-ca.js5
-rw-r--r--js/ckeditor/lang/en-gb.js5
-rw-r--r--js/ckeditor/lang/en.js5
-rw-r--r--js/ckeditor/lang/eo.js5
-rw-r--r--js/ckeditor/lang/es.js5
-rw-r--r--js/ckeditor/lang/et.js5
-rw-r--r--js/ckeditor/lang/eu.js5
-rw-r--r--js/ckeditor/lang/fa.js5
-rw-r--r--js/ckeditor/lang/fi.js5
-rw-r--r--js/ckeditor/lang/fo.js5
-rw-r--r--js/ckeditor/lang/fr-ca.js5
-rw-r--r--js/ckeditor/lang/fr.js5
-rw-r--r--js/ckeditor/lang/gl.js5
-rw-r--r--js/ckeditor/lang/gu.js5
-rw-r--r--js/ckeditor/lang/he.js5
-rw-r--r--js/ckeditor/lang/hi.js5
-rw-r--r--js/ckeditor/lang/hr.js5
-rw-r--r--js/ckeditor/lang/hu.js5
-rw-r--r--js/ckeditor/lang/id.js5
-rw-r--r--js/ckeditor/lang/is.js5
-rw-r--r--js/ckeditor/lang/it.js5
-rw-r--r--js/ckeditor/lang/ja.js5
-rw-r--r--js/ckeditor/lang/ka.js5
-rw-r--r--js/ckeditor/lang/km.js5
-rw-r--r--js/ckeditor/lang/ko.js5
-rw-r--r--js/ckeditor/lang/ku.js5
-rw-r--r--js/ckeditor/lang/lt.js5
-rw-r--r--js/ckeditor/lang/lv.js5
-rw-r--r--js/ckeditor/lang/mk.js5
-rw-r--r--js/ckeditor/lang/mn.js5
-rw-r--r--js/ckeditor/lang/ms.js5
-rw-r--r--js/ckeditor/lang/nb.js5
-rw-r--r--js/ckeditor/lang/nl.js5
-rw-r--r--js/ckeditor/lang/no.js5
-rw-r--r--js/ckeditor/lang/pl.js5
-rw-r--r--js/ckeditor/lang/pt-br.js5
-rw-r--r--js/ckeditor/lang/pt.js5
-rw-r--r--js/ckeditor/lang/ro.js5
-rw-r--r--js/ckeditor/lang/ru.js5
-rw-r--r--js/ckeditor/lang/si.js5
-rw-r--r--js/ckeditor/lang/sk.js5
-rw-r--r--js/ckeditor/lang/sl.js5
-rw-r--r--js/ckeditor/lang/sq.js5
-rw-r--r--js/ckeditor/lang/sr-latn.js5
-rw-r--r--js/ckeditor/lang/sr.js5
-rw-r--r--js/ckeditor/lang/sv.js5
-rw-r--r--js/ckeditor/lang/th.js5
-rw-r--r--js/ckeditor/lang/tr.js5
-rw-r--r--js/ckeditor/lang/tt.js5
-rw-r--r--js/ckeditor/lang/ug.js5
-rw-r--r--js/ckeditor/lang/uk.js5
-rw-r--r--js/ckeditor/lang/vi.js5
-rw-r--r--js/ckeditor/lang/zh-cn.js5
-rw-r--r--js/ckeditor/lang/zh.js5
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js10
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt25
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/af.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/da.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/de.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/el.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/en.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/es.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/et.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/he.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/id.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/it.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js9
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/km.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/no.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js13
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/si.js10
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sq.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sr-latn.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sr.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/sv.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/th.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/tr.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/tt.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/ug.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/uk.js12
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/vi.js11
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js9
-rw-r--r--js/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js9
-rw-r--r--js/ckeditor/plugins/about/dialogs/about.js7
-rw-r--r--js/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.pngbin0 -> 13339 bytes
-rw-r--r--js/ckeditor/plugins/about/dialogs/logo_ckeditor.pngbin0 -> 6757 bytes
-rw-r--r--js/ckeditor/plugins/clipboard/dialogs/paste.js11
-rw-r--r--js/ckeditor/plugins/dialog/dialogDefinition.js4
-rw-r--r--js/ckeditor/plugins/fakeobjects/images/spacer.gifbin0 -> 43 bytes
-rw-r--r--js/ckeditor/plugins/icons.pngbin0 -> 10227 bytes
-rw-r--r--js/ckeditor/plugins/icons_hidpi.pngbin0 -> 34465 bytes
-rw-r--r--js/ckeditor/plugins/image/dialogs/image.js43
-rw-r--r--js/ckeditor/plugins/image/images/noimage.pngbin0 -> 2115 bytes
-rw-r--r--js/ckeditor/plugins/link/dialogs/anchor.js7
-rw-r--r--js/ckeditor/plugins/link/dialogs/link.js26
-rw-r--r--js/ckeditor/plugins/link/images/anchor.pngbin0 -> 589 bytes
-rw-r--r--js/ckeditor/plugins/link/images/hidpi/anchor.pngbin0 -> 1379 bytes
-rw-r--r--js/ckeditor/plugins/magicline/images/hidpi/icon-rtl.pngbin0 -> 176 bytes
-rw-r--r--js/ckeditor/plugins/magicline/images/hidpi/icon.pngbin0 -> 199 bytes
-rw-r--r--js/ckeditor/plugins/magicline/images/icon-rtl.pngbin0 -> 138 bytes
-rw-r--r--js/ckeditor/plugins/magicline/images/icon.pngbin0 -> 133 bytes
-rw-r--r--js/ckeditor/plugins/pastefromword/filter/default.js31
-rw-r--r--js/ckeditor/plugins/scayt/LICENSE.md28
-rw-r--r--js/ckeditor/plugins/scayt/README.md25
-rw-r--r--js/ckeditor/plugins/scayt/dialogs/options.js17
-rw-r--r--js/ckeditor/plugins/scayt/dialogs/toolbar.css71
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/_translationstatus.txt20
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/af.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ar.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/bg.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ca.js14
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/cs.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/cy.js14
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/da.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/de.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/el.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/en-gb.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/en.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/eo.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/es.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/et.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/fa.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/fi.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/fr-ca.js10
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/fr.js11
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/gl.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/he.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/hr.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/hu.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/id.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/it.js14
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ja.js9
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/km.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ku.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/lt.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/lv.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/nb.js11
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/nl.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/no.js11
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/pl.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/pt-br.js11
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/pt.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ru.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/si.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/sk.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/sl.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/sq.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/sv.js11
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/th.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/tr.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/tt.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/ug.js13
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/uk.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/vi.js14
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/zh-cn.js9
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/lang/zh.js12
-rw-r--r--js/ckeditor/plugins/specialchar/dialogs/specialchar.js14
-rw-r--r--js/ckeditor/plugins/table/dialogs/table.js21
-rw-r--r--js/ckeditor/plugins/tabletools/dialogs/tableCell.js17
-rw-r--r--js/ckeditor/plugins/wsc/LICENSE.md28
-rw-r--r--js/ckeditor/plugins/wsc/README.md25
-rw-r--r--js/ckeditor/plugins/wsc/dialogs/ciframe.html66
-rw-r--r--js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html52
-rw-r--r--js/ckeditor/plugins/wsc/dialogs/wsc.css82
-rw-r--r--js/ckeditor/plugins/wsc/dialogs/wsc.js74
-rw-r--r--js/ckeditor/plugins/wsc/dialogs/wsc_ie.js11
-rw-r--r--js/ckeditor/samples/ajax.html82
-rw-r--r--js/ckeditor/samples/api.html207
-rw-r--r--js/ckeditor/samples/appendto.html56
-rw-r--r--js/ckeditor/samples/assets/inlineall/logo.pngbin0 -> 4283 bytes
-rw-r--r--js/ckeditor/samples/assets/outputxhtml/outputxhtml.css204
-rw-r--r--js/ckeditor/samples/assets/posteddata.php59
-rw-r--r--js/ckeditor/samples/assets/sample.css3
-rw-r--r--js/ckeditor/samples/assets/sample.jpgbin0 -> 14449 bytes
-rw-r--r--js/ckeditor/samples/assets/uilanguages/languages.js7
-rw-r--r--js/ckeditor/samples/datafiltering.html401
-rw-r--r--js/ckeditor/samples/divreplace.html141
-rw-r--r--js/ckeditor/samples/index.html128
-rw-r--r--js/ckeditor/samples/inlineall.html311
-rw-r--r--js/ckeditor/samples/inlinebycode.html121
-rw-r--r--js/ckeditor/samples/inlinetextarea.html110
-rw-r--r--js/ckeditor/samples/jquery.html100
-rw-r--r--js/ckeditor/samples/plugins/dialog/assets/my_dialog.js48
-rw-r--r--js/ckeditor/samples/plugins/dialog/dialog.html187
-rw-r--r--js/ckeditor/samples/plugins/enterkey/enterkey.html103
-rw-r--r--js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.flabin0 -> 85504 bytes
-rw-r--r--js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swfbin0 -> 15571 bytes
-rw-r--r--js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js18
-rw-r--r--js/ckeditor/samples/plugins/htmlwriter/outputforflash.html280
-rw-r--r--js/ckeditor/samples/plugins/htmlwriter/outputhtml.html221
-rw-r--r--js/ckeditor/samples/plugins/magicline/magicline.html206
-rw-r--r--js/ckeditor/samples/plugins/toolbar/toolbar.html232
-rw-r--r--js/ckeditor/samples/plugins/wysiwygarea/fullpage.html77
-rw-r--r--js/ckeditor/samples/readonly.html73
-rw-r--r--js/ckeditor/samples/replacebyclass.html57
-rw-r--r--js/ckeditor/samples/replacebycode.html56
-rw-r--r--js/ckeditor/samples/sample.css365
-rw-r--r--js/ckeditor/samples/sample.js50
-rw-r--r--js/ckeditor/samples/sample_posteddata.php16
-rw-r--r--js/ckeditor/samples/tabindex.html75
-rw-r--r--js/ckeditor/samples/uicolor.html69
-rw-r--r--js/ckeditor/samples/uilanguages.html119
-rw-r--r--js/ckeditor/samples/xhtmlstyle.html231
-rw-r--r--js/ckeditor/skins/moono/dialog.css5
-rw-r--r--js/ckeditor/skins/moono/dialog_ie.css5
-rw-r--r--js/ckeditor/skins/moono/dialog_ie7.css5
-rw-r--r--js/ckeditor/skins/moono/dialog_ie8.css5
-rw-r--r--js/ckeditor/skins/moono/dialog_iequirks.css5
-rw-r--r--js/ckeditor/skins/moono/dialog_opera.css5
-rw-r--r--js/ckeditor/skins/moono/editor.css5
-rw-r--r--js/ckeditor/skins/moono/editor_gecko.css5
-rw-r--r--js/ckeditor/skins/moono/editor_ie.css5
-rw-r--r--js/ckeditor/skins/moono/editor_ie7.css5
-rw-r--r--js/ckeditor/skins/moono/editor_ie8.css5
-rw-r--r--js/ckeditor/skins/moono/editor_iequirks.css5
-rw-r--r--js/ckeditor/skins/moono/icons.pngbin0 -> 10227 bytes
-rw-r--r--js/ckeditor/skins/moono/icons_hidpi.pngbin0 -> 34465 bytes
-rw-r--r--js/ckeditor/skins/moono/images/arrow.pngbin0 -> 191 bytes
-rw-r--r--js/ckeditor/skins/moono/images/close.pngbin0 -> 468 bytes
-rw-r--r--js/ckeditor/skins/moono/images/hidpi/close.pngbin0 -> 1271 bytes
-rw-r--r--js/ckeditor/skins/moono/images/hidpi/lock-open.pngbin0 -> 1329 bytes
-rw-r--r--js/ckeditor/skins/moono/images/hidpi/lock.pngbin0 -> 1299 bytes
-rw-r--r--js/ckeditor/skins/moono/images/hidpi/refresh.pngbin0 -> 1842 bytes
-rw-r--r--js/ckeditor/skins/moono/images/lock-open.pngbin0 -> 349 bytes
-rw-r--r--js/ckeditor/skins/moono/images/lock.pngbin0 -> 475 bytes
-rw-r--r--js/ckeditor/skins/moono/images/mini.pngbin0 -> 818 bytes
-rw-r--r--js/ckeditor/skins/moono/images/refresh.pngbin0 -> 422 bytes
-rw-r--r--js/ckeditor/skins/moono/readme.md51
-rw-r--r--js/ckeditor/styles.js111
-rw-r--r--js/details.js46
-rw-r--r--js/functions.js576
-rw-r--r--js/index.js91
-rw-r--r--js/jit/jit.js16841
-rw-r--r--js/jscalendar/calendar-blue.css232
-rw-r--r--js/jscalendar/calendar-blue2.css236
-rw-r--r--js/jscalendar/calendar-brown.css225
-rw-r--r--js/jscalendar/calendar-green.css229
-rw-r--r--js/jscalendar/calendar-setup.js203
-rw-r--r--js/jscalendar/calendar-setup_stripped.js22
-rw-r--r--js/jscalendar/calendar-system.css225
-rw-r--r--js/jscalendar/calendar-tas.css239
-rw-r--r--js/jscalendar/calendar-win2k-1.css271
-rw-r--r--js/jscalendar/calendar-win2k-2.css271
-rw-r--r--js/jscalendar/calendar-win2k-cold-1.css265
-rw-r--r--js/jscalendar/calendar-win2k-cold-2.css271
-rw-r--r--js/jscalendar/calendar.js1807
-rw-r--r--js/jscalendar/calendar.php119
-rw-r--r--js/jscalendar/calendar_stripped.js14
-rw-r--r--js/jscalendar/img.gifbin0 -> 223 bytes
-rw-r--r--js/jscalendar/lang/calendar-af.js39
-rw-r--r--js/jscalendar/lang/calendar-al.js101
-rw-r--r--js/jscalendar/lang/calendar-bg.js131
-rw-r--r--js/jscalendar/lang/calendar-big5-utf8.js123
-rw-r--r--js/jscalendar/lang/calendar-big5.js123
-rw-r--r--js/jscalendar/lang/calendar-br.js108
-rw-r--r--js/jscalendar/lang/calendar-ca.js123
-rw-r--r--js/jscalendar/lang/calendar-cs.js69
-rw-r--r--js/jscalendar/lang/calendar-da.js125
-rw-r--r--js/jscalendar/lang/calendar-de.js127
-rw-r--r--js/jscalendar/lang/calendar-du.js45
-rw-r--r--js/jscalendar/lang/calendar-el.js100
-rw-r--r--js/jscalendar/lang/calendar-en.js127
-rw-r--r--js/jscalendar/lang/calendar-es.js129
-rw-r--r--js/jscalendar/lang/calendar-fi.js102
-rw-r--r--js/jscalendar/lang/calendar-fr.js127
-rw-r--r--js/jscalendar/lang/calendar-he.js122
-rw-r--r--js/jscalendar/lang/calendar-hr.js53
-rw-r--r--js/jscalendar/lang/calendar-hu.js126
-rw-r--r--js/jscalendar/lang/calendar-it.js126
-rw-r--r--js/jscalendar/lang/calendar-ja.js127
-rw-r--r--js/jscalendar/lang/calendar-jp.js45
-rw-r--r--js/jscalendar/lang/calendar-ko.js119
-rw-r--r--js/jscalendar/lang/calendar-lt.js114
-rw-r--r--js/jscalendar/lang/calendar-lv.js123
-rw-r--r--js/jscalendar/lang/calendar-mk.js119
-rw-r--r--js/jscalendar/lang/calendar-nl.js75
-rw-r--r--js/jscalendar/lang/calendar-no.js127
-rw-r--r--js/jscalendar/lang/calendar-pl-utf8.js93
-rw-r--r--js/jscalendar/lang/calendar-pl.js133
-rw-r--r--js/jscalendar/lang/calendar-pt.js123
-rw-r--r--js/jscalendar/lang/calendar-ro.js66
-rw-r--r--js/jscalendar/lang/calendar-ru.js126
-rw-r--r--js/jscalendar/lang/calendar-si.js97
-rw-r--r--js/jscalendar/lang/calendar-sk.js68
-rw-r--r--js/jscalendar/lang/calendar-sp.js110
-rw-r--r--js/jscalendar/lang/calendar-sr.js122
-rw-r--r--js/jscalendar/lang/calendar-sv.js98
-rw-r--r--js/jscalendar/lang/calendar-tr.js50
-rw-r--r--js/jscalendar/lang/calendar-zh.js123
-rw-r--r--js/jscalendar/menuarrow.gifbin0 -> 68 bytes
-rw-r--r--js/jscalendar/menuarrow2.gifbin0 -> 49 bytes
-rw-r--r--js/jscalendar/skins/aqua/active-bg.gifbin0 -> 89 bytes
-rw-r--r--js/jscalendar/skins/aqua/dark-bg.gifbin0 -> 85 bytes
-rw-r--r--js/jscalendar/skins/aqua/hover-bg.gifbin0 -> 89 bytes
-rw-r--r--js/jscalendar/skins/aqua/menuarrow.gifbin0 -> 49 bytes
-rw-r--r--js/jscalendar/skins/aqua/normal-bg.gifbin0 -> 110 bytes
-rw-r--r--js/jscalendar/skins/aqua/rowhover-bg.gifbin0 -> 110 bytes
-rw-r--r--js/jscalendar/skins/aqua/status-bg.gifbin0 -> 116 bytes
-rw-r--r--js/jscalendar/skins/aqua/theme.css236
-rw-r--r--js/jscalendar/skins/aqua/title-bg.gifbin0 -> 116 bytes
-rw-r--r--js/jscalendar/skins/aqua/today-bg.gifbin0 -> 1122 bytes
-rw-r--r--js/lightbox/css/lightbox.css27
-rw-r--r--js/lightbox/images/bullet.gifbin0 -> 49 bytes
-rw-r--r--js/lightbox/images/close.gifbin0 -> 222 bytes
-rw-r--r--js/lightbox/images/closelabel.gifbin0 -> 979 bytes
-rw-r--r--js/lightbox/images/loading.gifbin0 -> 2767 bytes
-rw-r--r--js/lightbox/images/nextlabel.gifbin0 -> 1252 bytes
-rw-r--r--js/lightbox/images/prevlabel.gifbin0 -> 1264 bytes
-rw-r--r--js/lightbox/js/lightbox.js497
-rw-r--r--js/prototype/prototype.js6081
-rw-r--r--js/script.aculo.us/builder.js136
-rw-r--r--js/script.aculo.us/controls.js965
-rw-r--r--js/script.aculo.us/dragdrop.js974
-rw-r--r--js/script.aculo.us/effects.js1123
-rw-r--r--js/script.aculo.us/scriptaculous.js68
-rw-r--r--js/script.aculo.us/slider.js275
-rw-r--r--js/script.aculo.us/sound.js59
-rw-r--r--js/script.aculo.us/unittest.js568
-rw-r--r--js/tablecontrol.js487
-rw-r--r--js/tabs.js148
-rw-r--r--lang/bg.php826
-rw-r--r--lang/de.php1101
-rw-r--r--lang/dk.php809
-rw-r--r--lang/el.php1065
-rw-r--r--lang/en.php1117
-rw-r--r--lang/es.php1039
-rw-r--r--lang/fi.php1058
-rw-r--r--lang/fr.php1108
-rw-r--r--lang/he.php1005
-rw-r--r--lang/hu.php851
-rw-r--r--lang/it.php1101
-rw-r--r--lang/ja.php829
-rw-r--r--lang/mk.php831
-rw-r--r--lang/nl.php823
-rw-r--r--lang/no.php1007
-rw-r--r--lang/pl.php926
-rw-r--r--lang/pt_br.php840
-rw-r--r--lang/pt_pt.php1051
-rw-r--r--lang/ro.php1067
-rw-r--r--lang/ru.php1036
-rw-r--r--lang/sk.php929
-rw-r--r--lang/sl.php424
-rw-r--r--lang/sr.php830
-rw-r--r--lang/sr_lat.php830
-rw-r--r--lang/sv_se.php836
-rw-r--r--lang/zh_cn.php1080
-rw-r--r--lang/zh_tw.php1045
-rw-r--r--phpunit_mysql.xml26
-rw-r--r--phpunit_pgsql.xml26
-rw-r--r--plugins/.htaccess8
-rw-r--r--plugins/dokuwiki/conf/.htaccess3
-rw-r--r--plugins/dokuwiki/conf/acronyms.conf143
-rw-r--r--plugins/dokuwiki/conf/dokuwiki.php132
-rw-r--r--plugins/dokuwiki/conf/entities.conf21
-rw-r--r--plugins/dokuwiki/conf/interwiki.conf121
-rw-r--r--plugins/dokuwiki/conf/mime.conf41
-rw-r--r--plugins/dokuwiki/conf/smileys.conf28
-rw-r--r--plugins/dokuwiki/dokuwiki_constants.inc.php9
-rw-r--r--plugins/dokuwiki/dokuwiki_formattext.inc.php155
-rw-r--r--plugins/dokuwiki/img/divider.gifbin0 -> 56 bytes
-rw-r--r--plugins/dokuwiki/img/email.pngbin0 -> 1333 bytes
-rw-r--r--plugins/dokuwiki/img/format-text-bold.pngbin0 -> 939 bytes
-rw-r--r--plugins/dokuwiki/img/format-text-italic.pngbin0 -> 784 bytes
-rw-r--r--plugins/dokuwiki/img/format-text-strikethrough.pngbin0 -> 797 bytes
-rw-r--r--plugins/dokuwiki/img/format-text-underline.pngbin0 -> 869 bytes
-rw-r--r--plugins/dokuwiki/img/h1.gifbin0 -> 696 bytes
-rw-r--r--plugins/dokuwiki/img/h2.gifbin0 -> 699 bytes
-rw-r--r--plugins/dokuwiki/img/h3.gifbin0 -> 700 bytes
-rw-r--r--plugins/dokuwiki/img/hr.gifbin0 -> 664 bytes
-rw-r--r--plugins/dokuwiki/img/image-x-generic.pngbin0 -> 900 bytes
-rw-r--r--plugins/dokuwiki/img/img.gifbin0 -> 370 bytes
-rw-r--r--plugins/dokuwiki/img/network.pngbin0 -> 1779 bytes
-rw-r--r--plugins/dokuwiki/img/ol.gifbin0 -> 677 bytes
-rw-r--r--plugins/dokuwiki/img/source.pngbin0 -> 1443 bytes
-rw-r--r--plugins/dokuwiki/img/source_php.pngbin0 -> 1376 bytes
-rw-r--r--plugins/dokuwiki/img/text-html.pngbin0 -> 1097 bytes
-rw-r--r--plugins/dokuwiki/img/ul.gifbin0 -> 668 bytes
-rw-r--r--plugins/dokuwiki/inc/HTTPClient.php436
-rw-r--r--plugins/dokuwiki/inc/JpegMeta.php2982
-rw-r--r--plugins/dokuwiki/inc/cache.php291
-rw-r--r--plugins/dokuwiki/inc/common.php1009
-rw-r--r--plugins/dokuwiki/inc/confutils.php189
-rw-r--r--plugins/dokuwiki/inc/events.php202
-rw-r--r--plugins/dokuwiki/inc/geshi.php4775
-rw-r--r--plugins/dokuwiki/inc/geshi/actionscript-french.php957
-rw-r--r--plugins/dokuwiki/inc/geshi/actionscript.php199
-rw-r--r--plugins/dokuwiki/inc/geshi/ada.php135
-rw-r--r--plugins/dokuwiki/inc/geshi/apache.php173
-rw-r--r--plugins/dokuwiki/inc/geshi/applescript.php136
-rw-r--r--plugins/dokuwiki/inc/geshi/asm.php200
-rw-r--r--plugins/dokuwiki/inc/geshi/asp.php155
-rw-r--r--plugins/dokuwiki/inc/geshi/autoit.php196
-rw-r--r--plugins/dokuwiki/inc/geshi/bash.php137
-rw-r--r--plugins/dokuwiki/inc/geshi/c.php144
-rw-r--r--plugins/dokuwiki/inc/geshi/c_mac.php176
-rw-r--r--plugins/dokuwiki/inc/geshi/caddcl.php127
-rw-r--r--plugins/dokuwiki/inc/geshi/cadlisp.php187
-rw-r--r--plugins/dokuwiki/inc/geshi/cpp.php172
-rw-r--r--plugins/dokuwiki/inc/geshi/csharp.php233
-rw-r--r--plugins/dokuwiki/inc/geshi/css.php178
-rw-r--r--plugins/dokuwiki/inc/geshi/d.php287
-rw-r--r--plugins/dokuwiki/inc/geshi/delphi.php272
-rw-r--r--plugins/dokuwiki/inc/geshi/diff.php186
-rw-r--r--plugins/dokuwiki/inc/geshi/div.php128
-rw-r--r--plugins/dokuwiki/inc/geshi/dos.php185
-rw-r--r--plugins/dokuwiki/inc/geshi/eiffel.php397
-rw-r--r--plugins/dokuwiki/inc/geshi/freebasic.php137
-rw-r--r--plugins/dokuwiki/inc/geshi/gml.php504
-rw-r--r--plugins/dokuwiki/inc/geshi/html4strict.php256
-rw-r--r--plugins/dokuwiki/inc/geshi/html5.php210
-rw-r--r--plugins/dokuwiki/inc/geshi/ini.php125
-rw-r--r--plugins/dokuwiki/inc/geshi/inno.php215
-rw-r--r--plugins/dokuwiki/inc/geshi/java.php1390
-rw-r--r--plugins/dokuwiki/inc/geshi/javascript.php146
-rw-r--r--plugins/dokuwiki/inc/geshi/lisp.php135
-rw-r--r--plugins/dokuwiki/inc/geshi/lua.php137
-rw-r--r--plugins/dokuwiki/inc/geshi/matlab.php869
-rw-r--r--plugins/dokuwiki/inc/geshi/mpasm.php160
-rw-r--r--plugins/dokuwiki/inc/geshi/nsis.php354
-rw-r--r--plugins/dokuwiki/inc/geshi/objc.php241
-rw-r--r--plugins/dokuwiki/inc/geshi/ocaml-brief.php114
-rw-r--r--plugins/dokuwiki/inc/geshi/ocaml.php163
-rw-r--r--plugins/dokuwiki/inc/geshi/oobas.php132
-rw-r--r--plugins/dokuwiki/inc/geshi/oracle8.php489
-rw-r--r--plugins/dokuwiki/inc/geshi/pascal.php145
-rw-r--r--plugins/dokuwiki/inc/geshi/perl.php169
-rw-r--r--plugins/dokuwiki/inc/geshi/php-brief.php162
-rw-r--r--plugins/dokuwiki/inc/geshi/php.php356
-rw-r--r--plugins/dokuwiki/inc/geshi/python.php229
-rw-r--r--plugins/dokuwiki/inc/geshi/qbasic.php147
-rw-r--r--plugins/dokuwiki/inc/geshi/ruby.php149
-rw-r--r--plugins/dokuwiki/inc/geshi/scheme.php172
-rw-r--r--plugins/dokuwiki/inc/geshi/sdlbasic.php163
-rw-r--r--plugins/dokuwiki/inc/geshi/smarty.php168
-rw-r--r--plugins/dokuwiki/inc/geshi/sql.php137
-rw-r--r--plugins/dokuwiki/inc/geshi/vb.php150
-rw-r--r--plugins/dokuwiki/inc/geshi/vbnet.php199
-rw-r--r--plugins/dokuwiki/inc/geshi/vhdl.php140
-rw-r--r--plugins/dokuwiki/inc/geshi/visualfoxpro.php444
-rw-r--r--plugins/dokuwiki/inc/geshi/xml.php147
-rw-r--r--plugins/dokuwiki/inc/html.php1290
-rw-r--r--plugins/dokuwiki/inc/infoutils.php249
-rw-r--r--plugins/dokuwiki/inc/init.php367
-rw-r--r--plugins/dokuwiki/inc/io.php567
-rw-r--r--plugins/dokuwiki/inc/pageutils.php478
-rw-r--r--plugins/dokuwiki/inc/parser/handler.php1653
-rw-r--r--plugins/dokuwiki/inc/parser/lexer.php607
-rw-r--r--plugins/dokuwiki/inc/parser/parser.php932
-rw-r--r--plugins/dokuwiki/inc/parser/renderer.php212
-rw-r--r--plugins/dokuwiki/inc/parser/xhtml.php1097
-rw-r--r--plugins/dokuwiki/inc/parserutils.php524
-rw-r--r--plugins/dokuwiki/inc/pluginutils.php95
-rw-r--r--plugins/dokuwiki/inc/utf8.php1276
-rw-r--r--plugins/dokuwiki/lib/exe/fetch.php433
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/bz2.pngbin0 -> 720 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/conf.pngbin0 -> 717 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/deb.pngbin0 -> 716 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/doc.pngbin0 -> 659 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/file.gifbin0 -> 942 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/file.pngbin0 -> 720 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/gif.pngbin0 -> 1001 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/gz.pngbin0 -> 716 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/htm.pngbin0 -> 748 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/html.pngbin0 -> 748 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/index.php49
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/jpeg.pngbin0 -> 1001 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/jpg.pngbin0 -> 1001 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odc.pngbin0 -> 749 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odf.pngbin0 -> 807 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odg.pngbin0 -> 788 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odi.pngbin0 -> 788 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odp.pngbin0 -> 744 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/ods.pngbin0 -> 749 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/odt.pngbin0 -> 577 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/pdf.pngbin0 -> 663 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/png.pngbin0 -> 1001 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/ppt.pngbin0 -> 762 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/ps.pngbin0 -> 534 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/rpm.pngbin0 -> 638 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/rtf.pngbin0 -> 474 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/swf.pngbin0 -> 843 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/sxc.pngbin0 -> 749 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/sxd.pngbin0 -> 788 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/sxi.pngbin0 -> 744 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/sxw.pngbin0 -> 577 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/tar.pngbin0 -> 747 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/tgz.pngbin0 -> 716 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/txt.pngbin0 -> 542 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/xls.pngbin0 -> 731 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/xml.pngbin0 -> 475 bytes
-rw-r--r--plugins/dokuwiki/lib/images/fileicons/zip.pngbin0 -> 874 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/amazon.de.gifbin0 -> 882 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/amazon.gifbin0 -> 882 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/amazon.uk.gifbin0 -> 882 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/bug.gifbin0 -> 166 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/coral.gifbin0 -> 85 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/doku.gifbin0 -> 257 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/google.gifbin0 -> 980 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/meatball.gifbin0 -> 1100 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/phpfn.gifbin0 -> 330 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/sb.gifbin0 -> 886 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/wiki.gifbin0 -> 909 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/wp.gifbin0 -> 680 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/wpde.gifbin0 -> 680 bytes
-rw-r--r--plugins/dokuwiki/lib/images/interwiki/wpmeta.gifbin0 -> 680 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/delete.gifbin0 -> 1421 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/fixme.gifbin0 -> 1435 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_arrow.gifbin0 -> 170 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_biggrin.gifbin0 -> 172 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_confused.gifbin0 -> 171 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_cool.gifbin0 -> 172 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_cry.gifbin0 -> 498 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_doubt.gifbin0 -> 990 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_doubt2.gifbin0 -> 992 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_eek.gifbin0 -> 170 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_evil.gifbin0 -> 236 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_exclaim.gifbin0 -> 236 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_frown.gifbin0 -> 171 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_fun.gifbin0 -> 590 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_idea.gifbin0 -> 176 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_kaddi.gifbin0 -> 991 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_lol.gifbin0 -> 336 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_mrgreen.gifbin0 -> 349 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_neutral.gifbin0 -> 171 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_question.gifbin0 -> 248 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_razz.gifbin0 -> 176 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_redface.gifbin0 -> 650 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_rolleyes.gifbin0 -> 485 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_sad.gifbin0 -> 171 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_silenced.gifbin0 -> 231 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_smile.gifbin0 -> 174 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_smile2.gifbin0 -> 174 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_surprised.gifbin0 -> 174 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_twisted.gifbin0 -> 238 bytes
-rw-r--r--plugins/dokuwiki/lib/images/smileys/icon_wink.gifbin0 -> 170 bytes
-rw-r--r--plugins/dokuwiki/lib/plugins/changelinks/syntax.php157
-rw-r--r--plugins/dokuwiki/lib/plugins/fslink/syntax.php81
-rw-r--r--plugins/dokuwiki/lib/plugins/newline/syntax.php77
-rw-r--r--plugins/dokuwiki/lib/plugins/syntax.php270
-rw-r--r--robots.txt2
-rw-r--r--schedule.php91
-rw-r--r--scripts/activity.php64
-rw-r--r--scripts/admin.php185
-rw-r--r--scripts/authenticate.php104
-rw-r--r--scripts/depends.php196
-rw-r--r--scripts/details.php768
-rw-r--r--scripts/editcomment.php28
-rw-r--r--scripts/index.php534
-rw-r--r--scripts/langdiff.php192
-rw-r--r--scripts/langedit.php329
-rw-r--r--scripts/lostpw.php32
-rw-r--r--scripts/myprofile.php41
-rw-r--r--scripts/newmultitasks.php20
-rw-r--r--scripts/newtask.php53
-rw-r--r--scripts/oauth.php201
-rw-r--r--scripts/pm.php61
-rw-r--r--scripts/register.php63
-rw-r--r--scripts/reports.php122
-rw-r--r--scripts/roadmap.php84
-rw-r--r--scripts/toplevel.php103
-rw-r--r--scripts/user.php43
-rw-r--r--securimage.php6
-rw-r--r--setup/cleanupaftersetup.php7
-rw-r--r--setup/composerit.php68
-rwxr-xr-xsetup/composerit.pl24
-rw-r--r--setup/composerit2.php78
-rwxr-xr-xsetup/composerit2.pl29
-rw-r--r--setup/composertest.php68
-rw-r--r--setup/exportdb.php27
-rw-r--r--setup/images/exclamation.pngbin0 -> 1917 bytes
-rw-r--r--setup/images/title.pngbin0 -> 13303 bytes
-rw-r--r--setup/index.php1226
-rw-r--r--setup/lang/de.php22
-rw-r--r--setup/lang/en.php101
-rw-r--r--setup/lang/es.php100
-rw-r--r--setup/lang/fr.php14
-rw-r--r--setup/lang/it.php100
-rw-r--r--setup/lang/zh_cn.php46
-rw-r--r--setup/styles/setup.css177
-rw-r--r--setup/styles/theme.css994
-rw-r--r--setup/templates/administration.tpl79
-rw-r--r--setup/templates/complete_install.tpl50
-rw-r--r--setup/templates/database.tpl47
-rw-r--r--setup/templates/pre_install.tpl124
-rw-r--r--setup/templates/structure.tpl56
-rw-r--r--setup/templates/upgrade.tpl76
-rw-r--r--setup/upgrade.php643
-rw-r--r--setup/upgrade/0.9.9.2/flyspray-install.xml1049
-rw-r--r--setup/upgrade/0.9.9.2/flyspray.conf.php33
-rw-r--r--setup/upgrade/0.9.9.2/lowercase_emails.php6
-rw-r--r--setup/upgrade/0.9.9.2/update_users.xml70
-rw-r--r--setup/upgrade/0.9.9.2/upgrade.info34
-rw-r--r--setup/upgrade/0.9.9.4/flyspray-install.xml1055
-rw-r--r--setup/upgrade/0.9.9.4/flyspray.conf.php33
-rw-r--r--setup/upgrade/0.9.9.4/upgrade.info32
-rw-r--r--setup/upgrade/0.9.9.4/upgrade.xml199
-rw-r--r--setup/upgrade/0.9.9.5/flyspray-install.xml1058
-rw-r--r--setup/upgrade/0.9.9.5/flyspray.conf.php33
-rw-r--r--setup/upgrade/0.9.9.5/upgrade.info34
-rw-r--r--setup/upgrade/0.9.9.5/upgrade.xml41
-rw-r--r--setup/upgrade/0.9.9.7/flyspray-install.xml1059
-rw-r--r--setup/upgrade/0.9.9.7/flyspray.conf.php30
-rw-r--r--setup/upgrade/0.9.9.7/upgrade.info35
-rw-r--r--setup/upgrade/0.9.9/add_data.php34
-rw-r--r--setup/upgrade/0.9.9/add_duplicates.php41
-rw-r--r--setup/upgrade/0.9.9/add_searches.php22
-rw-r--r--setup/upgrade/0.9.9/clean_unique.php67
-rw-r--r--setup/upgrade/0.9.9/convert_categories.php43
-rw-r--r--setup/upgrade/0.9.9/convert_private.php26
-rw-r--r--setup/upgrade/0.9.9/flyspray-begin.xml748
-rw-r--r--setup/upgrade/0.9.9/flyspray-final.xml976
-rw-r--r--setup/upgrade/0.9.9/flyspray-install.xml1044
-rw-r--r--setup/upgrade/0.9.9/flyspray.conf.php33
-rw-r--r--setup/upgrade/0.9.9/rename_columns.php14
-rw-r--r--setup/upgrade/0.9.9/upgrade.info44
-rw-r--r--setup/upgrade/0.9.9/upgrade_assignments.php31
-rw-r--r--setup/upgrade/1.0/datadict-postgres.inc.php606
-rw-r--r--setup/upgrade/1.0/flyspray-install.xml1373
-rw-r--r--setup/upgrade/1.0/flyspray.conf.php48
-rw-r--r--setup/upgrade/1.0/upgrade.info70
-rw-r--r--setup/upgrade/1.0/upgrade.xml902
-rw-r--r--setup/upgrade/1.0/varchartotext.php17
-rw-r--r--tests/bootstrap.php19
-rw-r--r--tests/createTestData.php702
-rw-r--r--tests/flysprayTest.php46
-rw-r--r--themes/.htaccess4
-rw-r--r--themes/CleanFS/README.md19
-rw-r--r--themes/CleanFS/_tags.css19
-rw-r--r--themes/CleanFS/archnavbar.css29
-rw-r--r--themes/CleanFS/asc.pngbin0 -> 162 bytes
-rw-r--r--themes/CleanFS/button_cancel.pngbin0 -> 248 bytes
-rw-r--r--themes/CleanFS/calendar.css251
-rw-r--r--themes/CleanFS/comment.pngbin0 -> 353 bytes
-rw-r--r--themes/CleanFS/custom_example.css60
-rw-r--r--themes/CleanFS/desc.pngbin0 -> 159 bytes
-rw-r--r--themes/CleanFS/down.pngbin0 -> 249 bytes
-rw-r--r--themes/CleanFS/edit_add.pngbin0 -> 152 bytes
-rw-r--r--themes/CleanFS/edit_remove.pngbin0 -> 117 bytes
-rw-r--r--themes/CleanFS/font-awesome.css2090
-rw-r--r--themes/CleanFS/font-awesome.min.css4
-rw-r--r--themes/CleanFS/fonts/FontAwesome.otfbin0 -> 109688 bytes
-rw-r--r--themes/CleanFS/fonts/fontawesome-webfont.eotbin0 -> 70807 bytes
-rw-r--r--themes/CleanFS/fonts/fontawesome-webfont.svg655
-rw-r--r--themes/CleanFS/fonts/fontawesome-webfont.ttfbin0 -> 142072 bytes
-rw-r--r--themes/CleanFS/fonts/fontawesome-webfont.woffbin0 -> 83588 bytes
-rw-r--r--themes/CleanFS/fonts/fontawesome-webfont.woff2bin0 -> 66624 bytes
-rw-r--r--themes/CleanFS/fonts/octicons/LICENSE.txt9
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.css236
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.eotbin0 -> 31908 bytes
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.less235
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.svg200
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.ttfbin0 -> 31740 bytes
-rw-r--r--themes/CleanFS/fonts/octicons/octicons.woffbin0 -> 17832 bytes
-rw-r--r--themes/CleanFS/fonts/octicons/sprockets-octicons.scss232
-rw-r--r--themes/CleanFS/geshi.css16
-rw-r--r--themes/CleanFS/img/black/calendar_alt_fill_16x16.pngbin0 -> 201 bytes
-rw-r--r--themes/CleanFS/img/black/comment_stroke_16x14.pngbin0 -> 254 bytes
-rw-r--r--themes/CleanFS/img/black/loop_alt3_12x9.pngbin0 -> 238 bytes
-rw-r--r--themes/CleanFS/img/caret.gifbin0 -> 61 bytes
-rw-r--r--themes/CleanFS/img/gray/blocking_13x12.pngbin0 -> 367 bytes
-rw-r--r--themes/CleanFS/img/gray/calendar_alt_stroke_12x12.pngbin0 -> 239 bytes
-rw-r--r--themes/CleanFS/img/gray/cog_alt_12x12.pngbin0 -> 259 bytes
-rw-r--r--themes/CleanFS/img/gray/comment_stroke_16x14.pngbin0 -> 296 bytes
-rw-r--r--themes/CleanFS/img/gray/compass_12x12.pngbin0 -> 284 bytes
-rw-r--r--themes/CleanFS/img/gray/dependent_13x12.pngbin0 -> 289 bytes
-rw-r--r--themes/CleanFS/img/gray/document_alt_stroke_9x12.pngbin0 -> 204 bytes
-rw-r--r--themes/CleanFS/img/gray/folder_stroke_12x12.pngbin0 -> 209 bytes
-rw-r--r--themes/CleanFS/img/gray/list_12x11.pngbin0 -> 139 bytes
-rw-r--r--themes/CleanFS/img/gray/pin_24x24.pngbin0 -> 506 bytes
-rw-r--r--themes/CleanFS/img/green/check_24x20.pngbin0 -> 293 bytes
-rw-r--r--themes/CleanFS/img/red/x_alt_24x24.pngbin0 -> 404 bytes
-rw-r--r--themes/CleanFS/img/white/calendar_alt_stroke_12x12.pngbin0 -> 239 bytes
-rw-r--r--themes/CleanFS/img/white/cog_alt_12x12.pngbin0 -> 215 bytes
-rw-r--r--themes/CleanFS/img/white/compass_12x12.pngbin0 -> 223 bytes
-rw-r--r--themes/CleanFS/img/white/document_alt_stroke_9x12.pngbin0 -> 184 bytes
-rw-r--r--themes/CleanFS/img/white/folder_stroke_12x12.pngbin0 -> 204 bytes
-rw-r--r--themes/CleanFS/img/white/list_12x11.pngbin0 -> 133 bytes
-rw-r--r--themes/CleanFS/index.html7
-rw-r--r--themes/CleanFS/kaboodleloop.pngbin0 -> 238 bytes
-rw-r--r--themes/CleanFS/left.pngbin0 -> 208 bytes
-rw-r--r--themes/CleanFS/mime/application.pngbin0 -> 960 bytes
-rw-r--r--themes/CleanFS/mime/application/octet-stream.pngbin0 -> 848 bytes
-rw-r--r--themes/CleanFS/mime/application/pdf.pngbin0 -> 857 bytes
-rw-r--r--themes/CleanFS/mime/application/x-gzip.pngbin0 -> 1078 bytes
-rw-r--r--themes/CleanFS/mime/audio.pngbin0 -> 979 bytes
-rw-r--r--themes/CleanFS/mime/image.pngbin0 -> 839 bytes
-rw-r--r--themes/CleanFS/mime/text.pngbin0 -> 734 bytes
-rw-r--r--themes/CleanFS/mime/text/html.pngbin0 -> 951 bytes
-rw-r--r--themes/CleanFS/mime/video.pngbin0 -> 710 bytes
-rw-r--r--themes/CleanFS/oldwebkitsiblingfix.css9
-rw-r--r--themes/CleanFS/reset.css34
-rw-r--r--themes/CleanFS/right.pngbin0 -> 221 bytes
-rw-r--r--themes/CleanFS/templates/admin.cat.tpl4
-rw-r--r--themes/CleanFS/templates/admin.checks.tpl99
-rw-r--r--themes/CleanFS/templates/admin.editallusers.tpl4
-rw-r--r--themes/CleanFS/templates/admin.editgroup.tpl4
-rw-r--r--themes/CleanFS/templates/admin.groups.tpl147
-rw-r--r--themes/CleanFS/templates/admin.menu.tpl43
-rw-r--r--themes/CleanFS/templates/admin.newgroup.tpl7
-rw-r--r--themes/CleanFS/templates/admin.newproject.tpl58
-rw-r--r--themes/CleanFS/templates/admin.newuser.tpl7
-rw-r--r--themes/CleanFS/templates/admin.newuserbulk.tpl7
-rw-r--r--themes/CleanFS/templates/admin.os.tpl9
-rw-r--r--themes/CleanFS/templates/admin.prefs.tpl529
-rw-r--r--themes/CleanFS/templates/admin.resolution.tpl8
-rw-r--r--themes/CleanFS/templates/admin.status.tpl9
-rw-r--r--themes/CleanFS/templates/admin.tag.tpl10
-rw-r--r--themes/CleanFS/templates/admin.tasktype.tpl8
-rw-r--r--themes/CleanFS/templates/admin.translation.tpl1
-rw-r--r--themes/CleanFS/templates/admin.userrequest.tpl50
-rw-r--r--themes/CleanFS/templates/admin.users.tpl6
-rw-r--r--themes/CleanFS/templates/admin.version.tpl8
-rw-r--r--themes/CleanFS/templates/common.attachments.tpl50
-rw-r--r--themes/CleanFS/templates/common.cat.tpl157
-rw-r--r--themes/CleanFS/templates/common.datepicker.tpl9
-rw-r--r--themes/CleanFS/templates/common.dualselect.tpl15
-rw-r--r--themes/CleanFS/templates/common.editallusers.tpl137
-rw-r--r--themes/CleanFS/templates/common.editattachments.tpl46
-rw-r--r--themes/CleanFS/templates/common.editgroup.tpl233
-rw-r--r--themes/CleanFS/templates/common.editlinks.tpl15
-rw-r--r--themes/CleanFS/templates/common.links.tpl10
-rw-r--r--themes/CleanFS/templates/common.list.tpl226
-rw-r--r--themes/CleanFS/templates/common.multiuserselect.tpl49
-rw-r--r--themes/CleanFS/templates/common.newgroup.tpl169
-rw-r--r--themes/CleanFS/templates/common.newuser.tpl120
-rw-r--r--themes/CleanFS/templates/common.newuserbulk.tpl78
-rw-r--r--themes/CleanFS/templates/common.profile.tpl152
-rw-r--r--themes/CleanFS/templates/common.userselect.tpl6
-rw-r--r--themes/CleanFS/templates/depends.tpl107
-rw-r--r--themes/CleanFS/templates/details.edit.tpl255
-rw-r--r--themes/CleanFS/templates/details.tabs.comment.tpl118
-rw-r--r--themes/CleanFS/templates/details.tabs.efforttracking.tpl52
-rw-r--r--themes/CleanFS/templates/details.tabs.history.callback.tpl32
-rw-r--r--themes/CleanFS/templates/details.tabs.history.tpl3
-rw-r--r--themes/CleanFS/templates/details.tabs.notifs.tpl30
-rw-r--r--themes/CleanFS/templates/details.tabs.related.tpl65
-rw-r--r--themes/CleanFS/templates/details.tabs.remind.tpl76
-rw-r--r--themes/CleanFS/templates/details.tabs.tpl39
-rw-r--r--themes/CleanFS/templates/details.view.tpl882
-rw-r--r--themes/CleanFS/templates/editcomment.tpl83
-rw-r--r--themes/CleanFS/templates/feed.atom.tpl73
-rw-r--r--themes/CleanFS/templates/feed.rss1.tpl70
-rw-r--r--themes/CleanFS/templates/feed.rss2.tpl65
-rw-r--r--themes/CleanFS/templates/footer.tpl10
-rw-r--r--themes/CleanFS/templates/header.tpl131
-rw-r--r--themes/CleanFS/templates/index.tpl623
-rw-r--r--themes/CleanFS/templates/links.searches.tpl15
-rw-r--r--themes/CleanFS/templates/links.tpl148
-rw-r--r--themes/CleanFS/templates/loginbox.tpl45
-rw-r--r--themes/CleanFS/templates/lostpw.step1.tpl11
-rw-r--r--themes/CleanFS/templates/lostpw.step2.tpl23
-rw-r--r--themes/CleanFS/templates/myprofile.tpl40
-rw-r--r--themes/CleanFS/templates/newmultitasks.tpl261
-rw-r--r--themes/CleanFS/templates/newtask.tpl268
-rw-r--r--themes/CleanFS/templates/permicons.tpl30
-rw-r--r--themes/CleanFS/templates/pm.cat.tpl5
-rw-r--r--themes/CleanFS/templates/pm.editgroup.tpl4
-rw-r--r--themes/CleanFS/templates/pm.groups.tpl138
-rw-r--r--themes/CleanFS/templates/pm.menu.tpl34
-rw-r--r--themes/CleanFS/templates/pm.newgroup.tpl7
-rw-r--r--themes/CleanFS/templates/pm.os.tpl12
-rw-r--r--themes/CleanFS/templates/pm.pendingreq.tpl79
-rw-r--r--themes/CleanFS/templates/pm.prefs.tpl370
-rw-r--r--themes/CleanFS/templates/pm.resolution.tpl12
-rw-r--r--themes/CleanFS/templates/pm.status.tpl12
-rw-r--r--themes/CleanFS/templates/pm.tag.tpl14
-rw-r--r--themes/CleanFS/templates/pm.tasktype.tpl12
-rw-r--r--themes/CleanFS/templates/pm.version.tpl12
-rw-r--r--themes/CleanFS/templates/profile.tpl70
-rw-r--r--themes/CleanFS/templates/register.magic.tpl31
-rw-r--r--themes/CleanFS/templates/register.no-magic.tpl78
-rw-r--r--themes/CleanFS/templates/register.oauth.tpl15
-rw-r--r--themes/CleanFS/templates/register.ok.tpl4
-rw-r--r--themes/CleanFS/templates/reports.tpl120
-rw-r--r--themes/CleanFS/templates/roadmap.text.tpl51
-rw-r--r--themes/CleanFS/templates/roadmap.tpl116
-rw-r--r--themes/CleanFS/templates/shortcuts.tpl31
-rw-r--r--themes/CleanFS/templates/toplevel.tpl169
-rw-r--r--themes/CleanFS/theme.css1511
-rw-r--r--themes/CleanFS/theme_print.css51
-rw-r--r--themes/CleanFS/typography.css87
-rw-r--r--themes/CleanFS/up.pngbin0 -> 268 bytes
-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
2465 files changed, 395484 insertions, 0 deletions
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..1f00039
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,142 @@
+#############################################################################
+##### Flyspray .htaccess Config. To be used with Apache Server ###
+##### Check for Mod-Rewrite module ############################################
+#### rename this file to .htaccess to use these features
+###############################################################################
+
+AddDefaultCharset utf-8
+
+DirectoryIndex index.php index.html
+
+# Mod security conflicts with flyspray and you do not need it here
+
+#for mod_security 1
+<IfModule mod_security.c>
+ SecFilterEngine Off
+</IfModule>
+#modsecurity 2
+<IfModule security2_module>
+ SecRuleEngine Off
+</IfModule>
+
+# php5 as apache 1/2 module
+<IfModule mod_php5.c>
+php_flag register_globals Off
+php_flag magic_quotes_gpc Off
+php_flag session.use_trans_sid 0
+php_flag session.auto_start 0
+php_flag short_open_tag off
+php_flag register_argc_argv Off
+php_flag register_long_arrays Off
+#not for now, flyspray has it's own filters.
+php_value filter.default "unsafe_raw"
+php_flag always_populate_raw_post_data Off
+</IfModule>
+
+# php4 as **apache 2** module
+<IfModule sapi_apache2.c>
+php_flag register_globals Off
+php_flag magic_quotes_gpc Off
+php_flag session.use_trans_sid 0
+php_flag session.auto_start 0
+php_flag short_open_tag off
+php_flag register_argc_argv Off
+php_value filter.default "unsafe_raw"
+php_flag always_populate_raw_post_data Off
+</IfModule>
+
+# We only use POST GET and HEAD (implicit)
+<LimitExcept POST GET>
+ Order Deny,Allow
+ Deny From All
+</LimitExcept>
+
+# hide possible development files
+<Files ~ "^composer\.(json|lock|phar)$">
+ order deny,allow
+ deny from all
+</Files>
+
+# Some hostings have autocorrection of minor misspelled URLs enabled.
+# We do not want that for Flyspray as it could lead to subtile unwanted behavior.
+<IfModule mod_speling.c>
+ CheckSpelling Off
+</IfModule>
+
+# probably mod_negotiation plays foul with mod_rewrite on this two rules:
+# RewriteRule ^index$ index.php?do=index [L,QSA]
+# RewriteRule ^index/proj([0-9]+)$ index.php?do=index&project=$1 [L,QSA]
+# So try to deactivate it by -MultiViews (which is sadly not possible in every web hosting environment - gives error500)
+#Options -MultiViews
+
+<IfModule mod_rewrite.c>
+RewriteEngine on
+
+# Set RewriteBase to urlpath where Flyspray is accessible.
+RewriteBase /
+# Use this if Flyspray is accessible from a subdirectory of your domain.
+#RewriteBase /flyspray/
+
+# You can force TLS/SSL by uncommenting this 2 lines if you have a valid certificate for your domain
+#RewriteCond %{HTTPS} !=on
+#RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
+# In the case of TLS/SSL available, set also session.cookie_secure=1
+# so that the session cookie can't be sniffed by Man-in-the-Middle or same network.
+# Depends on your server setup/webhosting if done in php.ini, .user.ini or somehow/somewhere else.
+# Ask your web hoster if you don't know.
+
+# used for enabling url_rewriting check in admin prefs
+RewriteRule . - [E=HTTP_MOD_REWRITE:On]
+
+RewriteRule ^.*\?do=admin&area=prefs$ index.php?do=admin&area=prefs [L,QSA]
+
+RewriteRule ^([0-9]+)$ index.php?do=details&task_id=$1 [L,QSA]
+RewriteRule ^task/([0-9]+)$ index.php?do=details&task_id=$1 [L,QSA]
+RewriteRule ^task/([0-9]+)comment([0-9]+)$ index.php?do=details&task_id=$1comment$2 [L,QSA]
+RewriteRule ^task/([0-9]+)/depends$ index.php?do=depends&task_id=$1 [L,QSA]
+RewriteRule ^task/([0-9]+)/depends&prune=([0-9]+)$ index.php?do=depends&task_id=$1&prune=$2 [L,QSA]
+RewriteRule ^task/([0-9]+)/edit$ index.php?do=details&task_id=$1&edit=yep [L,QSA]
+
+RewriteRule ^newtask$ index.php?do=newtask [L,QSA]
+RewriteRule ^newtask/proj([0-9]+)$ index.php?do=newtask&project=$1 [L,QSA]
+RewriteRule ^newtask/proj([0-9]+)/supertask([0-9]+)$ index.php?do=newtask&project=$1&supertask=$2 [L,QSA]
+
+RewriteRule ^reports/proj([0-9]+)$ index.php?do=reports&project=$1 [L,QSA]
+RewriteRule ^myprofile$ index.php?do=myprofile [L,QSA]
+RewriteRule ^user/([0-9]+)$ index.php?do=user&id=$1 [L,QSA]
+RewriteRule ^logout$ index.php?do=authenticate&logout=1 [L,QSA]
+
+RewriteRule ^admin/([a-zA-Z]+)$ index.php?do=admin&area=$1 [L,QSA]
+RewriteRule ^pm/proj([0-9]+)/([a-zA-Z]+)$ index.php?do=pm&project=$1&area=$2 [L,QSA]
+
+RewriteRule ^admin/editgroup/([0-9]+)$ index.php?do=admin&area=editgroup&id=$1 [L,QSA]
+RewriteRule ^pm/editgroup/([0-9]+)$ index.php?do=pm&area=editgroup&id=$1 [L,QSA]
+RewriteRule ^edituser/([0-9]+)$ index.php?do=admin&area=users&user_id=$1 [L,QSA]
+RewriteRule ^register$ index.php?do=register [L,QSA]
+RewriteRule ^lostpw$ index.php?do=lostpw [L,QSA]
+
+# gantt and team are experimental pagetypes not within FS1.0
+RewriteRule ^gantt/proj([0-9]+)$ index.php?do=gantt&project=$1 [L,QSA]
+RewriteRule ^team$ index.php?do=team [L,QSA]
+RewriteRule ^team/proj([0-9]+)$ index.php?do=team&project=$1 [L,QSA]
+
+RewriteRule ^roadmap$ index.php?do=roadmap [L,QSA]
+RewriteRule ^roadmap/proj([0-9]+)$ index.php?do=roadmap&project=$1 [L,QSA]
+RewriteRule ^toplevel$ index.php?do=toplevel [L,QSA]
+RewriteRule ^toplevel/proj([0-9]+)$ index.php?do=toplevel&project=$1 [L,QSA]
+RewriteRule ^tasklist$ index.php?do=tasklist [L,QSA]
+RewriteRule ^tasklist/proj([0-9]+)$ index.php?do=tasklist&project=$1 [L,QSA]
+
+# homepage (can be tasklist, toplevel, or roadmap; maybe gantt in future too)
+RewriteRule ^index$ index.php?do=index [L,QSA]
+RewriteRule ^index/proj([0-9]+)$ index.php?do=index&project=$1 [L,QSA]
+RewriteRule ^newmultitasks/proj([0-9]+)$ index.php?do=newmultitasks&project=$1 [L,QSA]
+
+RewriteRule ^proj([0-9]+)/dev([0-9]+)$ index.php?project=$1&do=index&dev=$2 [L,QSA]
+RewriteRule ^proj([0-9]+)$ index.php?project=$1 [L,QSA]
+
+</IfModule>
+
+<IfModule mod_env.c>
+#SetEnv HTTP_HTACCESS_ENABLED on
+</IfModule>
diff --git a/archnav32.png b/archnav32.png
new file mode 100644
index 0000000..a7fd0d8
--- /dev/null
+++ b/archnav32.png
Binary files differ
diff --git a/attachments/.htaccess b/attachments/.htaccess
new file mode 100644
index 0000000..31f17f9
--- /dev/null
+++ b/attachments/.htaccess
@@ -0,0 +1 @@
+Deny From All
diff --git a/attachments/index.html b/attachments/index.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/attachments/index.html
diff --git a/avatars/1808f8a929.jpg b/avatars/1808f8a929.jpg
new file mode 100644
index 0000000..87f2174
--- /dev/null
+++ b/avatars/1808f8a929.jpg
Binary files differ
diff --git a/avatars/index.html b/avatars/index.html
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/avatars/index.html
@@ -0,0 +1 @@
+
diff --git a/cache/index.html b/cache/index.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cache/index.html
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..f801f1d
--- /dev/null
+++ b/favicon.ico
Binary files differ
diff --git a/feed.php b/feed.php
new file mode 100644
index 0000000..9819050
--- /dev/null
+++ b/feed.php
@@ -0,0 +1,160 @@
+<?php
+// We can't include this script as part of index.php?do= etc,
+// as that would introduce html code into it. HTML != Valid XML
+// So, include the headerfile to set up database access etc
+
+define('IN_FS', true);
+define('IN_FEED', true);
+
+require_once(dirname(__FILE__).'/header.php');
+$page = new FSTpl();
+
+// Set up the basic XML head
+header('Content-Type: application/xml; charset=utf-8');
+
+$max_items = (Req::num('num', 10) == 10) ? 10 : 20;
+$sql_project = ' 1=1 ';
+if ($proj->id) {
+ $sql_project = sprintf(' p.project_id = %d', $proj->id);
+}
+
+$feed_type = Req::val('feed_type', 'rss2');
+if ($feed_type != 'rss1' && $feed_type != 'rss2') {
+ $feed_type = 'atom';
+}
+
+switch (Req::val('topic')) {
+ case 'clo': $orderby = 'date_closed'; $closed = 't.is_closed = 1 ';
+ $topic = 1;
+ $title = 'Recently closed tasks';
+ break;
+
+ case 'edit':$orderby = 'last_edited_time'; $closed = '1=1';
+ $topic = 2;
+ $title = 'Recently edited tasks';
+ break;
+
+ case 'open':
+ $orderby = 'date_opened'; $closed = 't.is_closed = 0 ';
+ $topic = 4;
+ $title = 'Latest open tasks';
+ break;
+
+ default: $orderby = 'date_opened'; $closed = '1=1';
+ $topic = 3;
+ $title = 'Recently opened tasks';
+ break;
+}
+
+$filename = md5(sprintf('%s-%s-%d-%d', $feed_type, $orderby, $proj->id, $max_items) . $conf['general']['cookiesalt']);
+$cachefile = sprintf('%s/%s', FS_CACHE_DIR, $filename);
+
+// Get the time when a task has been changed last
+$most_recent = 0;
+if($proj->prefs['others_view']){
+ $sql = $db->query("SELECT t.date_opened, t.date_closed, t.last_edited_time, t.item_summary
+ FROM {tasks} t
+ INNER JOIN {projects} p ON t.project_id = p.project_id AND p.project_is_active = '1'
+ WHERE $closed
+ AND $sql_project
+ AND t.mark_private <> '1'
+ AND p.others_view = '1'
+ ORDER BY $orderby DESC",
+ false,
+ $max_items
+ );
+ while ($row = $db->fetchRow($sql)) {
+ $most_recent = max($most_recent, $row['date_opened'], $row['date_closed'], $row['last_edited_time']);
+ }
+}
+
+if ($fs->prefs['cache_feeds']) {
+ if ($fs->prefs['cache_feeds'] == '1') {
+ if (!is_link($cachefile) && is_file($cachefile) && $most_recent <= filemtime($cachefile)) {
+ readfile($cachefile);
+ exit;
+ }
+ }
+ else {
+ $sql = $db->query("SELECT content FROM {cache} p
+ WHERE type = ?
+ AND topic = ?
+ AND $sql_project
+ AND max_items = ?
+ AND last_updated >= ?",
+ array($feed_type, $topic, $max_items, $most_recent)
+ );
+ if ($content = $db->fetchOne($sql)) {
+ echo $content;
+ exit;
+ }
+ }
+}
+
+/* build a new feed if cache didn't work */
+if($proj->prefs['others_view']){
+ $sql = $db->query("SELECT t.task_id, t.item_summary, t.detailed_desc, t.date_opened, t.date_closed, t.last_edited_time, t.opened_by,
+ COALESCE(u.real_name, 'anonymous') AS real_name,
+ COALESCE(u.email_address, t.anon_email) AS email_address
+ FROM {tasks} t
+ LEFT JOIN {users} u ON t.opened_by = u.user_id
+ INNER JOIN {projects} p ON t.project_id = p.project_id AND p.project_is_active = '1'
+ WHERE $closed
+ AND $sql_project
+ AND t.mark_private <> '1'
+ AND p.others_view = '1'
+ ORDER BY $orderby DESC",
+ false,
+ $max_items
+ );
+ $task_details = $db->fetchAllArray($sql);
+} else{
+ $task_details = array();
+}
+
+if($proj->prefs['others_view'] || $proj->prefs['others_viewroadmap']){
+ $feed_description = $proj->prefs['feed_description'] ? $proj->prefs['feed_description'] : $fs->prefs['page_title'] . $proj->prefs['project_title'].': '.$title;
+} else{
+ $feed_description = $fs->prefs['page_title']; # do not show info about the project
+}
+
+$feed_image = false;
+if ($proj->prefs['feed_img_url'] && !strncmp($proj->prefs['feed_img_url'], 'http://', 7)) {
+ $feed_image = $proj->prefs['feed_img_url'];
+}
+
+$page->uses('most_recent', 'feed_description', 'feed_image', 'task_details');
+$content = $page->fetch('feed.'.$feed_type.'.tpl');
+
+// cache feed
+if ($fs->prefs['cache_feeds']) {
+ if ($fs->prefs['cache_feeds'] == '1') {
+ // Remove old cached files
+ if(!is_link($cachefile) && ($handle = @fopen($cachefile, 'w+b'))) {
+ if (flock($handle, LOCK_EX)) {
+ fwrite($handle, $content);
+ flock($handle, LOCK_UN);
+ }
+ fclose($handle);
+ chmod($cachefile, 0600);
+ }
+ }
+ else {
+ /**
+ * See http://phplens.com/adodb/reference.functions.replace.html
+ *
+ * " Try to update a record, and if the record is not found,
+ * an insert statement is generated and executed "
+ */
+
+ $fields = array('content'=> $content , 'type'=> $feed_type , 'topic'=> $topic ,
+ 'project_id'=> $proj->id ,'max_items'=> $max_items , 'last_updated'=> time() );
+
+ $keys = array('type','topic','project_id','max_items');
+
+ $db->replace('{cache}', $fields, $keys) or die ('error updating the database cache');
+ }
+}
+
+echo $content;
+?>
diff --git a/flyspray.conf.php b/flyspray.conf.php
new file mode 100644
index 0000000..95bf3cc
--- /dev/null
+++ b/flyspray.conf.php
@@ -0,0 +1,48 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+ ; This is the Flysplay configuration file. It contains the basic settings
+ ; needed for Flyspray to operate. All other preferences are stored in the
+ ; database itself and are managed directly within the Flyspray admin interface.
+ ; You should consider putting this file somewhere that isn't accessible using
+ ; a web browser, and editing header.php to point to wherever you put this file.
+[database]
+dbtype = "mysqli" ; Type of database ("mysql", "mysqli" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "flyspray" ; The name of the database
+dbuser = "flyspray" ; The user to access the database
+dbpass = "c2Q7bGZrandlcjUy" ; The password to go with that username above
+dbprefix = "flyspray_" ; The prefix to the Flyspray tables
+
+
+[general]
+cookiesalt = "cc5c4c65de6a35d5b9cabec60a94fcb3" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "on" or "gzip"
+passwdcrypt = "" ; Available options: "" which chooses best default (coming FS1.0: using crypt/password_hash() with blowfish), "crypt" (auto salted md5), "md5", "sha1" Note: md5 and sha1 are considered insecure for hashing passwords, avoid if possible.
+dot_path = "" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)
+dot_format = "png" ; "png" or "svg"
+reminder_daemon = "1" ; Boolean. 0 = off, 1 = on (cron job), 2 = on (PHP).
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "dokuwiki" ; dokuwiki, none, or html
+update_check = "1" ; Boolean. 0=off, 1=on
+
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files
+
+
+[oauth]
+; These are only needed if you plan to use them. You can turn them on in the admin panel.
+
+
+github_secret = ""
+github_id = ""
+github_redirect = "YOURDOMAIN/index.php?do=oauth&provider=github"
+google_secret = ""
+google_id = ""
+google_redirect = "YOURDOMAIN/index.php?do=oauth&provider=google"
+facebook_secret = ""
+facebook_id = ""
+facebook_redirect = "YOURDOMAIN/index.php?do=oauth&provider=facebook"
+microsoft_secret = ""
+microsoft_id = ""
+microsoft_redirect = "YOURDOMAIN/index.php" \ No newline at end of file
diff --git a/flyspray.png b/flyspray.png
new file mode 100644
index 0000000..5a3bd06
--- /dev/null
+++ b/flyspray.png
Binary files differ
diff --git a/flyspray_small.png b/flyspray_small.png
new file mode 100644
index 0000000..a797054
--- /dev/null
+++ b/flyspray_small.png
Binary files differ
diff --git a/fonts/index.html b/fonts/index.html
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/fonts/index.html
@@ -0,0 +1 @@
+
diff --git a/header.php b/header.php
new file mode 100644
index 0000000..3e36110
--- /dev/null
+++ b/header.php
@@ -0,0 +1,75 @@
+<?php
+
+// cant easly for the time being because of globals
+require_once dirname(__FILE__) . '/includes/fix.inc.php';
+require_once dirname(__FILE__) . '/includes/class.flyspray.php';
+require_once dirname(__FILE__) . '/includes/constants.inc.php';
+require_once BASEDIR . '/includes/i18n.inc.php';
+require_once BASEDIR . '/includes/class.tpl.php';
+require_once BASEDIR . '/includes/class.csp.php';
+
+// Get the translation for the wrapper page (this page)
+setlocale(LC_ALL, str_replace('-', '_', L('locale')) . '.utf8');
+
+// make browsers back button work
+header('Expires: -1');
+header('Pragma: no-cache');
+header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
+
+if(is_readable(BASEDIR . '/vendor/autoload.php')){
+ // Use composer autoloader
+ require 'vendor/autoload.php';
+}else{
+ Flyspray::redirect('setup/composertest.php');
+ exit;
+}
+
+$csp= new ContentSecurityPolicy();
+# deny everything first, then whitelist what is required.
+$csp->add('default-src', "'none'");
+
+// If it is empty, take the user to the setup page
+if (!$conf) {
+ Flyspray::redirect('setup/index.php');
+}
+
+$db = new Database();
+$db->dbOpenFast($conf['database']);
+$fs = new Flyspray();
+
+// If version number of database and files do not match, run upgrader
+if (Flyspray::base_version($fs->version) != Flyspray::base_version($fs->prefs['fs_ver'])) {
+ Flyspray::redirect('setup/upgrade.php');
+}
+
+
+# load the correct $proj early also for checks on quickedit.php taskediting calls
+if( (BASEDIR.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR.'callbacks'.DIRECTORY_SEPARATOR.'quickedit.php' == $_SERVER['SCRIPT_FILENAME']) && Post::num('task_id')){
+ $result = $db->query('SELECT project_id FROM {tasks} WHERE task_id = ?', array(Post::num('task_id')));
+ $project_id = $db->fetchOne($result);
+}
+# Any "do" mode that accepts a task_id field should be added here.
+elseif (in_array(Req::val('do'), array('details', 'depends', 'editcomment'))) {
+ if (Req::num('task_id')) {
+ $result = $db->query('SELECT project_id FROM {tasks} WHERE task_id = ?', array(Req::num('task_id')));
+ $project_id = $db->fetchOne($result);
+ }
+}
+
+if (Req::val('do') =='pm' && Req::val('area')=='editgroup') {
+ if (Req::num('id')) {
+ $result = $db->query('SELECT project_id FROM {groups} WHERE group_id = ?', array(Req::num('id')));
+ $project_id = $db->fetchOne($result);
+ }
+}
+
+if (!isset($project_id)) {
+ $project_id = $fs->prefs['default_project'];
+ # Force default value if input format is not allowed
+ if(is_array(Req::val('project'))) {
+ Req::set('project', $fs->prefs['default_project']);
+ }
+ $project_id = Req::val('project', Req::val('project_id', $project_id));
+}
+
+$proj = new Project($project_id);
diff --git a/includes/.htaccess b/includes/.htaccess
new file mode 100644
index 0000000..31f17f9
--- /dev/null
+++ b/includes/.htaccess
@@ -0,0 +1 @@
+Deny From All
diff --git a/includes/GithubProvider.php b/includes/GithubProvider.php
new file mode 100644
index 0000000..5639383
--- /dev/null
+++ b/includes/GithubProvider.php
@@ -0,0 +1,60 @@
+<?php
+
+use League\OAuth2\Client\Provider\Github;
+use League\OAuth2\Client\Token\AccessToken as AccessToken;
+
+/**
+ * A workaround for fetching the users email address if the user does not have a
+ * public email address.
+ */
+class GithubProvider extends Github
+{
+ public function userDetails($response, AccessToken $token)
+ {
+ $user = parent::userDetails($response, $token);
+
+ // Fetch the primary email address
+ if (!$user->email) {
+ $emails = $this->fetchUserEmails($token);
+ $emails = json_decode($emails);
+ $email = null;
+
+ foreach ($emails as $email) {
+ if ($email->primary) {
+ $email = $email->email;
+ break;
+ }
+ }
+
+ $user->email = $email;
+ }
+
+ return $user;
+ }
+
+ protected function fetchUserEmails(AccessToken $token)
+ {
+ $url = "https://api.github.com/user/emails?access_token={$token}";
+
+ try {
+
+ $client = $this->getHttpClient();
+ $client->setBaseUrl($url);
+
+ if ($this->headers) {
+ $client->setDefaultOption('headers', $this->headers);
+ }
+
+ $request = $client->get()->send();
+ $response = $request->getBody();
+
+ } catch (BadResponseException $e) {
+ // @codeCoverageIgnoreStart
+ $raw_response = explode("\n", $e->getResponse());
+ throw new IDPException(end($raw_response));
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $response;
+ }
+} \ No newline at end of file
diff --git a/includes/class.backend.php b/includes/class.backend.php
new file mode 100644
index 0000000..f440db9
--- /dev/null
+++ b/includes/class.backend.php
@@ -0,0 +1,1869 @@
+<?php
+/**
+ * Flyspray
+ *
+ * Backend class
+ *
+ * This script contains reusable functions we use to modify
+ * various things in the Flyspray database tables.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
+ * @package flyspray
+ * @author Tony Collins, Florian Schmitz
+ */
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+abstract class Backend
+{
+ /**
+ * Adds the user $user_id to the notifications list of $tasks
+ * @param integer $user_id
+ * @param array $tasks
+ * @param bool $do Force execution independent of user permissions
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function add_notification($user_id, $tasks, $do = false)
+ {
+ global $db, $user;
+
+ settype($tasks, 'array');
+
+ $user_id = Flyspray::validUserId($user_id);
+
+ if (!$user_id || !count($tasks)) {
+ return false;
+ }
+
+ $sql = $db->query(' SELECT *
+ FROM {tasks}
+ WHERE ' . substr(str_repeat(' task_id = ? OR ', count($tasks)), 0, -3),
+ $tasks);
+
+ while ($row = $db->fetchRow($sql)) {
+ // -> user adds himself
+ if ($user->id == $user_id) {
+ if (!$user->can_view_task($row) && !$do) {
+ continue;
+ }
+ // -> user is added by someone else
+ } else {
+ if (!$user->perms('manage_project', $row['project_id']) && !$do) {
+ continue;
+ }
+ }
+
+ $notif = $db->query('SELECT notify_id
+ FROM {notifications}
+ WHERE task_id = ? and user_id = ?',
+ array($row['task_id'], $user_id));
+
+ if (!$db->countRows($notif)) {
+ $db->query('INSERT INTO {notifications} (task_id, user_id)
+ VALUES (?,?)', array($row['task_id'], $user_id));
+ Flyspray::logEvent($row['task_id'], 9, $user_id);
+ }
+ }
+
+ return (bool) $db->countRows($sql);
+ }
+
+
+ /**
+ * Removes a user $user_id from the notifications list of $tasks
+ * @param integer $user_id
+ * @param array $tasks
+ * @access public
+ * @return void
+ * @version 1.0
+ */
+
+ public static function remove_notification($user_id, $tasks)
+ {
+ global $db, $user;
+
+ settype($tasks, 'array');
+
+ if (!count($tasks)) {
+ return;
+ }
+
+ $sql = $db->query(' SELECT *
+ FROM {tasks}
+ WHERE ' . substr(str_repeat(' task_id = ? OR ', count($tasks)), 0, -3),
+ $tasks);
+
+ while ($row = $db->fetchRow($sql)) {
+ // -> user removes himself
+ if ($user->id == $user_id) {
+ if (!$user->can_view_task($row)) {
+ continue;
+ }
+ // -> user is removed by someone else
+ } else {
+ if (!$user->perms('manage_project', $row['project_id'])) {
+ continue;
+ }
+ }
+
+ $db->query('DELETE FROM {notifications}
+ WHERE task_id = ? AND user_id = ?',
+ array($row['task_id'], $user_id));
+ if ($db->affectedRows()) {
+ Flyspray::logEvent($row['task_id'], 10, $user_id);
+ }
+ }
+ }
+
+
+ /**
+ * Assigns one or more $tasks only to a user $user_id
+ * @param integer $user_id
+ * @param array $tasks
+ * @access public
+ * @return void
+ * @version 1.0
+ */
+ public static function assign_to_me($user_id, $tasks)
+ {
+ global $db, $notify;
+
+ $user = $GLOBALS['user'];
+ if ($user_id != $user->id) {
+ $user = new User($user_id);
+ }
+
+ settype($tasks, 'array');
+ if (!count($tasks)) {
+ return;
+ }
+
+ $sql = $db->query(' SELECT *
+ FROM {tasks}
+ WHERE ' . substr(str_repeat(' task_id = ? OR ', count($tasks)), 0, -3),
+ $tasks);
+
+ while ($row = $db->fetchRow($sql)) {
+ if (!$user->can_take_ownership($row)) {
+ continue;
+ }
+
+ $db->query('DELETE FROM {assigned}
+ WHERE task_id = ?',
+ array($row['task_id']));
+
+ $db->query('INSERT INTO {assigned}
+ (task_id, user_id)
+ VALUES (?,?)',
+ array($row['task_id'], $user->id));
+
+ if ($db->affectedRows()) {
+ $current_proj = new Project($row['project_id']);
+ Flyspray::logEvent($row['task_id'], 19, $user->id, implode(' ', Flyspray::getAssignees($row['task_id'])));
+ $notify->create(NOTIFY_OWNERSHIP, $row['task_id'], null, null, NOTIFY_BOTH, $current_proj->prefs['lang_code']);
+ }
+
+ if ($row['item_status'] == STATUS_UNCONFIRMED || $row['item_status'] == STATUS_NEW) {
+ $db->query('UPDATE {tasks} SET item_status = 3 WHERE task_id = ?', array($row['task_id']));
+ Flyspray::logEvent($row['task_id'], 3, 3, 1, 'item_status');
+ }
+ }
+ }
+
+ /**
+ * Adds a user $user_id to the assignees of one or more $tasks
+ * @param integer $user_id
+ * @param array $tasks
+ * @param bool $do Force execution independent of user permissions
+ * @access public
+ * @return void
+ * @version 1.0
+ */
+ public static function add_to_assignees($user_id, $tasks, $do = false)
+ {
+ global $db, $notify;
+
+ settype($tasks, 'array');
+
+ $user = $GLOBALS['user'];
+ if ($user_id != $user->id) {
+ $user = new User($user_id);
+ }
+
+ settype($tasks, 'array');
+ if (!count($tasks)) {
+ return;
+ }
+
+ $sql = $db->query(' SELECT *
+ FROM {tasks}
+ WHERE ' . substr(str_repeat(' task_id = ? OR ', count($tasks)), 0, -3),
+ $tasks);
+
+ while ($row = $db->fetchRow($sql)) {
+ if (!$user->can_add_to_assignees($row) && !$do) {
+ continue;
+ }
+
+ $db->replace('{assigned}', array('user_id'=> $user->id, 'task_id'=> $row['task_id']), array('user_id','task_id'));
+
+ if ($db->affectedRows()) {
+ $current_proj = new Project($row['project_id']);
+ Flyspray::logEvent($row['task_id'], 29, $user->id, implode(' ', Flyspray::getAssignees($row['task_id'])));
+ $notify->create(NOTIFY_ADDED_ASSIGNEES, $row['task_id'], null, null, NOTIFY_BOTH, $current_proj->prefs['lang_code']);
+ }
+
+ if ($row['item_status'] == STATUS_UNCONFIRMED || $row['item_status'] == STATUS_NEW) {
+ $db->query('UPDATE {tasks} SET item_status = 3 WHERE task_id = ?', array($row['task_id']));
+ Flyspray::logEvent($row['task_id'], 3, 3, 1, 'item_status');
+ }
+ }
+ }
+
+ /**
+ * Adds a vote from $user_id to the task $task_id
+ * @param integer $user_id
+ * @param integer $task_id
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function add_vote($user_id, $task_id)
+ {
+ global $db;
+
+ $user = $GLOBALS['user'];
+ if ($user_id != $user->id) {
+ $user = new User($user_id);
+ }
+
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$task) {
+ return false;
+ }
+
+ if ($user->can_vote($task) > 0) {
+
+ if($db->query("INSERT INTO {votes} (user_id, task_id, date_time)
+ VALUES (?,?,?)", array($user->id, $task_id, time()))) {
+ // TODO: Log event in a later version.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes a vote from $user_id to the task $task_id
+ * @param integer $user_id
+ * @param integer $task_id
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function remove_vote($user_id, $task_id)
+ {
+ global $db;
+
+ $user = $GLOBALS['user'];
+ if ($user_id != $user->id) {
+ $user = new User($user_id);
+ }
+
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$task) {
+ return false;
+ }
+
+ if ($user->can_vote($task) == -2) {
+
+ if($db->query("DELETE FROM {votes} WHERE user_id = ? and task_id = ?",
+ array($user->id, $task_id))) {
+ // TODO: Log event in a later version.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adds a comment to $task
+ * @param array $task
+ * @param string $comment_text
+ * @param integer $time for synchronisation with other functions
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function add_comment($task, $comment_text, $time = null)
+ {
+ global $conf, $db, $user, $notify, $proj;
+
+ if (!($user->perms('add_comments', $task['project_id']) && (!$task['is_closed'] || $user->perms('comment_closed', $task['project_id'])))) {
+ return false;
+ }
+
+ if($conf['general']['syntax_plugin'] != 'dokuwiki'){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $comment_text = $purifier->purify($comment_text);
+ }
+
+ if (!is_string($comment_text) || !strlen($comment_text)) {
+ return false;
+ }
+
+ $time = !is_numeric($time) ? time() : $time ;
+
+ $db->query('INSERT INTO {comments}
+ (task_id, date_added, last_edited_time, user_id, comment_text)
+ VALUES ( ?, ?, ?, ?, ? )',
+ array($task['task_id'], $time, $time, $user->id, $comment_text));
+ $cid = $db->Insert_ID();
+ Backend::upload_links($task['task_id'], $cid);
+ Flyspray::logEvent($task['task_id'], 4, $cid);
+
+ if (Backend::upload_files($task['task_id'], $cid)) {
+ $notify->create(NOTIFY_COMMENT_ADDED, $task['task_id'], 'files', null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ } else {
+ $notify->create(NOTIFY_COMMENT_ADDED, $task['task_id'], null, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+
+ return true;
+ }
+
+ /**
+ * Upload files for a comment or a task
+ * @param integer $task_id
+ * @param integer $comment_id if it is 0, the files will be attached to the task itself
+ * @param string $source name of the file input
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function upload_files($task_id, $comment_id = 0, $source = 'userfile')
+ {
+ global $db, $notify, $conf, $user;
+
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$user->perms('create_attachments', $task['project_id'])) {
+ return false;
+ }
+
+ $res = false;
+
+ if (!isset($_FILES[$source]['error'])) {
+ return false;
+ }
+
+ foreach ($_FILES[$source]['error'] as $key => $error) {
+ if ($error != UPLOAD_ERR_OK) {
+ continue;
+ }
+
+
+ $fname = substr($task_id . '_' . md5(uniqid(mt_rand(), true)), 0, 30);
+ $path = BASEDIR .'/attachments/'. $fname ;
+
+ $tmp_name = $_FILES[$source]['tmp_name'][$key];
+
+ // Then move the uploaded file and remove exe permissions
+ if(!@move_uploaded_file($tmp_name, $path)) {
+ //upload failed. continue
+ continue;
+ }
+
+ @chmod($path, 0644);
+ $res = true;
+
+ // Use a different MIME type
+ $fileparts = explode( '.', $_FILES[$source]['name'][$key]);
+ $extension = end($fileparts);
+ if (isset($conf['attachments'][$extension])) {
+ $_FILES[$source]['type'][$key] = $conf['attachments'][$extension];
+ //actually, try really hard to get the real filetype, not what the browser reports.
+ } elseif($type = Flyspray::check_mime_type($path)) {
+ $_FILES[$source]['type'][$key] = $type;
+ }// we can try even more, however, far too much code is needed.
+
+ $db->query("INSERT INTO {attachments}
+ ( task_id, comment_id, file_name,
+ file_type, file_size, orig_name,
+ added_by, date_added)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ array($task_id, $comment_id, $fname,
+ $_FILES[$source]['type'][$key],
+ $_FILES[$source]['size'][$key],
+ $_FILES[$source]['name'][$key],
+ $user->id, time()));
+ $attid = $db->insert_ID();
+ Flyspray::logEvent($task_id, 7, $attid, $_FILES[$source]['name'][$key]);
+ }
+
+ return $res;
+ }
+
+ public static function upload_links($task_id, $comment_id = 0, $source = 'userlink')
+ {
+ global $db, $user;
+
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$user->perms('create_attachments', $task['project_id'])) {
+ return false;
+ }
+
+ if (!isset($_POST[$source])) {
+ return false;
+ }
+
+ $res = false;
+ foreach($_POST[$source] as $text) {
+ $text = filter_var($text, FILTER_SANITIZE_URL);
+
+ if( preg_match( '/^\s*(javascript:|data:)/', $text)){
+ continue;
+ }
+
+ if(empty($text)) {
+ continue;
+ }
+
+ $res = true;
+
+ // Insert into database
+ $db->query("INSERT INTO {links} (task_id, comment_id, url, added_by, date_added) VALUES (?, ?, ?, ?, ?)",
+ array($task_id, $comment_id, $text, $user->id, time()));
+ // TODO: Log event in a later version.
+ }
+
+ return $res;
+ }
+
+ /**
+ * Delete one or more attachments of a task or comment
+ * @param array $attachments
+ * @access public
+ * @return void
+ * @version 1.0
+ */
+ public static function delete_files($attachments)
+ {
+ global $db, $user;
+
+ settype($attachments, 'array');
+ if (!count($attachments)) {
+ return;
+ }
+
+ $sql = $db->query(' SELECT t.*, a.*
+ FROM {attachments} a
+ LEFT JOIN {tasks} t ON t.task_id = a.task_id
+ WHERE ' . substr(str_repeat(' attachment_id = ? OR ', count($attachments)), 0, -3),
+ $attachments);
+
+ while ($task = $db->fetchRow($sql)) {
+ if (!$user->perms('delete_attachments', $task['project_id'])) {
+ continue;
+ }
+
+ $db->query('DELETE FROM {attachments} WHERE attachment_id = ?',
+ array($task['attachment_id']));
+ @unlink(BASEDIR . '/attachments/' . $task['file_name']);
+ Flyspray::logEvent($task['task_id'], 8, $task['orig_name']);
+ }
+ }
+
+ public static function delete_links($links)
+ {
+ global $db, $user;
+
+ settype($links, 'array');
+
+ if(!count($links)) {
+ return;
+ }
+
+ $sql = $db->query('SELECT t.*, l.* FROM {links} l LEFT JOIN {tasks} t ON t.task_id = l.task_id WHERE '.substr(str_repeat('link_id = ? OR ', count($links)), 0, -3), $links);
+
+ //Delete from database
+ while($task = $db->fetchRow($sql)) {
+ if (!$user->perms('delete_attachments', $task['project_id'])) {
+ continue;
+ }
+
+ $db->query('DELETE FROM {links} WHERE link_id = ?', array($task['link_id']));
+ // TODO: Log event in a later version.
+ }
+ }
+
+ /**
+ * Cleans a username (length, special chars, spaces)
+ * @param string $user_name
+ * @access public
+ * @return string
+ */
+ public static function clean_username($user_name)
+ {
+ // Limit length
+ $user_name = substr(trim($user_name), 0, 32);
+ // Remove doubled up spaces and control chars
+ $user_name = preg_replace('![\x00-\x1f\s]+!u', ' ', $user_name);
+ // Strip special chars
+ return utf8_keepalphanum($user_name);
+ }
+
+ public static function getAdminAddresses() {
+ global $db;
+
+ $emails = array();
+ $jabbers = array();
+ $onlines = array();
+
+ $sql = $db->query('SELECT DISTINCT u.user_id, u.email_address, u.jabber_id,
+ u.notify_online, u.notify_type, u.notify_own, u.lang_code
+ FROM {users} u
+ JOIN {users_in_groups} ug ON u.user_id = ug.user_id
+ JOIN {groups} g ON g.group_id = ug.group_id
+ WHERE g.is_admin = 1 AND u.account_enabled = 1');
+
+ Notifications::assignRecipients($db->fetchAllArray($sql), $emails, $jabbers, $onlines);
+
+ return array($emails, $jabbers, $onlines);
+ }
+
+ public static function getProjectManagerAddresses($project_id) {
+ global $db;
+
+ $emails = array();
+ $jabbers = array();
+ $onlines = array();
+
+ $sql = $db->query('SELECT DISTINCT u.user_id, u.email_address, u.jabber_id,
+ u.notify_online, u.notify_type, u.notify_own, u.lang_code
+ FROM {users} u
+ JOIN {users_in_groups} ug ON u.user_id = ug.user_id
+ JOIN {groups} g ON g.group_id = ug.group_id
+ WHERE g.manage_project = 1 AND g.project_id = ? AND u.account_enabled = 1',
+ array($project_id));
+
+ Notifications::assignRecipients($db->fetchAllArray($sql), $emails, $jabbers, $onlines);
+
+ return array($emails, $jabbers, $onlines);
+ }
+ /**
+ * Creates a new user
+ * @param string $user_name
+ * @param string $password
+ * @param string $real_name
+ * @param string $jabber_id
+ * @param string $email
+ * @param integer $notify_type
+ * @param integer $time_zone
+ * @param integer $group_in
+ * @access public
+ * @return bool false if username is already taken
+ * @version 1.0
+ * @notes This function does not have any permission checks (checked elsewhere)
+ */
+ public static function create_user($user_name, $password, $real_name, $jabber_id, $email, $notify_type, $time_zone, $group_in, $enabled, $oauth_uid = '', $oauth_provider = '', $profile_image = '')
+ {
+ global $fs, $db, $notify, $baseurl;
+
+ $user_name = Backend::clean_username($user_name);
+
+ // TODO Handle this whole create_user better concerning return false. Why did it fail?
+ # 'notassigned' and '-1' are possible filtervalues for advanced task search
+ if( empty($user_name) || ctype_digit($user_name) || $user_name == '-1' || $user_name=='notassigned' ) {
+ return false;
+ }
+
+ // Limit length
+ $real_name = substr(trim($real_name), 0, 100);
+ // Remove doubled up spaces and control chars
+ $real_name = preg_replace('![\x00-\x1f\s]+!u', ' ', $real_name);
+
+ # 'notassigned' and '-1' are possible filtervalues for advanced task search, lets avoid them
+ if( ctype_digit($real_name) || $real_name == '-1' || $real_name=='notassigned' ) {
+ return false;
+ }
+
+ // Check to see if the username is available
+ $sql = $db->query('SELECT COUNT(*) FROM {users} WHERE user_name = ?', array($user_name));
+
+ if ($db->fetchOne($sql)) {
+ return false;
+ }
+
+ $auto = false;
+ // Autogenerate a password
+ if (!$password) {
+ $auto = true;
+ $password = substr(md5(uniqid(mt_rand(), true)), 0, mt_rand(8, 12));
+ }
+
+ // Check the emails before inserting anything to database.
+ $emailList = explode(';',$email);
+ foreach ($emailList as $mail) { //Still need to do: check email
+ $count = $db->query("SELECT COUNT(*) FROM {user_emails} WHERE email_address = ?",array($mail));
+ $count = $db->fetchOne($count);
+ if ($count > 0) {
+ Flyspray::show_error("Email address has alredy been taken");
+ return false;
+ }
+ }
+
+ $db->query("INSERT INTO {users}
+ ( user_name, user_pass, real_name, jabber_id, profile_image, magic_url,
+ email_address, notify_type, account_enabled,
+ tasks_perpage, register_date, time_zone, dateformat,
+ dateformat_extended, oauth_uid, oauth_provider, lang_code)
+ VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, 25, ?, ?, ?, ?, ?, ?, ?)",
+ array($user_name, Flyspray::cryptPassword($password), $real_name, strtolower($jabber_id),
+ $profile_image, '', strtolower($email), $notify_type, $enabled, time(), $time_zone, '', '', $oauth_uid, $oauth_provider, $fs->prefs['lang_code']));
+
+ // Get this user's id for the record
+ $uid = Flyspray::userNameToId($user_name);
+
+ foreach ($emailList as $mail) {
+ if ($mail != '') {
+ $db->query("INSERT INTO {user_emails}(id,email_address,oauth_uid,oauth_provider) VALUES (?,?,?,?)",
+ array($uid,strtolower($mail),$oauth_uid, $oauth_provider));
+ }
+ }
+
+ // Now, create a new record in the users_in_groups table
+ $db->query('INSERT INTO {users_in_groups} (user_id, group_id)
+ VALUES (?, ?)', array($uid, $group_in));
+
+ Flyspray::logEvent(0, 30, serialize(Flyspray::getUserDetails($uid)));
+
+ $varnames = array('iwatch','atome','iopened');
+
+ $toserialize = array('string' => NULL,
+ 'type' => array (''),
+ 'sev' => array (''),
+ 'due' => array (''),
+ 'dev' => NULL,
+ 'cat' => array (''),
+ 'status' => array ('open'),
+ 'order' => NULL,
+ 'sort' => NULL,
+ 'percent' => array (''),
+ 'opened' => NULL,
+ 'search_in_comments' => NULL,
+ 'search_for_all' => NULL,
+ 'reported' => array (''),
+ 'only_primary' => NULL,
+ 'only_watched' => NULL);
+
+ foreach($varnames as $tmpname) {
+ if($tmpname == 'iwatch') {
+ $tmparr = array('only_watched' => '1');
+ } elseif ($tmpname == 'atome') {
+ $tmparr = array('dev'=> $uid);
+ } elseif($tmpname == 'iopened') {
+ $tmparr = array('opened'=> $uid);
+ }
+ $$tmpname = $tmparr + $toserialize;
+ }
+
+ // Now give him his default searches
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time)
+ VALUES (?, ?, ?, ?)',
+ array($uid, L('taskswatched'), serialize($iwatch), time()));
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time)
+ VALUES (?, ?, ?, ?)',
+ array($uid, L('assignedtome'), serialize($atome), time()));
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time)
+ VALUES (?, ?, ?, ?)',
+ array($uid, L('tasksireported'), serialize($iopened), time()));
+
+ if ($jabber_id) {
+ Notifications::jabberRequestAuth($jabber_id);
+ }
+
+ // Send a user his details (his username might be altered, password auto-generated)
+ // dont send notifications if the user logged in using oauth
+ if (!$oauth_provider) {
+ $recipients = self::getAdminAddresses();
+ $newuser = array();
+
+ // Add the right message here depending on $enabled.
+ if ($enabled === 0) {
+ $newuser[0][$email] = array('recipient' => $email, 'lang' => $fs->prefs['lang_code']);
+
+ } else {
+ $newuser[0][$email] = array('recipient' => $email, 'lang' => $fs->prefs['lang_code']);
+ }
+
+ // Notify the appropriate users
+ if ($fs->prefs['notify_registration']) {
+ $notify->create(NOTIFY_NEW_USER, null,
+ array($baseurl, $user_name, $real_name, $email, $jabber_id, $password, $auto),
+ $recipients, NOTIFY_EMAIL);
+ }
+ // And also the new user
+ $notify->create(NOTIFY_OWN_REGISTRATION, null,
+ array($baseurl, $user_name, $real_name, $email, $jabber_id, $password, $auto),
+ $newuser, NOTIFY_EMAIL);
+ }
+
+ // If the account is created as not enabled, no matter what any
+ // preferences might say or how the registration was made in first
+ // place, it MUST be first approved by an admin. And a small
+ // work-around: there's no field for email, so we use reason_given
+ // for that purpose.
+ if ($enabled === 0) {
+ Flyspray::adminRequest(3, 0, 0, $uid, $email);
+ }
+
+ return true;
+ }
+
+ /**
+ * Deletes a user
+ * @param integer $uid
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function delete_user($uid)
+ {
+ global $db, $user;
+
+ if (!$user->perms('is_admin')) {
+ return false;
+ }
+
+ $userDetails = Flyspray::getUserDetails($uid);
+
+ if (is_file(BASEDIR.'/avatars/'.$userDetails['profile_image'])) {
+ unlink(BASEDIR.'/avatars/'.$userDetails['profile_image']);
+ }
+
+ $tables = array('users', 'users_in_groups', 'searches', 'notifications', 'assigned', 'votes', 'effort');
+ # FIXME Deleting a users effort without asking when user is deleted may not be wanted in every situation.
+ # For example for billing a project and the deleted user worked for a project.
+ # The better solution is to just deactivate the user, but maybe there are cases a user MUSt be deleted from the database.
+ # Move that effort to an 'anonymous users' effort if the effort(s) was legal and should be measured for project(s)?
+ foreach ($tables as $table) {
+ if (!$db->query('DELETE FROM ' .'{' . $table .'}' . ' WHERE user_id = ?', array($uid))) {
+ return false;
+ }
+ }
+
+ if (!empty($userDetails['profile_image']) && is_file(BASEDIR.'/avatars/'.$userDetails['profile_image'])) {
+ unlink(BASEDIR.'/avatars/'.$userDetails['profile_image']);
+ }
+
+ $db->query('DELETE FROM {registrations} WHERE email_address = ?',
+ array($userDetails['email_address']));
+
+ $db->query('DELETE FROM {user_emails} WHERE id = ?',
+ array($uid));
+
+ $db->query('DELETE FROM {reminders} WHERE to_user_id = ? OR from_user_id = ?',
+ array($uid, $uid));
+
+ // for the unusual situuation that a user ID is re-used, make sure that the new user doesn't
+ // get permissions for a task automatically
+ $db->query('UPDATE {tasks} SET opened_by = 0 WHERE opened_by = ?', array($uid));
+
+ Flyspray::logEvent(0, 31, serialize($userDetails));
+
+ return true;
+ }
+
+
+ /**
+ * Deletes a project
+ * @param integer $pid
+ * @param integer $move_to to which project contents of the project are moved
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function delete_project($pid, $move_to = 0)
+ {
+ global $db, $user;
+
+ if (!$user->perms('manage_project', $pid)) {
+ return false;
+ }
+
+ // Delete all project's tasks related information
+ if (!$move_to) {
+ $task_ids = $db->query('SELECT task_id FROM {tasks} WHERE project_id = ' . intval($pid));
+ $task_ids = $db->fetchCol($task_ids);
+ // What was supposed to be in tables field_values, notification_threads
+ // and redundant, they do not exist in database?
+ $tables = array('admin_requests', 'assigned', 'attachments', 'comments',
+ 'dependencies', 'related', 'history',
+ 'notifications',
+ 'reminders', 'votes');
+ foreach ($tables as $table) {
+ if ($table == 'related') {
+ $stmt = $db->dblink->prepare('DELETE FROM ' . $db->dbprefix . $table . ' WHERE this_task = ? OR related_task = ? ');
+ } else {
+ $stmt = $db->dblink->prepare('DELETE FROM ' . $db->dbprefix . $table . ' WHERE task_id = ?');
+ }
+ foreach ($task_ids as $id) {
+ $db->dblink->execute($stmt, ($table == 'related') ? array($id, $id) : array($id));
+ }
+ }
+ }
+
+ // unset category of tasks because we don't move categories
+ if ($move_to) {
+ $db->query('UPDATE {tasks} SET product_category = 0 WHERE project_id = ?', array($pid));
+ }
+
+ $tables = array('list_category', 'list_os', 'list_resolution', 'list_tasktype',
+ 'list_status', 'list_version', 'admin_requests',
+ 'cache', 'projects', 'tasks');
+
+ foreach ($tables as $table) {
+ if ($move_to && $table !== 'projects' && $table !== 'list_category') {
+ // Having a unique index in most list_* tables prevents
+ // doing just a simple update, if the list item already
+ // exists in target project, so we have to update existing
+ // tasks to use the one in target project. Something similar
+ // should be done when moving a single task to another project.
+ // Consider making this a separate function that can be used
+ // for that purpose too, if possible.
+ if (strpos($table, 'list_') === 0) {
+ list($type, $name) = explode('_', $table);
+ $sql = $db->query('SELECT ' . $name . '_id, ' . $name . '_name
+ FROM {' . $table . '}
+ WHERE project_id = ?',
+ array($pid));
+ $rows = $db->fetchAllArray($sql);
+ foreach ($rows as $row) {
+ $sql = $db->query('SELECT ' . $name . '_id
+ FROM {' . $table . '}
+ WHERE project_id = ? AND '. $name . '_name = ?',
+ array($move_to, $row[$name .'_name']));
+ $new_id = $db->fetchOne($sql);
+ if ($new_id) {
+ switch ($name) {
+ case 'os';
+ $column = 'operating_system';
+ break;
+ case 'resolution';
+ $column = 'resolution_reason';
+ break;
+ case 'tasktype';
+ $column = 'task_type';
+ break;
+ case 'status';
+ $column = 'item_status';
+ break;
+ case 'version';
+ // Questionable what to do with this one. 1.0 could
+ // have been still future in the old project and
+ // already past in the new one...
+ $column = 'product_version';
+ break;
+ }
+ if (isset($column)) {
+ $db->query('UPDATE {tasks}
+ SET ' . $column . ' = ?
+ WHERE ' . $column . ' = ?',
+ array($new_id, $row[$name . '_id']));
+ $db->query('DELETE FROM {' . $table . '}
+ WHERE ' . $name . '_id = ?',
+ array($row[$name . '_id']));
+ }
+ }
+ }
+ }
+ $base_sql = 'UPDATE {' . $table . '} SET project_id = ?';
+ $sql_params = array($move_to, $pid);
+ } else {
+ $base_sql = 'DELETE FROM {' . $table . '}';
+ $sql_params = array($pid);
+ }
+
+ if (!$db->query($base_sql . ' WHERE project_id = ?', $sql_params)) {
+ return false;
+ }
+ }
+
+ // groups are only deleted, not moved (it is likely
+ // that the destination project already has all kinds
+ // of groups which are also used by the old project)
+ $sql = $db->query('SELECT group_id FROM {groups} WHERE project_id = ?', array($pid));
+ while ($row = $db->fetchRow($sql)) {
+ $db->query('DELETE FROM {users_in_groups} WHERE group_id = ?', array($row['group_id']));
+ }
+ $sql = $db->query('DELETE FROM {groups} WHERE project_id = ?', array($pid));
+
+ //we have enough reasons .. the process is OK.
+ return true;
+ }
+
+ /**
+ * Adds a reminder to a task
+ * @param integer $task_id
+ * @param string $message
+ * @param integer $how_often send a reminder every ~ seconds
+ * @param integer $start_time time when the reminder starts
+ * @param $user_id the user who is reminded. by default (null) all users assigned to the task are reminded.
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function add_reminder($task_id, $message, $how_often, $start_time, $user_id = null)
+ {
+ global $user, $db;
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$user->perms('manage_project', $task['project_id'])) {
+ return false;
+ }
+
+ if (is_null($user_id)) {
+ // Get all users assigned to a task
+ $user_id = Flyspray::getAssignees($task_id);
+ } else {
+ $user_id = array(Flyspray::validUserId($user_id));
+ if (!reset($user_id)) {
+ return false;
+ }
+ }
+
+ foreach ($user_id as $id) {
+ $sql = $db->replace('{reminders}',
+ array('task_id'=> $task_id, 'to_user_id'=> $id,
+ 'from_user_id' => $user->id, 'start_time' => $start_time,
+ 'how_often' => $how_often, 'reminder_message' => $message),
+ array('task_id', 'to_user_id', 'how_often', 'reminder_message'));
+ if(!$sql) {
+ // query has failed :(
+ return false;
+ }
+ }
+ // 2 = no record has found and was INSERT'ed correclty
+ if (isset($sql) && $sql == 2) {
+ Flyspray::logEvent($task_id, 17, $task_id);
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new task
+ * @param array $args array containing all task properties. unknown properties will be ignored
+ * @access public
+ * @return integer the task ID on success
+ * @version 1.0
+ * @notes $args is POST data, bad..bad user..
+ */
+ public static function create_task($args)
+ {
+ global $conf, $db, $user, $proj;
+
+ if (!isset($args)) return 0;
+
+ // these are the POST variables that the user MUST send, if one of
+ // them is missing or if one of them is empty, then we have to abort
+ $requiredPostArgs = array('item_summary', 'project_id');//modify: made description not required
+ foreach ($requiredPostArgs as $required) {
+ if (empty($args[$required])) return 0;
+ }
+
+ $notify = new Notifications();
+ if ($proj->id != $args['project_id']) {
+ $proj = new Project($args['project_id']);
+ }
+
+ if (!$user->can_open_task($proj)) {
+ return 0;
+ }
+
+ // first populate map with default values
+ $sql_args = array(
+ 'project_id' => $proj->id,
+ 'date_opened' => time(),
+ 'last_edited_time' => time(),
+ 'opened_by' => intval($user->id),
+ 'percent_complete' => 0,
+ 'mark_private' => 0,
+ 'supertask_id' => 0,
+ 'closedby_version' => 0,
+ 'closure_comment' => '',
+ 'task_priority' => 2,
+ 'due_date' => 0,
+ 'anon_email' => '',
+ 'item_status'=> STATUS_UNCONFIRMED
+ );
+
+ // POST variables the user is ALLOWED to provide
+ $allowedPostArgs = array(
+ 'task_type', 'product_category', 'product_version',
+ 'operating_system', 'task_severity', 'estimated_effort',
+ 'supertask_id', 'item_summary', 'detailed_desc'
+ );
+ // these POST variables the user is only ALLOWED to provide if he got the permissions
+ if ($user->perms('modify_all_tasks')) {
+ $allowedPostArgs[] = 'closedby_version';
+ $allowedPostArgs[] = 'task_priority';
+ $allowedPostArgs[] = 'due_date';
+ $allowedPostArgs[] = 'item_status';
+ }
+ if ($user->perms('manage_project')) {
+ $allowedPostArgs[] = 'mark_private';
+ }
+ // now copy all over all POST variables the user is ALLOWED to provide
+ // (but only if they are not empty)
+ foreach ($allowedPostArgs as $allowed) {
+ if (!empty($args[$allowed])) {
+ $sql_args[$allowed] = $args[$allowed];
+ }
+ }
+
+ // Process the due_date
+ if ( isset($args['due_date']) && ($due_date = $args['due_date']) || ($due_date = 0) ) {
+ $due_date = Flyspray::strtotime($due_date);
+ }
+
+ $sql_params[] = 'mark_private';
+ $sql_values[] = intval($user->perms('manage_project') && isset($args['mark_private']) && $args['mark_private'] == '1');
+
+ $sql_params[] = 'due_date';
+ $sql_values[] = $due_date;
+
+ $sql_params[] = 'closure_comment';
+ $sql_values[] = '';
+
+ // Process estimated effort
+ $estimated_effort = 0;
+ if ($proj->prefs['use_effort_tracking'] && isset($sql_args['estimated_effort'])) {
+ if (($estimated_effort = effort::editStringToSeconds($sql_args['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format'])) === FALSE) {
+ Flyspray::show_error(L('invalideffort'));
+ $estimated_effort = 0;
+ }
+ $sql_args['estimated_effort'] = $estimated_effort;
+ }
+
+ // Token for anonymous users
+ $token = '';
+ if ($user->isAnon()) {
+ if (empty($args['anon_email'])) {
+ return 0;
+ }
+ $token = md5(function_exists('openssl_random_pseudo_bytes') ?
+ openssl_random_pseudo_bytes(32) :
+ uniqid(mt_rand(), true));
+ $sql_args['task_token'] = $token;
+ $sql_args['anon_email'] = $args['anon_email'];
+ }
+
+ // ensure all variables are in correct format
+ if (!empty($sql_args['due_date'])) {
+ $sql_args['due_date'] = Flyspray::strtotime($sql_args['due_date']);
+ }
+ if (isset($sql_args['mark_private'])) {
+ $sql_args['mark_private'] = intval($sql_args['mark_private'] == '1');
+ }
+
+ # dokuwiki syntax plugin filters on output
+ if($conf['general']['syntax_plugin'] != 'dokuwiki' && isset($sql_args['detailed_desc']) ){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $sql_args['detailed_desc'] = $purifier->purify($sql_args['detailed_desc']);
+ }
+
+ // split keys and values into two separate arrays
+ $sql_keys = array();
+ $sql_values = array();
+ foreach ($sql_args as $key => $value) {
+ $sql_keys[] = $key;
+ $sql_values[] = $value;
+ }
+
+ /*
+ * TODO: At least with PostgreSQL, this has caused the sequence to be
+ * out of sync with reality. Must be fixed in upgrade process. Check
+ * what's the situation with MySQL. (It's fine, it updates the value even
+ * if the column was manually adjusted. Remove this whole block later.)
+ $result = $db->query('SELECT MAX(task_id)+1
+ FROM {tasks}');
+ $task_id = $db->fetchOne($result);
+ $task_id = $task_id ? $task_id : 1;
+ */
+ //now, $task_id is always the first element of $sql_values
+ #array_unshift($sql_keys, 'task_id');
+ #array_unshift($sql_values, $task_id);
+
+ $sql_keys_string = join(', ', $sql_keys);
+ $sql_placeholder = $db->fill_placeholders($sql_values);
+
+ $result = $db->query("INSERT INTO {tasks}
+ ($sql_keys_string)
+ VALUES ($sql_placeholder)", $sql_values);
+ $task_id=$db->insert_ID();
+
+ Backend::upload_links($task_id);
+
+ // create tags
+ if (isset($args['tags'])) {
+ $tagList = explode(';', $args['tags']);
+ $tagList = array_map('strip_tags', $tagList);
+ $tagList = array_map('trim', $tagList);
+ $tagList = array_unique($tagList); # avoid duplicates for inputs like: "tag1;tag1" or "tag1; tag1<p></p>"
+ foreach ($tagList as $tag){
+ if ($tag == ''){
+ continue;
+ }
+
+ # old tag feature
+ #$result2 = $db->query("INSERT INTO {tags} (task_id, tag) VALUES (?,?)",array($task_id,$tag));
+
+ # new tag feature. let's do it in 2 steps, it is getting too complicated to make it cross database compatible, drawback is possible (rare) race condition (use transaction?)
+ $res=$db->query("SELECT tag_id FROM {list_tag} WHERE (project_id=0 OR project_id=?) AND tag_name LIKE ? ORDER BY project_id", array($proj->id,$tag) );
+ if($t=$db->fetchRow($res)){
+ $tag_id=$t['tag_id'];
+ } else{
+ if( $proj->prefs['freetagging']==1){
+ # add to taglist of the project
+ $db->query("INSERT INTO {list_tag} (project_id,tag_name) VALUES (?,?)", array($proj->id,$tag));
+ $tag_id=$db->insert_ID();
+ } else{
+ continue;
+ }
+ };
+ $db->query("INSERT INTO {task_tag}(task_id,tag_id) VALUES(?,?)", array($task_id, $tag_id) );
+ }
+ }
+
+ // Log the assignments and send notifications to the assignees
+ if (isset($args['rassigned_to']) && is_array($args['rassigned_to']))
+ {
+ // Convert assigned_to and store them in the 'assigned' table
+ foreach ($args['rassigned_to'] as $val)
+ {
+ $db->replace('{assigned}', array('user_id'=> $val, 'task_id'=> $task_id), array('user_id','task_id'));
+ }
+ // Log to task history
+ Flyspray::logEvent($task_id, 14, implode(' ', $args['rassigned_to']));
+
+ // Notify the new assignees what happened. This obviously won't happen if the task is now assigned to no-one.
+ $notify->create(NOTIFY_NEW_ASSIGNEE, $task_id, null, $notify->specificAddresses($args['rassigned_to']), NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ // Log that the task was opened
+ Flyspray::logEvent($task_id, 1);
+
+ $result = $db->query('SELECT *
+ FROM {list_category}
+ WHERE category_id = ?',
+ array($args['product_category']));
+ $cat_details = $db->fetchRow($result);
+
+ // We need to figure out who is the category owner for this task
+ if (!empty($cat_details['category_owner'])) {
+ $owner = $cat_details['category_owner'];
+ }
+ else {
+ // check parent categories
+ $result = $db->query('SELECT *
+ FROM {list_category}
+ WHERE lft < ? AND rgt > ? AND project_id = ?
+ ORDER BY lft DESC',
+ array($cat_details['lft'], $cat_details['rgt'], $cat_details['project_id']));
+ while ($row = $db->fetchRow($result)) {
+ // If there's a parent category owner, send to them
+ if (!empty($row['category_owner'])) {
+ $owner = $row['category_owner'];
+ break;
+ }
+ }
+ }
+
+ if (!isset($owner)) {
+ $owner = $proj->prefs['default_cat_owner'];
+ }
+
+ if ($owner) {
+ if ($proj->prefs['auto_assign'] && ($args['item_status'] == STATUS_UNCONFIRMED || $args['item_status'] == STATUS_NEW)) {
+ Backend::add_to_assignees($owner, $task_id, true);
+ }
+ Backend::add_notification($owner, $task_id, true);
+ }
+
+ // Reminder for due_date field
+ if (!empty($sql_args['due_date'])) {
+ Backend::add_reminder($task_id, L('defaultreminder') . "\n\n" . createURL('details', $task_id), 2*24*60*60, time());
+ }
+
+ // Create the Notification
+ if (Backend::upload_files($task_id)) {
+ $notify->create(NOTIFY_TASK_OPENED, $task_id, 'files', null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ } else {
+ $notify->create(NOTIFY_TASK_OPENED, $task_id, null, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ // If the reporter wanted to be added to the notification list
+ if (isset($args['notifyme']) && $args['notifyme'] == '1' && $user->id != $owner) {
+ Backend::add_notification($user->id, $task_id, true);
+ }
+
+ if ($user->isAnon()) {
+ $anonuser = array();
+ $anonuser[$email] = array('recipient' => $args['anon_email'], 'lang' => $fs->prefs['lang_code']);
+ $recipients = array($anonuser);
+ $notify->create(NOTIFY_ANON_TASK, $task_id, $token,
+ $recipients, NOTIFY_EMAIL, $proj->prefs['lang_code']);
+ }
+
+ return array($task_id, $token);
+ }
+
+ /**
+ * Closes a task
+ * @param integer $task_id
+ * @param integer $reason
+ * @param string $comment
+ * @param bool $mark100
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ public static function close_task($task_id, $reason, $comment, $mark100 = true)
+ {
+ global $db, $notify, $user, $proj;
+ $task = Flyspray::getTaskDetails($task_id);
+
+ if (!$user->can_close_task($task)) {
+ return false;
+ }
+
+ if ($task['is_closed']) {
+ return false;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET date_closed = ?, closed_by = ?, closure_comment = ?,
+ is_closed = 1, resolution_reason = ?, last_edited_time = ?,
+ last_edited_by = ?
+ WHERE task_id = ?',
+ array(time(), $user->id, $comment, $reason, time(), $user->id, $task_id));
+
+ if ($mark100) {
+ $db->query('UPDATE {tasks} SET percent_complete = 100 WHERE task_id = ?',
+ array($task_id));
+
+ Flyspray::logEvent($task_id, 3, 100, $task['percent_complete'], 'percent_complete');
+ }
+
+ $notify->create(NOTIFY_TASK_CLOSED, $task_id, null, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ Flyspray::logEvent($task_id, 2, $reason, $comment);
+
+ // If there's an admin request related to this, close it
+ $db->query('UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?
+ WHERE task_id = ? AND request_type = ?',
+ array($user->id, time(), $task_id, 1));
+
+ // duplicate
+ if ($reason == RESOLUTION_DUPLICATE) {
+ preg_match("/\b(?:FS#|bug )(\d+)\b/", $comment, $dupe_of);
+ if (count($dupe_of) >= 2) {
+ $existing = $db->query('SELECT * FROM {related} WHERE this_task = ? AND related_task = ? AND is_duplicate = 1',
+ array($task_id, $dupe_of[1]));
+
+ if ($existing && $db->countRows($existing) == 0) {
+ $db->query('INSERT INTO {related} (this_task, related_task, is_duplicate) VALUES(?, ?, 1)',
+ array($task_id, $dupe_of[1]));
+ }
+ Backend::add_vote($task['opened_by'], $dupe_of[1]);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns an array of tasks (respecting pagination) and an ID list (all tasks)
+ * @param array $args
+ * @param array $visible
+ * @param integer $offset
+ * @param integer $comment
+ * @param bool $perpage
+ * @access public
+ * @return array
+ * @version 1.0
+ */
+ public static function get_task_list($args, $visible, $offset = 0, $perpage = 20) {
+ global $fs, $proj, $db, $user, $conf;
+ /* build SQL statement {{{ */
+ // Original SQL courtesy of Lance Conry http://www.rhinosw.com/
+ $where = $sql_params = array();
+
+ // echo '<pre>' . print_r($visible, true) . '</pre>';
+ // echo '<pre>' . print_r($args, true) . '</pre>';
+ // PostgreSQL LIKE searches are by default case sensitive,
+ // so we use ILIKE instead. For other databases, in our case
+ // only MySQL/MariaDB, LIKE is good for our purposes.
+ $LIKEOP = 'LIKE';
+ if ($db->dblink->dataProvider == 'postgres') {
+ $LIKEOP = 'ILIKE';
+ }
+
+ $select = '';
+ $groupby = 't.task_id, ';
+ $cgroupbyarr = array();
+
+ // Joins absolutely needed for user viewing rights
+ $from = ' {tasks} t
+-- All tasks have a project!
+JOIN {projects} p ON t.project_id = p.project_id';
+
+ // Not needed for anonymous users
+ if (!$user->isAnon()) {
+$from .= ' -- Global group always exists
+JOIN ({groups} gpg
+ JOIN {users_in_groups} gpuig ON gpg.group_id = gpuig.group_id AND gpuig.user_id = ?
+) ON gpg.project_id = 0
+-- Project group might exist or not.
+LEFT JOIN ({groups} pg
+ JOIN {users_in_groups} puig ON pg.group_id = puig.group_id AND puig.user_id = ?
+) ON pg.project_id = t.project_id';
+ $sql_params[] = $user->id;
+ $sql_params[] = $user->id;
+ }
+
+ // Keep this always, could also used for showing assigned users for a task.
+ // Keeps the overall logic somewhat simpler.
+ $from .= ' LEFT JOIN {assigned} ass ON t.task_id = ass.task_id';
+ $from .= ' LEFT JOIN {task_tag} tt ON t.task_id = tt.task_id';
+ $cfrom = $from;
+
+ // Seems resution name really is needed...
+ $select .= 'lr.resolution_name, ';
+ $from .= ' LEFT JOIN {list_resolution} lr ON t.resolution_reason = lr.resolution_id ';
+ $groupby .= 'lr.resolution_name, ';
+
+ // Otherwise, only join tables which are really necessary to speed up the db-query
+ if (array_get($args, 'type') || in_array('tasktype', $visible)) {
+ $select .= ' lt.tasktype_name, ';
+ $from .= '
+LEFT JOIN {list_tasktype} lt ON t.task_type = lt.tasktype_id ';
+ $groupby .= ' lt.tasktype_id, ';
+ }
+
+ if (array_get($args, 'status') || in_array('status', $visible)) {
+ $select .= ' lst.status_name, ';
+ $from .= '
+LEFT JOIN {list_status} lst ON t.item_status = lst.status_id ';
+ $groupby .= ' lst.status_id, ';
+ }
+
+ if (array_get($args, 'cat') || in_array('category', $visible)) {
+ $select .= ' lc.category_name AS category_name, ';
+ $from .= '
+LEFT JOIN {list_category} lc ON t.product_category = lc.category_id ';
+ $groupby .= 'lc.category_id, ';
+ }
+
+ if (in_array('votes', $visible)) {
+ $select .= ' (SELECT COUNT(vot.vote_id) FROM {votes} vot WHERE vot.task_id = t.task_id) AS num_votes, ';
+ }
+
+ $maxdatesql = ' GREATEST(COALESCE((SELECT max(c.date_added) FROM {comments} c WHERE c.task_id = t.task_id), 0), t.date_opened, t.date_closed, t.last_edited_time) ';
+ $search_for_changes = in_array('lastedit', $visible) || array_get($args, 'changedto') || array_get($args, 'changedfrom');
+ if ($search_for_changes) {
+ $select .= ' GREATEST(COALESCE((SELECT max(c.date_added) FROM {comments} c WHERE c.task_id = t.task_id), 0), t.date_opened, t.date_closed, t.last_edited_time) AS max_date, ';
+ $cgroupbyarr[] = 't.task_id';
+ }
+
+ if (array_get($args, 'search_in_comments')) {
+ $from .= '
+LEFT JOIN {comments} c ON t.task_id = c.task_id ';
+ $cfrom .= '
+LEFT JOIN {comments} c ON t.task_id = c.task_id ';
+ $cgroupbyarr[] = 't.task_id';
+ }
+
+ if (in_array('comments', $visible)) {
+ $select .= ' (SELECT COUNT(cc.comment_id) FROM {comments} cc WHERE cc.task_id = t.task_id) AS num_comments, ';
+ }
+
+ if (in_array('reportedin', $visible)) {
+ $select .= ' lv.version_name AS product_version_name, ';
+ $from .= '
+LEFT JOIN {list_version} lv ON t.product_version = lv.version_id ';
+ $groupby .= 'lv.version_id, ';
+ }
+
+ if (array_get($args, 'opened') || in_array('openedby', $visible)) {
+ $select .= ' uo.real_name AS opened_by_name, ';
+ $from .= '
+LEFT JOIN {users} uo ON t.opened_by = uo.user_id ';
+ $groupby .= 'uo.user_id, ';
+ if (array_get($args, 'opened')) {
+ $cfrom .= '
+LEFT JOIN {users} uo ON t.opened_by = uo.user_id ';
+ }
+ }
+
+ if (array_get($args, 'closed')) {
+ $select .= ' uc.real_name AS closed_by_name, ';
+ $from .= '
+LEFT JOIN {users} uc ON t.closed_by = uc.user_id ';
+ $groupby .= 'uc.user_id, ';
+ $cfrom .= '
+LEFT JOIN {users} uc ON t.closed_by = uc.user_id ';
+ }
+
+ if (array_get($args, 'due') || in_array('dueversion', $visible)) {
+ $select .= ' lvc.version_name AS closedby_version_name, ';
+ $from .= '
+LEFT JOIN {list_version} lvc ON t.closedby_version = lvc.version_id ';
+ $groupby .= 'lvc.version_id, lvc.list_position, ';
+ }
+
+ if (in_array('os', $visible)) {
+ $select .= ' los.os_name AS os_name, ';
+ $from .= '
+LEFT JOIN {list_os} los ON t.operating_system = los.os_id ';
+ $groupby .= 'los.os_id, ';
+ }
+
+ if (in_array('attachments', $visible)) {
+ $select .= ' (SELECT COUNT(attc.attachment_id) FROM {attachments} attc WHERE attc.task_id = t.task_id) AS num_attachments, ';
+ }
+
+ if (array_get($args, 'has_attachment')) {
+ $where[] = 'EXISTS (SELECT 1 FROM {attachments} att WHERE t.task_id = att.task_id)';
+ }
+ # 20150213 currently without recursive subtasks!
+ if (in_array('effort', $visible)) {
+ $select .= ' (SELECT SUM(ef.effort) FROM {effort} ef WHERE t.task_id = ef.task_id) AS effort, ';
+ }
+
+ if (array_get($args, 'dev') || in_array('assignedto', $visible)) {
+ # not every db system has this feature out of box
+ if($conf['database']['dbtype']=='mysqli' || $conf['database']['dbtype']=='mysql'){
+ $select .= ' GROUP_CONCAT(DISTINCT u.user_name ORDER BY u.user_id) AS assigned_to_name, ';
+ $select .= ' GROUP_CONCAT(DISTINCT u.user_id ORDER BY u.user_id) AS assignedids, ';
+ $select .= ' GROUP_CONCAT(DISTINCT u.profile_image ORDER BY u.user_id) AS assigned_image, ';
+ } elseif( $conf['database']['dbtype']=='pgsql'){
+ $select .= " array_to_string(array_agg(u.user_name ORDER BY u.user_id), ',') AS assigned_to_name, ";
+ $select .= " array_to_string(array_agg(CAST(u.user_id as text) ORDER BY u.user_id), ',') AS assignedids, ";
+ $select .= " array_to_string(array_agg(u.profile_image ORDER BY u.user_id), ',') AS assigned_image, ";
+ } else{
+ $select .= ' MIN(u.user_name) AS assigned_to_name, ';
+ $select .= ' (SELECT COUNT(assc.user_id) FROM {assigned} assc WHERE assc.task_id = t.task_id) AS num_assigned, ';
+ }
+ // assigned table is now always included in join
+ $from .= '
+LEFT JOIN {users} u ON ass.user_id = u.user_id ';
+ $groupby .= 'ass.task_id, ';
+ if (array_get($args, 'dev')) {
+ $cfrom .= '
+LEFT JOIN {users} u ON ass.user_id = u.user_id ';
+ $cgroupbyarr[] = 't.task_id';
+ $cgroupbyarr[] = 'ass.task_id';
+ }
+ }
+
+ # not every db system has this feature out of box, it is not standard sql
+ if($conf['database']['dbtype']=='mysqli' || $conf['database']['dbtype']=='mysql'){
+ #$select .= ' GROUP_CONCAT(DISTINCT tg.tag_name ORDER BY tg.list_position) AS tags, ';
+ $select .= ' GROUP_CONCAT(DISTINCT tg.tag_id ORDER BY tg.list_position) AS tagids, ';
+ #$select .= ' GROUP_CONCAT(DISTINCT tg.class ORDER BY tg.list_position) AS tagclass, ';
+ } elseif($conf['database']['dbtype']=='pgsql'){
+ #$select .= " array_to_string(array_agg(tg.tag_name ORDER BY tg.list_position), ',') AS tags, ";
+ $select .= " array_to_string(array_agg(CAST(tg.tag_id as text) ORDER BY tg.list_position), ',') AS tagids, ";
+ #$select .= " array_to_string(array_agg(tg.class ORDER BY tg.list_position), ',') AS tagclass, ";
+ } else{
+ # unsupported groupconcat or we just do not know how write it for the other databasetypes in this section
+ #$select .= ' MIN(tg.tag_name) AS tags, ';
+ #$select .= ' (SELECT COUNT(tt.tag_id) FROM {task_tag} tt WHERE tt.task_id = t.task_id) AS tagnum, ';
+ $select .= ' MIN(tg.tag_id) AS tagids, ';
+ #$select .= " '' AS tagclass, ";
+ }
+ // task_tag join table is now always included in join
+ $from .= '
+LEFT JOIN {list_tag} tg ON tt.tag_id = tg.tag_id ';
+ $groupby .= 'tt.task_id, ';
+ $cfrom .= '
+LEFT JOIN {list_tag} tg ON tt.tag_id = tg.tag_id ';
+ $cgroupbyarr[] = 't.task_id';
+ $cgroupbyarr[] = 'tt.task_id';
+
+
+ # use preparsed task description cache for dokuwiki when possible
+ if($conf['general']['syntax_plugin']=='dokuwiki' && FLYSPRAY_USE_CACHE==true){
+ $select.=' MIN(cache.content) desccache, ';
+ $from.='
+LEFT JOIN {cache} cache ON t.task_id=cache.topic AND cache.type=\'task\' ';
+ } else {
+ $select .= 'NULL AS desccache, ';
+ }
+
+ if (array_get($args, 'only_primary')) {
+ $where[] = 'NOT EXISTS (SELECT 1 FROM {dependencies} dep WHERE dep.dep_task_id = t.task_id)';
+ }
+
+ # feature FS#1600
+ if (array_get($args, 'only_blocker')) {
+ $where[] = 'EXISTS (SELECT 1 FROM {dependencies} dep WHERE dep.dep_task_id = t.task_id)';
+ }
+
+ if (array_get($args, 'only_blocked')) {
+ $where[] = 'EXISTS (SELECT 1 FROM {dependencies} dep WHERE dep.task_id = t.task_id)';
+ }
+
+ # feature FS#1599
+ if (array_get($args, 'only_unblocked')) {
+ $where[] = 'NOT EXISTS (SELECT 1 FROM {dependencies} dep WHERE dep.task_id = t.task_id)';
+ }
+
+ if (array_get($args, 'hide_subtasks')) {
+ $where[] = 't.supertask_id = 0';
+ }
+
+ if (array_get($args, 'only_watched')) {
+ $where[] = 'EXISTS (SELECT 1 FROM {notifications} fsn WHERE t.task_id = fsn.task_id AND fsn.user_id = ?)';
+ $sql_params[] = $user->id;
+ }
+
+ if ($proj->id) {
+ $where[] = 't.project_id = ?';
+ $sql_params[] = $proj->id;
+ } else {
+ if (!$user->isAnon()) { // Anon-case handled later.
+ $allowed = array();
+ foreach($fs->projects as $p) {
+ $allowed[] = $p['project_id'];
+ }
+ if(count($allowed)>0){
+ $where[] = 't.project_id IN (' . implode(',', $allowed). ')';
+ }else{
+ $where[] = '0 = 1'; # always empty result
+ }
+ }
+ }
+
+ // process users viewing rights, if not anonymous
+ if (!$user->isAnon()) {
+ $where[] = '
+( -- Begin block where users viewing rights are checked.
+ -- Case everyone can see all project tasks anyway and task not private
+ (t.mark_private = 0 AND p.others_view = 1)
+ OR
+ -- Case admin or project manager, can see any task, even private
+ (gpg.is_admin = 1 OR gpg.manage_project = 1 OR pg.is_admin = 1 OR pg.manage_project = 1)
+ OR
+ -- Case allowed to see all tasks, but not private
+ ((gpg.view_tasks = 1 OR pg.view_tasks = 1) AND t.mark_private = 0)
+ OR
+ -- Case allowed to see own tasks (automatically covers private tasks also for this user!)
+ ((gpg.view_own_tasks = 1 OR pg.view_own_tasks = 1) AND (t.opened_by = ? OR ass.user_id = ?))
+ OR
+ -- Case task is private, but user either opened it or is an assignee
+ (t.mark_private = 1 AND (t.opened_by = ? OR ass.user_id = ?))
+ OR
+ -- Leave groups tasks as the last one to check. They are the only ones that actually need doing a subquery
+ -- for checking viewing rights. There\'s a chance that a previous check already matched and the subquery is
+ -- not executed at all. All this of course depending on how the database query optimizer actually chooses
+ -- to fetch the results and execute this query... At least it has been given the hint.
+
+ -- Case allowed to see groups tasks, all projects (NOTE: both global and project specific groups accepted here)
+ -- Strange... do not use OR here with user_id in EXISTS clause, seems to prevent using index with both mysql and
+ -- postgresql, query times go up a lot. So it\'ll be 2 different EXISTS OR\'ed together.
+ (gpg.view_groups_tasks = 1 AND t.mark_private = 0 AND (
+ EXISTS (SELECT 1 FROM {users_in_groups} WHERE (group_id = pg.group_id OR group_id = gpg.group_id) AND user_id = t.opened_by)
+ OR
+ EXISTS (SELECT 1 FROM {users_in_groups} WHERE (group_id = pg.group_id OR group_id = gpg.group_id) AND user_id = ass.user_id)
+ ))
+ OR
+ -- Case allowed to see groups tasks, current project. Only project group allowed here.
+ (pg.view_groups_tasks = 1 AND t.mark_private = 0 AND (
+ EXISTS (SELECT 1 FROM {users_in_groups} WHERE group_id = pg.group_id AND user_id = t.opened_by)
+ OR
+ EXISTS (SELECT 1 FROM {users_in_groups} WHERE group_id = pg.group_id AND user_id = ass.user_id)
+ ))
+) -- Rights have been checked
+';
+ $sql_params[] = $user->id;
+ $sql_params[] = $user->id;
+ $sql_params[] = $user->id;
+ $sql_params[] = $user->id;
+ }
+ /// process search-conditions {{{
+ $submits = array('type' => 'task_type', 'sev' => 'task_severity',
+ 'due' => 'closedby_version', 'reported' => 'product_version',
+ 'cat' => 'product_category', 'status' => 'item_status',
+ 'percent' => 'percent_complete', 'pri' => 'task_priority',
+ 'dev' => array('ass.user_id', 'u.user_name', 'u.real_name'),
+ 'opened' => array('opened_by', 'uo.user_name', 'uo.real_name'),
+ 'closed' => array('closed_by', 'uc.user_name', 'uc.real_name'));
+ foreach ($submits as $key => $db_key) {
+ $type = array_get($args, $key, ($key == 'status') ? 'open' : '');
+ settype($type, 'array');
+
+ if (in_array('', $type)) {
+ continue;
+ }
+
+ $temp = '';
+ $condition = '';
+ foreach ($type as $val) {
+ // add conditions for the status selection
+ if ($key == 'status' && $val == 'closed' && !in_array('open', $type)) {
+ $temp .= ' is_closed = 1 AND';
+ } elseif ($key == 'status' && !in_array('closed', $type)) {
+ $temp .= ' is_closed = 0 AND';
+ }
+ if (is_numeric($val) && !is_array($db_key) && !($key == 'status' && $val == 'closed')) {
+ $temp .= ' ' . $db_key . ' = ? OR';
+ $sql_params[] = $val;
+ } elseif (is_array($db_key)) {
+ if ($key == 'dev' && ($val == 'notassigned' || $val == '0' || $val == '-1')) {
+ $temp .= ' ass.user_id is NULL OR';
+ } else {
+ foreach ($db_key as $singleDBKey) {
+ if(ctype_digit($val) && strpos($singleDBKey, '_name') === false) {
+ $temp .= ' ' . $singleDBKey . ' = ? OR';
+ $sql_params[] = $val;
+ } elseif (!ctype_digit($val) && strpos($singleDBKey, '_name') !== false) {
+ $temp .= ' ' . $singleDBKey . " $LIKEOP ? OR";
+ $sql_params[] = '%' . $val . '%';
+ }
+ }
+ }
+ }
+
+ // Add the subcategories to the query
+ if ($key == 'cat') {
+ $result = $db->query('SELECT *
+ FROM {list_category}
+ WHERE category_id = ?', array($val));
+ $cat_details = $db->fetchRow($result);
+
+ $result = $db->query('SELECT *
+ FROM {list_category}
+ WHERE lft > ? AND rgt < ? AND project_id = ?', array($cat_details['lft'], $cat_details['rgt'], $cat_details['project_id']));
+ while ($row = $db->fetchRow($result)) {
+ $temp .= ' product_category = ? OR';
+ $sql_params[] = $row['category_id'];
+ }
+ }
+ }
+
+ if ($temp) {
+ $where[] = '(' . substr($temp, 0, -3) . ')'; # strip last ' OR' and 'AND'
+ }
+ }
+/// }}}
+
+ $order_keys = array(
+ 'id' => 't.task_id',
+ 'project' => 'project_title',
+ 'tasktype' => 'tasktype_name',
+ 'dateopened' => 'date_opened',
+ 'summary' => 'item_summary',
+ 'severity' => 'task_severity',
+ 'category' => 'lc.category_name',
+ 'status' => 'is_closed, item_status',
+ 'dueversion' => 'lvc.list_position',
+ 'duedate' => 'due_date',
+ 'progress' => 'percent_complete',
+ 'lastedit' => 'max_date',
+ 'priority' => 'task_priority',
+ 'openedby' => 'uo.real_name',
+ 'reportedin' => 't.product_version',
+ 'assignedto' => 'u.real_name',
+ 'dateclosed' => 't.date_closed',
+ 'os' => 'los.os_name',
+ 'votes' => 'num_votes',
+ 'attachments' => 'num_attachments',
+ 'comments' => 'num_comments',
+ 'private' => 'mark_private',
+ 'supertask' => 't.supertask_id',
+ );
+
+ // make sure that only columns can be sorted that are visible (and task severity, since it is always loaded)
+ $order_keys = array_intersect_key($order_keys, array_merge(array_flip($visible), array('severity' => 'task_severity')));
+
+ // Implementing setting "Default order by"
+ if (!array_key_exists('order', $args)) {
+ # now also for $proj->id=0 (allprojects)
+ $orderBy = $proj->prefs['sorting'][0]['field'];
+ $sort = $proj->prefs['sorting'][0]['dir'];
+ if (count($proj->prefs['sorting']) >1){
+ $orderBy2 =$proj->prefs['sorting'][1]['field'];
+ $sort2= $proj->prefs['sorting'][1]['dir'];
+ } else{
+ $orderBy2='severity';
+ $sort2='DESC';
+ }
+ } else {
+ $orderBy = $args['order'];
+ $sort = $args['sort'];
+ $orderBy2='severity';
+ $sort2='desc';
+ }
+
+ // TODO: Fix this! If something is already ordered by task_id, there's
+ // absolutely no use to even try to order by something else also.
+ $order_column[0] = $order_keys[Filters::enum(array_get($args, 'order', $orderBy), array_keys($order_keys))];
+ $order_column[1] = $order_keys[Filters::enum(array_get($args, 'order2', $orderBy2), array_keys($order_keys))];
+ $sortorder = sprintf('%s %s, %s %s, t.task_id ASC',
+ $order_column[0],
+ Filters::enum(array_get($args, 'sort', $sort), array('asc', 'desc')),
+ $order_column[1],
+ Filters::enum(array_get($args, 'sort2', $sort2), array('asc', 'desc'))
+ );
+
+ $having = array();
+ $dates = array('duedate' => 'due_date', 'changed' => $maxdatesql,
+ 'opened' => 'date_opened', 'closed' => 'date_closed');
+ foreach ($dates as $post => $db_key) {
+ $var = ($post == 'changed') ? 'having' : 'where';
+ if ($date = array_get($args, $post . 'from')) {
+ ${$var}[] = '(' . $db_key . ' >= ' . Flyspray::strtotime($date) . ')';
+ }
+ if ($date = array_get($args, $post . 'to')) {
+ ${$var}[] = '(' . $db_key . ' <= ' . Flyspray::strtotime($date) . ' AND ' . $db_key . ' > 0)';
+ }
+ }
+
+ if (array_get($args, 'string')) {
+ $words = explode(' ', strtr(array_get($args, 'string'), '()', ' '));
+ $comments = '';
+ $where_temp = array();
+
+ if (array_get($args, 'search_in_comments')) {
+ $comments .= " OR c.comment_text $LIKEOP ?";
+ }
+ if (array_get($args, 'search_in_details')) {
+ $comments .= " OR t.detailed_desc $LIKEOP ?";
+ }
+
+ foreach ($words as $word) {
+ $word=trim($word);
+ if($word==''){
+ continue;
+ }
+ $likeWord = '%' . str_replace('+', ' ', $word) . '%';
+ $where_temp[] = "(t.item_summary $LIKEOP ? OR t.task_id = ? $comments)";
+ array_push($sql_params, $likeWord, intval($word));
+ if (array_get($args, 'search_in_comments')) {
+ array_push($sql_params, $likeWord);
+ }
+ if (array_get($args, 'search_in_details')) {
+ array_push($sql_params, $likeWord);
+ }
+ }
+
+ if(count($where_temp)>0){
+ $where[] = '(' . implode((array_get($args, 'search_for_all') ? ' AND ' : ' OR '), $where_temp) . ')';
+ }
+ }
+
+ if ($user->isAnon()) {
+ $where[] = 't.mark_private = 0 AND p.others_view = 1';
+ if(array_key_exists('status', $args)){
+ if (in_array('closed', $args['status']) && !in_array('open', $args['status'])) {
+ $where[] = 't.is_closed = 1';
+ } elseif (in_array('open', $args['status']) && !in_array('closed', $args['status'])) {
+ $where[] = 't.is_closed = 0';
+ }
+ }
+ }
+
+ $where = (count($where)) ? 'WHERE ' . join(' AND ', $where) : '';
+
+ // Get the column names of table tasks for the group by statement
+ if (!strcasecmp($conf['database']['dbtype'], 'pgsql')) {
+ $groupby .= "p.project_title, p.project_is_active, ";
+ // Remove this after checking old PostgreSQL docs.
+ // 1 column from task table should be enough, after
+ // already grouping by task_id, there's no possibility
+ // to have anything more in that table to group by.
+ $groupby .= $db->getColumnNames('{tasks}', 't.task_id', 't.');
+ } else {
+ $groupby = 't.task_id';
+ }
+
+ $having = (count($having)) ? 'HAVING ' . join(' AND ', $having) : '';
+
+ // echo '<pre>' . print_r($args, true) . '</pre>';
+ // echo '<pre>' . print_r($cgroupbyarr, true) . '</pre>';
+ $cgroupby = count($cgroupbyarr) ? 'GROUP BY ' . implode(',', array_unique($cgroupbyarr)) : '';
+
+ $sqlcount = "SELECT COUNT(*) FROM (SELECT 1, t.task_id, t.date_opened, t.date_closed, t.last_edited_time
+ FROM $cfrom
+ $where
+ $cgroupby
+ $having) s";
+ $sqltext = "SELECT t.*, $select
+p.project_title, p.project_is_active
+FROM $from
+$where
+GROUP BY $groupby
+$having
+ORDER BY $sortorder";
+
+ // Very effective alternative with a little bit more work
+ // and if row_number() can be emulated in mysql. Idea:
+ // Move every join and other operation not needed in
+ // the inner clause to select rows to the outer query,
+ // and do the rest when we already know which rows
+ // are in the window to show. Got it to run constantly
+ // under 6000 ms.
+ /* Leave this for next version, don't have enough time for testing.
+ $sqlexperiment = "SELECT * FROM (
+SELECT row_number() OVER(ORDER BY task_id) AS rownum,
+t.*, $select p.project_title, p.project_is_active FROM $from
+$where
+GROUP BY $groupby
+$having
+ORDER BY $sortorder
+)
+t WHERE rownum BETWEEN $offset AND " . ($offset + $perpage);
+*/
+
+// echo '<pre>'.print_r($sql_params, true).'</pre>'; # for debugging
+// echo '<pre>'.$sqlcount.'</pre>'; # for debugging
+// echo '<pre>'.$sqltext.'</pre>'; # for debugging
+ $sql = $db->query($sqlcount, $sql_params);
+ $totalcount = $db->fetchOne($sql);
+
+# 20150313 peterdd: Do not override task_type with tasktype_name until we changed t.task_type to t.task_type_id! We need the id too.
+
+ $sql = $db->query($sqltext, $sql_params, $perpage, $offset);
+ // $sql = $db->query($sqlexperiment, $sql_params);
+ $tasks = $db->fetchAllArray($sql);
+ $id_list = array();
+ $limit = array_get($args, 'limit', -1);
+ $forbidden_tasks_count = 0;
+ foreach ($tasks as $key => $task) {
+ $id_list[] = $task['task_id'];
+ if (!$user->can_view_task($task)) {
+ unset($tasks[$key]);
+ $forbidden_tasks_count++;
+ }
+ }
+
+// Work on this is not finished until $forbidden_tasks_count is always zero.
+// echo "<pre>$offset : $perpage : $totalcount : $forbidden_tasks_count</pre>";
+ return array($tasks, $id_list, $totalcount, $forbidden_tasks_count);
+// # end alternative
+ }
+
+# end get_task_list
+} # end class
diff --git a/includes/class.csp.php b/includes/class.csp.php
new file mode 100644
index 0000000..ecf4541
--- /dev/null
+++ b/includes/class.csp.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ *
+ * A simple class for dynamic construction of Content-Security-Policy HTTP header string.
+ *
+ * This is just quick write to get the job for Flyspray done. May change completely!
+ *
+ * It does not check if the added rules are valid or make sense in the context and http request/response!
+ * So it is currently up to the code sections who use that class that the resulting csp string is correct.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
+ * @author peterdd
+ *
+ * @see https://www.w3.org/TR/CSP2/
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
+ * @see https://caniuse.com/#search=csp
+ *
+ * Example: $csp=new ContentSecurityPolicy(); $csp->add('default-src', "'none'"); $csp->add('style-src', "'self'"); $csp->emit();
+ */
+class ContentSecurityPolicy {
+
+ #private $csp=array();
+ public $csp=array();
+
+ # for debugging just to track which extension/plugins wants to add csp-entries
+ #public $history=array();
+
+ function __construct(){
+ $this->csp=array();
+ }
+
+ /**
+ * get the constructed concatenated value string part for the http Content-Security-Header
+ *
+ * TODO: maybe some syntax checks and logical verification when building the string.
+ *
+ * MAYBE: add optional parameter to get only a part like $csp->get('img-src')
+ * Alternatively the user can just access the currently public $csp->csp['img-src'] to get that values as array.
+ *
+ */
+ public function get(){
+ $out = '';
+ foreach ( $this->csp as $key => $values ) {
+ $out .= $key.' '.implode(' ', $values).'; ';
+ }
+ $out = trim($out, '; ');
+ return $out;
+ }
+
+ /**
+ * adds a value to a csp type
+ *
+ * @param type
+ * @param value single values for a type
+ *
+ * examples:
+ * $csp->add('default-src', "'self'"); # surrounding double quotes "" used to pass the single quotes
+ * $csp->add('img-src', 'mycdn.example.com'); # single quoted string ok
+ */
+ public function add($type, $value){
+ if( isset($this->csp[$type]) ) {
+ if( !in_array( $value, $this->csp[$type] ) ) {
+ $this->csp[$type][] = $value;
+ }
+ } else {
+ $this->csp[$type] = array($value);
+ }
+ #$this->history[]=debug_backtrace()[1];
+ }
+
+ /**
+ * sends the Content-Security-Policy http headers
+ */
+ public function emit() {
+ $string=$this->get();
+ header('Content-Security-Policy: '.$string );
+ # some older web browsers used vendor prefixes before csp got w3c recommendation.
+ # maybe use useragent string to detect who should receive this outdated vendor csp strings.
+ # for IE 10-11
+ header('X-Content-Security-Policy: '.$string );
+ # for Chrome 15-24, Safari 5.1-6, ..
+ header('X-WebKit-CSP: '.$string );
+ }
+
+ /**
+ * Put the csp as meta-tags in the HTML-head section.
+ *
+ * Q: What is the benefit of adding csp as meta tags too?
+ *
+ * I don't know, maybe this way the csp persist if someone saves a page to his harddrive for instance or if bad web proxies rip off csp http headers?
+ * Do webbrowsers store the CSP-HTTP header to the HTML-head as metatags automatically if there is no related metatag in the original page? Mhh..
+ */
+ public function getMeta() {
+ $string=$this->get();
+ $out= '<meta http-equiv="Content-Security-Policy" content="'.$string.'">';
+ # enable if you think it is necessary for your customers.
+ # older web browsers used vendor prefixes before csp2 got a w3c recommendation standard..
+ # maybe use useragent string to detect who should receive this outdated vendor csp strings.
+ # for IE 10-11
+ $out.= '<meta http-equiv="X-Content-Security-Policy" content="'.$string.'">';
+ # for Chrome 15-24, Safari 5.1-6, ..
+ $out.= '<meta http-equiv="X-WebKit-CSP" content="'.$string.'">';
+ return $out;
+ }
+
+}
diff --git a/includes/class.database.php b/includes/class.database.php
new file mode 100644
index 0000000..652cc98
--- /dev/null
+++ b/includes/class.database.php
@@ -0,0 +1,434 @@
+<?php
+/**
+ * Flyspray
+ *
+ * Database class class
+ *
+ * This class is a wrapper for ADOdb functions.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
+ * @package flyspray
+ * @author Tony Collins
+ * @author Cristian Rodriguez
+ */
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+class Database
+{
+ /**
+ * Table prefix, usually flyspray_
+ * @var string
+ * @access private
+ */
+ public $dbprefix;
+
+ /**
+ * Cache for queries done by cached_query()
+ * @var array
+ * @access private
+ * @see cached_query();
+ */
+ private $cache = array();
+
+ /**
+ * dblink
+ * adodb handler object
+ * @var object
+ * @access public
+ */
+ public $dblink = null;
+
+ /**
+ * Open a connection to the database quickly
+ * @param array $conf connection data
+ * @return void
+ */
+ public function dbOpenFast($conf)
+ {
+ if(!is_array($conf) || extract($conf, EXTR_REFS|EXTR_SKIP) < 5) {
+
+ die( 'Flyspray was unable to connect to the database. '
+ .'Check your settings in flyspray.conf.php');
+ }
+
+ $this->dbOpen($dbhost, $dbuser, $dbpass, $dbname, $dbtype, isset($dbprefix) ? $dbprefix : '');
+ }
+
+ /**
+ * Open a connection to the database and set connection parameters
+ * @param string $dbhost hostname where the database server uses
+ * @param string $dbuser username to connect to the database
+ * @param string $dbpass password to connect to the database
+ * @param string $dbname
+ * @param string $dbtype database driver to use, currently : "mysql", "mysqli", "pgsql"
+ * "pdo_mysql" and "pdo_pgsql" experimental
+ * @param string $dbprefix database prefix.
+ */
+ public function dbOpen($dbhost = '', $dbuser = '', $dbpass = '', $dbname = '', $dbtype = '', $dbprefix = '')
+ {
+
+ $this->dbtype = $dbtype;
+ $this->dbprefix = $dbprefix;
+ $ADODB_COUNTRECS = false;
+
+ # 20160408 peterdd: hack to enable database socket usage with adodb-5.20.3
+ # For instance on german 1und1 managed linux servers, e.g. $dbhost='localhost:/tmp/mysql5.sock'
+ if( ($dbtype=='mysqli' || $dbtype='pdo_mysql') && 'localhost:/'==substr($dbhost,0,11) ){
+ $dbsocket=substr($dbhost,10);
+ $dbhost='localhost';
+ if($dbtype=='mysqli'){
+ ini_set('mysqli.default_socket', $dbsocket );
+ }else{
+ ini_set('pdo_mysql.default_socket',$dbsocket);
+ }
+ }
+
+ # adodb for pdo is a bit different then the others at the moment (adodb 5.20.4)
+ # see http://adodb.org/dokuwiki/doku.php?id=v5:database:pdo
+ if($this->dbtype=='pdo_mysql'){
+ $this->dblink = ADOnewConnection('pdo');
+ $dsnString= 'host='.$dbhost.';dbname='.$dbname.';charset=utf8mb4';
+ $this->dblink->connect('mysql:' . $dsnString, $dbuser, $dbpass);
+ }else{
+ $this->dblink = ADOnewConnection($this->dbtype);
+ $this->dblink->connect($dbhost, $dbuser, $dbpass, $dbname);
+ }
+
+ if ($this->dblink === false || (!empty($this->dbprefix) && !preg_match('/^[a-z][a-z0-9_]+$/i', $this->dbprefix))) {
+
+ die('Flyspray was unable to connect to the database. '
+ .'Check your settings in flyspray.conf.php');
+ }
+ $this->dblink->setFetchMode(ADODB_FETCH_BOTH);
+
+ if($dbtype=='mysqli'){
+ $sinfo=$this->dblink->serverInfo();
+ if(version_compare($sinfo['version'], '5.5.3')>=0 ){
+ $this->dblink->setCharSet('utf8mb4');
+ }else{
+ $this->dblink->setCharSet('utf8');
+ }
+ }else{
+ $this->dblink->setCharSet('utf8');
+ }
+
+ // enable debug if constant DEBUG_SQL is defined.
+ !defined('DEBUG_SQL') || $this->dblink->debug = true;
+
+ if($dbtype === 'mysql' || $dbtype === 'mysqli') {
+ $dbinfo = $this->dblink->serverInfo();
+ if(isset($dbinfo['version']) && version_compare($dbinfo['version'], '5.0.2', '>=')) {
+ $this->dblink->execute("SET SESSION SQL_MODE='TRADITIONAL'");
+ }
+ }
+ }
+
+ /**
+ * Closes the database connection
+ * @return void
+ */
+ public function dbClose()
+ {
+ $this->dblink->close();
+ }
+
+ /**
+ * insert_ID
+ *
+ * @access public
+ */
+ public function insert_ID()
+ {
+ return $this->dblink->insert_ID();
+ }
+
+ /**
+ * countRows
+ * Returns the number of rows in a result
+ * @param object $result
+ * @access public
+ * @return int
+ */
+ public function countRows($result)
+ {
+ return (int) $result->recordCount();
+ }
+
+ /**
+ * affectedRows
+ *
+ * @access public
+ * @return int
+ */
+ public function affectedRows()
+ {
+ return (int) $this->dblink->affected_Rows();
+ }
+
+ /**
+ * fetchRow
+ *
+ * @param $result
+ * @access public
+ * @return void
+ */
+
+ public function fetchRow($result)
+ {
+ return $result->fetchRow();
+ }
+
+ /**
+ * fetchCol
+ *
+ * @param $result
+ * @param int $col
+ * @access public
+ * @return void
+ */
+
+ public function fetchCol($result, $col=0)
+ {
+ $tab = array();
+ while ($tmp = $result->fetchRow()) {
+ $tab[] = $tmp[$col];
+ }
+ return $tab;
+ }
+
+ /**
+ * query
+ *
+ * @param mixed $sql
+ * @param mixed $inputarr
+ * @param mixed $numrows
+ * @param mixed $offset
+ * @access public
+ * @return void
+ */
+
+ public function query($sql, $inputarr = false, $numrows = -1, $offset = -1)
+ {
+ // auto add $dbprefix where we have {table}
+ $sql = $this->_add_prefix($sql);
+ // remove conversions for MySQL
+ if (strcasecmp($this->dbtype, 'pgsql') != 0) {
+ $sql = str_replace('::int', '', $sql);
+ $sql = str_replace('::text', '', $sql);
+ }
+
+ $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
+
+ if (($numrows >= 0 ) or ($offset >= 0 )) {
+ /* adodb drivers are inconsisent with the casting of $numrows and $offset so WE
+ * cast to integer here anyway */
+ $result = $this->dblink->selectLimit($sql, (int) $numrows, (int) $offset, $inputarr);
+ } else {
+ $result = $this->dblink->execute($sql, $inputarr);
+ }
+
+ if (!$result) {
+
+ if(function_exists("debug_backtrace") && defined('DEBUG_SQL')) {
+ echo "<pre style='text-align: left;'>";
+ var_dump(debug_backtrace());
+ echo "</pre>";
+ }
+
+ $query_params = '';
+
+ if(is_array($inputarr) && count($inputarr)) {
+ $query_params = implode(',', array_map(array('Filters','noXSS'), $inputarr));
+ }
+
+ die(sprintf("Query {%s} with params {%s} failed! (%s)",
+ Filters::noXSS($sql), $query_params, Filters::noXSS($this->dblink->errorMsg())));
+
+ }
+
+ return $result;
+ }
+
+ /**
+ * cached_query
+ *
+ * @param mixed $idx
+ * @param mixed $sql
+ * @param array $sqlargs
+ * @access public
+ * @return array
+ */
+ public function cached_query($idx, $sql, $sqlargs = array())
+ {
+ if (isset($this->cache[$idx])) {
+ return $this->cache[$idx];
+ }
+
+ $sql = $this->query($sql, $sqlargs);
+ return ($this->cache[$idx] = $this->fetchAllArray($sql));
+ }
+
+ /**
+ * fetchOne
+ *
+ * @param $result
+ * @access public
+ * @return array
+ */
+ public function fetchOne($result)
+ {
+ $row = $this->fetchRow($result);
+ return (isset($row[0]) ? $row[0] : '');
+ }
+
+ /**
+ * fetchAllArray
+ *
+ * @param $result
+ * @access public
+ * @return array
+ */
+ public function fetchAllArray($result)
+ {
+ return $result->getArray();
+ }
+
+ /**
+ * groupBy
+ *
+ * This groups a result by a single column the way
+ * MySQL would do it. Postgre doesn't like the queries MySQL needs.
+ *
+ * @param object $result
+ * @param string $column
+ * @access public
+ * @return array process the returned array with foreach ($return as $row) {}
+ */
+ public function groupBy($result, $column)
+ {
+ $rows = array();
+ while ($row = $this->fetchRow($result)) {
+ $rows[$row[$column]] = $row;
+ }
+ return array_values($rows);
+ }
+
+ /**
+ * getColumnNames
+ *
+ * @param mixed $table
+ * @param mixed $alt
+ * @param mixed $prefix
+ * @access public
+ * @return void
+ */
+
+ public function getColumnNames($table, $alt, $prefix)
+ {
+ global $conf;
+
+ if (strcasecmp($conf['database']['dbtype'], 'pgsql')) {
+ return $alt;
+ }
+
+ $table = $this->_add_prefix($table);
+ $fetched_columns = $this->query('SELECT column_name FROM information_schema.columns WHERE table_name = ?',
+ array(str_replace('"', '', $table)));
+ $fetched_columns = $this->fetchAllArray($fetched_columns);
+
+ foreach ($fetched_columns as $key => $value)
+ {
+ $col_names[$key] = $prefix . $value[0];
+ }
+
+ $groupby = implode(', ', $col_names);
+
+ return $groupby;
+ }
+
+ /**
+ * replace
+ *
+ * Try to update a record,
+ * and if the record is not found,
+ * an insert statement is generated and executed.
+ *
+ * @param string $table
+ * @param array $field
+ * @param array $keys
+ * @param bool $autoquote
+ * @access public
+ * @return integer 0 on error, 1 on update. 2 on insert
+ */
+ public function replace($table, $field, $keys, $autoquote = true)
+ {
+ $table = $this->_add_prefix($table);
+ return $this->dblink->replace($table, $field, $keys, $autoquote);
+ }
+
+ /**
+ * Adds the table prefix
+ * @param string $sql_data table name or sql query
+ * @return string sql with correct,quoted table prefix
+ * @access private
+ * @since 0.9.9
+ */
+ private function _add_prefix($sql_data)
+ {
+ return preg_replace('/{([\w\-]*?)}/', $this->quoteIdentifier($this->dbprefix . '\1'), $sql_data);
+ }
+
+ /**
+ * Helper method to quote an indentifier
+ * (table or field name) with the database specific quote
+ * @param string $ident table or field name to be quoted
+ * @return string
+ * @access public
+ * @since 0.9.9
+ */
+ public function quoteIdentifier($ident)
+ {
+ return (string) $this->dblink->nameQuote . $ident . $this->dblink->nameQuote ;
+ }
+
+ /**
+ * Quote a string in a safe way to be entered to the database
+ * (for the very few cases we don't use prepared statements)
+ *
+ * @param string $string string to be quoted
+ * @return string quoted string
+ * @access public
+ * @since 0.9.9
+ * @notes please use this little as possible, always prefer prepared statements
+ */
+ public function qstr($string)
+ {
+ return $this->dblink->qstr($string, false);
+ }
+
+ /**
+ * fill_placeholders
+ * a convenience function to fill sql query placeholders
+ * according to the number of columns to be used.
+ * @param array $cols
+ * @param integer $additional generate N additional placeholders
+ * @access public
+ * @return string comma separated "?" placeholders
+ * @static
+ */
+ public function fill_placeholders($cols, $additional=0)
+ {
+ if(is_array($cols) && count($cols) && is_int($additional)) {
+
+ return join(',', array_fill(0, (count($cols) + $additional), '?'));
+
+ } else {
+ //this is not an user error, is a programmer error.
+ trigger_error("incorrect data passed to fill_placeholders", E_USER_ERROR);
+ }
+ }
+ // End of Database Class
+}
diff --git a/includes/class.effort.php b/includes/class.effort.php
new file mode 100644
index 0000000..984feeb
--- /dev/null
+++ b/includes/class.effort.php
@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * Class effort
+ *
+ * Task level Effort Tracking functionality.
+ */
+class effort
+{
+ const FORMAT_HOURS_COLON_MINUTES = 0; // Default value in database
+ const FORMAT_HOURS_SPACE_MINUTES = 1;
+ const FORMAT_HOURS_PLAIN = 2;
+ const FORMAT_HOURS_ONE_DECIMAL = 3;
+ const FORMAT_MINUTES = 4;
+ const FORMAT_DAYS_PLAIN = 5;
+ const FORMAT_DAYS_ONE_DECIMAL = 6;
+ const FORMAT_DAYS_PLAIN_HOURS_PLAIN = 7;
+ const FORMAT_DAYS_PLAIN_HOURS_ONE_DECIMAL = 8;
+ const FORMAT_DAYS_PLAIN_HOURS_COLON_MINUTES = 9;
+ const FORMAT_DAYS_PLAIN_HOURS_SPACE_MINUTES = 10;
+
+ private $_task_id;
+ private $_userId;
+ public $details;
+
+ /**
+ * Class Constructor: Requires the user id and task id as all effort is in context of the task.
+ *
+ * @param $task_id
+ * @param $user_id
+ */
+ public function __construct($task_id,$user_id)
+ {
+ $this->_task_id = $task_id;
+ $this->_userId = $user_id;
+ }
+
+ /**
+ * Manually add effort to the effort table for this issue / user.
+ *
+ * @param $effort_to_add int amount of effort in hh:mm to add to effort table.
+ */
+ public function addEffort($effort_to_add, $proj)
+ {
+ global $db;
+
+ # note: third parameter seem useless, not used by EditStringToSeconds().., maybe drop it..
+ $effort = self::editStringToSeconds($effort_to_add, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ if ($effort === FALSE) {
+ Flyspray::show_error(L('invalideffort'));
+ return false;
+ }
+
+ # quickfix to avoid useless table entries.
+ if($effort==0){
+ Flyspray::show_error(L('zeroeffort'));
+ return false;
+ } else{
+ $db->query('INSERT INTO {effort}
+ (task_id, date_added, user_id,start_timestamp,end_timestamp,effort)
+ VALUES ( ?, ?, ?, ?,?,? )',
+ array($this->_task_id, time(), $this->_userId,time(),time(),$effort)
+ );
+ return true;
+ }
+ }
+
+ /**
+ * Starts tracking effort for the current user against the current issue.
+ *
+ * @return bool Returns Success or Failure of the action.
+ */
+ public function startTracking()
+ {
+ global $db;
+
+ //check if the user is already tracking time against this task.
+ $result = $db->query('SELECT * FROM {effort} WHERE task_id ='.$this->_task_id.' AND user_id='.$this->_userId.' AND end_timestamp IS NULL;');
+ if($db->countRows($result)>0)
+ {
+ return false;
+ }
+ else
+ {
+ $db->query('INSERT INTO {effort}
+ (task_id, date_added, user_id,start_timestamp)
+ VALUES ( ?, ?, ?, ? )',
+ array ($this->_task_id, time(), $this->_userId,time()));
+
+ return true;
+ }
+ }
+
+ /**
+ * Stops tracking the current tracking request and then updates the actual hours field on the table, this
+ * is useful as both stops constant calculation from start/end timestamps and provides a quick aggregation
+ * method as we only need to deal with one field.
+ */
+ public function stopTracking()
+ {
+ global $db;
+
+ $time = time();
+
+
+ $sql = $db->query('SELECT start_timestamp FROM {effort} WHERE user_id='.$this->_userId.' AND task_id='.$this->_task_id.' AND end_timestamp IS NULL;');
+ $result = $db->fetchRow($sql);
+ $start_time = $result[0];
+ $seconds = $time - $start_time;
+
+ // Round to full minutes upwards.
+ $effort = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+
+ $sql = $db->query("UPDATE {effort} SET end_timestamp = ".$time.",effort = ".$effort." WHERE user_id=".$this->_userId." AND task_id=".$this->_task_id." AND end_timestamp IS NULL;");
+ }
+
+ /**
+ * Removes any outstanding tracking requests for this task for this user.
+ */
+ public function cancelTracking()
+ {
+ global $db;
+
+ # 2016-07-04: also remove invalid finished 0 effort entries that were accidently possible up to Flyspray 1.0-rc
+ $db->query('DELETE FROM {effort}
+ WHERE user_id='.$this->_userId.'
+ AND task_id='.$this->_task_id.'
+ AND (
+ end_timestamp IS NULL
+ OR (start_timestamp=end_timestamp AND effort=0)
+ );'
+ );
+ }
+
+ public function populateDetails()
+ {
+ global $db;
+
+ $this->details = $db->query('SELECT * FROM {effort} WHERE task_id ='.$this->_task_id.';');
+ }
+
+ public static function secondsToString($seconds, $factor, $format) {
+ if ($seconds == 0) {
+ return '';
+ }
+
+ $factor = ($factor == 0 ? 86400 : $factor);
+
+ switch ($format) {
+ case self::FORMAT_HOURS_COLON_MINUTES:
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+ $hours = floor($seconds / 3600);
+ $minutes = floor(($seconds - ($hours * 3600)) / 60);
+ return sprintf('%01u:%02u', $hours, $minutes);
+ break;
+ case self::FORMAT_HOURS_SPACE_MINUTES:
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+ $hours = floor($seconds / 3600);
+ $minutes = floor(($seconds - ($hours * 3600)) / 60);
+ if ($hours == 0) {
+ return sprintf('%u %s', $minutes, L('minuteabbrev'));
+ } else {
+ return sprintf('%u %s %u %s', $hours, L('hourabbrev'), $minutes, L('minuteabbrev'));
+ }
+ break;
+ case self::FORMAT_HOURS_PLAIN:
+ $hours = ceil($seconds / 3600);
+ return sprintf('%01u %s', $hours, ($hours == 1 ? L('hoursingular') : L('hourplural')));
+ break;
+ case self::FORMAT_HOURS_ONE_DECIMAL:
+ $hours = round(ceil($seconds * 10 / 3600) / 10, 1);
+ return sprintf('%01.1f %s', $hours, ($hours == 1 ? L('hoursingular') : L('hourplural')));
+ break;
+ case self::FORMAT_MINUTES:
+ $minutes = ceil($seconds / 60);
+ return sprintf('%01u %s', $minutes, L('minuteabbrev'));
+ break;
+ case self::FORMAT_DAYS_PLAIN:
+ $days = ceil($seconds / $factor);
+ return sprintf('%01u %s', $days, ($days == 1 ? L('manday') : L('mandays')));
+ break;
+ case self::FORMAT_DAYS_ONE_DECIMAL:
+ $days = round(ceil($seconds * 10 / $factor) / 10, 1);
+ return sprintf('%01.1f %s', $days, ($days == 1 ? L('manday') : L('mandays')));
+ break;
+ case self::FORMAT_DAYS_PLAIN_HOURS_PLAIN:
+ $days = floor($seconds / $factor);
+ $hours = ceil(($seconds - ($days * $factor)) / 3600);
+ if ($days == 0) {
+ return sprintf('%1u %s', $hours, L('hourabbrev'));
+ } else {
+ return sprintf('%u %s %1u %s', $days, L('mandayabbrev'), $hours, L('hourabbrev'));
+ }
+ break;
+ case self::FORMAT_DAYS_PLAIN_HOURS_ONE_DECIMAL:
+ $days = floor($seconds / $factor);
+ $hours = round(ceil(($seconds - ($days * $factor)) * 10 / 3600) / 10, 1);
+ if ($days == 0) {
+ return sprintf('%01.1f %s', $hours, L('hourabbrev'));
+ } else {
+ return sprintf('%u %s %01.1f %s', $days, L('mandayabbrev'), $hours, L('hourabbrev'));
+ }
+ break;
+ case self::FORMAT_DAYS_PLAIN_HOURS_COLON_MINUTES:
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+ $days = floor($seconds / $factor);
+ $hours = floor(($seconds - ($days * $factor)) / 3600);
+ $minutes = floor(($seconds - (($days * $factor) + ($hours * 3600))) / 60);
+ if ($days == 0) {
+ return sprintf('%01u:%02u', $hours, $minutes);
+ } else {
+ return sprintf('%u %s %01u:%02u', $days, L('mandayabbrev'), $hours, $minutes);
+ }
+ break;
+ case self::FORMAT_DAYS_PLAIN_HOURS_SPACE_MINUTES:
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+ $days = floor($seconds / $factor);
+ $hours = floor(($seconds - ($days * $factor)) / 3600);
+ $minutes = floor(($seconds - (($days * $factor) + ($hours * 3600))) / 60);
+ if ($days == 0) {
+ return sprintf('%u %s %u %s', $hours, L('hourabbrev'), $minutes, L('minuteabbrev'));
+ } else {
+ return sprintf('%u %s %u %s %u %s', $days, L('mandayabbrev'), $hours, L('hourabbrev'), $minutes, L('minuteabbrev'));
+ }
+ break;
+ default:
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+ $hours = floor($seconds / 3600);
+ $minutes = floor(($seconds - ($hours * 3600)) / 60);
+ return sprintf('%01u:%02u', $hours, $minutes);
+ }
+ }
+
+ public static function secondsToEditString($seconds, $factor, $format) {
+ $factor = ($factor == 0 ? 86400 : $factor);
+
+ // Adjust seconds to be evenly dividable by 60, so
+ // 3595 -> 3600, floor can be safely used for minutes in formats
+ // and the result will be 1:00 instead of 0:60 (if ceil would be used).
+
+ $seconds = ($seconds % 60 == 0 ? $seconds : floor($seconds / 60) * 60 + 60);
+
+ switch ($format) {
+ case self::FORMAT_HOURS_COLON_MINUTES:
+ case self::FORMAT_HOURS_SPACE_MINUTES:
+ case self::FORMAT_HOURS_PLAIN:
+ case self::FORMAT_HOURS_ONE_DECIMAL:
+ case self::FORMAT_MINUTES:
+ $hours = floor($seconds / 3600);
+ $minutes = floor(($seconds - ($hours * 3600)) / 60);
+ return sprintf('%01u:%02u', $hours, $minutes);
+ break;
+ case self::FORMAT_DAYS_PLAIN:
+ case self::FORMAT_DAYS_ONE_DECIMAL:
+ case self::FORMAT_DAYS_PLAIN_HOURS_PLAIN:
+ case self::FORMAT_DAYS_PLAIN_HOURS_ONE_DECIMAL:
+ case self::FORMAT_DAYS_PLAIN_HOURS_COLON_MINUTES:
+ case self::FORMAT_DAYS_PLAIN_HOURS_SPACE_MINUTES:
+ $days = floor($seconds / $factor);
+ $hours = floor(($seconds - ($days * $factor)) / 3600);
+ $minutes = floor(($seconds - ($hours * 3600)) / 60);
+ if ($days == 0) {
+ return sprintf('%01u:%02u', $hours, $minutes);
+ } else {
+ return sprintf('%u %02u:%02u', $days, $hours, $minutes);
+ }
+ break;
+ default:
+ $hours = floor($seconds / 3600);
+ $minutes = floor(($seconds - (($days * $factor) + ($hours * 3600))) / 60);
+ return sprintf('%01u:%02u', $hours, $minutes);
+ }
+ }
+
+ public static function editStringToSeconds($string, $factor, $format) {
+ if (!isset($string) || empty($string)) {
+ return 0;
+ }
+
+ $factor = ($factor == 0 ? 86400 : $factor);
+
+ $matches = array();
+ if (preg_match('/^((\d+)\s)?(\d+)(:(\d{2}))?$/', $string, $matches) !== 1) {
+ return FALSE;
+ }
+
+ if (!isset($matches[2])) {
+ $matches[2] = 0;
+ }
+
+ if (!isset($matches[5])) {
+ $matches[5] = 0;
+ } else {
+ if ($matches[5] > 59) {
+ return FALSE;
+ }
+ }
+
+ $effort = ($matches[2] * $factor) + ($matches[3] * 3600) + ($matches[5] * 60);
+ return $effort;
+ }
+}
diff --git a/includes/class.flyspray.php b/includes/class.flyspray.php
new file mode 100644
index 0000000..1dc2352
--- /dev/null
+++ b/includes/class.flyspray.php
@@ -0,0 +1,1471 @@
+<?php
+/**
+ * Flyspray
+ *
+ * Flyspray class
+ *
+ * This script contains all the functions we use often in
+ * Flyspray to do miscellaneous things.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
+ * @package flyspray
+ * @author Tony Collins
+ * @author Florian Schmitz
+ * @author Cristian Rodriguez
+ */
+
+class Flyspray
+{
+
+ /**
+ * Current Flyspray version. Change this for each release. Don't forget!
+ * @access public
+ * @var string
+ * For github development use e.g. '1.0-beta dev' ; Flyspray::base_version() currently splits on the ' ' ...
+ * For making github release use e.g. '1.0-beta' here.
+ * For online version check www.flyspray.org/version.txt use e.g. '1.0-beta'
+ * For making releases on github use github's recommended versioning e.g. 'v1.0-beta' --> release files are then named v1.0-beta.zip and v1.0-beta.tar.gz and unzips to a flyspray-1.0-beta/ directory.
+ * Well, looks like a mess but hopefully consolidate this in future. Maybe use version_compare() everywhere in future instead of an own invented Flyspray::base_version()
+ */
+ public $version = '1.0-rc9';
+
+ /**
+ * Flyspray preferences
+ * @access public
+ * @var array
+ */
+ public $prefs = array();
+
+ /**
+ * Max. file size for file uploads. 0 = no uploads allowed
+ * @access public
+ * @var integer
+ */
+ public $max_file_size = 0;
+
+ /**
+ * List of projects the user is allowed to view
+ * @access public
+ * @var array
+ */
+ public $projects = array();
+
+ /**
+ * List of severities. Loaded in i18n.inc.php
+ * @access public
+ * @var array
+ */
+ public $severities = array();
+
+ /**
+ * List of priorities. Loaded in i18n.inc.php
+ * @access public
+ * @var array
+ */
+ public $priorities = array();
+
+ /**
+ * Constructor, starts session, loads settings
+ * @access private
+ * @return void
+ * @version 1.0
+ */
+ public function __construct()
+ {
+ global $db;
+
+ $this->startSession();
+
+ $res = $db->query('SELECT pref_name, pref_value FROM {prefs}');
+
+ while ($row = $db->fetchRow($res)) {
+ $this->prefs[$row['pref_name']] = $row['pref_value'];
+ }
+
+ $this->setDefaultTimezone();
+
+ $sizes = array();
+ foreach (array(ini_get('memory_limit'), ini_get('post_max_size'), ini_get('upload_max_filesize')) as $val) {
+ if($val === '-1'){
+ // unlimited value in php configuration
+ $val = PHP_INT_MAX;
+ }
+ if (!$val || $val < 0) {
+ continue;
+ }
+
+ $last = strtolower($val{strlen($val)-1});
+ $val = trim($val, 'gGmMkK');
+ switch ($last) {
+ // The 'G' modifier is available since PHP 5.1.0
+ case 'g':
+ $val *= 1024;
+ case 'm':
+ $val *= 1024;
+ case 'k':
+ $val *= 1024;
+ }
+
+ $sizes[] = $val;
+ }
+ clearstatcache();
+ $this->max_file_size = (
+ (bool) ini_get('file_uploads')
+ && is_file(BASEDIR.DIRECTORY_SEPARATOR.'attachments'.DIRECTORY_SEPARATOR.'index.html')
+ && is_writable(BASEDIR.DIRECTORY_SEPARATOR.'attachments')
+ ) ? round((min($sizes)/1024/1024), 1) : 0;
+ }
+
+ protected function setDefaultTimezone()
+ {
+ $default_timezone = isset($this->prefs['default_timezone']) && !empty($this->prefs['default_timezone']) ? $this->prefs['default_timezone'] : 'UTC';
+ // set the default time zone - this will be redefined as we go
+ define('DEFAULT_TIMEZONE',$default_timezone);
+ date_default_timezone_set(DEFAULT_TIMEZONE);
+ }
+
+ public static function base_version($version)
+ {
+ if (strpos($version, ' ') === false) {
+ return $version;
+ }
+ return substr($version, 0, strpos($version, ' '));
+ }
+
+ public static function get_config_path($basedir = BASEDIR)
+ {
+ $cfile = $basedir . '/flyspray.conf.php';
+ if (is_readable($hostconfig = sprintf('%s/%s.conf.php', $basedir, $_SERVER['SERVER_NAME']))) {
+ $cfile = $hostconfig;
+ }
+ return $cfile;
+ }
+
+ /**
+ * Redirects the browser to the page in $url
+ * This function is based on PEAR HTTP class
+ * @param string $url
+ * @param bool $exit
+ * @param bool $rfc2616
+ * @license BSD
+ * @access public static
+ * @return bool
+ * @version 1.0
+ */
+ public static function redirect($url, $exit = true, $rfc2616 = true)
+ {
+
+ @ob_clean();
+
+ if (isset($_SESSION) && count($_SESSION)) {
+ session_write_close();
+ }
+
+ if (headers_sent()) {
+ die('Headers are already sent, this should not have happened. Please inform Flyspray developers.');
+ }
+
+ $url = Flyspray::absoluteURI($url);
+
+ if($_SERVER['REQUEST_METHOD']=='POST' && version_compare(PHP_VERSION, '5.4.0')>=0 ) {
+ http_response_code(303);
+ }
+ header('Location: '. $url);
+
+ if ($rfc2616 && isset($_SERVER['REQUEST_METHOD']) &&
+ $_SERVER['REQUEST_METHOD'] != 'HEAD') {
+ $url = htmlspecialchars($url, ENT_QUOTES, 'utf-8');
+ printf('%s to: <a href="%s">%s</a>.', eL('Redirect'), $url, $url);
+ }
+ if ($exit) {
+ exit;
+ }
+
+ return true;
+ }
+
+ /**
+ * Absolute URI (This function is part of PEAR::HTTP licensed under the BSD) {{{
+ *
+ * This function returns the absolute URI for the partial URL passed.
+ * The current scheme (HTTP/HTTPS), host server, port, current script
+ * location are used if necessary to resolve any relative URLs.
+ *
+ * Offsets potentially created by PATH_INFO are taken care of to resolve
+ * relative URLs to the current script.
+ *
+ * You can choose a new protocol while resolving the URI. This is
+ * particularly useful when redirecting a web browser using relative URIs
+ * and to switch from HTTP to HTTPS, or vice-versa, at the same time.
+ *
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @static
+ * @access public
+ * @return string The absolute URI.
+ * @param string $url Absolute or relative URI the redirect should go to.
+ * @param string $protocol Protocol to use when redirecting URIs.
+ * @param integer $port A new port number.
+ */
+ public static function absoluteURI($url = null, $protocol = null, $port = null)
+ {
+ // filter CR/LF
+ $url = str_replace(array("\r", "\n"), ' ', $url);
+
+ // Mess around with already absolute URIs
+ if (preg_match('!^([a-z0-9]+)://!i', $url)) {
+ if (empty($protocol) && empty($port)) {
+ return $url;
+ }
+ if (!empty($protocol)) {
+ $url = $protocol .':'. end($array = explode(':', $url, 2));
+ }
+ if (!empty($port)) {
+ $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i',
+ '\1:'. $port, $url);
+ }
+ return $url;
+ }
+
+ $host = 'localhost';
+ if (!empty($_SERVER['HTTP_HOST'])) {
+ list($host) = explode(':', $_SERVER['HTTP_HOST']);
+
+ if (strpos($_SERVER['HTTP_HOST'], ':') !== false && !isset($port)) {
+ $port = explode(':', $_SERVER['HTTP_HOST']);
+ }
+ } elseif (!empty($_SERVER['SERVER_NAME'])) {
+ list($host) = explode(':', $_SERVER['SERVER_NAME']);
+ }
+
+ if (empty($protocol)) {
+ if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) {
+ $protocol = 'https';
+ } else {
+ $protocol = 'http';
+ }
+ if (!isset($port) || $port != intval($port)) {
+ $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
+ }
+ }
+
+ if ($protocol == 'http' && $port == 80) {
+ unset($port);
+ }
+ if ($protocol == 'https' && $port == 443) {
+ unset($port);
+ }
+
+ $server = $protocol .'://'. $host . (isset($port) ? ':'. $port : '');
+
+
+ if (!strlen($url) || $url{0} == '?' || $url{0} == '#') {
+ $uri = isset($_SERVER['REQUEST_URI']) ?
+ $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF'];
+ if ($url && $url{0} == '?' && false !== ($q = strpos($uri, '?'))) {
+ $url = substr($uri, 0, $q) . $url;
+ } else {
+ $url = $uri . $url;
+ }
+ }
+
+ if ($url{0} == '/') {
+ return $server . $url;
+ }
+
+ // Check for PATH_INFO
+ if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO']) &&
+ $_SERVER['PHP_SELF'] != $_SERVER['PATH_INFO']) {
+ $path = dirname(substr($_SERVER['PHP_SELF'], 0, -strlen($_SERVER['PATH_INFO'])));
+ } else {
+ $path = dirname($_SERVER['PHP_SELF']);
+ }
+
+ if (substr($path = strtr($path, '\\', '/'), -1) != '/') {
+ $path .= '/';
+ }
+
+ return $server . $path . $url;
+ }
+
+ /**
+ * Test to see if user resubmitted a form.
+ * Checks only newtask and addcomment actions.
+ * @return bool true if user has submitted the same action within less than 6 hours, false otherwise
+ * @access public static
+ * @version 1.0
+ */
+ public static function requestDuplicated()
+ {
+ // garbage collection -- clean entries older than 6 hrs
+ $now = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
+ if (!empty($_SESSION['requests_hash'])) {
+ foreach ($_SESSION['requests_hash'] as $key => $val) {
+ if ($val < $now-6*60*60) {
+ unset($_SESSION['requests_hash'][$key]);
+ }
+ }
+ }
+
+ if (count($_POST)) {
+
+ if (preg_match('/^newtask.newtask|details.addcomment$/', Post::val('action', '')))
+ {
+ $currentrequest = md5(serialize($_POST));
+ if (!empty($_SESSION['requests_hash'][$currentrequest])) {
+ return true;
+ }
+ $_SESSION['requests_hash'][$currentrequest] = time();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets all information about a task (and caches information if wanted)
+ * @param integer $task_id
+ * @param bool $cache_enabled
+ * @access public static
+ * @return mixed an array with all taskdetails or false on failure
+ * @version 1.0
+ */
+ public static function getTaskDetails($task_id, $cache_enabled = false)
+ {
+ global $db, $fs;
+
+ static $cache = array();
+
+ if (isset($cache[$task_id]) && $cache_enabled) {
+ return $cache[$task_id];
+ }
+
+ //for some reason, task_id is not here
+ // run away immediately..
+ if(!is_numeric($task_id)) {
+ return false;
+ }
+
+ $get_details = $db->query('SELECT t.*, p.*,
+ c.category_name, c.category_owner, c.lft, c.rgt, c.project_id as cproj,
+ o.os_name,
+ r.resolution_name,
+ tt.tasktype_name,
+ vr.version_name AS reported_version_name,
+ vd.version_name AS due_in_version_name,
+ uo.real_name AS opened_by_name,
+ ue.real_name AS last_edited_by_name,
+ uc.real_name AS closed_by_name,
+ lst.status_name AS status_name
+ FROM {tasks} t
+ LEFT JOIN {projects} p ON t.project_id = p.project_id
+ LEFT JOIN {list_category} c ON t.product_category = c.category_id
+ LEFT JOIN {list_os} o ON t.operating_system = o.os_id
+ LEFT JOIN {list_resolution} r ON t.resolution_reason = r.resolution_id
+ LEFT JOIN {list_tasktype} tt ON t.task_type = tt.tasktype_id
+ LEFT JOIN {list_version} vr ON t.product_version = vr.version_id
+ LEFT JOIN {list_version} vd ON t.closedby_version = vd.version_id
+ LEFT JOIN {list_status} lst ON t.item_status = lst.status_id
+ LEFT JOIN {users} uo ON t.opened_by = uo.user_id
+ LEFT JOIN {users} ue ON t.last_edited_by = ue.user_id
+ LEFT JOIN {users} uc ON t.closed_by = uc.user_id
+ WHERE t.task_id = ?', array($task_id));
+
+ if (!$db->countRows($get_details)) {
+ return false;
+ }
+
+ if ($get_details = $db->fetchRow($get_details)) {
+ $get_details += array('severity_name' => $get_details['task_severity']==0 ? '' : $fs->severities[$get_details['task_severity']]);
+ $get_details += array('priority_name' => $get_details['task_priority']==0 ? '' : $fs->priorities[$get_details['task_priority']]);
+ }
+
+ $get_details['tags'] = Flyspray::getTags($task_id);
+
+ $get_details['assigned_to'] = $get_details['assigned_to_name'] = array();
+ if ($assignees = Flyspray::getAssignees($task_id, true)) {
+ $get_details['assigned_to'] = $assignees[0];
+ $get_details['assigned_to_name'] = $assignees[1];
+ }
+
+ /**
+ * prevent RAM growing array like creating 100000 tasks with Backend::create_task() in a loop (Tests)
+ * Costs maybe some SQL queries if getTaskDetails is called first without $cache_enabled
+ * and later with $cache_enabled within same request
+ */
+ if($cache_enabled){
+ $cache[$task_id] = $get_details;
+ }
+ return $get_details;
+ }
+
+ /**
+ * Returns a list of all projects
+ * @param bool $active_only show only active projects
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ // FIXME: $active_only would not work since the templates are accessing the returned array implying to be sortyed by project id, which is aparently wrong and error prone ! Same applies to the case when a project was deleted, causing a shift in the project id sequence, hence -> severe bug!
+ # comment by peterdd 20151012: reenabled param active_only with false as default. I do not see a problem within current Flyspray version. But consider using $fs->projects when possible, saves this extra sql request.
+ public static function listProjects($active_only = false)
+ {
+ global $db;
+ $query = 'SELECT project_id, project_title, project_is_active FROM {projects}';
+
+ if ($active_only) {
+ $query .= ' WHERE project_is_active = 1';
+ }
+
+ $query .= ' ORDER BY project_is_active DESC, project_id DESC'; # active first, latest projects first for option groups and new projects are probably the most used.
+
+ $sql = $db->query($query);
+ return $db->fetchAllArray($sql);
+ }
+
+ /**
+ * Returns a list of all themes
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function listThemes()
+ {
+ $themes = array();
+ $dirname = dirname(dirname(__FILE__));
+ if ($handle = opendir($dirname . '/themes/')) {
+ while (false !== ($file = readdir($handle))) {
+ if (substr($file,0,1) != '.' && is_dir("$dirname/themes/$file")
+ && (is_file("$dirname/themes/$file/theme.css") || is_dir("$dirname/themes/$file/templates"))
+ ) {
+ $themes[] = $file;
+ }
+ }
+ closedir($handle);
+ }
+
+ sort($themes);
+ # always put the full default Flyspray theme first, [0] works as fallback in class Tpl->setTheme()
+ array_unshift($themes, 'CleanFS');
+ $themes = array_unique($themes);
+ return $themes;
+ }
+
+ /**
+ * Returns a list of global groups or a project's groups
+ * @param integer $proj_id
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function listGroups($proj_id = 0)
+ {
+ global $db;
+ $res = $db->query('SELECT g.*, COUNT(uig.user_id) AS users
+ FROM {groups} g
+ LEFT JOIN {users_in_groups} uig ON uig.group_id=g.group_id
+ WHERE project_id = ?
+ GROUP BY g.group_id
+ ORDER BY g.group_id ASC', array($proj_id));
+ return $db->fetchAllArray($res);
+ }
+
+ /**
+ * Returns a list of a all users
+ * @access public static
+ * @param array $opts optional filter which fields (or group of fields) are needed, more may be added later (sorting, where ..)
+ * @return array
+ * @version 1.0
+ */
+ public static function listUsers($opts=array())
+ {
+ global $db;
+
+ if( empty($opts) || !isset($opts['stats']) ){
+
+ $res = $db->query('SELECT account_enabled, user_id, user_name, real_name,
+ email_address, jabber_id, oauth_provider, oauth_uid,
+ notify_type, notify_own, notify_online,
+ tasks_perpage, lang_code, time_zone, dateformat, dateformat_extended,
+ register_date, login_attempts, lock_until,
+ profile_image, hide_my_email, last_login
+ FROM {users}
+ ORDER BY account_enabled DESC, user_name ASC');
+
+ } else {
+ # Well, this is a big and slow query, but the current solution I found.
+ # If you know a more elegant for calculating user stats from the different tables with one query let us know!
+ $res = $db->query('
+SELECT
+MIN(u.account_enabled) AS account_enabled,
+MIN(u.user_id) AS user_id,
+MIN(u.user_name) AS user_name,
+MIN(u.real_name) AS real_name,
+MIN(u.email_address) AS email_address,
+MIN(u.jabber_id) AS jabber_id,
+MIN(u.oauth_provider) AS oauth_provider,
+MIN(u.oauth_uid) AS oauth_uid,
+MIN(u.notify_type) AS notify_type,
+MIN(u.notify_own) AS notify_own,
+MIN(u.notify_online) AS notify_online,
+MIN(u.tasks_perpage) AS tasks_perpage,
+MIN(u.lang_code) AS lang_code,
+MIN(u.time_zone) AS time_zone,
+MIN(u.dateformat) AS dateformat,
+MIN(u.dateformat_extended) AS dateformat_extended,
+MIN(u.register_date) AS register_date,
+MIN(u.login_attempts) AS login_attempts,
+MIN(u.lock_until) AS lock_until,
+MIN(u.profile_image) AS profile_image,
+MIN(u.hide_my_email) AS hide_my_email,
+MIN(u.last_login) AS last_login,
+SUM(countopen) AS countopen,
+SUM(countclose) AS countclose,
+SUM(countlastedit) AS countlastedit,
+SUM(comments) AS countcomments,
+SUM(assigned) AS countassign,
+SUM(watching) AS countwatching,
+SUM(votes) AS countvotes
+FROM
+( SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ COUNT(topen.opened_by) AS countopen, 0 AS countclose, 0 AS countlastedit, 0 AS comments, 0 AS assigned, 0 AS watching, 0 AS votes
+ FROM {users} u
+ LEFT JOIN {tasks} topen ON topen.opened_by=u.user_id
+ GROUP BY u.user_id
+UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, COUNT(tclose.closed_by) AS countclose, 0, 0, 0, 0, 0
+ FROM {users} u
+ LEFT JOIN {tasks} tclose ON tclose.closed_by=u.user_id
+ GROUP BY u.user_id
+UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, 0, COUNT(tlast.last_edited_by) AS countlastedit, 0, 0, 0, 0
+ FROM {users} u
+ LEFT JOIN {tasks} tlast ON tlast.last_edited_by=u.user_id
+ GROUP BY u.user_id
+UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, 0, 0, COUNT(c.user_id) AS comments, 0, 0, 0
+ FROM {users} u
+ LEFT JOIN {comments} c ON c.user_id=u.user_id
+ GROUP BY u.user_id
+ UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, 0, 0, 0, COUNT(a.user_id) AS assigned, 0, 0
+ FROM {users} u
+ LEFT JOIN {assigned} a ON a.user_id=u.user_id
+ GROUP BY u.user_id
+UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, 0, 0, 0, 0, COUNT(n.user_id) AS watching, 0
+ FROM {users} u
+ LEFT JOIN {notifications} n ON n.user_id=u.user_id
+ GROUP BY u.user_id
+UNION
+ SELECT u.account_enabled, u.user_id, u.user_name, u.real_name,
+ u.email_address, u.jabber_id, u.oauth_provider, u.oauth_uid,
+ u.notify_type, u.notify_own, u.notify_online,
+ u.tasks_perpage, u.lang_code, u.time_zone, u.dateformat, u.dateformat_extended,
+ u.register_date, u.login_attempts, u.lock_until,
+ u.profile_image, u.hide_my_email, u.last_login,
+ 0, 0, 0, 0, 0, 0, COUNT(v.user_id) AS votes
+ FROM {users} u
+ LEFT JOIN {votes} v ON v.user_id=u.user_id
+ GROUP BY u.user_id
+) u
+GROUP BY u.user_id
+ORDER BY MIN(u.account_enabled) DESC, MIN(u.user_name) ASC');
+ }
+
+ return $db->fetchAllArray($res);
+ }
+
+ /**
+ * Returns a list of installed languages
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function listLangs()
+ {
+ return str_replace('.php', '', array_map('basename', glob_compat(BASEDIR ."/lang/[a-zA-Z]*.php")));
+
+ }
+
+ /**
+ * Saves an event to the {history} db table
+ * @param integer $task_id
+ * @param integer $type
+ * @param string $newvalue
+ * @param string $oldvalue
+ * @param string $field
+ * @param integer $time for synchronisation with other functions
+ * @access public static
+ * @return void
+ * @version 1.0
+ */
+ public static function logEvent($task_id, $type, $newvalue = '', $oldvalue = '', $field = '', $time = null)
+ {
+ global $db, $user;
+
+ // This function creates entries in the history table. These are the event types:
+ // 0: Fields changed in a task
+ // 1: New task created
+ // 2: Task closed
+ // 3: Task edited (for backwards compatibility with events prior to the history system)
+ // 4: Comment added
+ // 5: Comment edited
+ // 6: Comment deleted
+ // 7: Attachment added
+ // 8: Attachment deleted
+ // 9: User added to notification list
+ // 10: User removed from notification list
+ // 11: Related task added to this task
+ // 12: Related task removed from this task
+ // 13: Task re-opened
+ // 14: Task assigned to user / re-assigned to different user / Unassigned
+ // 15: This task was added to another task's related list
+ // 16: This task was removed from another task's related list
+ // 17: Reminder added
+ // 18: Reminder deleted
+ // 19: User took ownership
+ // 20: Closure request made
+ // 21: Re-opening request made
+ // 22: Adding a new dependency
+ // 23: This task added as a dependency of another task
+ // 24: Removing a dependency
+ // 25: This task removed from another task's dependency list
+ // 26: Task was made private
+ // 27: Task was made public
+ // 28: PM request denied
+ // 29: User added to the list of assignees
+ // 30: New user registration
+ // 31: User deletion
+ // 32: Add new subtask
+ // 33: Remove Subtask
+ // 34: Add new parent
+ // 35: Remove parent
+
+ $query_params = array(intval($task_id), intval($user->id),
+ ((!is_numeric($time)) ? time() : $time),
+ $type, $field, $oldvalue, $newvalue);
+
+ if($db->query('INSERT INTO {history} (task_id, user_id, event_date, event_type, field_changed,
+ old_value, new_value) VALUES (?, ?, ?, ?, ?, ?, ?)', $query_params)) {
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds an admin or project manager request to the database
+ * @param integer $type 1: Task close, 2: Task re-open, 3: Pending user registration
+ * @param integer $project_id
+ * @param integer $task_id
+ * @param integer $submitter
+ * @param string $reason
+ * @access public static
+ * @return void
+ * @version 1.0
+ */
+ public static function adminRequest($type, $project_id, $task_id, $submitter, $reason)
+ {
+ global $db;
+ $db->query('INSERT INTO {admin_requests} (project_id, task_id, submitted_by, request_type, reason_given, time_submitted, deny_reason)
+ VALUES (?, ?, ?, ?, ?, ?, ?)',
+ array($project_id, $task_id, $submitter, $type, $reason, time(), ''));
+ }
+
+ /**
+ * Checks whether or not there is an admin request for a task
+ * @param integer $type 1: Task close, 2: Task re-open, 3: Pending user registration
+ * @param integer $task_id
+ * @access public static
+ * @return bool
+ * @version 1.0
+ */
+ public static function adminRequestCheck($type, $task_id)
+ {
+ global $db;
+
+ $check = $db->query("SELECT *
+ FROM {admin_requests}
+ WHERE request_type = ? AND task_id = ? AND resolved_by = 0",
+ array($type, $task_id));
+ return (bool)($db->countRows($check));
+ }
+
+ /**
+ * Gets all user details of a user
+ * @param integer $user_id
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function getUserDetails($user_id)
+ {
+ global $db;
+
+ // Get current user details. We need this to see if their account is enabled or disabled
+ $result = $db->query('SELECT * FROM {users} WHERE user_id = ?', array(intval($user_id)));
+ return $db->fetchRow($result);
+ }
+
+ /**
+ * Gets all information about a group
+ * @param integer $group_id
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function getGroupDetails($group_id)
+ {
+ global $db;
+ $sql = $db->query('SELECT * FROM {groups} WHERE group_id = ?', array($group_id));
+ return $db->fetchRow($sql);
+ }
+
+ /**
+ * Crypt a password with the method set in the configfile
+ * @param string $password
+ * @access public static
+ * @return string
+ * @version 1.0
+ */
+ public static function cryptPassword($password)
+ {
+ global $conf;
+
+ # during install e.g. not set
+ if(isset($conf['general']['passwdcrypt'])){
+ $pwcrypt = strtolower($conf['general']['passwdcrypt']);
+ }else{
+ $pwcrypt='';
+ }
+
+ # sha1, md5, sha512 are unsalted, hashing methods, not suited for storing passwords anymore.
+ # Use password_hash(), that adds random salt, customizable rounds and customizable hashing algorithms.
+ if ($pwcrypt == 'sha1') {
+ return sha1($password);
+ } elseif ($pwcrypt == 'md5') {
+ return md5($password);
+ } elseif ($pwcrypt == 'sha512') {
+ return hash('sha512', $password);
+ } elseif ($pwcrypt =='argon2i' && version_compare(PHP_VERSION,'7.2.0')>=0){
+ # php7.2+
+ return password_hash($password, PASSWORD_ARGON2I);
+ } else {
+ $bcryptoptions=array('cost'=>14);
+ return password_hash($password, PASSWORD_BCRYPT, $bcryptoptions);
+ }
+ }
+
+ /**
+ * Check if a user provided the right credentials
+ * @param string $username
+ * @param string $password
+ * @param string $method '', 'oauth', 'ldap', 'native'
+ * @access public static
+ * @return integer user_id on success, 0 if account or user is disabled, -1 if password is wrong
+ * @version 1.0
+ */
+ public static function checkLogin($username, $password, $method = 'native')
+ {
+ global $db;
+
+ $email_address = $username; //handle multiple email addresses
+ $temp = $db->query("SELECT id FROM {user_emails} WHERE email_address = ?",$email_address);
+ $user_id = $db->fetchRow($temp);
+ $user_id = $user_id["id"];
+
+ $result = $db->query("SELECT uig.*, g.group_open, u.account_enabled, u.user_pass,
+ lock_until, login_attempts
+ FROM {users_in_groups} uig
+ LEFT JOIN {groups} g ON uig.group_id = g.group_id
+ LEFT JOIN {users} u ON uig.user_id = u.user_id
+ WHERE u.user_id = ? OR u.user_name = ? AND g.project_id = ?
+ ORDER BY g.group_id ASC", array($user_id, $username, 0));
+
+ $auth_details = $db->fetchRow($result);
+
+ if($auth_details === false) {
+ return -2;
+ }
+ if(!$result || !count($auth_details)) {
+ return 0;
+ }
+
+ if ($auth_details['lock_until'] > 0 && $auth_details['lock_until'] < time()) {
+ $db->query('UPDATE {users} SET lock_until = 0, account_enabled = 1, login_attempts = 0
+ WHERE user_id = ?', array($auth_details['user_id']));
+ $auth_details['account_enabled'] = 1;
+ $_SESSION['was_locked'] = true;
+ }
+
+ // skip password check if the user is using oauth
+ if($method == 'oauth'){
+ $pwok = true;
+ } elseif( $method == 'ldap'){
+ $pwok = Flyspray::checkForLDAPUser($username, $password);
+ } else{
+ // encrypt the password with the method used in the db
+ if(substr($auth_details['user_pass'],0,1)!='$' && (
+ strlen($auth_details['user_pass'])==32
+ || strlen($auth_details['user_pass'])==40
+ || strlen($auth_details['user_pass'])==128
+ )){
+ # detecting (old) password stored with old unsalted hashing methods: md5,sha1,sha512
+ switch(strlen($auth_details['user_pass'])){
+ case 32:
+ $pwhash = md5($password);
+ break;
+ case 40:
+ $pwhash = sha1($password);
+ break;
+ case 128:
+ $pwhash = hash('sha512', $password);
+ break;
+ }
+ $pwok = hash_equals($auth_details['user_pass'], $pwhash);
+ }else{
+ #$pwhash = crypt($password, $auth_details['user_pass']); // user_pass contains algorithm, rounds, salt
+ $pwok = password_verify($password, $auth_details['user_pass']);
+ }
+ }
+
+ // Admin users cannot be disabled
+ if ($auth_details['group_id'] == 1 /* admin */ && $pwok) {
+ return $auth_details['user_id'];
+ }
+ if ($pwok && $auth_details['account_enabled'] == '1' && $auth_details['group_open'] == '1'){
+ return $auth_details['user_id'];
+ }
+
+ return ($auth_details['account_enabled'] && $auth_details['group_open']) ? 0 : -1;
+ }
+
+ static public function checkForOauthUser($uid, $provider)
+ {
+ global $db;
+
+ if(empty($uid) || empty($provider)) {
+ return false;
+ }
+
+ $sql = $db->query("SELECT id FROM {user_emails} WHERE oauth_uid = ? AND oauth_provider = ?",array($uid, $provider));
+
+ if ($db->fetchOne($sql)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 20150320 just added from provided patch, untested!
+ */
+ public static function checkForLDAPUser($username, $password)
+ {
+ # TODO: add to admin settings area, maybe let user set the config at final installation step
+ $ldap_host = 'ldaphost';
+ $ldap_port = '389';
+ $ldap_version = '3';
+ $base_dn = 'OU=SBSUsers,OU=Users,OU=MyBusiness,DC=MyDomain,DC=local';
+ $ldap_search_user = 'ldapuser@mydomain.local';
+ $ldap_search_pass = "ldapuserpass";
+ $filter = "SAMAccountName=%USERNAME%"; // this is for AD - may be different with other setups
+ $username = $username;
+
+ if (strlen($password) == 0){ // LDAP will succeed binding with no password on AD (defaults to anon bind)
+ return false;
+ }
+
+ $rs = ldap_connect($ldap_host, $ldap_port);
+ @ldap_set_option($rs, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
+ @ldap_set_option($rs, LDAP_OPT_REFERRALS, 0);
+ $ldap_bind_dn = empty($ldap_search_user) ? NULL : $ldap_search_user;
+ $ldap_bind_pw = empty($ldap_search_pass) ? NULL : $ldap_search_pass;
+ if (!$bindok = @ldap_bind($rs, $ldap_bind_dn, $ldap_search_pass)){
+ // Uncomment for LDAP debugging
+ $error_msg = ldap_error($rs);
+ die("Couldn't bind using ".$ldap_bind_dn."@".$ldap_host.":".$ldap_port." Because:".$error_msg);
+ return false;
+ } else{
+ $filter_r = str_replace("%USERNAME%", $username, $filter);
+ $result = @ldap_search($rs, $base_dn, $filter_r);
+ if (!$result){ // ldap search returned nothing or error
+ return false;
+ }
+ $result_user = ldap_get_entries($rs, $result);
+ if ($result_user["count"] == 0){ // No users match the filter
+ return false;
+ }
+ $first_user = $result_user[0];
+ $ldap_user_dn = $first_user["dn"];
+ // Bind with the dn of the user that matched our filter (only one user should match sAMAccountName or uid etc..)
+ if (!$bind_user = @ldap_bind($rs, $ldap_user_dn, $password)){
+ $error_msg = ldap_error($rs);
+ die("Couldn't bind using ".$ldap_user_dn."@".$ldap_host.":".$ldap_port." Because:".$error_msg);
+ return false;
+ } else{
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Sets a cookie, automatically setting the URL
+ * Now same params as PHP's builtin setcookie()
+ * @param string $name
+ * @param string $val
+ * @param integer $time
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httponly
+ * @access public static
+ * @return bool
+ * @version 1.1
+ */
+ public static function setCookie($name, $val, $time = null, $path=null, $domain=null, $secure=false, $httponly=false)
+ {
+ global $conf;
+
+ if (null===$path){
+ $url = parse_url($GLOBALS['baseurl']);
+ }else{
+ $url['path']=$path;
+ }
+
+ if (!is_int($time)) {
+ $time = time()+60*60*24*30;
+ }
+ if(null===$domain){
+ $domain='';
+ }
+ if(null===$secure){
+ $secure = isset($conf['general']['securecookies']) ? $conf['general']['securecookies'] : false;
+ }
+ if((strlen($name) + strlen($val)) > 4096) {
+ //violation of the protocol
+ trigger_error("Flyspray sent a too big cookie, browsers will not handle it");
+ return false;
+ }
+
+ return setcookie($name, $val, $time, $url['path'],$domain,$secure,$httponly);
+ }
+
+ /**
+ * Starts the session
+ * @access public static
+ * @return void
+ * @version 1.0
+ * @notes smile intented
+ */
+ public static function startSession()
+ {
+ global $conf;
+ if (defined('IN_FEED') || php_sapi_name() === 'cli') {
+ return;
+ }
+
+ $url = parse_url($GLOBALS['baseurl']);
+ session_name('flyspray');
+ session_set_cookie_params(0,$url['path'],'', (isset($conf['general']['securecookies'])? $conf['general']['securecookies']:false), TRUE);
+ session_start();
+ if(!isset($_SESSION['csrftoken'])){
+ $_SESSION['csrftoken']=rand(); # lets start with one anti csrf token secret for the session and see if it's simplicity is good enough (I hope together with enforced Content Security Policies)
+ }
+ }
+
+ /**
+ * Compares two tasks and returns an array of differences
+ * @param array $old
+ * @param array $new
+ * @access public static
+ * @return array array('field', 'old', 'new')
+ * @version 1.0
+ */
+ public static function compare_tasks($old, $new)
+ {
+ $comp = array('priority_name', 'severity_name', 'status_name', 'assigned_to_name', 'due_in_version_name',
+ 'reported_version_name', 'tasktype_name', 'os_name', 'category_name',
+ 'due_date', 'percent_complete', 'item_summary', 'due_in_version_name',
+ 'detailed_desc', 'project_title', 'mark_private');
+
+ $changes = array();
+ foreach ($old as $key => $value)
+ {
+ if (!in_array($key, $comp) || ($key === 'due_date' && intval($old[$key]) === intval($new[$key]))) {
+ continue;
+ }
+
+ if($old[$key] != $new[$key]) {
+ switch ($key)
+ {
+ case 'due_date':
+ $new[$key] = formatDate($new[$key]);
+ $value = formatDate($value);
+ break;
+
+ case 'percent_complete':
+ $new[$key] .= '%';
+ $value .= '%';
+ break;
+
+ case 'mark_private':
+ $new[$key] = $new[$key] ? L('private') : L('public');
+ $value = $value ? L('private') : L('public');
+ break;
+ }
+ $changes[] = array($key, $value, $new[$key]);
+ }
+ }
+
+ return $changes;
+ }
+
+ /**
+ * Get all tags of a task
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function getTags($task_id)
+ {
+ global $db;
+ # pre FS1.0beta
+ #$sql = $db->query('SELECT * FROM {tags} WHERE task_id = ?', array($task_id));
+ # since FS1.0beta
+ $sql = $db->query('SELECT tg.tag_id, tg.tag_name AS tag, tg.class FROM {task_tag} tt
+ JOIN {list_tag} tg ON tg.tag_id=tt.tag_id
+ WHERE task_id = ?
+ ORDER BY list_position', array($task_id));
+ return $db->fetchAllArray($sql);
+ }
+
+ /**
+ * load all task tags into array
+ *
+ * Compared to listTags() of class project, this loads all tags in Flyspray database into a global array.
+ * Ideally called only once per http request, then using the array index for getting tag info.
+ *
+ * Used mainly for tasklist view to simplify get_task_list() sql query.
+ *
+ * @return array
+ */
+ public static function getAllTags()
+ {
+ global $db;
+ $at=array();
+ $res = $db->query('SELECT tag_id, project_id, list_position, tag_name, class, show_in_list FROM {list_tag}');
+ while ($t = $db->fetchRow($res)){
+ $at[$t['tag_id']]=array(
+ 'project_id'=>$t['project_id'],
+ 'list_position'=>$t['list_position'],
+ 'tag_name'=>$t['tag_name'],
+ 'class'=>$t['class'],
+ 'show_in_list'=>$t['show_in_list']
+ );
+ }
+ return $at;
+ }
+
+ /**
+ * Get a list of assignees for a task
+ * @param integer $task_id
+ * @param bool $name whether or not names of the assignees should be returned as well
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function getAssignees($task_id, $name = false)
+ {
+ global $db;
+
+ $sql = $db->query('SELECT u.real_name, u.user_id
+ FROM {users} u, {assigned} a
+ WHERE task_id = ? AND u.user_id = a.user_id',
+ array($task_id));
+
+ $assignees = array();
+ while ($row = $db->fetchRow($sql)) {
+ if ($name) {
+ $assignees[0][] = $row['user_id'];
+ $assignees[1][] = $row['real_name'];
+ } else {
+ $assignees[] = $row['user_id'];
+ }
+ }
+
+ return $assignees;
+ }
+
+ /**
+ * Explode string to the array of integers
+ * @param string $separator
+ * @param string $string
+ * @access public static
+ * @return array
+ * @version 1.0
+ */
+ public static function int_explode($separator, $string)
+ {
+ $ret = array();
+ foreach (explode($separator, $string) as $v)
+ {
+ if (ctype_digit($v)) {// $v is always string, this func returns false if $v == ''
+ $ret[] = intval($v); // convert to int
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Checks if a function is disabled
+ * @param string $func_name
+ * @access public static
+ * @return bool
+ * @version 1.0
+ */
+ public static function function_disabled($func_name)
+ {
+ $disabled_functions = explode(',', ini_get('disable_functions'));
+ return in_array($func_name, $disabled_functions);
+ }
+
+ /**
+ * Returns the key number of an array which contains an array like array($key => $value)
+ * For use with SQL result arrays
+ * returns 0 for first index, so take care if you want check when useing to check if a value exists, use ===
+ *
+ * @param string $key
+ * @param string $value
+ * @param array $array
+ * @access public static
+ * @return integer
+ * @version 1.0
+ */
+ public static function array_find($key, $value, $array)
+ {
+ foreach ($array as $num => $part) {
+ if (isset($part[$key]) && $part[$key] == $value) {
+ return $num;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Shows an error message
+ * @param string $error_message if it is an integer, an error message from the language file will be loaded
+ * @param bool $die enable/disable redirection (if outside the database modification script)
+ * @param string $advanced_info append a string to the error message
+ * @param string $url alternate redirection
+ * @access public static
+ * @return void
+ * @version 1.0
+ * @notes if a success and error happens on the same page, a mixed error message will be shown
+ * @todo is the if ($die) meant to be inside the else clause?
+ */
+ public static function show_error($error_message, $die = true, $advanced_info = null, $url = null)
+ {
+ global $modes, $baseurl;
+
+ if (!is_int($error_message)) {
+ // in modify.inc.php
+ $_SESSION['ERROR'] = $error_message;
+ } else {
+ $_SESSION['ERROR'] = L('error#') . $error_message . ': ' . L('error' . $error_message);
+ if (!is_null($advanced_info)) {
+ $_SESSION['ERROR'] .= ' ' . $advanced_info;
+ }
+ if ($die) {
+ Flyspray::redirect( (is_null($url) ? $baseurl : $url) );
+ }
+ }
+ }
+
+ /**
+ * Returns the user ID if valid, 0 otherwise
+ * @param int $id
+ * @access public static
+ * @return integer 0 if the user does not exist
+ * @version 1.0
+ */
+ public static function validUserId($id)
+ {
+ global $db;
+
+ $sql = $db->query('SELECT user_id FROM {users} WHERE user_id = ?', array(intval($id)));
+
+ return intval($db->fetchOne($sql));
+ }
+
+ /**
+ * Returns the ID of a user with $name
+ * @param string $name
+ * @access public static
+ * @return integer 0 if the user does not exist
+ * @version 1.0
+ */
+ public static function usernameToId($name)
+ {
+ global $db;
+
+ if(!is_string($name)){
+ return 0;
+ }
+
+ $sql = $db->query('SELECT user_id FROM {users} WHERE user_name = ?', array($name));
+
+ return intval($db->fetchOne($sql));
+ }
+
+ /**
+ * check_email
+ * checks if an email is valid
+ * @param string $email
+ * @access public
+ * @return bool
+ */
+ public static function check_email($email)
+ {
+ return is_string($email) && filter_var($email, FILTER_VALIDATE_EMAIL);
+ }
+
+ /**
+ * get_tmp_dir
+ * Based on PEAR System::tmpdir() by Tomas V.V.Cox.
+ * @access public
+ * @return void
+ */
+ public static function get_tmp_dir()
+ {
+ $return = '';
+
+ if (function_exists('sys_get_temp_dir')) {
+ $return = sys_get_temp_dir();
+ } elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
+ $return = $var;
+ } else
+ if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
+ $return = $var;
+ } else
+ if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
+ $return = $var;
+ } else {
+ $return = getenv('SystemRoot') . '\temp';
+ }
+
+ } elseif ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
+ $return = $var;
+ } else {
+ $return = '/tmp';
+ }
+ // Now, the final check
+ if (@is_dir($return) && is_writable($return)) {
+ return rtrim($return, DIRECTORY_SEPARATOR);
+ // we have a problem at this stage.
+ } elseif(is_writable(ini_get('upload_tmp_dir'))) {
+ $return = ini_get('upload_tmp_dir');
+ } elseif(is_writable(ini_get('session.save_path'))) {
+ $return = ini_get('session.save_path');
+ }
+ return rtrim($return, DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * check_mime_type
+ *
+ * @param string $fname path to filename
+ * @access public
+ * @return string the mime type of the offended file.
+ * @notes DO NOT use this function for any security related
+ * task (i.e limiting file uploads by type)
+ * it wasn't designed for that purpose but to UI related tasks.
+ */
+ public static function check_mime_type($fname) {
+
+ $type = '';
+
+ if (extension_loaded('fileinfo') && class_exists('finfo')) {
+
+ $info = new finfo(FILEINFO_MIME);
+ $type = $info->file($fname);
+
+ } elseif(function_exists('mime_content_type')) {
+
+ $type = @mime_content_type($fname);
+ // I hope we don't have to...
+ } elseif(!FlySpray::function_disabled('exec') && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN'
+ && php_uname('s') !== 'SunOS') {
+
+ $type = @exec(sprintf('file -bi %s', escapeshellarg($fname)));
+
+ }
+ // if wasn't possible to determine , return empty string so
+ // we can use the browser reported mime-type (probably fake)
+ return trim($type);
+ }
+
+ /**
+ * Works like strtotime, but it considers the user's timezone
+ * @access public
+ * @param string $time
+ * @return integer
+ */
+ public static function strtotime($time)
+ {
+ global $user;
+
+ $time = strtotime($time);
+
+ if (!$user->isAnon()) {
+ $st = date('Z')/3600; // server GMT timezone
+ // Example: User is GMT+3, Server GMT-2.
+ // User enters 7:00. For the server it must be converted to 2:00 (done below)
+ $time += ($st - $user->infos['time_zone']) * 60 * 60;
+ // later it adds 5 hours to 2:00 for the user when the date is displayed.
+ }
+ //strtotime() may return false, making this method to return bool instead of int.
+ return $time ? $time : 0;
+ }
+
+ /**
+ * Writes content to a file using a lock.
+ * @access public
+ * @param string $filename location to write to
+ * @param string $content data to write
+ */
+ public static function write_lock($filename, $content)
+ {
+ if ($f = fopen($filename, 'wb')) {
+ if(flock($f, LOCK_EX)) {
+ fwrite($f, $content);
+ flock($f, LOCK_UN);
+ }
+ fclose($f);
+ }
+ }
+
+ /**
+ * file_get_contents replacement for remote files
+ * @access public
+ * @param string $url
+ * @param bool $get_contents whether or not to return file contents, use GET_CONTENTS for true
+ * @param integer $port
+ * @param string $connect manually choose server for connection
+ * @return string an empty string is not necessarily a failure
+ */
+ public static function remote_request($url, $get_contents = false, $port = 80, $connect = '', $host = null)
+ {
+ $url = parse_url($url);
+ if (!$connect) {
+ $connect = $url['host'];
+ }
+
+ if ($host) {
+ $url['host'] = $host;
+ }
+
+ $data = '';
+
+ if ($conn = @fsockopen($connect, $port, $errno, $errstr, 10)) {
+ $out = "GET {$url['path']} HTTP/1.0\r\n";
+ $out .= "Host: {$url['host']}\r\n";
+ $out .= "Connection: Close\r\n\r\n";
+
+ stream_set_timeout($conn, 5);
+ fwrite($conn, $out);
+
+ if ($get_contents) {
+ while (!feof($conn)) {
+ $data .= fgets($conn, 128);
+ }
+
+ $pos = strpos($data, "\r\n\r\n");
+
+ if ($pos !== false) {
+ //strip the http headers.
+ $data = substr($data, $pos + 2 * strlen("\r\n"));
+ }
+ }
+ fclose($conn);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns an array containing all notification options the user is
+ * allowed to use.
+ * @access public
+ * @return array
+ */
+ public function getNotificationOptions($noneAllowed = true)
+ {
+ switch ($this->prefs['user_notify'])
+ {
+ case 0:
+ return array(0 => L('none'));
+ case 2:
+ return array(NOTIFY_EMAIL => L('email'));
+ case 3:
+ return array(NOTIFY_JABBER => L('jabber'));
+
+ }
+
+ $return = array(0 => L('none'),
+ NOTIFY_EMAIL => L('email'),
+ NOTIFY_JABBER => L('jabber'),
+ NOTIFY_BOTH => L('both'));
+ if (!$noneAllowed) {
+ unset($return[0]);
+ }
+
+ return $return;
+ }
+
+ public static function weedOutTasks($user, $tasks) {
+ $allowedtasks = array();
+ foreach ($tasks as $task) {
+ if ($user->can_view_task($task)) {
+ $allowedtasks[] = $task;
+ }
+ }
+ return $allowedtasks;
+ }
+}
diff --git a/includes/class.gpc.php b/includes/class.gpc.php
new file mode 100644
index 0000000..88235ef
--- /dev/null
+++ b/includes/class.gpc.php
@@ -0,0 +1,257 @@
+<?php
+// {{{ class Req
+/**
+ * Flyspray
+ *
+ * GPC classes
+ *
+ * This script contains classes for $_GET, $_REQUEST, $_POST and $_COOKIE
+ * to safely retrieve values. Example: Get::val('foo', 'bar') to get $_GET['foo'] or 'bar' if
+ * the key does not exist.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
+ * @package flyspray
+ * @author Pierre Habouzit
+ */
+
+abstract class Req
+{
+ public static function has($key)
+ {
+ return isset($_REQUEST[$key]);
+ }
+
+ public static function val($key, $default = null)
+ {
+ return Req::has($key) ? $_REQUEST[$key] : $default;
+ }
+
+ //it will always return a number no matter what(null is 0)
+ public static function num($key, $default = null)
+ {
+ return Filters::num(Req::val($key, $default));
+ }
+
+ public static function enum($key, $options, $default = null)
+ {
+ return Filters::enum(Req::val($key, $default), $options);
+ }
+
+ //always a string (null is typed to an empty string)
+ public static function safe($key)
+ {
+ return Filters::noXSS(Req::val($key));
+ }
+
+ public static function isAlnum($key)
+ {
+ return Filters::isAlnum(Req::val($key));
+ }
+
+ /**
+ * Overwrites or sets a request value
+ */
+ public static function set($key, $value = null) {
+ $_REQUEST[$key] = $value;
+ }
+}
+
+ // }}}
+// {{{ class Post
+
+abstract class Post
+{
+ public static function has($key)
+ {
+ // XXX semantics is different for POST, as POST of '' values is never
+ // unintentionnal, whereas GET/COOKIE may have '' values for empty
+ // ones.
+ return isset($_POST[$key]);
+ }
+
+ public static function val($key, $default = null)
+ {
+ return Post::has($key) ? $_POST[$key] : $default;
+ }
+
+ //it will always return a number no matter what(null is 0)
+ public static function num($key, $default = null)
+ {
+ return Filters::num(Post::val($key, $default));
+ }
+
+ //always a string (null is typed to an empty string)
+ public static function safe($key)
+ {
+ return Filters::noXSS(Post::val($key));
+ }
+
+ public static function isAlnum($key)
+ {
+ return Filters::isAlnum(Post::val($key));
+ }
+}
+
+// }}}
+// {{{ class Get
+
+abstract class Get
+{
+ public static function has($key)
+ {
+ return isset($_GET[$key]) && $_GET[$key] !== '';
+ }
+
+ public static function val($key, $default = null)
+ {
+ return Get::has($key) ? $_GET[$key] : $default;
+ }
+
+ //it will always return a number no matter what(null is 0)
+ public static function num($key, $default = null)
+ {
+ return Filters::num(Get::val($key, $default));
+ }
+
+ //always a string (null is typed to an empty string)
+ public static function safe($key)
+ {
+ return Filters::noXSS(Get::val($key));
+ }
+
+ public static function enum($key, $options, $default = null)
+ {
+ return Filters::enum(Get::val($key, $default), $options);
+ }
+
+}
+
+// }}}
+//{{{ class Cookie
+
+abstract class Cookie
+{
+ public static function has($key)
+ {
+ return isset($_COOKIE[$key]) && $_COOKIE[$key] !== '';
+ }
+
+ public static function val($key, $default = null)
+ {
+ return Cookie::has($key) ? $_COOKIE[$key] : $default;
+ }
+}
+//}}}
+/**
+ * Class Filters
+ *
+ * This is a simple class for safe input validation
+ * no mixed stuff here, functions returns always the same type.
+ * @author Cristian Rodriguez R <judas.iscariote@flyspray.org>
+ * @license BSD
+ * @notes this intented to be used by Flyspray internals functions/methods
+ * please DO NOT use this in templates , if the code processing the input there
+ * is not safe, please fix the underlying problem.
+ */
+abstract class Filters {
+ /**
+ * give me a number only please?
+ * @param mixed $data
+ * @return int
+ * @access public static
+ * @notes changed before 0.9.9 to avoid strange results
+ * with arrays and objects
+ */
+ public static function num($data)
+ {
+ return intval($data); // no further checks here please
+ }
+
+ /**
+ * Give user input free from potentially mailicious html
+ * @param mixed $data
+ * @return string htmlspecialchar'ed
+ * @access public static
+ */
+ public static function noXSS($data)
+ {
+ if(empty($data) || is_numeric($data)) {
+ return $data;
+ } elseif(is_string($data)) {
+ return htmlspecialchars($data, ENT_QUOTES, 'utf-8');
+ }
+ return '';
+ }
+
+ /**
+ * Give user input free from potentially mailicious html and JS insertions
+ * @param mixed $data
+ * @return string
+ * @access public static
+ */
+ public static function noJsXSS($data)
+ {
+ if(empty($data) || is_numeric($data)) {
+ return $data;
+ } elseif(is_string($data)) {
+ return Filters::noXSS(preg_replace("/[\x01-\x1F\x7F]|\xC2[\x80-\x9F]/", "", addcslashes($data, "\t\"'\\")));
+ }
+ return '';
+ }
+
+ /**
+ * is $data alphanumeric eh ?
+ * @param string $data string value to check
+ * @return bool
+ * @access public static
+ * @notes unfortunately due to a bug in PHP < 5.1
+ * http://bugs.php.net/bug.php?id=30945 ctype_alnum
+ * returned true on empty string, that's the reason why
+ * we have to use strlen too.
+ *
+ * Be aware: $data MUST be an string, integers or any other
+ * type is evaluated to FALSE
+ */
+ public static function isAlnum($data)
+ {
+ return ctype_alnum($data) && strlen($data);
+ }
+
+ /**
+ * Checks if $data is a value of $options and returns the first element of
+ * $options if it is not (for input validation if all possible values are known)
+ * @param mixed $data
+ * @param array $options
+ * @return mixed
+ * @access public static
+ */
+ public static function enum($data, $options)
+ {
+ if (!in_array($data, $options) && isset($options[0])) {
+ return $options[0];
+ }
+
+ return $data;
+ }
+
+ public static function escapeqs($qs)
+ {
+ parse_str($qs, $clean_qs);
+ return http_build_query($clean_qs);
+ }
+}
+
+/**
+ * A basic function which works like the GPC classes above for any array
+ * @param array $array
+ * @param mixed $key
+ * @param mixed $default
+ * @return mixed
+ * @version 1.0
+ * @since 0.9.9
+ * @see Backend::get_task_list()
+ */
+function array_get(&$array, $key, $default = null)
+{
+ return (isset($array[$key])) ? $array[$key] : $default;
+}
diff --git a/includes/class.jabber2.php b/includes/class.jabber2.php
new file mode 100644
index 0000000..4617395
--- /dev/null
+++ b/includes/class.jabber2.php
@@ -0,0 +1,943 @@
+<?php
+/**
+ * Jabber class
+ *
+ * @version $Id$
+ * @copyright 2006 Flyspray.org
+ * @notes: This lib has been created due to the lack of any good and modern jabber class out there
+ * @author: Florian Schmitz (floele)
+ */
+
+define('SECURITY_NONE', 0);
+define('SECURITY_SSL', 1);
+define('SECURITY_TLS', 2);
+
+class Jabber
+{
+ public $connection = null;
+ public $session = array();
+ public $resource = 'class.jabber2.php';
+ public $log = array();
+ public $log_enabled = true;
+ public $timeout = 10;
+ public $user = '';
+ public $password = '';
+ public $server = '';
+ public $features = array();
+
+ public function __construct($login, $password, $security = SECURITY_NONE, $port = 5222, $host = '')
+ {
+ // Can we use Jabber at all?
+ // Note: Maybe replace with SimpleXML in the future
+ if (!extension_loaded('xml')) {
+ $this->log('Error: No XML functions available, Jabber functions can not operate.');
+ return false;
+ }
+
+ //bug in php 5.2.1 renders this stuff more or less useless.
+ if ((version_compare(phpversion(), '5.2.1', '>=') && version_compare(phpversion(), '5.2.3RC2', '<')) && $security != SECURITY_NONE) {
+ $this->log('Error: PHP ' . phpversion() . ' + SSL is incompatible with jabber, see http://bugs.php.net/41236');
+ return false;
+ }
+
+ if (!Jabber::check_jid($login)) {
+ $this->log('Error: Jabber ID is not valid: ' . $login);
+ return false;
+ }
+
+ // Extract data from user@server.org
+ list($username, $server) = explode('@', $login);
+
+ // Decide whether or not to use encryption
+ if ($security == SECURITY_SSL && !Jabber::can_use_ssl()) {
+ $this->log('Warning: SSL encryption is not supported (openssl required). Falling back to no encryption.');
+ $security = SECURITY_NONE;
+ }
+ if ($security == SECURITY_TLS && !Jabber::can_use_tls()) {
+ $this->log('Warning: TLS encryption is not supported (openssl and stream_socket_enable_crypto() required). Falling back to no encryption.');
+ $security = SECURITY_NONE;
+ }
+
+ $this->session['security'] = $security;
+ $this->server = $server;
+ $this->user = $username;
+ $this->password = $password;
+
+ if ($this->open_socket( ($host != '') ? $host : $server, $port, $security == SECURITY_SSL)) {
+ $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
+ $this->send("<stream:stream to='{$server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
+ } else {
+ return false;
+ }
+ // Now we listen what the server has to say...and give appropriate responses
+ $this->response($this->listen());
+ }
+
+ /**
+ * Sets the resource which is used. No validation is done here, only escaping.
+ * @param string $$name
+ * @access public
+ */
+ public function setResource($name)
+ {
+ $this->resource = $name;
+ }
+
+ /**
+ * Send data to the Jabber server
+ * @param string $xml
+ * @access public
+ * @return bool
+ */
+ public function send($xml)
+ {
+ if ($this->connected()) {
+ $xml = trim($xml);
+ $this->log('SEND: '. $xml);
+ return fwrite($this->connection, $xml);
+ } else {
+ $this->log('Error: Could not send, connection lost (flood?).');
+ return false;
+ }
+ }
+
+ /**
+ * OpenSocket
+ * @param string $server host to connect to
+ * @param int $port port number
+ * @param bool $ssl use ssl or not
+ * @access public
+ * @return bool
+ */
+ public function open_socket($server, $port, $ssl = false)
+ {
+ if (function_exists("dns_get_record")) {
+ $record = dns_get_record("_xmpp-client._tcp.$server", DNS_SRV);
+ if (!empty($record)) {
+ $server = $record[0]['target'];
+ }
+ } else {
+ $this->log('Warning: dns_get_record function not found. gtalk will not work.');
+ }
+
+ $server = $ssl ? 'ssl://' . $server : $server;
+
+ if ($ssl) {
+ $this->session['ssl'] = true;
+ }
+
+ if ($this->connection = @fsockopen($server, $port, $errorno, $errorstr, $this->timeout)) {
+ socket_set_blocking($this->connection, 0);
+ socket_set_timeout($this->connection, 60);
+
+ return true;
+ }
+ // Apparently an error occured...
+ $this->log('Error: ' . $errorstr);
+ return false;
+ }
+
+ public function log($msg)
+ {
+ if ($this->log_enabled) {
+ $this->log[] = $msg;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Listens to the connection until it gets data or the timeout is reached.
+ * Thus, it should only be called if data is expected to be received.
+ * @access public
+ * @return mixed either false for timeout or an array with the received data
+ */
+ public function listen($timeout = 10, $wait = false)
+ {
+ if (!$this->connected()) {
+ return false;
+ }
+
+ // Wait for a response until timeout is reached
+ $start = time();
+ $data = '';
+
+ do {
+ $read = trim(fread($this->connection, 4096));
+ $data .= $read;
+ } while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != ''
+ || (substr(rtrim($data), -1) != '>')));
+
+ if ($data != '') {
+ $this->log('RECV: '. $data);
+ return Jabber::xmlize($data);
+ } else {
+ $this->log('Timeout, no response from server.');
+ return false;
+ }
+ }
+
+ /**
+ * Initiates login (using data from contructor)
+ * @access public
+ * @return bool
+ */
+ public function login()
+ {
+ if (!count($this->features)) {
+ $this->log('Error: No feature information from server available.');
+ return false;
+ }
+
+ return $this->response($this->features);
+ }
+
+ /**
+ * Initiates account registration (based on data used for contructor)
+ * @access public
+ * @return bool
+ */
+ public function register()
+ {
+ if (!isset($this->session['id']) || isset($this->session['jid'])) {
+ $this->log('Error: Cannot initiate registration.');
+ return false;
+ }
+
+ $this->send("<iq type='get' id='reg_1'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>");
+ return $this->response($this->listen());
+ }
+
+ /**
+ * Initiates account un-registration (based on data used for contructor)
+ * @access public
+ * @return bool
+ */
+ public function unregister()
+ {
+ if (!isset($this->session['id']) || !isset($this->session['jid'])) {
+ $this->log('Error: Cannot initiate un-registration.');
+ return false;
+ }
+
+ $this->send("<iq type='set' from='" . Jabber::jspecialchars($this->session['jid']) . "' id='unreg_1'>
+ <query xmlns='jabber:iq:register'>
+ <remove/>
+ </query>
+ </iq>");
+ return $this->response($this->listen(2)); // maybe we don't even get a response
+ }
+
+ /**
+ * Sets account presence. No additional info required (default is "online" status)
+ * @param $type dnd, away, chat, xa or nothing
+ * @param $message
+ * @param $unavailable set this to true if you want to become unavailable
+ * @access public
+ * @return bool
+ */
+ public function presence($type = '', $message = '', $unavailable = false)
+ {
+ if (!isset($this->session['jid'])) {
+ $this->log('Error: Cannot set presence at this point.');
+ return false;
+ }
+
+ if (in_array($type, array('dnd', 'away', 'chat', 'xa'))) {
+ $type = '<show>'. $type .'</show>';
+ } else {
+ $type = '';
+ }
+
+ $unavailable = ($unavailable) ? " type='unavailable'" : '';
+ $message = ($message) ? '<status>' . Jabber::jspecialchars($message) .'</status>' : '';
+
+ $this->session['sent_presence'] = !$unavailable;
+
+ return $this->send("<presence$unavailable>" .
+ $type .
+ $message .
+ '</presence>');
+ }
+
+ /**
+ * This handles all the different XML elements
+ * @param array $xml
+ * @access public
+ * @return bool
+ */
+ public function response($xml)
+ {
+ if (!is_array($xml) || !count($xml)) {
+ return false;
+ }
+
+ // did we get multiple elements? do one after another
+ // array('message' => ..., 'presence' => ...)
+ if (count($xml) > 1) {
+ foreach ($xml as $key => $value) {
+ $this->response(array($key => $value));
+ }
+ return;
+ } else
+ // or even multiple elements of the same type?
+ // array('message' => array(0 => ..., 1 => ...))
+ if (count(reset($xml)) > 1) {
+ foreach (reset($xml) as $value) {
+ $this->response(array(key($xml) => array(0 => $value)));
+ }
+ return;
+ }
+
+ switch (key($xml)) {
+ case 'stream:stream':
+ // Connection initialised (or after authentication). Not much to do here...
+ if (isset($xml['stream:stream'][0]['#']['stream:features'])) {
+ // we already got all info we need
+ $this->features = $xml['stream:stream'][0]['#'];
+ } else {
+ $this->features = $this->listen();
+ }
+ $second_time = isset($this->session['id']);
+ $this->session['id'] = $xml['stream:stream'][0]['@']['id'];
+ if ($second_time) {
+ // If we are here for the second time after TLS, we need to continue logging in
+ $this->login();
+ return;
+ }
+
+ // go on with authentication?
+ if (isset($this->features['stream:features'][0]['#']['bind'])) {
+ return $this->response($this->features);
+ }
+ break;
+
+ case 'stream:features':
+ // Resource binding after successful authentication
+ if (isset($this->session['authenticated'])) {
+ // session required?
+ $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']);
+
+ $this->send("<iq type='set' id='bind_1'>
+ <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
+ <resource>" . Jabber::jspecialchars($this->resource) . "</resource>
+ </bind>
+ </iq>");
+ return $this->response($this->listen());
+ }
+ // Let's use TLS if SSL is not enabled and we can actually use it
+ if ($this->session['security'] == SECURITY_TLS && isset($xml['stream:features'][0]['#']['starttls'])) {
+ $this->log('Switching to TLS.');
+ $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n");
+ return $this->response($this->listen());
+ }
+ // Does the server support SASL authentication?
+
+ // I hope so, because we do (and no other method).
+ if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) &&
+ $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') {
+ // Now decide on method
+ $methods = array();
+ foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) {
+ $methods[] = $value['#'];
+ }
+
+ // we prefer this one
+ if (in_array('DIGEST-MD5', $methods)) {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
+ // we don't want to use this (neither does the server usually) if no encryption is in place
+ # http://www.xmpp.org/extensions/attic/jep-0078-1.7.html
+ # The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS)
+ # and the client has verified that the server certificate is signed by a trusted certificate authority.
+ } else if (in_array('PLAIN', $methods) && (isset($this->session['ssl']) || isset($this->session['tls']))) {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"
+ . base64_encode(chr(0) . $this->user . '@' . $this->server . chr(0) . $this->password) .
+ "</auth>");
+ } else if (in_array('ANONYMOUS', $methods)) {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ // not good...
+ } else {
+ $this->log('Error: No authentication method supported.');
+ $this->disconnect();
+ return false;
+ }
+ return $this->response($this->listen());
+
+ } else {
+ // ok, this is it. bye.
+ $this->log('Error: Server does not offer SASL authentication.');
+ $this->disconnect();
+ return false;
+ }
+ break;
+
+ case 'challenge':
+ // continue with authentication...a challenge literally -_-
+ $decoded = base64_decode($xml['challenge'][0]['#']);
+ $decoded = Jabber::parse_data($decoded);
+ if (!isset($decoded['digest-uri'])) {
+ $decoded['digest-uri'] = 'xmpp/'. $this->server;
+ }
+
+ // better generate a cnonce, maybe it's needed
+
+ $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true)));
+
+ // second challenge?
+ if (isset($decoded['rspauth'])) {
+ $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
+ } else {
+ $response = array('username' => $this->user,
+ 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))),
+ 'charset' => 'utf-8',
+ 'nc' => '00000001',
+ 'qop' => 'auth'); // the only option we support anyway
+
+ foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) {
+ if (isset($decoded[$key])) {
+ $response[$key] = $decoded[$key];
+ }
+ }
+
+ $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" .
+ base64_encode(Jabber::implode_data($response))
+ . "</response>");
+ }
+
+ return $this->response($this->listen());
+
+ case 'failure':
+ $this->log('Error: Server sent "failure".');
+ $this->disconnect();
+ return false;
+
+ case 'proceed':
+ // continue switching to TLS
+ $meta = stream_get_meta_data($this->connection);
+ socket_set_blocking($this->connection, 1);
+ if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+ $this->log('Error: TLS mode change failed.');
+ return false;
+ }
+ socket_set_blocking($this->connection, $meta['blocked']);
+ $this->session['tls'] = true;
+ // new stream
+ $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
+ $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
+
+ return $this->response($this->listen());
+
+ case 'success':
+ // Yay, authentication successful.
+ $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
+ $this->session['authenticated'] = true;
+ return $this->response($this->listen()); // we have to wait for another response
+
+ case 'iq':
+ // we are not interested in IQs we did not expect
+ if (!isset($xml['iq'][0]['@']['id'])) {
+ return false;
+ }
+ // multiple possibilities here
+ switch ($xml['iq'][0]['@']['id'])
+ {
+ case 'bind_1':
+ $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#'];
+ // and (maybe) yet another request to be able to send messages *finally*
+ if ($this->session['sess_required']) {
+ $this->send("<iq to='{$this->server}'
+ type='set'
+ id='sess_1'>
+ <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
+ </iq>");
+ return $this->response($this->listen());
+ }
+ return true;
+
+ case 'sess_1':
+ return true;
+
+ case 'reg_1':
+ $this->send("<iq type='set' id='reg_2'>
+ <query xmlns='jabber:iq:register'>
+ <username>" . Jabber::jspecialchars($this->user) . "</username>
+ <password>" . Jabber::jspecialchars($this->password) . "</password>
+ </query>
+ </iq>");
+ return $this->response($this->listen());
+
+ case 'reg_2':
+ // registration end
+ if (isset($xml['iq'][0]['#']['error'])) {
+ $this->log('Warning: Registration failed.');
+ return false;
+ }
+ return true;
+
+ case 'unreg_1':
+ return true;
+
+ default:
+ $this->log('Notice: Received unexpected IQ.');
+ return false;
+ }
+ break;
+
+ case 'message':
+ // we are only interested in content...
+ if (!isset($xml['message'][0]['#']['body'])) {
+ return false;
+ }
+
+ $message['body'] = $xml['message'][0]['#']['body'][0]['#'];
+ $message['from'] = $xml['message'][0]['@']['from'];
+ if (isset($xml['message'][0]['#']['subject'])) {
+ $message['subject'] = $xml['message'][0]['#']['subject'][0]['#'];
+ }
+ $this->session['messages'][] = $message;
+ break;
+
+ default:
+ // hm...don't know this response
+ $this->log('Notice: Unknown server response (' . key($xml) . ')');
+ return false;
+ }
+ }
+
+ public function send_message($to, $text, $subject = '', $type = 'normal')
+ {
+ if (!isset($this->session['jid'])) {
+ return false;
+ }
+
+ if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline'))) {
+ $type = 'normal';
+ }
+
+ return $this->send("<message from='" . Jabber::jspecialchars($this->session['jid']) . "'
+ to='" . Jabber::jspecialchars($to) . "'
+ type='$type'
+ id='" . uniqid('msg') . "'>
+ <subject>" . Jabber::jspecialchars($subject) . "</subject>
+ <body>" . Jabber::jspecialchars($text) . "</body>
+ </message>");
+ }
+
+ public function get_messages($waitfor = 3)
+ {
+ if (!isset($this->session['sent_presence']) || !$this->session['sent_presence']) {
+ $this->presence();
+ }
+
+ if ($waitfor > 0) {
+ $this->response($this->listen($waitfor, $wait = true)); // let's see if any messages fly in
+ }
+
+ return isset($this->session['messages']) ? $this->session['messages'] : array();
+ }
+
+ public function connected()
+ {
+ return is_resource($this->connection) && !feof($this->connection);
+ }
+
+ public function disconnect()
+ {
+ if ($this->connected()) {
+ // disconnect gracefully
+ if (isset($this->session['sent_presence'])) {
+ $this->presence('', 'offline', $unavailable = true);
+ }
+ $this->send('</stream:stream>');
+ $this->session = array();
+ return fclose($this->connection);
+ }
+ return false;
+ }
+
+ public static function can_use_ssl()
+ {
+ return extension_loaded('openssl');
+ }
+
+ public static function can_use_tls()
+ {
+ return Jabber::can_use_ssl() && function_exists('stream_socket_enable_crypto');
+ }
+
+ /**
+ * Encrypts a password as in RFC 2831
+ * @param array $data Needs data from the client-server connection
+ * @access public
+ * @return string
+ */
+ public function encrypt_password($data)
+ {
+ // let's me think about <challenge> again...
+ foreach (array('realm', 'cnonce', 'digest-uri') as $key) {
+ if (!isset($data[$key])) {
+ $data[$key] = '';
+ }
+ }
+
+ $pack = md5($this->user . ':' . $data['realm'] . ':' . $this->password);
+ if (isset($data['authzid'])) {
+ $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']);
+ } else {
+ $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']);
+ }
+
+ // should be: qop = auth
+ $a2 = 'AUTHENTICATE:'. $data['digest-uri'];
+
+ return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2)));
+ }
+
+ /**
+ * parse_data like a="b",c="d",...
+ * @param string $data
+ * @access public
+ * @return array a => b ...
+ */
+ public function parse_data($data)
+ {
+ // super basic, but should suffice
+ $data = explode(',', $data);
+ $pairs = array();
+ foreach ($data as $pair) {
+ $dd = strpos($pair, '=');
+ if ($dd) {
+ $pairs[substr($pair, 0, $dd)] = trim(substr($pair, $dd + 1), '"');
+ }
+ }
+ return $pairs;
+ }
+
+ /**
+ * opposite of Jabber::parse_data()
+ * @param array $data
+ * @access public
+ * @return string
+ */
+ public function implode_data($data)
+ {
+ $return = array();
+ foreach ($data as $key => $value) {
+ $return[] = $key . '="' . $value . '"';
+ }
+ return implode(',', $return);
+ }
+
+ /**
+ * Checks whether or not a Jabber ID is valid (FS#1131)
+ * @param string $jid
+ * @access public
+ * @return string
+ */
+ public function check_jid($jid)
+ {
+ $i = strpos($jid, '@');
+ if ($i === false) {
+ return false;
+ }
+
+ $username = substr($jid, 0, $i);
+ $realm = substr($jid, $i + 1);
+
+ if (strlen($username) == 0 || strlen($realm) < 3) {
+ return false;
+ }
+
+ $arr = explode('.', $realm);
+
+ if (count($arr) == 0) {
+ return false;
+ }
+
+ foreach ($arr as $part)
+ {
+ if (substr($part, 0, 1) == '-' || substr($part, -1, 1) == '-') {
+ return false;
+ }
+
+ if (preg_match("@^[a-zA-Z0-9-.]+$@", $part) == false) {
+ return false;
+ }
+ }
+
+ $b = array(array(0, 127), array(192, 223), array(224, 239),
+ array(240, 247), array(248, 251), array(252, 253));
+
+ // Prohibited Characters RFC3454 + RFC3920
+ $p = array(
+ // Table C.1.1
+ array(0x0020, 0x0020), // SPACE
+ // Table C.1.2
+ array(0x00A0, 0x00A0), // NO-BREAK SPACE
+ array(0x1680, 0x1680), // OGHAM SPACE MARK
+ array(0x2000, 0x2001), // EN QUAD
+ array(0x2001, 0x2001), // EM QUAD
+ array(0x2002, 0x2002), // EN SPACE
+ array(0x2003, 0x2003), // EM SPACE
+ array(0x2004, 0x2004), // THREE-PER-EM SPACE
+ array(0x2005, 0x2005), // FOUR-PER-EM SPACE
+ array(0x2006, 0x2006), // SIX-PER-EM SPACE
+ array(0x2007, 0x2007), // FIGURE SPACE
+ array(0x2008, 0x2008), // PUNCTUATION SPACE
+ array(0x2009, 0x2009), // THIN SPACE
+ array(0x200A, 0x200A), // HAIR SPACE
+ array(0x200B, 0x200B), // ZERO WIDTH SPACE
+ array(0x202F, 0x202F), // NARROW NO-BREAK SPACE
+ array(0x205F, 0x205F), // MEDIUM MATHEMATICAL SPACE
+ array(0x3000, 0x3000), // IDEOGRAPHIC SPACE
+ // Table C.2.1
+ array(0x0000, 0x001F), // [CONTROL CHARACTERS]
+ array(0x007F, 0x007F), // DELETE
+ // Table C.2.2
+ array(0x0080, 0x009F), // [CONTROL CHARACTERS]
+ array(0x06DD, 0x06DD), // ARABIC END OF AYAH
+ array(0x070F, 0x070F), // SYRIAC ABBREVIATION MARK
+ array(0x180E, 0x180E), // MONGOLIAN VOWEL SEPARATOR
+ array(0x200C, 0x200C), // ZERO WIDTH NON-JOINER
+ array(0x200D, 0x200D), // ZERO WIDTH JOINER
+ array(0x2028, 0x2028), // LINE SEPARATOR
+ array(0x2029, 0x2029), // PARAGRAPH SEPARATOR
+ array(0x2060, 0x2060), // WORD JOINER
+ array(0x2061, 0x2061), // FUNCTION APPLICATION
+ array(0x2062, 0x2062), // INVISIBLE TIMES
+ array(0x2063, 0x2063), // INVISIBLE SEPARATOR
+ array(0x206A, 0x206F), // [CONTROL CHARACTERS]
+ array(0xFEFF, 0xFEFF), // ZERO WIDTH NO-BREAK SPACE
+ array(0xFFF9, 0xFFFC), // [CONTROL CHARACTERS]
+ array(0x1D173, 0x1D17A), // [MUSICAL CONTROL CHARACTERS]
+ // Table C.3
+ array(0xE000, 0xF8FF), // [PRIVATE USE, PLANE 0]
+ array(0xF0000, 0xFFFFD), // [PRIVATE USE, PLANE 15]
+ array(0x100000, 0x10FFFD), // [PRIVATE USE, PLANE 16]
+ // Table C.4
+ array(0xFDD0, 0xFDEF), // [NONCHARACTER CODE POINTS]
+ array(0xFFFE, 0xFFFF), // [NONCHARACTER CODE POINTS]
+ array(0x1FFFE, 0x1FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x2FFFE, 0x2FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x3FFFE, 0x3FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x4FFFE, 0x4FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x5FFFE, 0x5FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x6FFFE, 0x6FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x7FFFE, 0x7FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x8FFFE, 0x8FFFF), // [NONCHARACTER CODE POINTS]
+ array(0x9FFFE, 0x9FFFF), // [NONCHARACTER CODE POINTS]
+ array(0xAFFFE, 0xAFFFF), // [NONCHARACTER CODE POINTS]
+ array(0xBFFFE, 0xBFFFF), // [NONCHARACTER CODE POINTS]
+ array(0xCFFFE, 0xCFFFF), // [NONCHARACTER CODE POINTS]
+ array(0xDFFFE, 0xDFFFF), // [NONCHARACTER CODE POINTS]
+ array(0xEFFFE, 0xEFFFF), // [NONCHARACTER CODE POINTS]
+ array(0xFFFFE, 0xFFFFF), // [NONCHARACTER CODE POINTS]
+ array(0x10FFFE, 0x10FFFF), // [NONCHARACTER CODE POINTS]
+ // Table C.5
+ array(0xD800, 0xDFFF), // [SURROGATE CODES]
+ // Table C.6
+ array(0xFFF9, 0xFFF9), // INTERLINEAR ANNOTATION ANCHOR
+ array(0xFFFA, 0xFFFA), // INTERLINEAR ANNOTATION SEPARATOR
+ array(0xFFFB, 0xFFFB), // INTERLINEAR ANNOTATION TERMINATOR
+ array(0xFFFC, 0xFFFC), // OBJECT REPLACEMENT CHARACTER
+ array(0xFFFD, 0xFFFD), // REPLACEMENT CHARACTER
+ // Table C.7
+ array(0x2FF0, 0x2FFB), // [IDEOGRAPHIC DESCRIPTION CHARACTERS]
+ // Table C.8
+ array(0x0340, 0x0340), // COMBINING GRAVE TONE MARK
+ array(0x0341, 0x0341), // COMBINING ACUTE TONE MARK
+ array(0x200E, 0x200E), // LEFT-TO-RIGHT MARK
+ array(0x200F, 0x200F), // RIGHT-TO-LEFT MARK
+ array(0x202A, 0x202A), // LEFT-TO-RIGHT EMBEDDING
+ array(0x202B, 0x202B), // RIGHT-TO-LEFT EMBEDDING
+ array(0x202C, 0x202C), // POP DIRECTIONAL FORMATTING
+ array(0x202D, 0x202D), // LEFT-TO-RIGHT OVERRIDE
+ array(0x202E, 0x202E), // RIGHT-TO-LEFT OVERRIDE
+ array(0x206A, 0x206A), // INHIBIT SYMMETRIC SWAPPING
+ array(0x206B, 0x206B), // ACTIVATE SYMMETRIC SWAPPING
+ array(0x206C, 0x206C), // INHIBIT ARABIC FORM SHAPING
+ array(0x206D, 0x206D), // ACTIVATE ARABIC FORM SHAPING
+ array(0x206E, 0x206E), // NATIONAL DIGIT SHAPES
+ array(0x206F, 0x206F), // NOMINAL DIGIT SHAPES
+ // Table C.9
+ array(0xE0001, 0xE0001), // LANGUAGE TAG
+ array(0xE0020, 0xE007F), // [TAGGING CHARACTERS]
+ // RFC3920
+ array(0x22, 0x22), // "
+ array(0x26, 0x26), // &
+ array(0x27, 0x27), // '
+ array(0x2F, 0x2F), // /
+ array(0x3A, 0x3A), // :
+ array(0x3C, 0x3C), // <
+ array(0x3E, 0x3E), // >
+ array(0x40, 0x40) // @
+ );
+
+ $pos = 0;
+ $result = true;
+
+ while ($pos < strlen($username))
+ {
+ $len = 0;
+ $uni = 0;
+ for ($i = 0; $i <= 5; $i++)
+ {
+ if (ord($username[$pos]) >= $b[$i][0] && ord($username[$pos]) <= $b[$i][1])
+ {
+ $len = $i + 1;
+
+ $uni = (ord($username[$pos]) - $b[$i][0]) * pow(2, $i * 6);
+
+ for ($k = 1; $k < $len; $k++) {
+ $uni += (ord($username[$pos + $k]) - 128) * pow(2, ($i - $k) * 6);
+ }
+
+ break;
+ }
+ }
+
+ if ($len == 0) {
+ return false;
+ }
+
+ foreach ($p as $pval)
+ {
+ if ($uni >= $pval[0] && $uni <= $pval[1]) {
+ $result = false;
+ break 2;
+ }
+ }
+
+ $pos = $pos + $len;
+ }
+
+ return $result;
+ }
+
+ public static function jspecialchars($data)
+ {
+ return htmlspecialchars($data, ENT_QUOTES, 'utf-8');
+ }
+
+ // ======================================================================
+ // Third party code, taken from old jabber lib (the only usable code left)
+ // ======================================================================
+
+ // xmlize()
+ // (c) Hans Anderson / http://www.hansanderson.com/php/xml/
+
+ public static function xmlize($data, $WHITE=1, $encoding='UTF-8') {
+
+ $data = trim($data);
+ if (substr($data, 0, 5) != '<?xml') {
+ $data = '<root>'. $data . '</root>'; // mod
+ }
+ $vals = $array = array();
+ $parser = xml_parser_create($encoding);
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
+ xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $WHITE);
+ xml_parse_into_struct($parser, $data, $vals);
+ xml_parser_free($parser);
+
+ $i = 0;
+
+ $tagname = $vals[$i]['tag'];
+ if ( isset ($vals[$i]['attributes'] ) )
+ {
+ $array[$tagname][0]['@'] = $vals[$i]['attributes']; // mod
+ } else {
+ $array[$tagname][0]['@'] = array(); // mod
+ }
+
+ $array[$tagname][0]["#"] = Jabber::_xml_depth($vals, $i); // mod
+ if (substr($data, 0, 5) != '<?xml') {
+ $array = $array['root'][0]['#']; // mod
+ }
+
+ return $array;
+ }
+
+
+
+ // _xml_depth()
+ // (c) Hans Anderson / http://www.hansanderson.com/php/xml/
+
+ public static function _xml_depth($vals, &$i) {
+ $children = array();
+
+ if ( isset($vals[$i]['value']) )
+ {
+ array_push($children, $vals[$i]['value']);
+ }
+
+ while (++$i < count($vals)) {
+
+ switch ($vals[$i]['type']) {
+
+ case 'open':
+
+ if ( isset ( $vals[$i]['tag'] ) )
+ {
+ $tagname = $vals[$i]['tag'];
+ } else {
+ $tagname = '';
+ }
+
+ if ( isset ( $children[$tagname] ) )
+ {
+ $size = sizeof($children[$tagname]);
+ } else {
+ $size = 0;
+ }
+
+ if ( isset ( $vals[$i]['attributes'] ) ) {
+ $children[$tagname][$size]['@'] = $vals[$i]["attributes"];
+
+ }
+
+ $children[$tagname][$size]['#'] = Jabber::_xml_depth($vals, $i);
+
+ break;
+
+
+ case 'cdata':
+ array_push($children, $vals[$i]['value']);
+ break;
+
+ case 'complete':
+ $tagname = $vals[$i]['tag'];
+
+ if( isset ($children[$tagname]) )
+ {
+ $size = sizeof($children[$tagname]);
+ } else {
+ $size = 0;
+ }
+
+ if( isset ( $vals[$i]['value'] ) )
+ {
+ $children[$tagname][$size]["#"] = $vals[$i]['value'];
+ } else {
+ $children[$tagname][$size]["#"] = array();
+ }
+
+ if ( isset ($vals[$i]['attributes']) ) {
+ $children[$tagname][$size]['@']
+ = $vals[$i]['attributes'];
+ }
+
+ break;
+
+ case 'close':
+ return $children;
+ break;
+ }
+ }
+
+ return $children;
+ }
+}
+
diff --git a/includes/class.notify.php b/includes/class.notify.php
new file mode 100644
index 0000000..386158a
--- /dev/null
+++ b/includes/class.notify.php
@@ -0,0 +1,1114 @@
+<?php
+
+/*
+ ---------------------------------------------------
+ | This script contains the notification functions |
+ ---------------------------------------------------
+*/
+
+/**
+ * Notifications
+ *
+ * @package
+ * @version $Id$
+ * @copyright 2006 Flyspray.org
+ * @notes: This is a mess and should be replaced for 1.0
+ */
+
+class Notifications {
+
+ // {{{ Wrapper function for all others
+ function create($type, $task_id, $info = null, $to = null, $ntype = NOTIFY_BOTH, $proj_lang = null) {
+ global $fs;
+
+ if (is_null($to)) {
+ $to = $this->address($task_id, $type);
+ }
+
+ if (!is_array($to)) {
+ settype($to, 'array');
+ }
+
+ if (!count($to)) {
+ return false;
+ }
+
+ $languages = array();
+ $emails = array();
+ $jabbers = array();
+ $onlines = array();
+
+ if (isset($to[0])) {
+ foreach ($to[0] as $recipient) {
+ if (!empty($recipient['lang'])) {
+ $lang = $recipient['lang'];
+ } else if (!empty($proj_lang)) {
+ $lang = $proj_lang;
+ } else {
+ $lang = $fs->prefs['lang_code'];
+ }
+ $emails[$lang][] = $recipient['recipient'];
+ if (!in_array($lang, $languages)) {
+ $languages[] = $lang;
+ }
+ }
+ }
+
+ if (isset($to[1])) {
+ foreach ($to[1] as $recipient) {
+ if (!empty($recipient['lang'])) {
+ $lang = $recipient['lang'];
+ } else if (!empty($proj_lang)) {
+ $lang = $proj_lang;
+ } else {
+ $lang = $fs->prefs['lang_code'];
+ }
+ $jabbers[$lang][] = $recipient['recipient'];
+ if (!in_array($lang, $languages)) {
+ $languages[] = $lang;
+ }
+ }
+ }
+ /*
+ if (isset($to[2])) {
+ foreach ($to[2] as $recipient) {
+ $lang = $recipient['lang'];
+ if ($lang == 'j')
+ echo "<pre>Error 3!</pre>";
+ $onlines[$lang][] = $recipient['recipient'];
+ if (!in_array($lang, $languages)) {
+ $languages[] = $lang;
+ }
+ }
+ }
+ */
+
+ $result = true;
+ foreach ($languages as $lang) {
+ $msg = $this->generateMsg($type, $task_id, $info, $lang);
+ if (isset($emails[$lang]) && ($ntype == NOTIFY_EMAIL || $ntype == NOTIFY_BOTH)) {
+ if (!$this->sendEmail($emails[$lang], $msg[0], $msg[1], $task_id)) {
+ $result = false;
+ }
+ }
+
+ if (isset($jabbers[$lang]) && ($ntype == NOTIFY_JABBER || $ntype == NOTIFY_BOTH)) {
+ if (!$this->storeJabber($jabbers[$lang], $msg[0], $msg[1])) {
+ $result = false;
+ }
+ }
+
+ // Get rid of undefined offset 2 when notify type is explicitly set,
+ // in these cases caller really has not set offset 2. Track down the
+ // callers later.
+ /*
+ if (isset($onlines[$lang]) && ($ntype != NOTIFY_EMAIL && $ntype != NOTIFY_JABBER)) {
+ if (!$this->StoreOnline($onlines[$lang], $msg[2], $msg[3], $task_id)) {
+ $result = false;
+ }
+ }
+ */
+ }
+ return $result;
+
+ // End of Create() function
+ }
+
+ function storeOnline($to, $subject, $body, $online, $task_id = null) {
+ global $db, $fs;
+
+ if (!count($to)) {
+ return false;
+ }
+
+ $date = time();
+
+ // store notification in table
+ $db->query("INSERT INTO {notification_messages}
+ (message_subject, message_body, time_created)
+ VALUES (?, ?, ?)", array($online, '', $date)
+ );
+
+ // grab notification id
+ /*
+ $result = $db->query("SELECT message_id FROM {notification_messages}
+ WHERE time_created = ? ORDER BY message_id DESC", array($date), 1);
+
+ $row = $db->fetchRow($result);
+ $message_id = $row['message_id'];
+ */
+ $message_id = $db->insert_ID();
+ // If message could not be inserted for whatever reason...
+ if (!$message_id) {
+ return false;
+ }
+
+ // make sure every user is only added once
+ settype($to, 'array');
+ $to = array_unique($to);
+
+ foreach ($to as $jid) {
+ // store each recipient in table
+ $db->query("INSERT INTO {notification_recipients}
+ (notify_method, message_id, notify_address)
+ VALUES (?, ?, ?)", array('o', $message_id, $jid)
+ );
+ }
+
+ return true;
+ }
+
+ static function getUnreadNotifications() {
+ global $db, $fs, $user;
+
+ $notifications = $db->query('SELECT r.recipient_id, m.message_subject
+ FROM {notification_recipients} r
+ JOIN {notification_messages} m ON r.message_id = m.message_id
+ WHERE r.notify_method = ? AND notify_address = ?',
+ array('o', $user['user_id']));
+ return $db->fetchAllArray($notifications);
+ }
+
+ static function NotificationsHaveBeenRead($ids) {
+ global $db, $fs, $user;
+
+ $readones = join(",", array_map('intval', $ids));
+ if($readones !=''){
+ $db->query("
+ DELETE FROM {notification_recipients}
+ WHERE message_id IN ($readones)
+ AND notify_method = ?
+ AND notify_address = ?",
+ array('o', $user['user_id']
+ )
+ );
+ }
+ }
+
+ // {{{ Store Jabber messages for sending later
+ function storeJabber( $to, $subject, $body )
+ {
+ global $db, $fs;
+
+ if (empty($fs->prefs['jabber_server'])
+ || empty($fs->prefs['jabber_port'])
+ || empty($fs->prefs['jabber_username'])
+ || empty($fs->prefs['jabber_password'])) {
+ return false;
+ }
+
+ if (empty($to)) {
+ return false;
+ }
+
+ $date = time();
+
+ // store notification in table
+ $db->query("INSERT INTO {notification_messages}
+ (message_subject, message_body, time_created)
+ VALUES (?, ?, ?)",
+ array($subject, $body, $date)
+ );
+
+ // grab notification id
+ /*
+ $result = $db->query("SELECT message_id FROM {notification_messages}
+ WHERE time_created = ? ORDER BY message_id DESC",
+ array($date), 1);
+
+ $row = $db->fetchRow($result);
+ $message_id = $row['message_id'];
+ */
+ $message_id = $db->insert_ID();
+ // If message could not be inserted for whatever reason...
+ if (!$message_id) {
+ return false;
+ }
+
+ settype($to, 'array');
+
+ $duplicates = array();
+ foreach ($to as $jid) {
+ // make sure every recipient is only added once
+ if (in_array($jid, $duplicates)) {
+ continue;
+ }
+ $duplicates[] = $jid;
+ // store each recipient in table
+ $db->query("INSERT INTO {notification_recipients}
+ (notify_method, message_id, notify_address)
+ VALUES (?, ?, ?)",
+ array('j', $message_id, $jid)
+ );
+
+ }
+
+ return true;
+ } // }}}
+
+ static function jabberRequestAuth($email)
+ {
+ global $fs;
+
+ include_once BASEDIR . '/includes/class.jabber2.php';
+
+ if (empty($fs->prefs['jabber_server'])
+ || empty($fs->prefs['jabber_port'])
+ || empty($fs->prefs['jabber_username'])
+ || empty($fs->prefs['jabber_password'])) {
+ return false;
+ }
+
+ $JABBER = new Jabber($fs->prefs['jabber_username'] . '@' . $fs->prefs['jabber_server'],
+ $fs->prefs['jabber_password'],
+ $fs->prefs['jabber_ssl'],
+ $fs->prefs['jabber_port']);
+ $JABBER->login();
+ $JABBER->send("<presence to='" . Jabber::jspecialchars($email) . "' type='subscribe'/>");
+ $JABBER->disconnect();
+ }
+
+ // {{{ send Jabber messages that were stored earlier
+ function sendJabber()
+ {
+ global $db, $fs;
+
+ include_once BASEDIR . '/includes/class.jabber2.php';
+
+ if ( empty($fs->prefs['jabber_server'])
+ || empty($fs->prefs['jabber_port'])
+ || empty($fs->prefs['jabber_username'])
+ || empty($fs->prefs['jabber_password'])) {
+ return false;
+ }
+
+ // get listing of all pending jabber notifications
+ $result = $db->query("SELECT DISTINCT message_id
+ FROM {notification_recipients}
+ WHERE notify_method='j'");
+
+ if (!$db->countRows($result)) {
+ return false;
+ }
+
+ $JABBER = new Jabber($fs->prefs['jabber_username'] . '@' . $fs->prefs['jabber_server'],
+ $fs->prefs['jabber_password'],
+ $fs->prefs['jabber_ssl'],
+ $fs->prefs['jabber_port']);
+ $JABBER->login();
+
+ // we have notifications to process - connect
+ $JABBER->log("We have notifications to process...");
+ $JABBER->log("Starting Jabber session:");
+
+ $ids = array();
+
+ while ( $row = $db->fetchRow($result) ) {
+ $ids[] = $row['message_id'];
+ }
+
+ $desired = join(",", array_map('intval', $ids));
+ $JABBER->log("message ids to send = {" . $desired . "}");
+
+ // removed array usage as it's messing up the select
+ // I suspect this is due to the variable being comma separated
+ // Jamin W. Collins 20050328
+ $notifications = $db->query("
+ SELECT * FROM {notification_messages}
+ WHERE message_id IN ($desired)
+ ORDER BY time_created ASC"
+ );
+ $JABBER->log("number of notifications {" . $db->countRows($notifications) . "}");
+
+ // loop through notifications
+ while ( $notification = $db->fetchRow($notifications) ) {
+ $subject = $notification['message_subject'];
+ $body = $notification['message_body'];
+
+ $JABBER->log("Processing notification {" . $notification['message_id'] . "}");
+ $recipients = $db->query("
+ SELECT * FROM {notification_recipients}
+ WHERE message_id = ?
+ AND notify_method = 'j'",
+ array($notification['message_id'])
+ );
+
+ // loop through recipients
+ while ($recipient = $db->fetchRow($recipients) ) {
+ $jid = $recipient['notify_address'];
+ $JABBER->log("- attempting send to {" . $jid . "}");
+
+ // send notification
+ if ($JABBER->send_message($jid, $body, $subject, 'normal')) {
+ // delete entry from notification_recipients
+ $result = $db->query("DELETE FROM {notification_recipients}
+ WHERE message_id = ?
+ AND notify_method = 'j'
+ AND notify_address = ?",
+ array($notification['message_id'], $jid)
+ );
+ $JABBER->log("- notification sent");
+ } else {
+ $JABBER->log("- notification not sent");
+ }
+ }
+ // check to see if there are still recipients for this notification
+ $result = $db->query("SELECT * FROM {notification_recipients}
+ WHERE message_id = ?",
+ array($notification['message_id'])
+ );
+
+ if ( $db->countRows($result) == 0 ) {
+ $JABBER->log("No further recipients for message id {" . $notification['message_id'] . "}");
+ // remove notification no more recipients
+ $result = $db->query("DELETE FROM {notification_messages}
+ WHERE message_id = ?",
+ array($notification['message_id'])
+ );
+ $JABBER->log("- Notification deleted");
+ }
+ }
+
+ // disconnect from server
+ $JABBER->disconnect();
+ $JABBER->log("Disconnected from Jabber server");
+
+ return true;
+ } // }}}
+ // {{{ send email
+ function sendEmail($to, $subject, $body, $task_id = null)
+ {
+ global $fs, $proj, $user;
+
+ if (empty($to) || empty($to[0])) {
+ return;
+ }
+
+ // Do we want to use a remote mail server?
+ if (!empty($fs->prefs['smtp_server'])) {
+
+ // connection... SSL, TLS or none
+ if ($fs->prefs['email_tls']) {
+ $swiftconn = Swift_SmtpTransport::newInstance($fs->prefs['smtp_server'], 587, 'tls');
+ } else if ($fs->prefs['email_ssl']) {
+ $swiftconn = Swift_SmtpTransport::newInstance($fs->prefs['smtp_server'], 465, 'ssl');
+ } else {
+ $swiftconn = Swift_SmtpTransport::newInstance($fs->prefs['smtp_server']);
+ }
+
+ if ($fs->prefs['smtp_user']) {
+ $swiftconn->setUsername($fs->prefs['smtp_user']);
+ }
+
+ if ($fs->prefs['smtp_pass']){
+ $swiftconn->setPassword($fs->prefs['smtp_pass']);
+ }
+
+ if(defined('FS_SMTP_TIMEOUT')) {
+ $swiftconn->setTimeout(FS_SMTP_TIMEOUT);
+ }
+ // Use php's built-in mail() function
+ } else {
+ $swiftconn = Swift_MailTransport::newInstance();
+ }
+
+ // Make plaintext URLs into hyperlinks, but don't disturb existing ones!
+ $htmlbody = preg_replace("/(?<!\")(https?:\/\/)([a-zA-Z0-9\-.]+\.[a-zA-Z0-9\-]+([\/]([a-zA-Z0-9_\/\-.?&%=+#])*)*)/", '<a href="$1$2">$2</a>', $body);
+ $htmlbody = str_replace("\n","<br>", $htmlbody);
+
+ // Those constants used were introduced in 5.4.
+ if (version_compare(phpversion(), '5.4.0', '<')) {
+ $plainbody= html_entity_decode(strip_tags($body));
+ } else {
+ $plainbody= html_entity_decode(strip_tags($body), ENT_COMPAT | ENT_HTML401, 'utf-8');
+ }
+
+ $swift = Swift_Mailer::newInstance($swiftconn);
+
+ if(defined('FS_MAIL_LOGFILE')) {
+ $logger = new Swift_Plugins_Loggers_ArrayLogger();
+ $swift->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
+ }
+
+ $message = new Swift_Message($subject);
+ if (isset($fs->prefs['emailNoHTML']) && $fs->prefs['emailNoHTML'] == '1'){
+ $message->setBody($plainbody, 'text/plain');
+ }else{
+ $message->setBody($htmlbody, 'text/html');
+ $message->addPart($plainbody, 'text/plain');
+ }
+
+ $type = $message->getHeaders()->get('Content-Type');
+ $type->setParameter('charset', 'utf-8');
+
+ $message->getHeaders()->addTextHeader('Precedence', 'list');
+ $message->getHeaders()->addTextHeader('X-Mailer', 'Flyspray');
+
+ if ($proj->prefs['notify_reply']) {
+ $message->setReplyTo($proj->prefs['notify_reply']);
+ }
+
+ if (isset($task_id)) {
+ $hostdata = parse_url($GLOBALS['baseurl']);
+ $inreplyto = sprintf('<FS%d@%s>', $task_id, $hostdata['host']);
+ // see http://cr.yp.to/immhf/thread.html this does not seems to work though :(
+ $message->getHeaders()->addTextHeader('In-Reply-To', $inreplyto);
+ $message->getHeaders()->addTextHeader('References', $inreplyto);
+ }
+
+ // accepts string, array, or Swift_Address
+ if( is_array($to) && count($to)>1 ){
+ $message->setTo($fs->prefs['admin_email']);
+ $message->setBcc($to);
+ } else{
+ $message->setTo($to);
+ }
+ $message->setFrom(array($fs->prefs['admin_email'] => $proj->prefs['project_title']));
+ $swift->send($message);
+
+ if(defined('FS_MAIL_LOGFILE')) {
+ if(is_writable(dirname(FS_MAIL_LOGFILE))) {
+ if($fh = fopen(FS_MAIL_LOGFILE, 'ab')) {
+ fwrite($fh, $logger->dump());
+ fwrite($fh, php_uname());
+ fclose($fh);
+ }
+ }
+ }
+
+ return true;
+ } //}}}
+ // {{{ create a message for any occasion
+ function generateMsg($type, $task_id, $arg1 = '0', $lang) {
+ global $db, $fs, $user, $proj;
+
+ // Get the task details
+ $task_details = Flyspray::getTaskDetails($task_id);
+ if ($task_id) {
+ $proj = new Project($task_details['project_id']);
+ }
+
+ // Set the due date correctly
+ if ($task_details['due_date'] == '0') {
+ $due_date = tL('undecided', $lang);
+ } else {
+ $due_date = formatDate($task_details['due_date']);
+ }
+
+ // Set the due version correctly
+ if ($task_details['closedby_version'] == '0') {
+ $task_details['due_in_version_name'] = tL('undecided', $lang);
+ }
+
+ // Get the string of modification
+ $notify_type_msg = array(
+ 0 => tL('none'),
+ NOTIFY_TASK_OPENED => tL('taskopened', $lang),
+ NOTIFY_TASK_CHANGED => tL('pm.taskchanged', $lang),
+ NOTIFY_TASK_CLOSED => tL('taskclosed', $lang),
+ NOTIFY_TASK_REOPENED => tL('pm.taskreopened', $lang),
+ NOTIFY_DEP_ADDED => tL('pm.depadded', $lang),
+ NOTIFY_DEP_REMOVED => tL('pm.depremoved', $lang),
+ NOTIFY_COMMENT_ADDED => tL('commentadded', $lang),
+ NOTIFY_ATT_ADDED => tL('attachmentadded', $lang),
+ NOTIFY_REL_ADDED => tL('relatedadded', $lang),
+ NOTIFY_OWNERSHIP => tL('ownershiptaken', $lang),
+ NOTIFY_PM_REQUEST => tL('pmrequest', $lang),
+ NOTIFY_PM_DENY_REQUEST => tL('pmrequestdenied', $lang),
+ NOTIFY_NEW_ASSIGNEE => tL('newassignee', $lang),
+ NOTIFY_REV_DEP => tL('revdepadded', $lang),
+ NOTIFY_REV_DEP_REMOVED => tL('revdepaddedremoved', $lang),
+ NOTIFY_ADDED_ASSIGNEES => tL('assigneeadded', $lang),
+ );
+
+ // Generate the nofication message
+ if (isset($proj->prefs['notify_subject']) && !$proj->prefs['notify_subject']) {
+ $proj->prefs['notify_subject'] = '[%p][#%t] %s';
+ }
+ if (!isset($proj->prefs['notify_subject']) ||
+ $type == NOTIFY_CONFIRMATION ||
+ $type == NOTIFY_ANON_TASK ||
+ $type == NOTIFY_PW_CHANGE ||
+ $type == NOTIFY_NEW_USER ||
+ $type == NOTIFY_OWN_REGISTRATION) {
+ $subject = tL('notifyfromfs', $lang);
+ } else {
+ $subject = strtr($proj->prefs['notify_subject'], array('%p' => $proj->prefs['project_title'],
+ '%s' => $task_details['item_summary'],
+ '%t' => $task_id,
+ '%a' => $notify_type_msg[$type],
+ '%u' => $user->infos['user_name']));
+ }
+
+ $subject = strtr($subject, "\n", '');
+
+
+ /* -------------------------------
+ | List of notification types: |
+ | 1. Task opened |
+ | 2. Task details changed |
+ | 3. Task closed |
+ | 4. Task re-opened |
+ | 5. Dependency added |
+ | 6. Dependency removed |
+ | 7. Comment added |
+ | 8. Attachment added |
+ | 9. Related task added |
+ |10. Taken ownership |
+ |11. Confirmation code |
+ |12. PM request |
+ |13. PM denied request |
+ |14. New assignee |
+ |15. Reversed dep |
+ |16. Reversed dep removed |
+ |17. Added to assignees list |
+ |18. Anon-task opened |
+ |19. Password change |
+ |20. New user |
+ |21. User registration |
+ -------------------------------
+ */
+
+ $body = tL('donotreply', $lang) . "\n\n";
+ $online = '';
+
+ // {{{ New task opened
+ if ($type == NOTIFY_TASK_OPENED) {
+ $body .= tL('newtaskopened', $lang) . " \n\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ") \n\n";
+ $body .= tL('attachedtoproject', $lang) . ' - ' . $task_details['project_title'] . "\n";
+ $body .= tL('summary', $lang) . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('tasktype', $lang) . ' - ' . $task_details['tasktype_name'] . "\n";
+ $body .= tL('category', $lang) . ' - ' . $task_details['category_name'] . "\n";
+ $body .= tL('status', $lang) . ' - ' . $task_details['status_name'] . "\n";
+ $body .= tL('assignedto', $lang) . ' - ' . implode(', ', $task_details['assigned_to_name']) . "\n";
+ $body .= tL('operatingsystem', $lang) . ' - ' . $task_details['os_name'] . "\n";
+ $body .= tL('severity', $lang) . ' - ' . $task_details['severity_name'] . "\n";
+ $body .= tL('priority', $lang) . ' - ' . $task_details['priority_name'] . "\n";
+ $body .= tL('reportedversion', $lang) . ' - ' . $task_details['reported_version_name'] . "\n";
+ $body .= tL('dueinversion', $lang) . ' - ' . $task_details['due_in_version_name'] . "\n";
+ $body .= tL('duedate', $lang) . ' - ' . $due_date . "\n";
+ $body .= tL('details', $lang) . ' - ' . $task_details['detailed_desc'] . "\n\n";
+
+ if ($arg1 == 'files') {
+ $body .= tL('fileaddedtoo', $lang) . "\n\n";
+ $subject .= ' (' . tL('attachmentadded', $lang) . ')';
+ }
+
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('newtaskopened', $lang) . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ $online .= tL('attachedtoproject', $lang) . ' - ' . $task_details['project_title'] . ". ";
+ $online .= tL('summary', $lang) . ' - ' . $task_details['item_summary'];
+ } // }}}
+ // {{{ Task details changed
+ if ($type == NOTIFY_TASK_CHANGED) {
+ $translation = array('priority_name' => tL('priority', $lang),
+ 'severity_name' => tL('severity', $lang),
+ 'status_name' => tL('status', $lang),
+ 'assigned_to_name' => tL('assignedto', $lang),
+ 'due_in_version_name' => tL('dueinversion', $lang),
+ 'reported_version_name' => tL('reportedversion', $lang),
+ 'tasktype_name' => tL('tasktype', $lang),
+ 'os_name' => tL('operatingsystem', $lang),
+ 'category_name' => tL('category', $lang),
+ 'due_date' => tL('duedate', $lang),
+ 'percent_complete' => tL('percentcomplete', $lang),
+ 'mark_private' => tL('visibility', $lang),
+ 'item_summary' => tL('summary', $lang),
+ 'detailed_desc' => tL('taskedited', $lang),
+ 'project_title' => tL('attachedtoproject', $lang),
+ 'estimated_effort' => tL('estimatedeffort', $lang));
+
+ $body .= tL('taskchanged', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ': ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+
+ $online .= tL('taskchanged', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'];
+
+ foreach ($arg1 as $change) {
+ if ($change[0] == 'assigned_to_name') {
+ $change[1] = implode(', ', $change[1]);
+ $change[2] = implode(', ', $change[2]);
+ }
+
+ if ($change[0] == 'detailed_desc') {
+ $body .= $translation[$change[0]] . ":\n-------\n" . $change[2] . "\n-------\n";
+ } else {
+ $body .= $translation[$change[0]] . ': ' . ( ($change[1]) ? $change[1] : '[-]' ) . ' -> ' . ( ($change[2]) ? $change[2] : '[-]' ) . "\n";
+ }
+ }
+ $body .= "\n" . tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+ } // }}}
+ // {{{ Task closed
+ if ($type == NOTIFY_TASK_CLOSED) {
+ $body .= tL('notify.taskclosed', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('reasonforclosing', $lang) . ' ' . $task_details['resolution_name'] . "\n";
+
+ if (!empty($task_details['closure_comment'])) {
+ $body .= tL('closurecomment', $lang) . ' ' . $task_details['closure_comment'] . "\n\n";
+ }
+
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('notify.taskclosed', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Task re-opened
+ if ($type == NOTIFY_TASK_REOPENED) {
+ $body .= tL('notify.taskreopened', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('notify.taskreopened', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Dependency added
+ if ($type == NOTIFY_DEP_ADDED) {
+ $depend_task = Flyspray::getTaskDetails($arg1);
+
+ $body .= tL('newdep', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id) . "\n\n\n";
+ $body .= tL('newdepis', $lang) . ':' . "\n\n";
+ $body .= 'FS#' . $depend_task['task_id'] . ' - ' . $depend_task['item_summary'] . "\n";
+ $body .= createURL('details', $depend_task['task_id']);
+
+ $online .= tL('newdep', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Dependency removed
+ if ($type == NOTIFY_DEP_REMOVED) {
+ $depend_task = Flyspray::getTaskDetails($arg1);
+
+ $body .= tL('notify.depremoved', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id) . "\n\n\n";
+ $body .= tL('removeddepis', $lang) . ':' . "\n\n";
+ $body .= 'FS#' . $depend_task['task_id'] . ' - ' . $depend_task['item_summary'] . "\n";
+ $body .= createURL('details', $depend_task['task_id']);
+
+ $online .= tL('notify.depremoved', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Comment added
+ if ($type == NOTIFY_COMMENT_ADDED) {
+ // Get the comment information
+ $result = $db->query("SELECT comment_id, comment_text
+ FROM {comments}
+ WHERE user_id = ?
+ AND task_id = ?
+ ORDER BY comment_id DESC", array($user->id, $task_id), '1');
+ $comment = $db->fetchRow($result);
+
+ $body .= tL('notify.commentadded', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= "----------\n";
+ $body .= $comment['comment_text'] . "\n";
+ $body .= "----------\n\n";
+
+ if ($arg1 == 'files') {
+ $body .= tL('fileaddedtoo', $lang) . "\n\n";
+ $subject .= ' (' . tL('attachmentadded', $lang) . ')';
+ }
+
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id) . '#comment' . $comment['comment_id'];
+
+ $online .= tL('notify.commentadded', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Attachment added
+ if ($type == NOTIFY_ATT_ADDED) {
+ $body .= tL('newattachment', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('newattachment', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Related task added
+ if ($type == NOTIFY_REL_ADDED) {
+ $related_task = Flyspray::getTaskDetails($arg1);
+
+ $body .= tL('notify.relatedadded', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id) . "\n\n\n";
+ $body .= tL('relatedis', $lang) . ':' . "\n\n";
+ $body .= 'FS#' . $related_task['task_id'] . ' - ' . $related_task['item_summary'] . "\n";
+ $body .= createURL('details', $related_task['task_id']);
+
+ $online .= tL('notify.relatedadded', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Ownership taken
+ if ($type == NOTIFY_OWNERSHIP) {
+ $body .= implode(', ', $task_details['assigned_to_name']) . ' ' . tL('takenownership', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= implode(', ', $task_details['assigned_to_name']) . ' ' . tL('takenownership', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ".";
+ } // }}}
+ // {{{ Confirmation code
+ if ($type == NOTIFY_CONFIRMATION) {
+ $body .= tL('noticefrom', $lang) . " {$proj->prefs['project_title']}\n\n"
+ . tL('addressused', $lang) . "\n\n"
+ . " {$arg1[0]}index.php?do=register&magic_url={$arg1[1]} \n\n"
+ // In case that spaces in the username have been removed
+ . tL('username', $lang) . ': ' . $arg1[2] . "\n"
+ . tL('confirmcodeis', $lang) . " $arg1[3] \n\n";
+
+ $online = $body;
+ } // }}}
+ // {{{ Pending PM request
+ if ($type == NOTIFY_PM_REQUEST) {
+ $body .= tL('requiresaction', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho') . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('requiresaction', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ PM request denied
+ if ($type == NOTIFY_PM_DENY_REQUEST) {
+ $body .= tL('pmdeny', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('denialreason', $lang) . ':' . "\n";
+ $body .= $arg1 . "\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('pmdeny', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ New assignee
+ if ($type == NOTIFY_NEW_ASSIGNEE) {
+ $body .= tL('assignedtoyou', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n\n";
+ $body .= tL('moreinfo', $lang) . "\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('assignedtoyou', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Reversed dep
+ if ($type == NOTIFY_REV_DEP) {
+ $depend_task = Flyspray::getTaskDetails($arg1);
+
+ $body .= tL('taskwatching', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id) . "\n\n\n";
+ $body .= tL('isdepfor', $lang) . ':' . "\n\n";
+ $body .= 'FS#' . $depend_task['task_id'] . ' - ' . $depend_task['item_summary'] . "\n";
+ $body .= createURL('details', $depend_task['task_id']);
+
+ $online .= tL('taskwatching', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Reversed dep - removed
+ if ($type == NOTIFY_REV_DEP_REMOVED) {
+ $depend_task = Flyspray::getTaskDetails($arg1);
+
+ $body .= tL('taskwatching', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id) . "\n\n\n";
+ $body .= tL('isnodepfor', $lang) . ':' . "\n\n";
+ $body .= 'FS#' . $depend_task['task_id'] . ' - ' . $depend_task['item_summary'] . "\n";
+ $body .= createURL('details', $depend_task['task_id']);
+
+ $online .= tL('taskwatching', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ User added to assignees list
+ if ($type == NOTIFY_ADDED_ASSIGNEES) {
+ $body .= tL('useraddedtoassignees', $lang) . "\n\n";
+ $body .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . "\n";
+ $body .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . ")\n";
+ $body .= createURL('details', $task_id);
+
+ $online .= tL('useraddedtoassignees', $lang) . ". ";
+ $online .= 'FS#' . $task_id . ' - ' . $task_details['item_summary'] . ". ";
+ $online .= tL('userwho', $lang) . ' - ' . $user->infos['real_name'] . ' (' . $user->infos['user_name'] . "). ";
+ } // }}}
+ // {{{ Anon-task has been opened
+ if ($type == NOTIFY_ANON_TASK) {
+ $body .= tL('thankyouforbug', $lang) . "\n\n";
+ $body .= createURL('details', $task_id, null, array('task_token' => $arg1)) . "\n\n";
+
+ $online .= tL('thankyouforbug') . "";
+ } // }}}
+ // {{{ Password change
+ if ($type == NOTIFY_PW_CHANGE) {
+ $body = tL('magicurlmessage', $lang) . " \n"
+ . "{$arg1[0]}index.php?do=lostpw&magic_url=$arg1[1]\n\n"
+ . tL('messagefrom', $lang) . $arg1[0];
+ $online = $body;
+ } // } }}
+ // {{{ New user
+ if ($type == NOTIFY_NEW_USER) {
+ $body = tL('newuserregistered', $lang) . " \n\n"
+ . tL('username', $lang) . ': ' . $arg1[1] . "\n" .
+ tL('realname', $lang) . ': ' . $arg1[2] . "\n";
+ $online = $body;
+
+ if ($arg1[6]) {
+ $body .= tL('password', $lang) . ': ' . $arg1[5] . "\n";
+ }
+
+ $body .= tL('emailaddress', $lang) . ': ' . $arg1[3] . "\n";
+ $body .= tL('jabberid', $lang) . ':' . $arg1[4] . "\n\n";
+ $body .= tL('messagefrom', $lang) . $arg1[0];
+ } // }}}
+ // {{{ New user him/herself
+ if ($type == NOTIFY_OWN_REGISTRATION) {
+ $body = tL('youhaveregistered', $lang) . " \n\n"
+ . tL('username', $lang) . ': ' . $arg1[1] . "\n" .
+ tL('realname', $lang) . ': ' . $arg1[2] . "\n";
+ $online = $body;
+
+ if ($arg1[6]) {
+ $body .= tL('password', $lang) . ': ' . $arg1[5] . "\n";
+ }
+
+ $body .= tL('emailaddress', $lang) . ': ' . $arg1[3] . "\n";
+ $body .= tL('jabberid', $lang) . ':' . $arg1[4] . "\n\n";
+
+ // Add something here to tell the user whether the registration must
+ // first be accepted by Administrators or not. And if it had and was
+ // rejected, the reason. Check first what happening when requests are
+ // either denied or accepted.
+
+ $body .= tL('messagefrom', $lang) . $arg1[0];
+ } // }}}
+
+ $body .= "\n\n" . tL('disclaimer', $lang);
+ return array(Notifications::fixMsgData($subject), Notifications::fixMsgData($body), $online);
+ }
+
+// }}}
+
+ public static function assignRecipients($recipients, &$emails, &$jabbers, &$onlines, $ignoretype = false) {
+ global $db, $fs, $user;
+
+ if (!is_array($recipients)) {
+ return false;
+ }
+
+ foreach ($recipients as $recipient) {
+ if ($recipient['user_id'] == $user->id && !$user->infos['notify_own']) {
+ continue;
+ }
+
+ if (($fs->prefs['user_notify'] == '1' && ($recipient['notify_type'] == NOTIFY_EMAIL || $recipient['notify_type'] == NOTIFY_BOTH) ) || $fs->prefs['user_notify'] == '2' || $ignoretype) {
+ if (isset($recipient['email_address']) && !empty($recipient['email_address'])) {
+ $emails[$recipient['email_address']] = array('recipient' => $recipient['email_address'], 'lang' => $recipient['lang_code']);
+ }
+ }
+
+ if (($fs->prefs['user_notify'] == '1' && ($recipient['notify_type'] == NOTIFY_JABBER || $recipient['notify_type'] == NOTIFY_BOTH) ) || $fs->prefs['user_notify'] == '3' || $ignoretype) {
+ if (isset($recipient['jabber_id']) && !empty($recipient['jabber_id']) && $recipient['jabber_id']) {
+ $jabbers[$recipient['jabber_id']] = array('recipient' => $recipient['jabber_id'], 'lang' => $recipient['lang_code']);
+ }
+ }
+ /*
+ if ($fs->prefs['user_notify'] == '1' && $recipient['notify_online']) {
+ $onlines[$recipient['user_id']] = array('recipient' => $recipient['user_id'], 'lang' => $recipient['lang_code']);
+ }
+ */
+ }
+ }
+
+ // {{{ Create an address list for specific users
+ function specificAddresses($users, $ignoretype = false) {
+ global $db, $fs, $user;
+
+ $emails = array();
+ $jabbers = array();
+ $onlines = array();
+
+ if (!is_array($users)) {
+ settype($users, 'array');
+ }
+
+ if (count($users) < 1) {
+ return array();
+ }
+
+ $sql = $db->query('SELECT u.user_id, u.email_address, u.jabber_id,
+ u.notify_online, u.notify_type, u.notify_own, u.lang_code
+ FROM {users} u
+ WHERE' . substr(str_repeat(' user_id = ? OR ', count($users)), 0, -3), array_values($users));
+
+ self::assignRecipients($db->fetchAllArray($sql), $emails, $jabbers, $onlines, $ignoretype);
+
+ return array($emails, $jabbers, $onlines);
+ }
+
+// }}}
+
+ // {{{ Create a standard address list of users (assignees, notif tab and proj addresses)
+ function address($task_id, $type) {
+ global $db, $fs, $proj, $user;
+
+ $users = array();
+ $emails = array();
+ $jabbers = array();
+ $onlines = array();
+
+ $task_details = Flyspray::getTaskDetails($task_id);
+
+ // Get list of users from the notification tab
+ $get_users = $db->query('
+ SELECT * FROM {notifications} n
+ LEFT JOIN {users} u ON n.user_id = u.user_id
+ WHERE n.task_id = ?',
+ array($task_id)
+ );
+ self::assignRecipients($db->fetchAllArray($get_users), $emails, $jabbers, $onlines);
+
+ // Get list of assignees
+ $get_users = $db->query('
+ SELECT * FROM {assigned} a
+ LEFT JOIN {users} u ON a.user_id = u.user_id
+ WHERE a.task_id = ?',
+ array($task_id)
+ );
+ self::assignRecipients($db->fetchAllArray($get_users), $emails, $jabbers, $onlines);
+
+ // Now, we add the project contact addresses...
+ // ...but only if the task is public
+ if ($task_details['mark_private'] != '1'
+ && in_array($type, Flyspray::int_explode(' ', $proj->prefs['notify_types']))) {
+
+ // FIXME! Have to find users preferred language here too,
+ // must fetch from database. But the address could also be a mailing
+ // list address and user not exist in database, use fs->prefs in that case,
+
+ $proj_emails = preg_split('/[\s,;]+/', $proj->prefs['notify_email'], -1, PREG_SPLIT_NO_EMPTY);
+ $desired = implode("','", $proj_emails);
+ if($desired !=''){
+ $get_users = $db->query("
+ SELECT DISTINCT u.user_id, u.email_address, u.jabber_id,
+ u.notify_online, u.notify_type, u.notify_own, u.lang_code
+ FROM {users} u
+ WHERE u.email_address IN ('$desired')"
+ );
+
+ self::assignRecipients($db->fetchAllArray($get_users), $emails, $jabbers, $onlines);
+ }
+
+ $proj_jids = explode(',', $proj->prefs['notify_jabber']);
+ $desired = implode("','", $proj_jids);
+ if($desired!='') {
+ $get_users = $db->query("
+ SELECT DISTINCT u.user_id, u.email_address, u.jabber_id,
+ u.notify_online, u.notify_type, u.notify_own, u.lang_code
+ FROM {users} u
+ WHERE u.jabber_id IN ('$desired')"
+ );
+ self::assignRecipients($db->fetchAllArray($get_users), $emails, $jabbers, $onlines);
+ }
+
+ // Now, handle notification addresses that are not assigned to any user...
+ foreach ($proj_emails as $email) {
+ if (!array_key_exists($email, $emails)) {
+ $emails[$email] = array('recipient' => $email, 'lang' => $fs->prefs['lang_code']);
+ }
+ }
+
+ foreach ($proj_jids as $jabber) {
+ if (!array_key_exists($jabber, $jabbers)) {
+ $jabbers[$jabber] = array('recipient' => $jabber, 'lang' => $fs->prefs['lang_code']);
+ }
+ }
+ /*
+ echo "<pre>";
+ echo var_dump($proj_emails);
+ echo var_dump($proj_jids);
+ echo "</pre>";
+ */
+ // End of checking if a task is private
+ }
+ // Send back three arrays containing the notification addresses
+ return array($emails, $jabbers, $onlines);
+ }
+
+// }}}
+
+ // {{{ Fix the message data
+ /**
+ * fixMsgData
+ * a 0.9.9.x ONLY workaround for the "truncated email problem"
+ * based on code Henri Sivonen (http://hsivonen.iki.fi)
+ * @param mixed $data
+ * @access public
+ * @return void
+ */
+ function fixMsgData($data)
+ {
+ // at the first step, remove all NUL bytes
+ //users with broken databases encoding can give us this :(
+ $data = str_replace(chr(0), '', $data);
+
+ //then remove all invalid utf8 secuences
+ $UTF8_BAD =
+ '([\x00-\x7F]'. # ASCII (including control chars)
+ '|[\xC2-\xDF][\x80-\xBF]'. # non-overlong 2-byte
+ '|\xE0[\xA0-\xBF][\x80-\xBF]'. # excluding overlongs
+ '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'. # straight 3-byte
+ '|\xED[\x80-\x9F][\x80-\xBF]'. # excluding surrogates
+ '|\xF0[\x90-\xBF][\x80-\xBF]{2}'. # planes 1-3
+ '|[\xF1-\xF3][\x80-\xBF]{3}'. # planes 4-15
+ '|\xF4[\x80-\x8F][\x80-\xBF]{2}'. # plane 16
+ '|(.{1}))'; # invalid byte
+
+ $valid_data = '';
+
+ while (preg_match('/'.$UTF8_BAD.'/S', $data, $matches)) {
+ if ( !isset($matches[2])) {
+ $valid_data .= $matches[0];
+ } else {
+ $valid_data .= '?';
+ }
+ $data = substr($data, strlen($matches[0]));
+ }
+ return $valid_data;
+ } //}}}
+
+// End of Notify class
+}
diff --git a/includes/class.project.php b/includes/class.project.php
new file mode 100644
index 0000000..55a4231
--- /dev/null
+++ b/includes/class.project.php
@@ -0,0 +1,474 @@
+<?php
+
+class Project
+{
+ var $id = 0;
+ var $prefs = array();
+
+ function __construct($id)
+ {
+ global $db, $fs;
+
+ if (is_numeric($id)) {
+ $sql = $db->query("SELECT p.*, c.content AS pm_instructions, c.last_updated AS cache_update
+ FROM {projects} p
+ LEFT JOIN {cache} c ON c.topic = p.project_id AND c.type = 'msg'
+ WHERE p.project_id = ?", array($id));
+ if ($db->countRows($sql)) {
+ $this->prefs = $db->fetchRow($sql);
+ $this->id = (int) $this->prefs['project_id'];
+ $sortrules=explode(',', $this->prefs['default_order_by']);
+ foreach($sortrules as $rule){
+ $last_space=strrpos($rule, ' ');
+ if ($last_space === false){
+ # temporarly
+ $sorting[]=array('field'=>$rule, 'dir'=> $this->prefs['default_order_by_dir']);
+ # future - when column default_order_by_dir removed from project table:
+ #$sorting[]=array('field'=>$rule, 'dir'=>'desc');
+ }else{
+ $sorting[]=array(
+ 'field'=>trim(substr($rule, 0, $last_space)),
+ 'dir'=>trim(substr($rule, $last_space))
+ );
+ }
+ }
+ # using an extra name until default_order_by_dir completely removed
+ $this->prefs['sorting']=$sorting; # we can use this also for highlighting in template which columns are sorted by default in task list!
+
+ # For users with only the 'modify_own_tasks' permission within the project
+ # Currently hardcoded here to have it available for Flyspray1.0. May move to dbfield in future.
+ $this->prefs['basic_fields']=array(
+ 'item_summary',
+ 'detailed_desc',
+ 'task_type',
+ 'product_category',
+ 'operating_system',
+ 'task_severity',
+ 'percent_complete',
+ 'product_version',
+ 'estimated_effort'
+ );
+
+ return;
+ }
+ }
+
+ $this->id = 0;
+ $this->prefs['project_title'] = L('allprojects');
+ $this->prefs['feed_description'] = L('feedforall');
+ $this->prefs['theme_style'] = $fs->prefs['global_theme'];
+ $this->prefs['default_entry'] = $fs->prefs['default_entry'];
+ $this->prefs['lang_code'] = $fs->prefs['lang_code'];
+ $this->prefs['project_is_active'] = 1;
+ $this->prefs['others_view'] = 1;
+ $this->prefs['others_viewroadmap'] = 0;
+ $this->prefs['intro_message'] = '';
+ $this->prefs['anon_open'] = 0;
+ $this->prefs['feed_img_url'] = '';
+ $this->prefs['notify_reply'] = '';
+ $this->prefs['default_due_version'] = 'Undecided';
+ $this->prefs['disable_lostpw'] = 0;
+ $this->prefs['disable_changepw'] = 0;
+ $this->prefs['hours_per_manday'] = 0;
+ $this->prefs['estimated_effort_format'] = 0;
+ $this->prefs['current_effort_done_format'] = 0;
+ $this->prefs['custom_style']= $fs->prefs['custom_style'];
+
+ $sortrules=explode(',', $fs->prefs['default_order_by']);
+ foreach($sortrules as $rule){
+ $last_space=strrpos($rule, ' ');
+ if ($last_space === false){
+ # temporarly
+ $sorting[]=array('field'=>$rule, 'dir'=> $fs->prefs['default_order_by_dir']);
+ # future - when column default_order_by_dir removed from project table:
+ #$sorting[]=array('field'=>$rule, 'dir'=>'desc');
+ }else{
+ $sorting[]=array(
+ 'field'=>trim(substr($rule, 0, $last_space)),
+ 'dir'=>trim(substr($rule, $last_space))
+ );
+ }
+ }
+ # using an extra name until default_order_by_dir completely removed
+ $this->prefs['sorting']=$sorting;
+ }
+
+ # 20150219 peterdd: deprecated
+ function setCookie()
+ {
+ # 20150219 peterdd: unnecessary, setting and using a projectid-cookie makes parallel handling of 2 or more projects in different browser tabs impossible.
+ # instead, use form variables or variables from the url!
+ #Flyspray::setCookie('flyspray_project', $this->id);
+ }
+
+ /**
+ * private method
+ */
+ function _pm_list_sql($type, $join)
+ {
+ global $db;
+
+ // deny the possibility of shooting ourselves in the foot.
+ // although there is no risky usage atm, the api should never do unexpected things.
+ if(preg_match('![^A-Za-z0-9_]!', $type)) {
+ return '';
+ }
+ // Get the column names of list tables for the group by statement
+ $groupby = $db->getColumnNames('{list_' . $type . '}', 'l.' . $type . '_id', 'l.');
+
+ $join = 't.'.join(" = l.{$type}_id OR t.", $join)." = l.{$type}_id";
+
+ return "SELECT l.*, COUNT(t.task_id) AS used_in_tasks, COUNT(CASE t.is_closed WHEN 0 THEN 1 ELSE NULL END) AS opentasks, COUNT(CASE t.is_closed WHEN 1 THEN 1 ELSE NULL END) AS closedtasks
+ FROM {list_{$type}} l
+ LEFT JOIN {tasks} t ON ($join) AND (l.project_id=0 OR t.project_id = l.project_id)
+ WHERE l.project_id = ?
+ GROUP BY $groupby
+ ORDER BY list_position";
+ }
+
+ /**
+ * private method
+ *
+ * @param mixed $type
+ * @param mixed $where
+ * @access protected
+ * @return string
+ * @notes The $where parameter is dangerous, think twice what you pass there..
+ */
+ function _list_sql($type, $where = null)
+ {
+ // sanity check.
+ if(preg_match('![^A-Za-z0-9_]!', $type)) {
+ return '';
+ }
+
+ return "SELECT {$type}_id, {$type}_name
+ FROM {list_{$type}}
+ WHERE show_in_list = 1 AND ( project_id = ? OR project_id = 0 )
+ $where
+ ORDER BY list_position";
+ }
+
+ function listTaskTypes($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ return $db->cached_query(
+ 'pm_task_types'.$this->id,
+ $this->_pm_list_sql('tasktype', array('task_type')),
+ array($this->id));
+ } else {
+ return $db->cached_query(
+ 'task_types'.$this->id, $this->_list_sql('tasktype'), array($this->id));
+ }
+ }
+
+ function listOs($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ return $db->cached_query(
+ 'pm_os'.$this->id,
+ $this->_pm_list_sql('os', array('operating_system')),
+ array($this->id));
+ } else {
+ return $db->cached_query('os'.$this->id, $this->_list_sql('os'),
+ array($this->id));
+ }
+ }
+
+ function listVersions($pm = false, $tense = null, $reported_version = null)
+ {
+ global $db;
+
+ $params = array($this->id);
+
+ if (is_null($tense)) {
+ $where = '';
+ } else {
+ $where = 'AND version_tense = ?';
+ $params[] = $tense;
+ }
+
+ if ($pm) {
+ return $db->cached_query(
+ 'pm_version'.$this->id,
+ $this->_pm_list_sql('version', array('product_version', 'closedby_version')),
+ array($params[0]));
+ } elseif (is_null($reported_version)) {
+ return $db->cached_query(
+ 'version_'.$tense,
+ $this->_list_sql('version', $where),
+ $params);
+ } else {
+ $params[] = $reported_version;
+ return $db->cached_query(
+ 'version_'.$tense,
+ $this->_list_sql('version', $where . ' OR version_id = ?'),
+ $params);
+ }
+ }
+
+
+ function listCategories($project_id = null, $hide_hidden = true, $remove_root = true, $depth = true)
+ {
+ global $db, $conf;
+
+ // start with a empty arrays
+ $right = array();
+ $cats = array();
+ $g_cats = array();
+
+ // null = categories of current project + global project, int = categories of specific project
+ if (is_null($project_id)) {
+ $project_id = $this->id;
+ if ($this->id != 0) {
+ $g_cats = $this->listCategories(0);
+ }
+ }
+
+ // retrieve the left and right value of the root node
+ $result = $db->query("SELECT lft, rgt
+ FROM {list_category}
+ WHERE category_name = 'root' AND lft = 1 AND project_id = ?",
+ array($project_id));
+ $row = $db->fetchRow($result);
+
+ $groupby = $db->getColumnNames('{list_category}', 'c.category_id', 'c.');
+
+ // now, retrieve all descendants of the root node
+ $result = $db->query('SELECT c.category_id, c.category_name, c.*, count(t.task_id) AS used_in_tasks
+ FROM {list_category} c
+ LEFT JOIN {tasks} t ON (t.product_category = c.category_id)
+ WHERE c.project_id = ? AND lft BETWEEN ? AND ?
+ GROUP BY ' . $groupby . '
+ ORDER BY lft ASC',
+ array($project_id, intval($row['lft']), intval($row['rgt'])));
+
+ while ($row = $db->fetchRow($result)) {
+ if ($hide_hidden && !$row['show_in_list'] && $row['lft'] != 1) {
+ continue;
+ }
+
+ // check if we should remove a node from the stack
+ while (count($right) > 0 && $right[count($right)-1] < $row['rgt']) {
+ array_pop($right);
+ }
+ $cats[] = $row + array('depth' => count($right)-1);
+
+ // add this node to the stack
+ $right[] = $row['rgt'];
+ }
+
+ // Adjust output for select boxes
+ if ($depth) {
+ foreach ($cats as $key => $cat) {
+ if ($cat['depth'] > 0) {
+ $cats[$key]['category_name'] = str_repeat('...', $cat['depth']) . $cat['category_name'];
+ $cats[$key]['1'] = str_repeat('...', $cat['depth']) . $cat['1'];
+ }
+ }
+ }
+
+ if ($remove_root) {
+ unset($cats[0]);
+ }
+
+ return array_merge($cats, $g_cats);
+ }
+
+ function listResolutions($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ return $db->cached_query(
+ 'pm_resolutions'.$this->id,
+ $this->_pm_list_sql('resolution', array('resolution_reason')),
+ array($this->id));
+ } else {
+ return $db->cached_query('resolution'.$this->id,
+ $this->_list_sql('resolution'), array($this->id));
+ }
+ }
+
+ function listTaskStatuses($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ return $db->cached_query(
+ 'pm_statuses'.$this->id,
+ $this->_pm_list_sql('status', array('item_status')),
+ array($this->id));
+ } else {
+ return $db->cached_query('status'.$this->id,
+ $this->_list_sql('status'), array($this->id));
+ }
+ }
+
+ /* between FS0.9.9.7 to FS1.0alpha2 */
+ /*
+ function listTags($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ $result= $db->query('SELECT tag AS tag_name, 1 AS list_position, 1 AS show_in_list, COUNT(*) AS used_in_tasks
+ FROM {tags} tg
+ JOIN {tasks} t ON t.task_id=tg.task_id
+ WHERE t.project_id=?
+ GROUP BY tag
+ ORDER BY tag', array($this->id));
+ } else {
+ $result= $db->query('SELECT tag AS tag_name, 1 AS list_position, 1 AS show_in_list, COUNT(*) AS used_in_tasks
+ FROM {tags}
+ GROUP BY tag
+ ORDER BY tag');
+ }
+
+ $tags=array();
+ while ($row = $db->fetchRow($result)) {
+ $tags[]=$row;
+ }
+ return $tags;
+ }
+ */
+ /* rewrite of tags feature, FS1.0beta1 */
+ function listTags($pm = false)
+ {
+ global $db;
+ if ($pm) {
+ $result= $db->query('SELECT tg.*, COUNT(tt.task_id) AS used_in_tasks
+ FROM {list_tag} tg
+ LEFT JOIN {task_tag} tt ON tt.tag_id=tg.tag_id
+ LEFT JOIN {tasks} t ON t.task_id=tt.task_id
+ WHERE tg.project_id=?
+ GROUP BY tg.tag_id
+ ORDER BY tg.list_position', array($this->id));
+ $tags=array();
+ while ($row = $db->fetchRow($result)) {
+ $tags[]=$row;
+ }
+ return $tags;
+ } else {
+ return $db->cached_query('tag'.$this->id, $this->_list_sql('tag'), array($this->id));
+ }
+ }
+
+ // This should really be moved to class Flyspray like some other ones too.
+ // Something todo for 1.1.
+ static function listUsersIn($group_id = null)
+ {
+ global $db;
+ return $db->cached_query(
+ 'users_in'.(is_null($group_id) ? $group_id : intval($group_id)),
+ "SELECT u.*
+ FROM {users} u
+ INNER JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ INNER JOIN {groups} g ON uig.group_id = g.group_id
+ WHERE g.group_id = ?
+ ORDER BY u.user_name ASC",
+ array($group_id));
+ }
+
+ function listAttachments($cid, $tid)
+ {
+ global $db;
+ return $db->cached_query(
+ 'attach_'.intval($cid),
+ "SELECT *
+ FROM {attachments}
+ WHERE comment_id = ? AND task_id = ?
+ ORDER BY attachment_id ASC",
+ array($cid, $tid));
+ }
+
+ function listLinks($cid, $tid)
+ {
+ global $db;
+ return $db->cached_query(
+ 'link_'.intval($cid),
+ "SELECT *
+ FROM {links}
+ WHERE comment_id = ? AND task_id = ?
+ ORDER BY link_id ASC",
+ array($cid, $tid));
+ }
+
+ function listTaskAttachments($tid)
+ {
+ global $db;
+ return $db->cached_query(
+ 'attach_'.intval($tid),
+ "SELECT * FROM {attachments}
+ WHERE task_id = ? AND comment_id = 0
+ ORDER BY attachment_id ASC",
+ array($tid)
+ );
+ }
+
+ function listTaskLinks($tid)
+ {
+ global $db;
+ return $db->cached_query(
+ 'link_'.intval($tid),
+ "SELECT * FROM {links}
+ WHERE task_id = ? AND comment_id = 0
+ ORDER BY link_id ASC",
+ array($tid));
+ }
+
+ /**
+ * Returns the activity by between dates for a project.
+ * @param date $startdate
+ * @param date $enddate
+ * @param integer $project_id
+ * @return array used to get the count
+ * @access public
+ */
+ static function getActivityProjectCount($startdate, $enddate, $project_id) {
+ global $db;
+ $result = $db->query('SELECT count(event_date) as val
+ FROM {history} h left join {tasks} t on t.task_id = h.task_id
+ WHERE t.project_id = ? AND event_date BETWEEN ? and ?',
+ array($project_id, $startdate, $enddate));
+
+ $result = $db->fetchCol($result);
+ return $result[0];
+ }
+
+ /**
+ * Returns the day activity by the date for a project.
+ * @param date $date
+ * @param integer $project_id
+ * @return array used to get the count
+ * @access public
+ */
+ static function getDayActivityByProject($date_start, $date_end, $project_id) {
+ global $db;
+ //NOTE: from_unixtime() on mysql, to_timestamp() on PostreSQL
+ $func = ('mysql' == $db->dblink->dataProvider) ? 'from_unixtime' : 'to_timestamp';
+
+ $result = $db->query("SELECT count(date({$func}(event_date))) as val, MIN(event_date) as event_date
+ FROM {history} h left join {tasks} t on t.task_id = h.task_id
+ WHERE t.project_id = ? AND event_date BETWEEN ? and ?
+ GROUP BY date({$func}(event_date)) ORDER BY event_date DESC",
+ array($project_id, $date_start, $date_end));
+
+ $date1 = new \DateTime("@$date_start");
+ $date2 = new \DateTime("@$date_end");
+ $days = $date1->diff($date2);
+ $days = $days->format('%a');
+ $results = array();
+
+ for ($i = $days; $i >0; $i--) {
+ $event_date = (string) strtotime("-{$i} day", $date_end);
+ $results[date('Y-m-d', $event_date)] = 0;
+ }
+
+ while ($row = $result->fetchRow()) {
+ $event_date = date('Y-m-d', $row['event_date']);
+ $results[$event_date] = (integer) $row['val'];
+ }
+
+ return array_values($results);
+ }
+}
diff --git a/includes/class.recaptcha.php b/includes/class.recaptcha.php
new file mode 100644
index 0000000..d998d43
--- /dev/null
+++ b/includes/class.recaptcha.php
@@ -0,0 +1,33 @@
+<?php
+/* quick solution
+* https://developers.google.com/recaptcha/docs/verify
+*/
+class recaptcha
+{
+
+ function verify(){
+ global $fs;
+
+ $url = 'https://www.google.com/recaptcha/api/siteverify';
+ $data = array(
+ 'secret' => $fs->prefs['captcha_recaptcha_secret'],
+ 'response' => $_POST['g-recaptcha-response']
+ );
+
+ $options = array(
+ 'http' => array (
+ 'method' => 'POST',
+ /* for php5.3, default enctype for http_build_query() was added with php5.4, http://php.net/manual/en/function.http-build-query.php */
+ 'header' => 'Content-type: application/x-www-form-urlencoded',
+ 'content' => http_build_query($data, '', '&')
+ )
+ );
+
+ $context = stream_context_create($options);
+ $verify = file_get_contents($url, false, $context);
+ $captcha_success=json_decode($verify);
+
+ return $captcha_success->success;
+ }
+
+} # end class
diff --git a/includes/class.tpl.php b/includes/class.tpl.php
new file mode 100644
index 0000000..24b1105
--- /dev/null
+++ b/includes/class.tpl.php
@@ -0,0 +1,1525 @@
+<?php
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+class Tpl
+{
+ public $_uses = array();
+ public $_vars = array();
+ public $_theme = '';
+ public $_tpls = array();
+ public $_title = "";
+
+ public function uses()
+ {
+ $args = func_get_args();
+ $this->_uses = array_merge($this->_uses, $args);
+ }
+
+ public function assign($arg0 = null, $arg1 = null)
+ {
+ if (is_string($arg0)) {
+ $this->_vars[$arg0] = $arg1;
+ }elseif (is_array($arg0)) {
+ $this->_vars += $arg0;
+ }elseif (is_object($arg0)) {
+ $this->_vars += get_object_vars($arg0);
+ }
+ }
+
+ public function getTheme()
+ {
+ return $this->_theme;
+ }
+
+ public function setTheme($theme)
+ {
+ // Check available themes
+ $theme = trim($theme, '/');
+ $themes = Flyspray::listThemes();
+ if (in_array($theme, $themes)) {
+ $this->_theme = $theme.'/';
+ } else {
+ $this->_theme = $themes[0].'/';
+ }
+ }
+
+ public function setTitle($title)
+ {
+ $this->_title = $title;
+ }
+
+ public function themeUrl()
+ {
+ return sprintf('%sthemes/%s', $GLOBALS['baseurl'], $this->_theme);
+ }
+
+ public function pushTpl($_tpl)
+ {
+ $this->_tpls[] = $_tpl;
+ }
+
+ public function catch_start()
+ {
+ ob_start();
+ }
+
+ public function catch_end()
+ {
+ $this->_tpls[] = array(ob_get_contents());
+ ob_end_clean();
+ }
+
+ public function display($_tpl, $_arg0 = null, $_arg1 = null)
+ {
+ // if only plain text
+ if (is_array($_tpl) && count($tpl)) {
+ echo $_tpl[0];
+ return;
+ }
+
+ // variables part
+ if (!is_null($_arg0)) {
+ $this->assign($_arg0, $_arg1);
+ }
+
+ foreach ($this->_uses as $_var) {
+ global $$_var;
+ }
+
+ extract($this->_vars, EXTR_REFS|EXTR_SKIP);
+
+ if (is_readable(BASEDIR . '/themes/' . $this->_theme.'templates/'.$_tpl)) {
+ require BASEDIR . '/themes/' . $this->_theme.'templates/'.$_tpl;
+ } elseif (is_readable(BASEDIR . '/themes/CleanFS/templates/'.$_tpl)) {
+ # if a custom theme folder only contains a fraction of the .tpl files, use the template of the default full theme as fallback.
+ require BASEDIR . '/themes/CleanFS/templates/'.$_tpl;
+ } else {
+ # This is needed to catch times when there is no theme (for example setup pages, where BASEDIR is ../setup/ not ../)
+ require BASEDIR . "/templates/".$_tpl;
+ }
+ }
+
+ public function render()
+ {
+ while (count($this->_tpls)) {
+ $this->display(array_shift($this->_tpls));
+ }
+ }
+
+ public function fetch($tpl, $arg0 = null, $arg1 = null)
+ {
+ ob_start();
+ $this->display($tpl, $arg0, $arg1);
+ return ob_get_clean();
+ }
+}
+
+class FSTpl extends Tpl
+{
+ public $_uses = array('fs', 'conf', 'baseurl', 'language', 'proj', 'user');
+
+ public function get_image($name, $base = true)
+ {
+ global $proj, $baseurl;
+ $pathinfo = pathinfo($name);
+ $link = sprintf('themes/%s/', $proj->prefs['theme_style']);
+ if ($pathinfo['dirname'] != '.') {
+ $link .= $pathinfo['dirname'] . '/';
+ $name = $pathinfo['basename'];
+ }
+
+ $extensions = array('.png', '.gif', '.jpg', '.ico');
+
+ foreach ($extensions as $ext) {
+ if (is_file(BASEDIR . '/' . $link . $name . $ext)) {
+ return ($base) ? ($baseurl . $link . $name . $ext) : ($link . $name . $ext);
+ }
+ }
+ return '';
+ }
+
+}
+
+/**
+ * Draws the form start tag and the important anticsrftoken on 'post'-forms
+ *
+ * @param string action
+ * @param string name optional attribute of form tag
+ * @param string method optional request method, default 'post'
+ * @param string enctype optional enctype, default 'multipart/form-data'
+ * @param string attr optional attributes for the form tag, example: 'id="myformid" class="myextracssclass"'
+ *
+ * @return string
+ */
+function tpl_form($action, $name=null, $method=null, $enctype=null, $attr='')
+{
+ global $baseurl;
+
+ if (null === $method) {
+ $method='post';
+ }
+ if (null === $enctype) {
+ $enctype='multipart/form-data';
+ }
+
+ if(substr($action,0,4)!='http'){$action=$baseurl.$action;}
+ return '<form action="'.$action.'"'.($method=='get'?' method="get"':' method="post"').
+ ( $name!='' ? ' name="'.$name.'"':'').
+ ( ' enctype="'.$enctype.'"').
+ ( ' '.$attr).'>'.
+ ( $method=='post' ? '<input type="hidden" name="csrftoken" value="'.$_SESSION['csrftoken'].'" />':'');
+}
+
+/**
+ * Creates a link to a task
+ *
+ * @param array task with properties of a task. It also accepts a task_id, but that requires extra queries executed by this function.
+ * @param string text optional, by default the FS# + summary of task is used.
+ * @param bool strict check task permissions by the function too. Extra SQL queries if set true. default false.
+ * @param array attr extra attributes
+ * @param array title informations shown when hover over the link (title attribute of the HTML a-tag)
+ *
+ * @return string ready for html output
+ */
+function tpl_tasklink($task, $text = null, $strict = false, $attrs = array(), $title = array('status','summary','percent_complete'))
+{
+ global $user;
+
+ $params = array();
+
+ if (!is_array($task) || !isset($task['status_name'])) {
+ $td_id = (is_array($task) && isset($task['task_id'])) ? $task['task_id'] : $task;
+ $task = Flyspray::getTaskDetails($td_id, true);
+ }
+
+ if ($strict === true && (!is_object($user) || !$user->can_view_task($task))) {
+ return '';
+ }
+
+ if (is_object($user) && $user->can_view_task($task)) {
+ $summary = utf8_substr($task['item_summary'], 0, 64);
+ } else {
+ $summary = L('taskmadeprivate');
+ }
+
+ if (is_null($text)) {
+ $text = sprintf('FS#%d - %s', $task['task_id'], Filters::noXSS($summary));
+ } elseif(is_string($text)) {
+ $text = htmlspecialchars(utf8_substr($text, 0, 64), ENT_QUOTES, 'utf-8');
+ } else {
+ //we can't handle non-string stuff here.
+ return '';
+ }
+
+ if (!$task['task_id']) {
+ return $text;
+ }
+
+ $title_text = array();
+
+ foreach($title as $info)
+ {
+ switch($info)
+ {
+ case 'status':
+ if ($task['is_closed']) {
+ $title_text[] = $task['resolution_name'];
+ $attrs['class'] = 'closedtasklink';
+ } else {
+ $title_text[] = $task['status_name'];
+ }
+ break;
+
+ case 'summary':
+ $title_text[] = $summary;
+ break;
+
+ case 'assignedto':
+ if (isset($task['assigned_to_name']) ) {
+ if (is_array($task['assigned_to_name'])) {
+ $title_text[] = implode(', ', $task['assigned_to_name']);
+ } else {
+ $title_text[] = $task['assigned_to_name'];
+ }
+ }
+ break;
+
+ case 'percent_complete':
+ $title_text[] = $task['percent_complete'].'%';
+ break;
+
+ case 'category':
+ if ($task['product_category']) {
+ if (!isset($task['category_name'])) {
+ $task = Flyspray::getTaskDetails($task['task_id'], true);
+ }
+ $title_text[] = $task['category_name'];
+ }
+ break;
+
+ // ... more options if necessary
+ }
+ }
+
+ $title_text = implode(' | ', $title_text);
+
+ // to store search options
+ $params = $_GET;
+ unset($params['do'], $params['action'], $params['task_id'], $params['switch']);
+ if(isset($params['event_number'])){
+ # shorter links to tasks from report page
+ unset($params['events'], $params['event_number'], $params['fromdate'], $params['todate'], $params['submit']);
+ }
+
+ # We can unset the project param for shorter urls because flyspray knows project_id from current task data.
+ # Except we made a search from an 'all projects' view before, so the prev/next navigation on details page knows
+ # if it must search only in the project of current task or all projects the user is allowed to see tasks.
+ if(!isset($params['advancedsearch']) || (isset($params['project']) && $params['project']!=0) ){
+ unset($params['project']);
+ }
+
+ $url = htmlspecialchars(createURL('details', $task['task_id'], null, $params), ENT_QUOTES, 'utf-8');
+ $title_text = htmlspecialchars($title_text, ENT_QUOTES, 'utf-8');
+ $link = sprintf('<a href="%s" title="%s" %s>%s</a>',$url, $title_text, join_attrs($attrs), $text);
+
+ if ($task['is_closed']) {
+ $link = '<del>&#160;' . $link . '&#160;</del>';
+ }
+ return $link;
+}
+
+/*
+ * Creates a textlink to a user profile.
+ *
+ * For a link with user icon use tpl_userlinkavatar().
+ *
+ * @param int uid user_id from {users} db table
+ */
+function tpl_userlink($uid)
+{
+ global $db, $user;
+
+ static $cache = array();
+
+ if (is_array($uid)) {
+ list($uid, $uname, $rname) = $uid;
+ } elseif (empty($cache[$uid])) {
+ $sql = $db->query('SELECT user_name, real_name FROM {users} WHERE user_id = ?',
+ array(intval($uid)));
+ if ($sql && $db->countRows($sql)) {
+ list($uname, $rname) = $db->fetchRow($sql);
+ }
+ }
+
+ if (isset($uname)) {
+ #$url = createURL(($user->perms('is_admin')) ? 'edituser' : 'user', $uid);
+ # peterdd: I think it is better just to link to the user's page instead direct to the 'edit user' page also for admins.
+ # With more personalisation coming (personal todo list, charts, ..) in future to flyspray
+ # the user page itself is of increasing value. Instead show the 'edit user'-button on user's page.
+ $url = createURL('user', $uid);
+ $cache[$uid] = vsprintf('<a href="%s">%s</a>', array_map(array('Filters', 'noXSS'), array($url, $rname)));
+ } elseif (empty($cache[$uid])) {
+ $cache[$uid] = eL('anonymous');
+ }
+
+ return $cache[$uid];
+}
+
+/**
+* Builds the HTML string for displaying a gravatar image or an uploaded user image.
+* The string for a user and a size is cached per request.
+*
+* Class and style parameter should be avoided to make this function more effective for caching (less SQL queries)
+*
+* @param int uid the id of the user
+* @param int size in pixel for displaying. Should use global max_avatar_size pref setting by default.
+* @param string class optional, avoid calling with class parameter for better 'cacheability'
+* @param string style optional, avoid calling with style parameter for better 'cacheability'
+*/
+function tpl_userlinkavatar($uid, $size, $class='', $style='')
+{
+ global $db, $user, $baseurl, $fs;
+
+ static $avacache=array();
+
+ if( !($uid>0) ){
+ return '<i class="fa fa-user"></i>';
+ }
+
+ if($uid>0 && (empty($avacache[$uid]) || !isset($avacache[$uid][$size]))){
+ if (!isset($avacache[$uid]['uname'])) {
+ $sql = $db->query('SELECT user_name, real_name, email_address, profile_image FROM {users} WHERE user_id = ?', array(intval($uid)));
+ if ($sql && $db->countRows($sql)) {
+ list($uname, $rname, $email, $profile_image) = $db->fetchRow($sql);
+ } else {
+ return;
+ }
+ $avacache[$uid]['profile_image'] = $profile_image;
+ $avacache[$uid]['uname'] = $uname;
+ $avacache[$uid]['rname'] = $rname;
+ $avacache[$uid]['email'] = $email;
+ }
+
+ if (is_file(BASEDIR.'/avatars/'.$avacache[$uid]['profile_image'])) {
+ $image = '<img src="'.$baseurl.'avatars/'.$avacache[$uid]['profile_image'].'"/>';
+ } else {
+ if (isset($fs->prefs['gravatars']) && $fs->prefs['gravatars'] == 1) {
+ $email = md5(strtolower(trim($avacache[$uid]['email'])));
+ $default = 'mm';
+ $imgurl = '//www.gravatar.com/avatar/'.$email.'?d='.urlencode($default).'&s='.$size;
+ $image = '<img src="'.$imgurl.'"/>';
+ } else {
+ $image = '<i class="fa fa-user" style="font-size:'.$size.'px"></i>';
+ }
+ }
+ if (isset($avacache[$uid]['uname'])) {
+ #$url = createURL(($user->perms('is_admin')) ? 'edituser' : 'user', $uid);
+ # peterdd: I think it is better just to link to the user's page instead direct to the 'edit user' page also for admins.
+ # With more personalisation coming (personal todo list, charts, ..) in future to flyspray
+ # the user page itself is of increasing value. Instead show the 'edit user'-button on user's page.
+ $url = createURL('user', $uid);
+ $avacache[$uid][$size] = '<a'.($class!='' ? ' class="'.$class.'"':'').($style!='' ? ' style="'.$style.'"':'').' href="'.$url.'" title="'.Filters::noXSS($avacache[$uid]['rname']).'">'.$image.'</a>';
+ }
+ }
+ return $avacache[$uid][$size];
+}
+
+function tpl_fast_tasklink($arr)
+{
+ return tpl_tasklink($arr[1], $arr[0]);
+}
+
+/**
+ * Formats a task tag for HTML output based on a global $alltags array
+ *
+ * @param int id tag_id of {list_tag} db table
+ * @param bool showid set true if the tag_id is shown instead of the tag_name
+ *
+ * @return string ready for output
+ */
+function tpl_tag($id, $showid=false) {
+ global $alltags;
+
+ if(!is_array($alltags)) {
+ $alltags=Flyspray::getAllTags();
+ }
+
+ if(isset($alltags[$id])){
+ $out='<i class="tag t'.$id;
+ if( isset($alltags[$id]['class']) && preg_match('/^#([0-9a-f]{3}){1,2}$/i', $alltags[$id]['class']) ) {
+ $out.= '" style="background-color:'.$alltags[$id]['class'];
+ # max only calc once per tag per request
+ # assumes theme css of default tag font color is #000
+ if(!isset($alltags[$id]['fcolor'])){
+ $bg = hex2RGB($alltags[$id]['class']);
+ # from https://www.w3.org/TR/AERT/#color-contrast
+ $brightness=(299*$bg['r'] + 587*$bg['g'] + 114*$bg['b']) / 1000;
+ if($brightness<126){
+ $out.=';color:#fff';
+ $alltags[$id]['fcolor']='#fff';
+ }else{
+ $alltags[$id]['fcolor']='';
+ }
+ } else if( $alltags[$id]['fcolor']==='#fff'){
+ $out.=';color:#fff';
+ }
+ $out.='"';
+ } else {
+ $out.= (isset($alltags[$id]['class']) ? ' '.htmlspecialchars($alltags[$id]['class'], ENT_QUOTES, 'utf-8') : '').'"';
+ }
+ if($showid){
+ $out.='>'.$id;
+ } else{
+ $out.=' title="'.htmlspecialchars($alltags[$id]['tag_name'], ENT_QUOTES, 'utf-8').'">';
+ }
+
+ $out.='</i>';
+ return $out;
+ }
+}
+
+/**
+* Convert a hexa decimal color code to its RGB equivalent
+*
+* used by tpl_tag()
+*
+* @param string $hexstr (hexadecimal color value)
+* @param boolean $returnasstring (if set true, returns the value separated by the separator character. Otherwise returns associative array)
+* @param string $seperator (to separate RGB values. Applicable only if second parameter is true.)
+* @return array or string (depending on second parameter. Returns False if invalid hex color value)
+*
+* function is adapted from an exmaple on http://php.net/manual/de/function.hexdec.php
+*/
+function hex2RGB($hexstr, $returnasstring = false, $seperator = ',') {
+ $hexstr = preg_replace("/[^0-9A-Fa-f]/", '', $hexstr); // Gets a proper hex string
+ $rgb = array();
+ if (strlen($hexstr) == 6) { // if a proper hex code, convert using bitwise operation. No overhead... faster
+ $colorval = hexdec($hexstr);
+ $rgb['r'] = 0xFF & ($colorval >> 0x10);
+ $rgb['g'] = 0xFF & ($colorval >> 0x8);
+ $rgb['b'] = 0xFF & $colorval;
+ } elseif (strlen($hexstr) == 3) { // if shorthand notation, need some string manipulations
+ $rgb['r'] = hexdec(str_repeat(substr($hexstr, 0, 1), 2));
+ $rgb['g'] = hexdec(str_repeat(substr($hexstr, 1, 1), 2));
+ $rgb['b'] = hexdec(str_repeat(substr($hexstr, 2, 1), 2));
+ } else {
+ return false; // invalid hex color code
+ }
+ return $returnasstring ? implode($seperator, $rgb) : $rgb; // returns the rgb string or the associative array
+}
+
+/**
+ * joins an array of tag attributes together for output in a HTML tag.
+ *
+ * @param array attr
+ *
+ * @return string
+ */
+function join_attrs($attr = null) {
+ if (is_array($attr) && count($attr)) {
+ $arr = array();
+ foreach ($attr as $key=>$val) {
+ $arr[] = vsprintf('%s = "%s"', array_map(array('Filters', 'noXSS'), array($key, $val)));
+ }
+ return ' '.join(' ', $arr);
+ }
+ return '';
+}
+
+/**
+ * Datepicker
+ */
+function tpl_datepicker($name, $label = '', $value = 0) {
+ global $user, $page;
+
+ $date = '';
+
+ if ($value) {
+ if (!is_numeric($value)) {
+ $value = strtotime($value);
+ }
+
+ if (!$user->isAnon()) {
+ $st = date('Z')/3600; // server GMT timezone
+ $value += ($user->infos['time_zone'] - $st) * 60 * 60;
+ }
+
+ $date = date('Y-m-d', intval($value));
+
+ /* It must "look" as a date..
+ * XXX : do not blindly copy this code to validate other dates
+ * this is mostly a tongue-in-cheek validation
+ * 1. it will fail on 32 bit systems on dates < 1970
+ * 2. it will produce different results bewteen 32 and 64 bit systems for years < 1970
+ * 3. it will not work when year > 2038 on 32 bit systems (see http://en.wikipedia.org/wiki/Year_2038_problem)
+ *
+ * Fortunately tasks are never opened to be dated on 1970 and maybe our sons or the future flyspray
+ * coders may be willing to fix the 2038 issue ( in the strange case 32 bit systems are still used by that year) :-)
+ */
+
+ } elseif (Req::has($name) && strlen(Req::val($name))) {
+
+ //strtotime sadly returns -1 on faliure in php < 5.1 instead of false
+ $ts = strtotime(Req::val($name));
+
+ foreach (array('m','d','Y') as $period) {
+ //checkdate only accepts arguments of type integer
+ $$period = intval(date($period, $ts));
+ }
+ // $ts has to be > 0 to get around php behavior change
+ // false is casted to 0 by the ZE
+ $date = ($ts > 0 && checkdate($m, $d, $Y)) ? Req::val($name) : '';
+ }
+
+
+ $subPage = new FSTpl;
+ $subPage->setTheme($page->getTheme());
+ $subPage->assign('name', $name);
+ $subPage->assign('date', $date);
+ $subPage->assign('label', $label);
+ $subPage->assign('dateformat', '%Y-%m-%d');
+ $subPage->display('common.datepicker.tpl');
+}
+
+/**
+ * user selector
+ */
+function tpl_userselect($name, $value = null, $id = '', $attrs = array()) {
+ global $db, $user, $proj;
+
+ if (!$id) {
+ $id = $name;
+ }
+
+ if ($value && ctype_digit($value)) {
+ $sql = $db->query('SELECT user_name FROM {users} WHERE user_id = ?', array($value));
+ $value = $db->fetchOne($sql);
+ }
+
+ if (!$value) {
+ $value = '';
+ }
+
+
+ $page = new FSTpl;
+ $page->setTheme($proj->prefs['theme_style']);
+ $page->assign('name', $name);
+ $page->assign('id', $id);
+ $page->assign('value', $value);
+ $page->assign('attrs', $attrs);
+ $page->display('common.userselect.tpl');
+}
+
+/**
+ * Creates the options for a date format select
+ *
+ * @selected The format that should by selected by default
+ * @return html formatted options for a select tag
+**/
+function tpl_date_formats($selected, $detailed = false)
+{
+ $time = time();
+
+ # TODO: rewrite using 'return tpl_select(...)'
+ if (!$detailed) {
+ $dateFormats = array(
+ '%d.%m.%Y' => strftime('%d.%m.%Y', $time).' (DD.MM.YYYY)', # popular in many european countries
+ '%d/%m/%Y' => strftime('%d/%m/%Y', $time).' (DD/MM/YYYY)', # popular in Greek
+ '%m/%d/%Y' => strftime('%m/%d/%Y', $time).' (MM/DD/YYYY)', # popular in USA
+
+ '%d.%m.%y' => strftime('%d.%m.%y', $time),
+
+ '%Y.%m.%d' => strftime('%Y.%m.%d', $time),
+ '%y.%m.%d' => strftime('%y.%m.%d', $time),
+
+ '%d-%m-%Y' => strftime('%d-%m-%Y', $time),
+ '%d-%m-%y' => strftime('%d-%m-%y', $time),
+
+ '%Y-%m-%d' => strftime('%Y-%m-%d', $time).' (YYYY-MM-DD, ISO 8601)',
+ '%y-%m-%d' => strftime('%y-%m-%d', $time),
+
+ '%d %b %Y' => strftime('%d %b %Y', $time),
+ '%d %B %Y' => strftime('%d %B %Y', $time),
+
+ '%b %d %Y' => strftime('%b %d %Y', $time),
+ '%B %d %Y' => strftime('%B %d %Y', $time),
+ );
+ }
+ else {
+ # TODO: maybe use optgroups for tpl_select() to separate 24h and 12h (am/pm) formats
+ $dateFormats = array(
+ '%d.%m.%Y %H:%M' => strftime('%d.%m.%Y %H:%M', $time),
+ '%d.%m.%y %H:%M' => strftime('%d.%m.%y %H:%M', $time),
+
+ '%d.%m.%Y %I:%M %p' => strftime('%d.%m.%Y %I:%M %p', $time),
+ '%d.%m.%y %I:%M %p' => strftime('%d.%m.%y %I:%M %p', $time),
+
+ '%Y.%m.%d %H:%M' => strftime('%Y.%m.%d %H:%M', $time),
+ '%y.%m.%d %H:%M' => strftime('%y.%m.%d %H:%M', $time),
+
+ '%Y.%m.%d %I:%M %p' => strftime('%Y.%m.%d %I:%M %p', $time),
+ '%y.%m.%d %I:%M %p' => strftime('%y.%m.%d %I:%M %p', $time),
+
+ '%d-%m-%Y %H:%M' => strftime('%d-%m-%Y %H:%M', $time),
+ '%d-%m-%y %H:%M' => strftime('%d-%m-%y %H:%M', $time),
+
+ '%d-%m-%Y %I:%M %p' => strftime('%d-%m-%Y %I:%M %p', $time),
+ '%d-%m-%y %I:%M %p' => strftime('%d-%m-%y %I:%M %p', $time),
+
+ '%Y-%m-%d %H:%M' => strftime('%Y-%m-%d %H:%M', $time),
+ '%y-%m-%d %H:%M' => strftime('%y-%m-%d %H:%M', $time),
+
+ '%Y-%m-%d %I:%M %p' => strftime('%Y-%m-%d %I:%M %p', $time),
+ '%y-%m-%d %I:%M %p' => strftime('%y-%m-%d %I:%M %p', $time),
+
+ '%d %b %Y %H:%M' => strftime('%d %b %Y %H:%M', $time),
+ '%d %B %Y %H:%M' => strftime('%d %B %Y %H:%M', $time),
+
+ '%d %b %Y %I:%M %p' => strftime('%d %b %Y %I:%M %p', $time),
+ '%d %B %Y %I:%M %p' => strftime('%d %B %Y %I:%M %p', $time),
+
+ '%b %d %Y %H:%M' => strftime('%b %d %Y %H:%M', $time),
+ '%B %d %Y %H:%M' => strftime('%B %d %Y %H:%M', $time),
+
+ '%b %d %Y %I:%M %p' => strftime('%b %d %Y %I:%M %p', $time),
+ '%B %d %Y %I:%M %p' => strftime('%B %d %Y %I:%M %p', $time),
+ );
+ }
+
+ return tpl_options($dateFormats, $selected);
+}
+
+
+/**
+ * Options for a <select>
+ *
+ * FIXME peterdd: This function is currently often called by templates with just
+ * results from sqltablequeries like select * from tablex,
+ * so data[0] and data[1] of each row works only by table structure convention as wished.
+ * not by names, lack of a generic optgroup feature, css-id, css-classes, disabled option.
+ * Maybe rewrite a as tpl_select() ..
+ *
+ * @options array of values
+ * For optgroups, the values should be presorted by the optgroups
+ * example:
+ * $options=array(
+ * array(3,'project3',1), # active project group
+ * array(2,'project2',1),
+ * array(5,'project5',0) # inactive project optgroup
+ * ); tpl_options($options, 2)
+*/
+function tpl_options($options, $selected = null, $labelIsValue = false, $attr = null, $remove = null)
+{
+ $html = '';
+
+ // force $selected to be an array.
+ // this allows multi-selects to have multiple selected options.
+ $selected = is_array($selected) ? $selected : (array) $selected;
+ $options = is_array($options) ? $options : (array) $options;
+
+ $lastoptgroup=0;
+ $optgroup=0;
+ $ingroup=false;
+ foreach ($options as $idx=>$data) {
+ if (is_array($data)) {
+ $value = $data[0];
+ $label = $data[1];
+ # just a temp hack, we currently use optgroups only for project dropdown...
+ $optgroup=array_key_exists('project_is_active',$data) ? $data['project_is_active'] : 0;
+ if (array_key_exists('project_title', $data) && $optgroup!=$lastoptgroup) {
+ if ($ingroup) {
+ $html.='</optgroup>';
+ }
+ $html.='<optgroup'.($optgroup==0 ? ' label="'.L('inactive').'"' : '' ).'>';
+ $ingroup=true;
+ }
+ } else{
+ $value=$idx;
+ $label=$data;
+ }
+ $label = htmlspecialchars($label, ENT_QUOTES, 'utf-8');
+ $value = $labelIsValue ? $label : htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ if ($value === $remove) {
+ continue;
+ }
+
+ $html .= '<option value="'.$value.'"';
+ if (in_array($value, $selected)) {
+ $html .= ' selected="selected"';
+ }
+ $html .= ($attr ? join_attrs($attr): '') . '>' . $label . '</option>';
+ $lastoptgroup=$optgroup;
+ }
+
+ if ($ingroup) {
+ $html.='</optgroup>';
+ }
+
+ if (!$html) {
+ $html .= '<option value="0">---</option>';
+ }
+
+ return $html;
+}
+
+
+/**
+ * Builds a complete HTML-select with select options.
+ *
+ * Supports free choosable attributes for select, options and optgroup tags. optgroups can also be nested.
+ *
+ * @author peterdd
+ *
+ * @param array key-values pairs and can be nested
+ *
+ * @return string the complete html-select
+ *
+ * @since 1.0.0-beta3
+ *
+ * @example
+ * example output of print_r($array) to see the structure of the param $array
+ * Array
+ (
+ [name] => varname // required if you want submit it with a form to the server
+ [attr] => Array // optional
+ (
+ [id] => selid // optional
+ [class] => selclass1 // optional
+ )
+ [options] => Array // optional, but without doesn't make much sense ;-)
+ (
+ [0] => Array // at least one would be useful
+ (
+ [value] => opt1val // recommended
+ [label] => opt1label // recommended
+ [disabled] => 1 // optional
+ [selected] => 1 // optional
+ [attr] => Array // optional
+ (
+ [id] => opt1id // optional
+ [class] => optclass1 // optional
+ )
+ )
+ [1] => Array
+ (
+ [optgroup] => 1 // this tells the function that now comes an optgroup
+ [label] => optgrouplabel // optional
+ [attr] => Array // optional
+ (
+ [id] => optgroupid1 // optional
+ [class] => optgroupclass1 // optional
+ )
+ [options] => Array
+ // ... nested options and optgroups can follow here....
+ )
+ // ... and so on
+ )
+ )
+ */
+function tpl_select($select=array()){
+
+ if(isset($select['name'])){
+ $name=' name="'.$select['name'].'"';
+ }else{
+ $name='';
+ }
+
+ $attrjoin='';
+ if(isset($select['attr'])){
+ foreach($select['attr'] as $key=>$val){
+ $attrjoin.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html='<select'.$name.$attrjoin.'>';
+ if(isset($select['options'])){
+ $html.=tpl_selectoptions($select['options']);
+ }
+ $html.="\n".'</select>';
+ return $html;
+}
+
+/**
+ * called by tpl_select()
+ *
+ * @author peterdd
+ *
+ * @param array key-values pairs and can be nested
+ *
+ * @return string option- and optgroup-tags as one string
+ *
+ * @since 1.0.0-beta3
+ *
+ * called recursively by itself
+ * Can also be called alone from template if the templates writes the wrapping select-tags.
+ *
+ * @example see [options]-array of example of tpl_select()
+ */
+function tpl_selectoptions($options=array(), $level=0){
+ $html='';
+ # such deep nesting is too weired - probably an endless loop lets
+ # return before something bad happens
+ if( $level>10){
+ return;
+ }
+ #print_r($options);
+ #print_r($level);
+ foreach($options as $o){
+ if(isset($o['optgroup'])){
+ # we have an optgroup
+ $html.="\n".str_repeat("\t",$level).'<optgroup label="'.$o['label'].'"';
+ if(isset($o['attr'])){
+ foreach($o['attr'] as $key=>$val){
+ $html.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html.='>';
+ # may contain options and suboptgroups..
+ $html.=tpl_selectoptions($o['options'], $level+1);
+ $html.="\n".str_repeat("\t",$level).'</optgroup>';
+ } else{
+ # we have a simple option
+ $html.="\n".str_repeat("\t",$level).'<option value="'.htmlspecialchars($o['value'], ENT_QUOTES, 'utf-8').'"';
+ if(isset($o['disabled'])){
+ $html.=' disabled="disabled"'; # xhtml compatible
+ }
+ if(isset($o['selected'])){
+ $html.=' selected="selected"'; # xhtml compatible
+ }
+ if(isset($o['attr'])){
+ foreach($o['attr'] as $key=>$val){
+ $html.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html.='>'.htmlspecialchars($o['label'], ENT_QUOTES, 'utf-8').'</option>';
+ }
+ }
+
+ return $html;
+}
+
+
+/**
+ * Creates a double select.
+ *
+ * Elements of arrays $options and $selected can be moved between eachother. The $selected list can also be sorted.
+ *
+ * @param string name
+ * @param array options
+ * @param array selected
+ * @param bool labelisvalue
+ * @param bool updown
+ */
+function tpl_double_select($name, $options, $selected = null, $labelisvalue = false, $updown = true)
+{
+ static $_id = 0;
+ static $tpl = null;
+
+ if (!$tpl) {
+ global $proj;
+
+ // poor man's cache
+ $tpl = new FSTpl();
+ $tpl->setTheme($proj->prefs['theme_style']);
+ }
+
+ settype($selected, 'array');
+ settype($options, 'array');
+
+ $tpl->assign('id', '_task_id_'.($_id++));
+ $tpl->assign('name', $name);
+ $tpl->assign('selected', $selected);
+ $tpl->assign('updown', $updown);
+
+ $html = $tpl->fetch('common.dualselect.tpl');
+
+ $selectedones = array();
+
+ $opt1 = '';
+ foreach ($options as $value => $label) {
+ if (is_array($label) && count($label) >= 2) {
+ $value = $label[0];
+ $label = $label[1];
+ }
+ if ($labelisvalue) {
+ $value = $label;
+ }
+ if (in_array($value, $selected)) {
+ $selectedones[$value] = $label;
+ continue;
+ }
+ $label = htmlspecialchars($label, ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ $opt1 .= sprintf('<option title="%2$s" value="%1$s">%2$s</option>', $value, $label);
+ }
+
+ $opt2 = '';
+ foreach ($selected as $value) {
+ if (!isset($selectedones[$value])) {
+ continue;
+ }
+ $label = htmlspecialchars($selectedones[$value], ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ $opt2 .= sprintf('<option title="%2$s" value="%1$s">%2$s</option>', $value, $label);
+ }
+
+ return sprintf($html, $opt1, $opt2);
+}
+
+/**
+ * Creates a HTML checkbox
+ *
+ * @param string name
+ * @param bool checked
+ * @param string id id attribute of the checkbox HTML element
+ * @param string value
+ * @param array attr tag attributes
+ *
+ * @return string for ready for HTML output
+ */
+function tpl_checkbox($name, $checked = false, $id = null, $value = 1, $attr = null)
+{
+ $name = htmlspecialchars($name, ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+ $html = sprintf('<input type="checkbox" name="%s" value="%s" ', $name, $value);
+ if (is_string($id)) {
+ $html .= sprintf('id="%s" ', Filters::noXSS($id));
+ }
+ if ($checked == true) {
+ $html .= 'checked="checked" ';
+ }
+ // do not call join_attrs if $attr is null or nothing..
+ return ($attr ? $html. join_attrs($attr) : $html) . '/>';
+}
+
+/**
+ * Image display
+ */
+function tpl_img($src, $alt = '')
+{
+ global $baseurl;
+ if (is_file(BASEDIR .'/'.$src)) {
+ return sprintf('<img src="%s%s" alt="%s" />', $baseurl, Filters::noXSS($src), Filters::noXSS($alt));
+ }
+ return Filters::noXSS($alt);
+}
+
+// Text formatting
+//format has been already checked in constants.inc.php
+if(isset($conf['general']['syntax_plugin'])) {
+
+ $path_to_plugin = BASEDIR . '/plugins/' . $conf['general']['syntax_plugin'] . '/' . $conf['general']['syntax_plugin'] . '_formattext.inc.php';
+
+ if (is_readable($path_to_plugin)) {
+ include($path_to_plugin);
+ }
+}
+
+class TextFormatter
+{
+ public static function get_javascript()
+ {
+ global $conf;
+
+ $path_to_plugin = sprintf('%s/plugins/%s', BASEDIR, $conf['general']['syntax_plugin']);
+ $return = array();
+
+ if (!is_readable($path_to_plugin)) {
+ return $return;
+ }
+
+ $d = dir($path_to_plugin);
+ while (false !== ($entry = $d->read())) {
+ if (substr($entry, -3) == '.js') {
+ $return[] = $conf['general']['syntax_plugin'] . '/' . $entry;
+ }
+ }
+
+ return $return;
+ }
+
+ public static function render($text, $type = null, $id = null, $instructions = null)
+ {
+ global $conf;
+
+ $methods = get_class_methods($conf['general']['syntax_plugin'] . '_TextFormatter');
+ $methods = is_array($methods) ? $methods : array();
+
+ if (in_array('render', $methods)) {
+ return call_user_func(array($conf['general']['syntax_plugin'] . '_TextFormatter', 'render'),
+ $text, $type, $id, $instructions);
+ } else {
+ $text=strip_tags($text, '<br><br/><p><h2><h3><h4><h5><h5><h6><blockquote><a><img><u><b><strong><s><ins><del><ul><ol><li><table><caption><tr><col><colgroup><td><th><thead><tfoot><tbody><code>');
+ if ( $conf['general']['syntax_plugin']
+ && $conf['general']['syntax_plugin'] != 'none'
+ && $conf['general']['syntax_plugin'] != 'html') {
+ $text='Unsupported output plugin '.$conf['general']['syntax_plugin'].'!'
+ .'<br/>Couldn\'t call '.$conf['general']['syntax_plugin'].'_TextFormatter::render()'
+ .'<br/>Temporarily handled like it is HTML until fixed.<br/>'
+ .$text;
+ }
+
+ //TODO: Remove Redundant Code once tested completely
+ //Author: Steve Tredinnick
+ //Have removed this as creating additional </br> lines even though <p> is already dealing with it
+ //possibly an conversion from Dokuwiki syntax to html issue, left in in case anyone has issues and needs to comment out
+ //$text = ' ' . nl2br($text) . ' ';
+
+ // Change FS#123 into hyperlinks to tasks
+ return preg_replace_callback("/\b(?:FS#|bug )(\d+)\b/", 'tpl_fast_tasklink', trim($text));
+ }
+ }
+
+ public static function textarea($name, $rows, $cols, $attrs = null, $content = null)
+ {
+ global $conf;
+
+ if (@in_array('textarea', get_class_methods($conf['general']['syntax_plugin'] . '_TextFormatter'))) {
+ return call_user_func(array($conf['general']['syntax_plugin'] . '_TextFormatter', 'textarea'),
+ $name, $rows, $cols, $attrs, $content);
+ }
+
+ $name = htmlspecialchars($name, ENT_QUOTES, 'utf-8');
+ $return = sprintf('<textarea name="%s" cols="%d" rows="%d"', $name, $cols, $rows);
+ if (is_array($attrs) && count($attrs)) {
+ $return .= join_attrs($attrs);
+ }
+ $return .= '>';
+ if (is_string($content) && strlen($content)) {
+ $return .= htmlspecialchars($content, ENT_QUOTES, 'utf-8');
+ }
+ $return .= '</textarea>';
+
+ # Activate CkEditor on textareas
+ if($conf['general']['syntax_plugin']=='html'){
+ $return .= "
+<script>
+ CKEDITOR.replace( '".$name."', { entities: true, entities_latin: false, entities_processNumerical: false } );
+</script>";
+ }
+
+ return $return;
+ }
+}
+
+/**
+ * Format Date
+ *
+ * Questionable if this function belongs in this class. Usages also elsewhere and not UI-related.
+ */
+function formatDate($timestamp, $extended = false, $default = '')
+{
+ global $db, $conf, $user, $fs;
+
+ setlocale(LC_ALL, str_replace('-', '_', L('locale')) . '.utf8');
+
+ if (!$timestamp) {
+ return $default;
+ }
+
+ $dateformat = '';
+ $format_id = $extended ? 'dateformat_extended' : 'dateformat';
+ $st = date('Z')/3600; // server GMT timezone
+
+ if (!$user->isAnon()) {
+ $dateformat = $user->infos[$format_id];
+ $timestamp += ($user->infos['time_zone'] - $st) * 60 * 60;
+ $st = $user->infos['time_zone'];
+ }
+
+ if (!$dateformat) {
+ $dateformat = $fs->prefs[$format_id];
+ }
+
+ if (!$dateformat) {
+ $dateformat = $extended ? '%A, %d %B %Y, %H:%M %GMT' : '%Y-%m-%d';
+ }
+
+ $zone = L('GMT') . (($st == 0) ? ' ' : (($st > 0) ? '+' . $st : $st));
+ $dateformat = str_replace('%GMT', $zone, $dateformat);
+ //it returned utf-8 encoded by the system
+ return strftime(Filters::noXSS($dateformat), (int) $timestamp);
+}
+
+/**
+ * Draw permissions table
+ */
+function tpl_draw_perms($perms)
+{
+ global $proj;
+
+ $perm_fields = array(
+ 'is_admin',
+ 'manage_project',
+ 'view_tasks',
+ 'view_groups_tasks',
+ 'view_own_tasks',
+ 'open_new_tasks',
+ 'add_multiple_tasks',
+ 'modify_own_tasks',
+ 'modify_all_tasks',
+ 'create_attachments',
+ 'delete_attachments',
+ 'assign_to_self',
+ 'assign_others_to_self',
+ 'edit_assignments',
+ 'close_own_tasks',
+ 'close_other_tasks',
+ 'view_roadmap',
+ 'view_history',
+ 'view_reports',
+ 'add_votes',
+ 'view_comments',
+ 'add_comments',
+ 'edit_comments',
+ 'edit_own_comments',
+ 'delete_comments',
+ 'view_estimated_effort',
+ 'view_current_effort_done',
+ 'track_effort'
+ );
+
+ $yesno = array(
+ '<td class="bad fa fa-ban" title="'.eL('no').'"></td>',
+ '<td class="good fa fa-check" title="'.eL('yes').'"></td>'
+ );
+
+ # 20150307 peterdd: This a temporary hack
+ $i=0;
+ $html='';
+ $projpermnames='';
+
+ foreach ($perms as $projperm){
+ $html .= '<table class="perms"><thead><tr><th>'.($i==0? 'global' : L('project').' '.$i).'</th>'.($i==0? '<th>'.L('permissions').'</th>' : '').'</tr></thead><tbody>';
+ foreach ($projperm as $key => $val) {
+ if (!is_numeric($key) && in_array($key, $perm_fields)) {
+ $html .= '<tr>';
+ $html .= $yesno[ ($val || $perms[0]['is_admin']) ];
+ $html .= $i==0 ? '<th>'.eL(str_replace('_','',$key)).'</th>' : '';
+ $html .= '</tr>';
+
+ # all projects have same permnames
+ $projpermnames .= $i==1 ? '<tr><td>'.eL(str_replace('_','',$key)).'</td></tr>' : '';
+ }
+ }
+ $html.= '</tbody></table>';
+ $i++;
+ }
+ $html.='<table class="perms"><thead><th>'.L('permissions').'</th></thead><tbody>'.$projpermnames.'</tbody></table>';
+ $html.='<style>.perms tr{height:30px;}</style>';
+ # end 20150307
+ return $html;
+}
+
+/**
+ * Highlights searchqueries in HTML code
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function html_hilight($html,$query){
+ //split at common delimiters
+ $queries = preg_split ('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>]+/',$query,-1,PREG_SPLIT_NO_EMPTY);
+ foreach ($queries as $q){
+ $q = preg_quote($q,'/');
+ $html = preg_replace_callback("/((<[^>]*)|$q)/i",'html_hilight_callback',$html);
+ }
+ return $html;
+}
+
+/**
+ * Callback used by html_hilight()
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function html_hilight_callback($m) {
+ $hlight = unslash($m[0]);
+ if ( !isset($m[2])) {
+ $hlight = '<span class="search_hit">'.$hlight.'</span>';
+ }
+ return $hlight;
+}
+
+/**
+ * XHTML compatible output of disabled attribute
+ *
+ * @param bool if something that PHP sees as true or false
+ */
+function tpl_disableif ($if)
+{
+ if ($if) {
+ return 'disabled="disabled"';
+ }
+}
+
+/**
+ * Generates links for Flyspray
+ *
+ * Create an URL based upon address-rewriting preferences
+ *
+ */
+function createURL($type, $arg1 = null, $arg2 = null, $arg3 = array())
+{
+ global $baseurl, $conf, $fs;
+
+ $url = $baseurl;
+
+ // If we do want address rewriting
+ if ($fs->prefs['url_rewriting']) {
+ switch ($type) {
+ case 'depends':
+ $return = $url . 'task/' . $arg1 . '/' . $type;
+ break;
+ case 'details':
+ $return = $url . 'task/' . $arg1;
+ break;
+ case 'edittask':
+ $return = $url . 'task/' . $arg1 . '/edit';
+ break;
+ case 'pm':
+ $return = $url . 'pm/proj' . $arg2 . '/' . $arg1;
+ break;
+
+ case 'admin':
+ case 'edituser':
+ case 'user':
+ $return = $url . $type . '/' . $arg1;
+ break;
+
+ case 'project':
+ $return = $url . 'proj' . $arg1;
+ break;
+
+ case 'reports':
+ case 'roadmap':
+ case 'toplevel':
+ case 'gantt':
+ case 'index':
+ $return = $url.$type.'/proj'.$arg1;
+ break;
+
+ case 'newtask':
+ case 'newmultitasks':
+ $return = $url . $type . '/proj' . $arg1 . ($arg2 ? '/supertask' . $arg2 : '');
+ break;
+
+ case 'editgroup':
+ $return = $url . $arg2 . '/' . $type . '/' . $arg1;
+ break;
+
+ case 'logout':
+ case 'lostpw':
+ case 'myprofile':
+ case 'register':
+ $return = $url . $type;
+ break;
+
+ case 'mytasks':
+ $return = $url.'proj'.$arg1.'/dev'.$arg2;
+ break;
+ case 'tasklist':
+ # see also .htaccess for the mapping
+ if($arg1>0 && $fs->projects[$arg1]['default_entry']=='index'){
+ $return = $url.'proj'.$arg1;
+ }else{
+ $return = $url.$type.'/proj'.$arg1;
+ }
+
+ break;
+ default:
+ $return = $baseurl . 'index.php';
+ break;
+ }
+ } else {
+ if ($type == 'edittask') {
+ $url .= 'index.php?do=details';
+ } else {
+ $url .= 'index.php?do=' . $type;
+ }
+
+ switch ($type) {
+ case 'admin':
+ $return = $url . '&area=' . $arg1;
+ break;
+ case 'edittask':
+ $return = $url . '&task_id=' . $arg1 . '&edit=yep';
+ break;
+ case 'pm':
+ $return = $url . '&area=' . $arg1 . '&project=' . $arg2;
+ break;
+ case 'user':
+ $return = $baseurl . 'index.php?do=user&area=users&id=' . $arg1;
+ break;
+ case 'edituser':
+ $return = $baseurl . 'index.php?do=admin&area=users&user_id=' . $arg1;
+ break;
+ case 'logout':
+ $return = $baseurl . 'index.php?do=authenticate&logout=1';
+ break;
+
+ case 'details':
+ case 'depends':
+ $return = $url . '&task_id=' . $arg1;
+ break;
+
+ case 'project':
+ $return = $baseurl . 'index.php?project=' . $arg1;
+ break;
+
+ case 'reports':
+ case 'roadmap':
+ case 'toplevel':
+ case 'gantt':
+ case 'index':
+ case 'tasklist':
+ $return = $url . '&project=' . $arg1;
+ break;
+
+ case 'newtask':
+ case 'newmultitasks':
+ $return = $url . '&project=' . $arg1 . ($arg2 ? '&supertask=' . $arg2 : '');
+ break;
+
+ case 'editgroup':
+ $return = $baseurl . 'index.php?do=' . $arg2 . '&area=editgroup&id=' . $arg1;
+ break;
+
+ case 'lostpw':
+ case 'myprofile':
+ case 'register':
+ $return = $url;
+ break;
+
+ case 'mytasks':
+ $return = $baseurl.'index.php?do=index&project='.$arg1.'&dev='.$arg2;
+ break;
+
+ default:
+ $return = $baseurl . 'index.php';
+ break;
+ }
+ }
+
+ $url = new Url($return);
+ if( !is_null($arg3) && count($arg3) ) {
+ $url->addvars($arg3);
+ }
+ return $url->get();
+}
+
+/**
+ * Page numbering
+ *
+ * Thanks to Nathan Fritz for this. http://www.netflint.net/
+ */
+function pagenums($pagenum, $perpage, $totalcount)
+{
+ global $proj;
+ $pagenum = intval($pagenum);
+ $perpage = intval($perpage);
+ $totalcount = intval($totalcount);
+
+ // Just in case $perpage is something weird, like 0, fix it here:
+ if ($perpage < 1) {
+ $perpage = $totalcount > 0 ? $totalcount : 1;
+ }
+ $pages = ceil($totalcount / $perpage);
+ $output = sprintf(eL('page'), $pagenum, $pages);
+
+ if ( $totalcount / $perpage > 1 ) {
+ $params=$_GET;
+ # unset unneeded params for shorter urls
+ unset($params['do']);
+ unset($params['project']);
+ unset($params['switch']);
+ $output .= '<span class="pagenums DoNotPrint">';
+
+ $start = max(1, $pagenum - 4 + min(2, $pages - $pagenum));
+ $finish = min($start + 4, $pages);
+
+ if ($start > 1) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => 1))));
+ $output .= sprintf('<a href="%s">&lt;&lt;%s </a>', $url, eL('first'));
+ }
+ if ($pagenum > 1) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagenum - 1))));
+ $output .= sprintf('<a id="previous" accesskey="p" href="%s">&lt; %s</a> - ', $url, eL('previous'));
+ }
+
+ for ($pagelink = $start; $pagelink <= $finish; $pagelink++) {
+ if ($pagelink != $start) {
+ $output .= ' - ';
+ }
+
+ if ($pagelink == $pagenum) {
+ $output .= sprintf('<strong>%d</strong>', $pagelink);
+ } else {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagelink))));
+ $output .= sprintf('<a href="%s">%d</a>', $url, $pagelink);
+ }
+ }
+
+ if ($pagenum < $pages) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagenum + 1))));
+ $output .= sprintf(' - <a id="next" accesskey="n" href="%s">%s &gt;</a>', $url, eL('next'));
+ }
+ if ($finish < $pages) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pages))));
+ $output .= sprintf('<a href="%s"> %s &gt;&gt;</a>', $url, eL('last'));
+ }
+ $output .= '</span>';
+ }
+
+ return $output;
+}
+
+class Url {
+ public $url = '';
+ public $parsed;
+
+ public function __construct($url = '') {
+ $this->url = $url;
+ $this->parsed = parse_url($this->url);
+ }
+
+ public function seturl($url) {
+ $this->url = $url;
+ $this->parsed = parse_url($this->url);
+ }
+
+ public function getinfo($type = null) {
+ if (is_null($type)) {
+ return $this->parsed;
+ } elseif (isset($this->parsed[$type])) {
+ return $this->parsed[$type];
+ } else {
+ return '';
+ }
+ }
+
+ public function setinfo($type, $value) {
+ $this->parsed[$type] = $value;
+ }
+
+ public function addfrom($method = 'get', $vars = array()) {
+ $append = '';
+ foreach($vars as $key) {
+ $append .= http_build_query( (($method == 'get') ? Get::val($key) : Post::val($key)) ) . '&';
+ }
+ $append = substr($append, 0, -1);
+
+ $separator = ini_get('arg_separator.output');
+ if (strlen($separator) != 0) {
+ $append = str_replace($separator, '&', $append);
+ }
+
+ if ($this->getinfo('query')) {
+ $this->parsed['query'] .= '&' . $append;
+ } else {
+ $this->parsed['query'] = $append;
+ }
+ }
+
+ public function addvars($vars = array()) {
+ $append = http_build_query($vars);
+
+ $separator = ini_get('arg_separator.output');
+ if (strlen($separator) != 0) {
+ $append = str_replace($separator, '&', $append);
+ }
+
+ if ($this->getinfo('query')) {
+ $this->parsed['query'] .= '&' . $append;
+ } else {
+ $this->parsed['query'] = $append;
+ }
+ }
+
+ public function get($fullpath = true) {
+ $return = '';
+ if ($fullpath) {
+ $return .= $this->getinfo('scheme') . '://' . $this->getinfo('host');
+
+ if ($this->getinfo('port')) {
+ $return .= ':' . $this->getinfo('port');
+ }
+ }
+
+ $return .= $this->getinfo('path');
+
+ if ($this->getinfo('query')) {
+ $return .= '?' . $this->getinfo('query');
+ }
+
+ if ($this->getinfo('fragment')) {
+ $return .= '#' . $this->getinfo('fragment');
+ }
+
+ return $return;
+ }
+}
diff --git a/includes/class.user.php b/includes/class.user.php
new file mode 100644
index 0000000..f79ad04
--- /dev/null
+++ b/includes/class.user.php
@@ -0,0 +1,555 @@
+<?php
+
+class User
+{
+ public $id = -1;
+ public $perms = array();
+ public $infos = array();
+ public $searches = array();
+ public $search_keys = array('project', 'string', 'type', 'sev', 'pri', 'due', 'dev', 'cat', 'status', 'percent', 'changedfrom', 'closedfrom',
+ 'opened', 'closed', 'search_in_comments', 'search_in_details', 'search_for_all', 'reported', 'only_primary', 'only_watched', 'closedto',
+ 'changedto', 'duedatefrom', 'duedateto', 'openedfrom', 'openedto', 'has_attachment');
+
+ public function __construct($uid = 0)
+ {
+ global $db;
+
+ if ($uid > 0) {
+ $sql = $db->query('SELECT *, g.group_id AS global_group, uig.record_id AS global_record_id
+ FROM {users} u, {users_in_groups} uig, {groups} g
+ WHERE u.user_id = ? AND uig.user_id = ? AND g.project_id = 0
+ AND uig.group_id = g.group_id',
+ array($uid, $uid));
+ }
+
+ if ($uid > 0 && $db->countRows($sql) == 1) {
+ $this->infos = $db->fetchRow($sql);
+ $this->id = intval($uid);
+ } else {
+ $this->infos['real_name'] = L('anonuser');
+ $this->infos['user_name'] = '';
+ }
+
+ $this->get_perms();
+ }
+
+ /**
+ * save_search
+ *
+ * @param string $do
+ * @access public
+ * @return void
+ * @notes FIXME: must return something, should not merge _GET and _REQUEST with other stuff.
+ */
+ public function save_search($do = 'index')
+ {
+ global $db;
+
+ if($this->isAnon()) {
+ return;
+ }
+ // Only logged in users get to use the 'last search' functionality
+
+ if ($do == 'index') {
+ if (Post::val('search_name')) {
+ $arr = array();
+ foreach ($this->search_keys as $key) {
+ $arr[$key] = Post::val($key, ($key == 'status') ? 'open' : null);
+ }
+ foreach (array('order', 'sort', 'order2', 'sort2') as $key) {
+ if (Post::val($key)) {
+ $arr[$key] = Post::val($key);
+ }
+ }
+
+ $fields = array(
+ 'search_string'=> serialize($arr),
+ 'time'=> time(),
+ 'user_id'=> $this->id ,
+ 'name'=> Post::val('search_name')
+ );
+ $keys = array('name','user_id');
+ $db->replace('{searches}', $fields, $keys);
+ }
+ }
+
+ $sql = $db->query('SELECT * FROM {searches} WHERE user_id = ? ORDER BY name ASC', array($this->id));
+ $this->searches = $db->fetchAllArray($sql);
+ }
+
+ public function perms($name, $project = null) {
+ if (is_null($project)) {
+ global $proj;
+ $project = $proj->id;
+ }
+
+ if (isset($this->perms[$project][$name])) {
+ return $this->perms[$project][$name];
+ } else {
+ return 0;
+ }
+ }
+
+ public function get_perms()
+ {
+ global $db, $fs;
+
+ $fields = array('is_admin', 'manage_project', 'view_tasks', 'edit_own_comments',
+ 'open_new_tasks', 'modify_own_tasks', 'modify_all_tasks',
+ 'view_comments', 'add_comments', 'edit_comments', 'edit_assignments',
+ 'delete_comments', 'create_attachments',
+ 'delete_attachments', 'view_history', 'close_own_tasks',
+ 'close_other_tasks', 'assign_to_self', 'assign_others_to_self',
+ 'add_to_assignees', 'view_reports', 'add_votes', 'group_open','view_estimated_effort',
+ 'track_effort', 'view_current_effort_done', 'add_multiple_tasks', 'view_roadmap',
+ 'view_own_tasks', 'view_groups_tasks');
+
+ $this->perms = array(0 => array());
+ // Get project settings which are important for permissions
+ # php7.2 compatible variant without create_function(), instead use a SQL UNION to fill a fake global-project with project_id=0
+ $sql = $db->query('
+ SELECT project_id, others_view, project_is_active, anon_open, comment_closed
+ FROM {projects}
+ UNION
+ SELECT 0,1,1,1,1');
+ while ($row = $db->fetchRow($sql)) {
+ $this->perms[$row['project_id']] = $row;
+ }
+
+ if (!$this->isAnon()) {
+ // Get the global group permissions for the current user
+ $sql = $db->query("SELECT ".join(', ', $fields).", g.project_id, uig.record_id,
+ g.group_open, g.group_id AS project_group
+ FROM {groups} g
+ LEFT JOIN {users_in_groups} uig ON g.group_id = uig.group_id
+ LEFT JOIN {projects} p ON g.project_id = p.project_id
+ WHERE uig.user_id = ?
+ ORDER BY g.project_id, g.group_id ASC",
+ array($this->id));
+
+ while ($row = $db->fetchRow($sql)) {
+ if (!isset($this->perms[$row['project_id']])) {
+ // should not happen, so clean up the DB
+ $db->query('DELETE FROM {users_in_groups} WHERE record_id = ?', array($row['record_id']));
+ continue;
+ }
+
+ $this->perms[$row['project_id']] = array_merge($this->perms[$row['project_id']], $row);
+ }
+
+ // Set missing permissions and attachments
+ foreach ($this->perms as $proj_id => $value) {
+ foreach ($fields as $key) {
+ if ($key == 'project_group') {
+ continue;
+ }
+
+ $this->perms[$proj_id][$key] = max($this->perms[0]['is_admin'], @$this->perms[$proj_id][$key], $this->perms[0][$key]);
+ if ($proj_id && $key != 'is_admin') {
+ $this->perms[$proj_id][$key] = max(@$this->perms[$proj_id]['manage_project'], $this->perms[$proj_id][$key]);
+ }
+ }
+
+ // nobody can upload files if uploads are disabled at the system level..
+ if (!$fs->max_file_size || !is_writable(BASEDIR .'/attachments')) {
+ $this->perms[$proj_id]['create_attachments'] = 0;
+ }
+ }
+ }
+ }
+
+ public function check_account_ok()
+ {
+ global $conf, $baseurl;
+ // Anon users are always OK
+ if ($this->isAnon()) {
+ return;
+ }
+ $saltedpass = crypt($this->infos['user_pass'], $conf['general']['cookiesalt']);
+
+ if (Cookie::val('flyspray_passhash') !== $saltedpass || !$this->infos['account_enabled']
+ || !$this->perms('group_open', 0))
+ {
+ $this->logout();
+ Flyspray::redirect($baseurl);
+ }
+ }
+
+ public function isAnon()
+ {
+ return $this->id < 0;
+ }
+
+ /* }}} */
+ /* permission related {{{ */
+
+ public function can_edit_comment($comment)
+ {
+ return $this->perms('edit_comments')
+ || ($comment['user_id'] == $this->id && $this->perms('edit_own_comments'));
+ }
+
+ public function can_view_project($proj)
+ {
+ if (is_array($proj) && isset($proj['project_id'])) {
+ $proj = $proj['project_id'];
+ }
+
+ return ($this->perms('view_tasks', $proj) || $this->perms('view_groups_tasks', $proj) || $this->perms('view_own_tasks', $proj))
+ || ($this->perms('project_is_active', $proj)
+ && ($this->perms('others_view', $proj) || $this->perms('project_group', $proj)));
+ }
+
+ /* can_select_project() is similiar to can_view_project(), but
+ * allows anonymous users/guests to select this project if the project allows anon task creation,
+ * but all other stuff is restricted.
+ */
+ public function can_select_project($proj)
+ {
+ if (is_array($proj) && isset($proj['project_id'])) {
+ $proj = $proj['project_id'];
+ }
+
+ return (
+ $this->perms('view_tasks', $proj)
+ || $this->perms('view_groups_tasks', $proj)
+ || $this->perms('view_own_tasks', $proj)
+ )
+ ||
+ (
+ $this->perms('project_is_active', $proj)
+ && (
+ $this->perms('others_view', $proj)
+ || $this->perms('project_group', $proj)
+ || $this->perms('anon_open', $proj)
+ )
+ );
+ }
+
+ public function can_view_task($task)
+ {
+ if ($task['task_token'] && Get::val('task_token') == $task['task_token']) {
+ return true;
+ }
+
+ // Split into several separate tests so I can keep track on whats happening.
+
+ // Project managers and admins allowed always.
+ if ($this->perms('manage_project', $task['project_id'])
+ || $this->perms('is_admin', $task['project_id'])) {
+ return true;
+ }
+
+ // Allow if "allow anyone to view this project" is checked
+ // and task is not private.
+ if ($this->perms('others_view', $task['project_id']) && !$task['mark_private']) {
+ return true;
+ }
+
+ if ($this->isAnon()) {
+ // Following checks need identified user.
+ return false;
+ }
+
+ // Non-private task
+ if (!$task['mark_private']) {
+ // Can view tasks, always allow
+ if ($this->perms('view_tasks', $task['project_id'])) {
+ return true;
+ }
+ // User can view only own tasks
+ if ($this->perms('view_own_tasks', $task['project_id'])
+ && !$this->perms('view_groups_tasks', $task['project_id'])) {
+ if ($task['opened_by'] == $this->id) {
+ return true;
+ }
+ if (in_array($this->id, Flyspray::getAssignees($task['task_id']))) {
+ return true;
+ }
+ // No use to continue further.
+ return false;
+ }
+ // Ok, user *must* have view_groups_tasks permission,
+ // but do the check anyway just in case... there might
+ // appear more in the future.
+ if ($this->perms('view_groups_tasks', $task['project_id'])) {
+ // Two first checks the same as with view_own_tasks permission.
+ if ($task['opened_by'] == $this->id) {
+ return true;
+ }
+ // Fetch only once, could be needed three times.
+ $assignees = Flyspray::getAssignees($task['task_id']);
+ if (in_array($this->id, $assignees)) {
+ return true;
+ }
+
+ // Must fetch other persons in the group now. Find out
+ // how to detect the right group for project and the
+ // other persons in it. Funny, found it in $perms.
+ $group = $this->perms('project_group', $task['project_id']);
+ $others = Project::listUsersIn($group);
+
+ foreach ($others as $other) {
+ if ($other['user_id'] == $task['opened_by']) {
+ return true;
+ }
+ if (in_array($other['user_id'], $assignees)) {
+ return true;
+ }
+ }
+
+ // Check the global group next. Note that for users in that group to be included,
+ // the has to be specified at global group level. So even if our permission system
+ // works by OR'ing the permissions together, who is actually considered to be in
+ // in the same group now depends on whether this permission has been given on global
+ // or project level.
+ if ($this->perms('view_groups_tasks', 0)) {
+ $group = $this->perms('project_group', 0);
+ $others = Project::listUsersIn($group);
+
+ foreach ($others as $other) {
+ if ($other['user_id'] == $task['opened_by']) {
+ return true;
+ }
+ if (in_array($other['user_id'], $assignees)) {
+ return true;
+ }
+ }
+ }
+
+ // No use to continue further.
+ return false;
+ }
+ }
+
+ // Private task, user must be either assigned to the task
+ // or have opened it.
+ if ($task['mark_private']) {
+ if ($task['opened_by'] == $this->id) {
+ return true;
+ }
+ if (in_array($this->id, Flyspray::getAssignees($task['task_id']))) {
+ return true;
+ }
+ // No use to continue further.
+ return false;
+ }
+
+ // Could not find any permission for viewing the task.
+ return false;
+ }
+
+ public function can_edit_task($task)
+ {
+ return !$task['is_closed'] && (
+ $this->perms('modify_all_tasks', $task['project_id']) ||
+ ($this->id == $task['opened_by'] && $this->perms('modify_own_tasks', $task['project_id'])) ||
+ in_array($this->id, Flyspray::getAssignees($task['task_id']))
+ );
+ }
+
+ public function can_take_ownership($task)
+ {
+ $assignees = Flyspray::getAssignees($task['task_id']);
+
+ return ($this->perms('assign_to_self', $task['project_id']) && empty($assignees))
+ || ($this->perms('assign_others_to_self', $task['project_id']) && !in_array($this->id, $assignees));
+ }
+
+ public function can_add_to_assignees($task)
+ {
+ return ($this->perms('add_to_assignees', $task['project_id']) && !in_array($this->id, Flyspray::getAssignees($task['task_id'])));
+ }
+
+ public function can_close_task($task)
+ {
+ return ($this->perms('close_own_tasks', $task['project_id']) && in_array($this->id, $task['assigned_to']))
+ || $this->perms('close_other_tasks', $task['project_id']);
+ }
+
+ public function can_set_task_parent($task)
+ {
+ return !$task['is_closed'] && (
+ $this->perms('modify_all_tasks', $task['project_id']) ||
+ in_array($this->id, Flyspray::getAssignees($task['task_id']))
+ );
+ }
+
+ public function can_associate_task($task)
+ {
+ return !$task['is_closed'] && (
+ $this->perms('modify_all_tasks', $task['project_id']) ||
+ in_array($this->id, Flyspray::getAssignees($task['task_id']))
+ );
+ }
+
+ public function can_add_task_dependency($task)
+ {
+ return !$task['is_closed'] && (
+ $this->perms('modify_all_tasks', $task['project_id']) ||
+ in_array($this->id, Flyspray::getAssignees($task['task_id']))
+ );
+ }
+
+//admin approve user registration
+ public function need_admin_approval()
+ {
+ global $fs;
+ return $this->isAnon() && $fs->prefs['need_approval'] && $fs->prefs['anon_reg'];
+ }
+
+ public function get_group_id()
+ {
+ }
+
+ /**
+ * tests if current configuration allows a guest user to register - without email verification code
+ */
+ public function can_self_register()
+ {
+ global $fs;
+ return $this->isAnon() && $fs->prefs['anon_reg'] && !$fs->prefs['only_oauth_reg'] && !$fs->prefs['spam_proof'] ;
+ }
+
+ /**
+ * tests if current configuration allows a guest user to register - with email verification code
+ */
+ public function can_register()
+ {
+ global $fs;
+ return $this->isAnon() && $fs->prefs['anon_reg'] && !$fs->prefs['only_oauth_reg'] && $fs->prefs['spam_proof'] && !$fs->prefs['need_approval'] ;
+ }
+
+ public function can_open_task($proj)
+ {
+ return $proj->id && ($this->perms('manage_project') ||
+ $this->perms('project_is_active', $proj->id) && ($this->perms('open_new_tasks') || $this->perms('anon_open', $proj->id)));
+ }
+
+ public function can_change_private($task)
+ {
+ return !$task['is_closed'] && ($this->perms('manage_project', $task['project_id']) || in_array($this->id, Flyspray::getAssignees($task['task_id'])));
+ }
+
+ public function can_vote($task)
+ {
+ global $db, $fs;
+
+ if (!$this->perms('add_votes', $task['project_id'])) {
+ return -1;
+ }
+
+ // Check that the user hasn't already voted this task
+ $check = $db->query('SELECT vote_id
+ FROM {votes}
+ WHERE user_id = ? AND task_id = ?',
+ array($this->id, $task['task_id']));
+ if ($db->countRows($check)) {
+ return -2;
+ }
+
+ /* FS 1.0alpha daily vote limit
+ // Check that the user hasn't voted more than allowed today
+ $check = $db->query('SELECT vote_id
+ FROM {votes}
+ WHERE user_id = ? AND date_time > ?',
+ array($this->id, time() - 86400));
+ if ($db->countRows($check) >= $fs->prefs['max_vote_per_day']) {
+ return -3;
+ }
+ */
+
+ /* FS 1.0beta2 max votes per user per project limit */
+ $check = $db->query('
+ SELECT COUNT(v.vote_id)
+ FROM {votes} v
+ JOIN {tasks} t ON t.task_id=v.task_id
+ WHERE user_id = ?
+ AND t.project_id = ?
+ AND t.is_closed <>1',
+ array($this->id, $task['project_id'])
+ );
+ if ($db->countRows($check) >= $fs->prefs['votes_per_project']) {
+ return -4;
+ }
+
+
+ return 1;
+ }
+
+ public function logout()
+ {
+ // Set cookie expiry time to the past, thus removing them
+ Flyspray::setcookie('flyspray_userid', '', time()-60);
+ Flyspray::setcookie('flyspray_passhash', '', time()-60);
+ Flyspray::setcookie('flyspray_project', '', time()-60);
+ if (Cookie::has(session_name())) {
+ Flyspray::setcookie(session_name(), '', time()-60);
+ }
+
+ // Unset all of the session variables.
+ $_SESSION = array();
+ session_destroy();
+
+ return !$this->isAnon();
+ }
+
+ /**
+ * Returns the activity by between dates for a project and user.
+ * @param date $startdate
+ * @param date $enddate
+ * @param integer $project_id
+ * @param integer $userid
+ * @return array used to get the count
+ * @access public
+ */
+ static function getActivityUserCount($startdate, $enddate, $project_id, $userid) {
+ global $db;
+ $result = $db->query('SELECT count(event_date) as val
+ FROM {history} h left join {tasks} t on t.task_id = h.task_id
+ WHERE t.project_id = ? AND h.user_id = ? AND event_date BETWEEN ? AND ?',
+ array($project_id, $userid, $startdate, $enddate));
+ $result = $db->fetchCol($result);
+ return $result[0];
+ }
+
+ /**
+ * Returns the day activity by the date for a project and user.
+ * @param date $date
+ * @param integer $project_id
+ * @param integer $userid
+ * @return array used to get the count
+ * @access public
+ */
+ static function getDayActivityByUser($date_start, $date_end, $project_id, $userid) {
+ global $db;
+ //NOTE: from_unixtime() on mysql, to_timestamp() on PostreSQL
+ $func = ('mysql' == $db->dblink->dataProvider) ? 'from_unixtime' : 'to_timestamp';
+
+ $result = $db->query("SELECT count(date({$func}(event_date))) as val, MIN(event_date) as event_date
+ FROM {history} h left join {tasks} t on t.task_id = h.task_id
+ WHERE t.project_id = ? AND h.user_id = ? AND event_date BETWEEN ? AND ?
+ GROUP BY date({$func}(event_date)) ORDER BY event_date DESC",
+ array($project_id, $userid, $date_start, $date_end));
+
+ $date1 = new \DateTime("@$date_start");
+ $date2 = new \DateTime("@$date_end");
+ $days = $date1->diff($date2);
+ $days = $days->format('%a');
+ $results = array();
+
+ for ($i = $days; $i > 0; $i--) {
+ $event_date = (string) strtotime("-{$i} day", $date_end);
+ $results[date('Y-m-d', $event_date)] = 0;
+ }
+
+ while ($row = $result->fetchRow()) {
+ $event_date = date('Y-m-d', $row['event_date']);
+ $results[$event_date] = (integer) $row['val'];
+ }
+
+ return array_values($results);
+ }
+
+ /* }}} */
+}
diff --git a/includes/constants.inc.php b/includes/constants.inc.php
new file mode 100644
index 0000000..b3c3171
--- /dev/null
+++ b/includes/constants.inc.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Basic constants/variables required for flyspray operation
+ *
+ * @notes be a real paranoid here.
+ * @version $Id$
+ */
+
+define('BASEDIR', dirname(dirname(__FILE__)));
+
+// Change this line if you move flyspray.conf.php elsewhere
+$conf = @parse_ini_file(Flyspray::get_config_path(), true);
+
+// $baseurl
+// htmlspecialchars because PHP_SELF is user submitted data, and can be used as an XSS vector.
+if (isset($conf['general']['force_baseurl']) && $conf['general']['force_baseurl'] != '') {
+ $baseurl = $conf['general']['force_baseurl'];
+} else {
+ if (!isset($webdir)) {
+ $webdir = dirname(htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'utf-8'));
+ if (!$webdir) {
+ $webdir = dirname($_SERVER['SCRIPT_NAME']);
+ }
+ if(substr($webdir, -13) == '/js/callbacks'){
+ $webdir = dirname(dirname($webdir));
+ } elseif (substr($webdir, -9) == 'index.php') {
+ $webdir = dirname($webdir);
+ }
+ }
+
+ $baseurl = rtrim(Flyspray::absoluteURI($webdir),'/\\') . '/' ;
+}
+
+if(isset($conf['general']['syntax_plugin']) && preg_match('/^[a-z0-9_]+$/iD', $conf['general']['syntax_plugin'])) {
+
+$path_to_plugin = sprintf('%s/plugins/%s/%s_constants.inc.php', BASEDIR, $conf['general']['syntax_plugin'], $conf['general']['syntax_plugin']);
+
+ if (is_readable($path_to_plugin)) {
+ include($path_to_plugin);
+ }
+}
+
+define('NOTIFY_TASK_OPENED', 1);
+define('NOTIFY_TASK_CHANGED', 2);
+define('NOTIFY_TASK_CLOSED', 3);
+define('NOTIFY_TASK_REOPENED', 4);
+define('NOTIFY_DEP_ADDED', 5);
+define('NOTIFY_DEP_REMOVED', 6);
+define('NOTIFY_COMMENT_ADDED', 7);
+define('NOTIFY_ATT_ADDED', 8);
+define('NOTIFY_REL_ADDED', 9);
+define('NOTIFY_OWNERSHIP', 10);
+define('NOTIFY_CONFIRMATION', 11);
+define('NOTIFY_PM_REQUEST', 12);
+define('NOTIFY_PM_DENY_REQUEST', 13);
+define('NOTIFY_NEW_ASSIGNEE', 14);
+define('NOTIFY_REV_DEP', 15);
+define('NOTIFY_REV_DEP_REMOVED', 16);
+define('NOTIFY_ADDED_ASSIGNEES', 17);
+define('NOTIFY_ANON_TASK', 18);
+define('NOTIFY_PW_CHANGE', 19);
+define('NOTIFY_NEW_USER', 20);
+define('NOTIFY_OWN_REGISTRATION',21);
+
+define('NOTIFY_EMAIL', 1);
+define('NOTIFY_JABBER', 2);
+define('NOTIFY_BOTH', 3);
+
+define('STATUS_UNCONFIRMED', 1);
+define('STATUS_NEW', 2);
+define('STATUS_ASSIGNED', 3);
+
+define('GET_CONTENTS', true);
+
+# resolution_id with special meaning and protection, always 6 (Flyspray history)
+define('RESOLUTION_DUPLICATE', 6);
+
+// Others
+define('MIN_PW_LENGTH', 5);
+define('LOGIN_ATTEMPTS', 5);
+
+# 201508: webdot currently used not anymore in flyspray. Graphs can be done in future with svg or canvas elements.
+define('FLYSPRAY_WEBDOT', 'http://webdot.flyspray.org/');
+define('FS_DOMAIN_HASH', md5($_SERVER['SERVER_NAME'] . BASEDIR));
+define('FS_CACHE_DIR', Flyspray::get_tmp_dir() . DIRECTORY_SEPARATOR . FS_DOMAIN_HASH);
+
+is_dir(FS_CACHE_DIR) || @mkdir(FS_CACHE_DIR, 0700);
+
+// developers or advanced users only
+//define('DEBUG_SQL',true);
+
+# 201508: Currently without usage! Was once used in file fsjabber.php (not in src anymore), but not within class.jabber2.php.
+//define('JABBER_DEBUG', true);
+//define('JABBER_DEBUG_FILE', BASEDIR . '/logs/jabberlog.txt');
+
+//define('FS_MAIL_LOGFILE', BASEDIR . '/logs/maillog.txt');
diff --git a/includes/events.inc.php b/includes/events.inc.php
new file mode 100644
index 0000000..ec9f2a7
--- /dev/null
+++ b/includes/events.inc.php
@@ -0,0 +1,308 @@
+<?php
+
+// XXX be aware: make sure you quote correctly using qstr()
+// the variables used in the $where parameter, since statement is
+// executed AS IS.
+
+function get_events($task_id, $where = '')
+{
+ global $db;
+ return $db->query("SELECT h.*,
+ tt1.tasktype_name AS task_type1,
+ tt2.tasktype_name AS task_type2,
+ los1.os_name AS operating_system1,
+ los2.os_name AS operating_system2,
+ lc1.category_name AS product_category1,
+ lc2.category_name AS product_category2,
+ p1.project_title AS project_id1,
+ p2.project_title AS project_id2,
+ lv1.version_name AS product_version1,
+ lv2.version_name AS product_version2,
+ ls1.status_name AS item_status1,
+ ls2.status_name AS item_status2,
+ lr.resolution_name,
+ c.date_added AS c_date_added,
+ c.user_id AS c_user_id,
+ att.orig_name
+
+ FROM {history} h
+
+ LEFT JOIN {list_tasktype} tt1 ON tt1.tasktype_id::text = h.old_value AND h.field_changed='task_type'
+ LEFT JOIN {list_tasktype} tt2 ON tt2.tasktype_id::text = h.new_value AND h.field_changed='task_type'
+
+ LEFT JOIN {list_os} los1 ON los1.os_id::text = h.old_value AND h.field_changed='operating_system'
+ LEFT JOIN {list_os} los2 ON los2.os_id::text = h.new_value AND h.field_changed='operating_system'
+
+ LEFT JOIN {list_category} lc1 ON lc1.category_id::text = h.old_value AND h.field_changed='product_category'
+ LEFT JOIN {list_category} lc2 ON lc2.category_id::text = h.new_value AND h.field_changed='product_category'
+
+ LEFT JOIN {list_status} ls1 ON ls1.status_id::text = h.old_value AND h.field_changed='item_status'
+ LEFT JOIN {list_status} ls2 ON ls2.status_id::text = h.new_value AND h.field_changed='item_status'
+
+ LEFT JOIN {list_resolution} lr ON lr.resolution_id::text = h.new_value AND h.event_type = 2
+
+ LEFT JOIN {projects} p1 ON p1.project_id::text = h.old_value AND h.field_changed='project_id'
+ LEFT JOIN {projects} p2 ON p2.project_id::text = h.new_value AND h.field_changed='project_id'
+
+ LEFT JOIN {comments} c ON c.comment_id::text = h.field_changed AND h.event_type = 5
+
+ LEFT JOIN {attachments} att ON att.attachment_id::text = h.new_value AND h.event_type = 7
+
+ LEFT JOIN {list_version} lv1 ON lv1.version_id::text = h.old_value
+ AND (h.field_changed='product_version' OR h.field_changed='closedby_version')
+ LEFT JOIN {list_version} lv2 ON lv2.version_id::text = h.new_value
+ AND (h.field_changed='product_version' OR h.field_changed='closedby_version')
+
+ WHERE h.task_id = ? $where
+ ORDER BY event_date ASC, history_id ASC, event_type ASC", array($task_id));
+}
+
+/**
+ * XXX: A mess,remove my in 1.0. No time for that, sorry.
+ */
+function event_description($history) {
+ $return = '';
+ global $fs, $baseurl, $details, $proj;
+
+ $translate = array('item_summary' => 'summary', 'project_id' => 'attachedtoproject',
+ 'task_type' => 'tasktype', 'product_category' => 'category', 'item_status' => 'status',
+ 'task_priority' => 'priority', 'operating_system' => 'operatingsystem', 'task_severity' => 'severity',
+ 'product_version' => 'reportedversion', 'mark_private' => 'visibility',
+ 'estimated_effort' => 'estimatedeffort');
+ // if somehing gets double escaped, add it here.
+ $noescape = array('new_value', 'old_value');
+
+ foreach($history as $key=> $value) {
+ if(!in_array($key, $noescape)) {
+ $history[$key] = Filters::noXSS($value);
+ }
+ }
+
+ $new_value = $history['new_value'];
+ $old_value = $history['old_value'];
+
+ switch($history['event_type']) {
+ case '3': //Field changed
+ if (!$new_value && !$old_value) {
+ $return .= eL('taskedited');
+ break;
+ }
+
+ $field = $history['field_changed'];
+ switch ($field) {
+ case 'item_summary':
+ case 'project_id':
+ case 'task_type':
+ case 'product_category':
+ case 'item_status':
+ case 'task_priority':
+ case 'operating_system':
+ case 'task_severity':
+ case 'product_version':
+ if($field == 'task_priority') {
+ $old_value = $fs->priorities[$old_value];
+ $new_value = $fs->priorities[$new_value];
+ } elseif($field == 'task_severity') {
+ $old_value = $fs->severities[$old_value];
+ $new_value = $fs->severities[$new_value];
+ } elseif($field == 'item_summary') {
+ $old_value = Filters::noXSS($old_value);
+ $new_value = Filters::noXSS($new_value);
+ } else {
+ $old_value = $history[$field . '1'];
+ $new_value = $history[$field . '2'];
+ }
+ $field = eL($translate[$field]);
+ break;
+ case 'closedby_version':
+ $field = eL('dueinversion');
+ $old_value = ($old_value == '0') ? eL('undecided') : $history['product_version1'];
+ $new_value = ($new_value == '0') ? eL('undecided') : $history['product_version2'];
+ break;
+ case 'due_date':
+ $field = eL('duedate');
+ $old_value = formatDate($old_value, false, eL('undecided'));
+ $new_value = formatDate($new_value, false, eL('undecided'));
+ break;
+ case 'percent_complete':
+ $field = eL('percentcomplete');
+ $old_value .= '%';
+ $new_value .= '%';
+ break;
+ case 'mark_private':
+ $field = eL($translate[$field]);
+ if ($old_value == 1) {
+ $old_value = eL('private');
+ } else {
+ $old_value = eL('public');
+ }
+ if ($new_value == 1) {
+ $new_value = eL('private');
+ } else {
+ $new_value = eL('public');
+ }
+ break;
+ case 'detailed_desc':
+ $field = "<a href=\"javascript:getHistory('{$history['task_id']}', '$baseurl', 'history', '{$history['history_id']}');showTabById('history', true);\">" . eL('details') . '</a>';
+ if (!empty($details)) {
+ $details_previous = TextFormatter::render($old_value);
+ $details_new = TextFormatter::render($new_value);
+ }
+ $old_value = '';
+ $new_value = '';
+ break;
+ case 'estimated_effort':
+ $field = eL($translate[$field]);
+ $old_value = effort::secondsToString($old_value, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ $new_value = effort::secondsToString($new_value, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);;
+ break;
+ }
+ $return .= eL('fieldchanged').": {$field}";
+ if ($old_value || $new_value) {
+ $return .= " ({$old_value} &rarr; {$new_value})";
+ }
+ break;
+ case '1': //Task opened
+ $return .= eL('taskopened');
+ break;
+ case '2': //Task closed
+ $return .= eL('taskclosed');
+ $return .= " ({$history['resolution_name']}";
+ if (!empty($old_value)) {
+ $return .= ': ' . TextFormatter::render($old_value, true);
+ }
+ $return .= ')';
+ break;
+ case '4': //Comment added
+ $return .= '<a href="#comments">' . eL('commentadded') . '</a>';
+ break;
+ case '5': //Comment edited
+ $return .= "<a href=\"javascript:getHistory('{$history['task_id']}', '$baseurl', 'history', '{$history['history_id']}');\">".eL('commentedited')."</a>";
+ if ($history['c_date_added']) {
+ $return .= " (".eL('commentby').' ' . tpl_userlink($history['c_user_id']) . " - " . formatDate($history['c_date_added'], true) . ")";
+ }
+ if ($details) {
+ $details_previous = TextFormatter::render($old_value);
+ $details_new = TextFormatter::render($new_value);
+ }
+ break;
+ case '6': //Comment deleted
+ $return .= "<a href=\"javascript:getHistory('{$history['task_id']}', '$baseurl', 'history', '{$history['history_id']}');\">".eL('commentdeleted')."</a>";
+ if ($new_value != '' && $history['field_changed'] != '') {
+ $return .= " (". eL('commentby'). ' ' . tpl_userlink($new_value) . " - " . formatDate($history['field_changed'], true) . ")";
+ }
+ if (!empty($details)) {
+ $details_previous = TextFormatter::render($old_value);
+ $details_new = '';
+ }
+ break;
+ case '7': //Attachment added
+ $return .= eL('attachmentadded');
+ if ($history['orig_name']) {
+ $return .= ": <a href=\"{$baseurl}?getfile=" . intval($new_value) . '">' . "{$history['orig_name']}</a>";
+ } else if ($history['old_value']) {
+ $return .= ': ' . $history['old_value'];
+ }
+ break;
+ case '8': //Attachment deleted
+ $return .= eL('attachmentdeleted') . ': ' . Filters::noXSS($new_value);
+ break;
+ case '9': //Notification added
+ $return .= eL('notificationadded') . ': ' . tpl_userlink($new_value);
+ break;
+ case '10': //Notification deleted
+ $return .= eL('notificationdeleted') . ': ' . tpl_userlink($new_value);
+ break;
+ case '11': //Related task added
+ $return .= eL('relatedadded') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '12': //Related task deleted
+ $return .= eL('relateddeleted') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '13': //Task reopened
+ $return .= eL('taskreopened');
+ break;
+ case '14': //Task assigned
+ if (empty($old_value)) {
+ $users = explode(' ', trim($new_value));
+ $users = array_map('tpl_userlink', $users);
+ $return .= eL('taskassigned').' ';
+ $return .= implode(', ', $users);
+ } elseif (empty($new_value)) {
+ $return .= eL('assignmentremoved');
+ } else {
+ $users = explode(' ', trim($new_value));
+ $users = array_map('tpl_userlink', $users);
+ $return .= eL('taskreassigned').' ';
+ $return .= implode(', ', $users);
+ }
+ break;
+ // Mentioned in docs, not used anywhere. Will implement if suitable
+ // translations already exist, otherwise leave to 1.1. (Found translations)
+ case '15': // This task was added to another task's related list
+ $return .= eL('addedasrelated') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '16': // This task was removed from another task's related list
+ $return .= eL('deletedasrelated') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '17': //Reminder added
+ $return .= eL('reminderadded') . ': ' . tpl_userlink($new_value);
+ break;
+ case '18': //Reminder deleted
+ $return .= eL('reminderdeleted') . ': ' . tpl_userlink($new_value);
+ break;
+ case '19': //User took ownership
+ $return .= eL('ownershiptaken') . ': ' . tpl_userlink($new_value);
+ break;
+ case '20': //User requested task closure
+ $return .= eL('closerequestmade') . ' - ' . $new_value;
+ break;
+ case '21': //User requested task
+ $return .= eL('reopenrequestmade') . ' - ' . $new_value;
+ break;
+ case '22': // Dependency added
+ $return .= eL('depadded') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '23': // Dependency added to other task
+ $return .= eL('depaddedother') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '24': // Dependency removed
+ $return .= eL('depremoved') . ': ' . tpl_tasklink($new_value);
+ break;
+ case '25': // Dependency removed from other task
+ $return .= eL('depremovedother') . ': ' . tpl_tasklink($new_value);
+ break;
+ // 26 and 27 replaced by 0 (mark_private)
+ case '28': // PM request denied
+ $return .= eL('pmreqdenied') . ' - ' . $new_value;
+ break;
+ case '29': // User added to assignees list
+ $return .= eL('addedtoassignees');
+ break;
+ case '30': // user created
+ $return .= eL('usercreated');
+ break;
+ case '31': // user deleted
+ $return .= eL('userdeleted');
+ break;
+ case '32': // Subtask added
+ $return .= eL('subtaskadded') . ' ' . tpl_tasklink($new_value);
+ break;
+ case '33': // Subtask removed
+ $return .= eL('subtaskremoved') . ' ' . tpl_tasklink($new_value);
+ break;
+ case '34': // supertask added
+ $return .= eL('supertaskadded') . ' ' . tpl_tasklink($new_value);
+ break;
+ case '35': // supertask removed
+ $return .= eL('supertaskremoved') . ' ' . tpl_tasklink($new_value);
+ break;
+ }
+
+ if (isset($details_previous)) $GLOBALS['details_previous'] = $details_previous;
+ if (isset($details_new)) $GLOBALS['details_new'] = $details_new;
+
+ return $return;
+}
+
+?>
diff --git a/includes/fix.inc.php b/includes/fix.inc.php
new file mode 100644
index 0000000..1ba5229
--- /dev/null
+++ b/includes/fix.inc.php
@@ -0,0 +1,205 @@
+<?php
+
+/*
+ * This file is meant to add every hack that is needed to fix default PHP
+ * behaviours, and to ensure that our PHP env will be able to run flyspray
+ * correctly.
+ *
+ */
+ini_set('display_errors', 1);
+
+// html errors will mess the layout
+ini_set('html_errors', 0);
+
+//error_reporting(E_ALL);
+if(version_compare(PHP_VERSION, '7.2.0') >= 0) {
+ # temporary for php7.2+ (2017-11-30)
+ # not all parts of Flyspray and 3rd party libs like ADODB 5.20.9 not yet 'since-php7.2-deprecated'-ready
+ error_reporting(E_ALL & ~E_STRICT & ~E_DEPRECATED);
+}else{
+ error_reporting(E_ALL & ~E_STRICT);
+}
+// our default charset
+
+ini_set('default_charset','utf-8');
+
+// This to stop PHP being retarded and using the '&' char for session id delimiters
+ini_set('arg_separator.output','&amp;');
+
+// no transparent session id improperly configured servers
+if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/setup/') === false) // Skip installer, as it starts the session before calling fix.inc.php causing a warning as this can't be used when a session is already active
+ ini_set('session.use_trans_sid', 0);
+
+//see http://php.net/manual/en/ref.session.php#ini.session.use-only-cookies
+ini_set('session.use_only_cookies',1);
+
+//no session auto start
+ini_set('session.auto_start',0);
+
+/*this stops most cookie attacks via XSS at the interpreter level
+* see http://msdn.microsoft.com/workshop/author/dhtml/httponly_cookies.asp
+* supported by IE 6 SP1, Safari, Konqueror, Opera, silently ignored by others
+* ( sadly, including firefox) available since PHP 5.2.0
+ */
+
+ini_set('session.cookie_httponly',1);
+
+// use stronger entropy in sessions whenever possible
+ini_set('session.entropy_file', '/dev/urandom');
+ini_set('session.entropy_length', 16);
+// use sha-1 for sessions
+ini_set('session.hash_function',1);
+
+ini_set('auto_detect_line_endings', 0);
+
+# for using stronger blowfish hashing functions also with php5.3 < yourphpversion < php5.5
+# minimal php5.3.8 recommended, see https://github.com/ircmaxell/password_compat
+if(!function_exists('password_hash')){
+ require_once dirname(__FILE__).'/password_compat.php';
+}
+
+# for php < php.5.6
+if(!function_exists('hash_equals')) {
+ function hash_equals($str1, $str2) {
+ if(strlen($str1) != strlen($str2)) {
+ return false;
+ } else {
+ $res = $str1 ^ $str2;
+ $ret = 0;
+ for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]);
+ return !$ret;
+ }
+ }
+}
+
+
+ini_set('include_path', join( PATH_SEPARATOR, array(
+ dirname(__FILE__) . '/external' ,
+ ini_get('include_path'))));
+
+
+if(count($_GET)) {
+ foreach ($_GET as $key => $value) {
+ if(is_array($value))
+ $_GET[$key] = filter_input(INPUT_GET, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY);
+ else
+ $_GET[$key] = filter_input(INPUT_GET, $key, FILTER_UNSAFE_RAW);
+ }
+}
+if(count($_POST)) {
+ foreach ($_POST as $key => $value) {
+ if(is_array($value))
+ $_POST[$key] = filter_input(INPUT_POST, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY);
+ else
+ $_POST[$key] = filter_input(INPUT_POST, $key, FILTER_UNSAFE_RAW);
+ }
+}
+if(count($_COOKIE)) {
+ foreach ($_COOKIE as $key => $value) {
+ if(is_array($value))
+ $_COOKIE[$key] = filter_input(INPUT_COOKIE, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY);
+ else
+ $_COOKIE[$key] = filter_input(INPUT_COOKIE, $key, FILTER_UNSAFE_RAW);
+ }
+}
+if(isset($_SESSION) && is_array($_SESSION) && count($_SESSION)) {
+ foreach ($_SESSION as $key => $value) {
+ if(is_array($value))
+ $_SESSION[$key] = filter_input(INPUT_SESSION, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY);
+ else
+ $_SESSION[$key] = filter_input(INPUT_SESSION, $key, FILTER_UNSAFE_RAW);
+ }
+}
+
+
+// This is for retarded Windows servers not having REQUEST_URI
+
+if (!isset($_SERVER['REQUEST_URI']))
+{
+ if (isset($_SERVER['SCRIPT_NAME'])) {
+ $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
+ }
+ else {
+ // this is tained now.
+ $_SERVER['REQUEST_URI'] = $_SERVER['PHP_SELF'];
+ }
+
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $_SERVER['REQUEST_URI'] .= '?'.$_SERVER['QUERY_STRING'];
+ }
+}
+
+if (!isset($_SERVER['QUERY_STRING']))
+{
+ $_SERVER['QUERY_STRING'] = '';
+}
+
+
+/**
+ * Replace glob() since this function is apparently
+ * disabled for no apparent reason ("security") on some systems
+ *
+ * @see glob()
+ * @require PHP 4.3.0 (fnmatch)
+ * @todo is this still required?
+ */
+function glob_compat($pattern, $flags = 0) {
+
+ $split = explode('/', $pattern);
+ $match = array_pop($split);
+ $path = implode('/', $split);
+ if (($dir = opendir($path)) !== false) {
+ $glob = array();
+ while (($file = readdir($dir)) !== false) {
+ if (fnmatch($match, $file)) {
+ if (is_dir("$path/$file") || !($flags & GLOB_ONLYDIR)) {
+ if ($flags & GLOB_MARK) $file .= '/';
+ $glob[] = $file;
+ }
+ }
+ }
+ closedir($dir);
+ if (!($flags & GLOB_NOSORT)) sort($glob);
+ return $glob;
+ }
+ return false;
+}
+
+// now for all those borked PHP installations...
+// TODO still required. Enabled by default since 4.2
+if (!function_exists('ctype_alnum')) {
+ function ctype_alnum($text) {
+ return is_string($text) && preg_match('/^[a-z0-9]+$/iD', $text);
+ }
+}
+if (!function_exists('ctype_digit')) {
+ function ctype_digit($text) {
+ return is_string($text) && preg_match('/^[0-9]+$/iD', $text);
+ }
+}
+
+if(!isset($_SERVER['SERVER_NAME']) && php_sapi_name() === 'cli') {
+ $_SERVER['SERVER_NAME'] = php_uname('n');
+}
+
+//for reasons outside flsypray, the PHP core may throw Exceptions in PHP5
+// for a good example see this article
+// http://ilia.ws/archives/107-Another-unserialize-abuse.html
+
+function flyspray_exception_handler($exception) {
+ // Sometimes it helps removing temporary comments from the following three lines.
+ // echo "<pre>";
+ // var_dump(debug_backtrace());
+ // echo "</pre>";
+ die("Completely unexpected exception: " .
+ htmlspecialchars($exception->getMessage(),ENT_QUOTES, 'utf-8') . "<br/>" .
+ "This should <strong> never </strong> happend, please inform Flyspray Developers");
+
+}
+
+set_exception_handler('flyspray_exception_handler');
+
+
+// We don't need session IDs in URLs
+output_reset_rewrite_vars();
+
diff --git a/includes/i18n.inc.php b/includes/i18n.inc.php
new file mode 100644
index 0000000..47d6852
--- /dev/null
+++ b/includes/i18n.inc.php
@@ -0,0 +1,166 @@
+<?php
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+require_once BASEDIR . '/lang/en.php';
+FlySprayI18N::init('en', $language);
+FlySprayI18N::setDefault($language);
+
+class FlySprayI18N {
+ private static $translations = array();
+
+ public static function init($lang, $translation) {
+ self::$translations[$lang] = $translation;
+ }
+
+ public static function setDefault($translation) {
+ self::$translations['default'] = $translation;
+ }
+
+ public static function L($key, $lang = null) {
+ if (!isset($lang) || empty($lang) || !is_string($lang)) {
+ $lang = 'default';
+ }
+ if ($lang != 'default' && $lang != 'en' && !array_key_exists($lang, self::$translations)) {
+ // echo "<pre>Only once here for $lang!</pre>";
+ $language = BASEDIR . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $lang . '.php';
+ if (is_readable($language)) {
+ if ((@require $language) !== FALSE) {
+ // echo "<pre>Loaded: $lang!</pre>";
+ self::$translations[$lang] = $translation;
+ }
+ else {
+ $lang = 'default';
+ }
+ }
+ else {
+ $lang = 'default';
+ }
+ }
+ if (empty($key)) {
+ return '';
+ }
+ if (isset(self::$translations[$lang][$key])) {
+ // echo "<pre>Case 1: $lang!</pre>";
+ return self::$translations[$lang][$key];
+ }
+ if (isset(self::$translations['default'][$key])) {
+ // echo "<pre>Case 2: $lang!</pre>";
+ return self::$translations['default'][$key];
+ }
+ if (isset(self::$translations['en'][$key])) {
+ // echo "<pre>Case 3: $lang!</pre>";
+ return self::$translations['en'][$key];
+ }
+ // echo "<pre>Case 4: $lang!". var_dump(self::$translations['en']) ."</pre>";
+ return "[[$key]]";
+ }
+}
+/**
+ * get the language string $key
+ * return string
+ */
+
+function L($key){
+ global $language;
+ if (empty($key)) {
+ return '';
+ }
+ if (isset($language[$key])) {
+ return $language[$key];
+ }
+ return "[[$key]]";
+}
+
+/**
+ * get the language string $key in $lang
+ * or current default language if $lang
+ * is not given.
+ * return string
+ */
+
+function tL($key, $lang = null) {
+ return FlySprayI18N::L($key, $lang);
+}
+/**
+ * html escaped variant of the previous
+ * return $string
+ */
+function eL($key){
+ return htmlspecialchars(L($key), ENT_QUOTES, 'utf-8');
+}
+
+function load_translations(){
+ global $proj, $language, $user, $fs;
+ # Load translations
+ # if no valid lang_code, return english
+ # valid == a-z and "_" case insensitive
+
+ if (isset($user) && array_key_exists('lang_code', $user->infos)){
+ $lang_code=$user->infos['lang_code'];
+ }
+
+ # 20150211 add language preferences detection of visitors
+ # locale_accept_from_http() not available on every hosting, so we must parse it self.
+ # TODO ..and we can loop later through $langs until we find a matching translation file
+ if((!isset($lang_code) || $lang_code=='' || $lang_code=='browser') && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])){
+ foreach( explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lang) {
+ # taken from a php.net comment
+ $pattern = '/^(?P<primarytag>[a-zA-Z]{2,8})'.
+ '(?:-(?P<subtag>[a-zA-Z]{2,8}))?(?:(?:;q=)'.
+ '(?P<quantifier>\d\.\d))?$/';
+ $splits = array();
+ if (preg_match($pattern, $lang, $splits)) {
+ $langs[]=$splits;
+ }
+ }
+ # TODO maybe sort $langs-array by quantifiers, but for most browsers it should be ok, because they sent it in right order.
+ if(isset($langs)){
+ $lang_code=$langs[0]['primarytag'];
+ if(isset($langs[0]['subtag'])){
+ $lang_code.='_'.$langs[0]['subtag']; # '_' for our language files, '-' in HTTP_ACCEPT_LANGUAGE
+ }
+ }
+ }
+
+ if(!isset($lang_code) || $lang_code=='' || $lang_code=='project'){
+ if($proj->prefs['lang_code']){
+ $lang_code = $proj->prefs['lang_code'];
+ }else{
+ $lang_code = 'en';
+ }
+ }
+
+ if (!preg_match('/^[a-z_]+$/iD', $lang_code)) {
+ $lang_code ='en';
+ }
+
+ $lang_code = strtolower($lang_code);
+ $translation = BASEDIR.'/lang/'.$lang_code.'.php';
+ if ($lang_code != 'en' && is_readable($translation)) {
+ include_once($translation);
+ $language = is_array($translation) ? array_merge($language, $translation) : $language;
+ FlySprayI18N::init($lang_code, $language);
+ }elseif( 'en'!=substr($lang_code, 0, strpos($lang_code, '_')) && is_readable(BASEDIR.'/lang/'.(substr($lang_code, 0, strpos($lang_code, '_'))).'.php') ){
+ # fallback 'de_AT' to 'de', but not for 'en_US'
+ $translation=BASEDIR.'/lang/'.(substr($lang_code, 0, strpos($lang_code, '_'))).'.php';
+ include_once($translation);
+ $language = is_array($translation) ? array_merge($language, $translation) : $language;
+ }
+
+ FlySprayI18N::setDefault($language);
+ // correctly translate title since language not set when initialising the project
+ if (isset($proj) && !$proj->id) {
+ $proj->prefs['project_title'] = L('allprojects');
+ $proj->prefs['feed_description'] = L('feedforall');
+ }
+
+ for ($i = 6; $i >= 1; $i--) {
+ $fs->priorities[$i] = L('priority' . $i);
+ }
+ for ($i = 5; $i >= 1; $i--) {
+ $fs->severities[$i] = L('severity' . $i);
+ }
+}
diff --git a/includes/modify.inc.php b/includes/modify.inc.php
new file mode 100644
index 0000000..004a5a8
--- /dev/null
+++ b/includes/modify.inc.php
@@ -0,0 +1,3053 @@
+<?php
+
+/**
+ * Database Modifications
+ * @version $Id$
+ */
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$notify = new Notifications;
+
+$lt = Post::isAlnum('list_type') ? Post::val('list_type') : '';
+$list_table_name = null;
+$list_column_name = null;
+$list_id = null;
+
+if (strlen($lt)) {
+ $list_table_name = '{list_'.$lt .'}';
+ $list_column_name = $lt . '_name';
+ $list_id = $lt . '_id';
+}
+
+function Post_to0($key) { return Post::val($key, 0); }
+
+function resizeImage($file, $max_x, $max_y, $forcePng = false)
+{
+ if ($max_x <= 0 || $max_y <= 0) {
+ $max_x = 5;
+ $max_y = 5;
+ }
+
+ $src = BASEDIR.'/avatars/'.$file;
+
+ list($width, $height, $type) = getImageSize($src);
+
+ $scale = min($max_x / $width, $max_y / $height);
+ $newWidth = $width * $scale;
+ $newHeight = $height * $scale;
+
+ $img = imagecreatefromstring(file_get_contents($src));
+ $black = imagecolorallocate($img, 0, 0, 0);
+ $resizedImage = imageCreateTrueColor($newWidth, $newHeight);
+ imagecolortransparent($resizedImage, $black);
+ imageCopyResampled($resizedImage, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+ imageDestroy($img);
+ unlink($src);
+
+ if (!$forcePng) {
+ switch ($type) {
+ case IMAGETYPE_JPEG:
+ imageJpeg($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ case IMAGETYPE_GIF:
+ imageGif($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ case IMAGETYPE_PNG:
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ default:
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ }
+ }
+ else {
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file.'.png');
+ }
+
+ return;
+}
+
+if (Req::num('task_id')) {
+ $task = Flyspray::getTaskDetails(Req::num('task_id'));
+}
+
+if(isset($_SESSION)) {
+ unset($_SESSION['SUCCESS'], $_SESSION['ERROR'], $_SESSION['ERRORS']);
+}
+
+switch ($action = Req::val('action'))
+{
+ // ##################
+ // Adding a new task
+ // ##################
+ case 'newtask.newtask':
+
+ $newtaskerrors=array();
+
+ if (!Post::val('item_summary') || trim(Post::val('item_summary')) == '') { // description not required anymore
+ $newtaskerrors['summaryrequired']=1;
+ }
+
+ if ($user->isAnon() && !filter_var(Post::val('anon_email'), FILTER_VALIDATE_EMAIL)) {
+ $newtaskerrors['invalidemail']=1;
+ }
+
+ if (count($newtaskerrors)>0){
+ $_SESSION['ERRORS']=$newtaskerrors;
+ $_SESSION['ERROR']=L('invalidnewtask');
+ break;
+ }
+
+ list($task_id, $token) = Backend::create_task($_POST);
+ // Status and redirect
+ if ($task_id) {
+ $_SESSION['SUCCESS'] = L('newtaskadded');
+
+ if ($user->isAnon()) {
+ Flyspray::redirect(createURL('details', $task_id, null, array('task_token' => $token)));
+ } else {
+ Flyspray::redirect(createURL('details', $task_id));
+ }
+ } else {
+ Flyspray::show_error(L('databasemodfailed'));
+ break;
+ }
+ break;
+
+ // ##################
+ // Adding multiple new tasks
+ // ##################
+ case 'newmultitasks.newmultitasks':
+ if(!isset($_POST['item_summary'])) {
+ #Flyspray::show_error(L('summaryanddetails'));
+ Flyspray::show_error(L('summaryrequired'));
+ break;
+ }
+ $flag = true;
+ foreach($_POST['item_summary'] as $summary) {
+ if(!$summary || trim($summary) == "") {
+ $flag = false;
+ break;
+ }
+ }
+ $i = 0;
+ foreach($_POST['detailed_desc'] as $detail) {
+ if($detail){
+ # only for ckeditor/html, not for dokuwiki (or other syntax plugins in future)
+ if ($conf['general']['syntax_plugin'] != 'dokuwiki') {
+ $_POST['detailed_desc'][$i] = "<p>" . $detail . "</p>";
+ }
+ }
+ $i++;
+ }
+ if(!$flag) {
+ #Flyspray::show_error(L('summaryanddetails'));
+ Flyspray::show_error(L('summaryrequired'));
+ break;
+ }
+
+ $flag = true;
+ $length = count($_POST['detailed_desc']);
+ for($i = 0; $i < $length; $i++) {
+ $ticket = array();
+ foreach($_POST as $key => $value) {
+ if($key == "assigned_to") {
+ $sql = $db->query("SELECT user_id FROM {users} WHERE user_name = ? or real_name = ?", array($value[$i], $value[$i]));
+ $ticket["rassigned_to"] = array(intval($db->fetchOne($sql)));
+ continue;
+ }
+ if(is_array($value))
+ $ticket[$key] = $value[$i];
+ else
+ $ticket[$key] = $value;
+ }
+ list($task_id, $token) = Backend::create_task($ticket);
+ if (!$task_id) {
+ $flag = false;
+ break;
+ }
+ }
+
+ if(!$flag) {
+ Flyspray::show_error(L('databasemodfailed'));
+ break;
+ }
+
+ $_SESSION['SUCCESS'] = L('newtaskadded');
+ Flyspray::redirect(createURL('index', $proj->id));
+ break;
+
+ // ##################
+ // Modifying an existing task
+ // ##################
+ case 'details.update':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission')); # TODO create a better error message
+ break;
+ }
+
+ $errors=array();
+
+ # TODO add checks who should be able to move a task, modify_all_tasks perm should not be enough and the target project perms are required too.
+ # - User has project manager permission in source project AND in target project: Allowed to move task
+ # - User has project manager permission in source project, but NOT in target project: Can send request to PUSH task to target project. A user with project manager permission of target project can accept the PUSH request.
+ # - User has NO project manager permission in source project, but in target project: Can send request to PULL task to target project. A user with project manager permission of source project can accept the PULL request.
+ # - User has calculated can_edit_task permission in source project AND (at least) newtask perm in target project: Can send a request to move task (similiar to 'close task please'-request) with the target project id, sure.
+
+ $move=0;
+ if($task['project_id'] != Post::val('project_id')) {
+ $toproject=new Project(Post::val('project_id'));
+ if($user->perms('modify_all_tasks', $toproject->id)){
+ $move=1;
+ } else{
+ $errors['invalidtargetproject']=1;
+ }
+ }
+
+ if($move==1){
+ # Check that a task is not moved to a different project than its
+ # possible parent or subtasks. Note that even closed tasks are
+ # included in the result, a task can be always reopened later.
+ $result = $db->query('
+ SELECT parent.task_id, parent.project_id FROM {tasks} p
+ JOIN {tasks} parent ON parent.task_id = p.supertask_id
+ WHERE p.task_id = ?
+ AND parent.project_id <> ?',
+ array( $task['task_id'], Post::val('project_id') )
+ );
+ $parentcheck = $db->fetchRow($result);
+ if ($parentcheck && $parentcheck['task_id']) {
+ if ($parentcheck['project_id'] != Post::val('project_id')) {
+ $errors['denymovehasparent']=L('denymovehasparent');
+ }
+ }
+
+ $result = $db->query('
+ SELECT sub.task_id, sub.project_id FROM {tasks} p
+ JOIN {tasks} sub ON p.task_id = sub.supertask_id
+ WHERE p.task_id = ?
+ AND sub.project_id <> ?',
+ array( $task['task_id'], Post::val('project_id') )
+ );
+ $subcheck = $db->fetchRow($result);
+
+ # if there are any subtasks, check that the project is not changed
+ if ($subcheck && $subcheck['task_id']) {
+ $errors['denymovehassub']=L('denymovehassub');
+ }
+ }
+
+ # summary form input fields, so user get notified what needs to be done right to be accepted
+ if (!Post::val('item_summary')) {
+ # description can be empty now
+ #Flyspray::show_error(L('summaryanddetails'));
+ #Flyspray::show_error(L('summaryrequired'));
+ $errors['summaryrequired']=L('summaryrequired');
+ }
+
+ # ids of severity and priority are (probably!) intentional fixed in Flyspray.
+ if( isset($_POST['task_severity']) && (!is_numeric(Post::val('task_severity')) || Post::val('task_severity')>5 || Post::val('task_severity')<0 ) ){
+ $errors['invalidseverity']=1;
+ }
+
+ # peterdd:temp fix to allow priority 6 again
+ # But I think about 1-5 valid (and 0 for unset) only in future to harmonize
+ # with other trackers/taskplaner software and for severity-priority graphs like
+ # https://en.wikipedia.org/wiki/Time_management#The_Eisenhower_Method
+ if( isset($_POST['task_priority']) && (!is_numeric(Post::val('task_priority')) || Post::val('task_priority')>6 || Post::val('task_priority')<0 ) ){
+ $errors['invalidpriority']=1;
+ }
+
+ if( isset($_POST['percent_complete']) && (!is_numeric(Post::val('percent_complete')) || Post::val('percent_complete')>100 || Post::val('percent_complete')<0 ) ){
+ $errors['invalidprogress']=1;
+ }
+
+ # Description for the following list values here when moving a task to a different project:
+ # - Do we use the old invalid values? (current behavior until 1.0-beta2, invalid id-values in database can be set, can result in php-'notices' or values arent shown on pages)
+ # - Or set to default value of the new project? And inform the user to adjust the task properties in the new project?
+ # - Or create a new tasktype for the new project, but:
+ # - Has the user the permission to create a new tasktype for the new project?
+ # - similiar named tasktypes exists?
+ #
+ # Maybe let's go with 2 steps when in this situation:
+ # When user want move task to other project, a second page shows the form again but:
+ # dropdown list forms show - maybe divided as optiongroups - :
+ # -global list values ()
+ # -current project list values
+ # -target project list values
+ # -option to create a new option based on current project value (if the user has the permission for the target project!)
+ # -option to set to default value in target project or unset value
+ # Also consider that not all list dropdown field may be shown to the user because of project settings (visible_fields)!
+
+
+ # which $proj should we use here? $proj object is set in header.php by a request param before modify.inc.php is loaded, so it can differ from $task['project_id']!
+ if($move==1){
+ $statusarray=$toproject->listTaskStatuses();
+ } else{
+ $statusarray=$proj->listTaskStatuses();
+ }
+
+ # FIXME what if we move to different project, but the status_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and item_status selection is deactivated/not shown in edit task page?
+ if( isset($_POST['item_status']) && (!is_numeric(Post::val('item_status')) || false===Flyspray::array_find('status_id', Post::val('item_status'), $statusarray) ) ){
+ $errors['invalidstatus']=1;
+ }
+
+ if($move==1){
+ $typearray=$toproject->listTaskTypes();
+ } else{
+ $typearray=$proj->listTaskTypes();
+ }
+
+ # FIXME what if we move to different project, but tasktype_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and task_type selection is deactiveated/not shown in edit task page?
+ if( isset($_POST['task_type']) && (!is_numeric(Post::val('task_type')) || false===Flyspray::array_find('tasktype_id', Post::val('task_type'), $typearray) ) ){
+ $errors['invalidtasktype']=1;
+ }
+
+ # FIXME what if we move to different project and reportedver selection is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and reportedver is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and closedby_version selection is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and closedby_version is deactivated/not shown in edit task page?
+ if($move==1){
+ $versionarray=$toproject->listVersions();
+ } else{
+ $versionarray=$proj->listVersions();
+ }
+ if( isset($_POST['reportedver']) && (!is_numeric(Post::val('reportedver')) || ( $_POST['reportedver']!=='0' && false===Flyspray::array_find('version_id', Post::val('reportedver'), $versionarray)) ) ){
+ $errors['invalidreportedversion']=1;
+ }
+ if( isset($_POST['closedby_version']) && (!is_numeric(Post::val('closedby_version')) || ( $_POST['closedby_version']!=='0' && false===Flyspray::array_find('version_id', Post::val('closedby_version'), $versionarray)) ) ){
+ $errors['invaliddueversion']=1;
+ }
+
+ # FIXME what if we move to different project, but category_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and category selection is deactivated/not shown in edit task page?
+ if($move==1){
+ $catarray=$toproject->listCategories();
+ } else{
+ $catarray=$proj->listCategories();
+ }
+ if( isset($_POST['product_category']) && (!is_numeric(Post::val('product_category')) || false===Flyspray::array_find('category_id', Post::val('product_category'), $catarray) ) ){
+ $errors['invalidcategory']=1;
+ }
+
+ # FIXME what if we move to different project, but os_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and operating_system selection is deactivated/not shown in edit task page?
+ if($move==1){
+ $osarray=$toproject->listOs();
+ } else{
+ $osarray=$proj->listOs();
+ }
+ if( isset($_POST['operating_system']) && (!is_numeric(Post::val('operating_system')) || ( $_POST['operating_system']!=='0' && false===Flyspray::array_find('os_id', Post::val('operating_system'), $osarray)) ) ){
+ $errors['invalidos']=1;
+ }
+
+ if ($due_date = Post::val('due_date', 0)) {
+ $due_date = Flyspray::strtotime(Post::val('due_date'));
+ }
+
+ $estimated_effort = 0;
+ if (($estimated_effort = effort::editStringToSeconds(Post::val('estimated_effort'), $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format'])) === FALSE) {
+ $errors['invalideffort']=1;
+ }
+
+ $time = time();
+
+ $result = $db->query('SELECT * from {tasks} WHERE task_id = ?', array($task['task_id']));
+ $defaults = $db->fetchRow($result);
+
+ if (!Post::has('due_date')) {
+ $due_date = $defaults['due_date'];
+ }
+
+ if (!Post::has('estimated_effort')) {
+ $estimated_effort = $defaults['estimated_effort'];
+ }
+
+
+ if(count($errors)>0){
+ # some invalid input by the user. Do not save the input and in the details-edit-template show the user where in the form the invalid values are.
+ $_SESSION['ERRORS']=$errors; # $_SESSION['ERROR'] is very limited, holds only one string and often just overwritten
+ $_SESSION['ERROR']=L('invalidinput');
+ # pro and contra http 303 redirect here:
+ # - good: browser back button works, browser history.
+ # - bad: form inputs of user not preserved (at the moment). Annoying if user wrote a long description and then the form submit gets denied because of other reasons.
+ #Flyspray::redirect(createURL('edittask', $task['task_id']));
+ break;
+ }
+
+ # FIXME/TODO: If a user has only 'edit own task edit' permission and task remains in the same project,
+ # but there are not all fields visible/editable so the browser do not send that values with the form,
+ # the sql update query should not touch that fields. And it should not overwrite the field with the default value in this case.
+ # So this update query should be build dynamic (And for the future: when 'custom fields' are implemented ..)
+ # Alternative: Read task field values before update query.
+ # And we should check too what task fields the 'edit own task only'-user is allowed to change.
+ # (E.g ignore form fields the user is not allowed to change. Currently hardcoded in template..)
+
+/*
+ # Dynamic creation of the UPDATE query required
+ # First step: Settings which task fields can be changed by 'permission level': Based on situation found in FS 1.0-rc1 'status quo' in backend::create_task() and CleanFS/templates/template details.edit.tpl
+ #$basicfields[]=array('item_summary','detailed_desc', 'task_type', 'product_category', 'operating_system', 'task_severity', 'percent_complete', 'product_version', 'estimated_effort'); # modify_own_tasks, anon_open
+ $basicfields=$proj->prefs['basic_fields'];
+
+ # peterdd: just saved a bit work in progress for future dynamic sql update string
+ $sqlup='';
+ foreach($basicfields as $bf){
+ $sqlup.=' '.$bf.' = ?,';
+ $sqlparam[]= Post::val($bf, $oldvals[$bf]);
+ }
+ $sqlup.=' last_edited_by = ?,';
+ $sqlparam[]= $user->id;
+ $sqlup.=' last_edited_time = ?,';
+ $sqlparam[]= $time;
+
+ $devfields[]=array('task_priority', 'due_date', 'item_status', 'closedby_version'); # modify_all_tasks
+ $managerfields[]=array('project_id','mark_private'); # manage_project
+ #$customfields[]=array(); # Flyspray 1.? future: perms depend of each custom field setting in a project..
+
+ $sqlparam[]=$task['task_id'];
+ $sqlupdate='UPDATE {tasks} SET '.$sqlup.' WHERE task_id = ?';
+
+ echo '<pre>';print_r($sqlupdate);print_r($sqlparam);die();
+ $db->query($sqlupdate, $sqlparam);
+*/
+
+ $detailed_desc = Post::val('detailed_desc', $defaults['detailed_desc']);
+
+ # dokuwiki syntax plugin filters on output
+ if($conf['general']['syntax_plugin'] != 'dokuwiki'){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $detailed_desc = $purifier->purify($detailed_desc);
+ }
+
+ $db->query('UPDATE {tasks}
+ SET
+ project_id = ?,
+ task_type = ?,
+ item_summary = ?,
+ detailed_desc = ?,
+ item_status = ?,
+ mark_private = ?,
+ product_category = ?,
+ closedby_version = ?,
+ operating_system = ?,
+ task_severity = ?,
+ task_priority = ?,
+ last_edited_by = ?,
+ last_edited_time = ?,
+ due_date = ?,
+ percent_complete = ?,
+ product_version = ?,
+ estimated_effort = ?
+ WHERE task_id = ?',
+ array(
+ Post::val('project_id', $defaults['project_id']),
+ Post::val('task_type', $defaults['task_type']),
+ Post::val('item_summary', $defaults['item_summary']),
+ $detailed_desc,
+ Post::val('item_status', $defaults['item_status']),
+ intval($user->can_change_private($task) && Post::val('mark_private', $defaults['mark_private'])),
+ Post::val('product_category', $defaults['product_category']),
+ Post::val('closedby_version', $defaults['closedby_version']),
+ Post::val('operating_system', $defaults['operating_system']),
+ Post::val('task_severity', $defaults['task_severity']),
+ Post::val('task_priority', $defaults['task_priority']),
+ intval($user->id), $time, intval($due_date),
+ Post::val('percent_complete', $defaults['percent_complete']),
+ Post::val('reportedver', $defaults['product_version']),
+ intval($estimated_effort),
+ $task['task_id']
+ )
+ );
+
+ // Update the list of users assigned this task
+ $assignees = (array) Post::val('rassigned_to');
+ $assignees_changed = count(array_diff($task['assigned_to'], $assignees)) + count(array_diff($assignees, $task['assigned_to']));
+ if ($user->perms('edit_assignments') && $assignees_changed) {
+
+ // Delete the current assignees for this task
+ $db->query('DELETE FROM {assigned}
+ WHERE task_id = ?',
+ array($task['task_id']));
+
+ // Convert assigned_to and store them in the 'assigned' table
+ foreach ((array) Post::val('rassigned_to') as $key => $val)
+ {
+ $db->replace('{assigned}', array('user_id'=> $val, 'task_id'=> $task['task_id']), array('user_id','task_id'));
+ }
+ }
+
+ # FIXME what if we move to different project, but tag(s) is/are defined for the old project only (not global)?
+ # FIXME what if we move to different project and tag input field is deactivated/not shown in edit task page?
+ # - Create new tag(s) in target project if user has permission to create new tags but what with the users who have not the permission?
+ # update tags
+ $tagList = explode(';', Post::val('tags'));
+ $tagList = array_map('strip_tags', $tagList);
+ $tagList = array_map('trim', $tagList);
+ $tagList = array_unique($tagList); # avoid duplicates for inputs like: "tag1;tag1" or "tag1; tag1<p></p>"
+ $storedtags=array();
+ foreach($task['tags'] as $temptag){
+ $storedtags[]=$temptag['tag'];
+ }
+ $tags_changed = count(array_diff($storedtags, $tagList)) + count(array_diff($tagList, $storedtags));
+
+ if($tags_changed){
+ // Delete the current assigned tags for this task
+ $db->query('DELETE FROM {task_tag} WHERE task_id = ?', array($task['task_id']));
+ foreach ($tagList as $tag){
+ if ($tag == ''){
+ continue;
+ }
+ # size of {list_tag}.tag_name, see flyspray-install.xml
+ if(mb_strlen($tag) > 40){
+ # report that softerror
+ $errors['tagtoolong']=1;
+ continue;
+ }
+
+ $res=$db->query("SELECT tag_id FROM {list_tag} WHERE (project_id=0 OR project_id=?) AND tag_name LIKE ? ORDER BY project_id", array($proj->id,$tag) );
+ if($t=$db->fetchRow($res)){
+ $tag_id=$t['tag_id'];
+ } else{
+ if( $proj->prefs['freetagging']==1){
+ # add to taglist of the project
+ $db->query("INSERT INTO {list_tag} (project_id,tag_name) VALUES (?,?)", array($proj->id,$tag));
+ $tag_id=$db->insert_ID();
+ } else{
+ continue;
+ }
+ };
+ $db->query("INSERT INTO {task_tag}(task_id,tag_id) VALUES(?,?)", array($task['task_id'], $tag_id) );
+ }
+ }
+
+ // Get the details of the task we just updated
+ // To generate the changed-task message
+ $new_details_full = Flyspray::getTaskDetails($task['task_id']);
+ // Not very nice...maybe combine compare_tasks() and logEvent() ?
+ $result = $db->query("SELECT * FROM {tasks} WHERE task_id = ?",
+ array($task['task_id']));
+ $new_details = $db->fetchRow($result);
+
+ foreach ($new_details as $key => $val) {
+ if (strstr($key, 'last_edited_') || $key == 'assigned_to' || is_numeric($key)) {
+ continue;
+ }
+
+ if ($val != $task[$key]) {
+ // Log the changed fields in the task history
+ Flyspray::logEvent($task['task_id'], 3, $val, $task[$key], $key, $time);
+ }
+ }
+
+ $changes = Flyspray::compare_tasks($task, $new_details_full);
+ if (count($changes) > 0) {
+ $notify->create(NOTIFY_TASK_CHANGED, $task['task_id'], $changes, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ if ($assignees_changed) {
+ // Log to task history
+ Flyspray::logEvent($task['task_id'], 14, implode(' ', $assignees), implode(' ', $task['assigned_to']), '', $time);
+
+ // Notify the new assignees what happened. This obviously won't happen if the task is now assigned to no-one.
+ if (count($assignees)) {
+ $new_assignees = array_diff($task['assigned_to'], $assignees);
+ // Remove current user from notification list
+ if (!$user->infos['notify_own']) {
+ $new_assignees = array_filter($new_assignees, function($u) use($user) { return $user->id != $u; } );
+ }
+ if(count($new_assignees)) {
+ $notify->create(NOTIFY_NEW_ASSIGNEE, $task['task_id'], null, $notify->specificAddresses($new_assignees), NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+ }
+ }
+
+ Backend::add_comment($task, Post::val('comment_text'), $time);
+ Backend::delete_files(Post::val('delete_att'));
+ Backend::upload_files($task['task_id'], '0', 'usertaskfile');
+ Backend::delete_links(Post::val('delete_link'));
+ Backend::upload_links($task['task_id'], '0', 'userlink');
+
+ $_SESSION['SUCCESS'] = L('taskupdated');
+ # report minor/soft errors too that does not hindered saving task
+ if(count($errors)>0){
+ $_SESSION['ERRORS']=$errors;
+ }
+ Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // closing a task
+ // ##################
+ case 'details.close':
+ if (!$user->can_close_task($task)) {
+ break;
+ }
+
+ if ($task['is_closed']) {
+ break;
+ }
+
+ if (!Post::val('resolution_reason')) {
+ Flyspray::show_error(L('noclosereason'));
+ break;
+ }
+
+ Backend::close_task($task['task_id'], Post::val('resolution_reason'), Post::val('closure_comment', ''), Post::val('mark100', false));
+
+ $_SESSION['SUCCESS'] = L('taskclosedmsg');
+ # FIXME there are several pages using this form, details and pendingreq at least
+ #Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ case 'details.associatesubtask':
+ if ( $task['task_id'] == Post::num('associate_subtask_id')) {
+ Flyspray::show_error(L('selfsupertasknotallowed'));
+ break;
+ }
+ $sql = $db->query('SELECT supertask_id, project_id FROM {tasks} WHERE task_id = ?',
+ array(Post::num('associate_subtask_id')));
+
+ $suptask = $db->fetchRow($sql);
+
+ // check to see if the subtask exists.
+ if (!$suptask) {
+ Flyspray::show_error(L('subtasknotexist'));
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::num('associate_subtask_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('subtasknotexist'));
+ break;
+ }
+ }
+
+ // check to see if associated subtask is already the parent of this task
+ if ($suptask['supertask_id'] == Post::num('associate_subtask_id')) {
+ Flyspray::show_error(L('subtaskisparent'));
+ break;
+ }
+
+ // check to see if associated subtask already has a parent task
+ if ($suptask['supertask_id']) {
+ Flyspray::show_error(L('subtaskalreadyhasparent'));
+ break;
+ }
+
+ // check to see that both tasks belong to the same project
+ if ($task['project_id'] != $suptask['project_id']) {
+ Flyspray::show_error(L('musthavesameproject'));
+ break;
+ }
+
+ //associate the subtask
+ $db->query('UPDATE {tasks} SET supertask_id=? WHERE task_id=?',array( $task['task_id'], Post::num('associate_subtask_id')));
+ Flyspray::logEvent($task['task_id'], 32, Post::num('associate_subtask_id'));
+ Flyspray::logEvent(Post::num('associate_subtask_id'), 34, $task['task_id']);
+
+ $_SESSION['SUCCESS'] = sprintf( L('associatedsubtask'), Post::num('associate_subtask_id') );
+ break;
+
+
+ case 'reopen':
+ // ##################
+ // re-opening an task
+ // ##################
+ if (!$user->can_close_task($task)) {
+ break;
+ }
+
+ // Get last %
+ $old_percent = $db->query("SELECT old_value, new_value
+ FROM {history}
+ WHERE field_changed = 'percent_complete'
+ AND task_id = ? AND old_value != '100'
+ ORDER BY event_date DESC
+ LIMIT 1",
+ array($task['task_id']));
+ $old_percent = $db->fetchRow($old_percent);
+
+ $db->query("UPDATE {tasks}
+ SET resolution_reason = 0, closure_comment = '', date_closed = 0,
+ last_edited_time = ?, last_edited_by = ?, is_closed = 0, percent_complete = ?
+ WHERE task_id = ?",
+ array(time(), $user->id, intval($old_percent['old_value']), $task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, $old_percent['old_value'], $old_percent['new_value'], 'percent_complete');
+
+ $notify->create(NOTIFY_TASK_REOPENED, $task['task_id'], null, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ // add comment of PM request to comment page if accepted
+ $sql = $db->query('SELECT * FROM {admin_requests} WHERE task_id = ? AND request_type = ? AND resolved_by = 0',
+ array($task['task_id'], 2));
+ $request = $db->fetchRow($sql);
+ if ($request) {
+ $db->query('INSERT INTO {comments}
+ (task_id, date_added, last_edited_time, user_id, comment_text)
+ VALUES ( ?, ?, ?, ?, ? )',
+ array($task['task_id'], time(), time(), $request['submitted_by'], $request['reason_given']));
+ // delete existing PM request
+ $db->query('UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?
+ WHERE request_id = ?',
+ array($user->id, time(), $request['request_id']));
+ }
+
+ Flyspray::logEvent($task['task_id'], 13);
+
+ $_SESSION['SUCCESS'] = L('taskreopenedmsg');
+ # FIXME there are several pages using this form, details and pendingreq at least
+ #Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // adding a comment
+ // ##################
+ case 'details.addcomment':
+ if (!Backend::add_comment($task, Post::val('comment_text'))) {
+ Flyspray::show_error(L('nocommententered'));
+ break;
+ }
+
+ if (Post::val('notifyme') == '1') {
+ // If the user wanted to watch this task for changes
+ Backend::add_notification($user->id, $task['task_id']);
+ }
+
+ $_SESSION['SUCCESS'] = L('commentaddedmsg');
+ Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // Tracking
+ // ##################
+ case 'details.efforttracking':
+
+ require_once BASEDIR . '/includes/class.effort.php';
+ $effort = new effort($task['task_id'],$user->id);
+
+
+ if(Post::val('start_tracking')){
+ if($effort->startTracking())
+ {
+ $_SESSION['SUCCESS'] = L('efforttrackingstarted');
+ }
+ else
+ {
+ $_SESSION['ERROR'] = L('efforttrackingnotstarted');
+ }
+ }
+
+ if(Post::val('stop_tracking')){
+ $effort->stopTracking();
+ $_SESSION['SUCCESS'] = L('efforttrackingstopped');
+ }
+
+ if(Post::val('cancel_tracking')){
+ $effort->cancelTracking();
+ $_SESSION['SUCCESS'] = L('efforttrackingcancelled');
+ }
+
+ if(Post::val('manual_effort')){
+ if($effort->addEffort(Post::val('effort_to_add'), $proj)){
+ $_SESSION['SUCCESS'] = L('efforttrackingadded');
+ }
+ }
+
+ Flyspray::redirect(createURL('details', $task['task_id']).'#effort');
+ break;
+
+ // ##################
+ // sending a new user a confirmation code
+ // ##################
+ case 'register.sendcode':
+ if (!$user->can_register()) {
+ break;
+ }
+
+ $captchaerrors=array();
+ if($fs->prefs['captcha_securimage']){
+ $image = new Securimage();
+ if( !Post::isAlnum('captcha_code') || !$image->check(Post::val('captcha_code'))) {
+ $captchaerrors['invalidsecurimage']=1;
+ }
+ }
+
+ if($fs->prefs['captcha_recaptcha']){
+ require_once('class.recaptcha.php');
+ if( !recaptcha::verify()) {
+ $captchaerrors['invalidrecaptcha']=1;
+ }
+ }
+
+ if(count($captchaerrors)){
+ $_SESSION['ERRORS']=$captchaerrors;
+ Flyspray::show_error(L('captchaerror'));
+ break;
+ }
+
+ if (!Post::val('user_name') || !Post::val('real_name')
+ || !Post::val('email_address'))
+ {
+ // If the form wasn't filled out correctly, show an error
+ Flyspray::show_error(L('registererror'));
+ break;
+ }
+
+ if ($fs->prefs['repeat_emailaddress'] && Post::val('email_address') != Post::val('verify_email_address'))
+ {
+ Flyspray::show_error(L('emailverificationwrong'));
+ break;
+ }
+
+ $email = strtolower(Post::val('email_address'));
+ $jabber_id = strtolower(Post::val('jabber_id'));
+
+ //email is mandatory
+ if (!$email || !Flyspray::check_email($email)) {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+ //jabber_id is optional
+ if ($jabber_id && !Jabber::check_jid($jabber_id)) {
+ Flyspray::show_error(L('novalidjabber'));
+ break;
+ }
+
+ $user_name = Backend::clean_username(Post::val('user_name'));
+
+ // Limit length
+ $real_name = substr(trim(Post::val('real_name')), 0, 100);
+ // Remove doubled up spaces and control chars
+ $real_name = preg_replace('![\x00-\x1f\s]+!u', ' ', $real_name);
+
+ if (!$user_name || empty($user_name) || !$real_name) {
+ Flyspray::show_error(L('entervalidusername'));
+ break;
+ }
+
+ // Delete registration codes older than 24 hours
+ $yesterday = time() - 86400;
+ $db->query('DELETE FROM {registrations} WHERE reg_time < ?', array($yesterday));
+
+ $sql = $db->query('SELECT COUNT(*) FROM {users} u, {registrations} r
+ WHERE u.user_name = ? OR r.user_name = ?',
+ array($user_name, $user_name));
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE
+ jabber_id = ? AND jabber_id != ''
+ OR email_address = ? AND email_address != ''",
+ array($jabber_id, $email));
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('emailtaken'));
+ break;
+ }
+
+ // Generate a random bunch of numbers for the confirmation code and the confirmation url
+
+ foreach(array('randval','magic_url') as $genrandom) {
+
+ $$genrandom = md5(function_exists('openssl_random_pseudo_bytes') ?
+ openssl_random_pseudo_bytes(32) :
+ uniqid(mt_rand(), true));
+ }
+
+ $confirm_code = substr($randval, 0, 20);
+
+ // echo "<pre>Am I here?</pre>";
+ // send the email first
+ $userconfirmation = array();
+ $userconfirmation[$email] = array('recipient' => $email, 'lang' => $fs->prefs['lang_code']);
+ $recipients = array($userconfirmation);
+ if($notify->create(NOTIFY_CONFIRMATION, null, array($baseurl, $magic_url, $user_name, $confirm_code),
+ $recipients,
+ NOTIFY_EMAIL)) {
+
+ //email sent succefully, now update the database.
+ $reg_values = array(time(), $confirm_code, $user_name, $real_name,
+ $email, $jabber_id,
+ Post::num('notify_type'), $magic_url, Post::num('time_zone'));
+ // Insert everything into the database
+ $query = $db->query("INSERT INTO {registrations}
+ ( reg_time, confirm_code, user_name, real_name,
+ email_address, jabber_id, notify_type,
+ magic_url, time_zone )
+ VALUES ( " . $db->fill_placeholders($reg_values) . ' )', $reg_values);
+
+ if ($query) {
+ $_SESSION['SUCCESS'] = L('codesent');
+ Flyspray::redirect($baseurl);
+ }
+
+ } else {
+ Flyspray::show_error(L('codenotsent'));
+ break;
+ }
+
+ break;
+
+ // ##################
+ // new user self-registration with a confirmation code
+ // ##################
+ case 'register.registeruser':
+ if (!$user->can_register()) {
+ break;
+ }
+
+ if (!Post::val('user_pass') || !Post::val('confirmation_code')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ if (strlen(Post::val('user_pass')) < MIN_PW_LENGTH) {
+ Flyspray::show_error(L('passwordtoosmall'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_password'] && Post::val('user_pass') != Post::val('user_pass2')) {
+ Flyspray::show_error(L('nomatchpass'));
+ break;
+ }
+
+ // Check that the user entered the right confirmation code
+ $sql = $db->query("SELECT * FROM {registrations} WHERE magic_url = ?",
+ array(Post::val('magic_url')));
+ $reg_details = $db->fetchRow($sql);
+
+ if ($reg_details['confirm_code'] != trim(Post::val('confirmation_code'))) {
+ Flyspray::show_error(L('confirmwrong'));
+ break;
+ }
+
+ $profile_image = 'profile_image';
+ $image_path = '';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ $enabled = 1;
+ if (!Backend::create_user($reg_details['user_name'],
+ Post::val('user_pass'),
+ $reg_details['real_name'],
+ $reg_details['jabber_id'],
+ $reg_details['email_address'],
+ $reg_details['notify_type'], $reg_details['time_zone'], $fs->prefs['anon_group'], $enabled ,'', '', $image_path)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $db->query('DELETE FROM {registrations} WHERE magic_url = ? AND confirm_code = ?',
+ array(Post::val('magic_url'), Post::val('confirmation_code')));
+
+
+ $_SESSION['SUCCESS'] = L('accountcreated');
+ // If everything is ok, add here a notify to both administrators and the user.
+ // Otherwise, explain what wen wrong.
+
+ define('NO_DO', true);
+ break;
+
+ // ##################
+ // new user self-registration without a confirmation code
+ // ##################
+ case 'register.newuser':
+ case 'admin.newuser':
+ if (!($user->perms('is_admin') || $user->can_self_register())) {
+ break;
+ }
+
+ $captchaerrors=array();
+ if( !($user->perms('is_admin')) && $fs->prefs['captcha_securimage']) {
+ $image = new Securimage();
+ if( !Post::isAlnum('captcha_code') || !$image->check(Post::val('captcha_code'))) {
+ $captchaerrors['invalidsecurimage']=1;
+ }
+ }
+
+ if( !($user->perms('is_admin')) && $fs->prefs['captcha_recaptcha']){
+ require_once('class.recaptcha.php');
+ if( !recaptcha::verify()) {
+ $captchaerrors['invalidrecaptcha']=1;
+ }
+ }
+
+ # if both captchatypes are configured, maybe show the user which one or both failed.
+ if(count($captchaerrors)){
+ $_SESSION['ERRORS']=$captchaerrors;
+ Flyspray::show_error(L('captchaerror'));
+ break;
+ }
+
+ if (!Post::val('user_name') || !Post::val('real_name') || !Post::val('email_address'))
+ {
+ // If the form wasn't filled out correctly, show an error
+ Flyspray::show_error(L('registererror'));
+ break;
+ }
+
+ // Check email format
+ if (!Post::val('email_address') || !Flyspray::check_email(Post::val('email_address')))
+ {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_emailaddress'] && Post::val('email_address') != Post::val('verify_email_address'))
+ {
+ Flyspray::show_error(L('emailverificationwrong'));
+ break;
+ }
+
+ if (strlen(Post::val('user_pass')) && (strlen(Post::val('user_pass')) < MIN_PW_LENGTH)) {
+ Flyspray::show_error(L('passwordtoosmall'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_password'] && Post::val('user_pass') != Post::val('user_pass2')) {
+ Flyspray::show_error(L('nomatchpass'));
+ break;
+ }
+
+ if ($user->perms('is_admin')) {
+ $group_in = Post::val('group_in');
+ } else {
+ $group_in = $fs->prefs['anon_group'];
+ }
+
+ if(!$user->perms('is_admin')) {
+
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE
+ jabber_id = ? AND jabber_id != ''
+ OR email_address = ? AND email_address != ''",
+ array(Post::val('jabber_id'), Post::val('email_address')));
+
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('emailtaken'));
+ break;
+ }
+ }
+
+ $enabled = 1;
+ if($user->need_admin_approval()) $enabled = 0;
+
+ $profile_image = 'profile_image';
+ $image_path = '';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ if (!Backend::create_user(Post::val('user_name'), Post::val('user_pass'),
+ Post::val('real_name'), Post::val('jabber_id'),
+ Post::val('email_address'), Post::num('notify_type'),
+ Post::num('time_zone'), $group_in, $enabled, '', '', $image_path)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $_SESSION['SUCCESS'] = L('newusercreated');
+
+ if (!$user->perms('is_admin')) {
+ define('NO_DO', true);
+ }
+ break;
+
+
+ // ##################
+ // Admin based bulk registration of users
+ // ##################
+ case 'register.newuserbulk':
+ case 'admin.newuserbulk':
+ if (!($user->perms('is_admin')))
+ break;
+
+ $group_in = Post::val('group_in');
+ $error = '';
+ $success = '';
+ $noUsers = true;
+
+ // For each user in post, add them
+ for ($i = 0 ; $i < 10 ; $i++)
+ {
+ $user_name = Post::val('user_name' . $i);
+ $real_name = Post::val('real_name' . $i);
+ $email_address = Post::val('email_address' . $i);
+
+
+ if( $user_name == '' || $real_name == '' || $email_address == '')
+ continue;
+ else
+ $noUsers = false;
+
+ $enabled = 1;
+
+ // Avoid dups
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE email_address = ?",
+ array($email_address));
+
+ if ($db->fetchOne($sql))
+ {
+ $error .= "\n" . L('emailtakenbulk') . ": $email_address\n";
+ continue;
+ }
+
+ if (!Backend::create_user($user_name, Post::val('user_pass'),
+ $real_name, '',
+ $email_address,
+ Post::num('notify_type'),
+ Post::num('time_zone'), $group_in, $enabled, '', '', ''))
+ {
+ $error .= "\n" . L('usernametakenbulk') .": $user_name\n";
+ continue;
+ }
+ else
+ $success .= ' '.$user_name.' ';
+ }
+
+ if ($error != '')
+ Flyspray::show_error($error);
+ else if ( $noUsers == true)
+ Flyspray::show_error(L('nouserstoadd'));
+ else
+ {
+ $_SESSION['SUCCESS'] = L('created').$success;
+ if (!$user->perms('is_admin')) {
+ define('NO_DO', true);
+ }
+ }
+ break;
+
+
+ // ##################
+ // Bulk User Edit Form
+ // ##################
+ case 'admin.editallusers':
+
+ if (!($user->perms('is_admin'))) {
+ break;
+ }
+
+ $userids = Post::val('checkedUsers');
+
+ if(!is_array($userids)){
+ break;
+ }
+
+ $users=array();
+
+ foreach ($userids as $uid) {
+ if( ctype_digit($uid) ) {
+ if( $user->id == $uid ){
+ Flyspray::show_error(L('nosuicide'));
+ } else{
+ $users[]=$uid;
+ }
+ } else{
+ Flyspray::show_error(L('invalidinput'));
+ break 2;
+ }
+ }
+
+ if (count($users) == 0){
+ Flyspray::show_error(L('nouserselected'));
+ break;
+ }
+
+ // Make array of users to modify
+ $ids = "(" . $users[0];
+ for ($i = 1 ; $i < count($users) ; $i++)
+ {
+ $ids .= ", " . $users[$i];
+ }
+ $ids .= ")";
+
+ // Grab the action
+ if (isset($_POST['enable']))
+ {
+ $sql = $db->query("UPDATE {users} SET account_enabled = 1 WHERE user_id IN $ids");
+ }
+ else if (isset($_POST['disable']))
+ {
+ $sql = $db->query("UPDATE {users} SET account_enabled = 0 WHERE user_id IN $ids");
+ }
+ else if (isset($_POST['delete']))
+ {
+ //$sql = $db->query("DELETE FROM {users} WHERE user_id IN $ids");
+ foreach ($users as $uid) {
+ Backend::delete_user($uid);
+ }
+ }
+
+ // Show success message and exit
+ $_SESSION['SUCCESS'] = L('usersupdated');
+ break;
+
+
+
+ // ##################
+ // adding a new group
+ // ##################
+ case 'pm.newgroup':
+ case 'admin.newgroup':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('group_name')) {
+ Flyspray::show_error(L('groupanddesc'));
+ break;
+ } else {
+ // Check to see if the group name is available
+ $sql = $db->query("SELECT COUNT(*)
+ FROM {groups}
+ WHERE group_name = ? AND project_id = ?",
+ array(Post::val('group_name'), $proj->id));
+
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('groupnametaken'));
+ break;
+ } else {
+ $cols = array('group_name', 'group_desc', 'manage_project', 'edit_own_comments',
+ 'view_tasks', 'open_new_tasks', 'modify_own_tasks', 'add_votes',
+ 'modify_all_tasks', 'view_comments', 'add_comments', 'edit_assignments',
+ 'edit_comments', 'delete_comments', 'create_attachments',
+ 'delete_attachments', 'view_history', 'close_own_tasks',
+ 'close_other_tasks', 'assign_to_self', 'show_as_assignees',
+ 'assign_others_to_self', 'add_to_assignees', 'view_reports', 'group_open',
+ 'view_estimated_effort', 'track_effort', 'view_current_effort_done',
+ 'add_multiple_tasks', 'view_roadmap', 'view_own_tasks', 'view_groups_tasks');
+
+ $params = array_map('Post_to0',$cols);
+ array_unshift($params, $proj->id);
+
+ $db->query("INSERT INTO {groups} (project_id, ". join(',', $cols).")
+ VALUES (". $db->fill_placeholders($cols, 1) . ')', $params);
+
+ $_SESSION['SUCCESS'] = L('newgroupadded');
+ }
+ }
+
+ break;
+
+ // ##################
+ // Update the global application preferences
+ // ##################
+ case 'globaloptions':
+ if (!$user->perms('is_admin')) {
+ break;
+ }
+
+ $errors=array();
+
+ $settings = array('jabber_server', 'jabber_port', 'jabber_username', 'notify_registration',
+ 'jabber_password', 'anon_group', 'user_notify', 'admin_email', 'email_ssl', 'email_tls',
+ 'lang_code', 'gravatars', 'hide_emails', 'spam_proof', 'default_project', 'default_entry',
+ 'dateformat','dateformat_extended',
+ 'jabber_ssl', 'anon_reg', 'global_theme', 'smtp_server', 'page_title',
+ 'smtp_user', 'smtp_pass', 'funky_urls', 'reminder_daemon','cache_feeds', 'intro_message',
+ 'disable_lostpw','disable_changepw','days_before_alert', 'emailNoHTML', 'need_approval', 'pages_welcome_msg',
+ 'active_oauths', 'only_oauth_reg', 'enable_avatars', 'max_avatar_size', 'default_order_by',
+ 'max_vote_per_day', 'votes_per_project', 'url_rewriting',
+ 'custom_style', 'general_integration', 'footer_integration',
+ 'repeat_password', 'repeat_emailaddress', 'massops');
+
+ if(!isset($fs->prefs['massops'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('massops',0)");
+ }
+
+ # candid for a plugin, so separate them for the future.
+ $settings[]='captcha_securimage';
+ if(!isset($fs->prefs['captcha_securimage'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('captcha_securimage',0)");
+ }
+
+ # candid for a plugin
+ $settings[]='captcha_recaptcha';
+ $settings[]='captcha_recaptcha_sitekey';
+ $settings[]='captcha_recaptcha_secret';
+ if(!isset($fs->prefs['captcha_recaptcha'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('captcha_recaptcha',0),('captcha_recaptcha_sitekey',''),('captcha_recaptcha_secret','')");
+ }
+
+ if(Post::val('need_approval') == '1' && Post::val('spam_proof')){
+ unset($_POST['spam_proof']); // if self register request admin to approve, disable spam_proof
+ // if you think different, modify functions in class.user.php directing different regiser tpl
+ }
+ if (Post::val('url_rewriting') == '1' && !$fs->prefs['url_rewriting']) {
+ # Setenv can't be used to set the env variable in .htaccess, because apache module setenv is often disabled on hostings and brings server error 500.
+ # First check if htaccess is turned on
+ #if (!array_key_exists('HTTP_HTACCESS_ENABLED', $_SERVER)) {
+ # Flyspray::show_error(L('enablehtaccess'));
+ # break;
+ #}
+
+ # Make sure mod_rewrite is enabled by checking a env var defined as HTTP_MOD_REWRITE in the .htaccess .
+ # It is possible to be converted to REDIRECT_HTTP_MOD_REWRITE . It's sound weired, but that's the case here.
+ if ( !array_key_exists('HTTP_MOD_REWRITE', $_SERVER) && !array_key_exists('REDIRECT_HTTP_MOD_REWRITE' , $_SERVER) ) {
+ #print_r($_SERVER);die();
+ Flyspray::show_error(L('nomodrewrite'));
+ break;
+ }
+ }
+
+ if( substr(Post::val('custom_style'), -4) != '.css'){
+ $_POST['custom_style']='';
+ }
+
+ # TODO validation
+ if( Post::val('default_order_by2') !='' && Post::val('default_order_by2') !='n'){
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'].', '.$_POST['default_order_by2'].' '.$_POST['default_order_by_dir2'];
+ } else{
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'];
+ }
+
+
+ foreach ($settings as $setting) {
+ $db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?',
+ array(Post::val($setting, 0), $setting));
+ // Update prefs for following scripts
+ $fs->prefs[$setting] = Post::val($setting, 0);
+ }
+
+ // Process the list of groups into a format we can store
+ $viscols = trim(Post::val('visible_columns'));
+ $db->query("UPDATE {prefs} SET pref_value = ?
+ WHERE pref_name = 'visible_columns'",
+ array($viscols));
+ $fs->prefs['visible_columns'] = $viscols;
+
+ $visfields = trim(Post::val('visible_fields'));
+ $db->query("UPDATE {prefs} SET pref_value = ?
+ WHERE pref_name = 'visible_fields'",
+ array($visfields));
+ $fs->prefs['visible_fields'] = $visfields;
+
+ //save logo
+ if($_FILES['logo']['error'] == 0){
+ if( in_array(exif_imagetype($_FILES['logo']['tmp_name']), array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)) ) {
+ $logofilename=strtolower(basename($_FILES['logo']['name']));
+ $logoexplode = explode('.', $logofilename);
+ $logoextension = strtolower(end($logoexplode));
+ $allowedextensions = array('gif', 'jpg', 'jpeg', 'png');
+
+ if(in_array($logoextension, $allowedextensions)){
+ move_uploaded_file($_FILES['logo']['tmp_name'], './' . $logofilename);
+ $sql = $db->query("SELECT * FROM {prefs} WHERE pref_name='logo'");
+ if(!$db->fetchOne($sql)){
+ $db->query("INSERT INTO {prefs} (pref_name) VALUES('logo')");
+ }
+ $db->query("UPDATE {prefs} SET pref_value = ? WHERE pref_name='logo'", $logofilename);
+ } else{
+ $errors['invalidfileextension']=1;
+ }
+ }
+ }
+ //saved logo
+
+ $_SESSION['SUCCESS'] = L('optionssaved');
+ if(count($errors)>0){
+ $_SESSION['ERRORS']=$errors;
+ }
+
+ break;
+
+ // ##################
+ // adding a new project
+ // ##################
+ case 'admin.newproject':
+ if (!$user->perms('is_admin')) {
+ break;
+ }
+
+ if (!Post::val('project_title')) {
+ Flyspray::show_error(L('emptytitle'));
+ break;
+ }
+
+ $viscols = $fs->prefs['visible_columns']
+ ? $fs->prefs['visible_columns']
+ : 'id tasktype priority severity summary status dueversion progress';
+
+ $visfields = $fs->prefs['visible_fields']
+ ? $fs->prefs['visible_fields']
+ : 'id tasktype priority severity summary status dueversion progress';
+
+
+ $db->query('INSERT INTO {projects}
+ ( project_title, theme_style, intro_message,
+ others_view, others_viewroadmap, anon_open, project_is_active,
+ visible_columns, visible_fields, lang_code, notify_email, notify_jabber, disp_intro)
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)',
+ array(Post::val('project_title'), Post::val('theme_style'),
+ Post::val('intro_message'), Post::num('others_view', 0), Post::num('others_viewroadmap', 0),
+ Post::num('anon_open', 0), $viscols, $visfields,
+ Post::val('lang_code', 'en'), '', '',
+ Post::num('disp_intro')
+ ));
+
+ // $sql = $db->query('SELECT project_id FROM {projects} ORDER BY project_id DESC', false, 1);
+ // $pid = $db->fetchOne($sql);
+ $pid = $db->insert_ID();
+
+ $cols = array( 'manage_project', 'view_tasks', 'open_new_tasks',
+ 'modify_own_tasks', 'modify_all_tasks', 'view_comments',
+ 'add_comments', 'edit_comments', 'delete_comments', 'show_as_assignees',
+ 'create_attachments', 'delete_attachments', 'view_history', 'add_votes',
+ 'close_own_tasks', 'close_other_tasks', 'assign_to_self', 'edit_own_comments',
+ 'assign_others_to_self', 'add_to_assignees', 'view_reports', 'group_open',
+ 'view_estimated_effort', 'view_current_effort_done', 'track_effort',
+ 'add_multiple_tasks', 'view_roadmap', 'view_own_tasks', 'view_groups_tasks',
+ 'edit_assignments');
+ $args = array_fill(0, count($cols), '1');
+ array_unshift($args, 'Project Managers',
+ 'Permission to do anything related to this project.',
+ intval($pid));
+
+ $db->query("INSERT INTO {groups}
+ ( group_name, group_desc, project_id,
+ ".join(',', $cols).")
+ VALUES ( ". $db->fill_placeholders($cols, 3) .")", $args);
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt)
+ VALUES ( ?, ?, 1, 0, 1, 4)", array($pid, 'root'));
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt )
+ VALUES ( ?, ?, 1, 0, 2, 3)", array($pid, 'Backend / Core'));
+
+ $db->query("INSERT INTO {list_os}
+ ( project_id, os_name, list_position, show_in_list )
+ VALUES (?, ?, 1, 1)", array($pid, 'All'));
+
+ $db->query("INSERT INTO {list_version}
+ ( project_id, version_name, list_position,
+ show_in_list, version_tense )
+ VALUES (?, ?, 1, 1, 2)", array($pid, '1.0'));
+
+ $_SESSION['SUCCESS'] = L('projectcreated');
+ Flyspray::redirect(createURL('pm', 'prefs', $pid));
+ break;
+
+ // ##################
+ // updating project preferences
+ // ##################
+ case 'pm.updateproject':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (Post::val('delete_project')) {
+ if (Backend::delete_project($proj->id, Post::val('move_to'))) {
+ $_SESSION['SUCCESS'] = L('projectdeleted');
+ } else {
+ $_SESSION['ERROR'] = L('projectnotdeleted');
+ }
+
+ if (Post::val('move_to')) {
+ Flyspray::redirect(createURL('pm', 'prefs', Post::val('move_to')));
+ } else {
+ Flyspray::redirect($baseurl);
+ }
+ }
+
+ if (!Post::val('project_title')) {
+ Flyspray::show_error(L('emptytitle'));
+ break;
+ }
+
+ $cols = array( 'project_title', 'theme_style', 'lang_code', 'default_task', 'default_entry',
+ 'intro_message', 'notify_email', 'notify_jabber', 'notify_subject', 'notify_reply',
+ 'feed_description', 'feed_img_url','default_due_version','use_effort_tracking',
+ 'pages_intro_msg', 'estimated_effort_format', 'current_effort_done_format');
+ $args = array_map('Post_to0', $cols);
+ $cols = array_merge($cols, $ints = array('project_is_active', 'others_view', 'others_viewroadmap', 'anon_open', 'comment_closed', 'auto_assign', 'freetagging'));
+ $args = array_merge($args, array_map(array('Post', 'num'), $ints));
+ $cols[] = 'notify_types';
+ $args[] = implode(' ', (array) Post::val('notify_types'));
+ $cols[] = 'last_updated';
+ $args[] = time();
+ $cols[] = 'disp_intro';
+ $args[] = Post::num('disp_intro');
+ $cols[] = 'default_cat_owner';
+ $args[] = Flyspray::UserNameToId(Post::val('default_cat_owner'));
+ $cols[] = 'custom_style';
+ $args[] = Post::val('custom_style');
+
+ // Convert to seconds.
+ if (Post::val('hours_per_manday')) {
+ $args[] = effort::editStringToSeconds(Post::val('hours_per_manday'), $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ $cols[] = 'hours_per_manday';
+ }
+
+ # TODO validation
+ if( Post::val('default_order_by2') !=''){
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'].', '.$_POST['default_order_by2'].' '.$_POST['default_order_by_dir2'];
+ } else{
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'];
+ }
+ $cols[]='default_order_by';
+ $args[]= $_POST['default_order_by'];
+
+ $args[] = $proj->id;
+
+ $update = $db->query("UPDATE {projects}
+ SET ".join('=?, ', $cols)."=?
+ WHERE project_id = ?", $args);
+
+ $update = $db->query('UPDATE {projects} SET visible_columns = ? WHERE project_id = ?',
+ array(trim(Post::val('visible_columns')), $proj->id));
+
+ $update = $db->query('UPDATE {projects} SET visible_fields = ? WHERE project_id = ?',
+ array(trim(Post::val('visible_fields')), $proj->id));
+
+ // Update project prefs for following scripts
+ $proj = new Project($proj->id);
+ $_SESSION['SUCCESS'] = L('projectupdated');
+ Flyspray::redirect(createURL('pm', 'prefs', $proj->id));
+ break;
+
+ // ##################
+ // modifying user details/profile
+ // ##################
+ case 'admin.edituser':
+ case 'myprofile.edituser':
+ if (Post::val('delete_user')) {
+ // There probably is a bug here somewhere but I just can't find it just now.
+ // Anyway, I get the message also when just editing my details.
+ if ($user->id == (int)Post::val('user_id') && $user->perms('is_admin')) {
+ Flyspray::show_error(L('nosuicide'));
+ break;
+ }
+ else {
+ // check that he is not the last user
+ $sql = $db->query('SELECT count(*) FROM {users}');
+ if ($db->fetchOne($sql) > 1) {
+ Backend::delete_user(Post::val('user_id'));
+ $_SESSION['SUCCESS'] = L('userdeleted');
+ Flyspray::redirect(createURL('admin', 'groups'));
+ } else {
+ Flyspray::show_error(L('lastuser'));
+ break;
+ }
+ }
+ }
+
+ if (!Post::val('onlypmgroup')):
+ if ($user->perms('is_admin') || $user->id == Post::val('user_id')): // only admin or user himself can change
+
+ if (!Post::val('real_name') || (!Post::val('email_address') && !Post::val('jabber_id'))) {
+ Flyspray::show_error(L('realandnotify'));
+ break;
+ }
+
+ // Check email format
+ if (!Post::val('email_address') || !Flyspray::check_email(Post::val('email_address')))
+ {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+
+ # current CleanFS template skips oldpass input requirement for admin accounts: if someone is able to catch an admin session he could simply create another admin acc for example.
+ #if ( (!$user->perms('is_admin') || $user->id == Post::val('user_id')) && !Post::val('oldpass')
+ if ( !$user->perms('is_admin') && !Post::val('oldpass') && (Post::val('changepass') || Post::val('confirmpass')) ) {
+ Flyspray::show_error(L('nooldpass'));
+ break;
+ }
+
+ if ($user->infos['oauth_uid'] && Post::val('changepass')) {
+ Flyspray::show_error(sprintf(L('oauthreqpass'), ucfirst($uesr->infos['oauth_provider'])));
+ break;
+ }
+
+ if (Post::val('changepass')) {
+ if ($fs->prefs['repeat_password'] && Post::val('changepass') != Post::val('confirmpass')) {
+ Flyspray::show_error(L('passnomatch'));
+ break;
+ }
+ if (Post::val('oldpass')) {
+ $sql = $db->query('SELECT user_pass FROM {users} WHERE user_id = ?', array(Post::val('user_id')));
+ $oldpass = $db->fetchRow($sql);
+
+ $pwtest=false;
+ if(strlen($oldpass['user_pass'])==32){
+ $pwtest=hash_equals($oldpass['user_pass'], md5(Post::val('oldpass')));
+ }elseif(strlen($oldpass['user_pass'])==40){
+ $pwtest=hash_equals($oldpass['user_pass'], sha1(Post::val('oldpass')));
+ }elseif(strlen($oldpass['user_pass'])==128){
+ $pwtest=hash_equals($oldpass['user_pass'], hash('sha512',Post::val('oldpass')));
+ }else{
+ $pwtest=password_verify(Post::val('oldpass'), $oldpass['user_pass']);
+ }
+
+ if (!$pwtest){
+ Flyspray::show_error(L('oldpasswrong'));
+ break;
+ }
+ }
+ $new_hash = Flyspray::cryptPassword(Post::val('changepass'));
+ $db->query('UPDATE {users} SET user_pass = ? WHERE user_id = ?',
+ array($new_hash, Post::val('user_id')));
+
+ // If the user is changing their password, better update their cookie hash
+ if ($user->id == Post::val('user_id')) {
+ Flyspray::setCookie('flyspray_passhash',
+ crypt($new_hash, $conf['general']['cookiesalt']), time()+3600*24*30,null,null,null,true);
+ }
+ }
+ $jabId = Post::val('jabber_id');
+ if (!empty($jabId) && Post::val('old_jabber_id') != $jabId) {
+ Notifications::JabberRequestAuth(Post::val('jabber_id'));
+ }
+
+ $db->query('UPDATE {users}
+ SET real_name = ?, email_address = ?, notify_own = ?,
+ jabber_id = ?, notify_type = ?,
+ dateformat = ?, dateformat_extended = ?,
+ tasks_perpage = ?, time_zone = ?, lang_code = ?,
+ hide_my_email = ?, notify_online = ?
+ WHERE user_id = ?',
+ array(Post::val('real_name'), Post::val('email_address'), Post::num('notify_own', 0),
+ Post::val('jabber_id', ''), Post::num('notify_type'),
+ Post::val('dateformat', 0), Post::val('dateformat_extended', 0),
+ Post::num('tasks_perpage'), Post::num('time_zone'), Post::val('lang_code', 'en'),
+ Post::num('hide_my_email', 0), Post::num('notify_online', 0), Post::num('user_id')));
+
+ # 20150307 peterdd: Now we must reload translations, because the user maybe changed his language preferences!
+ # first reload user info
+ $user=new User($user->id);
+ load_translations();
+
+ $profile_image = 'profile_image';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $sql = $db->query('SELECT profile_image FROM {users} WHERE user_id = ?', array(Post::val('user_id')));
+ $avatar_oldname = $db->fetchRow($sql);
+
+ if (is_file(BASEDIR.'/avatars/'.$avatar_oldname['profile_image']))
+ unlink(BASEDIR.'/avatars/'.$avatar_oldname['profile_image']);
+
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ $db->query('UPDATE {users} SET profile_image = ? WHERE user_id = ?',
+ array($avatar_name, Post::num('user_id')));
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ endif; // end only admin or user himself can change
+
+ if ($user->perms('is_admin')) {
+ if($user->id == (int)Post::val('user_id')) {
+ if (Post::val('account_enabled', 0) <= 0 || Post::val('old_global_id') != 1) {
+ Flyspray::show_error(L('nosuicide'));
+ break;
+ }
+ } else {
+ $db->query('UPDATE {users} SET account_enabled = ? WHERE user_id = ?',
+ array(Post::val('account_enabled', 0), Post::val('user_id')));
+ $db->query('UPDATE {users_in_groups} SET group_id = ?
+ WHERE group_id = ? AND user_id = ?',
+ array(Post::val('group_in'), Post::val('old_global_id'), Post::val('user_id')));
+ }
+ }
+
+ endif; // end non project group changes
+
+ if ($user->perms('manage_project') && !is_null(Post::val('project_group_in')) && Post::val('project_group_in') != Post::val('old_group_id')) {
+ $db->query('DELETE FROM {users_in_groups} WHERE group_id = ? AND user_id = ?',
+ array(Post::val('old_group_id'), Post::val('user_id')));
+ if (Post::val('project_group_in')) {
+ $db->query('INSERT INTO {users_in_groups} (group_id, user_id) VALUES(?, ?)',
+ array(Post::val('project_group_in'), Post::val('user_id')));
+ }
+ }
+
+ $_SESSION['SUCCESS'] = L('userupdated');
+ if ($action === 'myprofile.edituser') {
+ Flyspray::redirect(createURL('myprofile'));
+ } elseif ($action === 'admin.edituser' && Post::val('area') === 'users') {
+ Flyspray::redirect(createURL('edituser', Post::val('user_id')));
+ } else {
+ Flyspray::redirect(createURL('user', Post::val('user_id')));
+ }
+ break;
+ // ##################
+ // approving a new user registration
+ // ##################
+ case 'approve.user':
+ if($user->perms('is_admin')) {
+ $db->query('UPDATE {users} SET account_enabled = ? WHERE user_id = ?',
+ array(1, Post::val('user_id')));
+
+ $db->query('UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?
+ WHERE submitted_by = ? AND request_type = ?',
+ array($user->id, time(), Post::val('user_id'), 3));
+ // Missing event constant, can't log yet...
+ // Missing notification constant, can't notify yet...
+ // Notification constant added, write the code for sending that message...
+
+ }
+ break;
+ // ##################
+ // updating a group definition
+ // ##################
+ case 'pm.editgroup':
+ case 'admin.editgroup':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('group_name')) {
+ Flyspray::show_error(L('groupanddesc'));
+ break;
+ }
+
+ $cols = array('group_name', 'group_desc');
+
+ // Add a user to a group
+ if ($uid = Post::val('uid')) {
+ $uids = preg_split('/[,;]+/', $uid, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($uids as $uid) {
+ $uid = Flyspray::usernameToId($uid);
+ if (!$uid) {
+ continue;
+ }
+
+ // If user is already a member of one of the project's groups, **move** (not add) him to the new group
+ $sql = $db->query('SELECT g.group_id
+ FROM {users_in_groups} uig, {groups} g
+ WHERE g.group_id = uig.group_id AND uig.user_id = ? AND project_id = ?',
+ array($uid, $proj->id));
+ if ($db->countRows($sql)) {
+ $oldid = $db->fetchOne($sql);
+ $db->query('UPDATE {users_in_groups} SET group_id = ? WHERE user_id = ? AND group_id = ?',
+ array(Post::val('group_id'), $uid, $oldid));
+ } else {
+ $db->query('INSERT INTO {users_in_groups} (group_id, user_id) VALUES(?, ?)',
+ array(Post::val('group_id'), $uid));
+ }
+ }
+ }
+
+ if (Post::val('delete_group') && Post::val('group_id') != '1') {
+ $db->query('DELETE FROM {groups} WHERE group_id = ?', Post::val('group_id'));
+
+ if (Post::val('move_to')) {
+ $db->query('UPDATE {users_in_groups} SET group_id = ? WHERE group_id = ?',
+ array(Post::val('move_to'), Post::val('group_id')));
+ }
+
+ $_SESSION['SUCCESS'] = L('groupupdated');
+ Flyspray::redirect(createURL( (($proj->id) ? 'pm' : 'admin'), 'groups', $proj->id));
+ }
+ // Allow all groups to update permissions except for global Admin
+ if (Post::val('group_id') != '1') {
+ $cols = array_merge($cols,
+ array('manage_project', 'view_tasks', 'edit_own_comments',
+ 'open_new_tasks', 'modify_own_tasks', 'modify_all_tasks',
+ 'view_comments', 'add_comments', 'edit_comments', 'delete_comments',
+ 'create_attachments', 'delete_attachments', 'show_as_assignees',
+ 'view_history', 'close_own_tasks', 'close_other_tasks', 'edit_assignments',
+ 'assign_to_self', 'assign_others_to_self', 'add_to_assignees', 'view_reports',
+ 'add_votes', 'group_open', 'view_estimated_effort', 'track_effort',
+ 'view_current_effort_done', 'add_multiple_tasks', 'view_roadmap',
+ 'view_own_tasks', 'view_groups_tasks'));
+ }
+
+ $args = array_map('Post_to0', $cols);
+ $args[] = Post::val('group_id');
+ $args[] = $proj->id;
+
+ $db->query("UPDATE {groups}
+ SET ".join('=?,', $cols)."=?
+ WHERE group_id = ? AND project_id = ?", $args);
+
+ $_SESSION['SUCCESS'] = L('groupupdated');
+ break;
+
+ // ##################
+ // updating a list
+ // ##################
+ case 'update_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listposition = Post::val('list_position');
+ $listshow = Post::val('show_in_list');
+ $listdelete = Post::val('delete');
+ if($lt=='tag'){
+ $listclass = Post::val('list_class');
+ }
+ foreach ($listnames as $id => $listname) {
+ if ($listname != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?
+ AND $list_id <> ?",
+ array($proj->id, $listnames[$id], $id)
+ );
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), $listnames[$id]));
+ return;
+ }
+
+ if($lt=='tag'){
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name=?, list_position=?, show_in_list=?, class=?
+ WHERE $list_id=? AND project_id=?",
+ array($listnames[$id], intval($listposition[$id]), intval($listshow[$id]), $listclass[$id], $id, $proj->id)
+ );
+ } else{
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name=?, list_position=?, show_in_list=?
+ WHERE $list_id=? AND project_id=?",
+ array($listnames[$id], intval($listposition[$id]), intval($listshow[$id]), $id, $proj->id)
+ );
+ }
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM $list_table_name WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a list item
+ // ##################
+ case 'pm.add_to_list':
+ case 'admin.add_to_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ $position = Post::num('list_position');
+ if (!$position) {
+ $position = intval($db->fetchOne($db->query("SELECT max(list_position)+1
+ FROM $list_table_name
+ WHERE project_id = ?",
+ array($proj->id))));
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?",
+ array($proj->id, Post::val('list_name')));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), Post::val('list_name')));
+ return;
+ }
+
+ $db->query("INSERT INTO $list_table_name
+ (project_id, $list_column_name, list_position, show_in_list)
+ VALUES (?, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'), $position, '1'));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // updating the version list
+ // ##################
+ case 'update_version_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listposition = Post::val('list_position');
+ $listshow = Post::val('show_in_list');
+ $listtense = Post::val('version_tense');
+ $listdelete = Post::val('delete');
+
+ foreach ($listnames as $id => $listname) {
+ if (is_numeric($listposition[$id]) && $listnames[$id] != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?
+ AND $list_id <> ?",
+ array($proj->id, $listnames[$id], $id));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), $listnames[$id]));
+ return;
+ }
+
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name = ?, list_position = ?,
+ show_in_list = ?, version_tense = ?
+ WHERE $list_id = ? AND project_id = ?",
+ array($listnames[$id], intval($listposition[$id]),
+ intval($listshow[$id]), intval($listtense[$id]), $id, $proj->id));
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM $list_table_name WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a version list item
+ // ##################
+ case 'pm.add_to_version_list':
+ case 'admin.add_to_version_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ $position = Post::num('list_position');
+ if (!$position) {
+ $position = $db->fetchOne($db->query("SELECT max(list_position)+1
+ FROM $list_table_name
+ WHERE project_id = ?",
+ array($proj->id)));
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?",
+ array($proj->id, Post::val('list_name')));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), Post::val('list_name')));
+ return;
+ }
+
+ $db->query("INSERT INTO $list_table_name
+ (project_id, $list_column_name, list_position, show_in_list, version_tense)
+ VALUES (?, ?, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'),
+ intval($position), '1', Post::val('version_tense')));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // updating the category list
+ // ##################
+ case 'update_category':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listshow = Post::val('show_in_list');
+ $listdelete = Post::val('delete');
+ $listlft = Post::val('lft');
+ $listrgt = Post::val('rgt');
+ $listowners = Post::val('category_owner');
+
+ foreach ($listnames as $id => $listname) {
+ if ($listname != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ // Check for duplicates on the same sub-level under same parent category.
+ // First, we'll have to find the right parent for the current category.
+ $sql = $db->query('SELECT *
+ FROM {list_category}
+ WHERE project_id = ? AND lft < ? and rgt > ?
+ AND lft = (SELECT MAX(lft) FROM {list_category} WHERE lft < ? and rgt > ?)',
+ array($proj->id, intval($listlft[$id]), intval($listrgt[$id]), intval($listlft[$id]), intval($listrgt[$id])));
+
+ $parent = $db->fetchRow($sql);
+
+ $check = $db->query('SELECT COUNT(*)
+ FROM {list_category} c
+ WHERE project_id = ? AND category_name = ? AND lft > ? AND rgt < ?
+ AND category_id <> ?
+ AND NOT EXISTS (SELECT *
+ FROM {list_category}
+ WHERE project_id = ?
+ AND lft > ? AND rgt < ?
+ AND lft < c.lft AND rgt > c.rgt)',
+ array($proj->id, $listname, $parent['lft'], $parent['rgt'], intval($id), $proj->id, $parent['lft'], $parent['rgt']));
+ $itemexists = $db->fetchOne($check);
+
+ // echo "<pre>" . $parent['category_name'] . "," . $listname . ", " . intval($id) . ", " . intval($listlft[$id]) . ", " . intval($listrgt[$id]) . ", " . $itemexists ."</pre>";
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('categoryitemexists'), $listname, $parent['category_name']));
+ return;
+ }
+
+
+ $update = $db->query('UPDATE {list_category}
+ SET category_name = ?,
+ show_in_list = ?, category_owner = ?,
+ lft = ?, rgt = ?
+ WHERE category_id = ? AND project_id = ?',
+ array($listname, intval($listshow[$id]), Flyspray::UserNameToId($listowners[$id]), intval($listlft[$id]), intval($listrgt[$id]), intval($id), $proj->id));
+ // Correct visibility for sub categories
+ if ($listshow[$id] == 0) {
+ foreach ($listnames as $key => $value) {
+ if ($listlft[$key] > $listlft[$id] && $listrgt[$key] < $listrgt[$id]) {
+ $listshow[$key] = 0;
+ }
+ }
+ }
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM {list_category} WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a category list item
+ // ##################
+ case 'pm.add_category':
+ case 'admin.add_category':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ // Get right value of last node
+ // Need also left value of parent for duplicate check and category name for errormessage.
+ $sql = $db->query('SELECT rgt, lft, category_name FROM {list_category} WHERE category_id = ?', array(Post::val('parent_id', -1)));
+ $parent = $db->fetchRow($sql);
+ $right = $parent['rgt'];
+ $left = $parent['lft'];
+
+ // echo "<pre>Parent: " . Post::val('parent_id', -1) . ", left: $left, right: $right</pre>";
+
+ // If parent has subcategories, check for possible duplicates
+ // on the same sub-level and under the same parent.
+ if ($left + 1 != $right) {
+ $check = $db->query('SELECT COUNT(*)
+ FROM {list_category} c
+ WHERE project_id = ? AND category_name = ? AND lft > ? AND rgt < ?
+ AND NOT EXISTS (SELECT *
+ FROM {list_category}
+ WHERE project_id = ?
+ AND lft > ? AND rgt < ?
+ AND lft < c.lft AND rgt > c.rgt)',
+ array($proj->id, Post::val('list_name'), $left, $right, $proj->id, $left, $right));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('categoryitemexists'), Post::val('list_name'), $parent['category_name']));
+ return;
+ }
+ }
+
+ $db->query('UPDATE {list_category} SET rgt=rgt+2 WHERE rgt >= ? AND project_id = ?', array($right, $proj->id));
+ $db->query('UPDATE {list_category} SET lft=lft+2 WHERE lft >= ? AND project_id = ?', array($right, $proj->id));
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name, show_in_list, category_owner, lft, rgt )
+ VALUES (?, ?, 1, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'),
+ Post::val('category_owner', 0) == '' ? '0' : Flyspray::usernameToId(Post::val('category_owner', 0)), $right, $right+1));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // adding a related task entry
+ // ##################
+ case 'details.add_related':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('related_task'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('relatedinvalid'));
+ break;
+ }
+ }
+
+ $sql = $db->query('SELECT project_id
+ FROM {tasks}
+ WHERE task_id = ?',
+ array(Post::val('related_task')));
+ if (!$db->countRows($sql)) {
+ Flyspray::show_error(L('relatedinvalid'));
+ break;
+ }
+
+ $sql = $db->query("SELECT related_id
+ FROM {related}
+ WHERE this_task = ? AND related_task = ?
+ OR
+ related_task = ? AND this_task = ?",
+ array($task['task_id'], Post::val('related_task'),
+ $task['task_id'], Post::val('related_task')));
+
+ if ($db->countRows($sql)) {
+ Flyspray::show_error(L('relatederror'));
+ break;
+ }
+
+ $db->query("INSERT INTO {related} (this_task, related_task) VALUES(?,?)",
+ array($task['task_id'], Post::val('related_task')));
+
+ Flyspray::logEvent($task['task_id'], 11, Post::val('related_task'));
+ Flyspray::logEvent(Post::val('related_task'), 15, $task['task_id']);
+ $notify->create(NOTIFY_REL_ADDED, $task['task_id'], Post::val('related_task'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ $_SESSION['SUCCESS'] = L('relatedaddedmsg');
+ break;
+
+ // ##################
+ // Removing a related task entry
+ // ##################
+ case 'remove_related':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+ if (!is_array(Post::val('related_id'))) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ foreach (Post::val('related_id') as $related) {
+ $sql = $db->query('SELECT this_task, related_task FROM {related} WHERE related_id = ?',
+ array($related));
+ $db->query('DELETE FROM {related} WHERE related_id = ? AND (this_task = ? OR related_task = ?)',
+ array($related, $task['task_id'], $task['task_id']));
+ if ($db->affectedRows()) {
+ $related_task = $db->fetchRow($sql);
+ $related_task = ($related_task['this_task'] == $task['task_id']) ? $related_task['related_task'] : $task['task_id'];
+ Flyspray::logEvent($task['task_id'], 12, $related_task);
+ Flyspray::logEvent($related_task, 16, $task['task_id']);
+ $_SESSION['SUCCESS'] = L('relatedremoved');
+ }
+ }
+
+ break;
+
+ // ##################
+ // adding a user to the notification list
+ // ##################
+ case 'details.add_notification':
+ if (Req::val('user_id')) {
+ $userId = Req::val('user_id');
+ } else {
+ $userId = Flyspray::usernameToId(Req::val('user_name'));
+ }
+ if (!Backend::add_notification($userId, Req::val('ids'))) {
+ Flyspray::show_error(L('couldnotaddusernotif'));
+ break;
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('notifyadded');
+ Flyspray::redirect(createURL('details', $task['task_id']).'#notify');
+ break;
+
+ // ##################
+ // removing a notification entry
+ // ##################
+ case 'remove_notification':
+ Backend::remove_notification(Req::val('user_id'), Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('notifyremoved');
+ # if on details page we should redirect to details with a GET
+ # but what if the request comes from another page (like myprofile for instance maybe in future)
+ Flyspray::redirect(createURL('details', $task['task_id']).'#notify');
+ break;
+
+ // ##################
+ // editing a comment
+ // ##################
+ case 'editcomment':
+ if (!($user->perms('edit_comments') || $user->perms('edit_own_comments'))) {
+ break;
+ }
+
+ $where = '';
+
+ $comment_text=Post::val('comment_text');
+ $previous_text=Post::val('previous_text');
+
+ # dokuwiki syntax plugin filters on output
+ if($conf['general']['syntax_plugin'] != 'dokuwiki'){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $comment_text = $purifier->purify($comment_text);
+ $previous_text= $purifier->purify($comment_text);
+ }
+
+ $params = array($comment_text, time(), Post::val('comment_id'), $task['task_id']);
+
+ if ($user->perms('edit_own_comments') && !$user->perms('edit_comments')) {
+ $where = ' AND user_id = ?';
+ array_push($params, $user->id);
+ }
+
+ $db->query("UPDATE {comments}
+ SET comment_text = ?, last_edited_time = ?
+ WHERE comment_id = ? AND task_id = ? $where", $params);
+ $db->query("DELETE FROM {cache} WHERE topic = ? AND type = ?", array(Post::val('comment_id'), 'comm'));
+
+ Flyspray::logEvent($task['task_id'], 5, $comment_text, $previous_text, Post::val('comment_id'));
+
+ Backend::upload_files($task['task_id'], Post::val('comment_id'));
+ Backend::delete_files(Post::val('delete_att'));
+ Backend::upload_links($task['task_id'], Post::val('comment_id'));
+ Backend::delete_links(Post::val('delete_link'));
+
+ $_SESSION['SUCCESS'] = L('editcommentsaved');
+ break;
+
+ // ##################
+ // deleting a comment
+ // ##################
+ case 'details.deletecomment':
+ if (!$user->perms('delete_comments')) {
+ break;
+ }
+
+ $result = $db->query('SELECT task_id, comment_text, user_id, date_added
+ FROM {comments}
+ WHERE comment_id = ?',
+ array(Get::val('comment_id')));
+ $comment = $db->fetchRow($result);
+
+ // Check for files attached to this comment
+ $check_attachments = $db->query('SELECT *
+ FROM {attachments}
+ WHERE comment_id = ?',
+ array(Req::val('comment_id')));
+
+ if ($db->countRows($check_attachments) && !$user->perms('delete_attachments')) {
+ Flyspray::show_error(L('commentattachperms'));
+ break;
+ }
+
+ $db->query("DELETE FROM {comments} WHERE comment_id = ? AND task_id = ?",
+ array(Req::val('comment_id'), $task['task_id']));
+
+ if ($db->affectedRows()) {
+ Flyspray::logEvent($task['task_id'], 6, $comment['user_id'],
+ $comment['comment_text'], '', $comment['date_added']);
+ }
+
+ while ($attachment = $db->fetchRow($check_attachments)) {
+ $db->query("DELETE from {attachments} WHERE attachment_id = ?",
+ array($attachment['attachment_id']));
+
+ @unlink(BASEDIR .'/attachments/' . $attachment['file_name']);
+
+ Flyspray::logEvent($attachment['task_id'], 8, $attachment['orig_name']);
+ }
+
+ $_SESSION['SUCCESS'] = L('commentdeletedmsg');
+ break;
+
+ // ##################
+ // adding a reminder
+ // ##################
+ case 'details.addreminder':
+ $how_often = Post::val('timeamount1', 1) * Post::val('timetype1');
+ $start_time = Flyspray::strtotime(Post::val('timeamount2', 0));
+
+ $userId = Flyspray::usernameToId(Post::val('to_user_id'));
+ if (!Backend::add_reminder($task['task_id'], Post::val('reminder_message'), $how_often, $start_time, $userId)) {
+ Flyspray::show_error(L('usernotexist'));
+ break;
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('reminderaddedmsg');
+ break;
+
+ // ##################
+ // removing a reminder
+ // ##################
+ case 'deletereminder':
+ if (!$user->perms('manage_project') || !is_array(Post::val('reminder_id'))) {
+ break;
+ }
+
+ foreach (Post::val('reminder_id') as $reminder_id) {
+ $sql = $db->query('SELECT to_user_id FROM {reminders} WHERE reminder_id = ?',
+ array($reminder_id));
+ $reminder = $db->fetchOne($sql);
+ $db->query('DELETE FROM {reminders} WHERE reminder_id = ? AND task_id = ?',
+ array($reminder_id, $task['task_id']));
+ if ($db && $db->affectedRows()) {
+ Flyspray::logEvent($task['task_id'], 18, $reminder);
+ }
+ }
+
+ $_SESSION['SUCCESS'] = L('reminderdeletedmsg');
+ break;
+
+ // ##################
+ // change a bunch of users' groups
+ // ##################
+ case 'movetogroup':
+ // Check that both groups belong to the same project
+ $sql = $db->query('SELECT project_id FROM {groups} WHERE group_id = ? OR group_id = ?',
+ array(Post::val('switch_to_group'), Post::val('old_group')));
+ $old_pr = $db->fetchOne($sql);
+ $new_pr = $db->fetchOne($sql);
+ if ($proj->id != $old_pr || ($new_pr && $new_pr != $proj->id)) {
+ break;
+ }
+
+ if (!$user->perms('manage_project', $old_pr) || !is_array(Post::val('users'))) {
+ break;
+ }
+
+ foreach (Post::val('users') as $user_id => $val) {
+ if($user->id!=$user_id || $proj->id!=0){
+ if (Post::val('switch_to_group') == '0') {
+ $db->query('DELETE FROM {users_in_groups} WHERE user_id=? AND group_id=?',
+ array($user_id, Post::val('old_group'))
+ );
+ } else {
+ # special case: user exists in multiple global groups (shouldn't, but happened)
+ # avoids duplicate entry error
+ if($old_pr==0){
+ $sql = $db->query('SELECT group_id FROM {users_in_groups} WHERE user_id = ? AND group_id = ?',
+ array($user_id, Post::val('switch_to_group'))
+ );
+ $uigexists = $db->fetchOne($sql);
+ if($uigexists > 0){
+ $db->query('DELETE FROM {users_in_groups} WHERE user_id=? AND group_id=?',
+ array($user_id, Post::val('old_group'))
+ );
+ }
+ }
+
+ $db->query('UPDATE {users_in_groups} SET group_id=? WHERE user_id=? AND group_id=?',
+ array(Post::val('switch_to_group'), $user_id, Post::val('old_group'))
+ );
+ }
+ } else {
+ Flyspray::show_error(L('nosuicide'));
+ }
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('groupswitchupdated');
+ break;
+
+ // ##################
+ // taking ownership
+ // ##################
+ case 'takeownership':
+ Backend::assign_to_me($user->id, Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('takenownershipmsg');
+ break;
+
+ // ##################
+ // add to assignees list
+ // ##################
+ case 'addtoassignees':
+ Backend::add_to_assignees($user->id, Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('addedtoassignees');
+ break;
+
+ // ##################
+ // admin request
+ // ##################
+ case 'requestclose':
+ case 'requestreopen':
+ if ($action == 'requestclose') {
+ Flyspray::adminRequest(1, $proj->id, $task['task_id'], $user->id, Post::val('reason_given'));
+ Flyspray::logEvent($task['task_id'], 20, Post::val('reason_given'));
+ } elseif ($action == 'requestreopen') {
+ Flyspray::adminRequest(2, $proj->id, $task['task_id'], $user->id, Post::val('reason_given'));
+ Flyspray::logEvent($task['task_id'], 21, Post::val('reason_given'));
+ Backend::add_notification($user->id, $task['task_id']);
+ }
+
+ // Now, get the project managers' details for this project
+ $sql = $db->query("SELECT u.user_id
+ FROM {users} u
+ LEFT JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ LEFT JOIN {groups} g ON uig.group_id = g.group_id
+ WHERE g.project_id = ? AND g.manage_project = '1'",
+ array($proj->id));
+
+ $pms = $db->fetchCol($sql);
+ if (count($pms)) {
+ // Call the functions to create the address arrays, and send notifications
+ $notify->create(NOTIFY_PM_REQUEST, $task['task_id'], null, $notify->specificAddresses($pms), NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ $_SESSION['SUCCESS'] = L('adminrequestmade');
+ break;
+
+ // ##################
+ // denying a PM request
+ // ##################
+ case 'denypmreq':
+ $result = $db->query("SELECT task_id, project_id
+ FROM {admin_requests}
+ WHERE request_id = ?",
+ array(Req::val('req_id')));
+ $req_details = $db->fetchRow($result);
+
+ if (!$user->perms('manage_project', $req_details['project_id'])) {
+ break;
+ }
+
+ // Mark the PM request as 'resolved'
+ $db->query("UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?, deny_reason = ?
+ WHERE request_id = ?",
+ array($user->id, time(), Req::val('deny_reason'), Req::val('req_id')));
+
+ Flyspray::logEvent($req_details['task_id'], 28, Req::val('deny_reason'));
+ $notify->create(NOTIFY_PM_DENY_REQUEST, $req_details['task_id'], Req::val('deny_reason'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ $_SESSION['SUCCESS'] = L('pmreqdeniedmsg');
+ break;
+
+ // ##################
+ // deny a new user request
+ // ##################
+ case 'denyuserreq':
+ if($user->perms('is_admin')) {
+ $db->query("UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?, deny_reason = ?
+ WHERE request_id = ?",
+ array($user->id, time(), Req::val('deny_reason'), Req::val('req_id')));
+ // Wrong event constant
+ Flyspray::logEvent(0, 28, Req::val('deny_reason'));//nee a new event number. need notification. fix smtp first
+ // Missing notification constant, can't notify yet...
+ $_SESSION['SUCCESS'] = "New user register request denied";
+ }
+ break;
+
+ // ##################
+ // adding a dependency
+ // ##################
+ case 'details.newdep':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ if (!Post::val('dep_task_id')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ // TODO: do the checks in some other order. Think about possibility
+ // to combine many of the checks used to to see if a task exists,
+ // if it's something user is allowed to know about etc to just one
+ // function taking the necessary arguments and could be used in
+ // several other places too.
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('dep_task_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('dependaddfailed'));
+ break;
+ }
+ }
+
+ // First check that the user hasn't tried to add this twice
+ $sql1 = $db->query('SELECT COUNT(*) FROM {dependencies}
+ WHERE task_id = ? AND dep_task_id = ?',
+ array($task['task_id'], Post::val('dep_task_id')));
+
+ // or that they are trying to reverse-depend the same task, creating a mutual-block
+ $sql2 = $db->query('SELECT COUNT(*) FROM {dependencies}
+ WHERE task_id = ? AND dep_task_id = ?',
+ array(Post::val('dep_task_id'), $task['task_id']));
+
+ // Check that the dependency actually exists!
+ $sql3 = $db->query('SELECT COUNT(*) FROM {tasks} WHERE task_id = ?',
+ array(Post::val('dep_task_id')));
+
+ if ($db->fetchOne($sql1) || $db->fetchOne($sql2) || !$db->fetchOne($sql3)
+ // Check that the user hasn't tried to add the same task as a dependency
+ || Post::val('task_id') == Post::val('dep_task_id'))
+ {
+ Flyspray::show_error(L('dependaddfailed'));
+ break;
+ }
+ $notify->create(NOTIFY_DEP_ADDED, $task['task_id'], Post::val('dep_task_id'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ $notify->create(NOTIFY_REV_DEP, Post::val('dep_task_id'), $task['task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ // Log this event to the task history, both ways
+ Flyspray::logEvent($task['task_id'], 22, Post::val('dep_task_id'));
+ Flyspray::logEvent(Post::val('dep_task_id'), 23, $task['task_id']);
+
+ $db->query('INSERT INTO {dependencies} (task_id, dep_task_id)
+ VALUES (?,?)',
+ array($task['task_id'], Post::val('dep_task_id')));
+
+ $_SESSION['SUCCESS'] = L('dependadded');
+ break;
+
+ // ##################
+ // removing a subtask
+ // ##################
+ case 'removesubtask':
+
+ //check if the user has permissions to remove the subtask
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ //set the subtask supertask_id to 0 removing parent child relationship
+ $db->query("UPDATE {tasks} SET supertask_id=0 WHERE task_id = ?",
+ array(Post::val('subtaskid')));
+
+ //write event log
+ Flyspray::logEvent(Get::val('task_id'), 33, Post::val('subtaskid'));
+ //post success message to the user
+ $_SESSION['SUCCESS'] = L('subtaskremovedmsg');
+ //redirect the user back to the right task
+ Flyspray::redirect(createURL('details', Get::val('task_id')));
+ break;
+
+ // ##################
+ // removing a dependency
+ // ##################
+ case 'removedep':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ $result = $db->query('SELECT * FROM {dependencies}
+ WHERE depend_id = ?',
+ array(Post::val('depend_id')));
+ $dep_info = $db->fetchRow($result);
+
+ $db->query('DELETE FROM {dependencies} WHERE depend_id = ? AND task_id = ?',
+ array(Post::val('depend_id'), $task['task_id']));
+
+ if ($db->affectedRows()) {
+ $notify->create(NOTIFY_DEP_REMOVED, $dep_info['task_id'], $dep_info['dep_task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ $notify->create(NOTIFY_REV_DEP_REMOVED, $dep_info['dep_task_id'], $dep_info['task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ Flyspray::logEvent($dep_info['task_id'], 24, $dep_info['dep_task_id']);
+ Flyspray::logEvent($dep_info['dep_task_id'], 25, $dep_info['task_id']);
+
+ $_SESSION['SUCCESS'] = L('depremovedmsg');
+ } else {
+ Flyspray::show_error(L('erroronform'));
+ }
+
+ //redirect the user back to the right task
+ Flyspray::redirect(createURL('details', Post::val('return_task_id')));
+ break;
+
+ // ##################
+ // user requesting a password change
+ // ##################
+ case 'lostpw.sendmagic':
+ // Check that the username exists
+ $sql = $db->query('SELECT * FROM {users} WHERE user_name = ?',
+ array(Post::val('user_name')));
+
+ // If the username doesn't exist, throw an error
+ if (!$db->countRows($sql)) {
+ Flyspray::show_error(L('usernotexist'));
+ break;
+ }
+
+ $user_details = $db->fetchRow($sql);
+
+ if ($user_details['oauth_provider']) {
+ Flyspray::show_error(sprintf(L('oauthreqpass'), ucfirst($user_details['oauth_provider'])));
+ Flyspray::redirect($baseurl);
+ break;
+ }
+
+ //no microtime(), time,even with microseconds is predictable ;-)
+ $magic_url = md5(function_exists('openssl_random_pseudo_bytes') ?
+ openssl_random_pseudo_bytes(32) :
+ uniqid(mt_rand(), true));
+
+
+ // Insert the random "magic url" into the user's profile
+ $db->query('UPDATE {users}
+ SET magic_url = ?
+ WHERE user_id = ?',
+ array($magic_url, $user_details['user_id']));
+
+ if(count($user_details)) {
+ $notify->create(NOTIFY_PW_CHANGE, null, array($baseurl, $magic_url), $notify->specificAddresses(array($user_details['user_id']), NOTIFY_EMAIL));
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('magicurlsent');
+ break;
+
+ // ##################
+ // Change the user's password
+ // ##################
+ case 'lostpw.chpass':
+ // Check that the user submitted both the fields, and they are the same
+ if (!Post::val('pass1') || strlen(trim(Post::val('magic_url'))) !== 32) {
+ Flyspray::show_error(L('erroronform'));
+ break;
+ }
+
+ if ($fs->prefs['repeat_password'] && Post::val('pass1') != Post::val('pass2')) {
+ Flyspray::show_error(L('passnomatch'));
+ break;
+ }
+
+ $new_pass_hash = Flyspray::cryptPassword(Post::val('pass1'));
+ $db->query("UPDATE {users} SET user_pass = ?, magic_url = ''
+ WHERE magic_url = ?",
+ array($new_pass_hash, Post::val('magic_url')));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('passchanged');
+ Flyspray::redirect($baseurl);
+ break;
+
+ // ##################
+ // making a task private
+ // ##################
+ case 'makeprivate':
+ // TODO: Have to think about this one a bit more. Are project manager
+ // rights really needed for making a task a private? Are there some
+ // other conditions that would permit it? Also making it back to public.
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET mark_private = 1
+ WHERE task_id = ?', array($task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, 1, 0, 'mark_private');
+
+ $_SESSION['SUCCESS'] = L('taskmadeprivatemsg');
+ break;
+
+ // ##################
+ // making a task public
+ // ##################
+ case 'makepublic':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET mark_private = 0
+ WHERE task_id = ?', array($task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, 0, 1, 'mark_private');
+
+ $_SESSION['SUCCESS'] = L('taskmadepublicmsg');
+ break;
+
+ // ##################
+ // Adding a vote for a task
+ // ##################
+ case 'details.addvote':
+ if (Backend::add_vote($user->id, $task['task_id'])) {
+ $_SESSION['SUCCESS'] = L('voterecorded');
+ } else {
+ Flyspray::show_error(L('votefailed'));
+ break;
+ }
+ // TODO: Log event in a later version.
+ break;
+
+
+ // ##################
+ // Removing a vote for a task
+ // ##################
+ # used to remove a vote from myprofile page
+ case 'removevote':
+ # peterdd: I found no details.removevote action in source, so details.removevote is not used, but was planned on the task details page or in the old blue theme?
+ case 'details.removevote':
+ if (Backend::remove_vote($user->id, $task['task_id'])) {
+ $_SESSION['SUCCESS'] = L('voteremoved');
+ } else {
+ Flyspray::show_error(L('voteremovefailed'));
+ break;
+ }
+ // TODO: Log event in a later version, but also see if maybe done here Backend::remove_vote()...
+ break;
+
+
+ // ##################
+ // set supertask id
+ // ##################
+ case 'details.setparent':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ if (!Post::val('supertask_id')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ // check that supertask_id is not same as task_id
+ // preventing it from referring to itself
+ if (Post::val('task_id') == Post::val('supertask_id')) {
+ Flyspray::show_error(L('selfsupertasknotallowed'));
+ break;
+ }
+
+ // Check that the supertask_id looks like unsigned integer
+ if ( !preg_match("/^[1-9][0-9]{0,8}$/", Post::val('supertask_id')) ) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+
+ $sql = $db->query('SELECT project_id FROM {tasks} WHERE task_id = ?', array(Post::val('supertask_id')) );
+ // check that supertask_id is a valid task id
+ $parent = $db->fetchRow($sql);
+ if (!$parent) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('supertask_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+ }
+
+ // check to see that both tasks belong to the same project
+ if ($task['project_id'] != $parent['project_id']) {
+ Flyspray::show_error(L('musthavesameproject'));
+ break;
+ }
+
+ // finally looks like all the checks are valid so update the supertask_id for the current task
+ $db->query('UPDATE {tasks}
+ SET supertask_id = ?
+ WHERE task_id = ?',
+ array(Post::val('supertask_id'),Post::val('task_id')));
+
+ // If task already had a different parent, then log removal too
+ if ($task['supertask_id']) {
+ Flyspray::logEvent($task['supertask_id'], 33, Post::val('task_id'));
+ Flyspray::logEvent(Post::val('task_id'), 35, $task['supertask_id']);
+ }
+
+ // Log the events in the task history
+ Flyspray::logEvent(Post::val('supertask_id'), 32, Post::val('task_id'));
+ Flyspray::logEvent(Post::val('task_id'), 34, Post::val('supertask_id'));
+
+ // set success message
+ $_SESSION['SUCCESS'] = L('supertaskmodified');
+
+ break;
+ case 'notifications.remove':
+ if(!isset($_POST['message_id'])) {
+ // Flyspray::show_error(L('summaryanddetails'));
+ break;
+ }
+
+ if (!is_array($_POST['message_id'])) {
+ // Flyspray::show_error(L('summaryanddetails'));
+ break;
+ }
+ if (!count($_POST['message_id'])) {
+ // Nothing to do.
+ break;
+ }
+
+ $validids = array();
+ foreach ($_POST['message_id'] as $id) {
+ if (is_numeric($id)) {
+ if (settype($id, 'int') && $id > 0) {
+ $validids[] = $id;
+ }
+ }
+ }
+
+ if (!count($validids)) {
+ // Nothing to do.
+ break;
+ }
+
+ Notifications::NotificationsHaveBeenRead($validids);
+ break;
+ case 'task.bulkupdate':
+ # TODO check if the user has the right to do each action on each task id he send with the form!
+ # TODO check if tasks have open subtasks before closing
+ # TODO SQL Transactions with rollback function if something went wrong in the middle of bulk action
+ # disabled by default and if currently allowed only for admins until proper checks are done
+ if(isset($fs->prefs['massops']) && $fs->prefs['massops']==1 && $user->perms('is_admin')){
+
+ // TODO: Log events in a later version.
+
+ if(Post::val('updateselectedtasks') == "true") {
+ //process quick actions
+ switch(Post::val('bulk_quick_action'))
+ {
+ case 'bulk_take_ownership':
+ Backend::assign_to_me(Post::val('user_id'),Post::val('ids'));
+ break;
+ case 'bulk_start_watching':
+ Backend::add_notification(Post::val('user_id'),Post::val('ids'));
+ break;
+ case 'bulk_stop_watching':
+ Backend::remove_notification(Post::val('user_id'),Post::val('ids'));
+ break;
+ }
+
+ //Process the tasks.
+ $columns = array();
+ $values = array();
+
+ //determine the tasks properties that have been modified.
+ if(!Post::val('bulk_status')==0){
+ array_push($columns,'item_status');
+ array_push($values, Post::val('bulk_status'));
+ }
+ if(!Post::val('bulk_percent_complete')==0){
+ array_push($columns,'percent_complete');
+ array_push($values, Post::val('bulk_percent_complete'));
+ }
+ if(!Post::val('bulk_task_type')==0){
+ array_push($columns,'task_type');
+ array_push($values, Post::val('bulk_task_type'));
+ }
+ if(!Post::val('bulk_category')==0){
+ array_push($columns,'product_category');
+ array_push($values, Post::val('bulk_category'));
+ }
+ if(!Post::val('bulk_os')==0){
+ array_push($columns,'operating_system');
+ array_push($values, Post::val('bulk_os'));
+ }
+ if(!Post::val('bulk_severity')==0){
+ array_push($columns,'task_severity');
+ array_push($values, Post::val('bulk_severity'));
+ }
+ if(!Post::val('bulk_priority')==0){
+ array_push($columns,'task_priority');
+ array_push($values, Post::val('bulk_priority'));
+ }
+ if(!Post::val('bulk_reportedver')==0){
+ array_push($columns,'product_version');
+ array_push($values, Post::val('bulk_reportedver'));
+ }
+ if(!Post::val('bulk_due_version')==0){
+ array_push($columns,'closedby_version');
+ array_push($values, Post::val('bulk_due_version'));
+ }
+ # TODO Does the user has similiar rights in current and target projects?
+ # TODO Does a task has subtasks? What happens to them? What if they are open/closed?
+ # But: Allowing task dependencies between tasks in different projects is a feature!
+ if(!Post::val('bulk_projects')==0){
+ array_push($columns,'project_id');
+ array_push($values, Post::val('bulk_projects'));
+ }
+ if(!is_null(Post::val('bulk_due_date'))){
+ array_push($columns,'due_date');
+ array_push($values, Flyspray::strtotime(Post::val('bulk_due_date')));
+ }
+
+ //only process if one of the task fields has been updated.
+ if(!array_count_values($columns)==0 && Post::val('ids')){
+ //add the selected task id's to the query string
+ $task_ids = Post::val('ids');
+ $valuesAndTasks = array_merge_recursive($values,$task_ids);
+
+ //execute the database update on all selected queries
+ $update = $db->query("UPDATE {tasks}
+ SET ".join('=?, ', $columns)."=?
+ WHERE". substr(str_repeat(' task_id = ? OR ', count(Post::val('ids'))), 0, -3), $valuesAndTasks);
+ }
+
+ //Set the assignments
+ if(Post::val('bulk_assignment')){
+ // Delete the current assignees for the selected tasks
+ $db->query("DELETE FROM {assigned} WHERE". substr(str_repeat(' task_id = ? OR ', count(Post::val('ids'))), 0, -3),Post::val('ids'));
+
+ // Convert assigned_to and store them in the 'assigned' table
+ foreach ((array)Post::val('ids') as $id){
+ //iterate the users that are selected on the user list.
+ foreach ((array) Post::val('bulk_assignment') as $assignee){
+ //if 'noone' has been selected then dont do the database update.
+ if(!$assignee == 0){
+ //insert the task and user id's into the assigned table.
+ $db->query('INSERT INTO {assigned}
+ (task_id,user_id)
+ VALUES (?, ?)',array($id,$assignee));
+ }
+ }
+ }
+ }
+
+ // set success message
+ $_SESSION['SUCCESS'] = L('tasksupdated');
+ break;
+ }
+ //bulk close
+ else {
+ if (!Post::val('resolution_reason')) {
+ Flyspray::show_error(L('noclosereason'));
+ break;
+ }
+ $task_ids = Post::val('ids');
+ foreach($task_ids as $task_id) {
+ $task = Flyspray::getTaskDetails($task_id);
+ if (!$user->can_close_task($task)) {
+ continue;
+ }
+
+ if ($task['is_closed']) {
+ continue;
+ }
+
+ Backend::close_task($task_id, Post::val('resolution_reason'), Post::val('closure_comment', ''), Post::val('mark100', false));
+ }
+ $_SESSION['SUCCESS'] = L('taskclosedmsg');
+ break;
+ }
+ } # end if massopsenabled
+ else{
+ Flyspray::show_error(L('massopsdisabled'));
+ }
+ }
diff --git a/includes/password_compat.php b/includes/password_compat.php
new file mode 100644
index 0000000..d681634
--- /dev/null
+++ b/includes/password_compat.php
@@ -0,0 +1,319 @@
+<?php
+/**
+ * A Compatibility library with PHP 5.5's simplified password hashing API.
+ *
+ * @author Anthony Ferrara <ircmaxell@php.net>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @copyright 2012 The Authors
+ *
+ * Comment of Flyspray dev peterdd: This is lib/password.php from https://github.com/ircmaxell/password_compat master branch at 2016-06-27
+ */
+
+namespace {
+
+ if (!defined('PASSWORD_BCRYPT')) {
+ /**
+ * PHPUnit Process isolation caches constants, but not function declarations.
+ * So we need to check if the constants are defined separately from
+ * the functions to enable supporting process isolation in userland
+ * code.
+ */
+ define('PASSWORD_BCRYPT', 1);
+ define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
+ define('PASSWORD_BCRYPT_DEFAULT_COST', 10);
+ }
+
+ if (!function_exists('password_hash')) {
+
+ /**
+ * Hash the password using the specified algorithm
+ *
+ * @param string $password The password to hash
+ * @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
+ * @param array $options The options for the algorithm to use
+ *
+ * @return string|false The hashed password, or false on error.
+ */
+ function password_hash($password, $algo, array $options = array()) {
+ if (!function_exists('crypt')) {
+ trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
+ return null;
+ }
+ if (is_null($password) || is_int($password)) {
+ $password = (string) $password;
+ }
+ if (!is_string($password)) {
+ trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
+ return null;
+ }
+ if (!is_int($algo)) {
+ trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
+ return null;
+ }
+ $resultLength = 0;
+ switch ($algo) {
+ case PASSWORD_BCRYPT:
+ $cost = PASSWORD_BCRYPT_DEFAULT_COST;
+ if (isset($options['cost'])) {
+ $cost = (int) $options['cost'];
+ if ($cost < 4 || $cost > 31) {
+ trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
+ return null;
+ }
+ }
+ // The length of salt to generate
+ $raw_salt_len = 16;
+ // The length required in the final serialization
+ $required_salt_len = 22;
+ $hash_format = sprintf("$2y$%02d$", $cost);
+ // The expected length of the final crypt() output
+ $resultLength = 60;
+ break;
+ default:
+ trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
+ return null;
+ }
+ $salt_req_encoding = false;
+ if (isset($options['salt'])) {
+ switch (gettype($options['salt'])) {
+ case 'NULL':
+ case 'boolean':
+ case 'integer':
+ case 'double':
+ case 'string':
+ $salt = (string) $options['salt'];
+ break;
+ case 'object':
+ if (method_exists($options['salt'], '__tostring')) {
+ $salt = (string) $options['salt'];
+ break;
+ }
+ case 'array':
+ case 'resource':
+ default:
+ trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
+ return null;
+ }
+ if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
+ trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
+ return null;
+ } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
+ $salt_req_encoding = true;
+ }
+ } else {
+ $buffer = '';
+ $buffer_valid = false;
+ if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
+ $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
+ $strong = false;
+ $buffer = openssl_random_pseudo_bytes($raw_salt_len, $strong);
+ if ($buffer && $strong) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && @is_readable('/dev/urandom')) {
+ $file = fopen('/dev/urandom', 'r');
+ $read = 0;
+ $local_buffer = '';
+ while ($read < $raw_salt_len) {
+ $local_buffer .= fread($file, $raw_salt_len - $read);
+ $read = PasswordCompat\binary\_strlen($local_buffer);
+ }
+ fclose($file);
+ if ($read >= $raw_salt_len) {
+ $buffer_valid = true;
+ }
+ $buffer = str_pad($buffer, $raw_salt_len, "\0") ^ str_pad($local_buffer, $raw_salt_len, "\0");
+ }
+ if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
+ $buffer_length = PasswordCompat\binary\_strlen($buffer);
+ for ($i = 0; $i < $raw_salt_len; $i++) {
+ if ($i < $buffer_length) {
+ $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
+ } else {
+ $buffer .= chr(mt_rand(0, 255));
+ }
+ }
+ }
+ $salt = $buffer;
+ $salt_req_encoding = true;
+ }
+ if ($salt_req_encoding) {
+ // encode string with the Base64 variant used by crypt
+ $base64_digits =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ $bcrypt64_digits =
+ './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+ $base64_string = base64_encode($salt);
+ $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
+ }
+ $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
+
+ $hash = $hash_format . $salt;
+
+ $ret = crypt($password, $hash);
+
+ if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
+ return false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get information about the password hash. Returns an array of the information
+ * that was used to generate the password hash.
+ *
+ * array(
+ * 'algo' => 1,
+ * 'algoName' => 'bcrypt',
+ * 'options' => array(
+ * 'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
+ * ),
+ * )
+ *
+ * @param string $hash The password hash to extract info from
+ *
+ * @return array The array of information about the hash.
+ */
+ function password_get_info($hash) {
+ $return = array(
+ 'algo' => 0,
+ 'algoName' => 'unknown',
+ 'options' => array(),
+ );
+ if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
+ $return['algo'] = PASSWORD_BCRYPT;
+ $return['algoName'] = 'bcrypt';
+ list($cost) = sscanf($hash, "$2y$%d$");
+ $return['options']['cost'] = $cost;
+ }
+ return $return;
+ }
+
+ /**
+ * Determine if the password hash needs to be rehashed according to the options provided
+ *
+ * If the answer is true, after validating the password using password_verify, rehash it.
+ *
+ * @param string $hash The hash to test
+ * @param int $algo The algorithm used for new password hashes
+ * @param array $options The options array passed to password_hash
+ *
+ * @return boolean True if the password needs to be rehashed.
+ */
+ function password_needs_rehash($hash, $algo, array $options = array()) {
+ $info = password_get_info($hash);
+ if ($info['algo'] !== (int) $algo) {
+ return true;
+ }
+ switch ($algo) {
+ case PASSWORD_BCRYPT:
+ $cost = isset($options['cost']) ? (int) $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST;
+ if ($cost !== $info['options']['cost']) {
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Verify a password against a hash using a timing attack resistant approach
+ *
+ * @param string $password The password to verify
+ * @param string $hash The hash to verify against
+ *
+ * @return boolean If the password matches the hash
+ */
+ function password_verify($password, $hash) {
+ if (!function_exists('crypt')) {
+ trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
+ return false;
+ }
+ $ret = crypt($password, $hash);
+ if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
+ return false;
+ }
+
+ $status = 0;
+ for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
+ $status |= (ord($ret[$i]) ^ ord($hash[$i]));
+ }
+
+ return $status === 0;
+ }
+ }
+
+}
+
+namespace PasswordCompat\binary {
+
+ if (!function_exists('PasswordCompat\\binary\\_strlen')) {
+
+ /**
+ * Count the number of bytes in a string
+ *
+ * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
+ * In this case, strlen() will count the number of *characters* based on the internal encoding. A
+ * sequence of bytes might be regarded as a single multibyte character.
+ *
+ * @param string $binary_string The input string
+ *
+ * @internal
+ * @return int The number of bytes
+ */
+ function _strlen($binary_string) {
+ if (function_exists('mb_strlen')) {
+ return mb_strlen($binary_string, '8bit');
+ }
+ return strlen($binary_string);
+ }
+
+ /**
+ * Get a substring based on byte limits
+ *
+ * @see _strlen()
+ *
+ * @param string $binary_string The input string
+ * @param int $start
+ * @param int $length
+ *
+ * @internal
+ * @return string The substring
+ */
+ function _substr($binary_string, $start, $length) {
+ if (function_exists('mb_substr')) {
+ return mb_substr($binary_string, $start, $length, '8bit');
+ }
+ return substr($binary_string, $start, $length);
+ }
+
+ /**
+ * Check if current PHP version is compatible with the library
+ *
+ * @return boolean the check result
+ */
+ function check() {
+ static $pass = NULL;
+
+ if (is_null($pass)) {
+ if (function_exists('crypt')) {
+ $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
+ $test = crypt("password", $hash);
+ $pass = $test == $hash;
+ } else {
+ $pass = false;
+ }
+ }
+ return $pass;
+ }
+
+ }
+}
diff --git a/includes/utf8.inc.php b/includes/utf8.inc.php
new file mode 100644
index 0000000..f950721
--- /dev/null
+++ b/includes/utf8.inc.php
@@ -0,0 +1,118 @@
+<?php
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+require_once(dirname(__DIR__) . '/plugins/dokuwiki/inc/utf8.php');
+
+// a-z A-Z . _ -, extended latin chars, Cyrillic and Greek
+global $UTF8_ALPHA_CHARS;
+$UTF8_ALPHA_CHARS = array(
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
+ 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
+ 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
+ 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x2e, 0x2d, 0x5f, 0x20, 0x00c1, 0x00e1, 0x0106, 0x0107,
+ 0x00c9, 0x00e9, 0x00cd, 0x00ed, 0x0139, 0x013a, 0x0143, 0x0144, 0x00d3,
+ 0x00f3, 0x0154, 0x0155, 0x015a, 0x015b, 0x00da, 0x00fa, 0x00dd, 0x00fd,
+ 0x0179, 0x017a, 0x010f, 0x013d, 0x013e, 0x0165, 0x0102, 0x0103, 0x011e,
+ 0x011f, 0x016c, 0x016d, 0x010c, 0x010d, 0x010e, 0x011a, 0x011b, 0x0147,
+ 0x0148, 0x0158, 0x0159, 0x0160, 0x0161, 0x0164, 0x017d, 0x017e, 0x00c7,
+ 0x00e7, 0x0122, 0x0123, 0x0136, 0x0137, 0x013b, 0x013c, 0x0145, 0x0146,
+ 0x0156, 0x0157, 0x015e, 0x015f, 0x0162, 0x0163, 0x00c2, 0x00e2, 0x0108,
+ 0x0109, 0x00ca, 0x00ea, 0x011c, 0x011d, 0x0124, 0x0125, 0x00ce, 0x00ee,
+ 0x0134, 0x0135, 0x00d4, 0x00f4, 0x015c, 0x015d, 0x00db, 0x00fb, 0x0174,
+ 0x0175, 0x0176, 0x0177, 0x00c4, 0x00e4, 0x00cb, 0x00eb, 0x00cf, 0x00ef,
+ 0x00d6, 0x00f6, 0x00dc, 0x00fc, 0x0178, 0x00ff, 0x010a, 0x010b, 0x0116,
+ 0x0117, 0x0120, 0x0121, 0x0130, 0x0131, 0x017b, 0x017c, 0x0150, 0x0151,
+ 0x0170, 0x0171, 0x00c0, 0x00e0, 0x00c8, 0x00e8, 0x00cc, 0x00ec, 0x00d2,
+ 0x00f2, 0x00d9, 0x00f9, 0x01a0, 0x01a1, 0x01af, 0x01b0, 0x0100, 0x0101,
+ 0x0112, 0x0113, 0x012a, 0x012b, 0x014c, 0x014d, 0x016a, 0x016b, 0x0104,
+ 0x0105, 0x0118, 0x0119, 0x012e, 0x012f, 0x0172, 0x0173, 0x00c5, 0x00e5,
+ 0x016e, 0x016f, 0x0110, 0x0111, 0x0126, 0x0127, 0x0141, 0x0142, 0x00d8,
+ 0x00f8, 0x00c3, 0x00e3, 0x00d1, 0x00f1, 0x00d5, 0x00f5, 0x00c6, 0x00e6,
+ 0x0152, 0x0153, 0x00d0, 0x00f0, 0x00de, 0x00fe, 0x00df, 0x017f, 0x0391,
+ 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a,
+ 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0, 0x03a1, 0x03a3, 0x03a4,
+ 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x0386, 0x0388, 0x0389, 0x038a,
+ 0x038c, 0x038e, 0x038f, 0x03aa, 0x03ab, 0x03b1, 0x03b2, 0x03b3, 0x03b4,
+ 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd,
+ 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6,
+ 0x03c7, 0x03c8, 0x03c9, 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x03cd,
+ 0x03ce, 0x03ca, 0x03cb, 0x0390, 0x03b0, 0x0410, 0x0411, 0x0412, 0x0413,
+ 0x0414, 0x0415, 0x0401, 0x0416, 0x0417, 0x0406, 0x0419, 0x041a, 0x041b,
+ 0x041c, 0x041d, 0x041e, 0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x040e,
+ 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x042b, 0x042c, 0x042d, 0x042e,
+ 0x042f, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0451, 0x0436,
+ 0x0437, 0x0456, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x045e, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0418, 0x0429, 0x042a,
+ 0x0438, 0x0449, 0x044a, 0x0403, 0x0405, 0x0408, 0x0409, 0x040a, 0x040c,
+ 0x040f, 0x0453, 0x0455, 0x0458, 0x0459, 0x045a, 0x045c, 0x045f, 0x0402,
+ 0x040b, 0x0452, 0x045b, 0x0490, 0x0404, 0x0407, 0x0491, 0x0454, 0x0457,
+ 0x04e8, 0x04ae, 0x04e9, 0x04af,
+);
+
+function utf8_keepalphanum($string)
+{
+
+ // a-z A-Z . _ -, extended latin chars, Cyrillic and Greek
+ static $UTF8_ALPHA_CHARS = array(
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
+ 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
+ 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
+ 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x2e, 0x2d, 0x5f, 0x20, 0x00c1, 0x00e1, 0x0106, 0x0107,
+ 0x00c9, 0x00e9, 0x00cd, 0x00ed, 0x0139, 0x013a, 0x0143, 0x0144, 0x00d3,
+ 0x00f3, 0x0154, 0x0155, 0x015a, 0x015b, 0x00da, 0x00fa, 0x00dd, 0x00fd,
+ 0x0179, 0x017a, 0x010f, 0x013d, 0x013e, 0x0165, 0x0102, 0x0103, 0x011e,
+ 0x011f, 0x016c, 0x016d, 0x010c, 0x010d, 0x010e, 0x011a, 0x011b, 0x0147,
+ 0x0148, 0x0158, 0x0159, 0x0160, 0x0161, 0x0164, 0x017d, 0x017e, 0x00c7,
+ 0x00e7, 0x0122, 0x0123, 0x0136, 0x0137, 0x013b, 0x013c, 0x0145, 0x0146,
+ 0x0156, 0x0157, 0x015e, 0x015f, 0x0162, 0x0163, 0x00c2, 0x00e2, 0x0108,
+ 0x0109, 0x00ca, 0x00ea, 0x011c, 0x011d, 0x0124, 0x0125, 0x00ce, 0x00ee,
+ 0x0134, 0x0135, 0x00d4, 0x00f4, 0x015c, 0x015d, 0x00db, 0x00fb, 0x0174,
+ 0x0175, 0x0176, 0x0177, 0x00c4, 0x00e4, 0x00cb, 0x00eb, 0x00cf, 0x00ef,
+ 0x00d6, 0x00f6, 0x00dc, 0x00fc, 0x0178, 0x00ff, 0x010a, 0x010b, 0x0116,
+ 0x0117, 0x0120, 0x0121, 0x0130, 0x0131, 0x017b, 0x017c, 0x0150, 0x0151,
+ 0x0170, 0x0171, 0x00c0, 0x00e0, 0x00c8, 0x00e8, 0x00cc, 0x00ec, 0x00d2,
+ 0x00f2, 0x00d9, 0x00f9, 0x01a0, 0x01a1, 0x01af, 0x01b0, 0x0100, 0x0101,
+ 0x0112, 0x0113, 0x012a, 0x012b, 0x014c, 0x014d, 0x016a, 0x016b, 0x0104,
+ 0x0105, 0x0118, 0x0119, 0x012e, 0x012f, 0x0172, 0x0173, 0x00c5, 0x00e5,
+ 0x016e, 0x016f, 0x0110, 0x0111, 0x0126, 0x0127, 0x0141, 0x0142, 0x00d8,
+ 0x00f8, 0x00c3, 0x00e3, 0x00d1, 0x00f1, 0x00d5, 0x00f5, 0x00c6, 0x00e6,
+ 0x0152, 0x0153, 0x00d0, 0x00f0, 0x00de, 0x00fe, 0x00df, 0x017f, 0x0391,
+ 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a,
+ 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0, 0x03a1, 0x03a3, 0x03a4,
+ 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x0386, 0x0388, 0x0389, 0x038a,
+ 0x038c, 0x038e, 0x038f, 0x03aa, 0x03ab, 0x03b1, 0x03b2, 0x03b3, 0x03b4,
+ 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd,
+ 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6,
+ 0x03c7, 0x03c8, 0x03c9, 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x03cd,
+ 0x03ce, 0x03ca, 0x03cb, 0x0390, 0x03b0, 0x0410, 0x0411, 0x0412, 0x0413,
+ 0x0414, 0x0415, 0x0401, 0x0416, 0x0417, 0x0406, 0x0419, 0x041a, 0x041b,
+ 0x041c, 0x041d, 0x041e, 0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x040e,
+ 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x042b, 0x042c, 0x042d, 0x042e,
+ 0x042f, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0451, 0x0436,
+ 0x0437, 0x0456, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x045e, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0418, 0x0429, 0x042a,
+ 0x0438, 0x0449, 0x044a, 0x0403, 0x0405, 0x0408, 0x0409, 0x040a, 0x040c,
+ 0x040f, 0x0453, 0x0455, 0x0458, 0x0459, 0x045a, 0x045c, 0x045f, 0x0402,
+ 0x040b, 0x0452, 0x045b, 0x0490, 0x0404, 0x0407, 0x0491, 0x0454, 0x0457,
+ 0x04e8, 0x04ae, 0x04e9, 0x04af,
+ );
+ $chars = utf8_to_unicode($string);
+
+ for ($i = 0, $size = count($chars); $i < $size; ++$i)
+ {
+ if (!in_array($chars[$i], $UTF8_ALPHA_CHARS))
+ {
+ unset($chars[$i]);
+ }
+ }
+ return unicode_to_utf8($chars);
+}
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..c94f545
--- /dev/null
+++ b/index.php
@@ -0,0 +1,275 @@
+<?php
+/*
+ This is the main script that everything else is included
+ in. Mostly what it does is check the user permissions
+ to see what they have access to.
+*/
+define('IN_FS', true);
+
+require_once(dirname(__FILE__).'/header.php');
+
+// Get available do-modes
+$modes = str_replace('.php', '', array_map('basename', glob_compat(BASEDIR ."/scripts/*.php")));
+
+$do = Req::enum('do', $modes, $proj->prefs['default_entry']);
+
+if ($do == 'admin' && Req::has('switch') && Req::val('project') != '0') {
+ $do = 'pm';
+} elseif ($do == 'pm' && Req::has('switch') && Req::val('project') == '0') {
+ $do = 'admin';
+} elseif (Req::has('show') || (Req::has('switch') && $do == 'details')
+ || ($do == 'newtask' && Req::val('project') == '0')) {
+ $do = 'index';
+} elseif (Req::has('code')) {
+ $_SESSION['oauth_provider'] = 'microsoft';
+ $do = 'oauth';
+} elseif( Req::has('do') && Req::val('do') == 'tasklist') {
+ $do='index';
+}
+
+// supertask_id for add new sub-task
+$supertask_id = 0;
+if (Req::has('supertask')) {
+ $supertask_id = Req::val('supertask');
+}
+
+
+/* permission stuff */
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'), $proj);
+ $user->check_account_ok();
+ $user->save_search($do);
+} else {
+ $user = new User(0, $proj);
+}
+
+
+if (Get::val('getfile')) {
+ // If a file was requested, deliver it
+ $result = $db->query("SELECT t.project_id,
+ a.orig_name, a.file_name, a.file_type, t.*
+ FROM {attachments} a
+ INNER JOIN {tasks} t ON a.task_id = t.task_id
+ WHERE attachment_id = ?", array(Get::val('getfile')));
+ $task = $db->fetchRow($result);
+ list($proj_id, $orig_name, $file_name, $file_type) = $task;
+
+ // Check if file exists, and user permission to access it!
+ if (!is_file(BASEDIR . "/attachments/$file_name")) {
+ header('HTTP/1.1 410 Gone');
+ echo 'File does not exist anymore.';
+ exit();
+ }
+
+ if($user->can_view_task($task)){
+ $path = BASEDIR . "/attachments/$file_name";
+ $csp->emit(); # only default-src 'none'
+
+ header("Content-type: $file_type");
+ # image view/download difference
+ if(isset($_GET['dl'])){
+ header('Content-Disposition: attachment; filename="'.$orig_name.'"');
+ }else{
+ header('Content-Disposition: filename="'.$orig_name.'"');
+ }
+
+ header('Content-transfer-encoding: binary');
+ header('Content-length: ' . filesize($path));
+
+ readfile($path);
+ exit();
+ }else{
+ Flyspray::show_error(1);
+ }
+ exit;
+}
+
+// Load translations
+load_translations();
+
+/*******************************************************************************/
+/* Here begins the deep flyspray : html rendering */
+/*******************************************************************************/
+# no cache headers are now in header.php!
+
+// see http://www.w3.org/TR/html401/present/styles.html#h-14.2.1
+header('Content-Style-Type: text/css');
+header('Content-type: text/html; charset=utf-8');
+
+$csp->add('img-src', "'self'");
+# a bit unsure if * is ok (data: http: https:)
+#$csp->add('img-src', "*");
+$csp->add('font-src', "'self'");
+$csp->add('style-src', "'self'");
+$csp->add('style-src', "'unsafe-inline'");
+$csp->add('script-src', "'self'");
+$csp->add('script-src', "'unsafe-inline'");
+$csp->add('connect-src', "'self'");
+
+if(isset($conf['general']['syntax_plugin']) && $conf['general']['syntax_plugin']=='dokuwiki'){
+ # unsafe-eval for tabs.js :-/ (can be replaced by a css only solution)
+ $csp->add('script-src', "'unsafe-eval'");
+} else{
+ # unsafe-eval for tabs.js and flyspray's version of ckeditor :-/
+ $csp->add('script-src', "'unsafe-eval'");
+}
+
+# maybe a future 'beforehttpheader' event position for extension/plugins to add their own exceptions/modifications
+if(isset($fs->prefs['gravatars']) && $fs->prefs['gravatars'] == 1){
+ $csp->add('img-src', 'www.gravatar.com');
+}
+
+# example calculation ..
+if($user->isAnon()){
+ $needcaptcha=1;
+}
+if(isset($needcaptcha) && $needcaptcha && isset($fs->prefs['captcha_recaptcha']) && $fs->prefs['captcha_recaptcha']==1){
+ $csp->add('script-src', 'https://www.google.com/recaptcha/');
+ $csp->add('script-src', 'https://www.gstatic.com/recaptcha/');
+ $csp->add('frame-src', 'https://www.google.com/recaptcha/');
+ $csp->add('style-src', "'unsafe-inline'"); # currently redundant, but handled ok by ContentSecurityPolicy::get() method.
+}
+
+$csp->emit();
+#echo $csp->get(); # debug
+#print_r($csp); # debug
+
+if ($conf['general']['output_buffering'] == 'gzip' && extension_loaded('zlib'))
+{
+ // Start Output Buffering and gzip encoding if setting is present.
+ ob_start('ob_gzhandler');
+} else {
+ ob_start();
+}
+
+$page = new FSTpl();
+
+// make sure people are not attempting to manually fiddle with projects they are not allowed to play with
+if (Req::has('project') && Req::val('project') != 0 && !$user->can_select_project(Req::val('project'))) {
+ Flyspray::show_error( L('nopermission') );
+ Flyspray::redirect($baseurl);
+ exit;
+}
+
+if ($show_task = Get::val('show_task')) {
+ // If someone used the 'show task' form, redirect them
+ if (is_numeric($show_task)) {
+ Flyspray::redirect( createURL('details', $show_task) );
+ } else {
+ Flyspray::redirect( $baseurl . '?string=' . $show_task);
+ }
+}
+
+if (Flyspray::requestDuplicated()) {
+ // Check that this page isn't being submitted twice
+ Flyspray::show_error(3);
+}
+
+# handle all forms request that modify data
+
+if (Req::has('action')) {
+ # enforcing if the form sent the correct anti csrf token
+ # only allow token by post
+ if( !Post::has('csrftoken') ){
+ die('missingtoken');
+ }elseif( Post::val('csrftoken')==$_SESSION['csrftoken']){
+ require_once(BASEDIR . '/includes/modify.inc.php');
+ }else{
+ die('wrongtoken');
+ }
+}
+
+# start collecting infos for the answer page
+
+if ($proj->id && $user->perms('manage_project')) {
+ // Find out if there are any PM requests wanting attention
+ $sql = $db->query(
+ 'SELECT COUNT(*) FROM {admin_requests} WHERE project_id = ? AND resolved_by = 0',
+ array($proj->id));
+ list($count) = $db->fetchRow($sql);
+
+ $page->assign('pm_pendingreq_num', $count);
+}
+if ($user->perms('is_admin')) {
+ $sql = $db->query(
+ 'SELECT COUNT(*) FROM {admin_requests} WHERE request_type = 3 AND project_id = 0 AND resolved_by = 0');
+ list($count) = $db->fetchRow($sql);
+ $page->assign('admin_pendingreq_num', $count);
+}
+
+# a bit hacky: First 3 MUST be project_id, project_title, project_is_active in this order!
+# This first 3 indexes are used by tpl_options currently..
+# removed upper(project_title) for sorting, should be handled by database collation (utf8_general_ci)
+$sql = $db->query('
+ SELECT project_id, project_title, project_is_active, others_view, default_entry
+ FROM {projects}
+ ORDER BY project_is_active DESC, project_title'
+);
+
+# new: project_id as index for easier access, needs testing and maybe simplification
+# similiar situation also includes/class.flyspray.php function listProjects()
+$sres=$db->fetchAllArray($sql);
+foreach($sres as $p){
+ $prs[$p['project_id']]=$p;
+}
+$fs->projects = array_filter($prs, array($user, 'can_select_project'));
+
+
+// Get e-mail addresses of the admins
+if ($user->isAnon() && !$fs->prefs['user_notify']) {
+ $sql = $db->query('SELECT email_address
+ FROM {users} u
+ LEFT JOIN {users_in_groups} g ON u.user_id = g.user_id
+ WHERE g.group_id = 1');
+ $page->assign('admin_emails', array_map(function($x) { return str_replace("@", "#", $x); }, $db->fetchCol($sql)));
+}
+
+// title tag
+if( $user->can_select_project($proj->id)){
+ $page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title']);
+} else{
+ $page->setTitle($fs->prefs['page_title']);
+}
+
+$page->assign('do', $do);
+$page->assign('supertask_id', $supertask_id);
+
+$page->pushTpl('header.tpl');
+
+if (!defined('NO_DO')) {
+ require_once(BASEDIR . "/scripts/$do.php");
+} else{
+ # not nicest solution, NO_DO currently only used on register actions
+ $page->pushTpl('register.ok.tpl');
+}
+
+# 2016-07-29 peterdd: The following 2 optional install wide template assignments will make upgrading a Flyspray installation which needs to be integrated into an existing website a nobrainer in future.
+# Because the 2 variables are loaded from flyspray database, there is no need to change the default CleanFS template files and your own CSS style modifications is kept in your custom_*.css
+# You can keep stuff like a custom topbar that contains links to a sitewide wiki or CMS or whatever by storing it in flysprays database prefs table.
+# Project specific stuff like optional linking to a wiki for a project maybe be handled similiar by the project database table in future.
+
+# Open questions: Should we treat that as dokuwiki content (if dokuwiki is the 'syntax_plugin')?
+# Or even eval it as php-code, which can be dangerous, but gives the most freedom and creativity.
+# (For installs where all admin users are real admins of the hosting area, so they know what they are doing. Not intended for SaaS.)?
+if(isset($fs->prefs['general_integration'])){
+ # adds within the body before the footer div, but could appear as a whole or parts anywhere on the page by due custom CSS styling, for example as top linkbar.
+ $page->assign('general_integration', $fs->prefs['general_integration']);
+}
+if(isset($fs->prefs['footer_integration'])){
+ # goes within the footer div, but still could appear as a whole or parts anywhere on the page by due custom CSS styling, for example as top linkbar.
+ $page->assign('footer_integration', $fs->prefs['footer_integration']);
+}
+# 2016-07-29 end
+
+$page->pushTpl('footer.tpl');
+$page->setTheme($proj->prefs['theme_style']);
+$page->render();
+
+if(isset($_SESSION)) {
+// remove dupe data on error, since no submission happened
+ if (isset($_SESSION['ERROR']) && isset($_SESSION['requests_hash'])) {
+ $currentrequest = md5(serialize($_POST));
+ unset($_SESSION['requests_hash'][$currentrequest]);
+ }
+ unset($_SESSION['ERROR'], $_SESSION['ERRORS'], $_SESSION['SUCCESS']);
+}
diff --git a/js/callbacks/checkrelated.php b/js/callbacks/checkrelated.php
new file mode 100644
index 0000000..3ce3ee8
--- /dev/null
+++ b/js/callbacks/checkrelated.php
@@ -0,0 +1,20 @@
+<?php
+/*
+ Checks if a related tasks belongs to a different project.
+*/
+
+define('IN_FS', true);
+
+require_once('../../header.php');
+
+$sql = $db->query('SELECT project_id
+ FROM {tasks}
+ WHERE task_id = ?',
+ array(Get::val('related_task')));
+
+$relatedproject = $db->fetchOne($sql);
+
+if (Get::val('project') == $relatedproject || !$relatedproject) {
+ echo 'ok';
+}
+?>
diff --git a/js/callbacks/checksave.php b/js/callbacks/checksave.php
new file mode 100644
index 0000000..a5fd1a4
--- /dev/null
+++ b/js/callbacks/checksave.php
@@ -0,0 +1,16 @@
+<?php
+/*
+ Checks if a task can be saved without danger or not.
+*/
+
+define('IN_FS', true);
+
+require_once('../../header.php');
+
+$res = $db->query('SELECT last_edited_time FROM {tasks} WHERE task_id = ?', array(Get::val('task_id')));
+$last_edit = $db->fetchOne($res);
+
+if (Get::val('time') >= $last_edit) {
+ echo 'ok';
+}
+?>
diff --git a/js/callbacks/deletesearches.php b/js/callbacks/deletesearches.php
new file mode 100644
index 0000000..2ff9e3b
--- /dev/null
+++ b/js/callbacks/deletesearches.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ This script is the AJAX callback that deletes a user's saved search
+*/
+
+define('IN_FS', true);
+
+require_once('../../header.php');
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+
+ if( !Post::has('csrftoken') ){
+ http_response_code(428); # 'Precondition Required'
+ die('missingtoken');
+ }elseif( Post::val('csrftoken')==$_SESSION['csrftoken']){
+ # empty
+ }else{
+ http_response_code(412); # 'Precondition Failed'
+ die('wrongtoken');
+ }
+
+ if (!$user->isAnon()) {
+ $db->query('DELETE FROM {searches} WHERE id = ? AND user_id = ?', array(Post::num('id'), $user->id));
+ echo $db->affectedRows();
+ }
+}
+
+?>
diff --git a/js/callbacks/gethistory.php b/js/callbacks/gethistory.php
new file mode 100644
index 0000000..617b992
--- /dev/null
+++ b/js/callbacks/gethistory.php
@@ -0,0 +1,69 @@
+<?php
+/*
+ This script gets the history of a task and
+ returns it for HTML display in a page.
+*/
+
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+require_once('../../header.php');
+require_once('../../includes/events.inc.php');
+
+$csp->emit();
+
+if( !isset($_GET['task_id']) or !is_numeric($_GET['task_id'])){
+ die();
+} else {
+ $task_id = Get::num('task_id');
+}
+
+# recalculate $proj for permission check
+$result = $db->query('SELECT project_id FROM {tasks} WHERE task_id = ?', array($task_id));
+$project_id = $db->fetchOne($result);
+if (!$project_id) {
+ die();
+}
+$proj = new Project($project_id);
+
+// Initialise user
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+load_translations();
+
+# set project of task asked for and then check permissions based on that
+if ( !($task = Flyspray::getTaskDetails($task_id)) ) {
+ die();
+}
+
+# also check the calculated view task permission in addition to view_history permission
+if (!$user->can_view_task($task) or !$user->perms('view_history')) {
+ die();
+}
+
+if ($details = Get::num('details')) {
+ $details = " AND h.history_id = $details";
+} else {
+ $details = null;
+}
+
+$sql = get_events($task_id, $details);
+$histories = $db->fetchAllArray($sql);
+
+$page = new FSTpl;
+$page->setTheme($proj->prefs['theme_style']);
+$page->uses('histories', 'details');
+if ($details) {
+ event_description($histories[0]); // modifies global variables
+ $page->assign('details_previous', $GLOBALS['details_previous']);
+ $page->assign('details_new', $GLOBALS['details_new']);
+}
+$page->display('details.tabs.history.callback.tpl');
+
+?>
diff --git a/js/callbacks/getpreview.php b/js/callbacks/getpreview.php
new file mode 100644
index 0000000..94d973a
--- /dev/null
+++ b/js/callbacks/getpreview.php
@@ -0,0 +1,21 @@
+<?php
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+$webdir = dirname(dirname(dirname(htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'utf-8'))));
+require_once('../../header.php');
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+# TODO csrftoken checking
+
+
+echo TextFormatter::render(Post::val('text'));
+
+?>
diff --git a/js/callbacks/getsearches.php b/js/callbacks/getsearches.php
new file mode 100644
index 0000000..215d2d8
--- /dev/null
+++ b/js/callbacks/getsearches.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ This script gets the searches of current user and
+ returns it for HTML display in a page.
+*/
+
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+require_once('../../header.php');
+
+// Initialise user
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+// don't allow anonymous users to access this page at all
+if ($user->isAnon()) {
+ die();
+}
+
+$user->save_search(); # currently used for loading user searches from db into user object ...
+$page = new FSTpl;
+$page->setTheme($proj->prefs['theme_style']);
+$page->display('links.searches.tpl');
+?>
diff --git a/js/callbacks/quickedit.php b/js/callbacks/quickedit.php
new file mode 100644
index 0000000..fda26ea
--- /dev/null
+++ b/js/callbacks/quickedit.php
@@ -0,0 +1,170 @@
+<?php
+
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+require_once('../../header.php');
+global $proj, $fs;
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+// don't allow anonymous users to access this page at all
+if ($user->isAnon()) {
+ die();
+}
+load_translations();
+
+if( !Post::has('csrftoken') ){
+ http_response_code(428); # 'Precondition Required'
+ die('missingtoken');
+} elseif( Post::val('csrftoken')==$_SESSION['csrftoken']){
+ # ok
+} else{
+ http_response_code(412); # 'Precondition Failed'
+ die('wrongtoken');
+}
+
+$task = Flyspray::getTaskDetails(Post::val('task_id'));
+if (!$user->can_edit_task($task)){
+ http_response_code(403); # 'Forbidden'
+ die(L('nopermission'));
+}
+
+# check field for update against allowed dbfields for quickedit.
+# maybe FUTURE: add (dynamic read from database) allowed CUSTOM FIELDS checks for the project and user
+# (if there is urgent request for implementing custom fields into Flyspray
+# and using of tag-feature isn't enough to accomplish - like numbers/dates/timestamps as custom fields)
+$allowedFields=array(
+ 'due_date',
+ 'item_status',
+ 'percent_complete',
+ 'task_type',
+ 'product_category',
+ 'operating_system',
+ 'task_severity',
+ 'task_priority',
+ 'product_version',
+ 'closedby_version'
+);
+if ($proj->prefs['use_effort_tracking'] && $user->perms('track_effort')){
+ $allowedFields[]='estimated_effort';
+}
+
+if (!in_array(Post::val('name'), $allowedFields)){
+ http_response_code(403);
+ die(L('invalidfield'));
+}
+
+$value = Post::val('value');
+
+# check if user is not sending manipulated invalid values
+switch(Post::val('name')){
+ case 'due_date':
+ $value = Flyspray::strtotime(Post::val('value'));
+ $value = intval($value);
+ break;
+
+ case 'estimated_effort':
+ $value = effort::editStringToSeconds(Post::val('value'), $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ $value = intval($value);
+ break;
+
+ case 'task_severity':
+ if(!preg_match("/^[1-5]$/", $value)){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'task_priority':
+ if(!preg_match("/^[1-6]$/", $value)){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'percent_complete':
+ if(!is_numeric($value) || $value<0 || $value>100){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'item_status':
+ $res=$db->query('SELECT * FROM {list_status} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND status_id=?', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'task_type':
+ $res=$db->query('SELECT * FROM {list_tasktype} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND tasktype_id=?', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'operating_system':
+ $res=$db->query('SELECT * FROM {list_os} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND os_id=?', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'product_category':
+ $res=$db->query('SELECT * FROM {list_category} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND category_id=?', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+
+ case 'product_version':
+ $res=$db->query('SELECT * FROM {list_version} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND version_id=? AND version_tense=2', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+ case 'closedby_version':
+ $res=$db->query('SELECT * FROM {list_version} WHERE (project_id=0 OR project_id=?) AND show_in_list=1 AND version_id=? AND version_tense=3', array($task['project_id'], $value) );
+ if($db->countRows($res)<1){
+ http_response_code(403);
+ die(L('invalidvalue'));
+ }
+ break;
+ default:
+ http_response_code(403);
+ die(L('invalidfield'));
+ break;
+}
+
+$oldvalue = $task[Post::val('name')];
+
+$time=time();
+$sql = $db->query("UPDATE {tasks} SET ".Post::val('name')." = ?,last_edited_time = ? WHERE task_id = ?", array($value, $time, Post::val('task_id')));
+
+# load $proj again of task with correct project_id for getting active notification types in notification class
+$proj= new Project($task['project_id']);
+
+// Log the changed field in task history
+Flyspray::logEvent($task['task_id'], 3, $value, $oldvalue, Post::val('name'), $time);
+
+// Get the details of the task we just updated to generate the changed-task message
+$new_details_full = Flyspray::getTaskDetails($task['task_id']);
+$changes = Flyspray::compare_tasks($task, $new_details_full);
+if (count($changes) > 0) {
+ $notify = new Notifications;
+ $notify->create(NOTIFY_TASK_CHANGED, $task['task_id'], $changes, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+}
+
+?>
diff --git a/js/callbacks/savesearches.php b/js/callbacks/savesearches.php
new file mode 100644
index 0000000..e656a0a
--- /dev/null
+++ b/js/callbacks/savesearches.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * This script is the AJAX callback that saves a user's search
+ */
+
+define('IN_FS', true);
+
+require_once('../../header.php');
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+
+ if( !Post::has('csrftoken') ){
+ http_response_code(428); # 'Precondition Required'
+ die('missingtoken');
+ }elseif( Post::val('csrftoken')==$_SESSION['csrftoken']){
+ # empty
+ }else{
+ http_response_code(412); # 'Precondition Failed'
+ die('wrongtoken');
+ }
+
+ $user->save_search();
+}
+
+?>
diff --git a/js/callbacks/searchnames.php b/js/callbacks/searchnames.php
new file mode 100644
index 0000000..f696955
--- /dev/null
+++ b/js/callbacks/searchnames.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * This script is the AJAX callback that performs a search
+ * for users, and returns true if the user_name is not given.
+ */
+
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+require_once('../../header.php');
+
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+if ($user->isAnon()) {
+ # at least allow for guests when user registration is enabled, fix FS#2528
+ if( !($user->can_register() or $user->can_self_register()) ){
+ die();
+ }
+}
+
+if (Req::has('name')) {
+ $searchterm = strtolower(Req::val('name'));
+} else {
+ die();
+}
+
+// Get the list of users from the global groups above
+$get_users = $db->query('
+ SELECT count(u.user_name) AS anz_u_user, count(r.user_name) AS anz_r_user
+ FROM {users} u
+ LEFT JOIN {registrations} r ON u.user_name = r.user_name
+ WHERE LOWER(u.user_name) = ? OR LOWER(r.user_name) = ?',
+ array($searchterm, $searchterm)
+);
+
+load_translations();
+
+while ($row = $db->fetchRow($get_users)){
+ if ($row['anz_u_user'] > '0' || $row['anz_r_user'] > '0') {
+ $html = 'false|' . eL('usernametaken');
+ } else {
+ $html = 'true';
+ }
+}
+
+echo $html;
+?>
diff --git a/js/callbacks/searchtask.php b/js/callbacks/searchtask.php
new file mode 100644
index 0000000..47b0241
--- /dev/null
+++ b/js/callbacks/searchtask.php
@@ -0,0 +1,43 @@
+<?php
+define('IN_FS', true);
+require_once('../../header.php');
+
+
+// Require inputs
+if(!Post::has('detail') || !Post::has('summary') || !Post::has('project_id'))
+{
+ return;
+}
+
+
+// Load user profile
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')){
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+// Require right to open a task on current project
+if(!$user->can_open_task($proj)){
+ return;
+}
+
+
+// Prepare SQL params
+$params = array(
+ 'project_id' => Post::num('project_id'),
+ 'summary' => "%" . trim(Post::val('summary')) . "%",
+ 'details' => "%" . trim(Post::val('detail')) . "%"
+);
+
+$sql = $db->query('SELECT count(*)
+ FROM {tasks} t
+ WHERE t.project_id = ?
+ AND t.item_summary like ?
+ AND t.detailed_desc like ?',
+ $params);
+$sametask = $db->fetchOne($sql);
+echo $sametask;
+
+?>
diff --git a/js/callbacks/testemail.php b/js/callbacks/testemail.php
new file mode 100644
index 0000000..788a12f
--- /dev/null
+++ b/js/callbacks/testemail.php
@@ -0,0 +1,44 @@
+<?php
+
+define('IN_FS', true);
+
+header('Content-type: text/html; charset=utf-8');
+
+require_once('../../header.php');
+global $proj, $fs;
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+// don't allow anonymous users to access this page at all
+if ($user->isAnon()) {
+ die(L('nopermission'));
+}
+load_translations();
+
+if( !Post::has('csrftoken') ){
+ http_response_code(428); # 'Precondition Required'
+ die('missingtoken');
+}elseif( Post::val('csrftoken')==$_SESSION['csrftoken']){
+ # empty
+}else{
+ http_response_code(412); # 'Precondition Failed'
+ die('wrongtoken');
+}
+if (!$user->perms('is_admin')){
+ http_response_code(403); # 'Forbidden'
+ die(L('nopermission'));
+}
+
+$notify = new Notifications;
+$result=$notify->sendEmail($user->infos['email_address'],'test','testcontent',1);
+
+if($result !=1){
+ http_response_code(406); # 'Not Acceptable'
+}
+echo 'ok';
+?>
diff --git a/js/callbacks/usersearch.php b/js/callbacks/usersearch.php
new file mode 100644
index 0000000..a17833d
--- /dev/null
+++ b/js/callbacks/usersearch.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ This script is the AJAX callback that performs a search
+ for users, and returns them in an ordered list.
+*/
+
+define('IN_FS', true);
+header('Content-type: text/html; charset=utf-8');
+require_once('../../header.php');
+
+if (Cookie::has('flyspray_userid') && Cookie::has('flyspray_passhash')) {
+ $user = new User(Cookie::val('flyspray_userid'));
+ $user->check_account_ok();
+} else {
+ $user = new User(0, $proj);
+}
+
+// don't allow anonymous users to access this page at all
+if ($user->isAnon()) {
+ die();
+}
+$first = reset($_POST);
+if (is_array($first)) {
+ $first = reset($first);
+}
+$searchterm = '%' . $first . '%';
+
+// Get the list of users from the global groups above
+$get_users = $db->query('SELECT real_name, user_name, profile_image
+ FROM {users} u
+ WHERE u.user_name LIKE ? OR u.real_name LIKE ?',
+ array($searchterm, $searchterm), 20);
+
+$html = '<ul class="autocomplete">';
+
+while ($row = $db->fetchRow($get_users)) {
+ $data = array_map(array('Filters','noXSS'), $row);
+ $html .= '<li title="' . $data['real_name'] . '">'.($data['profile_image']!='' ? '<img src="avatars/'.$data['profile_image'].'" />' : '<span class="noavatar"></span>' ). $data['user_name'] . '<span class="informal"> ' . $data['real_name'] . '</span></li>';
+}
+
+$html .= '</ul>';
+
+echo $html;
+
+?>
diff --git a/js/ckeditor/CHANGES.md b/js/ckeditor/CHANGES.md
new file mode 100644
index 0000000..8b8ff47
--- /dev/null
+++ b/js/ckeditor/CHANGES.md
@@ -0,0 +1,720 @@
+CKEditor 4 Changelog
+====================
+
+## CKEditor 4.4.7
+
+Fixed Issues:
+
+* [#12825](http://dev.ckeditor.com/ticket/12825): Fixed: Preventing the [Table Resize](http://ckeditor.com/addon/tableresize) plugin from operating on elements outside the editor. Thanks to [Paul Martin](https://github.com/Paul-Martin)!
+* [#12157](http://dev.ckeditor.com/ticket/12157): Fixed: Lost text formatting on pressing *Tab* when the [`config.tabSpaces`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-tabSpaces) configuration option value was greater than zero.
+* [#12777](http://dev.ckeditor.com/ticket/12777): Fixed: The `table-layout` CSS property should be reset by skins. Thanks to [vita10gy](https://github.com/vita10gy)!
+* [#12812](http://dev.ckeditor.com/ticket/12812): Fixed: An uncaught security exception is thrown when [Line Utilities](http://ckeditor.com/addon/lineutils) are used in an inline editor loaded in a cross-domain `iframe`. Thanks to [Vitaliy Zurian](https://github.com/thecatontheflat)!
+* [#12735](http://dev.ckeditor.com/ticket/12735): Fixed: [`config.fillEmptyBlocks`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-fillEmptyBlocks) should only apply when outputting data.
+* [#10032](http://dev.ckeditor.com/ticket/10032): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) filter is executed for every paste after using the button.
+* [#12597](http://dev.ckeditor.com/ticket/12597): [Blink/Webkit] Fixed: Multi-byte Japanese characters entry not working properly after *Shift+Enter*.
+* [#12387](http://dev.ckeditor.com/ticket/12387): Fixed: An error is thrown if a skin does not have the [`chameleon`](http://docs.ckeditor.com/#!/api/CKEDITOR.skin-method-chameleon) property defined and [`config.uiColor`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-uiColor) is defined.
+* [#12747](http://dev.ckeditor.com/ticket/12747): [IE8-10] Fixed: Opening a drop-down for a specific selection when the editor is maximized results in incorrect drop-down panel position.
+* [#12850](http://dev.ckeditor.com/ticket/12850): [IEQM] Fixed: An error is thrown after focusing the editor.
+
+## CKEditor 4.4.6
+
+**Security Updates:**
+
+* Fixed XSS vulnerability in the HTML parser reported by [Maco Cortes](https://www.facebook.com/Maaacoooo).
+
+ Issue summary: It was possible to execute XSS inside CKEditor after persuading the victim to: (i) switch CKEditor to source mode, then (ii) paste a specially crafted HTML code, prepared by the attacker, into the opened CKEditor source area, and (iii) switch back to WYSIWYG mode.
+
+**An upgrade is highly recommended!**
+
+New Features:
+
+* [#12501](http://dev.ckeditor.com/ticket/12501): Allowed dashes in element names in the [string format of allowed content rules](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules-section-string-format).
+* [#12550](http://dev.ckeditor.com/ticket/12550): Added the `<main>` element to the [`CKEDITOR.dtd`](http://docs.ckeditor.com/#!/api/CKEDITOR.dtd).
+
+Fixed Issues:
+
+* [#12506](http://dev.ckeditor.com/ticket/12506): [Safari] Fixed: Cannot paste into inline editor if the page has `user-select: none` style. Thanks to [shaohua](https://github.com/shaohua)!
+* [#12683](http://dev.ckeditor.com/ticket/12683): Fixed: [Filter](http://docs.ckeditor.com/#!/guide/dev_acf) fails to remove custom tags. Thanks to [timselier](https://github.com/timselier)!
+* [#12489](http://dev.ckeditor.com/ticket/12489) and [#12491](http://dev.ckeditor.com/ticket/12491): Fixed: Various issues related to restoring the selection after performing operations on filler character. See the [fixed cases](http://dev.ckeditor.com/ticket/12491#comment:4).
+* [#12621](http://dev.ckeditor.com/ticket/12621): Fixed: Cannot remove inline styles (bold, italic, etc.) in empty lines.
+* [#12630](http://dev.ckeditor.com/ticket/12630): [Chrome] Fixed: Selection is placed outside the paragraph when the [New Page](http://ckeditor.com/addon/newpage) button is clicked. This patch significantly simplified the way how the initial selection (a selection after the content of the editable is overwritten) is being fixed. That might have fixed many related scenarios in all browsers.
+* [#11647](http://dev.ckeditor.com/ticket/11647): Fixed: The [`editor.blur`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-blur) event is not fired on first blur after initializing the inline editor on an already focused element.
+* [#12601](http://dev.ckeditor.com/ticket/12601): Fixed: [Strikethrough](http://ckeditor.com/addon/basicstyles) button tooltip spelling.
+* [#12546](http://dev.ckeditor.com/ticket/12546): Fixed: The Preview tab in the [Document Properties](http://ckeditor.com/addon/docprops) dialog window is always disabled.
+* [#12300](http://dev.ckeditor.com/ticket/12300): Fixed: The [`editor.change`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-change) event fired on first navigation key press after typing.
+* [#12141](http://dev.ckeditor.com/ticket/12141): Fixed: List items are lost when indenting a list item with content wrapped with a block element.
+* [#12515](http://dev.ckeditor.com/ticket/12515): Fixed: Cursor is in the wrong position when undoing after adding an image and typing some text.
+* [#12484](http://dev.ckeditor.com/ticket/12484): [Blink/Webkit] Fixed: DOM is changed outside the editor area in a certain case.
+* [#12688](http://dev.ckeditor.com/ticket/12688): Improved the tests of the [styles system](http://docs.ckeditor.com/#!/api/CKEDITOR.style) and fixed two minor issues.
+* [#12403](http://dev.ckeditor.com/ticket/12403): Fixed: Changing the [font](http://ckeditor.com/addon/font) style should not lead to nesting it in the previous style element.
+* [#12609](http://dev.ckeditor.com/ticket/12609): Fixed: Incorrect `config.magicline_putEverywhere` name used for a [Magic Line](http://ckeditor.com/addon/magicline) all-encompassing [`config.magicline_everywhere`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-magicline_everywhere) configuration option.
+
+
+## CKEditor 4.4.5
+
+New Features:
+
+* [#12279](http://dev.ckeditor.com/ticket/12279): Added a possibility to pass a custom evaluator to [`node.getAscendant()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.node-method-getAscendant).
+
+Fixed Issues:
+
+* [#12423](http://dev.ckeditor.com/ticket/12423): [Safari7.1+] Fixed: *Enter* key moved cursor to a strange position.
+* [#12381](http://dev.ckeditor.com/ticket/12381): [iOS] Fixed: Selection issue. Thanks to [Remiremi](https://github.com/Remiremi)!
+* [#10804](http://dev.ckeditor.com/ticket/10804): Fixed: `CKEDITOR_GETURL` is not used with some plugins where it should be used. Thanks to [Thomas Andraschko](https://github.com/tandraschko)!
+* [#9137](http://dev.ckeditor.com/ticket/9137): Fixed: The `<base>` tag is not created when `<head>` has an attribute. Thanks to [naoki.fujikawa](https://github.com/naoki-fujikawa)!
+* [#12377](http://dev.ckeditor.com/ticket/12377): Fixed: Errors thrown in the [Image](http://ckeditor.com/addon/image) plugin when removing preview from the dialog window definition. Thanks to [Axinet](https://github.com/Axinet)!
+* [#12162](http://dev.ckeditor.com/ticket/12162): Fixed: Auto paragraphing and *Enter* key in nested editables.
+* [#12315](http://dev.ckeditor.com/ticket/12315): Fixed: Marked [`config.autoParagraph`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-autoParagraph) as deprecated.
+* [#12113](http://dev.ckeditor.com/ticket/12113): Fixed: A [code snippet](http://ckeditor.com/addon/codesnippet) should be presented in the [elements path](http://ckeditor.com/addon/elementspath) as "code snippet" (translatable).
+* [#12311](http://dev.ckeditor.com/ticket/12311): Fixed: [Remove Format](http://ckeditor.com/addon/removeformat) should also remove `<cite>` elements.
+* [#12261](http://dev.ckeditor.com/ticket/12261): Fixed: Filter has to be destroyed and removed from [`CKEDITOR.filter.instances`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter-static-property-instances) on editor destroy.
+* [#12398](http://dev.ckeditor.com/ticket/12398): Fixed: [Maximize](http://ckeditor.com/addon/maximize) does not work on an instance without a [title](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-title).
+* [#12097](http://dev.ckeditor.com/ticket/12097): Fixed: JAWS not reading the number of options correctly in the [Text Color and Background Color](http://ckeditor.com/addon/colorbutton) button menu.
+* [#12411](http://dev.ckeditor.com/ticket/12411): Fixed: [Page Break](http://ckeditor.com/addon/pagebreak) used directly in the editable breaks the editor.
+* [#12354](http://dev.ckeditor.com/ticket/12354): Fixed: Various issues in undo manager when holding keys.
+* [#12324](http://dev.ckeditor.com/ticket/12324): [IE8] Fixed: Undo steps are not recorded when changing the caret position by clicking below the body.
+* [#12332](http://dev.ckeditor.com/ticket/12332): Fixed: Lowered DOM events listeners' priorities in undo manager in order to avoid ambiguity.
+* [#12402](http://dev.ckeditor.com/ticket/12402): [Blink] Fixed: Workaround for Blink bug with `document.title` which breaks updating title in the full HTML mode.
+* [#12338](http://dev.ckeditor.com/ticket/12338): Fixed: The CKEditor package contains unoptimized images.
+
+
+## CKEditor 4.4.4
+
+Fixed Issues:
+
+* [#12268](http://dev.ckeditor.com/ticket/12268): Cleanup of [UI Color](http://ckeditor.com/addon/uicolor) YUI styles. Thanks to [CasherWest](https://github.com/CasherWest)!
+* [#12263](http://dev.ckeditor.com/ticket/12263): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) filter does not properly normalize semicolons style text. Thanks to [Alin Purcaru](https://github.com/mesmerizero)!
+* [#12243](http://dev.ckeditor.com/ticket/12243): Fixed: Text formatting lost when pasting from Word. Thanks to [Alin Purcaru](https://github.com/mesmerizero)!
+* [#111739](http://dev.ckeditor.com/ticket/11739): Fixed: `keypress` listeners should not be used in the undo manager. A complete rewrite of keyboard handling in the undo manager was made. Numerous smaller issues were fixed, among others:
+ * [#10926](http://dev.ckeditor.com/ticket/10926): [Chrome@Android] Fixed: Typing does not record snapshots and does not fire the [`editor.change`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-change) event.
+ * [#11611](http://dev.ckeditor.com/ticket/11611): [Firefox] Fixed: The [`editor.change`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-change) event is fired when pressing Arrow keys.
+ * [#12219](http://dev.ckeditor.com/ticket/12219): [Safari] Fixed: Some modifications of the [`UndoManager.locked`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.undo.UndoManager-property-locked) property violate strict mode in the [Undo](http://ckeditor.com/addon/undo) plugin.
+* [#10916](http://dev.ckeditor.com/ticket/10916): Fixed: [Magic Line](http://ckeditor.com/addon/magicline) icon in Right-To-Left environments.
+* [#11970](http://dev.ckeditor.com/ticket/11970): [IE] Fixed: CKEditor `paste` event is not fired when pasting with *Shift+Ins*.
+* [#12111](http://dev.ckeditor.com/ticket/12111): Fixed: Linked image attributes are not read when opening the image dialog window by doubleclicking.
+* [#10030](http://dev.ckeditor.com/ticket/10030): [IE] Fixed: Prevented "Unspecified Error" thrown in various cases when IE8-9 does not allow access to `document.activeElement`.
+* [#12273](http://dev.ckeditor.com/ticket/12273): Fixed: Applying block style in a description list breaks it.
+* [#12218](http://dev.ckeditor.com/ticket/12218): Fixed: Minor syntax issue in CSS files.
+* [#12178](http://dev.ckeditor.com/ticket/12178): [Blink/WebKit] Fixed: Iterator does not return the block if the selection is located at the end of it.
+* [#12185](http://dev.ckeditor.com/ticket/12185): [IE9QM] Fixed: Error thrown when moving the mouse over focused editor's scrollbar.
+* [#12215](http://dev.ckeditor.com/ticket/12215): Fixed: Basepath resolution does not recognize semicolon as a query separator.
+* [#12135](http://dev.ckeditor.com/ticket/12135): Fixed: [Remove Format](http://ckeditor.com/addon/removeformat) does not work on widgets.
+* [#12298](http://dev.ckeditor.com/ticket/12298): [IE11] Fixed: Clicking below `<body>` in Compatibility Mode will no longer reset selection to the first line.
+* [#12204](http://dev.ckeditor.com/ticket/12204): Fixed: Editor's voice label is not affected by [`config.title`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-title).
+* [#11915](http://dev.ckeditor.com/ticket/11915): Fixed: With [SCAYT](http://ckeditor.com/addon/scayt) enabled, cursor moves to the beginning of the first highlighted, misspelled word after typing or pasting into the editor.
+* [SCAYT](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/69): Fixed: Error thrown in the console after enabling [SCAYT](http://ckeditor.com/addon/scayt) and trying to add a new image.
+
+
+Other Changes:
+
+* [#12296](http://dev.ckeditor.com/ticket/12296): Merged `benderjs-ckeditor` into the main CKEditor repository.
+
+## CKEditor 4.4.3
+
+**Security Updates:**
+
+* Fixed XSS vulnerability in the Preview plugin reported by Mario Heiderich of [Cure53](https://cure53.de/).
+
+**An upgrade is highly recommended!**
+
+New Features:
+
+* [#12164](http://dev.ckeditor.com/ticket/12164): Added the "Justify" option to the "Horizontal Alignment" drop-down in the Table Cell Properties dialog window.
+
+Fixed Issues:
+
+* [#12110](http://dev.ckeditor.com/ticket/12110): Fixed: Editor crash after deleting a table. Thanks to [Alin Purcaru](https://github.com/mesmerizero)!
+* [#11897](http://dev.ckeditor.com/ticket/11897): Fixed: *Enter* key used in an empty list item creates a new line instead of breaking the list. Thanks to [noam-si](https://github.com/noam-si)!
+* [#12140](http://dev.ckeditor.com/ticket/12140): Fixed: Double-clicking linked widgets opens two dialog windows.
+* [#12132](http://dev.ckeditor.com/ticket/12132): Fixed: Image is inserted with `width` and `height` styles even when they are not allowed.
+* [#9317](http://dev.ckeditor.com/ticket/9317): [IE] Fixed: [`config.disableObjectResizing`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-disableObjectResizing) does not work on IE. **Note**: We were not able to fix this issue on IE11+ because necessary events stopped working. See a [last resort workaround](http://dev.ckeditor.com/ticket/9317#comment:16) and make sure to [support our complaint to Microsoft](https://connect.microsoft.com/IE/feedback/details/742593/please-respect-execcommand-enableobjectresizing-in-contenteditable-elements).
+* [#9638](http://dev.ckeditor.com/ticket/9638): Fixed: There should be no information about accessibility help available under the *Alt+0* keyboard shortcut if the [Accessibility Help](http://ckeditor.com/addon/a11yhelp) plugin is not available.
+* [#8117](http://dev.ckeditor.com/ticket/8117) and [#9186](http://dev.ckeditor.com/ticket/9186): Fixed: In HTML5 `<meta>` tags should be allowed everywhere, including inside the `<body>` element.
+* [#10422](http://dev.ckeditor.com/ticket/10422): Fixed: [`config.fillEmptyBlocks`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-fillEmptyBlocks) not working properly if a function is specified.
+
+## CKEditor 4.4.2
+
+Important Notes:
+
+* The CKEditor testing environment is now publicly available. Read more about how to set up the environment and execute tests in the [CKEditor Testing Environment](http://docs.ckeditor.com/#!/guide/dev_tests) guide.
+ Please note that the [`tests/`](https://github.com/ckeditor/ckeditor-dev/tree/master/tests) directory which contains editor tests is not available in release packages. It can only be found in the development version of CKEditor on [GitHub](https://github.com/ckeditor/ckeditor-dev/).
+
+New Features:
+
+* [#11909](http://dev.ckeditor.com/ticket/11909): Introduced a parameter to prevent the [`editor.setData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setData) method from recording undo snapshots.
+
+Fixed Issues:
+
+* [#11757](http://dev.ckeditor.com/ticket/11757): Fixed: Imperfections in the [Moono](http://ckeditor.com/addon/moono) skin. Thanks to [danyaPostfactum](https://github.com/danyaPostfactum)!
+* [#10091](http://dev.ckeditor.com/ticket/10091): Blockquote should be treated like an object by the styles system. Thanks to [dan-james-deeson](https://github.com/dan-james-deeson)!
+* [#11478](http://dev.ckeditor.com/ticket/11478): Fixed: Issue with passing jQuery objects to [adapter](http://docs.ckeditor.com/#!/guide/dev_jquery) configuration.
+* [#10867](http://dev.ckeditor.com/ticket/10867): Fixed: Issue with setting encoded URI as image link.
+* [#11983](http://dev.ckeditor.com/ticket/11983): Fixed: Clicking a nested widget does not focus it. Additionally, performance of the [`widget.repository.getByElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-getByElement) method was improved.
+* [#12000](http://dev.ckeditor.com/ticket/12000): Fixed: Nested widgets should be initialized on [`editor.setData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setData) and [`nestedEditable.setData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.nestedEditable-method-setData).
+* [#12022](http://dev.ckeditor.com/ticket/12022): Fixed: Outer widget's drag handler is not created at all if it has any nested widgets inside.
+* [#11960](http://dev.ckeditor.com/ticket/11960): [Blink/WebKit] Fixed: The caret should be scrolled into view on *Backspace* and *Delete* (covers only the merging blocks case).
+* [#11306](http://dev.ckeditor.com/ticket/11306): [OSX][Blink/WebKit] Fixed: No widget entries in the context menu on widget right-click.
+* [#11957](http://dev.ckeditor.com/ticket/11957): Fixed: Alignment labels in the [Enhanced Image](http://ckeditor.com/addon/image2) dialog window are not translated.
+* [#11980](http://dev.ckeditor.com/ticket/11980): [Blink/WebKit] Fixed: `<span>` elements created when joining adjacent elements (non-collapsed selection).
+* [#12009](http://dev.ckeditor.com/ticket/12009): [Nested widgets] Integration with the [Magic Line](http://ckeditor.com/addon/magicline) plugin.
+* [#11387](http://dev.ckeditor.com/ticket/11387): Fixed: `role="radiogroup"` should be applied only to radio inputs' container.
+* [#7975](http://dev.ckeditor.com/ticket/7975): [IE8] Fixed: Errors when trying to select an empty table cell.
+* [#11947](http://dev.ckeditor.com/ticket/11947): [Firefox+IE11] Fixed: *Shift+Enter* in lists produces two line breaks.
+* [#11972](http://dev.ckeditor.com/ticket/11972): Fixed: Feature detection in the [`element.setText()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-setText) method should not trigger the layout engine.
+* [#7634](http://dev.ckeditor.com/ticket/7634): Fixed: The [Flash Dialog](http://ckeditor.com/addon/flash) plugin omits the `allowFullScreen` parameter in the editor data if set to `true`.
+* [#11910](http://dev.ckeditor.com/ticket/11910): Fixed: [Enhanced Image](http://ckeditor.com/addon/image2) does not take [`config.baseHref`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-baseHref) into account when updating image dimensions.
+* [#11753](http://dev.ckeditor.com/ticket/11753): Fixed: Wrong [`checkDirty()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-checkDirty) method value after focusing or blurring a widget.
+* [#11830](http://dev.ckeditor.com/ticket/11830): Fixed: Impossible to pass some arguments to [CKBuilder](https://github.com/ckeditor/ckbuilder) when using the `/dev/builder/build.sh` script.
+* [#11945](http://dev.ckeditor.com/ticket/11945): Fixed: [Form Elements](http://ckeditor.com/addon/forms) plugin should not change a core method.
+* [#11384](http://dev.ckeditor.com/ticket/11384): [IE9+] Fixed: `IndexSizeError` thrown when pasting into a non-empty selection anchored in one text node.
+
+## CKEditor 4.4.1
+
+New Features:
+
+* [#9661](http://dev.ckeditor.com/ticket/9661): Added the option to [configure](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-linkJavaScriptLinksAllowed) anchor tags with JavaScript code in the `href` attribute.
+
+Fixed Issues:
+
+* [#11861](http://dev.ckeditor.com/ticket/11861): [Webkit/Blink] Fixed: Span elements created while joining adjacent elements. **Note:** This patch only covers cases when *Backspace* or *Delete* is pressed on a collapsed (empty) selection. The remaining case, with a non-empty selection, will be fixed in the next release.
+* [#10714](http://dev.ckeditor.com/ticket/10714): [iOS] Fixed: Selection and drop-downs are broken if a touch event listener is used due to a [Webkit bug](https://bugs.webkit.org/show_bug.cgi?id=128924). Thanks to [Arty Gus](https://github.com/artygus)!
+* [#11911](http://dev.ckeditor.com/ticket/11911): Fixed setting the `dir` attribute for a preloaded language in [CKEDITOR.lang](http://docs.ckeditor.com/#!/api/CKEDITOR.lang). Thanks to [Akash Mohapatra](https://github.com/akashmohapatra)!
+* [#11926](http://dev.ckeditor.com/ticket/11926): Fixed: [Code Snippet](http://ckeditor.com/addon/codesnippet) does not decode HTML entities when loading code from the `<code>` element.
+* [#11223](http://dev.ckeditor.com/ticket/11223): Fixed: Issue when [Protected Source](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-protectedSource) was not working in the `<title>` element.
+* [#11859](http://dev.ckeditor.com/ticket/11859): Fixed: Removed the [Source Dialog](http://ckeditor.com/addon/sourcedialog) plugin dependency from the [Code Snippet](http://ckeditor.com/addon/codesnippet) sample.
+* [#11754](http://dev.ckeditor.com/ticket/11754): [Chrome] Fixed: Infinite loop when content includes not closed attributes.
+* [#11848](http://dev.ckeditor.com/ticket/11848): [IE] Fixed: [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) throwing an exception when there was no selection in the editor.
+* [#11801](http://dev.ckeditor.com/ticket/11801): Fixed: Editor anchors unavailable when linking the [Enhanced Image](http://ckeditor.com/addon/image2) widget.
+* [#11626](http://dev.ckeditor.com/ticket/11626): Fixed: [Table Resize](http://ckeditor.com/addon/tableresize) sets invalid column width.
+* [#11872](http://dev.ckeditor.com/ticket/11872): Made [`element.addClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-addClass) chainable symmetrically to [`element.removeClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-removeClass).
+* [#11813](http://dev.ckeditor.com/ticket/11813): Fixed: Link lost while pasting a captioned image and restoring an undo snapshot ([Enhanced Image](http://ckeditor.com/addon/image2)).
+* [#11814](http://dev.ckeditor.com/ticket/11814): Fixed: _Link_ and _Unlink_ entries persistently displayed in the [Enhanced Image](http://ckeditor.com/addon/image2) context menu.
+* [#11839](http://dev.ckeditor.com/ticket/11839): [IE9] Fixed: The caret jumps out of the editable area when resizing the editor in the source mode.
+* [#11822](http://dev.ckeditor.com/ticket/11822): [Webkit] Fixed: Editing anchors by double-click is broken in some cases.
+* [#11823](http://dev.ckeditor.com/ticket/11823): [IE8] Fixed: [Table Resize](http://ckeditor.com/addon/tableresize) throws an error over scrollbar.
+* [#11788](http://dev.ckeditor.com/ticket/11788): Fixed: It is not possible to change the language back to _Not set_ in the [Code Snippet](http://ckeditor.com/addon/codesnippet) dialog window.
+* [#11788](http://dev.ckeditor.com/ticket/11788): Fixed: [Filter](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.filter) rules are not applied inside elements with the `contenteditable` attribute set to `true`.
+* [#11798](http://dev.ckeditor.com/ticket/11798): Fixed: Inserting a non-editable element inside a table cell breaks the table.
+* [#11793](http://dev.ckeditor.com/ticket/11793): Fixed: Drop-down is not "on" when clicking it while the editor is blurred.
+* [#11850](http://dev.ckeditor.com/ticket/11850): Fixed: Fake objects with the `contenteditable` attribute set to `false` are not downcasted properly.
+* [#11811](http://dev.ckeditor.com/ticket/11811): Fixed: Widget's data is not encoded correctly when passed to an attribute.
+* [#11777](http://dev.ckeditor.com/ticket/11777): Fixed encoding ampersand in the [Mathematical Formulas](http://ckeditor.com/addon/mathjax) plugin.
+* [#11880](http://dev.ckeditor.com/ticket/11880): [IE8-9] Fixed: Linked image has a default thick border.
+
+Other Changes:
+
+* [#11807](http://dev.ckeditor.com/ticket/11807): Updated jQuery version used in the sample to 1.11.0 and tested CKEditor jQuery Adapter with version 1.11.0 and 2.1.0.
+* [#9504](http://dev.ckeditor.com/ticket/9504): Stopped using deprecated `attribute.specified` in all browsers except Internet Explorer.
+* [#11809](http://dev.ckeditor.com/ticket/11809): Changed tab size in `<pre>` to 4 spaces.
+
+## CKEditor 4.4
+
+**Important Notes:**
+
+* Marked the [`editor.beforePaste`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-beforePaste) event as deprecated.
+* The default class of captioned images has changed to `image` (was: `caption`). Please note that once edited in CKEditor 4.4+, all existing images of the `caption` class (`<figure class="caption">`) will be [filtered out](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) unless the [`config.image2_captionedClass`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_captionedClass) option is set to `caption`. For backward compatibility (i.e. when upgrading), it is highly recommended to use this setting, which also helps prevent CSS conflicts, etc. This does not apply to new CKEditor integrations.
+* Widgets without defined buttons are no longer registered automatically to the [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter). Before CKEditor 4.4 widgets were registered to the ACF which was an incorrect behavior ([#11567](http://dev.ckeditor.com/ticket/11567)). This change should not have any impact on standard scenarios, but if your button does not execute the widget command, you need to set [`allowedContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.feature-property-allowedContent) and [`requiredContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.feature-property-requiredContent) properties for it manually, because the editor will not be able to find them.
+* The [Show Borders](http://ckeditor.com/addon/showborders) plugin was added to the Standard installation package in order to ensure that unstyled tables are still visible for the user ([#11665](http://dev.ckeditor.com/ticket/11665)).
+* Since CKEditor 4.4 the editor instance should be passed to [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) methods to ensure full compatibility with other features (e.g. applying styles to widgets requires that). We ensured backward compatibility though, so the [`CKEDITOR.style`](http://docs.ckeditor.com/#!/api/CKEDITOR.style) will work even when the editor instance is not provided.
+
+New Features:
+
+* [#11297](http://dev.ckeditor.com/ticket/11297): Styles can now be applied to widgets. The definition of a style which can be applied to a specific widget must contain two additional properties &mdash; `type` and `widget`. Read more in the [Widget Styles](http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles) section of the "Syles Drop-down" guide. Note that by default, widgets support only classes and no other attributes or styles. Related changes and features:
+ * Introduced the [`CKEDITOR.style.addCustomHandler()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-static-method-addCustomHandler) method for registering custom style handlers.
+ * The [`CKEDITOR.style.apply()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-apply) and [`CKEDITOR.style.remove()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-remove) methods are now called with an editor instance instead of the document so they can be reused by the [`CKEDITOR.editor.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-applyStyle) and [`CKEDITOR.editor.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-removeStyle) methods. Backward compatibility was preserved, but from CKEditor 4.4 it is highly recommended to pass an editor instead of a document to these methods.
+ * Many new methods and properties were introduced in the [Widget API](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget) to make the handling of styles by widgets fully customizable. See: [`widget.definition.styleableElements`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.definition-property-styleableElements), [`widget.definition.styleToAllowedContentRule`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.definition-property-styleToAllowedContentRules), [`widget.addClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-addClass), [`widget.removeClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-removeClass), [`widget.getClasses()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-getClasses), [`widget.hasClass()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-hasClass), [`widget.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-applyStyle), [`widget.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-removeStyle), [`widget.checkStyleActive()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-method-checkStyleActive).
+ * Integration with the [Allowed Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) required an introduction of the [`CKEDITOR.style.toAllowedContent()`](http://docs.ckeditor.com/#!/api/CKEDITOR.style-method-toAllowedContentRules) method which can be implemented by the custom style handler and if exists, it is used by the [`CKEDITOR.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter) to translate a style to [allowed content rules](http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules).
+* [#11300](http://dev.ckeditor.com/ticket/11300): Various changes in the [Enhanced Image](http://ckeditor.com/addon/image2) plugin:
+ * Introduced the [`config.image2_captionedClass`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_captionedClass) option to configure the class of captioned images.
+ * Introduced the [`config.image2_alignClasses`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_alignClasses) option to configure the way images are aligned with CSS classes.
+ If this setting is defined, the editor produces classes instead of inline styles for aligned images.
+ * Default image caption can be translated (customized) with the `editor.lang.image2.captionPlaceholder` string.
+* [#11341](http://dev.ckeditor.com/ticket/11341): [Enhanced Image](http://ckeditor.com/addon/image2) plugin: It is now possible to add a link to any image type.
+* [#10202](http://dev.ckeditor.com/ticket/10202): Introduced wildcard support in the [Allowed Content Rules](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules) format.
+* [#10276](http://dev.ckeditor.com/ticket/10276): Introduced blacklisting in the [Allowed Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter).
+* [#10480](http://dev.ckeditor.com/ticket/10480): Introduced code snippets with code highlighting. There are two versions available so far &mdash; the default [Code Snippet](http://ckeditor.com/addon/codesnippet) which uses the [highlight.js](http://highlightjs.org) library and the [Code Snippet GeSHi](http://ckeditor.com/addon/codesnippetgeshi) which uses the [GeSHi](http://qbnz.com/highlighter/) library.
+* [#11737](http://dev.ckeditor.com/ticket/11737): Introduced an option to prevent [filtering](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) of an element that matches custom criteria (see [`filter.addElementCallback()`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter-method-addElementCallback)).
+* [#11532](http://dev.ckeditor.com/ticket/11532): Introduced the [`editor.addContentsCss()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-addContentsCss) method that can be used for [adding custom CSS files](http://docs.ckeditor.com/#!/guide/plugin_sdk_styles).
+* [#11536](http://dev.ckeditor.com/ticket/11536): Added the [`CKEDITOR.tools.htmlDecode()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-htmlDecode) method for decoding HTML entities.
+* [#11225](http://dev.ckeditor.com/ticket/11225): Introduced the [`CKEDITOR.tools.transparentImageData`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-property-transparentImageData) property which contains transparent image data to be used in CSS or as image source.
+
+Other Changes:
+
+* [#11377](http://dev.ckeditor.com/ticket/11377): Unified internal representation of empty anchors using the [fake objects](http://ckeditor.com/addon/fakeobjects).
+* [#11422](http://dev.ckeditor.com/ticket/11422): Removed Firefox 3.x, Internet Explorer 6 and Opera 12.x leftovers in code.
+* [#5217](http://dev.ckeditor.com/ticket/5217): Setting data (including switching between modes) creates a new undo snapshot. Besides that:
+ * Introduced the [`editable.status`](http://docs.ckeditor.com/#!/api/CKEDITOR.editable-property-status) property.
+ * Introduced a new `forceUpdate` option for the [`editor.lockSnapshot`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-lockSnapshot) event.
+ * Fixed: Selection not being unlocked in inline editor after setting data ([#11500](http://dev.ckeditor.com/ticket/11500)).
+* The [WebSpellChecker](http://ckeditor.com/addon/wsc) plugin was updated to the latest version.
+
+Fixed Issues:
+
+* [#10190](http://dev.ckeditor.com/ticket/10190): Fixed: Removing block style with [`editor.removeStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-removeStyle) should result in a paragraph and not a div.
+* [#11727](http://dev.ckeditor.com/ticket/11727): Fixed: The editor tries to select a non-editable image which was clicked.
+
+## CKEditor 4.3.5
+
+New Features:
+
+* Added new translation: Tatar.
+
+Fixed Issues:
+
+* [#11677](http://dev.ckeditor.com/ticket/11677): Fixed: Undo/Redo keystrokes are blocked in the source mode.
+* [#11717](http://dev.ckeditor.com/ticket/11717): [Document Properties](http://ckeditor.com/addon/docprops) plugin requires the [Color Dialog](http://ckeditor.com/addon/colordialog) plugin to work.
+
+## CKEditor 4.3.4
+
+Fixed Issues:
+
+* [#11597](http://dev.ckeditor.com/ticket/11597): [IE11] Fixed: Error thrown when trying to open the [preview](http://ckeditor.com/addon/preview) using the keyboard.
+* [#11544](http://dev.ckeditor.com/ticket/11544): [Placeholders](http://ckeditor.com/addon/placeholder) will no longer be upcasted in parents not accepting `<span>` elements.
+* [#8663](http://dev.ckeditor.com/ticket/8663): Fixed [`element.renameNode()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-renameNode) not clearing the [`element.getName()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-getName) cache.
+* [#11574](http://dev.ckeditor.com/ticket/11574): Fixed: *Backspace* destroying the DOM structure if an inline editable is placed in a list item.
+* [#11603](http://dev.ckeditor.com/ticket/11603): Fixed: [Table Resize](http://ckeditor.com/addon/tableresize) attaches to tables outside the editable.
+* [#9205](http://dev.ckeditor.com/ticket/9205), [#7805](http://dev.ckeditor.com/ticket/7805), [#8216](http://dev.ckeditor.com/ticket/8216): Fixed: `{cke_protected_1}` appearing in data in various cases where HTML comments are placed next to `"` or `'`.
+* [#11635](http://dev.ckeditor.com/ticket/11635): Fixed: Some attributes are not protected before the content is passed through the fix bin.
+* [#11660](http://dev.ckeditor.com/ticket/11660): [IE] Fixed: Table content is lost when some extra markup is inside the table.
+* [#11641](http://dev.ckeditor.com/ticket/11641): Fixed: Switching between modes in the classic editor removes content styles for the inline editor.
+* [#11568](http://dev.ckeditor.com/ticket/11568): Fixed: [Styles](http://ckeditor.com/addon/stylescombo) drop-down list is not enabled on selection change.
+
+## CKEditor 4.3.3
+
+Fixed Issues:
+
+* [#11500](http://dev.ckeditor.com/ticket/11500): [Webkit/Blink] Fixed: Selection lost when setting data in another inline editor. Additionally, [`selection.removeAllRanges()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-removeAllRanges) is now scoped to selection's [root](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-property-root).
+* [#11104](http://dev.ckeditor.com/ticket/11104): [IE] Fixed: Various issues with scrolling and selection when focusing widgets.
+* [#11487](http://dev.ckeditor.com/ticket/11487): Moving mouse over the [Enhanced Image](http://ckeditor.com/addon/image2) widget will no longer change the value returned by the [`editor.checkDirty()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-checkDirty) method.
+* [#8673](http://dev.ckeditor.com/ticket/8673): [WebKit] Fixed: Cannot select and remove the [Page Break](http://ckeditor.com/addon/pagebreak).
+* [#11413](http://dev.ckeditor.com/ticket/11413): Fixed: Incorrect [`editor.execCommand()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-execCommand) behavior.
+* [#11438](http://dev.ckeditor.com/ticket/11438): Splitting table cells vertically is no longer changing table structure.
+* [#8899](http://dev.ckeditor.com/ticket/8899): Fixed: Links in the [About CKEditor](http://ckeditor.com/addon/about) dialog window now open in a new browser window or tab.
+* [#11490](http://dev.ckeditor.com/ticket/11490): Fixed: [Menu button](http://ckeditor.com/addon/menubutton) panel not showing in the source mode.
+* [#11417](http://dev.ckeditor.com/ticket/11417): The [`widget.doubleclick`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget-event-doubleclick) event is not canceled anymore after editing was triggered.
+* [#11253](http://dev.ckeditor.com/ticket/11253): [IE] Fixed: Clipped upload button in the [Enhanced Image](http://ckeditor.com/addon/image2) dialog window.
+* [#11359](http://dev.ckeditor.com/ticket/11359): Standardized the way anchors are discovered by the [Link](http://ckeditor.com/addon/link) plugin.
+* [#11058](http://dev.ckeditor.com/ticket/11058): [IE8] Fixed: Error when deleting a table row.
+* [#11508](http://dev.ckeditor.com/ticket/11508): Fixed: [`htmlDataProcessor`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor) discovering protected attributes within other attributes' values.
+* [#11533](http://dev.ckeditor.com/ticket/11533): Widgets: Avoid recurring upcasts if the DOM structure was modified during an upcast.
+* [#11400](http://dev.ckeditor.com/ticket/11400): Fixed: The [`domObject.removeAllListeners()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.domObject-method-removeAllListeners) method does not remove custom listeners completely.
+* [#11493](http://dev.ckeditor.com/ticket/11493): Fixed: The [`selection.getRanges()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-getRanges) method does not override cached ranges when used with the `onlyEditables` argument.
+* [#11390](http://dev.ckeditor.com/ticket/11390): [IE] All [XML](http://ckeditor.com/addon/xml) plugin [methods](http://docs.ckeditor.com/#!/api/CKEDITOR.xml) now work in IE10+.
+* [#11542](http://dev.ckeditor.com/ticket/11542): [IE11] Fixed: Blurry toolbar icons when Right-to-Left UI language is set.
+* [#11504](http://dev.ckeditor.com/ticket/11504): Fixed: When [`config.fullPage`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-fullPage) is set to `true`, entities are not encoded in editor output.
+* [#11004](http://dev.ckeditor.com/ticket/11004): Integrated [Enhanced Image](http://ckeditor.com/addon/image2) dialog window with [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter).
+* [#11439](http://dev.ckeditor.com/ticket/11439): Fixed: Properties get cloned in the Cell Properties dialog window if multiple cells are selected.
+
+## CKEditor 4.3.2
+
+Fixed Issues:
+
+* [#11331](http://dev.ckeditor.com/ticket/11331): A menu button will have a changed label when selected instead of using the `aria-pressed` attribute.
+* [#11177](http://dev.ckeditor.com/ticket/11177): Widget drag handler improvements:
+ * [#11176](http://dev.ckeditor.com/ticket/11176): Fixed: Initial position is not updated when the widget data object is empty.
+ * [#11001](http://dev.ckeditor.com/ticket/11001): Fixed: Multiple synchronous layout recalculations are caused by initial drag handler positioning causing performance issues.
+ * [#11161](http://dev.ckeditor.com/ticket/11161): Fixed: Drag handler is not repositioned in various situations.
+ * [#11281](http://dev.ckeditor.com/ticket/11281): Fixed: Drag handler and mask are duplicated after widget reinitialization.
+* [#11207](http://dev.ckeditor.com/ticket/11207): [Firefox] Fixed: Misplaced [Enhanced Image](http://ckeditor.com/addon/image2) resizer in the inline editor.
+* [#11102](http://dev.ckeditor.com/ticket/11102): `CKEDITOR.template` improvements:
+ * [#11102](http://dev.ckeditor.com/ticket/11102): Added newline character support.
+ * [#11216](http://dev.ckeditor.com/ticket/11216): Added "\\'" substring support.
+* [#11121](http://dev.ckeditor.com/ticket/11121): [Firefox] Fixed: High Contrast mode is enabled when the editor is loaded in a hidden iframe.
+* [#11350](http://dev.ckeditor.com/ticket/11350): The default value of [`config.contentsCss`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-contentsCss) is affected by [`CKEDITOR.getUrl()`](http://docs.ckeditor.com/#!/api/CKEDITOR-method-getUrl).
+* [#11097](http://dev.ckeditor.com/ticket/11097): Improved the [Autogrow](http://ckeditor.com/addon/autogrow) plugin performance when dealing with very big tables.
+* [#11290](http://dev.ckeditor.com/ticket/11290): Removed redundant code in the [Source Dialog](http://ckeditor.com/addon/sourcedialog) plugin.
+* [#11133](http://dev.ckeditor.com/ticket/11133): [Page Break](http://ckeditor.com/addon/pagebreak) becomes editable if pasted.
+* [#11126](http://dev.ckeditor.com/ticket/11126): Fixed: Native Undo executed once the bottom of the snapshot stack is reached.
+* [#11131](http://dev.ckeditor.com/ticket/11131): [Div Editing Area](http://ckeditor.com/addon/divarea): Fixed: Error thrown when switching to source mode if the selection was in widget's nested editable.
+* [#11139](http://dev.ckeditor.com/ticket/11139): [Div Editing Area](http://ckeditor.com/addon/divarea): Fixed: Elements Path is not cleared after switching to source mode.
+* [#10778](http://dev.ckeditor.com/ticket/10778): Fixed a bug with range enlargement. The range no longer expands to visible whitespace.
+* [#11146](http://dev.ckeditor.com/ticket/11146): [IE] Fixed: Preview window switches Internet Explorer to Quirks Mode.
+* [#10762](http://dev.ckeditor.com/ticket/10762): [IE] Fixed: JavaScript code displayed in preview window's URL bar.
+* [#11186](http://dev.ckeditor.com/ticket/11186): Introduced the [`widgets.repository.addUpcastCallback()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-addUpcastCallback) method that allows to block upcasting given element to a widget.
+* [#11307](http://dev.ckeditor.com/ticket/11307): Fixed: Paste as Plain Text conflict with the [MooTools](http://mootools.net) library.
+* [#11140](http://dev.ckeditor.com/ticket/11140): [IE11] Fixed: Anchors are not draggable.
+* [#11379](http://dev.ckeditor.com/ticket/11379): Changed default contents `line-height` to unitless values to avoid huge text overlapping (like in [#9696](http://dev.ckeditor.com/ticket/9696)).
+* [#10787](http://dev.ckeditor.com/ticket/10787): [Firefox] Fixed: Broken replacement of text while pasting into `div`-based editor.
+* [#10884](http://dev.ckeditor.com/ticket/10884): Widgets integration with the [Show Blocks](http://ckeditor.com/addon/showblocks) plugin.
+* [#11021](http://dev.ckeditor.com/ticket/11021): Fixed: An error thrown when selecting entire editable contents while fake selection is on.
+* [#11086](http://dev.ckeditor.com/ticket/11086): [IE8] Re-enable inline widgets drag&drop in Internet Explorer 8.
+* [#11372](http://dev.ckeditor.com/ticket/11372): Widgets: Special characters encoded twice in nested editables.
+* [#10068](http://dev.ckeditor.com/ticket/10068): Fixed: Support for protocol-relative URLs.
+* [#11283](http://dev.ckeditor.com/ticket/11283): [Enhanced Image](http://ckeditor.com/addon/image2): A `<div>` element with `text-align: center` and an image inside is not recognised correctly.
+* [#11196](http://dev.ckeditor.com/ticket/11196): [Accessibility Instructions](http://ckeditor.com/addon/a11yhelp): Allowed additional keyboard button labels to be translated in the dialog window.
+
+## CKEditor 4.3.1
+
+**Important Notes:**
+
+* To match the naming convention, the `language` button is now `Language` ([#11201](http://dev.ckeditor.com/ticket/11201)).
+* [Enhanced Image](http://ckeditor.com/addon/image2) button, context menu, command, and icon names match those of the [Image](http://ckeditor.com/addon/image) plugin ([#11222](http://dev.ckeditor.com/ticket/11222)).
+
+Fixed Issues:
+
+* [#11244](http://dev.ckeditor.com/ticket/11244): Changed: The [`widget.repository.checkWidgets()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-checkWidgets) method now fires the [`widget.repository.checkWidgets`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-event-checkWidgets) event, so from CKEditor 4.3.1 it is preferred to use the method rather than fire the event.
+* [#11171](http://dev.ckeditor.com/ticket/11171): Fixed: [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) and [`editor.insertText()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertText) methods do not call the [`widget.repository.checkWidgets()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.repository-method-checkWidgets) method.
+* [#11085](http://dev.ckeditor.com/ticket/11085): [IE8] Replaced preview generated by the [Mathematical Formulas](http://ckeditor.com/addon/mathjax) widget with a placeholder.
+* [#11044](http://dev.ckeditor.com/ticket/11044): Enhanced WAI-ARIA support for the [Language](http://ckeditor.com/addon/language) plugin drop-down menu.
+* [#11075](http://dev.ckeditor.com/ticket/11075): With drop-down menu button focused, pressing the *Down Arrow* key will now open the menu and focus its first option.
+* [#11165](http://dev.ckeditor.com/ticket/11165): Fixed: The [File Browser](http://ckeditor.com/addon/filebrowser) plugin cannot be removed from the editor.
+* [#11159](http://dev.ckeditor.com/ticket/11159): [IE9-10] [Enhanced Image](http://ckeditor.com/addon/image2): Fixed buggy discovery of image dimensions.
+* [#11101](http://dev.ckeditor.com/ticket/11101): Drop-down lists no longer break when given double quotes.
+* [#11077](http://dev.ckeditor.com/ticket/11077): [Enhanced Image](http://ckeditor.com/addon/image2): Empty undo step recorded when resizing the image.
+* [#10853](http://dev.ckeditor.com/ticket/10853): [Enhanced Image](http://ckeditor.com/addon/image2): Widget has paragraph wrapper when de-captioning unaligned image.
+* [#11198](http://dev.ckeditor.com/ticket/11198): Widgets: Drag handler is not fully visible when an inline widget is in a heading.
+* [#11132](http://dev.ckeditor.com/ticket/11132): [Firefox] Fixed: Caret is lost after drag and drop of an inline widget.
+* [#11182](http://dev.ckeditor.com/ticket/11182): [IE10-11] Fixed: Editor crashes (IE11) or works with minor issues (IE10) if a page is loaded in Quirks Mode. See [`env.quirks`](http://docs.ckeditor.com/#!/api/CKEDITOR.env-property-quirks) for more details.
+* [#11204](http://dev.ckeditor.com/ticket/11204): Added `figure` and `figcaption` styles to the `contents.css` file so [Enhanced Image](http://ckeditor.com/addon/image2) looks nicer.
+* [#11202](http://dev.ckeditor.com/ticket/11202): Fixed: No newline in [BBCode](http://ckeditor.com/addon/bbcode) mode.
+* [#10890](http://dev.ckeditor.com/ticket/10890): Fixed: Error thrown when pressing the *Delete* key in a list item.
+* [#10055](http://dev.ckeditor.com/ticket/10055): [IE8-10] Fixed: *Delete* pressed on a selected image causes the browser to go back.
+* [#11183](http://dev.ckeditor.com/ticket/11183): Fixed: Inserting a horizontal rule or a table in multiple row selection causes a browser crash. Additionally, the [`editor.insertElement()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertElement) method does not insert the element into every range of a selection any more.
+* [#11042](http://dev.ckeditor.com/ticket/11042): Fixed: Selection made on an element containing a non-editable element was not auto faked.
+* [#11125](http://dev.ckeditor.com/ticket/11125): Fixed: Keyboard navigation through menu and drop-down items will now cycle.
+* [#11011](http://dev.ckeditor.com/ticket/11011): Fixed: The [`editor.applyStyle()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-applyStyle) method removes attributes from nested elements.
+* [#11179](http://dev.ckeditor.com/ticket/11179): Fixed: [`editor.destroy()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-destroy) does not cleanup content generated by the [Table Resize](http://ckeditor.com/addon/tableresize) plugin for inline editors.
+* [#11237](http://dev.ckeditor.com/ticket/11237): Fixed: Table border attribute value is deleted when pasting content from Microsoft Word.
+* [#11250](http://dev.ckeditor.com/ticket/11250): Fixed: HTML entities inside the `<textarea>` element are not encoded.
+* [#11260](http://dev.ckeditor.com/ticket/11260): Fixed: Initially disabled buttons are not read by JAWS as disabled.
+* [#11200](http://dev.ckeditor.com/ticket/11200): Added [Clipboard](http://ckeditor.com/addon/clipboard) plugin as a dependency for [Widget](http://ckeditor.com/addon/widget) to fix drag and drop.
+
+## CKEditor 4.3
+
+New Features:
+
+* [#10612](http://dev.ckeditor.com/ticket/10612): Internet Explorer 11 support.
+* [#10869](http://dev.ckeditor.com/ticket/10869): Widgets: Added better integration with the [Elements Path](http://ckeditor.com/addon/elementspath) plugin.
+* [#10886](http://dev.ckeditor.com/ticket/10886): Widgets: Added tooltip to the drag handle.
+* [#10933](http://dev.ckeditor.com/ticket/10933): Widgets: Introduced drag and drop of block widgets with the [Line Utilities](http://ckeditor.com/addon/lineutils) plugin.
+* [#10936](http://dev.ckeditor.com/ticket/10936): Widget System changes for easier integration with other dialog systems.
+* [#10895](http://dev.ckeditor.com/ticket/10895): [Enhanced Image](http://ckeditor.com/addon/image2): Added file browser integration.
+* [#11002](http://dev.ckeditor.com/ticket/11002): Added the [`draggable`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget.definition-property-draggable) option to disable drag and drop support for widgets.
+* [#10937](http://dev.ckeditor.com/ticket/10937): [Mathematical Formulas](http://ckeditor.com/addon/mathjax) widget improvements:
+ * loading indicator ([#10948](http://dev.ckeditor.com/ticket/10948)),
+ * applying paragraph changes (like font color change) to iframe ([#10841](http://dev.ckeditor.com/ticket/10841)),
+ * Firefox and IE9 clipboard fixes ([#10857](http://dev.ckeditor.com/ticket/10857)),
+ * fixing same origin policy issue ([#10840](http://dev.ckeditor.com/ticket/10840)),
+ * fixing undo bugs ([#10842](http://dev.ckeditor.com/ticket/10842), [#10930](http://dev.ckeditor.com/ticket/10930)),
+ * fixing other minor bugs.
+* [#10862](http://dev.ckeditor.com/ticket/10862): [Placeholder](http://ckeditor.com/addon/placeholder) plugin was rewritten as a widget.
+* [#10822](http://dev.ckeditor.com/ticket/10822): Added styles system integration with non-editable elements (for example widgets) and their nested editables. Styles cannot change non-editable content and are applied in nested editable only if allowed by its type and content filter.
+* [#10856](http://dev.ckeditor.com/ticket/10856): Menu buttons will now toggle the visibility of their panels when clicked multiple times. [Language](http://ckeditor.com/addon/language) plugin fixes: Added active language highlighting, added an option to remove the language.
+* [#10028](http://dev.ckeditor.com/ticket/10028): New [`config.dialog_noConfirmCancel`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-dialog_noConfirmCancel) configuration option that eliminates the need to confirm closing of a dialog window when the user changed any of its fields.
+* [#10848](http://dev.ckeditor.com/ticket/10848): Integrate remaining plugins ([Styles](http://ckeditor.com/addon/stylescombo), [Format](http://ckeditor.com/addon/format), [Font](http://ckeditor.com/addon/font), [Color Button](http://ckeditor.com/addon/colorbutton), [Language](http://ckeditor.com/addon/language) and [Indent](http://ckeditor.com/addon/indent)) with [active filter](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-activeFilter).
+* [#10855](http://dev.ckeditor.com/ticket/10855): Change the extension of emoticons in the [BBCode](http://ckeditor.com/addon/bbcode) sample from GIF to PNG.
+
+Fixed Issues:
+
+* [#10831](http://dev.ckeditor.com/ticket/10831): [Enhanced Image](http://ckeditor.com/addon/image2): Merged `image2inline` and `image2block` into one `image2` widget.
+* [#10835](http://dev.ckeditor.com/ticket/10835): [Enhanced Image](http://ckeditor.com/addon/image2): Improved visibility of the resize handle.
+* [#10836](http://dev.ckeditor.com/ticket/10836): [Enhanced Image](http://ckeditor.com/addon/image2): Preserve custom mouse cursor while resizing the image.
+* [#10939](http://dev.ckeditor.com/ticket/10939): [Firefox] [Enhanced Image](http://ckeditor.com/addon/image2): hovering the image causes it to change.
+* [#10866](http://dev.ckeditor.com/ticket/10866): Fixed: Broken *Tab* key navigation in the [Enhanced Image](http://ckeditor.com/addon/image2) dialog window.
+* [#10833](http://dev.ckeditor.com/ticket/10833): Fixed: *Lock ratio* option should be on by default in the [Enhanced Image](http://ckeditor.com/addon/image2) dialog window.
+* [#10881](http://dev.ckeditor.com/ticket/10881): Various improvements to *Enter* key behavior in nested editables.
+* [#10879](http://dev.ckeditor.com/ticket/10879): [Remove Format](http://ckeditor.com/addon/removeformat) should not leak from a nested editable.
+* [#10877](http://dev.ckeditor.com/ticket/10877): Fixed: [WebSpellChecker](http://ckeditor.com/addon/wsc) fails to apply changes if a nested editable was focused.
+* [#10877](http://dev.ckeditor.com/ticket/10877): Fixed: [SCAYT](http://ckeditor.com/addon/wsc) blocks typing in nested editables.
+* [#11079](http://dev.ckeditor.com/ticket/11079): Add button icons to the [Placeholder](http://ckeditor.com/addon/placeholder) sample.
+* [#10870](http://dev.ckeditor.com/ticket/10870): The `paste` command is no longer being disabled when the clipboard is empty.
+* [#10854](http://dev.ckeditor.com/ticket/10854): Fixed: Firefox prepends `<br>` to `<body>`, so it is stripped by the HTML data processor.
+* [#10823](http://dev.ckeditor.com/ticket/10823): Fixed: [Link](http://ckeditor.com/addon/link) plugin does not work with non-editable content.
+* [#10828](http://dev.ckeditor.com/ticket/10828): [Magic Line](http://ckeditor.com/addon/magicline) integration with the Widget System.
+* [#10865](http://dev.ckeditor.com/ticket/10865): Improved hiding copybin, so copying widgets works smoothly.
+* [#11066](http://dev.ckeditor.com/ticket/11066): Widget's private parts use CSS reset.
+* [#11027](http://dev.ckeditor.com/ticket/11027): Fixed: Block commands break on widgets; added the [`contentDomInvalidated`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-contentDomInvalidated) event.
+* [#10430](http://dev.ckeditor.com/ticket/10430): Resolve dependence of the [Image](http://ckeditor.com/addon/image) plugin on the [Form Elements](http://ckeditor.com/addon/forms) plugin.
+* [#10911](http://dev.ckeditor.com/ticket/10911): Fixed: Browser *Alt* hotkeys will no longer be blocked while a widget is focused.
+* [#11082](http://dev.ckeditor.com/ticket/11082): Fixed: Selected widget is not copied or cut when using toolbar buttons or context menu.
+* [#11083](http://dev.ckeditor.com/ticket/11083): Fixed list and div element application to block widgets.
+* [#10887](http://dev.ckeditor.com/ticket/10887): Internet Explorer 8 compatibility issues related to the Widget System.
+* [#11074](http://dev.ckeditor.com/ticket/11074): Temporarily disabled inline widget drag and drop, because of seriously buggy native `range#moveToPoint` method.
+* [#11098](http://dev.ckeditor.com/ticket/11098): Fixed: Wrong selection position after undoing widget drag and drop.
+* [#11110](http://dev.ckeditor.com/ticket/11110): Fixed: IFrame and Flash objects are being incorrectly pasted in certain conditions.
+* [#11129](http://dev.ckeditor.com/ticket/11129): Page break is lost when loading data.
+* [#11123](http://dev.ckeditor.com/ticket/11123): [Firefox] Widget is destroyed after being dragged outside of `<body>`.
+* [#11124](http://dev.ckeditor.com/ticket/11124): Fixed the [Elements Path](http://ckeditor.com/addon/elementspath) in an editor using the [Div Editing Area](http://ckeditor.com/addon/divarea).
+
+## CKEditor 4.3 Beta
+
+New Features:
+
+* [#9764](http://dev.ckeditor.com/ticket/9764): Widget System.
+ * [Widget plugin](http://ckeditor.com/addon/widget) introducing the [Widget API](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.widget).
+ * New [`editor.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-enterMode) and [`editor.shiftEnterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-shiftEnterMode) properties &ndash; normalized versions of [`config.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode) and [`config.shiftEnterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-shiftEnterMode).
+ * Dynamic editor settings. Starting from CKEditor 4.3 Beta, *Enter* mode values and [content filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) instances may be changed dynamically (for example when the caret was placed in an element in which editor features should be adjusted). When you are implementing a new editor feature, you should base its behavior on [dynamic](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-activeEnterMode) or [static](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-enterMode) *Enter* mode values depending on whether this feature works in selection context or globally on editor content.
+ * Dynamic *Enter* mode values &ndash; [`editor.setActiveEnterMode()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setActiveEnterMode) method, [`editor.activeEnterModeChange`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-activeEnterModeChange) event, and two properties: [`editor.activeEnterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-activeEnterMode) and [`editor.activeShiftEnterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-activeShiftEnterMode).
+ * Dynamic content filter instances &ndash; [`editor.setActiveFilter()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setActiveFilter) method, [`editor.activeFilterChange`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-activeFilterChange) event, and [`editor.activeFilter`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-activeFilter) property.
+ * "Fake" selection was introduced. It makes it possible to virtually select any element when the real selection remains hidden. See the [`selection.fake()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-fake) method.
+ * Default [`htmlParser.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.filter) rules are not applied to non-editable elements (elements with `contenteditable` attribute set to `false` and their descendants) anymore. To add a rule which will be applied to all elements you need to pass an additional argument to the [`filter.addRules()`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.filter-method-addRules) method.
+ * Dozens of new methods were introduced &ndash; most interesting ones:
+ * [`document.find()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document-method-find),
+ * [`document.findOne()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.document-method-findOne),
+ * [`editable.insertElementIntoRange()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editable-method-insertElementIntoRange),
+ * [`range.moveToClosestEditablePosition()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.range-method-moveToClosestEditablePosition),
+ * New methods for [`htmlParser.node`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.node) and [`htmlParser.element`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.element).
+* [#10659](http://dev.ckeditor.com/ticket/10659): New [Enhanced Image](http://ckeditor.com/addon/image2) plugin that introduces a widget with integrated image captions, an option to center images, and dynamic "click and drag" resizing.
+* [#10664](http://dev.ckeditor.com/ticket/10664): New [Mathematical Formulas](http://ckeditor.com/addon/mathjax) plugin that introduces the MathJax widget.
+* [#7987](https://dev.ckeditor.com/ticket/7987): New [Language](http://ckeditor.com/addon/language) plugin that implements Language toolbar button to support [WCAG 3.1.2 Language of Parts](http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html).
+* [#10708](http://dev.ckeditor.com/ticket/10708): New [smileys](http://ckeditor.com/addon/smiley).
+
+## CKEditor 4.2.3
+
+Fixed Issues:
+
+* [#10994](http://dev.ckeditor.com/ticket/10994): Fixed: Loading external jQuery library when opening the [jQuery Adapter](http://docs.ckeditor.com/#!/guide/dev_jquery) sample directly from file.
+* [#10975](http://dev.ckeditor.com/ticket/10975): [IE] Fixed: Error thrown while opening the color palette.
+* [#9929](http://dev.ckeditor.com/ticket/9929): [Blink/WebKit] Fixed: A non-breaking space is created once a character is deleted and a regular space is typed.
+* [#10963](http://dev.ckeditor.com/ticket/10963): Fixed: JAWS issue with the keyboard shortcut for [Magic Line](http://ckeditor.com/addon/magicline).
+* [#11096](http://dev.ckeditor.com/ticket/11096): Fixed: TypeError: Object has no method 'is'.
+
+## CKEditor 4.2.2
+
+Fixed Issues:
+
+* [#9314](http://dev.ckeditor.com/ticket/9314): Fixed: Incorrect error message on closing a dialog window without saving changs.
+* [#10308](http://dev.ckeditor.com/ticket/10308): [IE10] Fixed: Unspecified error when deleting a row.
+* [#10945](http://dev.ckeditor.com/ticket/10945): [Chrome] Fixed: Clicking with a mouse inside the editor does not show the caret.
+* [#10912](http://dev.ckeditor.com/ticket/10912): Prevent default action when content of a non-editable link is clicked.
+* [#10913](http://dev.ckeditor.com/ticket/10913): Fixed [`CKEDITOR.plugins.addExternal()`](http://docs.ckeditor.com/#!/api/CKEDITOR.resourceManager-method-addExternal) not handling paths including file name specified.
+* [#10666](http://dev.ckeditor.com/ticket/10666): Fixed [`CKEDITOR.tools.isArray()`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-isArray) not working cross frame.
+* [#10910](http://dev.ckeditor.com/ticket/10910): [IE9] Fixed JavaScript error thrown in Compatibility Mode when clicking and/or typing in the editing area.
+* [#10868](http://dev.ckeditor.com/ticket/10868): [IE8] Prevent the browser from crashing when applying the Inline Quotation style.
+* [#10915](http://dev.ckeditor.com/ticket/10915): Fixed: Invalid CSS filter in the Kama skin.
+* [#10914](http://dev.ckeditor.com/ticket/10914): Plugins [Indent List](http://ckeditor.com/addon/indentlist) and [Indent Block](http://ckeditor.com/addon/indentblock) are now included in the build configuration.
+* [#10812](http://dev.ckeditor.com/ticket/10812): Fixed [`range.createBookmark2()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.range-method-createBookmark2) incorrectly normalizing offsets. This bug was causing many issues: [#10850](http://dev.ckeditor.com/ticket/10850), [#10842](http://dev.ckeditor.com/ticket/10842).
+* [#10951](http://dev.ckeditor.com/ticket/10951): Reviewed and optimized focus handling on panels (combo, menu buttons, color buttons, and context menu) to enhance accessibility. Fixed [#10705](http://dev.ckeditor.com/ticket/10705), [#10706](http://dev.ckeditor.com/ticket/10706) and [#10707](http://dev.ckeditor.com/ticket/10707).
+* [#10704](http://dev.ckeditor.com/ticket/10704): Fixed a JAWS issue with the Select Color dialog window title not being announced.
+* [#10753](http://dev.ckeditor.com/ticket/10753): The floating toolbar in inline instances now has a dedicated accessibility label.
+
+## CKEditor 4.2.1
+
+Fixed Issues:
+
+* [#10301](http://dev.ckeditor.com/ticket/10301): [IE9-10] Undo fails after 3+ consecutive paste actions with a JavaScript error.
+* [#10689](http://dev.ckeditor.com/ticket/10689): Save toolbar button saves only the first editor instance.
+* [#10368](http://dev.ckeditor.com/ticket/10368): Move language reading direction definition (`dir`) from main language file to core.
+* [#9330](http://dev.ckeditor.com/ticket/9330): Fixed pasting anchors from MS Word.
+* [#8103](http://dev.ckeditor.com/ticket/8103): Fixed pasting nested lists from MS Word.
+* [#9958](http://dev.ckeditor.com/ticket/9958): [IE9] Pressing the "OK" button will trigger the `onbeforeunload` event in the popup dialog.
+* [#10662](http://dev.ckeditor.com/ticket/10662): Fixed styles from the Styles drop-down list not registering to the ACF in case when the [Shared Spaces plugin](http://ckeditor.com/addon/sharedspace) is used.
+* [#9654](http://dev.ckeditor.com/ticket/9654): Problems with Internet Explorer 10 Quirks Mode.
+* [#9816](http://dev.ckeditor.com/ticket/9816): Floating toolbar does not reposition vertically in several cases.
+* [#10646](http://dev.ckeditor.com/ticket/10646): Removing a selected sublist or nested table with *Backspace/Delete* removes the parent element.
+* [#10623](http://dev.ckeditor.com/ticket/10623): [WebKit] Page is scrolled when opening a drop-down list.
+* [#10004](http://dev.ckeditor.com/ticket/10004): [ChromeVox] Button names are not announced.
+* [#10731](http://dev.ckeditor.com/ticket/10731): [WebSpellChecker](http://ckeditor.com/addon/wsc) plugin breaks cloning of editor configuration.
+* It is now possible to set per instance [WebSpellChecker](http://ckeditor.com/addon/wsc) plugin configuration instead of setting the configuration globally.
+
+## CKEditor 4.2
+
+**Important Notes:**
+
+* Dropped compatibility support for Internet Explorer 7 and Firefox 3.6.
+
+* Both the Basic and the Standard distribution packages will not contain the new [Indent Block](http://ckeditor.com/addon/indentblock) plugin. Because of this the [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) might remove block indentations from existing contents. If you want to prevent this, either [add an appropriate ACF rule to your filter](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules) or create a custom build based on the Basic/Standard package and add the Indent Block plugin in [CKBuilder](http://ckeditor.com/builder).
+
+New Features:
+
+* [#10027](http://dev.ckeditor.com/ticket/10027): Separated list and block indentation into two plugins: [Indent List](http://ckeditor.com/addon/indentlist) and [Indent Block](http://ckeditor.com/addon/indentblock).
+* [#8244](http://dev.ckeditor.com/ticket/8244): Use *(Shift+)Tab* to indent and outdent lists.
+* [#10281](http://dev.ckeditor.com/ticket/10281): The [jQuery Adapter](http://docs.ckeditor.com/#!/guide/dev_jquery) is now available. Several jQuery-related issues fixed: [#8261](http://dev.ckeditor.com/ticket/8261), [#9077](http://dev.ckeditor.com/ticket/9077), [#8710](http://dev.ckeditor.com/ticket/8710), [#8530](http://dev.ckeditor.com/ticket/8530), [#9019](http://dev.ckeditor.com/ticket/9019), [#6181](http://dev.ckeditor.com/ticket/6181), [#7876](http://dev.ckeditor.com/ticket/7876), [#6906](http://dev.ckeditor.com/ticket/6906).
+* [#10042](http://dev.ckeditor.com/ticket/10042): Introduced [`config.title`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-title) setting to change the human-readable title of the editor.
+* [#9794](http://dev.ckeditor.com/ticket/9794): Added [`editor.change`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-change) event.
+* [#9923](http://dev.ckeditor.com/ticket/9923): HiDPI support in the editor UI. HiDPI icons for [Moono skin](http://ckeditor.com/addon/moono) added.
+* [#8031](http://dev.ckeditor.com/ticket/8031): Handle `required` attributes on `<textarea>` elements &mdash; introduced [`editor.required`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-required) event.
+* [#10280](http://dev.ckeditor.com/ticket/10280): Ability to replace `<textarea>` elements with the inline editor.
+
+Fixed Issues:
+
+* [#10599](http://dev.ckeditor.com/ticket/10599): [Indent](http://ckeditor.com/addon/indent) plugin is no longer required by the [List](http://ckeditor.com/addon/list) plugin.
+* [#10370](http://dev.ckeditor.com/ticket/10370): Inconsistency in data events between framed and inline editors.
+* [#10438](http://dev.ckeditor.com/ticket/10438): [FF, IE] No selection is done on an editable element on executing [`editor.setData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setData).
+
+## CKEditor 4.1.3
+
+New Features:
+
+* Added new translation: Indonesian.
+
+Fixed Issues:
+
+* [#10644](http://dev.ckeditor.com/ticket/10644): Fixed a critical bug when pasting plain text in Blink-based browsers.
+* [#5189](http://dev.ckeditor.com/ticket/5189): [Find/Replace](http://ckeditor.com/addon/find) dialog window: rename "Cancel" button to "Close".
+* [#10562](http://dev.ckeditor.com/ticket/10562): [Housekeeping] Unified CSS gradient filter formats in the [Moono](http://ckeditor.com/addon/moono) skin.
+* [#10537](http://dev.ckeditor.com/ticket/10537): Advanced Content Filter should register a default rule for [`config.shiftEnterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-shiftEnterMode).
+* [#10610](http://dev.ckeditor.com/ticket/10610): [`CKEDITOR.dialog.addIframe()`](http://docs.ckeditor.com/#!/api/CKEDITOR.dialog-static-method-addIframe) incorrectly sets the iframe size in dialog windows.
+
+## CKEditor 4.1.2
+
+New Features:
+
+* Added new translation: Sinhala.
+
+Fixed Issues:
+
+* [#10339](http://dev.ckeditor.com/ticket/10339): Fixed: Error thrown when inserted data was totally stripped out after filtering and processing.
+* [#10298](http://dev.ckeditor.com/ticket/10298): Fixed: Data processor breaks attributes containing protected parts.
+* [#10367](http://dev.ckeditor.com/ticket/10367): Fixed: [`editable.insertText()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editable-method-insertText) loses characters when `RegExp` replace controls are being inserted.
+* [#10165](http://dev.ckeditor.com/ticket/10165): [IE] Access denied error when `document.domain` has been altered.
+* [#9761](http://dev.ckeditor.com/ticket/9761): Update the *Backspace* key state in [`keystrokeHandler.blockedKeystrokes`](http://docs.ckeditor.com/#!/api/CKEDITOR.keystrokeHandler-property-blockedKeystrokes) when calling [`editor.setReadOnly()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setReadOnly).
+* [#6504](http://dev.ckeditor.com/ticket/6504): Fixed: Race condition while loading several [`config.customConfig`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-customConfig) files.
+* [#10146](http://dev.ckeditor.com/ticket/10146): [Firefox] Empty lines are being removed while [`config.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode) is [`CKEDITOR.ENTER_BR`](http://docs.ckeditor.com/#!/api/CKEDITOR-property-ENTER_BR).
+* [#10360](http://dev.ckeditor.com/ticket/10360): Fixed: ARIA `role="application"` should not be used for dialog windows.
+* [#10361](http://dev.ckeditor.com/ticket/10361): Fixed: ARIA `role="application"` should not be used for floating panels.
+* [#10510](http://dev.ckeditor.com/ticket/10510): Introduced unique voice labels to differentiate between different editor instances.
+* [#9945](http://dev.ckeditor.com/ticket/9945): [iOS] Scrolling not possible on iPad.
+* [#10389](http://dev.ckeditor.com/ticket/10389): Fixed: Invalid HTML in the "Text and Table" template.
+* [WebSpellChecker](http://ckeditor.com/addon/wsc) plugin user interface was changed to match CKEditor 4 style.
+
+## CKEditor 4.1.1
+
+New Features:
+
+* Added new translation: Albanian.
+
+Fixed Issues:
+
+* [#10172](http://dev.ckeditor.com/ticket/10172): Pressing *Delete* or *Backspace* in an empty table cell moves the cursor to the next/previous cell.
+* [#10219](http://dev.ckeditor.com/ticket/10219): Error thrown when destroying an editor instance in parallel with a `mouseup` event.
+* [#10265](http://dev.ckeditor.com/ticket/10265): Wrong loop type in the [File Browser](http://ckeditor.com/addon/filebrowser) plugin.
+* [#10249](http://dev.ckeditor.com/ticket/10249): Wrong undo/redo states at start.
+* [#10268](http://dev.ckeditor.com/ticket/10268): [Show Blocks](http://ckeditor.com/addon/showblocks) does not recover after switching to Source view.
+* [#9995](http://dev.ckeditor.com/ticket/9995): HTML code in the `<textarea>` should not be modified by the [`htmlDataProcessor`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlDataProcessor).
+* [#10320](http://dev.ckeditor.com/ticket/10320): [Justify](http://ckeditor.com/addon/justify) plugin should add elements to Advanced Content Filter based on current [Enter mode](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode).
+* [#10260](http://dev.ckeditor.com/ticket/10260): Fixed: Advanced Content Filter blocks [`tabSpaces`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-tabSpaces). Unified `data-cke-*` attributes filtering.
+* [#10315](http://dev.ckeditor.com/ticket/10315): [WebKit] [Undo manager](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.undo.UndoManager) should not record snapshots after a filling character was added/removed.
+* [#10291](http://dev.ckeditor.com/ticket/10291): [WebKit] Space after a filling character should be secured.
+* [#10330](http://dev.ckeditor.com/ticket/10330): [WebKit] The filling character is not removed on `keydown` in specific cases.
+* [#10285](http://dev.ckeditor.com/ticket/10285): Fixed: Styled text pasted from MS Word causes an infinite loop.
+* [#10131](http://dev.ckeditor.com/ticket/10131): Fixed: [`undoManager.update()`](http://docs.ckeditor.com/#!/api/CKEDITOR.plugins.undo.UndoManager-method-update) does not refresh the command state.
+* [#10337](http://dev.ckeditor.com/ticket/10337): Fixed: Unable to remove `<s>` using [Remove Format](http://ckeditor.com/addon/removeformat).
+
+## CKEditor 4.1
+
+Fixed Issues:
+
+* [#10192](http://dev.ckeditor.com/ticket/10192): Closing lists with the *Enter* key does not work with [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) in several cases.
+* [#10191](http://dev.ckeditor.com/ticket/10191): Fixed allowed content rules unification, so the [`filter.allowedContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter-property-allowedContent) property always contains rules in the same format.
+* [#10224](http://dev.ckeditor.com/ticket/10224): Advanced Content Filter does not remove non-empty `<a>` elements anymore.
+* Minor issues in plugin integration with Advanced Content Filter:
+ * [#10166](http://dev.ckeditor.com/ticket/10166): Added transformation from the `align` attribute to `float` style to preserve backward compatibility after the introduction of Advanced Content Filter.
+ * [#10195](http://dev.ckeditor.com/ticket/10195): [Image](http://ckeditor.com/addon/image) plugin no longer registers rules for links to Advanced Content Filter.
+ * [#10213](http://dev.ckeditor.com/ticket/10213): [Justify](http://ckeditor.com/addon/justify) plugin is now correctly registering rules to Advanced Content Filter when [`config.justifyClasses`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-justifyClasses) is defined.
+
+## CKEditor 4.1 RC
+
+New Features:
+
+* [#9829](http://dev.ckeditor.com/ticket/9829): Advanced Content Filter - data and features activation based on editor configuration.
+
+ Brand new data filtering system that works in 2 modes:
+
+ * Based on loaded features (toolbar items, plugins) - the data will be filtered according to what the editor in its
+ current configuration can handle.
+ * Based on [`config.allowedContent`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent) rules - the data
+ will be filtered and the editor features (toolbar items, commands, keystrokes) will be enabled if they are allowed.
+
+ See the `datafiltering.html` sample, [guides](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) and [`CKEDITOR.filter` API documentation](http://docs.ckeditor.com/#!/api/CKEDITOR.filter).
+* [#9387](http://dev.ckeditor.com/ticket/9387): Reintroduced [Shared Spaces](http://ckeditor.com/addon/sharedspace) - the ability to display toolbar and bottom editor space in selected locations and to share them by different editor instances.
+* [#9907](http://dev.ckeditor.com/ticket/9907): Added the [`contentPreview`](http://docs.ckeditor.com/#!/api/CKEDITOR-event-contentPreview) event for preview data manipulation.
+* [#9713](http://dev.ckeditor.com/ticket/9713): Introduced the [Source Dialog](http://ckeditor.com/addon/sourcedialog) plugin that brings raw HTML editing for inline editor instances.
+* Included in [#9829](http://dev.ckeditor.com/ticket/9829): Introduced new events, [`toHtml`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-toHtml) and [`toDataFormat`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-toDataFormat), allowing for better integration with data processing.
+* [#9981](http://dev.ckeditor.com/ticket/9981): Added ability to filter [`htmlParser.fragment`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.fragment), [`htmlParser.element`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.element) etc. by many [`htmlParser.filter`](http://docs.ckeditor.com/#!/api/CKEDITOR.htmlParser.filter)s before writing structure to an HTML string.
+* Included in [#10103](http://dev.ckeditor.com/ticket/10103):
+ * Introduced the [`editor.status`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-status) property to make it easier to check the current status of the editor.
+ * Default [`command`](http://docs.ckeditor.com/#!/api/CKEDITOR.command) state is now [`CKEDITOR.TRISTATE_DISABLE`](http://docs.ckeditor.com/#!/api/CKEDITOR-property-TRISTATE_DISABLED). It will be activated on [`editor.instanceReady`](http://docs.ckeditor.com/#!/api/CKEDITOR-event-instanceReady) or immediately after being added if the editor is already initialized.
+* [#9796](http://dev.ckeditor.com/ticket/9796): Introduced `<s>` as a default tag for strikethrough, which replaces obsolete `<strike>` in HTML5.
+
+## CKEditor 4.0.3
+
+Fixed Issues:
+
+* [#10196](http://dev.ckeditor.com/ticket/10196): Fixed context menus not opening with keyboard shortcuts when [Autogrow](http://ckeditor.com/addon/autogrow) is enabled.
+* [#10212](http://dev.ckeditor.com/ticket/10212): [IE7-10] Undo command throws errors after multiple switches between Source and WYSIWYG view.
+* [#10219](http://dev.ckeditor.com/ticket/10219): [Inline editor] Error thrown after calling [`editor.destroy()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-destroy).
+
+## CKEditor 4.0.2
+
+Fixed Issues:
+
+* [#9779](http://dev.ckeditor.com/ticket/9779): Fixed overriding [`CKEDITOR.getUrl()`](http://docs.ckeditor.com/#!/api/CKEDITOR-method-getUrl) with `CKEDITOR_GETURL`.
+* [#9772](http://dev.ckeditor.com/ticket/9772): Custom buttons in the dialog window footer have different look and size ([Moono](http://ckeditor.com/addon/moono), [Kama](http://ckeditor.com/addon/kama) skins).
+* [#9029](http://dev.ckeditor.com/ticket/9029): Custom styles added with the [`stylesSet.add()`](http://docs.ckeditor.com/#!/api/CKEDITOR.stylesSet-method-add) are displayed in the wrong order.
+* [#9887](http://dev.ckeditor.com/ticket/9887): Disable [Magic Line](http://ckeditor.com/addon/magicline) when [`editor.readOnly`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-property-readOnly) is set.
+* [#9882](http://dev.ckeditor.com/ticket/9882): Fixed empty document title on [`editor.getData()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getData) if set via the Document Properties dialog window.
+* [#9773](http://dev.ckeditor.com/ticket/9773): Fixed rendering problems with selection fields in the Kama skin.
+* [#9851](http://dev.ckeditor.com/ticket/9851): The [`selectionChange`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-selectionChange) event is not fired when mouse selection ended outside editable.
+* [#9903](http://dev.ckeditor.com/ticket/9903): [Inline editor] Bad positioning of floating space with page horizontal scroll.
+* [#9872](http://dev.ckeditor.com/ticket/9872): [`editor.checkDirty()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-checkDirty) returns `true` when called onload. Removed the obsolete `editor.mayBeDirty` flag.
+* [#9893](http://dev.ckeditor.com/ticket/9893): [IE] Fixed broken toolbar when editing mixed direction content in Quirks mode.
+* [#9845](http://dev.ckeditor.com/ticket/9845): Fixed TAB navigation in the [Link](http://ckeditor.com/addon/link) dialog window when the Anchor option is used and no anchors are available.
+* [#9883](http://dev.ckeditor.com/ticket/9883): Maximizing was making the entire page editable with [divarea](http://ckeditor.com/addon/divarea)-based editors.
+* [#9940](http://dev.ckeditor.com/ticket/9940): [Firefox] Navigating back to a page with the editor was making the entire page editable.
+* [#9966](http://dev.ckeditor.com/ticket/9966): Fixed: Unable to type square brackets with French keyboard layout. Changed [Magic Line](http://ckeditor.com/addon/magicline) keystrokes.
+* [#9507](http://dev.ckeditor.com/ticket/9507): [Firefox] Selection is moved before editable position when the editor is focused for the first time.
+* [#9947](http://dev.ckeditor.com/ticket/9947): [WebKit] Editor overflows parent container in some edge cases.
+* [#10105](http://dev.ckeditor.com/ticket/10105): Fixed: Broken [sourcearea](http://ckeditor.com/addon/sourcearea) view when an RTL language is set.
+* [#10123](http://dev.ckeditor.com/ticket/10123): [WebKit] Fixed: Several dialog windows have broken layout since the latest WebKit release.
+* [#10152](http://dev.ckeditor.com/ticket/10152): Fixed: Invalid ARIA property used on menu items.
+
+## CKEditor 4.0.1.1
+
+Fixed Issues:
+
+* Security update: Added protection against XSS attack and possible path disclosure in the PHP sample.
+
+## CKEditor 4.0.1
+
+Fixed Issues:
+
+* [#9655](http://dev.ckeditor.com/ticket/9655): Support for IE Quirks Mode in the new [Moono skin](http://ckeditor.com/addon/moono).
+* Accessibility issues (mainly in inline editor): [#9364](http://dev.ckeditor.com/ticket/9364), [#9368](http://dev.ckeditor.com/ticket/9368), [#9369](http://dev.ckeditor.com/ticket/9369), [#9370](http://dev.ckeditor.com/ticket/9370), [#9541](http://dev.ckeditor.com/ticket/9541), [#9543](http://dev.ckeditor.com/ticket/9543), [#9841](http://dev.ckeditor.com/ticket/9841), [#9844](http://dev.ckeditor.com/ticket/9844).
+* [Magic Line](http://ckeditor.com/addon/magicline) plugin:
+ * [#9481](http://dev.ckeditor.com/ticket/9481): Added accessibility support for Magic Line.
+ * [#9509](http://dev.ckeditor.com/ticket/9509): Added Magic Line support for forms.
+ * [#9573](http://dev.ckeditor.com/ticket/9573): Magic Line does not disappear on `mouseout` in a specific case.
+* [#9754](http://dev.ckeditor.com/ticket/9754): [WebKit] Cutting & pasting simple unformatted text generates an inline wrapper in WebKit browsers.
+* [#9456](http://dev.ckeditor.com/ticket/9456): [Chrome] Properly paste bullet list style from MS Word.
+* [#9699](http://dev.ckeditor.com/ticket/9699), [#9758](http://dev.ckeditor.com/ticket/9758): Improved selection locking when selecting by dragging.
+* Context menu:
+ * [#9712](http://dev.ckeditor.com/ticket/9712): Opening the context menu destroys editor focus.
+ * [#9366](http://dev.ckeditor.com/ticket/9366): Context menu should be displayed over the floating toolbar.
+ * [#9706](http://dev.ckeditor.com/ticket/9706): Context menu generates a JavaScript error in inline mode when the editor is attached to a header element.
+* [#9800](http://dev.ckeditor.com/ticket/9800): Hide float panel when resizing the window.
+* [#9721](http://dev.ckeditor.com/ticket/9721): Padding in content of div-based editor puts the editing area under the bottom UI space.
+* [#9528](http://dev.ckeditor.com/ticket/9528): Host page `box-sizing` style should not influence the editor UI elements.
+* [#9503](http://dev.ckeditor.com/ticket/9503): [Form Elements](http://ckeditor.com/addon/forms) plugin adds context menu listeners only on supported input types. Added support for `tel`, `email`, `search` and `url` input types.
+* [#9769](http://dev.ckeditor.com/ticket/9769): Improved floating toolbar positioning in a narrow window.
+* [#9875](http://dev.ckeditor.com/ticket/9875): Table dialog window does not populate width correctly.
+* [#8675](http://dev.ckeditor.com/ticket/8675): Deleting cells in a nested table removes the outer table cell.
+* [#9815](http://dev.ckeditor.com/ticket/9815): Cannot edit dialog window fields in an editor initialized in the jQuery UI modal dialog.
+* [#8888](http://dev.ckeditor.com/ticket/8888): CKEditor dialog windows do not show completely in a small window.
+* [#9360](http://dev.ckeditor.com/ticket/9360): [Inline editor] Blocks shown for a `<div>` element stay permanently even after the user exits editing the `<div>`.
+* [#9531](http://dev.ckeditor.com/ticket/9531): [Firefox & Inline editor] Toolbar is lost when closing the Format drop-down list by clicking its button.
+* [#9553](http://dev.ckeditor.com/ticket/9553): Table width incorrectly set when the `border-width` style is specified.
+* [#9594](http://dev.ckeditor.com/ticket/9594): Cannot tab past CKEditor when it is in read-only mode.
+* [#9658](http://dev.ckeditor.com/ticket/9658): [IE9] Justify not working on selected images.
+* [#9686](http://dev.ckeditor.com/ticket/9686): Added missing contents styles for `<pre>` elements.
+* [#9709](http://dev.ckeditor.com/ticket/9709): [Paste from Word](http://ckeditor.com/addon/pastefromword) should not depend on configuration from other styles.
+* [#9726](http://dev.ckeditor.com/ticket/9726): Removed [Color Dialog](http://ckeditor.com/addon/colordialog) plugin dependency from [Table Tools](http://ckeditor.com/addon/tabletools).
+* [#9765](http://dev.ckeditor.com/ticket/9765): Toolbar Collapse command documented incorrectly in the [Accessibility Instructions](http://ckeditor.com/addon/a11yhelp) dialog window.
+* [#9771](http://dev.ckeditor.com/ticket/9771): [WebKit & Opera] Fixed scrolling issues when pasting.
+* [#9787](http://dev.ckeditor.com/ticket/9787): [IE9] `onChange` is not fired for checkboxes in dialogs.
+* [#9842](http://dev.ckeditor.com/ticket/9842): [Firefox 17] When opening a toolbar menu for the first time and pressing the *Down Arrow* key, focus goes to the next toolbar button instead of the menu options.
+* [#9847](http://dev.ckeditor.com/ticket/9847): [Elements Path](http://ckeditor.com/addon/elementspath) should not be initialized in the inline editor.
+* [#9853](http://dev.ckeditor.com/ticket/9853): [`editor.addRemoveFormatFilter()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-addRemoveFormatFilter) is exposed before it really works.
+* [#8893](http://dev.ckeditor.com/ticket/8893): Value of the [`pasteFromWordCleanupFile`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordCleanupFile) configuration option is now taken from the instance configuration.
+* [#9693](http://dev.ckeditor.com/ticket/9693): Removed "Live Preview" checkbox from UI color picker.
+
+
+## CKEditor 4.0
+
+The first stable release of the new CKEditor 4 code line.
+
+The CKEditor JavaScript API has been kept compatible with CKEditor 4, whenever
+possible. The list of relevant changes can be found in the [API Changes page of
+the CKEditor 4 documentation][1].
+
+[1]: http://docs.ckeditor.com/#!/guide/dev_api_changes "API Changes"
diff --git a/js/ckeditor/LICENSE.md b/js/ckeditor/LICENSE.md
new file mode 100644
index 0000000..72cc97c
--- /dev/null
+++ b/js/ckeditor/LICENSE.md
@@ -0,0 +1,1264 @@
+Software License Agreement
+==========================
+
+CKEditor - The text editor for Internet - http://ckeditor.com
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+ - GNU General Public License Version 2 or later (the "GPL")
+ http://www.gnu.org/licenses/gpl.html
+ (See Appendix A)
+
+ - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ http://www.gnu.org/licenses/lgpl.html
+ (See Appendix B)
+
+ - Mozilla Public License Version 1.1 or later (the "MPL")
+ http://www.mozilla.org/MPL/MPL-1.1.html
+ (See Appendix C)
+
+You are not required to, but if you want to explicitly declare the
+license you have chosen to be bound to when using, reproducing,
+modifying and distributing this software, just include a text file
+titled "legal.txt" in your version of this software, indicating your
+license choice. In any case, your choice will not restrict any
+recipient of your version of this software to use, reproduce, modify
+and distribute this software under any of the above licenses.
+
+Sources of Intellectual Property Included in CKEditor
+-----------------------------------------------------
+
+Where not otherwise indicated, all CKEditor content is authored by
+CKSource engineers and consists of CKSource-owned intellectual
+property. In some specific instances, CKEditor will incorporate work
+done by developers outside of CKSource with their express permission.
+
+Trademarks
+----------
+
+CKEditor is a trademark of CKSource - Frederico Knabben. All other brand
+and product names are trademarks, registered trademarks or service
+marks of their respective holders.
+
+---
+
+Appendix A: The GPL License
+---------------------------
+
+GNU GENERAL PUBLIC LICENSE
+Version 2, June 1991
+
+ Copyright (C) 1989, 1991 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.
+
+Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software-to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, 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 or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+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 give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) 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; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, 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 executable. However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+ 5. 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 Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+ 7. 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 Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 Program
+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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+
+Appendix B: The LGPL License
+----------------------------
+
+GNU LESSER GENERAL PUBLIC LICENSE
+Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
+
+
+Appendix C: The MPL License
+---------------------------
+
+MOZILLA PUBLIC LICENSE
+Version 1.1
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in
+ compliance with the License. You may obtain a copy of the License at
+ http://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
diff --git a/js/ckeditor/README.md b/js/ckeditor/README.md
new file mode 100644
index 0000000..c5a55cd
--- /dev/null
+++ b/js/ckeditor/README.md
@@ -0,0 +1,39 @@
+CKEditor 4
+==========
+
+Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+http://ckeditor.com - See LICENSE.md for license information.
+
+CKEditor is a text editor to be used inside web pages. It's not a replacement
+for desktop text editors like Word or OpenOffice, but a component to be used as
+part of web applications and websites.
+
+## Documentation
+
+The full editor documentation is available online at the following address:
+http://docs.ckeditor.com
+
+## Installation
+
+Installing CKEditor is an easy task. Just follow these simple steps:
+
+ 1. **Download** the latest version from the CKEditor website:
+ http://ckeditor.com. You should have already completed this step, but be
+ sure you have the very latest version.
+ 2. **Extract** (decompress) the downloaded file into the root of your website.
+
+**Note:** CKEditor is by default installed in the `ckeditor` folder. You can
+place the files in whichever you want though.
+
+## Checking Your Installation
+
+The editor comes with a few sample pages that can be used to verify that
+installation proceeded properly. Take a look at the `samples` directory.
+
+To test your installation, just call the following page at your website:
+
+ http://<your site>/<CKEditor installation path>/samples/index.html
+
+For example:
+
+ http://www.example.com/ckeditor/samples/index.html
diff --git a/js/ckeditor/adapters/jquery.js b/js/ckeditor/adapters/jquery.js
new file mode 100644
index 0000000..704635f
--- /dev/null
+++ b/js/ckeditor/adapters/jquery.js
@@ -0,0 +1,10 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(a){CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;"undefined"!=typeof a&&(a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a},ckeditor:function(g,d){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g))var k=d,d=g,g=k;var i=[],d=d||{};this.each(function(){var b=
+a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,j=new a.Deferred;i.push(j.promise());if(c&&!f)g&&g.apply(c,[this]),j.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),j.resolve()):setTimeout(arguments.callee,100)},0)},null,null,9999);else{if(d.autoUpdateElement||"undefined"==typeof d.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)d.autoUpdateElementJquery=!0;d.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",
+!0);c=a(this).is("textarea")?CKEDITOR.replace(h,d):CKEDITOR.inline(h,d);b.data("ckeditorInstance",c);c.on("instanceReady",function(d){var e=d.editor;setTimeout(function(){if(e.element){d.removeListener();e.on("dataReady",function(){b.trigger("dataReady.ckeditor",[e])});e.on("setData",function(a){b.trigger("setData.ckeditor",[e,a.data])});e.on("getData",function(a){b.trigger("getData.ckeditor",[e,a.data])},999);e.on("destroy",function(){b.trigger("destroy.ckeditor",[e])});e.on("save",function(){a(h.form).submit();
+return!1},null,null,20);if(e.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){e.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize",c)})}e.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[e]);g&&g.apply(e,[h]);j.resolve()}else setTimeout(arguments.callee,
+100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,i).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}}),CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(d){if(arguments.length){var k=this,i=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(d,function(){f.resolve()});i.push(f.promise());
+return!0}return g.call(b,d)});if(i.length){var b=new a.Deferred;a.when.apply(this,i).done(function(){b.resolveWith(k)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}})))})(window.jQuery); \ No newline at end of file
diff --git a/js/ckeditor/build-config.js b/js/ckeditor/build-config.js
new file mode 100644
index 0000000..6c34525
--- /dev/null
+++ b/js/ckeditor/build-config.js
@@ -0,0 +1,157 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+
+/**
+ * This file was added automatically by CKEditor builder.
+ * You may re-use it at any time to build CKEditor again.
+ *
+ * If you would like to build CKEditor online again
+ * (for example to upgrade), visit one the following links:
+ *
+ * (1) http://ckeditor.com/builder
+ * Visit online builder to build CKEditor from scratch.
+ *
+ * (2) http://ckeditor.com/builder/e41bccb8290b6d530f8478ddafe95c48
+ * Visit online builder to build CKEditor, starting with the same setup as before.
+ *
+ * (3) http://ckeditor.com/builder/download/e41bccb8290b6d530f8478ddafe95c48
+ * Straight download link to the latest version of CKEditor (Optimized) with the same setup as before.
+ *
+ * NOTE:
+ * This file is not used by CKEditor, you may remove it.
+ * Changing this file will not change your CKEditor configuration.
+ */
+
+var CKBUILDER_CONFIG = {
+ skin: 'moono',
+ preset: 'standard',
+ ignore: [
+ '.bender',
+ 'bender.js',
+ 'bender-err.log',
+ 'bender-out.log',
+ 'dev',
+ '.DS_Store',
+ '.editorconfig',
+ '.gitattributes',
+ '.gitignore',
+ 'gruntfile.js',
+ '.idea',
+ '.jscsrc',
+ '.jshintignore',
+ '.jshintrc',
+ '.mailmap',
+ 'node_modules',
+ 'package.json',
+ 'README.md',
+ 'tests'
+ ],
+ plugins : {
+ 'a11yhelp' : 1,
+ 'about' : 1,
+ 'basicstyles' : 1,
+ 'blockquote' : 1,
+ 'clipboard' : 1,
+ 'contextmenu' : 1,
+ 'elementspath' : 1,
+ 'enterkey' : 1,
+ 'entities' : 1,
+ 'filebrowser' : 1,
+ 'floatingspace' : 1,
+ 'format' : 1,
+ 'horizontalrule' : 1,
+ 'htmlwriter' : 1,
+ 'image' : 1,
+ 'indentlist' : 1,
+ 'link' : 1,
+ 'list' : 1,
+ 'magicline' : 1,
+ 'maximize' : 1,
+ 'pastefromword' : 1,
+ 'pastetext' : 1,
+ 'removeformat' : 1,
+ 'resize' : 1,
+ 'scayt' : 1,
+ 'showborders' : 1,
+ 'sourcearea' : 1,
+ 'specialchar' : 1,
+ 'stylescombo' : 1,
+ 'tab' : 1,
+ 'table' : 1,
+ 'tabletools' : 1,
+ 'toolbar' : 1,
+ 'undo' : 1,
+ 'wsc' : 1,
+ 'wysiwygarea' : 1
+ },
+ languages : {
+ 'af' : 1,
+ 'ar' : 1,
+ 'bg' : 1,
+ 'bn' : 1,
+ 'bs' : 1,
+ 'ca' : 1,
+ 'cs' : 1,
+ 'cy' : 1,
+ 'da' : 1,
+ 'de' : 1,
+ 'el' : 1,
+ 'en' : 1,
+ 'en-au' : 1,
+ 'en-ca' : 1,
+ 'en-gb' : 1,
+ 'eo' : 1,
+ 'es' : 1,
+ 'et' : 1,
+ 'eu' : 1,
+ 'fa' : 1,
+ 'fi' : 1,
+ 'fo' : 1,
+ 'fr' : 1,
+ 'fr-ca' : 1,
+ 'gl' : 1,
+ 'gu' : 1,
+ 'he' : 1,
+ 'hi' : 1,
+ 'hr' : 1,
+ 'hu' : 1,
+ 'id' : 1,
+ 'is' : 1,
+ 'it' : 1,
+ 'ja' : 1,
+ 'ka' : 1,
+ 'km' : 1,
+ 'ko' : 1,
+ 'ku' : 1,
+ 'lt' : 1,
+ 'lv' : 1,
+ 'mk' : 1,
+ 'mn' : 1,
+ 'ms' : 1,
+ 'nb' : 1,
+ 'nl' : 1,
+ 'no' : 1,
+ 'pl' : 1,
+ 'pt' : 1,
+ 'pt-br' : 1,
+ 'ro' : 1,
+ 'ru' : 1,
+ 'si' : 1,
+ 'sk' : 1,
+ 'sl' : 1,
+ 'sq' : 1,
+ 'sr' : 1,
+ 'sr-latn' : 1,
+ 'sv' : 1,
+ 'th' : 1,
+ 'tr' : 1,
+ 'tt' : 1,
+ 'ug' : 1,
+ 'uk' : 1,
+ 'vi' : 1,
+ 'zh' : 1,
+ 'zh-cn' : 1
+ }
+}; \ No newline at end of file
diff --git a/js/ckeditor/ckeditor.js b/js/ckeditor/ckeditor.js
new file mode 100644
index 0000000..9ab3617
--- /dev/null
+++ b/js/ckeditor/ckeditor.js
@@ -0,0 +1,946 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){if(window.CKEDITOR&&window.CKEDITOR.dom)return;window.CKEDITOR||(window.CKEDITOR=function(){var a=/(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i,f={timestamp:"F0RD",version:"4.4.7",revision:"3a35b3d",rnd:Math.floor(900*Math.random())+100,_:{pending:[],basePathSrcPattern:a},status:"unloaded",basePath:function(){var e=window.CKEDITOR_BASEPATH||"";if(!e)for(var d=document.getElementsByTagName("script"),c=0;c<d.length;c++){var b=d[c].src.match(a);if(b){e=b[1];break}}-1==e.indexOf(":/")&&"//"!=e.slice(0,2)&&(e=0===e.indexOf("/")?location.href.match(/^.*?:\/\/[^\/]*/)[0]+
+e:location.href.match(/^[^\?]*\/(?:)/)[0]+e);if(!e)throw'The CKEditor installation path could not be automatically detected. Please set the global variable "CKEDITOR_BASEPATH" before creating editor instances.';return e}(),getUrl:function(a){-1==a.indexOf(":/")&&0!==a.indexOf("/")&&(a=this.basePath+a);this.timestamp&&("/"!=a.charAt(a.length-1)&&!/[&?]t=/.test(a))&&(a+=(0<=a.indexOf("?")?"&":"?")+"t="+this.timestamp);return a},domReady:function(){function a(){try{document.addEventListener?(document.removeEventListener("DOMContentLoaded",
+a,!1),d()):document.attachEvent&&"complete"===document.readyState&&(document.detachEvent("onreadystatechange",a),d())}catch(c){}}function d(){for(var a;a=c.shift();)a()}var c=[];return function(d){function b(){try{document.documentElement.doScroll("left")}catch(m){setTimeout(b,1);return}a()}c.push(d);"complete"===document.readyState&&setTimeout(a,1);if(1==c.length)if(document.addEventListener)document.addEventListener("DOMContentLoaded",a,!1),window.addEventListener("load",a,!1);else if(document.attachEvent){document.attachEvent("onreadystatechange",
+a);window.attachEvent("onload",a);d=!1;try{d=!window.frameElement}catch(f){}document.documentElement.doScroll&&d&&b()}}}()},b=window.CKEDITOR_GETURL;if(b){var c=f.getUrl;f.getUrl=function(a){return b.call(f,a)||c.call(f,a)}}return f}());
+CKEDITOR.event||(CKEDITOR.event=function(){},CKEDITOR.event.implementOn=function(a){var f=CKEDITOR.event.prototype,b;for(b in f)a[b]==null&&(a[b]=f[b])},CKEDITOR.event.prototype=function(){function a(a){var e=f(this);return e[a]||(e[a]=new b(a))}var f=function(a){a=a.getPrivate&&a.getPrivate()||a._||(a._={});return a.events||(a.events={})},b=function(a){this.name=a;this.listeners=[]};b.prototype={getListenerIndex:function(a){for(var e=0,d=this.listeners;e<d.length;e++)if(d[e].fn==a)return e;return-1}};
+return{define:function(b,e){var d=a.call(this,b);CKEDITOR.tools.extend(d,e,true)},on:function(b,e,d,f,k){function j(a,m,y,s){a={name:b,sender:this,editor:a,data:m,listenerData:f,stop:y,cancel:s,removeListener:g};return e.call(d,a)===false?false:a.data}function g(){y.removeListener(b,e)}var m=a.call(this,b);if(m.getListenerIndex(e)<0){m=m.listeners;d||(d=this);isNaN(k)&&(k=10);var y=this;j.fn=e;j.priority=k;for(var s=m.length-1;s>=0;s--)if(m[s].priority<=k){m.splice(s+1,0,j);return{removeListener:g}}m.unshift(j)}return{removeListener:g}},
+once:function(){var a=Array.prototype.slice.call(arguments),e=a[1];a[1]=function(a){a.removeListener();return e.apply(this,arguments)};return this.on.apply(this,a)},capture:function(){CKEDITOR.event.useCapture=1;var a=this.on.apply(this,arguments);CKEDITOR.event.useCapture=0;return a},fire:function(){var a=0,e=function(){a=1},d=0,b=function(){d=1};return function(k,j,g){var m=f(this)[k],k=a,y=d;a=d=0;if(m){var s=m.listeners;if(s.length)for(var s=s.slice(0),w,q=0;q<s.length;q++){if(m.errorProof)try{w=
+s[q].call(this,g,j,e,b)}catch(t){}else w=s[q].call(this,g,j,e,b);w===false?d=1:typeof w!="undefined"&&(j=w);if(a||d)break}}j=d?false:typeof j=="undefined"?true:j;a=k;d=y;return j}}(),fireOnce:function(a,e,d){e=this.fire(a,e,d);delete f(this)[a];return e},removeListener:function(a,e){var d=f(this)[a];if(d){var b=d.getListenerIndex(e);b>=0&&d.listeners.splice(b,1)}},removeAllListeners:function(){var a=f(this),e;for(e in a)delete a[e]},hasListeners:function(a){return(a=f(this)[a])&&a.listeners.length>
+0}}}());CKEDITOR.editor||(CKEDITOR.editor=function(){CKEDITOR._.pending.push([this,arguments]);CKEDITOR.event.call(this)},CKEDITOR.editor.prototype.fire=function(a,f){a in{instanceReady:1,loaded:1}&&(this[a]=true);return CKEDITOR.event.prototype.fire.call(this,a,f,this)},CKEDITOR.editor.prototype.fireOnce=function(a,f){a in{instanceReady:1,loaded:1}&&(this[a]=true);return CKEDITOR.event.prototype.fireOnce.call(this,a,f,this)},CKEDITOR.event.implementOn(CKEDITOR.editor.prototype));
+CKEDITOR.env||(CKEDITOR.env=function(){var a=navigator.userAgent.toLowerCase(),f={ie:a.indexOf("trident/")>-1,webkit:a.indexOf(" applewebkit/")>-1,air:a.indexOf(" adobeair/")>-1,mac:a.indexOf("macintosh")>-1,quirks:document.compatMode=="BackCompat"&&(!document.documentMode||document.documentMode<10),mobile:a.indexOf("mobile")>-1,iOS:/(ipad|iphone|ipod)/.test(a),isCustomDomain:function(){if(!this.ie)return false;var a=document.domain,d=window.location.hostname;return a!=d&&a!="["+d+"]"},secure:location.protocol==
+"https:"};f.gecko=navigator.product=="Gecko"&&!f.webkit&&!f.ie;if(f.webkit)a.indexOf("chrome")>-1?f.chrome=true:f.safari=true;var b=0;if(f.ie){b=f.quirks||!document.documentMode?parseFloat(a.match(/msie (\d+)/)[1]):document.documentMode;f.ie9Compat=b==9;f.ie8Compat=b==8;f.ie7Compat=b==7;f.ie6Compat=b<7||f.quirks}if(f.gecko){var c=a.match(/rv:([\d\.]+)/);if(c){c=c[1].split(".");b=c[0]*1E4+(c[1]||0)*100+(c[2]||0)*1}}f.air&&(b=parseFloat(a.match(/ adobeair\/(\d+)/)[1]));f.webkit&&(b=parseFloat(a.match(/ applewebkit\/(\d+)/)[1]));
+f.version=b;f.isCompatible=f.iOS&&b>=534||!f.mobile&&(f.ie&&b>6||f.gecko&&b>=2E4||f.air&&b>=1||f.webkit&&b>=522||false);f.hidpi=window.devicePixelRatio>=2;f.needsBrFiller=f.gecko||f.webkit||f.ie&&b>10;f.needsNbspFiller=f.ie&&b<11;f.cssClass="cke_browser_"+(f.ie?"ie":f.gecko?"gecko":f.webkit?"webkit":"unknown");if(f.quirks)f.cssClass=f.cssClass+" cke_browser_quirks";if(f.ie)f.cssClass=f.cssClass+(" cke_browser_ie"+(f.quirks?"6 cke_browser_iequirks":f.version));if(f.air)f.cssClass=f.cssClass+" cke_browser_air";
+if(f.iOS)f.cssClass=f.cssClass+" cke_browser_ios";if(f.hidpi)f.cssClass=f.cssClass+" cke_hidpi";return f}());
+"unloaded"==CKEDITOR.status&&function(){CKEDITOR.event.implementOn(CKEDITOR);CKEDITOR.loadFullCore=function(){if(CKEDITOR.status!="basic_ready")CKEDITOR.loadFullCore._load=1;else{delete CKEDITOR.loadFullCore;var a=document.createElement("script");a.type="text/javascript";a.src=CKEDITOR.basePath+"ckeditor.js";document.getElementsByTagName("head")[0].appendChild(a)}};CKEDITOR.loadFullCoreTimeout=0;CKEDITOR.add=function(a){(this._.pending||(this._.pending=[])).push(a)};(function(){CKEDITOR.domReady(function(){var a=
+CKEDITOR.loadFullCore,f=CKEDITOR.loadFullCoreTimeout;if(a){CKEDITOR.status="basic_ready";a&&a._load?a():f&&setTimeout(function(){CKEDITOR.loadFullCore&&CKEDITOR.loadFullCore()},f*1E3)}})})();CKEDITOR.status="basic_loaded"}();CKEDITOR.dom={};
+(function(){var a=[],f=CKEDITOR.env.gecko?"-moz-":CKEDITOR.env.webkit?"-webkit-":CKEDITOR.env.ie?"-ms-":"",b=/&/g,c=/>/g,e=/</g,d=/"/g,h=/&amp;/g,k=/&gt;/g,j=/&lt;/g,g=/&quot;/g;CKEDITOR.on("reset",function(){a=[]});CKEDITOR.tools={arrayCompare:function(a,e){if(!a&&!e)return true;if(!a||!e||a.length!=e.length)return false;for(var d=0;d<a.length;d++)if(a[d]!=e[d])return false;return true},clone:function(a){var e;if(a&&a instanceof Array){e=[];for(var d=0;d<a.length;d++)e[d]=CKEDITOR.tools.clone(a[d]);
+return e}if(a===null||typeof a!="object"||a instanceof String||a instanceof Number||a instanceof Boolean||a instanceof Date||a instanceof RegExp||a.nodeType||a.window===a)return a;e=new a.constructor;for(d in a)e[d]=CKEDITOR.tools.clone(a[d]);return e},capitalize:function(a,e){return a.charAt(0).toUpperCase()+(e?a.slice(1):a.slice(1).toLowerCase())},extend:function(a){var e=arguments.length,d,b;if(typeof(d=arguments[e-1])=="boolean")e--;else if(typeof(d=arguments[e-2])=="boolean"){b=arguments[e-1];
+e=e-2}for(var c=1;c<e;c++){var f=arguments[c],i;for(i in f)if(d===true||a[i]==null)if(!b||i in b)a[i]=f[i]}return a},prototypedCopy:function(a){var e=function(){};e.prototype=a;return new e},copy:function(a){var e={},d;for(d in a)e[d]=a[d];return e},isArray:function(a){return Object.prototype.toString.call(a)=="[object Array]"},isEmpty:function(a){for(var e in a)if(a.hasOwnProperty(e))return false;return true},cssVendorPrefix:function(a,e,d){if(d)return f+a+":"+e+";"+a+":"+e;d={};d[a]=e;d[f+a]=e;
+return d},cssStyleToDomStyle:function(){var a=document.createElement("div").style,e=typeof a.cssFloat!="undefined"?"cssFloat":typeof a.styleFloat!="undefined"?"styleFloat":"float";return function(a){return a=="float"?e:a.replace(/-./g,function(a){return a.substr(1).toUpperCase()})}}(),buildStyleHtml:function(a){for(var a=[].concat(a),e,d=[],b=0;b<a.length;b++)if(e=a[b])/@import|[{}]/.test(e)?d.push("<style>"+e+"</style>"):d.push('<link type="text/css" rel=stylesheet href="'+e+'">');return d.join("")},
+htmlEncode:function(a){return(""+a).replace(b,"&amp;").replace(c,"&gt;").replace(e,"&lt;")},htmlDecode:function(a){return a.replace(h,"&").replace(k,">").replace(j,"<")},htmlEncodeAttr:function(a){return a.replace(d,"&quot;").replace(e,"&lt;").replace(c,"&gt;")},htmlDecodeAttr:function(a){return a.replace(g,'"').replace(j,"<").replace(k,">")},getNextNumber:function(){var a=0;return function(){return++a}}(),getNextId:function(){return"cke_"+this.getNextNumber()},override:function(a,e){var d=e(a);d.prototype=
+a.prototype;return d},setTimeout:function(a,e,d,b,c){c||(c=window);d||(d=c);return c.setTimeout(function(){b?a.apply(d,[].concat(b)):a.apply(d)},e||0)},trim:function(){var a=/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;return function(e){return e.replace(a,"")}}(),ltrim:function(){var a=/^[ \t\n\r]+/g;return function(e){return e.replace(a,"")}}(),rtrim:function(){var a=/[ \t\n\r]+$/g;return function(e){return e.replace(a,"")}}(),indexOf:function(a,e){if(typeof e=="function")for(var d=0,b=a.length;d<b;d++){if(e(a[d]))return d}else{if(a.indexOf)return a.indexOf(e);
+d=0;for(b=a.length;d<b;d++)if(a[d]===e)return d}return-1},search:function(a,e){var d=CKEDITOR.tools.indexOf(a,e);return d>=0?a[d]:null},bind:function(a,e){return function(){return a.apply(e,arguments)}},createClass:function(a){var e=a.$,d=a.base,b=a.privates||a._,c=a.proto,a=a.statics;!e&&(e=function(){d&&this.base.apply(this,arguments)});if(b)var f=e,e=function(){var a=this._||(this._={}),e;for(e in b){var d=b[e];a[e]=typeof d=="function"?CKEDITOR.tools.bind(d,this):d}f.apply(this,arguments)};if(d){e.prototype=
+this.prototypedCopy(d.prototype);e.prototype.constructor=e;e.base=d;e.baseProto=d.prototype;e.prototype.base=function(){this.base=d.prototype.base;d.apply(this,arguments);this.base=arguments.callee}}c&&this.extend(e.prototype,c,true);a&&this.extend(e,a,true);return e},addFunction:function(e,d){return a.push(function(){return e.apply(d||this,arguments)})-1},removeFunction:function(e){a[e]=null},callFunction:function(e){var d=a[e];return d&&d.apply(window,Array.prototype.slice.call(arguments,1))},cssLength:function(){var a=
+/^-?\d+\.?\d*px$/,e;return function(d){e=CKEDITOR.tools.trim(d+"")+"px";return a.test(e)?e:d||""}}(),convertToPx:function(){var a;return function(e){if(!a){a=CKEDITOR.dom.element.createFromHtml('<div style="position:absolute;left:-9999px;top:-9999px;margin:0px;padding:0px;border:0px;"></div>',CKEDITOR.document);CKEDITOR.document.getBody().append(a)}if(!/%$/.test(e)){a.setStyle("width",e);return a.$.clientWidth}return e}}(),repeat:function(a,e){return Array(e+1).join(a)},tryThese:function(){for(var a,
+e=0,d=arguments.length;e<d;e++){var b=arguments[e];try{a=b();break}catch(c){}}return a},genKey:function(){return Array.prototype.slice.call(arguments).join("-")},defer:function(a){return function(){var e=arguments,d=this;window.setTimeout(function(){a.apply(d,e)},0)}},normalizeCssText:function(a,e){var d=[],b,c=CKEDITOR.tools.parseCssText(a,true,e);for(b in c)d.push(b+":"+c[b]);d.sort();return d.length?d.join(";")+";":""},convertRgbToHex:function(a){return a.replace(/(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi,
+function(a,e,d,b){a=[e,d,b];for(e=0;e<3;e++)a[e]=("0"+parseInt(a[e],10).toString(16)).slice(-2);return"#"+a.join("")})},parseCssText:function(a,e,d){var b={};if(d){d=new CKEDITOR.dom.element("span");d.setAttribute("style",a);a=CKEDITOR.tools.convertRgbToHex(d.getAttribute("style")||"")}if(!a||a==";")return b;a.replace(/&quot;/g,'"').replace(/\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,d,m){if(e){d=d.toLowerCase();d=="font-family"&&(m=m.toLowerCase().replace(/["']/g,"").replace(/\s*,\s*/g,","));
+m=CKEDITOR.tools.trim(m)}b[d]=m});return b},writeCssText:function(a,e){var d,b=[];for(d in a)b.push(d+":"+a[d]);e&&b.sort();return b.join("; ")},objectCompare:function(a,e,d){var b;if(!a&&!e)return true;if(!a||!e)return false;for(b in a)if(a[b]!=e[b])return false;if(!d)for(b in e)if(a[b]!=e[b])return false;return true},objectKeys:function(a){var e=[],d;for(d in a)e.push(d);return e},convertArrayToObject:function(a,e){var d={};arguments.length==1&&(e=true);for(var b=0,c=a.length;b<c;++b)d[a[b]]=e;
+return d},fixDomain:function(){for(var a;;)try{a=window.parent.document.domain;break}catch(e){a=a?a.replace(/.+?(?:\.|$)/,""):document.domain;if(!a)break;document.domain=a}return!!a},eventsBuffer:function(a,e){function d(){c=(new Date).getTime();b=false;e()}var b,c=0;return{input:function(){if(!b){var e=(new Date).getTime()-c;e<a?b=setTimeout(d,a-e):d()}},reset:function(){b&&clearTimeout(b);b=c=0}}},enableHtml5Elements:function(a,e){for(var d=["abbr","article","aside","audio","bdi","canvas","data",
+"datalist","details","figcaption","figure","footer","header","hgroup","mark","meter","nav","output","progress","section","summary","time","video"],b=d.length,c;b--;){c=a.createElement(d[b]);e&&a.appendChild(c)}},checkIfAnyArrayItemMatches:function(a,e){for(var d=0,b=a.length;d<b;++d)if(a[d].match(e))return true;return false},checkIfAnyObjectPropertyMatches:function(a,e){for(var d in a)if(d.match(e))return true;return false},transparentImageData:"data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw=="}})();
+CKEDITOR.dtd=function(){var a=CKEDITOR.tools.extend,f=function(a,e){for(var d=CKEDITOR.tools.clone(a),b=1;b<arguments.length;b++){var e=arguments[b],c;for(c in e)delete d[c]}return d},b={},c={},e={address:1,article:1,aside:1,blockquote:1,details:1,div:1,dl:1,fieldset:1,figure:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,hr:1,main:1,menu:1,nav:1,ol:1,p:1,pre:1,section:1,table:1,ul:1},d={command:1,link:1,meta:1,noscript:1,script:1,style:1},h={},k={"#":1},j={center:1,dir:1,noframes:1};
+a(b,{a:1,abbr:1,area:1,audio:1,b:1,bdi:1,bdo:1,br:1,button:1,canvas:1,cite:1,code:1,command:1,datalist:1,del:1,dfn:1,em:1,embed:1,i:1,iframe:1,img:1,input:1,ins:1,kbd:1,keygen:1,label:1,map:1,mark:1,meter:1,noscript:1,object:1,output:1,progress:1,q:1,ruby:1,s:1,samp:1,script:1,select:1,small:1,span:1,strong:1,sub:1,sup:1,textarea:1,time:1,u:1,"var":1,video:1,wbr:1},k,{acronym:1,applet:1,basefont:1,big:1,font:1,isindex:1,strike:1,style:1,tt:1});a(c,e,b,j);f={a:f(b,{a:1,button:1}),abbr:b,address:c,
+area:h,article:c,aside:c,audio:a({source:1,track:1},c),b:b,base:h,bdi:b,bdo:b,blockquote:c,body:c,br:h,button:f(b,{a:1,button:1}),canvas:b,caption:c,cite:b,code:b,col:h,colgroup:{col:1},command:h,datalist:a({option:1},b),dd:c,del:b,details:a({summary:1},c),dfn:b,div:c,dl:{dt:1,dd:1},dt:c,em:b,embed:h,fieldset:a({legend:1},c),figcaption:c,figure:a({figcaption:1},c),footer:c,form:c,h1:b,h2:b,h3:b,h4:b,h5:b,h6:b,head:a({title:1,base:1},d),header:c,hgroup:{h1:1,h2:1,h3:1,h4:1,h5:1,h6:1},hr:h,html:a({head:1,
+body:1},c,d),i:b,iframe:k,img:h,input:h,ins:b,kbd:b,keygen:h,label:b,legend:b,li:c,link:h,main:c,map:c,mark:b,menu:a({li:1},c),meta:h,meter:f(b,{meter:1}),nav:c,noscript:a({link:1,meta:1,style:1},b),object:a({param:1},b),ol:{li:1},optgroup:{option:1},option:k,output:b,p:b,param:h,pre:b,progress:f(b,{progress:1}),q:b,rp:b,rt:b,ruby:a({rp:1,rt:1},b),s:b,samp:b,script:k,section:c,select:{optgroup:1,option:1},small:b,source:h,span:b,strong:b,style:k,sub:b,summary:b,sup:b,table:{caption:1,colgroup:1,thead:1,
+tfoot:1,tbody:1,tr:1},tbody:{tr:1},td:c,textarea:k,tfoot:{tr:1},th:c,thead:{tr:1},time:f(b,{time:1}),title:k,tr:{th:1,td:1},track:h,u:b,ul:{li:1},"var":b,video:a({source:1,track:1},c),wbr:h,acronym:b,applet:a({param:1},c),basefont:h,big:b,center:c,dialog:h,dir:{li:1},font:b,isindex:h,noframes:c,strike:b,tt:b};a(f,{$block:a({audio:1,dd:1,dt:1,figcaption:1,li:1,video:1},e,j),$blockLimit:{article:1,aside:1,audio:1,body:1,caption:1,details:1,dir:1,div:1,dl:1,fieldset:1,figcaption:1,figure:1,footer:1,
+form:1,header:1,hgroup:1,main:1,menu:1,nav:1,ol:1,section:1,table:1,td:1,th:1,tr:1,ul:1,video:1},$cdata:{script:1,style:1},$editable:{address:1,article:1,aside:1,blockquote:1,body:1,details:1,div:1,fieldset:1,figcaption:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,main:1,nav:1,p:1,pre:1,section:1},$empty:{area:1,base:1,basefont:1,br:1,col:1,command:1,dialog:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1},$inline:b,$list:{dl:1,ol:1,
+ul:1},$listItem:{dd:1,dt:1,li:1},$nonBodyContent:a({body:1,head:1,html:1},f.head),$nonEditable:{applet:1,audio:1,button:1,embed:1,iframe:1,map:1,object:1,option:1,param:1,script:1,textarea:1,video:1},$object:{applet:1,audio:1,button:1,hr:1,iframe:1,img:1,input:1,object:1,select:1,table:1,textarea:1,video:1},$removeEmpty:{abbr:1,acronym:1,b:1,bdi:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,mark:1,meter:1,output:1,q:1,ruby:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,
+sub:1,sup:1,time:1,tt:1,u:1,"var":1},$tabIndex:{a:1,area:1,button:1,input:1,object:1,select:1,textarea:1},$tableContent:{caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1},$transparent:{a:1,audio:1,canvas:1,del:1,ins:1,map:1,noscript:1,object:1,video:1},$intermediate:{caption:1,colgroup:1,dd:1,dt:1,figcaption:1,legend:1,li:1,optgroup:1,option:1,rp:1,rt:1,summary:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1}});return f}();CKEDITOR.dom.event=function(a){this.$=a};
+CKEDITOR.dom.event.prototype={getKey:function(){return this.$.keyCode||this.$.which},getKeystroke:function(){var a=this.getKey();if(this.$.ctrlKey||this.$.metaKey)a=a+CKEDITOR.CTRL;this.$.shiftKey&&(a=a+CKEDITOR.SHIFT);this.$.altKey&&(a=a+CKEDITOR.ALT);return a},preventDefault:function(a){var f=this.$;f.preventDefault?f.preventDefault():f.returnValue=false;a&&this.stopPropagation()},stopPropagation:function(){var a=this.$;a.stopPropagation?a.stopPropagation():a.cancelBubble=true},getTarget:function(){var a=
+this.$.target||this.$.srcElement;return a?new CKEDITOR.dom.node(a):null},getPhase:function(){return this.$.eventPhase||2},getPageOffset:function(){var a=this.getTarget().getDocument().$;return{x:this.$.pageX||this.$.clientX+(a.documentElement.scrollLeft||a.body.scrollLeft),y:this.$.pageY||this.$.clientY+(a.documentElement.scrollTop||a.body.scrollTop)}}};CKEDITOR.CTRL=1114112;CKEDITOR.SHIFT=2228224;CKEDITOR.ALT=4456448;CKEDITOR.EVENT_PHASE_CAPTURING=1;CKEDITOR.EVENT_PHASE_AT_TARGET=2;
+CKEDITOR.EVENT_PHASE_BUBBLING=3;CKEDITOR.dom.domObject=function(a){if(a)this.$=a};
+CKEDITOR.dom.domObject.prototype=function(){var a=function(a,b){return function(c){typeof CKEDITOR!="undefined"&&a.fire(b,new CKEDITOR.dom.event(c))}};return{getPrivate:function(){var a;if(!(a=this.getCustomData("_")))this.setCustomData("_",a={});return a},on:function(f){var b=this.getCustomData("_cke_nativeListeners");if(!b){b={};this.setCustomData("_cke_nativeListeners",b)}if(!b[f]){b=b[f]=a(this,f);this.$.addEventListener?this.$.addEventListener(f,b,!!CKEDITOR.event.useCapture):this.$.attachEvent&&
+this.$.attachEvent("on"+f,b)}return CKEDITOR.event.prototype.on.apply(this,arguments)},removeListener:function(a){CKEDITOR.event.prototype.removeListener.apply(this,arguments);if(!this.hasListeners(a)){var b=this.getCustomData("_cke_nativeListeners"),c=b&&b[a];if(c){this.$.removeEventListener?this.$.removeEventListener(a,c,false):this.$.detachEvent&&this.$.detachEvent("on"+a,c);delete b[a]}}},removeAllListeners:function(){var a=this.getCustomData("_cke_nativeListeners"),b;for(b in a){var c=a[b];this.$.detachEvent?
+this.$.detachEvent("on"+b,c):this.$.removeEventListener&&this.$.removeEventListener(b,c,false);delete a[b]}CKEDITOR.event.prototype.removeAllListeners.call(this)}}}();
+(function(a){var f={};CKEDITOR.on("reset",function(){f={}});a.equals=function(a){try{return a&&a.$===this.$}catch(c){return false}};a.setCustomData=function(a,c){var e=this.getUniqueId();(f[e]||(f[e]={}))[a]=c;return this};a.getCustomData=function(a){var c=this.$["data-cke-expando"];return(c=c&&f[c])&&a in c?c[a]:null};a.removeCustomData=function(a){var c=this.$["data-cke-expando"],c=c&&f[c],e,d;if(c){e=c[a];d=a in c;delete c[a]}return d?e:null};a.clearCustomData=function(){this.removeAllListeners();
+var a=this.$["data-cke-expando"];a&&delete f[a]};a.getUniqueId=function(){return this.$["data-cke-expando"]||(this.$["data-cke-expando"]=CKEDITOR.tools.getNextNumber())};CKEDITOR.event.implementOn(a)})(CKEDITOR.dom.domObject.prototype);
+CKEDITOR.dom.node=function(a){return a?new CKEDITOR.dom[a.nodeType==CKEDITOR.NODE_DOCUMENT?"document":a.nodeType==CKEDITOR.NODE_ELEMENT?"element":a.nodeType==CKEDITOR.NODE_TEXT?"text":a.nodeType==CKEDITOR.NODE_COMMENT?"comment":a.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT?"documentFragment":"domObject"](a):this};CKEDITOR.dom.node.prototype=new CKEDITOR.dom.domObject;CKEDITOR.NODE_ELEMENT=1;CKEDITOR.NODE_DOCUMENT=9;CKEDITOR.NODE_TEXT=3;CKEDITOR.NODE_COMMENT=8;CKEDITOR.NODE_DOCUMENT_FRAGMENT=11;
+CKEDITOR.POSITION_IDENTICAL=0;CKEDITOR.POSITION_DISCONNECTED=1;CKEDITOR.POSITION_FOLLOWING=2;CKEDITOR.POSITION_PRECEDING=4;CKEDITOR.POSITION_IS_CONTAINED=8;CKEDITOR.POSITION_CONTAINS=16;
+CKEDITOR.tools.extend(CKEDITOR.dom.node.prototype,{appendTo:function(a,f){a.append(this,f);return a},clone:function(a,f){var b=this.$.cloneNode(a),c=function(e){e["data-cke-expando"]&&(e["data-cke-expando"]=false);if(e.nodeType==CKEDITOR.NODE_ELEMENT){f||e.removeAttribute("id",false);if(a)for(var e=e.childNodes,d=0;d<e.length;d++)c(e[d])}};c(b);return new CKEDITOR.dom.node(b)},hasPrevious:function(){return!!this.$.previousSibling},hasNext:function(){return!!this.$.nextSibling},insertAfter:function(a){a.$.parentNode.insertBefore(this.$,
+a.$.nextSibling);return a},insertBefore:function(a){a.$.parentNode.insertBefore(this.$,a.$);return a},insertBeforeMe:function(a){this.$.parentNode.insertBefore(a.$,this.$);return a},getAddress:function(a){for(var f=[],b=this.getDocument().$.documentElement,c=this.$;c&&c!=b;){var e=c.parentNode;e&&f.unshift(this.getIndex.call({$:c},a));c=e}return f},getDocument:function(){return new CKEDITOR.dom.document(this.$.ownerDocument||this.$.parentNode.ownerDocument)},getIndex:function(a){function f(a,e){var b=
+e?a.nextSibling:a.previousSibling;return!b||b.nodeType!=CKEDITOR.NODE_TEXT?null:b.nodeValue?b:f(b,e)}var b=this.$,c=-1,e;if(!this.$.parentNode||a&&b.nodeType==CKEDITOR.NODE_TEXT&&!b.nodeValue&&!f(b)&&!f(b,true))return-1;do if(!a||!(b!=this.$&&b.nodeType==CKEDITOR.NODE_TEXT&&(e||!b.nodeValue))){c++;e=b.nodeType==CKEDITOR.NODE_TEXT}while(b=b.previousSibling);return c},getNextSourceNode:function(a,f,b){if(b&&!b.call)var c=b,b=function(a){return!a.equals(c)};var a=!a&&this.getFirst&&this.getFirst(),e;
+if(!a){if(this.type==CKEDITOR.NODE_ELEMENT&&b&&b(this,true)===false)return null;a=this.getNext()}for(;!a&&(e=(e||this).getParent());){if(b&&b(e,true)===false)return null;a=e.getNext()}return!a||b&&b(a)===false?null:f&&f!=a.type?a.getNextSourceNode(false,f,b):a},getPreviousSourceNode:function(a,f,b){if(b&&!b.call)var c=b,b=function(a){return!a.equals(c)};var a=!a&&this.getLast&&this.getLast(),e;if(!a){if(this.type==CKEDITOR.NODE_ELEMENT&&b&&b(this,true)===false)return null;a=this.getPrevious()}for(;!a&&
+(e=(e||this).getParent());){if(b&&b(e,true)===false)return null;a=e.getPrevious()}return!a||b&&b(a)===false?null:f&&a.type!=f?a.getPreviousSourceNode(false,f,b):a},getPrevious:function(a){var f=this.$,b;do b=(f=f.previousSibling)&&f.nodeType!=10&&new CKEDITOR.dom.node(f);while(b&&a&&!a(b));return b},getNext:function(a){var f=this.$,b;do b=(f=f.nextSibling)&&new CKEDITOR.dom.node(f);while(b&&a&&!a(b));return b},getParent:function(a){var f=this.$.parentNode;return f&&(f.nodeType==CKEDITOR.NODE_ELEMENT||
+a&&f.nodeType==CKEDITOR.NODE_DOCUMENT_FRAGMENT)?new CKEDITOR.dom.node(f):null},getParents:function(a){var f=this,b=[];do b[a?"push":"unshift"](f);while(f=f.getParent());return b},getCommonAncestor:function(a){if(a.equals(this))return this;if(a.contains&&a.contains(this))return a;var f=this.contains?this:this.getParent();do if(f.contains(a))return f;while(f=f.getParent());return null},getPosition:function(a){var f=this.$,b=a.$;if(f.compareDocumentPosition)return f.compareDocumentPosition(b);if(f==
+b)return CKEDITOR.POSITION_IDENTICAL;if(this.type==CKEDITOR.NODE_ELEMENT&&a.type==CKEDITOR.NODE_ELEMENT){if(f.contains){if(f.contains(b))return CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_PRECEDING;if(b.contains(f))return CKEDITOR.POSITION_IS_CONTAINED+CKEDITOR.POSITION_FOLLOWING}if("sourceIndex"in f)return f.sourceIndex<0||b.sourceIndex<0?CKEDITOR.POSITION_DISCONNECTED:f.sourceIndex<b.sourceIndex?CKEDITOR.POSITION_PRECEDING:CKEDITOR.POSITION_FOLLOWING}for(var f=this.getAddress(),a=a.getAddress(),
+b=Math.min(f.length,a.length),c=0;c<=b-1;c++)if(f[c]!=a[c]){if(c<b)return f[c]<a[c]?CKEDITOR.POSITION_PRECEDING:CKEDITOR.POSITION_FOLLOWING;break}return f.length<a.length?CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_PRECEDING:CKEDITOR.POSITION_IS_CONTAINED+CKEDITOR.POSITION_FOLLOWING},getAscendant:function(a,f){var b=this.$,c,e;if(!f)b=b.parentNode;if(typeof a=="function"){e=true;c=a}else{e=false;c=function(e){e=typeof e.nodeName=="string"?e.nodeName.toLowerCase():"";return typeof a=="string"?e==
+a:e in a}}for(;b;){if(c(e?new CKEDITOR.dom.node(b):b))return new CKEDITOR.dom.node(b);try{b=b.parentNode}catch(d){b=null}}return null},hasAscendant:function(a,f){var b=this.$;if(!f)b=b.parentNode;for(;b;){if(b.nodeName&&b.nodeName.toLowerCase()==a)return true;b=b.parentNode}return false},move:function(a,f){a.append(this.remove(),f)},remove:function(a){var f=this.$,b=f.parentNode;if(b){if(a)for(;a=f.firstChild;)b.insertBefore(f.removeChild(a),f);b.removeChild(f)}return this},replace:function(a){this.insertBefore(a);
+a.remove()},trim:function(){this.ltrim();this.rtrim()},ltrim:function(){for(var a;this.getFirst&&(a=this.getFirst());){if(a.type==CKEDITOR.NODE_TEXT){var f=CKEDITOR.tools.ltrim(a.getText()),b=a.getLength();if(f){if(f.length<b){a.split(b-f.length);this.$.removeChild(this.$.firstChild)}}else{a.remove();continue}}break}},rtrim:function(){for(var a;this.getLast&&(a=this.getLast());){if(a.type==CKEDITOR.NODE_TEXT){var f=CKEDITOR.tools.rtrim(a.getText()),b=a.getLength();if(f){if(f.length<b){a.split(f.length);
+this.$.lastChild.parentNode.removeChild(this.$.lastChild)}}else{a.remove();continue}}break}if(CKEDITOR.env.needsBrFiller)(a=this.$.lastChild)&&(a.type==1&&a.nodeName.toLowerCase()=="br")&&a.parentNode.removeChild(a)},isReadOnly:function(){var a=this;this.type!=CKEDITOR.NODE_ELEMENT&&(a=this.getParent());if(a&&typeof a.$.isContentEditable!="undefined")return!(a.$.isContentEditable||a.data("cke-editable"));for(;a;){if(a.data("cke-editable"))break;if(a.getAttribute("contentEditable")=="false")return true;
+if(a.getAttribute("contentEditable")=="true")break;a=a.getParent()}return!a}});CKEDITOR.dom.window=function(a){CKEDITOR.dom.domObject.call(this,a)};CKEDITOR.dom.window.prototype=new CKEDITOR.dom.domObject;
+CKEDITOR.tools.extend(CKEDITOR.dom.window.prototype,{focus:function(){this.$.focus()},getViewPaneSize:function(){var a=this.$.document,f=a.compatMode=="CSS1Compat";return{width:(f?a.documentElement.clientWidth:a.body.clientWidth)||0,height:(f?a.documentElement.clientHeight:a.body.clientHeight)||0}},getScrollPosition:function(){var a=this.$;if("pageXOffset"in a)return{x:a.pageXOffset||0,y:a.pageYOffset||0};a=a.document;return{x:a.documentElement.scrollLeft||a.body.scrollLeft||0,y:a.documentElement.scrollTop||
+a.body.scrollTop||0}},getFrame:function(){var a=this.$.frameElement;return a?new CKEDITOR.dom.element.get(a):null}});CKEDITOR.dom.document=function(a){CKEDITOR.dom.domObject.call(this,a)};CKEDITOR.dom.document.prototype=new CKEDITOR.dom.domObject;
+CKEDITOR.tools.extend(CKEDITOR.dom.document.prototype,{type:CKEDITOR.NODE_DOCUMENT,appendStyleSheet:function(a){if(this.$.createStyleSheet)this.$.createStyleSheet(a);else{var f=new CKEDITOR.dom.element("link");f.setAttributes({rel:"stylesheet",type:"text/css",href:a});this.getHead().append(f)}},appendStyleText:function(a){if(this.$.createStyleSheet){var f=this.$.createStyleSheet("");f.cssText=a}else{var b=new CKEDITOR.dom.element("style",this);b.append(new CKEDITOR.dom.text(a,this));this.getHead().append(b)}return f||
+b.$.sheet},createElement:function(a,f){var b=new CKEDITOR.dom.element(a,this);if(f){f.attributes&&b.setAttributes(f.attributes);f.styles&&b.setStyles(f.styles)}return b},createText:function(a){return new CKEDITOR.dom.text(a,this)},focus:function(){this.getWindow().focus()},getActive:function(){var a;try{a=this.$.activeElement}catch(f){return null}return new CKEDITOR.dom.element(a)},getById:function(a){return(a=this.$.getElementById(a))?new CKEDITOR.dom.element(a):null},getByAddress:function(a,f){for(var b=
+this.$.documentElement,c=0;b&&c<a.length;c++){var e=a[c];if(f)for(var d=-1,h=0;h<b.childNodes.length;h++){var k=b.childNodes[h];if(!(f===true&&k.nodeType==3&&k.previousSibling&&k.previousSibling.nodeType==3)){d++;if(d==e){b=k;break}}}else b=b.childNodes[e]}return b?new CKEDITOR.dom.node(b):null},getElementsByTag:function(a,f){!(CKEDITOR.env.ie&&document.documentMode<=8)&&f&&(a=f+":"+a);return new CKEDITOR.dom.nodeList(this.$.getElementsByTagName(a))},getHead:function(){var a=this.$.getElementsByTagName("head")[0];
+return a=a?new CKEDITOR.dom.element(a):this.getDocumentElement().append(new CKEDITOR.dom.element("head"),true)},getBody:function(){return new CKEDITOR.dom.element(this.$.body)},getDocumentElement:function(){return new CKEDITOR.dom.element(this.$.documentElement)},getWindow:function(){return new CKEDITOR.dom.window(this.$.parentWindow||this.$.defaultView)},write:function(a){this.$.open("text/html","replace");CKEDITOR.env.ie&&(a=a.replace(/(?:^\s*<!DOCTYPE[^>]*?>)|^/i,'$&\n<script data-cke-temp="1">('+
+CKEDITOR.tools.fixDomain+")();<\/script>"));this.$.write(a);this.$.close()},find:function(a){return new CKEDITOR.dom.nodeList(this.$.querySelectorAll(a))},findOne:function(a){return(a=this.$.querySelector(a))?new CKEDITOR.dom.element(a):null},_getHtml5ShivFrag:function(){var a=this.getCustomData("html5ShivFrag");if(!a){a=this.$.createDocumentFragment();CKEDITOR.tools.enableHtml5Elements(a,true);this.setCustomData("html5ShivFrag",a)}return a}});CKEDITOR.dom.nodeList=function(a){this.$=a};
+CKEDITOR.dom.nodeList.prototype={count:function(){return this.$.length},getItem:function(a){if(a<0||a>=this.$.length)return null;return(a=this.$[a])?new CKEDITOR.dom.node(a):null}};CKEDITOR.dom.element=function(a,f){typeof a=="string"&&(a=(f?f.$:document).createElement(a));CKEDITOR.dom.domObject.call(this,a)};CKEDITOR.dom.element.get=function(a){return(a=typeof a=="string"?document.getElementById(a)||document.getElementsByName(a)[0]:a)&&(a.$?a:new CKEDITOR.dom.element(a))};
+CKEDITOR.dom.element.prototype=new CKEDITOR.dom.node;CKEDITOR.dom.element.createFromHtml=function(a,f){var b=new CKEDITOR.dom.element("div",f);b.setHtml(a);return b.getFirst().remove()};
+CKEDITOR.dom.element.setMarker=function(a,f,b,c){var e=f.getCustomData("list_marker_id")||f.setCustomData("list_marker_id",CKEDITOR.tools.getNextNumber()).getCustomData("list_marker_id"),d=f.getCustomData("list_marker_names")||f.setCustomData("list_marker_names",{}).getCustomData("list_marker_names");a[e]=f;d[b]=1;return f.setCustomData(b,c)};CKEDITOR.dom.element.clearAllMarkers=function(a){for(var f in a)CKEDITOR.dom.element.clearMarkers(a,a[f],1)};
+CKEDITOR.dom.element.clearMarkers=function(a,f,b){var c=f.getCustomData("list_marker_names"),e=f.getCustomData("list_marker_id"),d;for(d in c)f.removeCustomData(d);f.removeCustomData("list_marker_names");if(b){f.removeCustomData("list_marker_id");delete a[e]}};
+(function(){function a(a){var d=true;if(!a.$.id){a.$.id="cke_tmp_"+CKEDITOR.tools.getNextNumber();d=false}return function(){d||a.removeAttribute("id")}}function f(a,d){return"#"+a.$.id+" "+d.split(/,\s*/).join(", #"+a.$.id+" ")}function b(a){for(var d=0,b=0,f=c[a].length;b<f;b++)d=d+(parseInt(this.getComputedStyle(c[a][b])||0,10)||0);return d}CKEDITOR.tools.extend(CKEDITOR.dom.element.prototype,{type:CKEDITOR.NODE_ELEMENT,addClass:function(a){var d=this.$.className;d&&(RegExp("(?:^|\\s)"+a+"(?:\\s|$)",
+"").test(d)||(d=d+(" "+a)));this.$.className=d||a;return this},removeClass:function(a){var d=this.getAttribute("class");if(d){a=RegExp("(?:^|\\s+)"+a+"(?=\\s|$)","i");if(a.test(d))(d=d.replace(a,"").replace(/^\s+/,""))?this.setAttribute("class",d):this.removeAttribute("class")}return this},hasClass:function(a){return RegExp("(?:^|\\s+)"+a+"(?=\\s|$)","").test(this.getAttribute("class"))},append:function(a,d){typeof a=="string"&&(a=this.getDocument().createElement(a));d?this.$.insertBefore(a.$,this.$.firstChild):
+this.$.appendChild(a.$);return a},appendHtml:function(a){if(this.$.childNodes.length){var d=new CKEDITOR.dom.element("div",this.getDocument());d.setHtml(a);d.moveChildren(this)}else this.setHtml(a)},appendText:function(a){this.$.text!=null?this.$.text=this.$.text+a:this.append(new CKEDITOR.dom.text(a))},appendBogus:function(a){if(a||CKEDITOR.env.needsBrFiller){for(a=this.getLast();a&&a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.rtrim(a.getText());)a=a.getPrevious();if(!a||!a.is||!a.is("br")){a=this.getDocument().createElement("br");
+CKEDITOR.env.gecko&&a.setAttribute("type","_moz");this.append(a)}}},breakParent:function(a){var d=new CKEDITOR.dom.range(this.getDocument());d.setStartAfter(this);d.setEndAfter(a);a=d.extractContents();d.insertNode(this.remove());a.insertAfterNode(this)},contains:CKEDITOR.env.ie||CKEDITOR.env.webkit?function(a){var d=this.$;return a.type!=CKEDITOR.NODE_ELEMENT?d.contains(a.getParent().$):d!=a.$&&d.contains(a.$)}:function(a){return!!(this.$.compareDocumentPosition(a.$)&16)},focus:function(){function a(){try{this.$.focus()}catch(e){}}
+return function(d){d?CKEDITOR.tools.setTimeout(a,100,this):a.call(this)}}(),getHtml:function(){var a=this.$.innerHTML;return CKEDITOR.env.ie?a.replace(/<\?[^>]*>/g,""):a},getOuterHtml:function(){if(this.$.outerHTML)return this.$.outerHTML.replace(/<\?[^>]*>/,"");var a=this.$.ownerDocument.createElement("div");a.appendChild(this.$.cloneNode(true));return a.innerHTML},getClientRect:function(){var a=CKEDITOR.tools.extend({},this.$.getBoundingClientRect());!a.width&&(a.width=a.right-a.left);!a.height&&
+(a.height=a.bottom-a.top);return a},setHtml:CKEDITOR.env.ie&&CKEDITOR.env.version<9?function(a){try{var d=this.$;if(this.getParent())return d.innerHTML=a;var b=this.getDocument()._getHtml5ShivFrag();b.appendChild(d);d.innerHTML=a;b.removeChild(d);return a}catch(c){this.$.innerHTML="";d=new CKEDITOR.dom.element("body",this.getDocument());d.$.innerHTML=a;for(d=d.getChildren();d.count();)this.append(d.getItem(0));return a}}:function(a){return this.$.innerHTML=a},setText:function(){var a=document.createElement("p");
+a.innerHTML="x";a=a.textContent;return function(d){this.$[a?"textContent":"innerText"]=d}}(),getAttribute:function(){var a=function(a){return this.$.getAttribute(a,2)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){switch(a){case "class":a="className";break;case "http-equiv":a="httpEquiv";break;case "name":return this.$.name;case "tabindex":a=this.$.getAttribute(a,2);a!==0&&this.$.tabIndex===0&&(a=null);return a;case "checked":a=this.$.attributes.getNamedItem(a);
+return(a.specified?a.nodeValue:this.$.checked)?"checked":null;case "hspace":case "value":return this.$[a];case "style":return this.$.style.cssText;case "contenteditable":case "contentEditable":return this.$.attributes.getNamedItem("contentEditable").specified?this.$.getAttribute("contentEditable"):null}return this.$.getAttribute(a,2)}:a}(),getChildren:function(){return new CKEDITOR.dom.nodeList(this.$.childNodes)},getComputedStyle:CKEDITOR.env.ie?function(a){return this.$.currentStyle[CKEDITOR.tools.cssStyleToDomStyle(a)]}:
+function(a){var d=this.getWindow().$.getComputedStyle(this.$,null);return d?d.getPropertyValue(a):""},getDtd:function(){var a=CKEDITOR.dtd[this.getName()];this.getDtd=function(){return a};return a},getElementsByTag:CKEDITOR.dom.document.prototype.getElementsByTag,getTabIndex:CKEDITOR.env.ie?function(){var a=this.$.tabIndex;a===0&&(!CKEDITOR.dtd.$tabIndex[this.getName()]&&parseInt(this.getAttribute("tabindex"),10)!==0)&&(a=-1);return a}:CKEDITOR.env.webkit?function(){var a=this.$.tabIndex;if(a===void 0){a=
+parseInt(this.getAttribute("tabindex"),10);isNaN(a)&&(a=-1)}return a}:function(){return this.$.tabIndex},getText:function(){return this.$.textContent||this.$.innerText||""},getWindow:function(){return this.getDocument().getWindow()},getId:function(){return this.$.id||null},getNameAtt:function(){return this.$.name||null},getName:function(){var a=this.$.nodeName.toLowerCase();if(CKEDITOR.env.ie&&document.documentMode<=8){var d=this.$.scopeName;d!="HTML"&&(a=d.toLowerCase()+":"+a)}this.getName=function(){return a};
+return this.getName()},getValue:function(){return this.$.value},getFirst:function(a){var d=this.$.firstChild;(d=d&&new CKEDITOR.dom.node(d))&&(a&&!a(d))&&(d=d.getNext(a));return d},getLast:function(a){var d=this.$.lastChild;(d=d&&new CKEDITOR.dom.node(d))&&(a&&!a(d))&&(d=d.getPrevious(a));return d},getStyle:function(a){return this.$.style[CKEDITOR.tools.cssStyleToDomStyle(a)]},is:function(){var a=this.getName();if(typeof arguments[0]=="object")return!!arguments[0][a];for(var d=0;d<arguments.length;d++)if(arguments[d]==
+a)return true;return false},isEditable:function(a){var d=this.getName();if(this.isReadOnly()||this.getComputedStyle("display")=="none"||this.getComputedStyle("visibility")=="hidden"||CKEDITOR.dtd.$nonEditable[d]||CKEDITOR.dtd.$empty[d]||this.is("a")&&(this.data("cke-saved-name")||this.hasAttribute("name"))&&!this.getChildCount())return false;if(a!==false){a=CKEDITOR.dtd[d]||CKEDITOR.dtd.span;return!(!a||!a["#"])}return true},isIdentical:function(a){var d=this.clone(0,1),a=a.clone(0,1);d.removeAttributes(["_moz_dirty",
+"data-cke-expando","data-cke-saved-href","data-cke-saved-name"]);a.removeAttributes(["_moz_dirty","data-cke-expando","data-cke-saved-href","data-cke-saved-name"]);if(d.$.isEqualNode){d.$.style.cssText=CKEDITOR.tools.normalizeCssText(d.$.style.cssText);a.$.style.cssText=CKEDITOR.tools.normalizeCssText(a.$.style.cssText);return d.$.isEqualNode(a.$)}d=d.getOuterHtml();a=a.getOuterHtml();if(CKEDITOR.env.ie&&CKEDITOR.env.version<9&&this.is("a")){var b=this.getParent();if(b.type==CKEDITOR.NODE_ELEMENT){b=
+b.clone();b.setHtml(d);d=b.getHtml();b.setHtml(a);a=b.getHtml()}}return d==a},isVisible:function(){var a=(this.$.offsetHeight||this.$.offsetWidth)&&this.getComputedStyle("visibility")!="hidden",d,b;if(a&&CKEDITOR.env.webkit){d=this.getWindow();if(!d.equals(CKEDITOR.document.getWindow())&&(b=d.$.frameElement))a=(new CKEDITOR.dom.element(b)).isVisible()}return!!a},isEmptyInlineRemoveable:function(){if(!CKEDITOR.dtd.$removeEmpty[this.getName()])return false;for(var a=this.getChildren(),d=0,b=a.count();d<
+b;d++){var c=a.getItem(d);if(!(c.type==CKEDITOR.NODE_ELEMENT&&c.data("cke-bookmark"))&&(c.type==CKEDITOR.NODE_ELEMENT&&!c.isEmptyInlineRemoveable()||c.type==CKEDITOR.NODE_TEXT&&CKEDITOR.tools.trim(c.getText())))return false}return true},hasAttributes:CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(){for(var a=this.$.attributes,d=0;d<a.length;d++){var b=a[d];switch(b.nodeName){case "class":if(this.getAttribute("class"))return true;case "data-cke-expando":continue;default:if(b.specified)return true}}return false}:
+function(){var a=this.$.attributes,d=a.length,b={"data-cke-expando":1,_moz_dirty:1};return d>0&&(d>2||!b[a[0].nodeName]||d==2&&!b[a[1].nodeName])},hasAttribute:function(){function a(d){var e=this.$.attributes.getNamedItem(d);if(this.getName()=="input")switch(d){case "class":return this.$.className.length>0;case "checked":return!!this.$.checked;case "value":d=this.getAttribute("type");return d=="checkbox"||d=="radio"?this.$.value!="on":!!this.$.value}return!e?false:e.specified}return CKEDITOR.env.ie?
+CKEDITOR.env.version<8?function(d){return d=="name"?!!this.$.name:a.call(this,d)}:a:function(a){return!!this.$.attributes.getNamedItem(a)}}(),hide:function(){this.setStyle("display","none")},moveChildren:function(a,d){var b=this.$,a=a.$;if(b!=a){var c;if(d)for(;c=b.lastChild;)a.insertBefore(b.removeChild(c),a.firstChild);else for(;c=b.firstChild;)a.appendChild(b.removeChild(c))}},mergeSiblings:function(){function a(d,b,e){if(b&&b.type==CKEDITOR.NODE_ELEMENT){for(var c=[];b.data("cke-bookmark")||b.isEmptyInlineRemoveable();){c.push(b);
+b=e?b.getNext():b.getPrevious();if(!b||b.type!=CKEDITOR.NODE_ELEMENT)return}if(d.isIdentical(b)){for(var f=e?d.getLast():d.getFirst();c.length;)c.shift().move(d,!e);b.moveChildren(d,!e);b.remove();f&&f.type==CKEDITOR.NODE_ELEMENT&&f.mergeSiblings()}}}return function(d){if(d===false||CKEDITOR.dtd.$removeEmpty[this.getName()]||this.is("a")){a(this,this.getNext(),true);a(this,this.getPrevious())}}}(),show:function(){this.setStyles({display:"",visibility:""})},setAttribute:function(){var a=function(a,
+b){this.$.setAttribute(a,b);return this};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(d,b){d=="class"?this.$.className=b:d=="style"?this.$.style.cssText=b:d=="tabindex"?this.$.tabIndex=b:d=="checked"?this.$.checked=b:d=="contenteditable"?a.call(this,"contentEditable",b):a.apply(this,arguments);return this}:CKEDITOR.env.ie8Compat&&CKEDITOR.env.secure?function(d,b){if(d=="src"&&b.match(/^http:\/\//))try{a.apply(this,arguments)}catch(c){}else a.apply(this,arguments);
+return this}:a}(),setAttributes:function(a){for(var d in a)this.setAttribute(d,a[d]);return this},setValue:function(a){this.$.value=a;return this},removeAttribute:function(){var a=function(a){this.$.removeAttribute(a)};return CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?function(a){a=="class"?a="className":a=="tabindex"?a="tabIndex":a=="contenteditable"&&(a="contentEditable");this.$.removeAttribute(a)}:a}(),removeAttributes:function(a){if(CKEDITOR.tools.isArray(a))for(var b=0;b<
+a.length;b++)this.removeAttribute(a[b]);else for(b in a)a.hasOwnProperty(b)&&this.removeAttribute(b)},removeStyle:function(a){var b=this.$.style;if(!b.removeProperty&&(a=="border"||a=="margin"||a=="padding")){var c=["top","left","right","bottom"],f;a=="border"&&(f=["color","style","width"]);for(var b=[],j=0;j<c.length;j++)if(f)for(var g=0;g<f.length;g++)b.push([a,c[j],f[g]].join("-"));else b.push([a,c[j]].join("-"));for(a=0;a<b.length;a++)this.removeStyle(b[a])}else{b.removeProperty?b.removeProperty(a):
+b.removeAttribute(CKEDITOR.tools.cssStyleToDomStyle(a));this.$.style.cssText||this.removeAttribute("style")}},setStyle:function(a,b){this.$.style[CKEDITOR.tools.cssStyleToDomStyle(a)]=b;return this},setStyles:function(a){for(var b in a)this.setStyle(b,a[b]);return this},setOpacity:function(a){if(CKEDITOR.env.ie&&CKEDITOR.env.version<9){a=Math.round(a*100);this.setStyle("filter",a>=100?"":"progid:DXImageTransform.Microsoft.Alpha(opacity="+a+")")}else this.setStyle("opacity",a)},unselectable:function(){this.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select",
+"none"));if(CKEDITOR.env.ie){this.setAttribute("unselectable","on");for(var a,b=this.getElementsByTag("*"),c=0,f=b.count();c<f;c++){a=b.getItem(c);a.setAttribute("unselectable","on")}}},getPositionedAncestor:function(){for(var a=this;a.getName()!="html";){if(a.getComputedStyle("position")!="static")return a;a=a.getParent()}return null},getDocumentPosition:function(a){var b=0,c=0,f=this.getDocument(),j=f.getBody(),g=CKEDITOR.env.quirks;if(document.documentElement.getBoundingClientRect){var m=this.$.getBoundingClientRect(),
+y=f.$.documentElement,s=y.clientTop||j.$.clientTop||0,w=y.clientLeft||j.$.clientLeft||0,q=true;if(CKEDITOR.env.ie){q=f.getDocumentElement().contains(this);f=f.getBody().contains(this);q=g&&f||!g&&q}if(q){if(CKEDITOR.env.webkit){b=j.$.scrollLeft||y.scrollLeft;c=j.$.scrollTop||y.scrollTop}else{c=g?j.$:y;b=c.scrollLeft;c=c.scrollTop}b=m.left+b-w;c=m.top+c-s}}else{s=this;for(w=null;s&&!(s.getName()=="body"||s.getName()=="html");){b=b+(s.$.offsetLeft-s.$.scrollLeft);c=c+(s.$.offsetTop-s.$.scrollTop);if(!s.equals(this)){b=
+b+(s.$.clientLeft||0);c=c+(s.$.clientTop||0)}for(;w&&!w.equals(s);){b=b-w.$.scrollLeft;c=c-w.$.scrollTop;w=w.getParent()}w=s;s=(m=s.$.offsetParent)?new CKEDITOR.dom.element(m):null}}if(a){m=this.getWindow();s=a.getWindow();if(!m.equals(s)&&m.$.frameElement){a=(new CKEDITOR.dom.element(m.$.frameElement)).getDocumentPosition(a);b=b+a.x;c=c+a.y}}if(!document.documentElement.getBoundingClientRect&&CKEDITOR.env.gecko&&!g){b=b+(this.$.clientLeft?1:0);c=c+(this.$.clientTop?1:0)}return{x:b,y:c}},scrollIntoView:function(a){var b=
+this.getParent();if(b){do{(b.$.clientWidth&&b.$.clientWidth<b.$.scrollWidth||b.$.clientHeight&&b.$.clientHeight<b.$.scrollHeight)&&!b.is("body")&&this.scrollIntoParent(b,a,1);if(b.is("html")){var c=b.getWindow();try{var f=c.$.frameElement;f&&(b=new CKEDITOR.dom.element(f))}catch(j){}}}while(b=b.getParent())}},scrollIntoParent:function(a,b,c){var f,j,g,m;function y(b,d){if(/body|html/.test(a.getName()))a.getWindow().$.scrollBy(b,d);else{a.$.scrollLeft=a.$.scrollLeft+b;a.$.scrollTop=a.$.scrollTop+d}}
+function s(a,b){var d={x:0,y:0};if(!a.is(q?"body":"html")){var c=a.$.getBoundingClientRect();d.x=c.left;d.y=c.top}c=a.getWindow();if(!c.equals(b)){c=s(CKEDITOR.dom.element.get(c.$.frameElement),b);d.x=d.x+c.x;d.y=d.y+c.y}return d}function w(a,b){return parseInt(a.getComputedStyle("margin-"+b)||0,10)||0}!a&&(a=this.getWindow());g=a.getDocument();var q=g.$.compatMode=="BackCompat";a instanceof CKEDITOR.dom.window&&(a=q?g.getBody():g.getDocumentElement());g=a.getWindow();j=s(this,g);var t=s(a,g),i=this.$.offsetHeight;
+f=this.$.offsetWidth;var A=a.$.clientHeight,u=a.$.clientWidth;g=j.x-w(this,"left")-t.x||0;m=j.y-w(this,"top")-t.y||0;f=j.x+f+w(this,"right")-(t.x+u)||0;j=j.y+i+w(this,"bottom")-(t.y+A)||0;if(m<0||j>0)y(0,b===true?m:b===false?j:m<0?m:j);if(c&&(g<0||f>0))y(g<0?g:f,0)},setState:function(a,b,c){b=b||"cke";switch(a){case CKEDITOR.TRISTATE_ON:this.addClass(b+"_on");this.removeClass(b+"_off");this.removeClass(b+"_disabled");c&&this.setAttribute("aria-pressed",true);c&&this.removeAttribute("aria-disabled");
+break;case CKEDITOR.TRISTATE_DISABLED:this.addClass(b+"_disabled");this.removeClass(b+"_off");this.removeClass(b+"_on");c&&this.setAttribute("aria-disabled",true);c&&this.removeAttribute("aria-pressed");break;default:this.addClass(b+"_off");this.removeClass(b+"_on");this.removeClass(b+"_disabled");c&&this.removeAttribute("aria-pressed");c&&this.removeAttribute("aria-disabled")}},getFrameDocument:function(){var a=this.$;try{a.contentWindow.document}catch(b){a.src=a.src}return a&&new CKEDITOR.dom.document(a.contentWindow.document)},
+copyAttributes:function(a,b){for(var c=this.$.attributes,b=b||{},f=0;f<c.length;f++){var j=c[f],g=j.nodeName.toLowerCase(),m;if(!(g in b))if(g=="checked"&&(m=this.getAttribute(g)))a.setAttribute(g,m);else if(!CKEDITOR.env.ie||this.hasAttribute(g)){m=this.getAttribute(g);if(m===null)m=j.nodeValue;a.setAttribute(g,m)}}if(this.$.style.cssText!=="")a.$.style.cssText=this.$.style.cssText},renameNode:function(a){if(this.getName()!=a){var b=this.getDocument(),a=new CKEDITOR.dom.element(a,b);this.copyAttributes(a);
+this.moveChildren(a);this.getParent()&&this.$.parentNode.replaceChild(a.$,this.$);a.$["data-cke-expando"]=this.$["data-cke-expando"];this.$=a.$;delete this.getName}},getChild:function(){function a(b,c){var e=b.childNodes;if(c>=0&&c<e.length)return e[c]}return function(b){var c=this.$;if(b.slice)for(;b.length>0&&c;)c=a(c,b.shift());else c=a(c,b);return c?new CKEDITOR.dom.node(c):null}}(),getChildCount:function(){return this.$.childNodes.length},disableContextMenu:function(){this.on("contextmenu",function(a){a.data.getTarget().hasClass("cke_enable_context_menu")||
+a.data.preventDefault()})},getDirection:function(a){return a?this.getComputedStyle("direction")||this.getDirection()||this.getParent()&&this.getParent().getDirection(1)||this.getDocument().$.dir||"ltr":this.getStyle("direction")||this.getAttribute("dir")},data:function(a,b){a="data-"+a;if(b===void 0)return this.getAttribute(a);b===false?this.removeAttribute(a):this.setAttribute(a,b);return null},getEditor:function(){var a=CKEDITOR.instances,b,c;for(b in a){c=a[b];if(c.element.equals(this)&&c.elementMode!=
+CKEDITOR.ELEMENT_MODE_APPENDTO)return c}return null},find:function(b){var c=a(this),b=new CKEDITOR.dom.nodeList(this.$.querySelectorAll(f(this,b)));c();return b},findOne:function(b){var c=a(this),b=this.$.querySelector(f(this,b));c();return b?new CKEDITOR.dom.element(b):null},forEach:function(a,b,c){if(!c&&(!b||this.type==b))var f=a(this);if(f!==false)for(var c=this.getChildren(),j=0;j<c.count();j++){f=c.getItem(j);f.type==CKEDITOR.NODE_ELEMENT?f.forEach(a,b):(!b||f.type==b)&&a(f)}}});var c={width:["border-left-width",
+"border-right-width","padding-left","padding-right"],height:["border-top-width","border-bottom-width","padding-top","padding-bottom"]};CKEDITOR.dom.element.prototype.setSize=function(a,c,f){if(typeof c=="number"){if(f&&(!CKEDITOR.env.ie||!CKEDITOR.env.quirks))c=c-b.call(this,a);this.setStyle(a,c+"px")}};CKEDITOR.dom.element.prototype.getSize=function(a,c){var f=Math.max(this.$["offset"+CKEDITOR.tools.capitalize(a)],this.$["client"+CKEDITOR.tools.capitalize(a)])||0;c&&(f=f-b.call(this,a));return f}})();
+CKEDITOR.dom.documentFragment=function(a){a=a||CKEDITOR.document;this.$=a.type==CKEDITOR.NODE_DOCUMENT?a.$.createDocumentFragment():a};
+CKEDITOR.tools.extend(CKEDITOR.dom.documentFragment.prototype,CKEDITOR.dom.element.prototype,{type:CKEDITOR.NODE_DOCUMENT_FRAGMENT,insertAfterNode:function(a){a=a.$;a.parentNode.insertBefore(this.$,a.nextSibling)}},!0,{append:1,appendBogus:1,getFirst:1,getLast:1,getParent:1,getNext:1,getPrevious:1,appendTo:1,moveChildren:1,insertBefore:1,insertAfterNode:1,replace:1,trim:1,type:1,ltrim:1,rtrim:1,getDocument:1,getChildCount:1,getChild:1,getChildren:1});
+(function(){function a(a,b){var c=this.range;if(this._.end)return null;if(!this._.start){this._.start=1;if(c.collapsed){this.end();return null}c.optimize()}var d,e=c.startContainer;d=c.endContainer;var m=c.startOffset,f=c.endOffset,h,o=this.guard,l=this.type,p=a?"getPreviousSourceNode":"getNextSourceNode";if(!a&&!this._.guardLTR){var r=d.type==CKEDITOR.NODE_ELEMENT?d:d.getParent(),n=d.type==CKEDITOR.NODE_ELEMENT?d.getChild(f):d.getNext();this._.guardLTR=function(a,b){return(!b||!r.equals(a))&&(!n||
+!a.equals(n))&&(a.type!=CKEDITOR.NODE_ELEMENT||!b||!a.equals(c.root))}}if(a&&!this._.guardRTL){var g=e.type==CKEDITOR.NODE_ELEMENT?e:e.getParent(),C=e.type==CKEDITOR.NODE_ELEMENT?m?e.getChild(m-1):null:e.getPrevious();this._.guardRTL=function(a,b){return(!b||!g.equals(a))&&(!C||!a.equals(C))&&(a.type!=CKEDITOR.NODE_ELEMENT||!b||!a.equals(c.root))}}var j=a?this._.guardRTL:this._.guardLTR;h=o?function(a,b){return j(a,b)===false?false:o(a,b)}:j;if(this.current)d=this.current[p](false,l,h);else{if(a)d.type==
+CKEDITOR.NODE_ELEMENT&&(d=f>0?d.getChild(f-1):h(d,true)===false?null:d.getPreviousSourceNode(true,l,h));else{d=e;if(d.type==CKEDITOR.NODE_ELEMENT&&!(d=d.getChild(m)))d=h(e,true)===false?null:e.getNextSourceNode(true,l,h)}d&&h(d)===false&&(d=null)}for(;d&&!this._.end;){this.current=d;if(!this.evaluator||this.evaluator(d)!==false){if(!b)return d}else if(b&&this.evaluator)return false;d=d[p](false,l,h)}this.end();return this.current=null}function f(b){for(var c,d=null;c=a.call(this,b);)d=c;return d}
+function b(a){if(g(a))return false;if(a.type==CKEDITOR.NODE_TEXT)return true;if(a.type==CKEDITOR.NODE_ELEMENT){if(a.is(CKEDITOR.dtd.$inline)||a.is("hr")||a.getAttribute("contenteditable")=="false")return true;var b;if(b=!CKEDITOR.env.needsBrFiller)if(b=a.is(m))a:{b=0;for(var c=a.getChildCount();b<c;++b)if(!g(a.getChild(b))){b=false;break a}b=true}if(b)return true}return false}CKEDITOR.dom.walker=CKEDITOR.tools.createClass({$:function(a){this.range=a;this._={}},proto:{end:function(){this._.end=1},
+next:function(){return a.call(this)},previous:function(){return a.call(this,1)},checkForward:function(){return a.call(this,0,1)!==false},checkBackward:function(){return a.call(this,1,1)!==false},lastForward:function(){return f.call(this)},lastBackward:function(){return f.call(this,1)},reset:function(){delete this.current;this._={}}}});var c={block:1,"list-item":1,table:1,"table-row-group":1,"table-header-group":1,"table-footer-group":1,"table-row":1,"table-column-group":1,"table-column":1,"table-cell":1,
+"table-caption":1},e={absolute:1,fixed:1};CKEDITOR.dom.element.prototype.isBlockBoundary=function(a){return this.getComputedStyle("float")=="none"&&!(this.getComputedStyle("position")in e)&&c[this.getComputedStyle("display")]?true:!!(this.is(CKEDITOR.dtd.$block)||a&&this.is(a))};CKEDITOR.dom.walker.blockBoundary=function(a){return function(b){return!(b.type==CKEDITOR.NODE_ELEMENT&&b.isBlockBoundary(a))}};CKEDITOR.dom.walker.listItemBoundary=function(){return this.blockBoundary({br:1})};CKEDITOR.dom.walker.bookmark=
+function(a,b){function c(a){return a&&a.getName&&a.getName()=="span"&&a.data("cke-bookmark")}return function(d){var e,m;e=d&&d.type!=CKEDITOR.NODE_ELEMENT&&(m=d.getParent())&&c(m);e=a?e:e||c(d);return!!(b^e)}};CKEDITOR.dom.walker.whitespaces=function(a){return function(b){var c;b&&b.type==CKEDITOR.NODE_TEXT&&(c=!CKEDITOR.tools.trim(b.getText())||CKEDITOR.env.webkit&&b.getText()=="​");return!!(a^c)}};CKEDITOR.dom.walker.invisible=function(a){var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.env.webkit?
+1:0;return function(d){if(b(d))d=1;else{d.type==CKEDITOR.NODE_TEXT&&(d=d.getParent());d=d.$.offsetWidth<=c}return!!(a^d)}};CKEDITOR.dom.walker.nodeType=function(a,b){return function(c){return!!(b^c.type==a)}};CKEDITOR.dom.walker.bogus=function(a){function b(a){return!h(a)&&!k(a)}return function(c){var e=CKEDITOR.env.needsBrFiller?c.is&&c.is("br"):c.getText&&d.test(c.getText());if(e){e=c.getParent();c=c.getNext(b);e=e.isBlockBoundary()&&(!c||c.type==CKEDITOR.NODE_ELEMENT&&c.isBlockBoundary())}return!!(a^
+e)}};CKEDITOR.dom.walker.temp=function(a){return function(b){b.type!=CKEDITOR.NODE_ELEMENT&&(b=b.getParent());b=b&&b.hasAttribute("data-cke-temp");return!!(a^b)}};var d=/^[\t\r\n ]*(?:&nbsp;|\xa0)$/,h=CKEDITOR.dom.walker.whitespaces(),k=CKEDITOR.dom.walker.bookmark(),j=CKEDITOR.dom.walker.temp();CKEDITOR.dom.walker.ignored=function(a){return function(b){b=h(b)||k(b)||j(b);return!!(a^b)}};var g=CKEDITOR.dom.walker.ignored(),m=function(a){var b={},c;for(c in a)CKEDITOR.dtd[c]["#"]&&(b[c]=1);return b}(CKEDITOR.dtd.$block);
+CKEDITOR.dom.walker.editable=function(a){return function(c){return!!(a^b(c))}};CKEDITOR.dom.element.prototype.getBogus=function(){var a=this;do a=a.getPreviousSourceNode();while(k(a)||h(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$inline)&&!a.is(CKEDITOR.dtd.$empty));return a&&(CKEDITOR.env.needsBrFiller?a.is&&a.is("br"):a.getText&&d.test(a.getText()))?a:false}})();
+CKEDITOR.dom.range=function(a){this.endOffset=this.endContainer=this.startOffset=this.startContainer=null;this.collapsed=true;var f=a instanceof CKEDITOR.dom.document;this.document=f?a:a.getDocument();this.root=f?a.getBody():a};
+(function(){function a(){var a=false,b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(true),e=CKEDITOR.dom.walker.bogus();return function(f){if(c(f)||b(f))return true;if(e(f)&&!a)return a=true;return f.type==CKEDITOR.NODE_TEXT&&(f.hasAscendant("pre")||CKEDITOR.tools.trim(f.getText()).length)||f.type==CKEDITOR.NODE_ELEMENT&&!f.is(d)?false:true}}function f(a){var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(1);return function(d){return c(d)||b(d)?true:!a&&h(d)||
+d.type==CKEDITOR.NODE_ELEMENT&&d.is(CKEDITOR.dtd.$removeEmpty)}}function b(a){return function(){var b;return this[a?"getPreviousNode":"getNextNode"](function(a){!b&&g(a)&&(b=a);return j(a)&&!(h(a)&&a.equals(b))})}}var c=function(a){a.collapsed=a.startContainer&&a.endContainer&&a.startContainer.equals(a.endContainer)&&a.startOffset==a.endOffset},e=function(a,b,c,d){a.optimizeBookmark();var e=a.startContainer,f=a.endContainer,i=a.startOffset,A=a.endOffset,h,o;if(f.type==CKEDITOR.NODE_TEXT)f=f.split(A);
+else if(f.getChildCount()>0)if(A>=f.getChildCount()){f=f.append(a.document.createText(""));o=true}else f=f.getChild(A);if(e.type==CKEDITOR.NODE_TEXT){e.split(i);e.equals(f)&&(f=e.getNext())}else if(i)if(i>=e.getChildCount()){e=e.append(a.document.createText(""));h=true}else e=e.getChild(i).getPrevious();else{e=e.append(a.document.createText(""),1);h=true}var i=e.getParents(),A=f.getParents(),l,p,r;for(l=0;l<i.length;l++){p=i[l];r=A[l];if(!p.equals(r))break}for(var n=c,g,C,j,F=l;F<i.length;F++){g=
+i[F];n&&!g.equals(e)&&(C=n.append(g.clone()));for(g=g.getNext();g;){if(g.equals(A[F])||g.equals(f))break;j=g.getNext();if(b==2)n.append(g.clone(true));else{g.remove();b==1&&n.append(g)}g=j}n&&(n=C)}n=c;for(c=l;c<A.length;c++){g=A[c];b>0&&!g.equals(f)&&(C=n.append(g.clone()));if(!i[c]||g.$.parentNode!=i[c].$.parentNode)for(g=g.getPrevious();g;){if(g.equals(i[c])||g.equals(e))break;j=g.getPrevious();if(b==2)n.$.insertBefore(g.$.cloneNode(true),n.$.firstChild);else{g.remove();b==1&&n.$.insertBefore(g.$,
+n.$.firstChild)}g=j}n&&(n=C)}if(b==2){p=a.startContainer;if(p.type==CKEDITOR.NODE_TEXT){p.$.data=p.$.data+p.$.nextSibling.data;p.$.parentNode.removeChild(p.$.nextSibling)}a=a.endContainer;if(a.type==CKEDITOR.NODE_TEXT&&a.$.nextSibling){a.$.data=a.$.data+a.$.nextSibling.data;a.$.parentNode.removeChild(a.$.nextSibling)}}else{if(p&&r&&(e.$.parentNode!=p.$.parentNode||f.$.parentNode!=r.$.parentNode)){b=r.getIndex();h&&r.$.parentNode==e.$.parentNode&&b--;if(d&&p.type==CKEDITOR.NODE_ELEMENT){d=CKEDITOR.dom.element.createFromHtml('<span data-cke-bookmark="1" style="display:none">&nbsp;</span>',
+a.document);d.insertAfter(p);p.mergeSiblings(false);a.moveToBookmark({startNode:d})}else a.setStart(r.getParent(),b)}a.collapse(true)}h&&e.remove();o&&f.$.parentNode&&f.remove()},d={abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,"var":1},h=CKEDITOR.dom.walker.bogus(),k=/^[\t\r\n ]*(?:&nbsp;|\xa0)$/,j=CKEDITOR.dom.walker.editable(),g=CKEDITOR.dom.walker.ignored(true);CKEDITOR.dom.range.prototype=
+{clone:function(){var a=new CKEDITOR.dom.range(this.root);a._setStartContainer(this.startContainer);a.startOffset=this.startOffset;a._setEndContainer(this.endContainer);a.endOffset=this.endOffset;a.collapsed=this.collapsed;return a},collapse:function(a){if(a){this._setEndContainer(this.startContainer);this.endOffset=this.startOffset}else{this._setStartContainer(this.endContainer);this.startOffset=this.endOffset}this.collapsed=true},cloneContents:function(){var a=new CKEDITOR.dom.documentFragment(this.document);
+this.collapsed||e(this,2,a);return a},deleteContents:function(a){this.collapsed||e(this,0,null,a)},extractContents:function(a){var b=new CKEDITOR.dom.documentFragment(this.document);this.collapsed||e(this,1,b,a);return b},createBookmark:function(a){var b,c,d,e,f=this.collapsed;b=this.document.createElement("span");b.data("cke-bookmark",1);b.setStyle("display","none");b.setHtml("&nbsp;");if(a){d="cke_bm_"+CKEDITOR.tools.getNextNumber();b.setAttribute("id",d+(f?"C":"S"))}if(!f){c=b.clone();c.setHtml("&nbsp;");
+a&&c.setAttribute("id",d+"E");e=this.clone();e.collapse();e.insertNode(c)}e=this.clone();e.collapse(true);e.insertNode(b);if(c){this.setStartAfter(b);this.setEndBefore(c)}else this.moveToPosition(b,CKEDITOR.POSITION_AFTER_END);return{startNode:a?d+(f?"C":"S"):b,endNode:a?d+"E":c,serializable:a,collapsed:f}},createBookmark2:function(){function a(c){var d=c.container,e=c.offset,f;f=d;var m=e;f=f.type!=CKEDITOR.NODE_ELEMENT||m===0||m==f.getChildCount()?0:f.getChild(m-1).type==CKEDITOR.NODE_TEXT&&f.getChild(m).type==
+CKEDITOR.NODE_TEXT;if(f){d=d.getChild(e-1);e=d.getLength()}d.type==CKEDITOR.NODE_ELEMENT&&e>1&&(e=d.getChild(e-1).getIndex(true)+1);if(d.type==CKEDITOR.NODE_TEXT){f=d;for(m=0;(f=f.getPrevious())&&f.type==CKEDITOR.NODE_TEXT;)m=m+f.getLength();f=m;if(d.getText())e=e+f;else{m=d.getPrevious(b);if(f){e=f;d=m?m.getNext():d.getParent().getFirst()}else{d=d.getParent();e=m?m.getIndex(true)+1:0}}}c.container=d;c.offset=e}var b=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_TEXT,true);return function(b){var c=this.collapsed,
+d={container:this.startContainer,offset:this.startOffset},e={container:this.endContainer,offset:this.endOffset};if(b){a(d);c||a(e)}return{start:d.container.getAddress(b),end:c?null:e.container.getAddress(b),startOffset:d.offset,endOffset:e.offset,normalized:b,collapsed:c,is2:true}}}(),moveToBookmark:function(a){if(a.is2){var b=this.document.getByAddress(a.start,a.normalized),c=a.startOffset,d=a.end&&this.document.getByAddress(a.end,a.normalized),a=a.endOffset;this.setStart(b,c);d?this.setEnd(d,a):
+this.collapse(true)}else{b=(c=a.serializable)?this.document.getById(a.startNode):a.startNode;a=c?this.document.getById(a.endNode):a.endNode;this.setStartBefore(b);b.remove();if(a){this.setEndBefore(a);a.remove()}else this.collapse(true)}},getBoundaryNodes:function(){var a=this.startContainer,b=this.endContainer,c=this.startOffset,d=this.endOffset,e;if(a.type==CKEDITOR.NODE_ELEMENT){e=a.getChildCount();if(e>c)a=a.getChild(c);else if(e<1)a=a.getPreviousSourceNode();else{for(a=a.$;a.lastChild;)a=a.lastChild;
+a=new CKEDITOR.dom.node(a);a=a.getNextSourceNode()||a}}if(b.type==CKEDITOR.NODE_ELEMENT){e=b.getChildCount();if(e>d)b=b.getChild(d).getPreviousSourceNode(true);else if(e<1)b=b.getPreviousSourceNode();else{for(b=b.$;b.lastChild;)b=b.lastChild;b=new CKEDITOR.dom.node(b)}}a.getPosition(b)&CKEDITOR.POSITION_FOLLOWING&&(a=b);return{startNode:a,endNode:b}},getCommonAncestor:function(a,b){var c=this.startContainer,d=this.endContainer,c=c.equals(d)?a&&c.type==CKEDITOR.NODE_ELEMENT&&this.startOffset==this.endOffset-
+1?c.getChild(this.startOffset):c:c.getCommonAncestor(d);return b&&!c.is?c.getParent():c},optimize:function(){var a=this.startContainer,b=this.startOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setStartAfter(a):this.setStartBefore(a));a=this.endContainer;b=this.endOffset;a.type!=CKEDITOR.NODE_ELEMENT&&(b?b>=a.getLength()&&this.setEndAfter(a):this.setEndBefore(a))},optimizeBookmark:function(){var a=this.startContainer,b=this.endContainer;a.is&&(a.is("span")&&a.data("cke-bookmark"))&&
+this.setStartAt(a,CKEDITOR.POSITION_BEFORE_START);b&&(b.is&&b.is("span")&&b.data("cke-bookmark"))&&this.setEndAt(b,CKEDITOR.POSITION_AFTER_END)},trim:function(a,b){var c=this.startContainer,d=this.startOffset,e=this.collapsed;if((!a||e)&&c&&c.type==CKEDITOR.NODE_TEXT){if(d)if(d>=c.getLength()){d=c.getIndex()+1;c=c.getParent()}else{var f=c.split(d),d=c.getIndex()+1,c=c.getParent();if(this.startContainer.equals(this.endContainer))this.setEnd(f,this.endOffset-this.startOffset);else if(c.equals(this.endContainer))this.endOffset=
+this.endOffset+1}else{d=c.getIndex();c=c.getParent()}this.setStart(c,d);if(e){this.collapse(true);return}}c=this.endContainer;d=this.endOffset;if(!b&&!e&&c&&c.type==CKEDITOR.NODE_TEXT){if(d){d>=c.getLength()||c.split(d);d=c.getIndex()+1}else d=c.getIndex();c=c.getParent();this.setEnd(c,d)}},enlarge:function(a,b){function c(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.hasAttribute("contenteditable")?null:a}var d=RegExp(/[^\s\ufeff]/);switch(a){case CKEDITOR.ENLARGE_INLINE:var e=1;case CKEDITOR.ENLARGE_ELEMENT:if(this.collapsed)break;
+var f=this.getCommonAncestor(),i=this.root,h,g,o,l,p,r=false,n,j;n=this.startContainer;var C=this.startOffset;if(n.type==CKEDITOR.NODE_TEXT){if(C){n=!CKEDITOR.tools.trim(n.substring(0,C)).length&&n;r=!!n}if(n&&!(l=n.getPrevious()))o=n.getParent()}else{C&&(l=n.getChild(C-1)||n.getLast());l||(o=n)}for(o=c(o);o||l;){if(o&&!l){!p&&o.equals(f)&&(p=true);if(e?o.isBlockBoundary():!i.contains(o))break;if(!r||o.getComputedStyle("display")!="inline"){r=false;p?h=o:this.setStartBefore(o)}l=o.getPrevious()}for(;l;){n=
+false;if(l.type==CKEDITOR.NODE_COMMENT)l=l.getPrevious();else{if(l.type==CKEDITOR.NODE_TEXT){j=l.getText();d.test(j)&&(l=null);n=/[\s\ufeff]$/.test(j)}else if((l.$.offsetWidth>(CKEDITOR.env.webkit?1:0)||b&&l.is("br"))&&!l.data("cke-bookmark"))if(r&&CKEDITOR.dtd.$removeEmpty[l.getName()]){j=l.getText();if(d.test(j))l=null;else for(var C=l.$.getElementsByTagName("*"),k=0,F;F=C[k++];)if(!CKEDITOR.dtd.$removeEmpty[F.nodeName.toLowerCase()]){l=null;break}l&&(n=!!j.length)}else l=null;n&&(r?p?h=o:o&&this.setStartBefore(o):
+r=true);if(l){n=l.getPrevious();if(!o&&!n){o=l;l=null;break}l=n}else o=null}}o&&(o=c(o.getParent()))}n=this.endContainer;C=this.endOffset;o=l=null;p=r=false;var K=function(a,b){var c=new CKEDITOR.dom.range(i);c.setStart(a,b);c.setEndAt(i,CKEDITOR.POSITION_BEFORE_END);var c=new CKEDITOR.dom.walker(c),e;for(c.guard=function(a){return!(a.type==CKEDITOR.NODE_ELEMENT&&a.isBlockBoundary())};e=c.next();){if(e.type!=CKEDITOR.NODE_TEXT)return false;j=e!=a?e.getText():e.substring(b);if(d.test(j))return false}return true};
+if(n.type==CKEDITOR.NODE_TEXT)if(CKEDITOR.tools.trim(n.substring(C)).length)r=true;else{r=!n.getLength();if(C==n.getLength()){if(!(l=n.getNext()))o=n.getParent()}else K(n,C)&&(o=n.getParent())}else(l=n.getChild(C))||(o=n);for(;o||l;){if(o&&!l){!p&&o.equals(f)&&(p=true);if(e?o.isBlockBoundary():!i.contains(o))break;if(!r||o.getComputedStyle("display")!="inline"){r=false;p?g=o:o&&this.setEndAfter(o)}l=o.getNext()}for(;l;){n=false;if(l.type==CKEDITOR.NODE_TEXT){j=l.getText();K(l,0)||(l=null);n=/^[\s\ufeff]/.test(j)}else if(l.type==
+CKEDITOR.NODE_ELEMENT){if((l.$.offsetWidth>0||b&&l.is("br"))&&!l.data("cke-bookmark"))if(r&&CKEDITOR.dtd.$removeEmpty[l.getName()]){j=l.getText();if(d.test(j))l=null;else{C=l.$.getElementsByTagName("*");for(k=0;F=C[k++];)if(!CKEDITOR.dtd.$removeEmpty[F.nodeName.toLowerCase()]){l=null;break}}l&&(n=!!j.length)}else l=null}else n=1;n&&r&&(p?g=o:this.setEndAfter(o));if(l){n=l.getNext();if(!o&&!n){o=l;l=null;break}l=n}else o=null}o&&(o=c(o.getParent()))}if(h&&g){f=h.contains(g)?g:h;this.setStartBefore(f);
+this.setEndAfter(f)}break;case CKEDITOR.ENLARGE_BLOCK_CONTENTS:case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:o=new CKEDITOR.dom.range(this.root);i=this.root;o.setStartAt(i,CKEDITOR.POSITION_AFTER_START);o.setEnd(this.startContainer,this.startOffset);o=new CKEDITOR.dom.walker(o);var I,v,G=CKEDITOR.dom.walker.blockBoundary(a==CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS?{br:1}:null),z=null,B=function(a){if(a.type==CKEDITOR.NODE_ELEMENT&&a.getAttribute("contenteditable")=="false")if(z){if(z.equals(a)){z=null;return}}else z=
+a;else if(z)return;var b=G(a);b||(I=a);return b},e=function(a){var b=B(a);!b&&(a.is&&a.is("br"))&&(v=a);return b};o.guard=B;o=o.lastBackward();I=I||i;this.setStartAt(I,!I.is("br")&&(!o&&this.checkStartOfBlock()||o&&I.contains(o))?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_AFTER_END);if(a==CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS){o=this.clone();o=new CKEDITOR.dom.walker(o);var x=CKEDITOR.dom.walker.whitespaces(),E=CKEDITOR.dom.walker.bookmark();o.evaluator=function(a){return!x(a)&&!E(a)};if((o=o.previous())&&
+o.type==CKEDITOR.NODE_ELEMENT&&o.is("br"))break}o=this.clone();o.collapse();o.setEndAt(i,CKEDITOR.POSITION_BEFORE_END);o=new CKEDITOR.dom.walker(o);o.guard=a==CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS?e:B;I=z=v=null;o=o.lastForward();I=I||i;this.setEndAt(I,!o&&this.checkEndOfBlock()||o&&I.contains(o)?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_BEFORE_START);v&&this.setEndAfter(v)}},shrink:function(a,b,c){if(!this.collapsed){var a=a||CKEDITOR.SHRINK_TEXT,d=this.clone(),e=this.startContainer,f=this.endContainer,
+i=this.startOffset,h=this.endOffset,g=1,o=1;if(e&&e.type==CKEDITOR.NODE_TEXT)if(i)if(i>=e.getLength())d.setStartAfter(e);else{d.setStartBefore(e);g=0}else d.setStartBefore(e);if(f&&f.type==CKEDITOR.NODE_TEXT)if(h)if(h>=f.getLength())d.setEndAfter(f);else{d.setEndAfter(f);o=0}else d.setEndBefore(f);var d=new CKEDITOR.dom.walker(d),l=CKEDITOR.dom.walker.bookmark();d.evaluator=function(b){return b.type==(a==CKEDITOR.SHRINK_ELEMENT?CKEDITOR.NODE_ELEMENT:CKEDITOR.NODE_TEXT)};var p;d.guard=function(b,d){if(l(b))return true;
+if(a==CKEDITOR.SHRINK_ELEMENT&&b.type==CKEDITOR.NODE_TEXT||d&&b.equals(p)||c===false&&b.type==CKEDITOR.NODE_ELEMENT&&b.isBlockBoundary()||b.type==CKEDITOR.NODE_ELEMENT&&b.hasAttribute("contenteditable"))return false;!d&&b.type==CKEDITOR.NODE_ELEMENT&&(p=b);return true};if(g)(e=d[a==CKEDITOR.SHRINK_ELEMENT?"lastForward":"next"]())&&this.setStartAt(e,b?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_START);if(o){d.reset();(d=d[a==CKEDITOR.SHRINK_ELEMENT?"lastBackward":"previous"]())&&this.setEndAt(d,
+b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_END)}return!(!g&&!o)}},insertNode:function(a){this.optimizeBookmark();this.trim(false,true);var b=this.startContainer,c=b.getChild(this.startOffset);c?a.insertBefore(c):b.append(a);a.getParent()&&a.getParent().equals(this.endContainer)&&this.endOffset++;this.setStartBefore(a)},moveToPosition:function(a,b){this.setStartAt(a,b);this.collapse(true)},moveToRange:function(a){this.setStart(a.startContainer,a.startOffset);this.setEnd(a.endContainer,
+a.endOffset)},selectNodeContents:function(a){this.setStart(a,0);this.setEnd(a,a.type==CKEDITOR.NODE_TEXT?a.getLength():a.getChildCount())},setStart:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[a.getName()]){b=a.getIndex();a=a.getParent()}this._setStartContainer(a);this.startOffset=b;if(!this.endContainer){this._setEndContainer(a);this.endOffset=b}c(this)},setEnd:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$empty[a.getName()]){b=a.getIndex()+1;a=a.getParent()}this._setEndContainer(a);
+this.endOffset=b;if(!this.startContainer){this._setStartContainer(a);this.startOffset=b}c(this)},setStartAfter:function(a){this.setStart(a.getParent(),a.getIndex()+1)},setStartBefore:function(a){this.setStart(a.getParent(),a.getIndex())},setEndAfter:function(a){this.setEnd(a.getParent(),a.getIndex()+1)},setEndBefore:function(a){this.setEnd(a.getParent(),a.getIndex())},setStartAt:function(a,b){switch(b){case CKEDITOR.POSITION_AFTER_START:this.setStart(a,0);break;case CKEDITOR.POSITION_BEFORE_END:a.type==
+CKEDITOR.NODE_TEXT?this.setStart(a,a.getLength()):this.setStart(a,a.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setStartBefore(a);break;case CKEDITOR.POSITION_AFTER_END:this.setStartAfter(a)}c(this)},setEndAt:function(a,b){switch(b){case CKEDITOR.POSITION_AFTER_START:this.setEnd(a,0);break;case CKEDITOR.POSITION_BEFORE_END:a.type==CKEDITOR.NODE_TEXT?this.setEnd(a,a.getLength()):this.setEnd(a,a.getChildCount());break;case CKEDITOR.POSITION_BEFORE_START:this.setEndBefore(a);break;
+case CKEDITOR.POSITION_AFTER_END:this.setEndAfter(a)}c(this)},fixBlock:function(a,b){var c=this.createBookmark(),d=this.document.createElement(b);this.collapse(a);this.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);this.extractContents().appendTo(d);d.trim();d.appendBogus();this.insertNode(d);this.moveToBookmark(c);return d},splitBlock:function(a){var b=new CKEDITOR.dom.elementPath(this.startContainer,this.root),c=new CKEDITOR.dom.elementPath(this.endContainer,this.root),d=b.block,e=c.block,f=null;if(!b.blockLimit.equals(c.blockLimit))return null;
+if(a!="br"){if(!d){d=this.fixBlock(true,a);e=(new CKEDITOR.dom.elementPath(this.endContainer,this.root)).block}e||(e=this.fixBlock(false,a))}a=d&&this.checkStartOfBlock();b=e&&this.checkEndOfBlock();this.deleteContents();if(d&&d.equals(e))if(b){f=new CKEDITOR.dom.elementPath(this.startContainer,this.root);this.moveToPosition(e,CKEDITOR.POSITION_AFTER_END);e=null}else if(a){f=new CKEDITOR.dom.elementPath(this.startContainer,this.root);this.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START);d=null}else{e=
+this.splitElement(d);d.is("ul","ol")||d.appendBogus()}return{previousBlock:d,nextBlock:e,wasStartOfBlock:a,wasEndOfBlock:b,elementPath:f}},splitElement:function(a){if(!this.collapsed)return null;this.setEndAt(a,CKEDITOR.POSITION_BEFORE_END);var b=this.extractContents(),c=a.clone(false);b.appendTo(c);c.insertAfter(a);this.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);return c},removeEmptyBlocksAtEnd:function(){function a(d){return function(a){return b(a)||(c(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.isEmptyInlineRemoveable())||
+d.is("table")&&a.is("caption")?false:true}}var b=CKEDITOR.dom.walker.whitespaces(),c=CKEDITOR.dom.walker.bookmark(false);return function(b){for(var c=this.createBookmark(),d=this[b?"endPath":"startPath"](),e=d.block||d.blockLimit,f;e&&!e.equals(d.root)&&!e.getFirst(a(e));){f=e.getParent();this[b?"setEndAt":"setStartAt"](e,CKEDITOR.POSITION_AFTER_END);e.remove(1);e=f}this.moveToBookmark(c)}}(),startPath:function(){return new CKEDITOR.dom.elementPath(this.startContainer,this.root)},endPath:function(){return new CKEDITOR.dom.elementPath(this.endContainer,
+this.root)},checkBoundaryOfElement:function(a,b){var c=b==CKEDITOR.START,d=this.clone();d.collapse(c);d[c?"setStartAt":"setEndAt"](a,c?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END);d=new CKEDITOR.dom.walker(d);d.evaluator=f(c);return d[c?"checkBackward":"checkForward"]()},checkStartOfBlock:function(){var b=this.startContainer,c=this.startOffset;if(CKEDITOR.env.ie&&c&&b.type==CKEDITOR.NODE_TEXT){b=CKEDITOR.tools.ltrim(b.substring(0,c));k.test(b)&&this.trim(0,1)}this.trim();b=new CKEDITOR.dom.elementPath(this.startContainer,
+this.root);c=this.clone();c.collapse(true);c.setStartAt(b.block||b.blockLimit,CKEDITOR.POSITION_AFTER_START);b=new CKEDITOR.dom.walker(c);b.evaluator=a();return b.checkBackward()},checkEndOfBlock:function(){var b=this.endContainer,c=this.endOffset;if(CKEDITOR.env.ie&&b.type==CKEDITOR.NODE_TEXT){b=CKEDITOR.tools.rtrim(b.substring(c));k.test(b)&&this.trim(1,0)}this.trim();b=new CKEDITOR.dom.elementPath(this.endContainer,this.root);c=this.clone();c.collapse(false);c.setEndAt(b.block||b.blockLimit,CKEDITOR.POSITION_BEFORE_END);
+b=new CKEDITOR.dom.walker(c);b.evaluator=a();return b.checkForward()},getPreviousNode:function(a,b,c){var d=this.clone();d.collapse(1);d.setStartAt(c||this.root,CKEDITOR.POSITION_AFTER_START);c=new CKEDITOR.dom.walker(d);c.evaluator=a;c.guard=b;return c.previous()},getNextNode:function(a,b,c){var d=this.clone();d.collapse();d.setEndAt(c||this.root,CKEDITOR.POSITION_BEFORE_END);c=new CKEDITOR.dom.walker(d);c.evaluator=a;c.guard=b;return c.next()},checkReadOnly:function(){function a(b,c){for(;b;){if(b.type==
+CKEDITOR.NODE_ELEMENT){if(b.getAttribute("contentEditable")=="false"&&!b.data("cke-editable"))return 0;if(b.is("html")||b.getAttribute("contentEditable")=="true"&&(b.contains(c)||b.equals(c)))break}b=b.getParent()}return 1}return function(){var b=this.startContainer,c=this.endContainer;return!(a(b,c)&&a(c,b))}}(),moveToElementEditablePosition:function(a,b){if(a.type==CKEDITOR.NODE_ELEMENT&&!a.isEditable(false)){this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START);return true}for(var c=
+0;a;){if(a.type==CKEDITOR.NODE_TEXT){b&&this.endContainer&&this.checkEndOfBlock()&&k.test(a.getText())?this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START):this.moveToPosition(a,b?CKEDITOR.POSITION_AFTER_END:CKEDITOR.POSITION_BEFORE_START);c=1;break}if(a.type==CKEDITOR.NODE_ELEMENT)if(a.isEditable()){this.moveToPosition(a,b?CKEDITOR.POSITION_BEFORE_END:CKEDITOR.POSITION_AFTER_START);c=1}else if(b&&a.is("br")&&this.endContainer&&this.checkEndOfBlock())this.moveToPosition(a,CKEDITOR.POSITION_BEFORE_START);
+else if(a.getAttribute("contenteditable")=="false"&&a.is(CKEDITOR.dtd.$block)){this.setStartBefore(a);this.setEndAfter(a);return true}var d=a,e=c,f=void 0;d.type==CKEDITOR.NODE_ELEMENT&&d.isEditable(false)&&(f=d[b?"getLast":"getFirst"](g));!e&&!f&&(f=d[b?"getPrevious":"getNext"](g));a=f}return!!c},moveToClosestEditablePosition:function(a,b){var c=new CKEDITOR.dom.range(this.root),d=0,e,f=[CKEDITOR.POSITION_AFTER_END,CKEDITOR.POSITION_BEFORE_START];c.moveToPosition(a,f[b?0:1]);if(a.is(CKEDITOR.dtd.$block)){if(e=
+c[b?"getNextEditableNode":"getPreviousEditableNode"]()){d=1;if(e.type==CKEDITOR.NODE_ELEMENT&&e.is(CKEDITOR.dtd.$block)&&e.getAttribute("contenteditable")=="false"){c.setStartAt(e,CKEDITOR.POSITION_BEFORE_START);c.setEndAt(e,CKEDITOR.POSITION_AFTER_END)}else c.moveToPosition(e,f[b?1:0])}}else d=1;d&&this.moveToRange(c);return!!d},moveToElementEditStart:function(a){return this.moveToElementEditablePosition(a)},moveToElementEditEnd:function(a){return this.moveToElementEditablePosition(a,true)},getEnclosedNode:function(){var a=
+this.clone();a.optimize();if(a.startContainer.type!=CKEDITOR.NODE_ELEMENT||a.endContainer.type!=CKEDITOR.NODE_ELEMENT)return null;var a=new CKEDITOR.dom.walker(a),b=CKEDITOR.dom.walker.bookmark(false,true),c=CKEDITOR.dom.walker.whitespaces(true);a.evaluator=function(a){return c(a)&&b(a)};var d=a.next();a.reset();return d&&d.equals(a.previous())?d:null},getTouchedStartNode:function(){var a=this.startContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.startOffset)||a},getTouchedEndNode:function(){var a=
+this.endContainer;return this.collapsed||a.type!=CKEDITOR.NODE_ELEMENT?a:a.getChild(this.endOffset-1)||a},getNextEditableNode:b(),getPreviousEditableNode:b(1),scrollIntoView:function(){var a=new CKEDITOR.dom.element.createFromHtml("<span>&nbsp;</span>",this.document),b,c,d,e=this.clone();e.optimize();if(d=e.startContainer.type==CKEDITOR.NODE_TEXT){c=e.startContainer.getText();b=e.startContainer.split(e.startOffset);a.insertAfter(e.startContainer)}else e.insertNode(a);a.scrollIntoView();if(d){e.startContainer.setText(c);
+b.remove()}a.remove()},_setStartContainer:function(a){this.startContainer=a},_setEndContainer:function(a){this.endContainer=a}}})();CKEDITOR.POSITION_AFTER_START=1;CKEDITOR.POSITION_BEFORE_END=2;CKEDITOR.POSITION_BEFORE_START=3;CKEDITOR.POSITION_AFTER_END=4;CKEDITOR.ENLARGE_ELEMENT=1;CKEDITOR.ENLARGE_BLOCK_CONTENTS=2;CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS=3;CKEDITOR.ENLARGE_INLINE=4;CKEDITOR.START=1;CKEDITOR.END=2;CKEDITOR.SHRINK_ELEMENT=1;CKEDITOR.SHRINK_TEXT=2;"use strict";
+(function(){function a(a){if(!(arguments.length<1)){this.range=a;this.forceBrBreak=0;this.enlargeBr=1;this.enforceRealBlocks=0;this._||(this._={})}}function f(a){var b=[];a.forEach(function(a){if(a.getAttribute("contenteditable")=="true"){b.push(a);return false}},CKEDITOR.NODE_ELEMENT,true);return b}function b(a,c,d,e){a:{e==null&&(e=f(d));for(var h;h=e.shift();)if(h.getDtd().p){e={element:h,remaining:e};break a}e=null}if(!e)return 0;if((h=CKEDITOR.filter.instances[e.element.data("cke-filter")])&&
+!h.check(c))return b(a,c,d,e.remaining);c=new CKEDITOR.dom.range(e.element);c.selectNodeContents(e.element);c=c.createIterator();c.enlargeBr=a.enlargeBr;c.enforceRealBlocks=a.enforceRealBlocks;c.activeFilter=c.filter=h;a._.nestedEditable={element:e.element,container:d,remaining:e.remaining,iterator:c};return 1}function c(a,b,c){if(!b)return false;a=a.clone();a.collapse(!c);return a.checkBoundaryOfElement(b,c?CKEDITOR.START:CKEDITOR.END)}var e=/^[\r\n\t ]+$/,d=CKEDITOR.dom.walker.bookmark(false,true),
+h=CKEDITOR.dom.walker.whitespaces(true),k=function(a){return d(a)&&h(a)},j={dd:1,dt:1,li:1};a.prototype={getNextParagraph:function(a){var f,h,s,w,q,a=a||"p";if(this._.nestedEditable){if(f=this._.nestedEditable.iterator.getNextParagraph(a)){this.activeFilter=this._.nestedEditable.iterator.activeFilter;return f}this.activeFilter=this.filter;if(b(this,a,this._.nestedEditable.container,this._.nestedEditable.remaining)){this.activeFilter=this._.nestedEditable.iterator.activeFilter;return this._.nestedEditable.iterator.getNextParagraph(a)}this._.nestedEditable=
+null}if(!this.range.root.getDtd()[a])return null;if(!this._.started){var t=this.range.clone();h=t.startPath();var i=t.endPath(),A=!t.collapsed&&c(t,h.block),u=!t.collapsed&&c(t,i.block,1);t.shrink(CKEDITOR.SHRINK_ELEMENT,true);A&&t.setStartAt(h.block,CKEDITOR.POSITION_BEFORE_END);u&&t.setEndAt(i.block,CKEDITOR.POSITION_AFTER_START);h=t.endContainer.hasAscendant("pre",true)||t.startContainer.hasAscendant("pre",true);t.enlarge(this.forceBrBreak&&!h||!this.enlargeBr?CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
+CKEDITOR.ENLARGE_BLOCK_CONTENTS);if(!t.collapsed){h=new CKEDITOR.dom.walker(t.clone());i=CKEDITOR.dom.walker.bookmark(true,true);h.evaluator=i;this._.nextNode=h.next();h=new CKEDITOR.dom.walker(t.clone());h.evaluator=i;h=h.previous();this._.lastNode=h.getNextSourceNode(true,null,t.root);if(this._.lastNode&&this._.lastNode.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(this._.lastNode.getText())&&this._.lastNode.getParent().isBlockBoundary()){i=this.range.clone();i.moveToPosition(this._.lastNode,CKEDITOR.POSITION_AFTER_END);
+if(i.checkEndOfBlock()){i=new CKEDITOR.dom.elementPath(i.endContainer,i.root);this._.lastNode=(i.block||i.blockLimit).getNextSourceNode(true)}}if(!this._.lastNode||!t.root.contains(this._.lastNode)){this._.lastNode=this._.docEndMarker=t.document.createText("");this._.lastNode.insertAfter(h)}t=null}this._.started=1;h=t}i=this._.nextNode;t=this._.lastNode;for(this._.nextNode=null;i;){var A=0,u=i.hasAscendant("pre"),o=i.type!=CKEDITOR.NODE_ELEMENT,l=0;if(o)i.type==CKEDITOR.NODE_TEXT&&e.test(i.getText())&&
+(o=0);else{var p=i.getName();if(CKEDITOR.dtd.$block[p]&&i.getAttribute("contenteditable")=="false"){f=i;b(this,a,f);break}else if(i.isBlockBoundary(this.forceBrBreak&&!u&&{br:1})){if(p=="br")o=1;else if(!h&&!i.getChildCount()&&p!="hr"){f=i;s=i.equals(t);break}if(h){h.setEndAt(i,CKEDITOR.POSITION_BEFORE_START);if(p!="br")this._.nextNode=i}A=1}else{if(i.getFirst()){if(!h){h=this.range.clone();h.setStartAt(i,CKEDITOR.POSITION_BEFORE_START)}i=i.getFirst();continue}o=1}}if(o&&!h){h=this.range.clone();
+h.setStartAt(i,CKEDITOR.POSITION_BEFORE_START)}s=(!A||o)&&i.equals(t);if(h&&!A)for(;!i.getNext(k)&&!s;){p=i.getParent();if(p.isBlockBoundary(this.forceBrBreak&&!u&&{br:1})){A=1;o=0;s||p.equals(t);h.setEndAt(p,CKEDITOR.POSITION_BEFORE_END);break}i=p;o=1;s=i.equals(t);l=1}o&&h.setEndAt(i,CKEDITOR.POSITION_AFTER_END);i=this._getNextSourceNode(i,l,t);if((s=!i)||A&&h)break}if(!f){if(!h){this._.docEndMarker&&this._.docEndMarker.remove();return this._.nextNode=null}f=new CKEDITOR.dom.elementPath(h.startContainer,
+h.root);i=f.blockLimit;A={div:1,th:1,td:1};f=f.block;if(!f&&i&&!this.enforceRealBlocks&&A[i.getName()]&&h.checkStartOfBlock()&&h.checkEndOfBlock()&&!i.equals(h.root))f=i;else if(!f||this.enforceRealBlocks&&f.is(j)){f=this.range.document.createElement(a);h.extractContents().appendTo(f);f.trim();h.insertNode(f);w=q=true}else if(f.getName()!="li"){if(!h.checkStartOfBlock()||!h.checkEndOfBlock()){f=f.clone(false);h.extractContents().appendTo(f);f.trim();q=h.splitBlock();w=!q.wasStartOfBlock;q=!q.wasEndOfBlock;
+h.insertNode(f)}}else if(!s)this._.nextNode=f.equals(t)?null:this._getNextSourceNode(h.getBoundaryNodes().endNode,1,t)}if(w)(w=f.getPrevious())&&w.type==CKEDITOR.NODE_ELEMENT&&(w.getName()=="br"?w.remove():w.getLast()&&w.getLast().$.nodeName.toLowerCase()=="br"&&w.getLast().remove());if(q)(w=f.getLast())&&w.type==CKEDITOR.NODE_ELEMENT&&w.getName()=="br"&&(!CKEDITOR.env.needsBrFiller||w.getPrevious(d)||w.getNext(d))&&w.remove();if(!this._.nextNode)this._.nextNode=s||f.equals(t)||!t?null:this._getNextSourceNode(f,
+1,t);return f},_getNextSourceNode:function(a,b,c){function e(a){return!(a.equals(c)||a.equals(f))}for(var f=this.range.root,a=a.getNextSourceNode(b,null,e);!d(a);)a=a.getNextSourceNode(b,null,e);return a}};CKEDITOR.dom.range.prototype.createIterator=function(){return new a(this)}})();
+CKEDITOR.command=function(a,f){this.uiItems=[];this.exec=function(b){if(this.state==CKEDITOR.TRISTATE_DISABLED||!this.checkAllowed())return false;this.editorFocus&&a.focus();return this.fire("exec")===false?true:f.exec.call(this,a,b)!==false};this.refresh=function(a,b){if(!this.readOnly&&a.readOnly)return true;if(this.context&&!b.isContextFor(this.context)){this.disable();return true}if(!this.checkAllowed(true)){this.disable();return true}this.startDisabled||this.enable();this.modes&&!this.modes[a.mode]&&
+this.disable();return this.fire("refresh",{editor:a,path:b})===false?true:f.refresh&&f.refresh.apply(this,arguments)!==false};var b;this.checkAllowed=function(c){return!c&&typeof b=="boolean"?b:b=a.activeFilter.checkFeature(this)};CKEDITOR.tools.extend(this,f,{modes:{wysiwyg:1},editorFocus:1,contextSensitive:!!f.context,state:CKEDITOR.TRISTATE_DISABLED});CKEDITOR.event.call(this)};
+CKEDITOR.command.prototype={enable:function(){this.state==CKEDITOR.TRISTATE_DISABLED&&this.checkAllowed()&&this.setState(!this.preserveState||typeof this.previousState=="undefined"?CKEDITOR.TRISTATE_OFF:this.previousState)},disable:function(){this.setState(CKEDITOR.TRISTATE_DISABLED)},setState:function(a){if(this.state==a||a!=CKEDITOR.TRISTATE_DISABLED&&!this.checkAllowed())return false;this.previousState=this.state;this.state=a;this.fire("state");return true},toggleState:function(){this.state==CKEDITOR.TRISTATE_OFF?
+this.setState(CKEDITOR.TRISTATE_ON):this.state==CKEDITOR.TRISTATE_ON&&this.setState(CKEDITOR.TRISTATE_OFF)}};CKEDITOR.event.implementOn(CKEDITOR.command.prototype);CKEDITOR.ENTER_P=1;CKEDITOR.ENTER_BR=2;CKEDITOR.ENTER_DIV=3;
+CKEDITOR.config={customConfig:"config.js",autoUpdateElement:!0,language:"",defaultLanguage:"en",contentsLangDirection:"",enterMode:CKEDITOR.ENTER_P,forceEnterMode:!1,shiftEnterMode:CKEDITOR.ENTER_BR,docType:"<!DOCTYPE html>",bodyId:"",bodyClass:"",fullPage:!1,height:200,extraPlugins:"",removePlugins:"",protectedSource:[],tabIndex:0,width:"",baseFloatZIndex:1E4,blockedKeystrokes:[CKEDITOR.CTRL+66,CKEDITOR.CTRL+73,CKEDITOR.CTRL+85]};
+(function(){function a(a,b,c,d,e){var f,p,a=[];for(f in b){p=b[f];p=typeof p=="boolean"?{}:typeof p=="function"?{match:p}:K(p);if(f.charAt(0)!="$")p.elements=f;if(c)p.featureName=c.toLowerCase();var i=p;i.elements=h(i.elements,/\s+/)||null;i.propertiesOnly=i.propertiesOnly||i.elements===true;var l=/\s*,\s*/,r=void 0;for(r in z){i[r]=h(i[r],l)||null;var x=i,n=B[r],v=h(i[B[r]],l),q=i[r],E=[],g=true,o=void 0;v?g=false:v={};for(o in q)if(o.charAt(0)=="!"){o=o.slice(1);E.push(o);v[o]=true;g=false}for(;o=
+E.pop();){q[o]=q["!"+o];delete q["!"+o]}x[n]=(g?false:v)||null}i.match=i.match||null;d.push(p);a.push(p)}for(var b=e.elements,e=e.generic,C,c=0,d=a.length;c<d;++c){f=K(a[c]);p=f.classes===true||f.styles===true||f.attributes===true;i=f;r=n=l=void 0;for(l in z)i[l]=A(i[l]);x=true;for(r in B){l=B[r];n=i[l];v=[];q=void 0;for(q in n)q.indexOf("*")>-1?v.push(RegExp("^"+q.replace(/\*/g,".*")+"$")):v.push(q);n=v;if(n.length){i[l]=n;x=false}}i.nothingRequired=x;i.noProperties=!(i.attributes||i.classes||i.styles);
+if(f.elements===true||f.elements===null)e[p?"unshift":"push"](f);else{i=f.elements;delete f.elements;for(C in i)if(b[C])b[C][p?"unshift":"push"](f);else b[C]=[f]}}}function f(a,c,d,e){if(!a.match||a.match(c))if(e||k(a,c)){if(!a.propertiesOnly)d.valid=true;if(!d.allAttributes)d.allAttributes=b(a.attributes,c.attributes,d.validAttributes);if(!d.allStyles)d.allStyles=b(a.styles,c.styles,d.validStyles);if(!d.allClasses){a=a.classes;c=c.classes;e=d.validClasses;if(a)if(a===true)a=true;else{for(var f=0,
+p=c.length,i;f<p;++f){i=c[f];e[i]||(e[i]=a(i))}a=false}else a=false;d.allClasses=a}}}function b(a,b,c){if(!a)return false;if(a===true)return true;for(var d in b)c[d]||(c[d]=a(d));return false}function c(a,b,c){if(!a.match||a.match(b)){if(a.noProperties)return false;c.hadInvalidAttribute=e(a.attributes,b.attributes)||c.hadInvalidAttribute;c.hadInvalidStyle=e(a.styles,b.styles)||c.hadInvalidStyle;a=a.classes;b=b.classes;if(a){for(var d=false,f=a===true,p=b.length;p--;)if(f||a(b[p])){b.splice(p,1);d=
+true}a=d}else a=false;c.hadInvalidClass=a||c.hadInvalidClass}}function e(a,b){if(!a)return false;var c=false,d=a===true,e;for(e in b)if(d||a(e)){delete b[e];c=true}return c}function d(a,b,c){if(a.disabled||a.customConfig&&!c||!b)return false;a._.cachedChecks={};return true}function h(a,b){if(!a)return false;if(a===true)return a;if(typeof a=="string"){a=I(a);return a=="*"?true:CKEDITOR.tools.convertArrayToObject(a.split(b))}if(CKEDITOR.tools.isArray(a))return a.length?CKEDITOR.tools.convertArrayToObject(a):
+false;var c={},d=0,e;for(e in a){c[e]=a[e];d++}return d?c:false}function k(a,b){if(a.nothingRequired)return true;var c,d,e,f;if(e=a.requiredClasses){f=b.classes;for(c=0;c<e.length;++c){d=e[c];if(typeof d=="string"){if(CKEDITOR.tools.indexOf(f,d)==-1)return false}else if(!CKEDITOR.tools.checkIfAnyArrayItemMatches(f,d))return false}}return j(b.styles,a.requiredStyles)&&j(b.attributes,a.requiredAttributes)}function j(a,b){if(!b)return true;for(var c=0,d;c<b.length;++c){d=b[c];if(typeof d=="string"){if(!(d in
+a))return false}else if(!CKEDITOR.tools.checkIfAnyObjectPropertyMatches(a,d))return false}return true}function g(a){if(!a)return{};for(var a=a.split(/\s*,\s*/).sort(),b={};a.length;)b[a.shift()]=v;return b}function m(a){for(var b,c,d,e,f={},p=1,a=I(a);b=a.match(x);){if(c=b[2]){d=y(c,"styles");e=y(c,"attrs");c=y(c,"classes")}else d=e=c=null;f["$"+p++]={elements:b[1],classes:c,styles:d,attributes:e};a=a.slice(b[0].length)}return f}function y(a,b){var c=a.match(E[b]);return c?I(c[1]):null}function s(a){var b=
+a.styleBackup=a.attributes.style,c=a.classBackup=a.attributes["class"];if(!a.styles)a.styles=CKEDITOR.tools.parseCssText(b||"",1);if(!a.classes)a.classes=c?c.split(/\s+/):[]}function w(a,b,d,e){var l=0,r;if(e.toHtml)b.name=b.name.replace($,"$1");if(e.doCallbacks&&a.elementCallbacks){a:for(var x=a.elementCallbacks,h=0,n=x.length,v;h<n;++h)if(v=x[h](b)){r=v;break a}if(r)return r}if(e.doTransform)if(r=a._.transformations[b.name]){s(b);for(x=0;x<r.length;++x)p(a,b,r[x]);t(b)}if(e.doFilter){a:{x=b.name;
+h=a._;a=h.allowedRules.elements[x];r=h.allowedRules.generic;x=h.disallowedRules.elements[x];h=h.disallowedRules.generic;n=e.skipRequired;v={valid:false,validAttributes:{},validClasses:{},validStyles:{},allAttributes:false,allClasses:false,allStyles:false,hadInvalidAttribute:false,hadInvalidClass:false,hadInvalidStyle:false};var q,z;if(!a&&!r)a=null;else{s(b);if(x){q=0;for(z=x.length;q<z;++q)if(c(x[q],b,v)===false){a=null;break a}}if(h){q=0;for(z=h.length;q<z;++q)c(h[q],b,v)}if(a){q=0;for(z=a.length;q<
+z;++q)f(a[q],b,v,n)}if(r){q=0;for(z=r.length;q<z;++q)f(r[q],b,v,n)}a=v}}if(!a){d.push(b);return F}if(!a.valid){d.push(b);return F}z=a.validAttributes;var E=a.validStyles;r=a.validClasses;var x=b.attributes,A=b.styles,h=b.classes,n=b.classBackup,o=b.styleBackup,g,B,C=[];v=[];var j=/^data-cke-/;q=false;delete x.style;delete x["class"];delete b.classBackup;delete b.styleBackup;if(!a.allAttributes)for(g in x)if(!z[g])if(j.test(g)){if(g!=(B=g.replace(/^data-cke-saved-/,""))&&!z[B]){delete x[g];q=true}}else{delete x[g];
+q=true}if(!a.allStyles||a.hadInvalidStyle){for(g in A)a.allStyles||E[g]?C.push(g+":"+A[g]):q=true;if(C.length)x.style=C.sort().join("; ")}else if(o)x.style=o;if(!a.allClasses||a.hadInvalidClass){for(g=0;g<h.length;++g)(a.allClasses||r[h[g]])&&v.push(h[g]);v.length&&(x["class"]=v.sort().join(" "));n&&v.length<n.split(/\s+/).length&&(q=true)}else n&&(x["class"]=n);q&&(l=F);if(!e.skipFinalValidation&&!i(b)){d.push(b);return F}}if(e.toHtml)b.name=b.name.replace(aa,"cke:$1");return l}function q(a){var b=
+[],c;for(c in a)c.indexOf("*")>-1&&b.push(c.replace(/\*/g,".*"));return b.length?RegExp("^(?:"+b.join("|")+")$"):null}function t(a){var b=a.attributes,c;delete b.style;delete b["class"];if(c=CKEDITOR.tools.writeCssText(a.styles,true))b.style=c;a.classes.length&&(b["class"]=a.classes.sort().join(" "))}function i(a){switch(a.name){case "a":if(!a.children.length&&!a.attributes.name)return false;break;case "img":if(!a.attributes.src)return false}return true}function A(a){if(!a)return false;if(a===true)return true;
+var b=q(a);return function(c){return c in a||b&&c.match(b)}}function u(){return new CKEDITOR.htmlParser.element("br")}function o(a){return a.type==CKEDITOR.NODE_ELEMENT&&(a.name=="br"||L.$block[a.name])}function l(a,b,c){var d=a.name;if(L.$empty[d]||!a.children.length)if(d=="hr"&&b=="br")a.replaceWith(u());else{a.parent&&c.push({check:"it",el:a.parent});a.remove()}else if(L.$block[d]||d=="tr")if(b=="br"){if(a.previous&&!o(a.previous)){b=u();b.insertBefore(a)}if(a.next&&!o(a.next)){b=u();b.insertAfter(a)}a.replaceWithChildren()}else{var d=
+a.children,e;b:{e=L[b];for(var f=0,p=d.length,i;f<p;++f){i=d[f];if(i.type==CKEDITOR.NODE_ELEMENT&&!e[i.name]){e=false;break b}}e=true}if(e){a.name=b;a.attributes={};c.push({check:"parent-down",el:a})}else{e=a.parent;for(var f=e.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT||e.name=="body",l,r,p=d.length;p>0;){i=d[--p];if(f&&(i.type==CKEDITOR.NODE_TEXT||i.type==CKEDITOR.NODE_ELEMENT&&L.$inline[i.name])){if(!l){l=new CKEDITOR.htmlParser.element(b);l.insertAfter(a);c.push({check:"parent-down",el:l})}l.add(i,
+0)}else{l=null;r=L[e.name]||L.span;i.insertAfter(a);e.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(i.type==CKEDITOR.NODE_ELEMENT&&!r[i.name])&&c.push({check:"el-up",el:i})}}a.remove()}}else if(d=="style")a.remove();else{a.parent&&c.push({check:"it",el:a.parent});a.replaceWithChildren()}}function p(a,b,c){var d,e;for(d=0;d<c.length;++d){e=c[d];if((!e.check||a.check(e.check,false))&&(!e.left||e.left(b))){e.right(b,ba);break}}}function r(a,b){var c=b.getDefinition(),d=c.attributes,e=c.styles,f,p,i,l;if(a.name!=
+c.element)return false;for(f in d)if(f=="class"){c=d[f].split(/\s+/);for(i=a.classes.join("|");l=c.pop();)if(i.indexOf(l)==-1)return false}else if(a.attributes[f]!=d[f])return false;for(p in e)if(a.styles[p]!=e[p])return false;return true}function n(a,b){var c,d;if(typeof a=="string")c=a;else if(a instanceof CKEDITOR.style)d=a;else{c=a[0];d=a[1]}return[{element:c,left:d,right:function(a,c){c.transform(a,b)}}]}function P(a){return function(b){return r(b,a)}}function C(a){return function(b,c){c[a](b)}}
+var L=CKEDITOR.dtd,F=1,K=CKEDITOR.tools.copy,I=CKEDITOR.tools.trim,v="cke-test",G=["","p","br","div"];CKEDITOR.FILTER_SKIP_TREE=2;CKEDITOR.filter=function(a){this.allowedContent=[];this.disallowedContent=[];this.elementCallbacks=null;this.disabled=false;this.editor=null;this.id=CKEDITOR.tools.getNextNumber();this._={allowedRules:{elements:{},generic:[]},disallowedRules:{elements:{},generic:[]},transformations:{},cachedTests:{}};CKEDITOR.filter.instances[this.id]=this;if(a instanceof CKEDITOR.editor){a=
+this.editor=a;this.customConfig=true;var b=a.config.allowedContent;if(b===true)this.disabled=true;else{if(!b)this.customConfig=false;this.allow(b,"config",1);this.allow(a.config.extraAllowedContent,"extra",1);this.allow(G[a.enterMode]+" "+G[a.shiftEnterMode],"default",1);this.disallow(a.config.disallowedContent)}}else{this.customConfig=false;this.allow(a,"default",1)}};CKEDITOR.filter.instances={};CKEDITOR.filter.prototype={allow:function(b,c,e){if(!d(this,b,e))return false;var f,p;if(typeof b=="string")b=
+m(b);else if(b instanceof CKEDITOR.style){if(b.toAllowedContentRules)return this.allow(b.toAllowedContentRules(this.editor),c,e);f=b.getDefinition();b={};e=f.attributes;b[f.element]=f={styles:f.styles,requiredStyles:f.styles&&CKEDITOR.tools.objectKeys(f.styles)};if(e){e=K(e);f.classes=e["class"]?e["class"].split(/\s+/):null;f.requiredClasses=f.classes;delete e["class"];f.attributes=e;f.requiredAttributes=e&&CKEDITOR.tools.objectKeys(e)}}else if(CKEDITOR.tools.isArray(b)){for(f=0;f<b.length;++f)p=
+this.allow(b[f],c,e);return p}a(this,b,c,this.allowedContent,this._.allowedRules);return true},applyTo:function(a,b,c,d){if(this.disabled)return false;var e=this,f=[],p=this.editor&&this.editor.config.protectedSource,r,x=false,h={doFilter:!c,doTransform:true,doCallbacks:true,toHtml:b};a.forEach(function(a){if(a.type==CKEDITOR.NODE_ELEMENT){if(a.attributes["data-cke-filter"]=="off")return false;if(!b||!(a.name=="span"&&~CKEDITOR.tools.objectKeys(a.attributes).join("|").indexOf("data-cke-"))){r=w(e,
+a,f,h);if(r&F)x=true;else if(r&2)return false}}else if(a.type==CKEDITOR.NODE_COMMENT&&a.value.match(/^\{cke_protected\}(?!\{C\})/)){var c;a:{var d=decodeURIComponent(a.value.replace(/^\{cke_protected\}/,""));c=[];var i,l,n;if(p)for(l=0;l<p.length;++l)if((n=d.match(p[l]))&&n[0].length==d.length){c=true;break a}d=CKEDITOR.htmlParser.fragment.fromHtml(d);d.children.length==1&&(i=d.children[0]).type==CKEDITOR.NODE_ELEMENT&&w(e,i,c,h);c=!c.length}c||f.push(a)}},null,true);f.length&&(x=true);for(var n,
+a=[],d=G[d||(this.editor?this.editor.enterMode:CKEDITOR.ENTER_P)],q;c=f.pop();)c.type==CKEDITOR.NODE_ELEMENT?l(c,d,a):c.remove();for(;n=a.pop();){c=n.el;if(c.parent){q=L[c.parent.name]||L.span;switch(n.check){case "it":L.$removeEmpty[c.name]&&!c.children.length?l(c,d,a):i(c)||l(c,d,a);break;case "el-up":c.parent.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT&&!q[c.name]&&l(c,d,a);break;case "parent-down":c.parent.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT&&!q[c.name]&&l(c.parent,d,a)}}}return x},checkFeature:function(a){if(this.disabled||
+!a)return true;a.toFeature&&(a=a.toFeature(this.editor));return!a.requiredContent||this.check(a.requiredContent)},disable:function(){this.disabled=true},disallow:function(b){if(!d(this,b,true))return false;typeof b=="string"&&(b=m(b));a(this,b,null,this.disallowedContent,this._.disallowedRules);return true},addContentForms:function(a){if(!this.disabled&&a){var b,c,d=[],e;for(b=0;b<a.length&&!e;++b){c=a[b];if((typeof c=="string"||c instanceof CKEDITOR.style)&&this.check(c))e=c}if(e){for(b=0;b<a.length;++b)d.push(n(a[b],
+e));this.addTransformations(d)}}},addElementCallback:function(a){if(!this.elementCallbacks)this.elementCallbacks=[];this.elementCallbacks.push(a)},addFeature:function(a){if(this.disabled||!a)return true;a.toFeature&&(a=a.toFeature(this.editor));this.allow(a.allowedContent,a.name);this.addTransformations(a.contentTransformations);this.addContentForms(a.contentForms);return a.requiredContent&&(this.customConfig||this.disallowedContent.length)?this.check(a.requiredContent):true},addTransformations:function(a){var b,
+c;if(!this.disabled&&a){var d=this._.transformations,e;for(e=0;e<a.length;++e){b=a[e];var f=void 0,p=void 0,i=void 0,l=void 0,r=void 0,x=void 0;c=[];for(p=0;p<b.length;++p){i=b[p];if(typeof i=="string"){i=i.split(/\s*:\s*/);l=i[0];r=null;x=i[1]}else{l=i.check;r=i.left;x=i.right}if(!f){f=i;f=f.element?f.element:l?l.match(/^([a-z0-9]+)/i)[0]:f.left.getDefinition().element}r instanceof CKEDITOR.style&&(r=P(r));c.push({check:l==f?null:l,left:r,right:typeof x=="string"?C(x):x})}b=f;d[b]||(d[b]=[]);d[b].push(c)}}},
+check:function(a,b,c){if(this.disabled)return true;if(CKEDITOR.tools.isArray(a)){for(var d=a.length;d--;)if(this.check(a[d],b,c))return true;return false}var e,f;if(typeof a=="string"){f=a+"<"+(b===false?"0":"1")+(c?"1":"0")+">";if(f in this._.cachedChecks)return this._.cachedChecks[f];d=m(a).$1;e=d.styles;var i=d.classes;d.name=d.elements;d.classes=i=i?i.split(/\s*,\s*/):[];d.styles=g(e);d.attributes=g(d.attributes);d.children=[];i.length&&(d.attributes["class"]=i.join(" "));if(e)d.attributes.style=
+CKEDITOR.tools.writeCssText(d.styles);e=d}else{d=a.getDefinition();e=d.styles;i=d.attributes||{};if(e){e=K(e);i.style=CKEDITOR.tools.writeCssText(e,true)}else e={};e={name:d.element,attributes:i,classes:i["class"]?i["class"].split(/\s+/):[],styles:e,children:[]}}var i=CKEDITOR.tools.clone(e),l=[],r;if(b!==false&&(r=this._.transformations[e.name])){for(d=0;d<r.length;++d)p(this,e,r[d]);t(e)}w(this,i,l,{doFilter:true,doTransform:b!==false,skipRequired:!c,skipFinalValidation:!c});b=l.length>0?false:
+CKEDITOR.tools.objectCompare(e.attributes,i.attributes,true)?true:false;typeof a=="string"&&(this._.cachedChecks[f]=b);return b},getAllowedEnterMode:function(){var a=["p","div","br"],b={p:CKEDITOR.ENTER_P,div:CKEDITOR.ENTER_DIV,br:CKEDITOR.ENTER_BR};return function(c,d){var e=a.slice(),f;if(this.check(G[c]))return c;for(d||(e=e.reverse());f=e.pop();)if(this.check(f))return b[f];return CKEDITOR.ENTER_BR}}(),destroy:function(){delete CKEDITOR.filter.instances[this.id];delete this._;delete this.allowedContent;
+delete this.disallowedContent}};var z={styles:1,attributes:1,classes:1},B={styles:"requiredStyles",attributes:"requiredAttributes",classes:"requiredClasses"},x=/^([a-z0-9\-*\s]+)((?:\s*\{[!\w\-,\s\*]+\}\s*|\s*\[[!\w\-,\s\*]+\]\s*|\s*\([!\w\-,\s\*]+\)\s*){0,3})(?:;\s*|$)/i,E={styles:/{([^}]+)}/,attrs:/\[([^\]]+)\]/,classes:/\(([^\)]+)\)/},$=/^cke:(object|embed|param)$/,aa=/^(object|embed|param)$/,ba=CKEDITOR.filter.transformationsTools={sizeToStyle:function(a){this.lengthToStyle(a,"width");this.lengthToStyle(a,
+"height")},sizeToAttribute:function(a){this.lengthToAttribute(a,"width");this.lengthToAttribute(a,"height")},lengthToStyle:function(a,b,c){c=c||b;if(!(c in a.styles)){var d=a.attributes[b];if(d){/^\d+$/.test(d)&&(d=d+"px");a.styles[c]=d}}delete a.attributes[b]},lengthToAttribute:function(a,b,c){c=c||b;if(!(c in a.attributes)){var d=a.styles[b],e=d&&d.match(/^(\d+)(?:\.\d*)?px$/);e?a.attributes[c]=e[1]:d==v&&(a.attributes[c]=v)}delete a.styles[b]},alignmentToStyle:function(a){if(!("float"in a.styles)){var b=
+a.attributes.align;if(b=="left"||b=="right")a.styles["float"]=b}delete a.attributes.align},alignmentToAttribute:function(a){if(!("align"in a.attributes)){var b=a.styles["float"];if(b=="left"||b=="right")a.attributes.align=b}delete a.styles["float"]},matchesStyle:r,transform:function(a,b){if(typeof b=="string")a.name=b;else{var c=b.getDefinition(),d=c.styles,e=c.attributes,f,i,p,l;a.name=c.element;for(f in e)if(f=="class"){c=a.classes.join("|");for(p=e[f].split(/\s+/);l=p.pop();)c.indexOf(l)==-1&&
+a.classes.push(l)}else a.attributes[f]=e[f];for(i in d)a.styles[i]=d[i]}}}})();
+(function(){CKEDITOR.focusManager=function(a){if(a.focusManager)return a.focusManager;this.hasFocus=false;this.currentActive=null;this._={editor:a};return this};CKEDITOR.focusManager._={blurDelay:200};CKEDITOR.focusManager.prototype={focus:function(a){this._.timer&&clearTimeout(this._.timer);if(a)this.currentActive=a;if(!this.hasFocus&&!this._.locked){(a=CKEDITOR.currentInstance)&&a.focusManager.blur(1);this.hasFocus=true;(a=this._.editor.container)&&a.addClass("cke_focus");this._.editor.fire("focus")}},
+lock:function(){this._.locked=1},unlock:function(){delete this._.locked},blur:function(a){function f(){if(this.hasFocus){this.hasFocus=false;var a=this._.editor.container;a&&a.removeClass("cke_focus");this._.editor.fire("blur")}}if(!this._.locked){this._.timer&&clearTimeout(this._.timer);var b=CKEDITOR.focusManager._.blurDelay;a||!b?f.call(this):this._.timer=CKEDITOR.tools.setTimeout(function(){delete this._.timer;f.call(this)},b,this)}},add:function(a,f){var b=a.getCustomData("focusmanager");if(!b||
+b!=this){b&&b.remove(a);var b="focus",c="blur";if(f)if(CKEDITOR.env.ie){b="focusin";c="focusout"}else CKEDITOR.event.useCapture=1;var e={blur:function(){a.equals(this.currentActive)&&this.blur()},focus:function(){this.focus(a)}};a.on(b,e.focus,this);a.on(c,e.blur,this);if(f)CKEDITOR.event.useCapture=0;a.setCustomData("focusmanager",this);a.setCustomData("focusmanager_handlers",e)}},remove:function(a){a.removeCustomData("focusmanager");var f=a.removeCustomData("focusmanager_handlers");a.removeListener("blur",
+f.blur);a.removeListener("focus",f.focus)}}})();CKEDITOR.keystrokeHandler=function(a){if(a.keystrokeHandler)return a.keystrokeHandler;this.keystrokes={};this.blockedKeystrokes={};this._={editor:a};return this};
+(function(){var a,f=function(b){var b=b.data,e=b.getKeystroke(),d=this.keystrokes[e],f=this._.editor;a=f.fire("key",{keyCode:e,domEvent:b})===false;if(!a){d&&(a=f.execCommand(d,{from:"keystrokeHandler"})!==false);a||(a=!!this.blockedKeystrokes[e])}a&&b.preventDefault(true);return!a},b=function(b){if(a){a=false;b.data.preventDefault(true)}};CKEDITOR.keystrokeHandler.prototype={attach:function(a){a.on("keydown",f,this);if(CKEDITOR.env.gecko&&CKEDITOR.env.mac)a.on("keypress",b,this)}}})();
+(function(){CKEDITOR.lang={languages:{af:1,ar:1,bg:1,bn:1,bs:1,ca:1,cs:1,cy:1,da:1,de:1,el:1,"en-au":1,"en-ca":1,"en-gb":1,en:1,eo:1,es:1,et:1,eu:1,fa:1,fi:1,fo:1,"fr-ca":1,fr:1,gl:1,gu:1,he:1,hi:1,hr:1,hu:1,id:1,is:1,it:1,ja:1,ka:1,km:1,ko:1,ku:1,lt:1,lv:1,mk:1,mn:1,ms:1,nb:1,nl:1,no:1,pl:1,"pt-br":1,pt:1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,"sr-latn":1,sr:1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,"zh-cn":1,zh:1},rtl:{ar:1,fa:1,he:1,ku:1,ug:1},load:function(a,f,b){if(!a||!CKEDITOR.lang.languages[a])a=this.detect(f,
+a);var c=this,f=function(){c[a].dir=c.rtl[a]?"rtl":"ltr";b(a,c[a])};this[a]?f():CKEDITOR.scriptLoader.load(CKEDITOR.getUrl("lang/"+a+".js"),f,this)},detect:function(a,f){var b=this.languages,f=f||navigator.userLanguage||navigator.language||a,c=f.toLowerCase().match(/([a-z]+)(?:-([a-z]+))?/),e=c[1],c=c[2];b[e+"-"+c]?e=e+"-"+c:b[e]||(e=null);CKEDITOR.lang.detect=e?function(){return e}:function(a){return a};return e||a}}})();
+CKEDITOR.scriptLoader=function(){var a={},f={};return{load:function(b,c,e,d){var h=typeof b=="string";h&&(b=[b]);e||(e=CKEDITOR);var k=b.length,j=[],g=[],m=function(a){c&&(h?c.call(e,a):c.call(e,j,g))};if(k===0)m(true);else{var y=function(a,b){(b?j:g).push(a);if(--k<=0){d&&CKEDITOR.document.getDocumentElement().removeStyle("cursor");m(b)}},s=function(b,c){a[b]=1;var d=f[b];delete f[b];for(var e=0;e<d.length;e++)d[e](b,c)},w=function(b){if(a[b])y(b,true);else{var d=f[b]||(f[b]=[]);d.push(y);if(!(d.length>
+1)){var e=new CKEDITOR.dom.element("script");e.setAttributes({type:"text/javascript",src:b});if(c)if(CKEDITOR.env.ie&&CKEDITOR.env.version<11)e.$.onreadystatechange=function(){if(e.$.readyState=="loaded"||e.$.readyState=="complete"){e.$.onreadystatechange=null;s(b,true)}};else{e.$.onload=function(){setTimeout(function(){s(b,true)},0)};e.$.onerror=function(){s(b,false)}}e.appendTo(CKEDITOR.document.getHead())}}};d&&CKEDITOR.document.getDocumentElement().setStyle("cursor","wait");for(var q=0;q<k;q++)w(b[q])}},
+queue:function(){function a(){var b;(b=c[0])&&this.load(b.scriptUrl,b.callback,CKEDITOR,0)}var c=[];return function(e,d){var f=this;c.push({scriptUrl:e,callback:function(){d&&d.apply(this,arguments);c.shift();a.call(f)}});c.length==1&&a.call(this)}}()}}();CKEDITOR.resourceManager=function(a,f){this.basePath=a;this.fileName=f;this.registered={};this.loaded={};this.externals={};this._={waitingList:{}}};
+CKEDITOR.resourceManager.prototype={add:function(a,f){if(this.registered[a])throw'[CKEDITOR.resourceManager.add] The resource name "'+a+'" is already registered.';var b=this.registered[a]=f||{};b.name=a;b.path=this.getPath(a);CKEDITOR.fire(a+CKEDITOR.tools.capitalize(this.fileName)+"Ready",b);return this.get(a)},get:function(a){return this.registered[a]||null},getPath:function(a){var f=this.externals[a];return CKEDITOR.getUrl(f&&f.dir||this.basePath+a+"/")},getFilePath:function(a){var f=this.externals[a];
+return CKEDITOR.getUrl(this.getPath(a)+(f?f.file:this.fileName+".js"))},addExternal:function(a,f,b){for(var a=a.split(","),c=0;c<a.length;c++){var e=a[c];b||(f=f.replace(/[^\/]+$/,function(a){b=a;return""}));this.externals[e]={dir:f,file:b||this.fileName+".js"}}},load:function(a,f,b){CKEDITOR.tools.isArray(a)||(a=a?[a]:[]);for(var c=this.loaded,e=this.registered,d=[],h={},k={},j=0;j<a.length;j++){var g=a[j];if(g)if(!c[g]&&!e[g]){var m=this.getFilePath(g);d.push(m);m in h||(h[m]=[]);h[m].push(g)}else k[g]=
+this.get(g)}CKEDITOR.scriptLoader.load(d,function(a,d){if(d.length)throw'[CKEDITOR.resourceManager.load] Resource name "'+h[d[0]].join(",")+'" was not found at "'+d[0]+'".';for(var e=0;e<a.length;e++)for(var q=h[a[e]],g=0;g<q.length;g++){var i=q[g];k[i]=this.get(i);c[i]=1}f.call(b,k)},this)}};CKEDITOR.plugins=new CKEDITOR.resourceManager("plugins/","plugin");
+CKEDITOR.plugins.load=CKEDITOR.tools.override(CKEDITOR.plugins.load,function(a){var f={};return function(b,c,e){var d={},h=function(b){a.call(this,b,function(a){CKEDITOR.tools.extend(d,a);var b=[],m;for(m in a){var k=a[m],s=k&&k.requires;if(!f[m]){if(k.icons)for(var w=k.icons.split(","),q=w.length;q--;)CKEDITOR.skin.addIcon(w[q],k.path+"icons/"+(CKEDITOR.env.hidpi&&k.hidpi?"hidpi/":"")+w[q]+".png");f[m]=1}if(s){s.split&&(s=s.split(","));for(k=0;k<s.length;k++)d[s[k]]||b.push(s[k])}}if(b.length)h.call(this,
+b);else{for(m in d){k=d[m];if(k.onLoad&&!k.onLoad._called){k.onLoad()===false&&delete d[m];k.onLoad._called=1}}c&&c.call(e||window,d)}},this)};h.call(this,b)}});CKEDITOR.plugins.setLang=function(a,f,b){var c=this.get(a),a=c.langEntries||(c.langEntries={}),c=c.lang||(c.lang=[]);c.split&&(c=c.split(","));CKEDITOR.tools.indexOf(c,f)==-1&&c.push(f);a[f]=b};CKEDITOR.ui=function(a){if(a.ui)return a.ui;this.items={};this.instances={};this.editor=a;this._={handlers:{}};return this};
+CKEDITOR.ui.prototype={add:function(a,f,b){b.name=a.toLowerCase();var c=this.items[a]={type:f,command:b.command||null,args:Array.prototype.slice.call(arguments,2)};CKEDITOR.tools.extend(c,b)},get:function(a){return this.instances[a]},create:function(a){var f=this.items[a],b=f&&this._.handlers[f.type],c=f&&f.command&&this.editor.getCommand(f.command),b=b&&b.create.apply(this,f.args);this.instances[a]=b;c&&c.uiItems.push(b);if(b&&!b.type)b.type=f.type;return b},addHandler:function(a,f){this._.handlers[a]=
+f},space:function(a){return CKEDITOR.document.getById(this.spaceId(a))},spaceId:function(a){return this.editor.id+"_"+a}};CKEDITOR.event.implementOn(CKEDITOR.ui);
+(function(){function a(a,c,d){CKEDITOR.event.call(this);a=a&&CKEDITOR.tools.clone(a);if(c!==void 0){if(c instanceof CKEDITOR.dom.element){if(!d)throw Error("One of the element modes must be specified.");}else throw Error("Expect element of type CKEDITOR.dom.element.");if(CKEDITOR.env.ie&&CKEDITOR.env.quirks&&d==CKEDITOR.ELEMENT_MODE_INLINE)throw Error("Inline element mode is not supported on IE quirks.");if(!(d==CKEDITOR.ELEMENT_MODE_INLINE?c.is(CKEDITOR.dtd.$editable)||c.is("textarea"):d==CKEDITOR.ELEMENT_MODE_REPLACE?
+!c.is(CKEDITOR.dtd.$nonBodyContent):1))throw Error('The specified element mode is not supported on element: "'+c.getName()+'".');this.element=c;this.elementMode=d;this.name=this.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO&&(c.getId()||c.getNameAtt())}else this.elementMode=CKEDITOR.ELEMENT_MODE_NONE;this._={};this.commands={};this.templates={};this.name=this.name||f();this.id=CKEDITOR.tools.getNextId();this.status="unloaded";this.config=CKEDITOR.tools.prototypedCopy(CKEDITOR.config);this.ui=new CKEDITOR.ui(this);
+this.focusManager=new CKEDITOR.focusManager(this);this.keystrokeHandler=new CKEDITOR.keystrokeHandler(this);this.on("readOnly",b);this.on("selectionChange",function(a){e(this,a.data.path)});this.on("activeFilterChange",function(){e(this,this.elementPath(),true)});this.on("mode",b);this.on("instanceReady",function(){this.config.startupFocus&&this.focus()});CKEDITOR.fire("instanceCreated",null,this);CKEDITOR.add(this);CKEDITOR.tools.setTimeout(function(){h(this,a)},0,this)}function f(){do var a="editor"+
+++s;while(CKEDITOR.instances[a]);return a}function b(){var a=this.commands,b;for(b in a)c(this,a[b])}function c(a,b){b[b.startDisabled?"disable":a.readOnly&&!b.readOnly?"disable":b.modes[a.mode]?"enable":"disable"]()}function e(a,b,c){if(b){var d,e,f=a.commands;for(e in f){d=f[e];(c||d.contextSensitive)&&d.refresh(a,b)}}}function d(a){var b=a.config.customConfig;if(!b)return false;var b=CKEDITOR.getUrl(b),c=w[b]||(w[b]={});if(c.fn){c.fn.call(a,a.config);(CKEDITOR.getUrl(a.config.customConfig)==b||
+!d(a))&&a.fireOnce("customConfigLoaded")}else CKEDITOR.scriptLoader.queue(b,function(){c.fn=CKEDITOR.editorConfig?CKEDITOR.editorConfig:function(){};d(a)});return true}function h(a,b){a.on("customConfigLoaded",function(){if(b){if(b.on)for(var c in b.on)a.on(c,b.on[c]);CKEDITOR.tools.extend(a.config,b,true);delete a.config.on}c=a.config;a.readOnly=!(!c.readOnly&&!(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?a.element.is("textarea")?a.element.hasAttribute("disabled"):a.element.isReadOnly():a.elementMode==
+CKEDITOR.ELEMENT_MODE_REPLACE&&a.element.hasAttribute("disabled")));a.blockless=a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?!(a.element.is("textarea")||CKEDITOR.dtd[a.element.getName()].p):false;a.tabIndex=c.tabIndex||a.element&&a.element.getAttribute("tabindex")||0;a.activeEnterMode=a.enterMode=a.blockless?CKEDITOR.ENTER_BR:c.enterMode;a.activeShiftEnterMode=a.shiftEnterMode=a.blockless?CKEDITOR.ENTER_BR:c.shiftEnterMode;if(c.skin)CKEDITOR.skinName=c.skin;a.fireOnce("configLoaded");a.dataProcessor=
+new CKEDITOR.htmlDataProcessor(a);a.filter=a.activeFilter=new CKEDITOR.filter(a);k(a)});if(b&&b.customConfig!=null)a.config.customConfig=b.customConfig;d(a)||a.fireOnce("customConfigLoaded")}function k(a){CKEDITOR.skin.loadPart("editor",function(){j(a)})}function j(a){CKEDITOR.lang.load(a.config.language,a.config.defaultLanguage,function(b,c){var d=a.config.title;a.langCode=b;a.lang=CKEDITOR.tools.prototypedCopy(c);a.title=typeof d=="string"||d===false?d:[a.lang.editor,a.name].join(", ");if(!a.config.contentsLangDirection)a.config.contentsLangDirection=
+a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?a.element.getDirection(1):a.lang.dir;a.fire("langLoaded");g(a)})}function g(a){a.getStylesSet(function(b){a.once("loaded",function(){a.fire("stylesSet",{styles:b})},null,null,1);m(a)})}function m(a){var b=a.config,c=b.plugins,d=b.extraPlugins,e=b.removePlugins;if(d)var f=RegExp("(?:^|,)(?:"+d.replace(/\s*,\s*/g,"|")+")(?=,|$)","g"),c=c.replace(f,""),c=c+(","+d);if(e)var l=RegExp("(?:^|,)(?:"+e.replace(/\s*,\s*/g,"|")+")(?=,|$)","g"),c=c.replace(l,"");CKEDITOR.env.air&&
+(c=c+",adobeair");CKEDITOR.plugins.load(c.split(","),function(c){var d=[],e=[],f=[];a.plugins=c;for(var i in c){var h=c[i],g=h.lang,o=null,A=h.requires,v;CKEDITOR.tools.isArray(A)&&(A=A.join(","));if(A&&(v=A.match(l)))for(;A=v.pop();)CKEDITOR.tools.setTimeout(function(a,b){throw Error('Plugin "'+a.replace(",","")+'" cannot be removed from the plugins list, because it\'s required by "'+b+'" plugin.');},0,null,[A,i]);if(g&&!a.lang[i]){g.split&&(g=g.split(","));if(CKEDITOR.tools.indexOf(g,a.langCode)>=
+0)o=a.langCode;else{o=a.langCode.replace(/-.*/,"");o=o!=a.langCode&&CKEDITOR.tools.indexOf(g,o)>=0?o:CKEDITOR.tools.indexOf(g,"en")>=0?"en":g[0]}if(!h.langEntries||!h.langEntries[o])f.push(CKEDITOR.getUrl(h.path+"lang/"+o+".js"));else{a.lang[i]=h.langEntries[o];o=null}}e.push(o);d.push(h)}CKEDITOR.scriptLoader.load(f,function(){for(var c=["beforeInit","init","afterInit"],f=0;f<c.length;f++)for(var p=0;p<d.length;p++){var i=d[p];f===0&&(e[p]&&i.lang&&i.langEntries)&&(a.lang[i.name]=i.langEntries[e[p]]);
+if(i[c[f]])i[c[f]](a)}a.fireOnce("pluginsLoaded");b.keystrokes&&a.setKeystroke(a.config.keystrokes);for(p=0;p<a.config.blockedKeystrokes.length;p++)a.keystrokeHandler.blockedKeystrokes[a.config.blockedKeystrokes[p]]=1;a.status="loaded";a.fireOnce("loaded");CKEDITOR.fire("instanceLoaded",null,a)})})}function y(){var a=this.element;if(a&&this.elementMode!=CKEDITOR.ELEMENT_MODE_APPENDTO){var b=this.getData();this.config.htmlEncodeOutput&&(b=CKEDITOR.tools.htmlEncode(b));a.is("textarea")?a.setValue(b):
+a.setHtml(b);return true}return false}a.prototype=CKEDITOR.editor.prototype;CKEDITOR.editor=a;var s=0,w={};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{addCommand:function(a,b){b.name=a.toLowerCase();var d=new CKEDITOR.command(this,b);this.mode&&c(this,d);return this.commands[a]=d},_attachToForm:function(){function a(d){b.updateElement();b._.required&&(!c.getValue()&&b.fire("required")===false)&&d.data.preventDefault()}var b=this,c=b.element,d=new CKEDITOR.dom.element(c.$.form);if(c.is("textarea")&&
+d){d.on("submit",a);if(d.$.submit&&d.$.submit.call&&d.$.submit.apply)d.$.submit=CKEDITOR.tools.override(d.$.submit,function(b){return function(){a();b.apply?b.apply(this):b()}});b.on("destroy",function(){d.removeListener("submit",a)})}},destroy:function(a){this.fire("beforeDestroy");!a&&y.call(this);this.editable(null);this.filter.destroy();delete this.filter;delete this.activeFilter;this.status="destroyed";this.fire("destroy");this.removeAllListeners();CKEDITOR.remove(this);CKEDITOR.fire("instanceDestroyed",
+null,this)},elementPath:function(a){if(!a){a=this.getSelection();if(!a)return null;a=a.getStartElement()}return a?new CKEDITOR.dom.elementPath(a,this.editable()):null},createRange:function(){var a=this.editable();return a?new CKEDITOR.dom.range(a):null},execCommand:function(a,b){var c=this.getCommand(a),d={name:a,commandData:b,command:c};if(c&&c.state!=CKEDITOR.TRISTATE_DISABLED&&this.fire("beforeCommandExec",d)!==false){d.returnValue=c.exec(d.commandData);if(!c.async&&this.fire("afterCommandExec",
+d)!==false)return d.returnValue}return false},getCommand:function(a){return this.commands[a]},getData:function(a){!a&&this.fire("beforeGetData");var b=this._.data;if(typeof b!="string")b=(b=this.element)&&this.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE?b.is("textarea")?b.getValue():b.getHtml():"";b={dataValue:b};!a&&this.fire("getData",b);return b.dataValue},getSnapshot:function(){var a=this.fire("getSnapshot");if(typeof a!="string"){var b=this.element;b&&this.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE&&
+(a=b.is("textarea")?b.getValue():b.getHtml())}return a},loadSnapshot:function(a){this.fire("loadSnapshot",a)},setData:function(a,b,c){var d=true,e=b;if(b&&typeof b=="object"){c=b.internal;e=b.callback;d=!b.noSnapshot}!c&&d&&this.fire("saveSnapshot");if(e||!c)this.once("dataReady",function(a){!c&&d&&this.fire("saveSnapshot");e&&e.call(a.editor)});a={dataValue:a};!c&&this.fire("setData",a);this._.data=a.dataValue;!c&&this.fire("afterSetData",a)},setReadOnly:function(a){a=a==null||a;if(this.readOnly!=
+a){this.readOnly=a;this.keystrokeHandler.blockedKeystrokes[8]=+a;this.editable().setReadOnly(a);this.fire("readOnly")}},insertHtml:function(a,b){this.fire("insertHtml",{dataValue:a,mode:b})},insertText:function(a){this.fire("insertText",a)},insertElement:function(a){this.fire("insertElement",a)},focus:function(){this.fire("beforeFocus")},checkDirty:function(){return this.status=="ready"&&this._.previousValue!==this.getSnapshot()},resetDirty:function(){this._.previousValue=this.getSnapshot()},updateElement:function(){return y.call(this)},
+setKeystroke:function(){for(var a=this.keystrokeHandler.keystrokes,b=CKEDITOR.tools.isArray(arguments[0])?arguments[0]:[[].slice.call(arguments,0)],c,d,e=b.length;e--;){c=b[e];d=0;if(CKEDITOR.tools.isArray(c)){d=c[1];c=c[0]}d?a[c]=d:delete a[c]}},addFeature:function(a){return this.filter.addFeature(a)},setActiveFilter:function(a){if(!a)a=this.filter;if(this.activeFilter!==a){this.activeFilter=a;this.fire("activeFilterChange");a===this.filter?this.setActiveEnterMode(null,null):this.setActiveEnterMode(a.getAllowedEnterMode(this.enterMode),
+a.getAllowedEnterMode(this.shiftEnterMode,true))}},setActiveEnterMode:function(a,b){a=a?this.blockless?CKEDITOR.ENTER_BR:a:this.enterMode;b=b?this.blockless?CKEDITOR.ENTER_BR:b:this.shiftEnterMode;if(this.activeEnterMode!=a||this.activeShiftEnterMode!=b){this.activeEnterMode=a;this.activeShiftEnterMode=b;this.fire("activeEnterModeChange")}}})})();CKEDITOR.ELEMENT_MODE_NONE=0;CKEDITOR.ELEMENT_MODE_REPLACE=1;CKEDITOR.ELEMENT_MODE_APPENDTO=2;CKEDITOR.ELEMENT_MODE_INLINE=3;
+CKEDITOR.htmlParser=function(){this._={htmlPartsRegex:/<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)--\>)|(?:([^\/\s>]+)((?:\s+[\w\-:.]+(?:\s*=\s*?(?:(?:"[^"]*")|(?:'[^']*')|[^\s"'\/>]+))?)*)[\S\s]*?(\/?)>))/g}};
+(function(){var a=/([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g,f={checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(b){for(var c,e,d=0,h;c=this._.htmlPartsRegex.exec(b);){e=c.index;if(e>d){d=b.substring(d,e);if(h)h.push(d);else this.onText(d)}d=
+this._.htmlPartsRegex.lastIndex;if(e=c[1]){e=e.toLowerCase();if(h&&CKEDITOR.dtd.$cdata[e]){this.onCDATA(h.join(""));h=null}if(!h){this.onTagClose(e);continue}}if(h)h.push(c[0]);else if(e=c[3]){e=e.toLowerCase();if(!/="/.test(e)){var k={},j,g=c[4];c=!!c[5];if(g)for(;j=a.exec(g);){var m=j[1].toLowerCase();j=j[2]||j[3]||j[4]||"";k[m]=!j&&f[m]?m:CKEDITOR.tools.htmlDecodeAttr(j)}this.onTagOpen(e,k,c);!h&&CKEDITOR.dtd.$cdata[e]&&(h=[])}}else if(e=c[2])this.onComment(e)}if(b.length>d)this.onText(b.substring(d,
+b.length))}}})();
+CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._={output:[]}},proto:{openTag:function(a){this._.output.push("<",a)},openTagClose:function(a,f){f?this._.output.push(" />"):this._.output.push(">")},attribute:function(a,f){typeof f=="string"&&(f=CKEDITOR.tools.htmlEncodeAttr(f));this._.output.push(" ",a,'="',f,'"')},closeTag:function(a){this._.output.push("</",a,">")},text:function(a){this._.output.push(a)},comment:function(a){this._.output.push("<\!--",a,"--\>")},write:function(a){this._.output.push(a)},
+reset:function(){this._.output=[];this._.indent=false},getHtml:function(a){var f=this._.output.join("");a&&this.reset();return f}}});"use strict";
+(function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var a=this.parent.children,f=CKEDITOR.tools.indexOf(a,this),b=this.previous,c=this.next;b&&(b.next=c);c&&(c.previous=b);a.splice(f,1);this.parent=null},replaceWith:function(a){var f=this.parent.children,b=CKEDITOR.tools.indexOf(f,this),c=a.previous=this.previous,e=a.next=this.next;c&&(c.next=a);e&&(e.previous=a);f[b]=a;a.parent=this.parent;this.parent=null},insertAfter:function(a){var f=a.parent.children,
+b=CKEDITOR.tools.indexOf(f,a),c=a.next;f.splice(b+1,0,this);this.next=a.next;this.previous=a;a.next=this;c&&(c.previous=this);this.parent=a.parent},insertBefore:function(a){var f=a.parent.children,b=CKEDITOR.tools.indexOf(f,a);f.splice(b,0,this);this.next=a;(this.previous=a.previous)&&(a.previous.next=this);a.previous=this;this.parent=a.parent},getAscendant:function(a){var f=typeof a=="function"?a:typeof a=="string"?function(b){return b.name==a}:function(b){return b.name in a},b=this.parent;for(;b&&
+b.type==CKEDITOR.NODE_ELEMENT;){if(f(b))return b;b=b.parent}return null},wrapWith:function(a){this.replaceWith(a);a.add(this);return a},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(a){return a||{}}}})();"use strict";CKEDITOR.htmlParser.comment=function(a){this.value=a;this._={isBlockLike:false}};
+CKEDITOR.htmlParser.comment.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(a,f){var b=this.value;if(!(b=a.onComment(f,b,this))){this.remove();return false}if(typeof b!="string"){this.replaceWith(b);return false}this.value=b;return true},writeHtml:function(a,f){f&&this.filter(f);a.comment(this.value)}});"use strict";
+(function(){CKEDITOR.htmlParser.text=function(a){this.value=a;this._={isBlockLike:false}};CKEDITOR.htmlParser.text.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(a,f){if(!(this.value=a.onText(f,this.value,this))){this.remove();return false}},writeHtml:function(a,f){f&&this.filter(f);a.text(this.value)}})})();"use strict";
+(function(){CKEDITOR.htmlParser.cdata=function(a){this.value=a};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(a){a.write(this.value)}})})();"use strict";CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null;this._={isBlockLike:true,hasInlineStarted:false}};
+(function(){function a(a){return a.attributes["data-cke-survive"]?false:a.name=="a"&&a.attributes.href||CKEDITOR.dtd.$removeEmpty[a.name]}var f=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),b={ol:1,ul:1},c=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1}),e={ul:"li",ol:"li",dl:"dd",table:"tbody",tbody:"tr",thead:"tr",tfoot:"tr",tr:"td"};CKEDITOR.htmlParser.fragment.fromHtml=
+function(d,h,k){function j(a){var b;if(i.length>0)for(var c=0;c<i.length;c++){var d=i[c],e=d.name,f=CKEDITOR.dtd[e],l=u.name&&CKEDITOR.dtd[u.name];if((!l||l[e])&&(!a||!f||f[a]||!CKEDITOR.dtd[a])){if(!b){g();b=1}d=d.clone();d.parent=u;u=d;i.splice(c,1);c--}else if(e==u.name){y(u,u.parent,1);c--}}}function g(){for(;A.length;)y(A.shift(),u)}function m(a){if(a._.isBlockLike&&a.name!="pre"&&a.name!="textarea"){var b=a.children.length,c=a.children[b-1],d;if(c&&c.type==CKEDITOR.NODE_TEXT)(d=CKEDITOR.tools.rtrim(c.value))?
+c.value=d:a.children.length=b-1}}function y(b,c,d){var c=c||u||t,e=u;if(b.previous===void 0){if(s(c,b)){u=c;q.onTagOpen(k,{});b.returnPoint=c=u}m(b);(!a(b)||b.children.length)&&c.add(b);b.name=="pre"&&(l=false);b.name=="textarea"&&(o=false)}if(b.returnPoint){u=b.returnPoint;delete b.returnPoint}else u=d?c:e}function s(a,b){if((a==t||a.name=="body")&&k&&(!a.name||CKEDITOR.dtd[a.name][k])){var c,d;return(c=b.attributes&&(d=b.attributes["data-cke-real-element-type"])?d:b.name)&&c in CKEDITOR.dtd.$inline&&
+!(c in CKEDITOR.dtd.head)&&!b.isOrphan||b.type==CKEDITOR.NODE_TEXT}}function w(a,b){return a in CKEDITOR.dtd.$listItem||a in CKEDITOR.dtd.$tableContent?a==b||a=="dt"&&b=="dd"||a=="dd"&&b=="dt":false}var q=new CKEDITOR.htmlParser,t=h instanceof CKEDITOR.htmlParser.element?h:typeof h=="string"?new CKEDITOR.htmlParser.element(h):new CKEDITOR.htmlParser.fragment,i=[],A=[],u=t,o=t.name=="textarea",l=t.name=="pre";q.onTagOpen=function(d,e,h,m){e=new CKEDITOR.htmlParser.element(d,e);if(e.isUnknown&&h)e.isEmpty=
+true;e.isOptionalClose=m;if(a(e))i.push(e);else{if(d=="pre")l=true;else{if(d=="br"&&l){u.add(new CKEDITOR.htmlParser.text("\n"));return}d=="textarea"&&(o=true)}if(d=="br")A.push(e);else{for(;;){m=(h=u.name)?CKEDITOR.dtd[h]||(u._.isBlockLike?CKEDITOR.dtd.div:CKEDITOR.dtd.span):c;if(!e.isUnknown&&!u.isUnknown&&!m[d])if(u.isOptionalClose)q.onTagClose(h);else if(d in b&&h in b){h=u.children;(h=h[h.length-1])&&h.name=="li"||y(h=new CKEDITOR.htmlParser.element("li"),u);!e.returnPoint&&(e.returnPoint=u);
+u=h}else if(d in CKEDITOR.dtd.$listItem&&!w(d,h))q.onTagOpen(d=="li"?"ul":"dl",{},0,1);else if(h in f&&!w(d,h)){!e.returnPoint&&(e.returnPoint=u);u=u.parent}else{h in CKEDITOR.dtd.$inline&&i.unshift(u);if(u.parent)y(u,u.parent,1);else{e.isOrphan=1;break}}else break}j(d);g();e.parent=u;e.isEmpty?y(e):u=e}}};q.onTagClose=function(a){for(var b=i.length-1;b>=0;b--)if(a==i[b].name){i.splice(b,1);return}for(var c=[],d=[],e=u;e!=t&&e.name!=a;){e._.isBlockLike||d.unshift(e);c.push(e);e=e.returnPoint||e.parent}if(e!=
+t){for(b=0;b<c.length;b++){var f=c[b];y(f,f.parent)}u=e;e._.isBlockLike&&g();y(e,e.parent);if(e==u)u=u.parent;i=i.concat(d)}a=="body"&&(k=false)};q.onText=function(a){if((!u._.hasInlineStarted||A.length)&&!l&&!o){a=CKEDITOR.tools.ltrim(a);if(a.length===0)return}var b=u.name,d=b?CKEDITOR.dtd[b]||(u._.isBlockLike?CKEDITOR.dtd.div:CKEDITOR.dtd.span):c;if(!o&&!d["#"]&&b in f){q.onTagOpen(e[b]||"");q.onText(a)}else{g();j();!l&&!o&&(a=a.replace(/[\t\r\n ]{2,}|[\t\r\n]/g," "));a=new CKEDITOR.htmlParser.text(a);
+if(s(u,a))this.onTagOpen(k,{},0,1);u.add(a)}};q.onCDATA=function(a){u.add(new CKEDITOR.htmlParser.cdata(a))};q.onComment=function(a){g();j();u.add(new CKEDITOR.htmlParser.comment(a))};q.parse(d);for(g();u!=t;)y(u,u.parent,1);m(t);return t};CKEDITOR.htmlParser.fragment.prototype={type:CKEDITOR.NODE_DOCUMENT_FRAGMENT,add:function(a,b){isNaN(b)&&(b=this.children.length);var c=b>0?this.children[b-1]:null;if(c){if(a._.isBlockLike&&c.type==CKEDITOR.NODE_TEXT){c.value=CKEDITOR.tools.rtrim(c.value);if(c.value.length===
+0){this.children.pop();this.add(a);return}}c.next=a}a.previous=c;a.parent=this;this.children.splice(b,0,a);if(!this._.hasInlineStarted)this._.hasInlineStarted=a.type==CKEDITOR.NODE_TEXT||a.type==CKEDITOR.NODE_ELEMENT&&!a._.isBlockLike},filter:function(a,b){b=this.getFilterContext(b);a.onRoot(b,this);this.filterChildren(a,false,b)},filterChildren:function(a,b,c){if(this.childrenFilteredBy!=a.id){c=this.getFilterContext(c);if(b&&!this.parent)a.onRoot(c,this);this.childrenFilteredBy=a.id;for(b=0;b<this.children.length;b++)this.children[b].filter(a,
+c)===false&&b--}},writeHtml:function(a,b){b&&this.filter(b);this.writeChildrenHtml(a)},writeChildrenHtml:function(a,b,c){var e=this.getFilterContext();if(c&&!this.parent&&b)b.onRoot(e,this);b&&this.filterChildren(b,false,e);b=0;c=this.children;for(e=c.length;b<e;b++)c[b].writeHtml(a)},forEach:function(a,b,c){if(!c&&(!b||this.type==b))var e=a(this);if(e!==false)for(var c=this.children,f=0;f<c.length;f++){e=c[f];e.type==CKEDITOR.NODE_ELEMENT?e.forEach(a,b):(!b||e.type==b)&&a(e)}},getFilterContext:function(a){return a||
+{}}}})();"use strict";
+(function(){function a(){this.rules=[]}function f(b,c,e,d){var f,k;for(f in c){(k=b[f])||(k=b[f]=new a);k.add(c[f],e,d)}}CKEDITOR.htmlParser.filter=CKEDITOR.tools.createClass({$:function(b){this.id=CKEDITOR.tools.getNextNumber();this.elementNameRules=new a;this.attributeNameRules=new a;this.elementsRules={};this.attributesRules={};this.textRules=new a;this.commentRules=new a;this.rootRules=new a;b&&this.addRules(b,10)},proto:{addRules:function(a,c){var e;if(typeof c=="number")e=c;else if(c&&"priority"in
+c)e=c.priority;typeof e!="number"&&(e=10);typeof c!="object"&&(c={});a.elementNames&&this.elementNameRules.addMany(a.elementNames,e,c);a.attributeNames&&this.attributeNameRules.addMany(a.attributeNames,e,c);a.elements&&f(this.elementsRules,a.elements,e,c);a.attributes&&f(this.attributesRules,a.attributes,e,c);a.text&&this.textRules.add(a.text,e,c);a.comment&&this.commentRules.add(a.comment,e,c);a.root&&this.rootRules.add(a.root,e,c)},applyTo:function(a){a.filter(this)},onElementName:function(a,c){return this.elementNameRules.execOnName(a,
+c)},onAttributeName:function(a,c){return this.attributeNameRules.execOnName(a,c)},onText:function(a,c,e){return this.textRules.exec(a,c,e)},onComment:function(a,c,e){return this.commentRules.exec(a,c,e)},onRoot:function(a,c){return this.rootRules.exec(a,c)},onElement:function(a,c){for(var e=[this.elementsRules["^"],this.elementsRules[c.name],this.elementsRules.$],d,f=0;f<3;f++)if(d=e[f]){d=d.exec(a,c,this);if(d===false)return null;if(d&&d!=c)return this.onNode(a,d);if(c.parent&&!c.name)break}return c},
+onNode:function(a,c){var e=c.type;return e==CKEDITOR.NODE_ELEMENT?this.onElement(a,c):e==CKEDITOR.NODE_TEXT?new CKEDITOR.htmlParser.text(this.onText(a,c.value)):e==CKEDITOR.NODE_COMMENT?new CKEDITOR.htmlParser.comment(this.onComment(a,c.value)):null},onAttribute:function(a,c,e,d){return(e=this.attributesRules[e])?e.exec(a,d,c,this):d}}});CKEDITOR.htmlParser.filterRulesGroup=a;a.prototype={add:function(a,c,e){this.rules.splice(this.findIndex(c),0,{value:a,priority:c,options:e})},addMany:function(a,
+c,e){for(var d=[this.findIndex(c),0],f=0,k=a.length;f<k;f++)d.push({value:a[f],priority:c,options:e});this.rules.splice.apply(this.rules,d)},findIndex:function(a){for(var c=this.rules,e=c.length-1;e>=0&&a<c[e].priority;)e--;return e+1},exec:function(a,c){var e=c instanceof CKEDITOR.htmlParser.node||c instanceof CKEDITOR.htmlParser.fragment,d=Array.prototype.slice.call(arguments,1),f=this.rules,k=f.length,j,g,m,y;for(y=0;y<k;y++){if(e){j=c.type;g=c.name}m=f[y];if(!(a.nonEditable&&!m.options.applyToAll||
+a.nestedEditable&&m.options.excludeNestedEditable)){m=m.value.apply(null,d);if(m===false||e&&m&&(m.name!=g||m.type!=j))return m;m!=null&&(d[0]=c=m)}}return c},execOnName:function(a,c){for(var e=0,d=this.rules,f=d.length,k;c&&e<f;e++){k=d[e];!(a.nonEditable&&!k.options.applyToAll||a.nestedEditable&&k.options.excludeNestedEditable)&&(c=c.replace(k.value[0],k.value[1]))}return c}}})();
+(function(){function a(a,f){function p(a){return a||CKEDITOR.env.needsNbspFiller?new CKEDITOR.htmlParser.text(" "):new CKEDITOR.htmlParser.element("br",{"data-cke-bogus":1})}function r(a,e){return function(f){if(f.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT){var i=[],l=b(f),x,r;if(l)for(v(l,1)&&i.push(l);l;){if(d(l)&&(x=c(l))&&v(x))if((r=c(x))&&!d(r))i.push(x);else{p(g).insertAfter(x);x.remove()}l=l.previous}for(l=0;l<i.length;l++)i[l].remove();if(i=!a||(typeof e=="function"?e(f):e)!==false)if(!g&&!CKEDITOR.env.needsBrFiller&&
+f.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)i=false;else if(!g&&!CKEDITOR.env.needsBrFiller&&(document.documentMode>7||f.name in CKEDITOR.dtd.tr||f.name in CKEDITOR.dtd.$listItem))i=false;else{i=b(f);i=!i||f.name=="form"&&i.name=="input"}i&&f.add(p(a))}}}function v(a,b){if((!g||CKEDITOR.env.needsBrFiller)&&a.type==CKEDITOR.NODE_ELEMENT&&a.name=="br"&&!a.attributes["data-cke-eol"])return true;var c;if(a.type==CKEDITOR.NODE_TEXT&&(c=a.value.match(i))){if(c.index){(new CKEDITOR.htmlParser.text(a.value.substring(0,
+c.index))).insertBefore(a);a.value=c[0]}if(!CKEDITOR.env.needsBrFiller&&g&&(!b||a.parent.name in z))return true;if(!g)if((c=a.previous)&&c.name=="br"||!c||d(c))return true}return false}var n={elements:{}},g=f=="html",z=CKEDITOR.tools.extend({},l),o;for(o in z)"#"in u[o]||delete z[o];for(o in z)n.elements[o]=r(g,a.config.fillEmptyBlocks);n.root=r(g,false);n.elements.br=function(a){return function(b){if(b.parent.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT){var f=b.attributes;if("data-cke-bogus"in f||"data-cke-eol"in
+f)delete f["data-cke-bogus"];else{for(f=b.next;f&&e(f);)f=f.next;var i=c(b);!f&&d(b.parent)?h(b.parent,p(a)):d(f)&&(i&&!d(i))&&p(a).insertBefore(f)}}}}(g);return n}function f(a,b){return a!=CKEDITOR.ENTER_BR&&b!==false?a==CKEDITOR.ENTER_DIV?"div":"p":false}function b(a){for(a=a.children[a.children.length-1];a&&e(a);)a=a.previous;return a}function c(a){for(a=a.previous;a&&e(a);)a=a.previous;return a}function e(a){return a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(a.value)||a.type==CKEDITOR.NODE_ELEMENT&&
+a.attributes["data-cke-bookmark"]}function d(a){return a&&(a.type==CKEDITOR.NODE_ELEMENT&&a.name in l||a.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)}function h(a,b){var c=a.children[a.children.length-1];a.children.push(b);b.parent=a;if(c){c.next=b;b.previous=c}}function k(a){a=a.attributes;a.contenteditable!="false"&&(a["data-cke-editable"]=a.contenteditable?"true":1);a.contenteditable="false"}function j(a){a=a.attributes;switch(a["data-cke-editable"]){case "true":a.contenteditable="true";break;case "1":delete a.contenteditable}}
+function g(a){return a.replace(C,function(a,b,c){return"<"+b+c.replace(L,function(a,b){return F.test(b)&&c.indexOf("data-cke-saved-"+b)==-1?" data-cke-saved-"+a+" data-cke-"+CKEDITOR.rnd+"-"+a:a})+">"})}function m(a,b){return a.replace(b,function(a,b,c){a.indexOf("<textarea")===0&&(a=b+w(c).replace(/</g,"&lt;").replace(/>/g,"&gt;")+"</textarea>");return"<cke:encoded>"+encodeURIComponent(a)+"</cke:encoded>"})}function y(a){return a.replace(v,function(a,b){return decodeURIComponent(b)})}function s(a){return a.replace(/<\!--(?!{cke_protected})[\s\S]+?--\>/g,
+function(a){return"<\!--"+A+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\>"})}function w(a){return a.replace(/<\!--\{cke_protected\}\{C\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)})}function q(a,b){var c=b._.dataStore;return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function t(a,b){for(var c=[],d=b.config.protectedSource,e=b._.dataStore||(b._.dataStore=
+{id:1}),f=/<\!--\{cke_temp(comment)?\}(\d*?)--\>/g,d=[/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi,/<meta[\s\S]*?\/?>/gi].concat(d),a=a.replace(/<\!--[\s\S]*?--\>/g,function(a){return"<\!--{cke_tempcomment}"+(c.push(a)-1)+"--\>"}),i=0;i<d.length;i++)a=a.replace(d[i],function(a){a=a.replace(f,function(a,b,d){return c[d]});return/cke_temp(comment)?/.test(a)?a:"<\!--{cke_temp}"+(c.push(a)-1)+"--\>"});a=a.replace(f,function(a,b,d){return"<\!--"+A+(b?"{C}":"")+encodeURIComponent(c[d]).replace(/--/g,
+"%2D%2D")+"--\>"});a=a.replace(/<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=>]+))+\s*>/g,function(a){return a.replace(/<\!--\{cke_protected\}([^>]*)--\>/g,function(a,b){e[e.id]=decodeURIComponent(b);return"{cke_protected_"+e.id++ +"}"})});return a=a.replace(/<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g,function(a,c,d,e){return"<"+c+d+">"+q(w(e),b)+"</"+c+">"})}CKEDITOR.htmlDataProcessor=function(b){var c,d,e=this;this.editor=b;this.dataFilter=c=new CKEDITOR.htmlParser.filter;
+this.htmlFilter=d=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;c.addRules(p);c.addRules(r,{applyToAll:true});c.addRules(a(b,"data"),{applyToAll:true});d.addRules(n);d.addRules(P,{applyToAll:true});d.addRules(a(b,"html"),{applyToAll:true});b.on("toHtml",function(a){var a=a.data,c=a.dataValue,d,c=t(c,b),c=m(c,I),c=g(c),c=m(c,K),c=c.replace(G,"$1cke:$2"),c=c.replace(B,"<cke:$1$2></cke:$1>"),c=c.replace(/(<pre\b[^>]*>)(\r\n|\n)/g,"$1$2$2"),c=c.replace(/([^a-z0-9<\-])(on\w{3,})(?!>)/gi,
+"$1data-cke-"+CKEDITOR.rnd+"-$2");d=a.context||b.editable().getName();var e;if(CKEDITOR.env.ie&&CKEDITOR.env.version<9&&d=="pre"){d="div";c="<pre>"+c+"</pre>";e=1}d=b.document.createElement(d);d.setHtml("a"+c);c=d.getHtml().substr(1);c=c.replace(RegExp("data-cke-"+CKEDITOR.rnd+"-","ig"),"");e&&(c=c.replace(/^<pre>|<\/pre>$/gi,""));c=c.replace(z,"$1$2");c=y(c);c=w(c);d=a.fixForBody===false?false:f(a.enterMode,b.config.autoParagraph);c=CKEDITOR.htmlParser.fragment.fromHtml(c,a.context,d);if(d){e=c;
+if(!e.children.length&&CKEDITOR.dtd[e.name][d]){d=new CKEDITOR.htmlParser.element(d);e.add(d)}}a.dataValue=c},null,null,5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,true,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(e.dataFilter,true)},null,null,10);b.on("toHtml",function(a){var a=a.data,b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(true);a.dataValue=
+s(b)},null,null,15);b.on("toDataFormat",function(a){var c=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(c=c.replace(/^<br *\/?>/i,""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.data.context,f(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(e.htmlFilter,true)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,false,true)},null,null,11);b.on("toDataFormat",function(a){var c=
+a.data.dataValue,d=e.writer;d.reset();c.writeChildrenHtml(d);c=d.getHtml(true);c=w(c);c=q(c,b);a.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype={toHtml:function(a,b,c,d){var e=this.editor,f,i,l;if(b&&typeof b=="object"){f=b.context;c=b.fixForBody;d=b.dontFilter;i=b.filter;l=b.enterMode}else f=b;!f&&f!==null&&(f=e.editable().getName());return e.fire("toHtml",{dataValue:a,context:f,fixForBody:c,dontFilter:d,filter:i||e.filter,enterMode:l||e.enterMode}).dataValue},toDataFormat:function(a,
+b){var c,d,e;if(b){c=b.context;d=b.filter;e=b.enterMode}!c&&c!==null&&(c=this.editor.editable().getName());return this.editor.fire("toDataFormat",{dataValue:a,filter:d||this.editor.filter,context:c,enterMode:e||this.editor.enterMode}).dataValue}};var i=/(?:&nbsp;|\xa0)$/,A="{cke_protected}",u=CKEDITOR.dtd,o=["caption","colgroup","col","thead","tfoot","tbody"],l=CKEDITOR.tools.extend({},u.$blockLimit,u.$block),p={elements:{input:k,textarea:k}},r={attributeNames:[[/^on/,"data-cke-pa-on"],[/^data-cke-expando$/,
+""]]},n={elements:{embed:function(a){var b=a.parent;if(b&&b.name=="object"){var c=b.attributes.width,b=b.attributes.height;if(c)a.attributes.width=c;if(b)a.attributes.height=b}},a:function(a){if(!a.children.length&&!a.attributes.name&&!a.attributes["data-cke-saved-name"])return false}}},P={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/,""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return false;
+for(var c=["name","href","src"],d,e=0;e<c.length;e++){d="data-cke-saved-"+c[e];d in b&&delete b[c[e]]}}return a},table:function(a){a.children.slice(0).sort(function(a,b){var c,d;if(a.type==CKEDITOR.NODE_ELEMENT&&b.type==a.type){c=CKEDITOR.tools.indexOf(o,a.name);d=CKEDITOR.tools.indexOf(o,b.name)}if(!(c>-1&&d>-1&&c!=d)){c=a.parent?a.getIndex():-1;d=b.parent?b.getIndex():-1}return c>d?1:-1})},param:function(a){a.children=[];a.isEmpty=true;return a},span:function(a){a.attributes["class"]=="Apple-style-span"&&
+delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var b=a.children[0];if(b&&b.value)b.value=CKEDITOR.tools.trim(b.value);if(!a.attributes.type)a.attributes.type="text/css"},title:function(a){var b=a.children[0];!b&&h(a,b=new CKEDITOR.htmlParser.text);b.value=a.attributes["data-cke-title"]||""},input:j,textarea:j},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,
+""))||false}}};if(CKEDITOR.env.ie)P.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})};var C=/<(a|area|img|input|source)\b([^>]*)>/gi,L=/([\w-]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,F=/^(href|src|name)$/i,K=/(?:<style(?=[ >])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,I=/(<textarea(?=[ >])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,v=/<cke:encoded>([^<]*)<\/cke:encoded>/gi,G=/(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
+z=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,B=/<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi})();"use strict";
+CKEDITOR.htmlParser.element=function(a,f){this.name=a;this.attributes=f||{};this.children=[];var b=a||"",c=b.match(/^cke:(.*)/);c&&(b=c[1]);b=!(!CKEDITOR.dtd.$nonBodyContent[b]&&!CKEDITOR.dtd.$block[b]&&!CKEDITOR.dtd.$listItem[b]&&!CKEDITOR.dtd.$tableContent[b]&&!(CKEDITOR.dtd.$nonEditable[b]||b=="br"));this.isEmpty=!!CKEDITOR.dtd.$empty[a];this.isUnknown=!CKEDITOR.dtd[a];this._={isBlockLike:b,hasInlineStarted:this.isEmpty||!b}};
+CKEDITOR.htmlParser.cssStyle=function(a){var f={};((a instanceof CKEDITOR.htmlParser.element?a.attributes.style:a)||"").replace(/&quot;/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,c,e){c=="font-family"&&(e=e.replace(/["']/g,""));f[c.toLowerCase()]=e});return{rules:f,populate:function(a){var c=this.toString();if(c)a instanceof CKEDITOR.dom.element?a.setAttribute("style",c):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=c:a.style=c},toString:function(){var a=[],c;
+for(c in f)f[c]&&a.push(c,":",f[c],";");return a.join("")}}};
+(function(){function a(a){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&(typeof a=="string"?b.name==a:b.name in a)}}var f=function(a,b){a=a[0];b=b[0];return a<b?-1:a>b?1:0},b=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:b.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a,b){var d=this,f,k,b=d.getFilterContext(b);if(b.off)return true;
+if(!d.parent)a.onRoot(b,d);for(;;){f=d.name;if(!(k=a.onElementName(b,f))){this.remove();return false}d.name=k;if(!(d=a.onElement(b,d))){this.remove();return false}if(d!==this){this.replaceWith(d);return false}if(d.name==f)break;if(d.type!=CKEDITOR.NODE_ELEMENT){this.replaceWith(d);return false}if(!d.name){this.replaceWithChildren();return false}}f=d.attributes;var j,g;for(j in f){g=j;for(k=f[j];;)if(g=a.onAttributeName(b,j))if(g!=j){delete f[j];j=g}else break;else{delete f[j];break}g&&((k=a.onAttribute(b,
+d,g,k))===false?delete f[g]:f[g]=k)}d.isEmpty||this.filterChildren(a,false,b);return true},filterChildren:b.filterChildren,writeHtml:function(a,b){b&&this.filter(b);var d=this.name,h=[],k=this.attributes,j,g;a.openTag(d,k);for(j in k)h.push([j,k[j]]);a.sortAttributes&&h.sort(f);j=0;for(g=h.length;j<g;j++){k=h[j];a.attribute(k[0],k[1])}a.openTagClose(d,this.isEmpty);this.writeChildrenHtml(a);this.isEmpty||a.closeTag(d)},writeChildrenHtml:b.writeChildrenHtml,replaceWithChildren:function(){for(var a=
+this.children,b=a.length;b;)a[--b].insertAfter(this);this.remove()},forEach:b.forEach,getFirst:function(b){if(!b)return this.children.length?this.children[0]:null;typeof b!="function"&&(b=a(b));for(var e=0,d=this.children.length;e<d;++e)if(b(this.children[e]))return this.children[e];return null},getHtml:function(){var a=new CKEDITOR.htmlParser.basicWriter;this.writeChildrenHtml(a);return a.getHtml()},setHtml:function(a){for(var a=this.children=CKEDITOR.htmlParser.fragment.fromHtml(a).children,b=0,
+d=a.length;b<d;++b)a[b].parent=this},getOuterHtml:function(){var a=new CKEDITOR.htmlParser.basicWriter;this.writeHtml(a);return a.getHtml()},split:function(a){for(var b=this.children.splice(a,this.children.length-a),d=this.clone(),f=0;f<b.length;++f)b[f].parent=d;d.children=b;if(b[0])b[0].previous=null;if(a>0)this.children[a-1].next=null;this.parent.add(d,this.getIndex()+1);return d},addClass:function(a){if(!this.hasClass(a)){var b=this.attributes["class"]||"";this.attributes["class"]=b+(b?" ":"")+
+a}},removeClass:function(a){var b=this.attributes["class"];if(b)(b=CKEDITOR.tools.trim(b.replace(RegExp("(?:\\s+|^)"+a+"(?:\\s+|$)")," ")))?this.attributes["class"]=b:delete this.attributes["class"]},hasClass:function(a){var b=this.attributes["class"];return!b?false:RegExp("(?:^|\\s)"+a+"(?=\\s|$)").test(b)},getFilterContext:function(a){var b=[];a||(a={off:false,nonEditable:false,nestedEditable:false});!a.off&&this.attributes["data-cke-processor"]=="off"&&b.push("off",true);!a.nonEditable&&this.attributes.contenteditable==
+"false"?b.push("nonEditable",true):a.nonEditable&&(!a.nestedEditable&&this.attributes.contenteditable=="true")&&b.push("nestedEditable",true);if(b.length)for(var a=CKEDITOR.tools.copy(a),d=0;d<b.length;d=d+2)a[b[d]]=b[d+1];return a}},true)})();
+(function(){var a={},f=/{([^}]+)}/g,b=/([\\'])/g,c=/\n/g,e=/\r/g;CKEDITOR.template=function(d){if(a[d])this.output=a[d];else{var h=d.replace(b,"\\$1").replace(c,"\\n").replace(e,"\\r").replace(f,function(a,b){return"',data['"+b+"']==undefined?'{"+b+"}':data['"+b+"'],'"});this.output=a[d]=Function("data","buffer","return buffer?buffer.push('"+h+"'):['"+h+"'].join('');")}}})();delete CKEDITOR.loadFullCore;CKEDITOR.instances={};CKEDITOR.document=new CKEDITOR.dom.document(document);
+CKEDITOR.add=function(a){CKEDITOR.instances[a.name]=a;a.on("focus",function(){if(CKEDITOR.currentInstance!=a){CKEDITOR.currentInstance=a;CKEDITOR.fire("currentInstance")}});a.on("blur",function(){if(CKEDITOR.currentInstance==a){CKEDITOR.currentInstance=null;CKEDITOR.fire("currentInstance")}});CKEDITOR.fire("instance",null,a)};CKEDITOR.remove=function(a){delete CKEDITOR.instances[a.name]};
+(function(){var a={};CKEDITOR.addTemplate=function(f,b){var c=a[f];if(c)return c;c={name:f,source:b};CKEDITOR.fire("template",c);return a[f]=new CKEDITOR.template(c.source)};CKEDITOR.getTemplate=function(f){return a[f]}})();(function(){var a=[];CKEDITOR.addCss=function(f){a.push(f)};CKEDITOR.getCss=function(){return a.join("\n")}})();CKEDITOR.on("instanceDestroyed",function(){CKEDITOR.tools.isEmpty(this.instances)&&CKEDITOR.fire("reset")});CKEDITOR.TRISTATE_ON=1;CKEDITOR.TRISTATE_OFF=2;
+CKEDITOR.TRISTATE_DISABLED=0;
+(function(){CKEDITOR.inline=function(a,f){if(!CKEDITOR.env.isCompatible)return null;a=CKEDITOR.dom.element.get(a);if(a.getEditor())throw'The editor instance "'+a.getEditor().name+'" is already attached to the provided element.';var b=new CKEDITOR.editor(f,a,CKEDITOR.ELEMENT_MODE_INLINE),c=a.is("textarea")?a:null;if(c){b.setData(c.getValue(),null,true);a=CKEDITOR.dom.element.createFromHtml('<div contenteditable="'+!!b.readOnly+'" class="cke_textarea_inline">'+c.getValue()+"</div>",CKEDITOR.document);
+a.insertAfter(c);c.hide();c.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(c){b.container.clearCustomData();b.container.remove();c.show()}b.element.clearCustomData();delete b.element});return b};
+CKEDITOR.inlineAll=function(){var a,f,b;for(b in CKEDITOR.dtd.$editable)for(var c=CKEDITOR.document.getElementsByTag(b),e=0,d=c.count();e<d;e++){a=c.getItem(e);if(a.getAttribute("contenteditable")=="true"){f={element:a,config:{}};CKEDITOR.fire("inline",f)!==false&&CKEDITOR.inline(a,f.config)}}};CKEDITOR.domReady(function(){!CKEDITOR.disableAutoInline&&CKEDITOR.inlineAll()})})();CKEDITOR.replaceClass="ckeditor";
+(function(){function a(a,e,d,h){if(!CKEDITOR.env.isCompatible)return null;a=CKEDITOR.dom.element.get(a);if(a.getEditor())throw'The editor instance "'+a.getEditor().name+'" is already attached to the provided element.';var k=new CKEDITOR.editor(e,a,h);if(h==CKEDITOR.ELEMENT_MODE_REPLACE){a.setStyle("visibility","hidden");k._.required=a.hasAttribute("required");a.removeAttribute("required")}d&&k.setData(d,null,true);k.on("loaded",function(){b(k);h==CKEDITOR.ELEMENT_MODE_REPLACE&&(k.config.autoUpdateElement&&
+a.$.form)&&k._attachToForm();k.setMode(k.config.startupMode,function(){k.resetDirty();k.status="ready";k.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,k)})});k.on("destroy",f);return k}function f(){var a=this.container,b=this.element;if(a){a.clearCustomData();a.remove()}if(b){b.clearCustomData();if(this.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE){b.show();this._.required&&b.setAttribute("required","required")}delete this.element}}function b(a){var b=a.name,d=a.element,f=a.elementMode,
+k=a.fire("uiSpace",{space:"top",html:""}).html,j=a.fire("uiSpace",{space:"bottom",html:""}).html,g=new CKEDITOR.template('<{outerEl} id="cke_{name}" class="{id} cke cke_reset cke_chrome cke_editor_{name} cke_{langDir} '+CKEDITOR.env.cssClass+'" dir="{langDir}" lang="{langCode}" role="application"'+(a.title?' aria-labelledby="cke_{name}_arialbl"':"")+">"+(a.title?'<span id="cke_{name}_arialbl" class="cke_voice_label">{voiceLabel}</span>':"")+'<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation"></{outerEl}>{bottomHtml}</{outerEl}></{outerEl}>'),
+b=CKEDITOR.dom.element.createFromHtml(g.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:a.title,topHtml:k?'<span id="'+a.ui.spaceId("top")+'" class="cke_top cke_reset_all" role="presentation" style="height:auto">'+k+"</span>":"",contentId:a.ui.spaceId("contents"),bottomHtml:j?'<span id="'+a.ui.spaceId("bottom")+'" class="cke_bottom cke_reset_all" role="presentation">'+j+"</span>":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(f==CKEDITOR.ELEMENT_MODE_REPLACE){d.hide();b.insertAfter(d)}else d.append(b);
+a.container=b;k&&a.ui.space("top").unselectable();j&&a.ui.space("bottom").unselectable();d=a.config.width;f=a.config.height;d&&b.setStyle("width",CKEDITOR.tools.cssLength(d));f&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(f));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,e){return a(b,e,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,e,d){return a(b,e,d,CKEDITOR.ELEMENT_MODE_APPENDTO)};
+CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b<a.length;b++){var d=null,f=a[b];if(f.name||f.id){if(typeof arguments[0]=="string"){if(!RegExp("(?:^|\\s)"+arguments[0]+"(?:$|\\s)").test(f.className))continue}else if(typeof arguments[0]=="function"){d={};if(arguments[0](f,d)===false)continue}this.replace(f,d)}}};CKEDITOR.editor.prototype.addMode=function(a,b){(this._.modes||(this._.modes={}))[a]=b};CKEDITOR.editor.prototype.setMode=function(a,b){var d=this,f=
+this._.modes;if(!(a==d.mode||!f||!f[a])){d.fire("beforeSetMode",a);if(d.mode){var k=d.checkDirty(),f=d._.previousModeData,j,g=0;d.fire("beforeModeUnload");d.editable(0);d._.previousMode=d.mode;d._.previousModeData=j=d.getData(1);if(d.mode=="source"&&f==j){d.fire("lockSnapshot",{forceUpdate:true});g=1}d.ui.space("contents").setHtml("");d.mode=""}else d._.previousModeData=d.getData(1);this._.modes[a](function(){d.mode=a;k!==void 0&&!k&&d.resetDirty();g?d.fire("unlockSnapshot"):a=="wysiwyg"&&d.fire("saveSnapshot");
+setTimeout(function(){d.fire("mode");b&&b.call(d)},0)})}};CKEDITOR.editor.prototype.resize=function(a,b,d,f){var k=this.container,j=this.ui.space("contents"),g=CKEDITOR.env.webkit&&this.document&&this.document.getWindow().$.frameElement,f=f?this.container.getFirst(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasClass("cke_inner")}):k;f.setSize("width",a,true);g&&(g.style.width="1%");j.setStyle("height",Math.max(b-(d?0:(f.$.offsetHeight||0)-(j.$.clientHeight||0)),0)+"px");g&&(g.style.width=
+"100%");this.fire("resize")};CKEDITOR.editor.prototype.getResizable=function(a){return a?this.ui.space("contents"):this.container};CKEDITOR.domReady(function(){CKEDITOR.replaceClass&&CKEDITOR.replaceAll(CKEDITOR.replaceClass)})})();CKEDITOR.config.startupMode="wysiwyg";
+(function(){function a(a){var b=a.editor,d=a.data.path,e=d.blockLimit,l=a.data.selection,p=l.getRanges()[0],r;if(CKEDITOR.env.gecko||CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)if(l=f(l,d)){l.appendBogus();r=CKEDITOR.env.ie}if(h(b,d.block,e)&&p.collapsed&&!p.getCommonAncestor().isReadOnly()){d=p.clone();d.enlarge(CKEDITOR.ENLARGE_BLOCK_CONTENTS);e=new CKEDITOR.dom.walker(d);e.guard=function(a){return!c(a)||a.type==CKEDITOR.NODE_COMMENT||a.isReadOnly()};if(!e.checkForward()||d.checkStartOfBlock()&&
+d.checkEndOfBlock()){b=p.fixBlock(true,b.activeEnterMode==CKEDITOR.ENTER_DIV?"div":"p");if(!CKEDITOR.env.needsBrFiller)(b=b.getFirst(c))&&(b.type==CKEDITOR.NODE_TEXT&&CKEDITOR.tools.trim(b.getText()).match(/^(?:&nbsp;|\xa0)$/))&&b.remove();r=1;a.cancel()}}r&&p.select()}function f(a,b){if(a.isFake)return 0;var d=b.block||b.blockLimit,e=d&&d.getLast(c);if(d&&d.isBlockBoundary()&&(!e||!(e.type==CKEDITOR.NODE_ELEMENT&&e.isBlockBoundary()))&&!d.is("pre")&&!d.getBogus())return d}function b(a){var b=a.data.getTarget();
+if(b.is("input")){b=b.getAttribute("type");(b=="submit"||b=="reset")&&a.data.preventDefault()}}function c(a){return s(a)&&w(a)}function e(a,b){return function(c){var d=CKEDITOR.dom.element.get(c.data.$.toElement||c.data.$.fromElement||c.data.$.relatedTarget);(!d||!b.equals(d)&&!b.contains(d))&&a.call(this,c)}}function d(a){function b(a){return function(b,e){e&&(b.type==CKEDITOR.NODE_ELEMENT&&b.is(f))&&(d=b);if(!e&&c(b)&&(!a||!m(b)))return false}}var d,e=a.getRanges()[0],a=a.root,f={table:1,ul:1,ol:1,
+dl:1};if(e.startPath().contains(f)){var p=e.clone();p.collapse(1);p.setStartAt(a,CKEDITOR.POSITION_AFTER_START);a=new CKEDITOR.dom.walker(p);a.guard=b();a.checkBackward();if(d){p=e.clone();p.collapse();p.setEndAt(d,CKEDITOR.POSITION_AFTER_END);a=new CKEDITOR.dom.walker(p);a.guard=b(true);d=false;a.checkForward();return d}}return null}function h(a,b,c){return a.config.autoParagraph!==false&&a.activeEnterMode!=CKEDITOR.ENTER_BR&&a.editable().equals(c)&&!b||b&&b.getAttribute("contenteditable")=="true"}
+function k(a){a.editor.focus();a.editor.fire("saveSnapshot")}function j(a){var b=a.editor;b.getSelection().scrollIntoView();setTimeout(function(){b.fire("saveSnapshot")},0)}function g(a,b,c){for(var d=a.getCommonAncestor(b),b=a=c?b:a;(a=a.getParent())&&!d.equals(a)&&a.getChildCount()==1;)b=a;b.remove()}CKEDITOR.editable=CKEDITOR.tools.createClass({base:CKEDITOR.dom.element,$:function(a,b){this.base(b.$||b);this.editor=a;this.status="unloaded";this.hasFocus=false;this.setup()},proto:{focus:function(){var a;
+if(CKEDITOR.env.webkit&&!this.hasFocus){a=this.editor._.previousActive||this.getDocument().getActive();if(this.contains(a)){a.focus();return}}try{this.$[CKEDITOR.env.ie&&this.getDocument().equals(CKEDITOR.document)?"setActive":"focus"]()}catch(b){if(!CKEDITOR.env.ie)throw b;}if(CKEDITOR.env.safari&&!this.isInline()){a=CKEDITOR.document.getActive();a.equals(this.getWindow().getFrame())||this.getWindow().focus()}},on:function(a,b){var c=Array.prototype.slice.call(arguments,0);if(CKEDITOR.env.ie&&/^focus|blur$/.exec(a)){a=
+a=="focus"?"focusin":"focusout";b=e(b,this);c[0]=a;c[1]=b}return CKEDITOR.dom.element.prototype.on.apply(this,c)},attachListener:function(a){!this._.listeners&&(this._.listeners=[]);var b=Array.prototype.slice.call(arguments,1),b=a.on.apply(a,b);this._.listeners.push(b);return b},clearListeners:function(){var a=this._.listeners;try{for(;a.length;)a.pop().removeListener()}catch(b){}},restoreAttrs:function(){var a=this._.attrChanges,b,c;for(c in a)if(a.hasOwnProperty(c)){b=a[c];b!==null?this.setAttribute(c,
+b):this.removeAttribute(c)}},attachClass:function(a){var b=this.getCustomData("classes");if(!this.hasClass(a)){!b&&(b=[]);b.push(a);this.setCustomData("classes",b);this.addClass(a)}},changeAttr:function(a,b){var c=this.getAttribute(a);if(b!==c){!this._.attrChanges&&(this._.attrChanges={});a in this._.attrChanges||(this._.attrChanges[a]=c);this.setAttribute(a,b)}},insertHtml:function(a,b){k(this);q(this,b||"html",a)},insertText:function(a){k(this);var b=this.editor,c=b.getSelection().getStartElement().hasAscendant("pre",
+true)?CKEDITOR.ENTER_BR:b.activeEnterMode,b=c==CKEDITOR.ENTER_BR,d=CKEDITOR.tools,a=d.htmlEncode(a.replace(/\r\n/g,"\n")),a=a.replace(/\t/g,"&nbsp;&nbsp; &nbsp;"),c=c==CKEDITOR.ENTER_P?"p":"div";if(!b){var e=/\n{2}/g;if(e.test(a))var f="<"+c+">",r="</"+c+">",a=f+a.replace(e,function(){return r+f})+r}a=a.replace(/\n/g,"<br>");b||(a=a.replace(RegExp("<br>(?=</"+c+">)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g,"&nbsp;");a=a.replace(/(>|\s) /g,function(a,b){return b+"&nbsp;"}).replace(/ (?=<)/g,
+"&nbsp;");q(this,"text",a)},insertElement:function(a,b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a,b){var c=this.editor,d=c.config.enterMode,e=a.getName(),f=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.is({tr:1,table:1,tbody:1,thead:1,tfoot:1})&&t(b);var r,n;if(f)for(;(r=b.getCommonAncestor(0,1))&&(n=CKEDITOR.dtd[r.getName()])&&(!n||!n[e]);)if(r.getName()in
+CKEDITOR.dtd.span)b.splitElement(r);else if(b.checkStartOfBlock()&&b.checkEndOfBlock()){b.setStartBefore(r);b.collapse(true);r.remove()}else b.splitBlock(d==CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){k(this);var b=this.editor,d=b.activeEnterMode,b=b.getSelection(),e=b.getRanges()[0],f=a.getName(),f=CKEDITOR.dtd.$block[f];if(this.insertElementIntoRange(a,e)){e.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);if(f)if((f=a.getNext(function(a){return c(a)&&
+!m(a)}))&&f.type==CKEDITOR.NODE_ELEMENT&&f.is(CKEDITOR.dtd.$block))f.getDtd()["#"]?e.moveToElementEditStart(f):e.moveToElementEditEnd(a);else if(!f&&d!=CKEDITOR.ENTER_BR){f=e.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");e.moveToElementEditStart(f)}}b.selectRanges([e]);j(this)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);this.fixInitialSelection();if(this.status=="unloaded")this.status="ready";this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml();
+a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable");this.status="detached";var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},fixInitialSelection:function(){function a(){var b=c.getDocument().$,d=b.getSelection(),e;if(d.anchorNode&&d.anchorNode==c.$)e=true;else if(CKEDITOR.env.webkit){var f=
+c.getDocument().getActive();f&&(f.equals(c)&&!d.anchorNode)&&(e=true)}if(e){e=new CKEDITOR.dom.range(c);e.moveToElementEditStart(c);b=b.createRange();b.setStart(e.startContainer.$,e.startOffset);b.collapse(true);d.removeAllRanges();d.addRange(b)}}function b(){var a=c.getDocument().$,d=a.selection,e=c.getDocument().getActive();if(d.type=="None"&&e.equals(c)){d=new CKEDITOR.dom.range(c);a=a.body.createTextRange();d.moveToElementEditStart(c);d=d.startContainer;d.type!=CKEDITOR.NODE_ELEMENT&&(d=d.getParent());
+a.moveToElementText(d.$);a.collapse(true);a.select()}}var c=this;if(CKEDITOR.env.ie&&(CKEDITOR.env.version<9||CKEDITOR.env.quirks)){if(this.hasFocus){this.focus();b()}}else if(this.hasFocus){this.focus();a()}else this.once("focus",function(){a()},null,null,-999)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(y,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,
+"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},
+this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",
+function(){this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus=true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus(this)},this)}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var e=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var f=a.config.contentsLangDirection;
+this.getDirection(1)!=f&&this.changeAttr("dir",f);var h=CKEDITOR.getCss();if(h){f=e.getHead();if(!f.getCustomData("stylesheet")){h=e.appendStyleText(h);h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement);f.setCustomData("stylesheet",h);h.data("cke-temp",1)}}f=e.getCustomData("stylesheet_ref")||0;e.setCustomData("stylesheet_ref",f+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),
+this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()});var l={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.domEvent.getKey(),e;if(c in l){var b=a.getSelection(),f,h=b.getRanges()[0],g=h.startPath(),o,m,j,c=c==8;if(CKEDITOR.env.ie&&CKEDITOR.env.version<11&&(f=b.getSelectedElement())||(f=d(b))){a.fire("saveSnapshot");h.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START);f.remove();h.select();a.fire("saveSnapshot");e=1}else if(h.collapsed)if((o=
+g.block)&&(j=o[c?"getPrevious":"getNext"](s))&&j.type==CKEDITOR.NODE_ELEMENT&&j.is("table")&&h[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");h[c?"checkEndOfBlock":"checkStartOfBlock"]()&&o.remove();h["moveToElementEdit"+(c?"End":"Start")](j);h.select();a.fire("saveSnapshot");e=1}else if(g.blockLimit&&g.blockLimit.is("td")&&(m=g.blockLimit.getAscendant("table"))&&h.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END)&&(j=m[c?"getPrevious":"getNext"](s))){a.fire("saveSnapshot");
+h["moveToElementEdit"+(c?"End":"Start")](j);h.checkStartOfBlock()&&h.checkEndOfBlock()?j.remove():h.select();a.fire("saveSnapshot");e=1}else if((m=g.contains(["td","th","caption"]))&&h.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END))e=1}return!e});a.blockless&&(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in l&&!this.getFirst(c)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}});
+this.attachListener(this,"dblclick",function(b){if(a.readOnly)return false;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")&&!c.isReadOnly()){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==
+2){b=b.data.getTarget();if(!b.getOuterHtml().replace(y,"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){b=b.data.domEvent.getKey();if(b in l){var c=b==8,d=a.getSelection().getRanges()[0],
+b=d.startPath();if(d.collapsed){var e;a:{var f=b.block;if(f)if(d[c?"checkStartOfBlock":"checkEndOfBlock"]())if(!d.moveToClosestEditablePosition(f,!c)||!d.collapsed)e=false;else{if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var h=d.startContainer.getChild(d.startOffset-(c?1:0));if(h&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("hr")){a.fire("saveSnapshot");h.remove();e=true;break a}}if((d=d.startPath().block)&&(!d||!d.contains(f))){a.fire("saveSnapshot");var j;(j=(c?d:f).getBogus())&&j.remove();e=a.getSelection();
+j=e.createBookmarks();(c?f:d).moveChildren(c?d:f,false);b.lastElement.mergeSiblings();g(f,d,!c);e.selectBookmarks(j);e=true}}else e=false;else e=false}if(!e)return}else{c=d;e=b.block;j=c.endPath().block;if(!e||!j||e.equals(j))b=false;else{a.fire("saveSnapshot");(f=e.getBogus())&&f.remove();c.deleteContents();if(j.getParent()){j.moveChildren(e,false);b.lastElement.mergeSiblings();g(e,j,true)}c=a.getSelection().getRanges()[0];c.collapse(1);c.select();b=true}if(!b)return}a.getSelection().scrollIntoView();
+a.fire("saveSnapshot");return false}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref");b.removeCustomData("stylesheet").remove()}}}this.editor.fire("contentDomUnload");
+delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var m=CKEDITOR.dom.walker.bogus(),y=/(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,s=CKEDITOR.dom.walker.whitespaces(true),w=CKEDITOR.dom.walker.bookmark(false,true);CKEDITOR.on("instanceLoaded",
+function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty();c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})});CKEDITOR.on("instanceCreated",
+function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);var d=b.fire("ariaEditorHelpLabel",{}).label;if(d)if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var e=CKEDITOR.tools.getNextId(),d=CKEDITOR.dom.element.createFromHtml('<span id="'+e+'" class="cke_voice_label">'+d+"</span>");c.append(d);a.changeAttr("aria-describedby",e)}}})});
+CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var q=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,l,p,h=[],g=d.range.startContainer;e=d.range.startPath();for(var g=r[g.getName()],n=0,j=c.getChildren(),m=j.count(),o=-1,C=-1,k=0,q=e.contains(r.$list);n<m;++n){e=j.getItem(n);if(a(e)){l=e.getName();if(q&&l in CKEDITOR.dtd.$list)h=h.concat(b(e,d));else{p=!!g[l];if(l=="br"&&e.data("cke-eol")&&
+(!n||n==m-1)){k=(f=n?h[n-1].node:j.getItem(n+1))&&(!a(f)||!f.is("br"));f=f&&a(f)&&r.$block[f.getName()]}o==-1&&!p&&(o=n);p||(C=n);h.push({isElement:1,isLineBreak:k,isBlock:e.isBlockBoundary(),hasBlockSibling:f,node:e,name:l,allowed:p});f=k=0}}else h.push({isElement:0,node:e,allowed:1})}if(o>-1)h[o].firstNotAllowed=1;if(C>-1)h[C].lastNotAllowed=1;return h}function d(b,c){var e=[],f=b.getChildren(),l=f.count(),p,h=0,g=r[c],n=!b.is(r.$inline)||b.is("br");for(n&&e.push(" ");h<l;h++){p=f.getItem(h);a(p)&&
+!p.is(g)?e=e.concat(d(p,c)):e.push(p)}n&&e.push(" ");return e}function e(b){return b&&a(b)&&(b.is(r.$removeEmpty)||b.is("a")&&!b.isBlockBoundary())}function f(b,c,d,e){var p=b.clone(),h,r;p.setEndAt(c,CKEDITOR.POSITION_BEFORE_END);if((h=(new CKEDITOR.dom.walker(p)).next())&&a(h)&&g[h.getName()]&&(r=h.getPrevious())&&a(r)&&!r.getParent().equals(b.startContainer)&&d.contains(r)&&e.contains(h)&&h.isIdentical(r)){h.moveChildren(r);h.remove();f(b,c,d,e)}}function p(b,c){function d(b,c){if(c.isBlock&&c.isElement&&
+!c.node.is("br")&&a(b)&&b.is("br")){b.remove();return 1}}var e=c.endContainer.getChild(c.endOffset),f=c.endContainer.getChild(c.endOffset-1);e&&d(e,b[b.length-1]);if(f&&d(f,b[0])){c.setEnd(c.endContainer,c.endOffset-1);c.collapse()}}var r=CKEDITOR.dtd,g={p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,ul:1,ol:1,li:1,pre:1,dl:1,blockquote:1},m={p:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1},C=CKEDITOR.tools.extend({},r.$inline);delete C.br;return function(g,n,k){var q=g.editor,v=q.getSelection().getRanges()[0],
+G=false;if(n=="unfiltered_html"){n="html";G=true}if(!v.checkReadOnly()){var z=(new CKEDITOR.dom.elementPath(v.startContainer,v.root)).blockLimit||v.root,n={type:n,dontFilter:G,editable:g,editor:q,range:v,blockLimit:z,mergeCandidates:[],zombies:[]},q=n.range,G=n.mergeCandidates,B,x,E,s;if(n.type=="text"&&q.shrink(CKEDITOR.SHRINK_ELEMENT,true,false)){B=CKEDITOR.dom.element.createFromHtml("<span>&nbsp;</span>",q.document);q.insertNode(B);q.setStartAfter(B)}x=new CKEDITOR.dom.elementPath(q.startContainer);
+n.endPath=E=new CKEDITOR.dom.elementPath(q.endContainer);if(!q.collapsed){var z=E.block||E.blockLimit,w=q.getCommonAncestor();z&&(!z.equals(w)&&!z.contains(w)&&q.checkEndOfBlock())&&n.zombies.push(z);q.deleteContents()}for(;(s=a(q.startContainer)&&q.startContainer.getChild(q.startOffset-1))&&a(s)&&s.isBlockBoundary()&&x.contains(s);)q.moveToPosition(s,CKEDITOR.POSITION_BEFORE_END);f(q,n.blockLimit,x,E);if(B){q.setEndBefore(B);q.collapse();B.remove()}B=q.startPath();if(z=B.contains(e,false,1)){q.splitElement(z);
+n.inlineStylesRoot=z;n.inlineStylesPeak=B.lastElement}B=q.createBookmark();(z=B.startNode.getPrevious(c))&&a(z)&&e(z)&&G.push(z);(z=B.startNode.getNext(c))&&a(z)&&e(z)&&G.push(z);for(z=B.startNode;(z=z.getParent())&&e(z);)G.push(z);q.moveToBookmark(B);if(B=k){B=n.range;if(n.type=="text"&&n.inlineStylesRoot){s=n.inlineStylesPeak;q=s.getDocument().createText("{cke-peak}");for(G=n.inlineStylesRoot.getParent();!s.equals(G);){q=q.appendTo(s.clone());s=s.getParent()}k=q.getOuterHtml().split("{cke-peak}").join(k)}s=
+n.blockLimit.getName();if(/^\s+|\s+$/.test(k)&&"span"in CKEDITOR.dtd[s])var y='<span data-cke-marker="1">&nbsp;</span>',k=y+k+y;k=n.editor.dataProcessor.toHtml(k,{context:null,fixForBody:false,dontFilter:n.dontFilter,filter:n.editor.activeFilter,enterMode:n.editor.activeEnterMode});s=B.document.createElement("body");s.setHtml(k);if(y){s.getFirst().remove();s.getLast().remove()}if((y=B.startPath().block)&&!(y.getChildCount()==1&&y.getBogus()))a:{var t;if(s.getChildCount()==1&&a(t=s.getFirst())&&t.is(m)){y=
+t.getElementsByTag("*");B=0;for(G=y.count();B<G;B++){q=y.getItem(B);if(!q.is(C))break a}t.moveChildren(t.getParent(1));t.remove()}}n.dataWrapper=s;B=k}if(B){t=n.range;var y=t.document,D,k=n.blockLimit;B=0;var J;s=[];var H,Q,G=q=0,M,S;x=t.startContainer;var z=n.endPath.elements[0],T;E=z.getPosition(x);w=!!z.getCommonAncestor(x)&&E!=CKEDITOR.POSITION_IDENTICAL&&!(E&CKEDITOR.POSITION_CONTAINS+CKEDITOR.POSITION_IS_CONTAINED);x=b(n.dataWrapper,n);for(p(x,t);B<x.length;B++){E=x[B];if(D=E.isLineBreak){D=
+t;M=k;var O=void 0,V=void 0;if(E.hasBlockSibling)D=1;else{O=D.startContainer.getAscendant(r.$block,1);if(!O||!O.is({div:1,p:1}))D=0;else{V=O.getPosition(M);if(V==CKEDITOR.POSITION_IDENTICAL||V==CKEDITOR.POSITION_CONTAINS)D=0;else{M=D.splitElement(O);D.moveToPosition(M,CKEDITOR.POSITION_AFTER_START);D=1}}}}if(D)G=B>0;else{D=t.startPath();if(!E.isBlock&&h(n.editor,D.block,D.blockLimit)&&(Q=n.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&n.editor.config.autoParagraph!==false?n.editor.activeEnterMode==CKEDITOR.ENTER_DIV?
+"div":"p":false)){Q=y.createElement(Q);Q.appendBogus();t.insertNode(Q);CKEDITOR.env.needsBrFiller&&(J=Q.getBogus())&&J.remove();t.moveToPosition(Q,CKEDITOR.POSITION_BEFORE_END)}if((D=t.startPath().block)&&!D.equals(H)){if(J=D.getBogus()){J.remove();s.push(D)}H=D}E.firstNotAllowed&&(q=1);if(q&&E.isElement){D=t.startContainer;for(M=null;D&&!r[D.getName()][E.name];){if(D.equals(k)){D=null;break}M=D;D=D.getParent()}if(D){if(M){S=t.splitElement(M);n.zombies.push(S);n.zombies.push(M)}}else{M=k.getName();
+T=!B;D=B==x.length-1;M=d(E.node,M);for(var O=[],V=M.length,W=0,Y=void 0,Z=0,U=-1;W<V;W++){Y=M[W];if(Y==" "){if(!Z&&(!T||W)){O.push(new CKEDITOR.dom.text(" "));U=O.length}Z=1}else{O.push(Y);Z=0}}D&&U==O.length&&O.pop();T=O}}if(T){for(;D=T.pop();)t.insertNode(D);T=0}else t.insertNode(E.node);if(E.lastNotAllowed&&B<x.length-1){(S=w?z:S)&&t.setEndAt(S,CKEDITOR.POSITION_AFTER_START);q=0}t.collapse()}}n.dontMoveCaret=G;n.bogusNeededBlocks=s}J=n.range;var N;S=n.bogusNeededBlocks;for(T=J.createBookmark();H=
+n.zombies.pop();)if(H.getParent()){Q=J.clone();Q.moveToElementEditStart(H);Q.removeEmptyBlocksAtEnd()}if(S)for(;H=S.pop();)CKEDITOR.env.needsBrFiller?H.appendBogus():H.append(J.document.createText(" "));for(;H=n.mergeCandidates.pop();)H.mergeSiblings();J.moveToBookmark(T);if(!n.dontMoveCaret){for(H=a(J.startContainer)&&J.startContainer.getChild(J.startOffset-1);H&&a(H)&&!H.is(r.$empty);){if(H.isBlockBoundary())J.moveToPosition(H,CKEDITOR.POSITION_BEFORE_END);else{if(e(H)&&H.getHtml().match(/(\s|&nbsp;)$/g)){N=
+null;break}N=J.clone();N.moveToPosition(H,CKEDITOR.POSITION_BEFORE_END)}H=H.getLast(c)}N&&J.moveToRange(N)}v.select();j(g)}}}(),t=function(){function a(b){b=new CKEDITOR.dom.walker(b);b.guard=function(a,b){if(b)return false;if(a.type==CKEDITOR.NODE_ELEMENT)return a.is(CKEDITOR.dtd.$tableContent)};b.evaluator=function(a){return a.type==CKEDITOR.NODE_ELEMENT};return b}function b(a,c,d){c=a.getDocument().createElement(c);a.append(c,d);return c}function c(a){var b=a.count(),d;for(b;b-- >0;){d=a.getItem(b);
+if(!CKEDITOR.tools.trim(d.getHtml())){d.appendBogus();CKEDITOR.env.ie&&(CKEDITOR.env.version<9&&d.getChildCount())&&d.getFirst().remove()}}}return function(d){var e=d.startContainer,f=e.getAscendant("table",1),h=false;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();if(!f){f=d.clone();f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);f=a(f).lastForward();h=true}f||(f=e);if(f.is("table")){d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START);d.collapse(true);
+f.remove()}else{f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",h));f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",h));(e=f.getBogus())&&e.remove();d.moveToPosition(f,h?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END)}}}()})();
+(function(){function a(){var a=this._.fakeSelection,b;if(a){b=this.getSelection(1);if(!b||!b.isHidden()){a.reset();a=0}}if(!a){a=b||this.getSelection(1);if(!a||a.getType()==CKEDITOR.SELECTION_NONE)return}this.fire("selectionCheck",a);b=this.elementPath();if(!b.compare(this._.selectionPreviousPath)){if(CKEDITOR.env.webkit)this._.previousActive=this.document.getActive();this._.selectionPreviousPath=b;this.fire("selectionChange",{selection:a,path:b})}}function f(){q=true;if(!w){b.call(this);w=CKEDITOR.tools.setTimeout(b,
+200,this)}}function b(){w=null;if(q){CKEDITOR.tools.setTimeout(a,0,this);q=false}}function c(a){return t(a)||a.type==CKEDITOR.NODE_ELEMENT&&!a.is(CKEDITOR.dtd.$empty)?true:false}function e(a){function b(c,d){return!c||c.type==CKEDITOR.NODE_TEXT?false:a.clone()["moveToElementEdit"+(d?"End":"Start")](c)}if(!(a.root instanceof CKEDITOR.editable))return false;var d=a.startContainer,e=a.getPreviousNode(c,null,d),f=a.getNextNode(c,null,d);return b(e)||b(f,1)||!e&&!f&&!(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()&&
+d.getBogus())?true:false}function d(a){return a.getCustomData("cke-fillingChar")}function h(a,b){var c=a&&a.removeCustomData("cke-fillingChar");if(c){if(b!==false){var d,e=a.getDocument().getSelection().getNative(),f=e&&e.type!="None"&&e.getRangeAt(0);if(c.getLength()>1&&f&&f.intersectsNode(c.$)){d=j(e);f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0].offset--;f&&d[1].offset--}}c.setText(k(c.getText()));d&&g(a.getDocument().$,d)}}function k(a){return a.replace(/\u200B( )?/g,
+function(a){return a[1]?" ":""})}function j(a){return[{node:a.anchorNode,offset:a.anchorOffset},{node:a.focusNode,offset:a.focusOffset}]}function g(a,b){var c=a.getSelection(),d=a.createRange();d.setStart(b[0].node,b[0].offset);d.collapse(true);c.removeAllRanges();c.addRange(d);c.extend(b[1].node,b[1].offset)}function m(a){var b=CKEDITOR.dom.element.createFromHtml('<div data-cke-hidden-sel="1" data-cke-temp="1" style="'+(CKEDITOR.env.ie?"display:none":"position:fixed;top:0;left:-1000px")+'">&nbsp;</div>',
+a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(1),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START);d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function y(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];if(e.length==
+1&&f.collapsed)if((d=f[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}}function s(a){for(var b=0;b<a.length;b++){var c=a[b];c.getCommonAncestor().isReadOnly()&&a.splice(b,1);if(!c.collapsed){if(c.startContainer.isReadOnly())for(var d=c.startContainer,e;d;){if((e=d.type==CKEDITOR.NODE_ELEMENT)&&d.is("body")||!d.isReadOnly())break;e&&d.getAttribute("contentEditable")==
+"false"&&c.setStartAfter(d);d=d.getParent()}d=c.startContainer;e=c.endContainer;var f=c.startOffset,h=c.endOffset,g=c.clone();d&&d.type==CKEDITOR.NODE_TEXT&&(f>=d.getLength()?g.setStartAfter(d):g.setStartBefore(d));e&&e.type==CKEDITOR.NODE_TEXT&&(h?g.setEndAfter(e):g.setEndBefore(e));d=new CKEDITOR.dom.walker(g);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);if(!(d.getPosition(g.endContainer)&CKEDITOR.POSITION_CONTAINS)){e.setStartAfter(d);
+e.collapsed||a.splice(b+1,0,e)}return true}return false};d.next()}}return a}var w,q,t=CKEDITOR.dom.walker.invisible(1),i=function(){function a(b){return function(a){var c=a.editor.createRange();c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return false}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!(e=d.moveToClosestEditablePosition(b.selected,a)))e=d.moveToClosestEditablePosition(b.selected,!a);e&&c.getSelection().selectRanges([d]);
+c.fire("saveSnapshot");b.selected.remove();if(!e){d.moveToElementEditablePosition(c.editable());c.getSelection().selectRanges([d])}c.fire("saveSnapshot");return false}}var c=a(),d=a(1);return{37:c,38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function c(){var a=d.getSelection();a&&a.removeAllRanges()}var d=b.editor;d.on("contentDom",function(){function b(){z=new CKEDITOR.dom.selection(d.getSelection());z.lock()}function c(){l.removeListener("mouseup",c);i.removeListener("mouseup",
+c);var a=CKEDITOR.document.$.selection,b=a.createRange();a.type!="None"&&b.parentElement().ownerDocument==e.$&&b.select()}var e=d.document,l=CKEDITOR.document,g=d.editable(),p=e.getBody(),i=e.getDocumentElement(),v=g.isInline(),j,z;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(j!==0)if((a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$){a=d.createRange();a.moveToElementEditStart(g);a.select()}},null,null,-2);g.attachListener(g,CKEDITOR.env.webkit?
+"DOMFocusIn":"focus",function(){j&&CKEDITOR.env.webkit&&(j=d._.previousActive&&d._.previousActive.equals(e.getActive()));d.unlockSelection(j);j=0},null,null,-1);g.attachListener(g,"mousedown",function(){j=0});if(CKEDITOR.env.ie||v){A?g.attachListener(g,"beforedeactivate",b,null,null,-1):g.attachListener(d,"selectionCheck",b,null,null,-1);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusOut":"blur",function(){d.lockSelection(z);j=1},null,null,-1);g.attachListener(g,"mousedown",function(){j=0})}if(CKEDITOR.env.ie&&
+!v){var B;g.attachListener(g,"mousedown",function(a){if(a.data.$.button==2){a=d.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)B=d.window.getScrollPosition()}});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&B){d.document.$.documentElement.scrollLeft=B.x;d.document.$.documentElement.scrollTop=B.y}B=null});if(e.$.compatMode!="BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)i.on("mousedown",function(a){function b(a){a=a.data.$;if(d){var c=p.$.createTextRange();
+try{c.moveToPoint(a.clientX,a.clientY)}catch(e){}d.setEndPoint(f.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);d.select()}}function c(){i.removeListener("mousemove",b);l.removeListener("mouseup",c);i.removeListener("mouseup",c);d.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y<i.$.clientHeight&&a.$.x<i.$.clientWidth){var d=p.$.createTextRange();try{d.moveToPoint(a.$.clientX,a.$.clientY)}catch(e){}var f=d.duplicate();i.on("mousemove",b);l.on("mouseup",c);i.on("mouseup",c)}});
+if(CKEDITOR.env.version>7&&CKEDITOR.env.version<11)i.on("mousedown",function(a){if(a.data.getTarget().is("html")){l.on("mouseup",c);i.on("mouseup",c)}})}}g.attachListener(g,"selectionchange",a,d);g.attachListener(g,"keyup",f,d);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(v&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var x;g.attachListener(g,"mousedown",function(){x=1});g.attachListener(e.getDocumentElement(),"mouseup",
+function(){x&&f.call(d);x=0})}else g.attachListener(CKEDITOR.env.ie?g:e.getDocumentElement(),"mouseup",f,d);CKEDITOR.env.webkit&&g.attachListener(e,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:h(g)}},null,null,-1);g.attachListener(g,"keydown",y(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&c()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy",
+c,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection;delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),b=d.editable().getLast(a);if(b&&b.hasAttribute("data-cke-hidden-sel")){b.remove();if(CKEDITOR.env.gecko)(a=d.editable().getFirst(a))&&(a.is("br")&&a.getAttribute("_moz_editor_bogus_node"))&&a.remove()}},null,null,100);d.on("key",function(a){if(d.mode=="wysiwyg"){var b=d.getSelection();
+if(b.isFake){var c=i[a.data.keyCode];if(c)return c({editor:d,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){function b(){var a=e.editable();if(a)if(a=d(a)){var c=e.document.$.getSelection();if(c.type!="None"&&(c.anchorNode==a.$||c.focusNode==a.$))i=j(c);f=a.getText();a.setText(k(f))}}function c(){var a=e.editable();if(a)if(a=d(a)){a.setText(f);if(i){g(e.document.$,i);i=null}}}var e=a.editor,f,i;if(CKEDITOR.env.webkit){e.on("selectionChange",
+function(){var a=e.editable(),b=d(a);b&&(b.getCustomData("ready")?h(a):b.setCustomData("ready",1))},null,null,-1);e.on("beforeSetMode",function(){h(e.editable())},null,null,-1);e.on("beforeUndoImage",b);e.on("afterUndoImage",c);e.on("beforeGetData",b,null,null,0);e.on("getData",c)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:f).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){if((this._.savedSelection||this._.fakeSelection)&&!a)return this._.savedSelection||this._.fakeSelection;
+return(a=this.editable())&&this.mode=="wysiwyg"?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&&a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection;return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};
+CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var A=typeof window.getSelection!="function",u=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b=
+a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:u++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}var a=this.getNative(),d,e;if(a)if(a.getRangeAt)d=(e=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(e.commonAncestorContainer);else{try{e=a.createRange()}catch(f){}d=e&&CKEDITOR.dom.element.get(e.item&&
+e.item(0)||e.parentElement())}if(!d||!(d.type==CKEDITOR.NODE_ELEMENT||d.type==CKEDITOR.NODE_TEXT)||!this.root.equals(d)&&!this.root.contains(d)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement=null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var o={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype=
+{getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=A?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:A?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache;
+if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&&d.nodeType==1&&c.endOffset-c.startOffset==1&&o[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=A?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c);
+var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,f,g,h=b.duplicate(),v=0,l=e.length-1,i=-1,j,x;v<=l;){i=Math.floor((v+l)/2);f=e[i];h.moveToElementText(f);j=h.compareEndPoints("StartToStart",b);if(j>0)l=i-1;else if(j<0)v=i+1;else return{container:d,offset:a(f)}}if(i==-1||i==e.length-1&&j<0){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT?
+{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;h>0&&d>0;){g=e[--d];if(g.nodeType==CKEDITOR.NODE_TEXT){x=g;h=h-g.nodeValue.length}}return{container:x,offset:-h}}h.collapse(j>0?true:false);h.setEndPoint(j>0?"StartToStart":"EndToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;if(!h)return{container:d,offset:a(f)+(j>0?0:1)};for(;h>0;)try{g=f[j>0?"previousSibling":"nextSibling"];if(g.nodeType==CKEDITOR.NODE_TEXT){h=h-g.nodeValue.length;x=g}f=g}catch(m){return{container:d,
+offset:a(f)}}return{container:x,offset:j>0?-h:x.nodeValue.length+h}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root);d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d==
+CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e<c.length;e++){for(var f=c.item(e),h=f.parentNode,g=0,a=new CKEDITOR.dom.range(this.root);g<h.childNodes.length&&h.childNodes[g]!=f;g++);a.setStart(new CKEDITOR.dom.node(h),g);a.setEnd(new CKEDITOR.dom.node(h),g+1);d.push(a)}return d}return[]}}():function(){var a=[],b,c=this.getNative();if(!c)return a;for(var d=0;d<c.rangeCount;d++){var e=c.getRangeAt(d);b=new CKEDITOR.dom.range(this.root);b.setStart(new CKEDITOR.dom.node(e.startContainer),e.startOffset);
+b.setEnd(new CKEDITOR.dom.node(e.endContainer),e.endOffset);a.push(b)}return a};return function(b){var c=this._.cache,d=c.ranges;if(!d)c.ranges=d=new CKEDITOR.dom.rangeList(a.call(this));return!b?d:s(new CKEDITOR.dom.rangeList(d.slice()))}}(),getStartElement:function(){var a=this._.cache;if(a.startElement!==void 0)return a.startElement;var b;switch(this.getType()){case CKEDITOR.SELECTION_ELEMENT:return this.getSelectedElement();case CKEDITOR.SELECTION_TEXT:var c=this.getRanges()[0];if(c){if(c.collapsed){b=
+c.startContainer;b.type!=CKEDITOR.NODE_ELEMENT&&(b=b.getParent())}else{for(c.optimize();;){b=c.startContainer;if(c.startOffset==(b.getChildCount?b.getChildCount():b.getLength())&&!b.isBlockBoundary())c.setStartAfter(b);else break}b=c.startContainer;if(b.type!=CKEDITOR.NODE_ELEMENT)return b.getParent();b=b.getChild(c.startOffset);if(!b||b.type!=CKEDITOR.NODE_ELEMENT)b=c.startContainer;else for(c=b.getFirst();c&&c.type==CKEDITOR.NODE_ELEMENT;){b=c;c=c.getFirst()}}b=b.$}}return a.startElement=b?new CKEDITOR.dom.element(b):
+null},getSelectedElement:function(){var a=this._.cache;if(a.selectedElement!==void 0)return a.selectedElement;var b=this,c=CKEDITOR.tools.tryThese(function(){return b.getNative().createRange().item(0)},function(){for(var a=b.getRanges()[0].clone(),c,d,e=2;e&&(!(c=a.getEnclosedNode())||!(c.type==CKEDITOR.NODE_ELEMENT&&o[c.getName()]&&(d=c)));e--)a.shrink(CKEDITOR.SHRINK_ELEMENT);return d&&d.$});return a.selectedElement=c?new CKEDITOR.dom.element(c):null},getSelectedText:function(){var a=this._.cache;
+if(a.selectedText!==void 0)return a.selectedText;var b=this.getNative(),b=A?b.type=="Control"?"":b.createRange().text:b.toString();return a.selectedText=b},lock:function(){this.getRanges();this.getStartElement();this.getSelectedElement();this.getSelectedText();this._.cache.nativeSel=null;this.isLocked=1},unlock:function(a){if(this.isLocked){if(a)var b=this.getSelectedElement(),c=!b&&this.getRanges(),d=this.isFake;this.isLocked=0;this.reset();if(a)(a=b||c[0]&&c[0].getCommonAncestor())&&a.getAscendant("body",
+1)&&(d?this.fake(b):b?this.selectElement(b):this.selectRanges(c))}},reset:function(){this._.cache={};this.isFake=0;var a=this.root.editor;if(a&&a._.fakeSelection&&this.rev==a._.fakeSelection.rev){delete a._.fakeSelection;var b=a._.hiddenSelectionContainer;if(b){var c=a.checkDirty();a.fire("lockSnapshot");b.remove();a.fire("unlockSnapshot");!c&&a.resetDirty()}delete a._.hiddenSelectionContainer}this.rev=u++},selectElement:function(a){var b=new CKEDITOR.dom.range(this.root);b.setStartBefore(a);b.setEndAfter(a);
+this.selectRanges([b])},selectRanges:function(a){var b=this.root.editor,b=b&&b._.hiddenSelectionContainer;this.reset();if(b)for(var b=this.root,c,d=0;d<a.length;++d){c=a[d];if(c.endContainer.equals(b))c.endOffset=Math.min(c.endOffset,b.getChildCount())}if(a.length)if(this.isLocked){var f=CKEDITOR.document.getActive();this.unlock();this.selectRanges(a);this.lock();f&&!f.equals(this.root)&&f.focus()}else{var g;a:{var i,j;if(a.length==1&&!(j=a[0]).collapsed&&(g=j.getEnclosedNode())&&g.type==CKEDITOR.NODE_ELEMENT){j=
+j.clone();j.shrink(CKEDITOR.SHRINK_ELEMENT,true);if((i=j.getEnclosedNode())&&i.type==CKEDITOR.NODE_ELEMENT)g=i;if(g.getAttribute("contenteditable")=="false")break a}g=void 0}if(g)this.fake(g);else{if(A){j=CKEDITOR.dom.walker.whitespaces(true);i=/\ufeff|\u00a0/;b={table:1,tbody:1,tr:1};if(a.length>1){g=a[a.length-1];a[0].setEnd(g.endContainer,g.endOffset)}g=a[0];var a=g.collapsed,m,k,v;if((c=g.getEnclosedNode())&&c.type==CKEDITOR.NODE_ELEMENT&&c.getName()in o&&(!c.is("a")||!c.getText()))try{v=c.$.createControlRange();
+v.addElement(c.$);v.select();return}catch(q){}if(g.startContainer.type==CKEDITOR.NODE_ELEMENT&&g.startContainer.getName()in b||g.endContainer.type==CKEDITOR.NODE_ELEMENT&&g.endContainer.getName()in b){g.shrink(CKEDITOR.NODE_ELEMENT,true);a=g.collapsed}v=g.createBookmark();b=v.startNode;if(!a)f=v.endNode;v=g.document.$.body.createTextRange();v.moveToElementText(b.$);v.moveStart("character",1);if(f){i=g.document.$.body.createTextRange();i.moveToElementText(f.$);v.setEndPoint("EndToEnd",i);v.moveEnd("character",
+-1)}else{m=b.getNext(j);k=b.hasAscendant("pre");m=!(m&&m.getText&&m.getText().match(i))&&(k||!b.hasPrevious()||b.getPrevious().is&&b.getPrevious().is("br"));k=g.document.createElement("span");k.setHtml("&#65279;");k.insertBefore(b);m&&g.document.createText("").insertBefore(b)}g.setStartBefore(b);b.remove();if(a){if(m){v.moveStart("character",-1);v.select();g.document.$.selection.clear()}else v.select();g.moveToPosition(k,CKEDITOR.POSITION_BEFORE_START);k.remove()}else{g.setEndBefore(f);f.remove();
+v.select()}}else{f=this.getNative();if(!f)return;this.removeAllRanges();for(v=0;v<a.length;v++){if(v<a.length-1){m=a[v];k=a[v+1];i=m.clone();i.setStart(m.endContainer,m.endOffset);i.setEnd(k.startContainer,k.startOffset);if(!i.collapsed){i.shrink(CKEDITOR.NODE_ELEMENT,true);g=i.getCommonAncestor();i=i.getEnclosedNode();if(g.isReadOnly()||i&&i.isReadOnly()){k.setStart(m.startContainer,m.startOffset);a.splice(v--,1);continue}}}g=a[v];k=this.document.$.createRange();if(g.collapsed&&CKEDITOR.env.webkit&&
+e(g)){m=this.root;h(m,false);i=m.getDocument().createText("​");m.setCustomData("cke-fillingChar",i);g.insertNode(i);if((m=i.getNext())&&!i.getPrevious()&&m.type==CKEDITOR.NODE_ELEMENT&&m.getName()=="br"){h(this.root);g.moveToPosition(m,CKEDITOR.POSITION_BEFORE_START)}else g.moveToPosition(i,CKEDITOR.POSITION_AFTER_END)}k.setStart(g.startContainer.$,g.startOffset);try{k.setEnd(g.endContainer.$,g.endOffset)}catch(z){if(z.toString().indexOf("NS_ERROR_ILLEGAL_VALUE")>=0){g.collapse(1);k.setEnd(g.endContainer.$,
+g.endOffset)}else throw z;}f.addRange(k)}}this.reset();this.root.fire("selectionchange")}}},fake:function(a){var b=this.root.editor;this.reset();m(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root);d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT;c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=u++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor();
+a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a},createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b=[],c=0;c<a.length;c++){var d=new CKEDITOR.dom.range(this.root);d.moveToBookmark(a[c]);b.push(d)}a.isFake?this.fake(b[0].getEnclosedNode()):this.selectRanges(b);return this},
+getCommonAncestor:function(){var a=this.getRanges();return!a.length?null:a[0].startContainer.getCommonAncestor(a[a.length-1].endContainer)},scrollIntoView:function(){this.type!=CKEDITOR.SELECTION_NONE&&this.getRanges()[0].scrollIntoView()},removeAllRanges:function(){if(this.getType()!=CKEDITOR.SELECTION_NONE){var a=this.getNative();try{a&&a[A?"empty":"removeAllRanges"]()}catch(b){}this.reset()}}}})();"use strict";CKEDITOR.STYLE_BLOCK=1;CKEDITOR.STYLE_INLINE=2;CKEDITOR.STYLE_OBJECT=3;
+(function(){function a(a,b){for(var c,d;a=a.getParent();){if(a.equals(b))break;if(a.getAttribute("data-nostyle"))c=a;else if(!d){var e=a.getAttribute("contentEditable");e=="false"?c=a:e=="true"&&(d=1)}}return c}function f(b){var d=b.document;if(b.collapsed){d=i(this,d);b.insertNode(d);b.moveToPosition(d,CKEDITOR.POSITION_BEFORE_END)}else{var e=this.element,g=this._.definition,h,j=g.ignoreReadonly,m=j||g.includeReadonly;m==null&&(m=b.root.getCustomData("cke_includeReadonly"));var k=CKEDITOR.dtd[e];
+if(!k){h=true;k=CKEDITOR.dtd.span}b.enlarge(CKEDITOR.ENLARGE_INLINE,1);b.trim();var l=b.createBookmark(),q=l.startNode,o=l.endNode,n=q,p;if(!j){var s=b.getCommonAncestor(),j=a(q,s),s=a(o,s);j&&(n=j.getNextSourceNode(true));s&&(o=s)}for(n.getPosition(o)==CKEDITOR.POSITION_FOLLOWING&&(n=0);n;){j=false;if(n.equals(o)){n=null;j=true}else{var r=n.type==CKEDITOR.NODE_ELEMENT?n.getName():null,s=r&&n.getAttribute("contentEditable")=="false",t=r&&n.getAttribute("data-nostyle");if(r&&n.data("cke-bookmark")){n=
+n.getNextSourceNode(true);continue}if(s&&m&&CKEDITOR.dtd.$block[r])for(var y=n,u=c(y),A=void 0,C=u.length,F=0,y=C&&new CKEDITOR.dom.range(y.getDocument());F<C;++F){var A=u[F],P=CKEDITOR.filter.instances[A.data("cke-filter")];if(P?P.check(this):1){y.selectNodeContents(A);f.call(this,y)}}u=r?!k[r]||t?0:s&&!m?0:(n.getPosition(o)|K)==K&&(!g.childRule||g.childRule(n)):1;if(u)if((u=n.getParent())&&((u.getDtd()||CKEDITOR.dtd.span)[e]||h)&&(!g.parentRule||g.parentRule(u))){if(!p&&(!r||!CKEDITOR.dtd.$removeEmpty[r]||
+(n.getPosition(o)|K)==K)){p=b.clone();p.setStartBefore(n)}r=n.type;if(r==CKEDITOR.NODE_TEXT||s||r==CKEDITOR.NODE_ELEMENT&&!n.getChildCount()){for(var r=n,U;(j=!r.getNext(L))&&(U=r.getParent(),k[U.getName()])&&(U.getPosition(q)|I)==I&&(!g.childRule||g.childRule(U));)r=U;p.setEndAfter(r)}}else j=true;else j=true;n=n.getNextSourceNode(t||s)}if(j&&p&&!p.collapsed){for(var j=i(this,d),s=j.hasAttributes(),t=p.getCommonAncestor(),r={},u={},A={},C={},N,R,X;j&&t;){if(t.getName()==e){for(N in g.attributes)if(!C[N]&&
+(X=t.getAttribute(R)))j.getAttribute(N)==X?u[N]=1:C[N]=1;for(R in g.styles)if(!A[R]&&(X=t.getStyle(R)))j.getStyle(R)==X?r[R]=1:A[R]=1}t=t.getParent()}for(N in u)j.removeAttribute(N);for(R in r)j.removeStyle(R);s&&!j.hasAttributes()&&(j=null);if(j){p.extractContents().appendTo(j);p.insertNode(j);w.call(this,j);j.mergeSiblings();CKEDITOR.env.ie||j.$.normalize()}else{j=new CKEDITOR.dom.element("span");p.extractContents().appendTo(j);p.insertNode(j);w.call(this,j);j.remove(true)}p=null}}b.moveToBookmark(l);
+b.shrink(CKEDITOR.SHRINK_TEXT);b.shrink(CKEDITOR.NODE_ELEMENT,true)}}function b(a){function b(){for(var a=new CKEDITOR.dom.elementPath(d.getParent()),c=new CKEDITOR.dom.elementPath(j.getParent()),e=null,f=null,g=0;g<a.elements.length;g++){var h=a.elements[g];if(h==a.block||h==a.blockLimit)break;m.checkElementRemovable(h,true)&&(e=h)}for(g=0;g<c.elements.length;g++){h=c.elements[g];if(h==c.block||h==c.blockLimit)break;m.checkElementRemovable(h,true)&&(f=h)}f&&j.breakParent(f);e&&d.breakParent(e)}a.enlarge(CKEDITOR.ENLARGE_INLINE,
+1);var c=a.createBookmark(),d=c.startNode;if(a.collapsed){for(var e=new CKEDITOR.dom.elementPath(d.getParent(),a.root),f,g=0,h;g<e.elements.length&&(h=e.elements[g]);g++){if(h==e.block||h==e.blockLimit)break;if(this.checkElementRemovable(h)){var i;if(a.collapsed&&(a.checkBoundaryOfElement(h,CKEDITOR.END)||(i=a.checkBoundaryOfElement(h,CKEDITOR.START)))){f=h;f.match=i?"start":"end"}else{h.mergeSiblings();h.is(this.element)?s.call(this,h):q(h,o(this)[h.getName()])}}}if(f){h=d;for(g=0;;g++){i=e.elements[g];
+if(i.equals(f))break;else if(i.match)continue;else i=i.clone();i.append(h);h=i}h[f.match=="start"?"insertBefore":"insertAfter"](f)}}else{var j=c.endNode,m=this;b();for(e=d;!e.equals(j);){f=e.getNextSourceNode();if(e.type==CKEDITOR.NODE_ELEMENT&&this.checkElementRemovable(e)){e.getName()==this.element?s.call(this,e):q(e,o(this)[e.getName()]);if(f.type==CKEDITOR.NODE_ELEMENT&&f.contains(d)){b();f=d.getNext()}}e=f}}a.moveToBookmark(c);a.shrink(CKEDITOR.NODE_ELEMENT,true)}function c(a){var b=[];a.forEach(function(a){if(a.getAttribute("contenteditable")==
+"true"){b.push(a);return false}},CKEDITOR.NODE_ELEMENT,true);return b}function e(a){var b=a.getEnclosedNode()||a.getCommonAncestor(false,true);(a=(new CKEDITOR.dom.elementPath(b,a.root)).contains(this.element,1))&&!a.isReadOnly()&&A(a,this)}function d(a){var b=a.getCommonAncestor(true,true);if(a=(new CKEDITOR.dom.elementPath(b,a.root)).contains(this.element,1)){var b=this._.definition,c=b.attributes;if(c)for(var d in c)a.removeAttribute(d,c[d]);if(b.styles)for(var e in b.styles)b.styles.hasOwnProperty(e)&&
+a.removeStyle(e)}}function h(a){var b=a.createBookmark(true),c=a.createIterator();c.enforceRealBlocks=true;if(this._.enterMode)c.enlargeBr=this._.enterMode!=CKEDITOR.ENTER_BR;for(var d,e=a.document,f;d=c.getNextParagraph();)if(!d.isReadOnly()&&(c.activeFilter?c.activeFilter.check(this):1)){f=i(this,e,d);j(d,f)}a.moveToBookmark(b)}function k(a){var b=a.createBookmark(1),c=a.createIterator();c.enforceRealBlocks=true;c.enlargeBr=this._.enterMode!=CKEDITOR.ENTER_BR;for(var d,e;d=c.getNextParagraph();)if(this.checkElementRemovable(d))if(d.is("pre")){(e=
+this._.enterMode==CKEDITOR.ENTER_BR?null:a.document.createElement(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))&&d.copyAttributes(e);j(d,e)}else s.call(this,d);a.moveToBookmark(b)}function j(a,b){var c=!b;if(c){b=a.getDocument().createElement("div");a.copyAttributes(b)}var d=b&&b.is("pre"),e=a.is("pre"),f=!d&&e;if(d&&!e){e=b;(f=a.getBogus())&&f.remove();f=a.getHtml();f=m(f,/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g,"");f=f.replace(/[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+|&nbsp;)/g,
+" ");f=f.replace(/<br\b[^>]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div");h.append(e);e.$.outerHTML="<pre>"+f+"</pre>";e.copyAttributes(h.getFirst());e=h.getFirst().remove()}else e.setHtml(f);b=e}else f?b=y(c?[a.getHtml()]:g(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,i;if((i=c.getPrevious(F))&&i.type==CKEDITOR.NODE_ELEMENT&&i.is("pre")){d=m(i.getHtml(),/\n$/,"")+"\n\n"+m(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="<pre>"+d+"</pre>":c.setHtml(d);i.remove()}}else c&&
+t(b)}function g(a){var b=[];m(a.getOuterHtml(),/(\S\s*)\n(?:\s|(<span[^>]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"</pre>"+c+"<pre>"}).replace(/<pre\b.*?>([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function m(a,b,c){var d="",e="",a=a.replace(/(^<span[^>]+data-cke-bookmark.*?\/span>)|(<span[^>]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function y(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
+for(var d=0;d<a.length;d++){var e=a[d],e=e.replace(/(\r\n|\r)/g,"\n"),e=m(e,/^[ \t]*\n/,""),e=m(e,/\n$/,""),e=m(e,/^[ \t]+|[ \t]+$/g,function(a,b){return a.length==1?"&nbsp;":b?" "+CKEDITOR.tools.repeat("&nbsp;",a.length-1):CKEDITOR.tools.repeat("&nbsp;",a.length-1)+" "}),e=e.replace(/\n/g,"<br>"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat("&nbsp;",a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function s(a,b){var c=this._.definition,
+d=c.attributes,c=c.styles,e=o(this)[a.getName()],f=CKEDITOR.tools.isEmpty(d)&&CKEDITOR.tools.isEmpty(c),g;for(g in d)if(!((g=="class"||this._.definition.fullMatch)&&a.getAttribute(g)!=l(g,d[g]))&&!(b&&g.slice(0,5)=="data-")){f=a.hasAttribute(g);a.removeAttribute(g)}for(var h in c)if(!(this._.definition.fullMatch&&a.getStyle(h)!=l(h,c[h],true))){f=f||!!a.getStyle(h);a.removeStyle(h)}q(a,e,r[a.getName()]);f&&(this._.definition.alwaysRemoveElement?t(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==
+CKEDITOR.ENTER_BR&&!a.hasAttributes()?t(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function w(a){for(var b=o(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||s.call(this,d,true)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||q(d,b[f])}}}function q(a,b,c){if(b=b&&b.attributes)for(var d=0;d<b.length;d++){var e=b[d][0],f;if(f=a.getAttribute(e)){var g=b[d][1];(g===null||
+g.test&&g.test(f)||typeof g=="string"&&f==g)&&a.removeAttribute(e)}}c||t(a)}function t(a,b){if(!a.hasAttributes()||b)if(CKEDITOR.dtd.$block[a.getName()]){var c=a.getPrevious(F),d=a.getNext(F);c&&(c.type==CKEDITOR.NODE_TEXT||!c.isBlockBoundary({br:1}))&&a.append("br",1);d&&(d.type==CKEDITOR.NODE_TEXT||!d.isBlockBoundary({br:1}))&&a.append("br");a.remove(true)}else{c=a.getFirst();d=a.getLast();a.remove(true);if(c){c.type==CKEDITOR.NODE_ELEMENT&&c.mergeSiblings();d&&(!c.equals(d)&&d.type==CKEDITOR.NODE_ELEMENT)&&
+d.mergeSiblings()}}}function i(a,b,c){var d;d=a.element;d=="*"&&(d="span");d=new CKEDITOR.dom.element(d,b);c&&c.copyAttributes(d);d=A(d,a);b.getCustomData("doc_processing_style")&&d.hasAttribute("id")?d.removeAttribute("id"):b.setCustomData("doc_processing_style",1);return d}function A(a,b){var c=b._.definition,d=c.attributes,c=CKEDITOR.style.getStyleText(c);if(d)for(var e in d)a.setAttribute(e,d[e]);c&&a.setAttribute("style",c);return a}function u(a,b){for(var c in a)a[c]=a[c].replace(C,function(a,
+c){return b[c]})}function o(a){if(a._.overrides)return a._.overrides;var b=a._.overrides={},c=a._.definition.overrides;if(c){CKEDITOR.tools.isArray(c)||(c=[c]);for(var d=0;d<c.length;d++){var e=c[d],f,g;if(typeof e=="string")f=e.toLowerCase();else{f=e.element?e.element.toLowerCase():a.element;g=e.attributes}e=b[f]||(b[f]={});if(g){var e=e.attributes=e.attributes||[],h;for(h in g)e.push([h.toLowerCase(),g[h]])}}}return b}function l(a,b,c){var d=new CKEDITOR.dom.element("span");d[c?"setStyle":"setAttribute"](a,
+b);return d[c?"getStyle":"getAttribute"](a)}function p(a,b,c){for(var d=a.document,e=a.getRanges(),b=b?this.removeFromRange:this.applyToRange,f,g=e.createIterator();f=g.getNextRange();)b.call(this,f,c);a.selectRanges(e);d.removeCustomData("doc_processing_style")}var r={address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,section:1,header:1,footer:1,nav:1,article:1,aside:1,figure:1,dialog:1,hgroup:1,time:1,meter:1,menu:1,command:1,keygen:1,output:1,progress:1,details:1,datagrid:1,datalist:1},n=
+{a:1,blockquote:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,th:1,ul:1,dl:1,dt:1,dd:1,form:1,audio:1,video:1},P=/\s*(?:;\s*|$)/,C=/#\((.+?)\)/g,L=CKEDITOR.dom.walker.bookmark(0,1),F=CKEDITOR.dom.walker.whitespaces(1);CKEDITOR.style=function(a,b){if(typeof a.type=="string")return new CKEDITOR.style.customHandlers[a.type](a);var c=a.attributes;if(c&&c.style){a.styles=CKEDITOR.tools.extend({},a.styles,CKEDITOR.tools.parseCssText(c.style));delete c.style}if(b){a=CKEDITOR.tools.clone(a);u(a.attributes,
+b);u(a.styles,b)}c=this.element=a.element?typeof a.element=="string"?a.element.toLowerCase():a.element:"*";this.type=a.type||(r[c]?CKEDITOR.STYLE_BLOCK:n[c]?CKEDITOR.STYLE_OBJECT:CKEDITOR.STYLE_INLINE);if(typeof this.element=="object")this.type=CKEDITOR.STYLE_OBJECT;this._={definition:a}};CKEDITOR.style.prototype={apply:function(a){if(a instanceof CKEDITOR.dom.document)return p.call(this,a.getSelection());if(this.checkApplicable(a.elementPath(),a)){var b=this._.enterMode;if(!b)this._.enterMode=a.activeEnterMode;
+p.call(this,a.getSelection(),0,a);this._.enterMode=b}},remove:function(a){if(a instanceof CKEDITOR.dom.document)return p.call(this,a.getSelection(),1);if(this.checkApplicable(a.elementPath(),a)){var b=this._.enterMode;if(!b)this._.enterMode=a.activeEnterMode;p.call(this,a.getSelection(),1,a);this._.enterMode=b}},applyToRange:function(a){this.applyToRange=this.type==CKEDITOR.STYLE_INLINE?f:this.type==CKEDITOR.STYLE_BLOCK?h:this.type==CKEDITOR.STYLE_OBJECT?e:null;return this.applyToRange(a)},removeFromRange:function(a){this.removeFromRange=
+this.type==CKEDITOR.STYLE_INLINE?b:this.type==CKEDITOR.STYLE_BLOCK?k:this.type==CKEDITOR.STYLE_OBJECT?d:null;return this.removeFromRange(a)},applyToObject:function(a){A(a,this)},checkActive:function(a,b){switch(this.type){case CKEDITOR.STYLE_BLOCK:return this.checkElementRemovable(a.block||a.blockLimit,true,b);case CKEDITOR.STYLE_OBJECT:case CKEDITOR.STYLE_INLINE:for(var c=a.elements,d=0,e;d<c.length;d++){e=c[d];if(!(this.type==CKEDITOR.STYLE_INLINE&&(e==a.block||e==a.blockLimit))){if(this.type==
+CKEDITOR.STYLE_OBJECT){var f=e.getName();if(!(typeof this.element=="string"?f==this.element:f in this.element))continue}if(this.checkElementRemovable(e,true,b))return true}}}return false},checkApplicable:function(a,b,c){b&&b instanceof CKEDITOR.filter&&(c=b);if(c&&!c.check(this))return false;switch(this.type){case CKEDITOR.STYLE_OBJECT:return!!a.contains(this.element);case CKEDITOR.STYLE_BLOCK:return!!a.blockLimit.getDtd()[this.element]}return true},checkElementMatch:function(a,b){var c=this._.definition;
+if(!a||!c.ignoreReadonly&&a.isReadOnly())return false;var d=a.getName();if(typeof this.element=="string"?d==this.element:d in this.element){if(!b&&!a.hasAttributes())return true;if(d=c._AC)c=d;else{var d={},e=0,f=c.attributes;if(f)for(var g in f){e++;d[g]=f[g]}if(g=CKEDITOR.style.getStyleText(c)){d.style||e++;d.style=g}d._length=e;c=c._AC=d}if(c._length){for(var h in c)if(h!="_length"){e=a.getAttribute(h)||"";if(h=="style")a:{d=c[h];typeof d=="string"&&(d=CKEDITOR.tools.parseCssText(d));typeof e==
+"string"&&(e=CKEDITOR.tools.parseCssText(e,true));g=void 0;for(g in d)if(!(g in e&&(e[g]==d[g]||d[g]=="inherit"||e[g]=="inherit"))){d=false;break a}d=true}else d=c[h]==e;if(d){if(!b)return true}else if(b)return false}if(b)return true}else return true}return false},checkElementRemovable:function(a,b,c){if(this.checkElementMatch(a,b,c))return true;if(b=o(this)[a.getName()]){var d;if(!(b=b.attributes))return true;for(c=0;c<b.length;c++){d=b[c][0];if(d=a.getAttribute(d)){var e=b[c][1];if(e===null)return true;
+if(typeof e=="string"){if(d==e)return true}else if(e.test(d))return true}}}return false},buildPreview:function(a){var b=this._.definition,c=[],d=b.element;d=="bdo"&&(d="span");var c=["<",d],e=b.attributes;if(e)for(var f in e)c.push(" ",f,'="',e[f],'"');(e=CKEDITOR.style.getStyleText(b))&&c.push(' style="',e,'"');c.push(">",a||b.name,"</",d,">");return c.join("")},getDefinition:function(){return this._.definition}};CKEDITOR.style.getStyleText=function(a){var b=a._ST;if(b)return b;var b=a.styles,c=
+a.attributes&&a.attributes.style||"",d="";c.length&&(c=c.replace(P,";"));for(var e in b){var f=b[e],g=(e+":"+f).replace(P,";");f=="inherit"?d=d+g:c=c+g}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d};CKEDITOR.style.customHandlers={};CKEDITOR.style.addCustomHandler=function(a){var b=function(a){this._={definition:a};this.setup&&this.setup(a)};b.prototype=CKEDITOR.tools.extend(CKEDITOR.tools.prototypedCopy(CKEDITOR.style.prototype),{assignedTo:CKEDITOR.STYLE_OBJECT},a,true);
+return this.customHandlers[a.type]=b};var K=CKEDITOR.POSITION_PRECEDING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED,I=CKEDITOR.POSITION_FOLLOWING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED})();CKEDITOR.styleCommand=function(a,f){this.requiredContent=this.allowedContent=this.style=a;CKEDITOR.tools.extend(this,f,true)};
+CKEDITOR.styleCommand.prototype.exec=function(a){a.focus();this.state==CKEDITOR.TRISTATE_OFF?a.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&a.removeStyle(this.style)};CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);CKEDITOR.loadStylesSet=function(a,f,b){CKEDITOR.stylesSet.addExternal(a,f,"");CKEDITOR.stylesSet.load(a,b)};
+CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{attachStyleStateChange:function(a,f){var b=this._.styleStateChangeCallbacks;if(!b){b=this._.styleStateChangeCallbacks=[];this.on("selectionChange",function(a){for(var e=0;e<b.length;e++){var d=b[e],f=d.style.checkActive(a.data.path,this)?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF;d.fn.call(this,f)}})}b.push({style:a,fn:f})},applyStyle:function(a){a.apply(this)},removeStyle:function(a){a.remove(this)},getStylesSet:function(a){if(this._.stylesDefinitions)a(this._.stylesDefinitions);
+else{var f=this,b=f.config.stylesCombo_stylesSet||f.config.stylesSet;if(b===false)a(null);else if(b instanceof Array){f._.stylesDefinitions=b;a(b)}else{b||(b="default");var b=b.split(":"),c=b[0];CKEDITOR.stylesSet.addExternal(c,b[1]?b.slice(1).join(":"):CKEDITOR.getUrl("styles.js"),"");CKEDITOR.stylesSet.load(c,function(b){f._.stylesDefinitions=b[c];a(f._.stylesDefinitions)})}}}});
+CKEDITOR.dom.comment=function(a,f){typeof a=="string"&&(a=(f?f.$:document).createComment(a));CKEDITOR.dom.domObject.call(this,a)};CKEDITOR.dom.comment.prototype=new CKEDITOR.dom.node;CKEDITOR.tools.extend(CKEDITOR.dom.comment.prototype,{type:CKEDITOR.NODE_COMMENT,getOuterHtml:function(){return"<\!--"+this.$.nodeValue+"--\>"}});"use strict";
+(function(){var a={},f={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(f[b]=1);CKEDITOR.dom.elementPath=function(b,e){var d=null,h=null,k=[],j=b,g,e=e||b.getDocument().getBody();do if(j.type==CKEDITOR.NODE_ELEMENT){k.push(j);if(!this.lastElement){this.lastElement=j;if(j.is(CKEDITOR.dtd.$object)||j.getAttribute("contenteditable")=="false")continue}if(j.equals(e))break;if(!h){g=j.getName();
+j.getAttribute("contenteditable")=="true"?h=j:!d&&f[g]&&(d=j);if(a[g]){var m;if(m=!d){if(g=g=="div"){a:{g=j.getChildren();m=0;for(var y=g.count();m<y;m++){var s=g.getItem(m);if(s.type==CKEDITOR.NODE_ELEMENT&&CKEDITOR.dtd.$block[s.getName()]){g=true;break a}}g=false}g=!g}m=g}m?d=j:h=j}}}while(j=j.getParent());h||(h=e);this.block=d;this.blockLimit=h;this.root=e;this.elements=k}})();
+CKEDITOR.dom.elementPath.prototype={compare:function(a){var f=this.elements,a=a&&a.elements;if(!a||f.length!=a.length)return false;for(var b=0;b<f.length;b++)if(!f[b].equals(a[b]))return false;return true},contains:function(a,f,b){var c;typeof a=="string"&&(c=function(b){return b.getName()==a});a instanceof CKEDITOR.dom.element?c=function(b){return b.equals(a)}:CKEDITOR.tools.isArray(a)?c=function(b){return CKEDITOR.tools.indexOf(a,b.getName())>-1}:typeof a=="function"?c=a:typeof a=="object"&&(c=
+function(b){return b.getName()in a});var e=this.elements,d=e.length;f&&d--;if(b){e=Array.prototype.slice.call(e,0);e.reverse()}for(f=0;f<d;f++)if(c(e[f]))return e[f];return null},isContextFor:function(a){var f;if(a in CKEDITOR.dtd.$block){f=this.contains(CKEDITOR.dtd.$intermediate)||this.root.equals(this.block)&&this.block||this.blockLimit;return!!f.getDtd()[a]}return true},direction:function(){return(this.block||this.blockLimit||this.root).getDirection(1)}};
+CKEDITOR.dom.text=function(a,f){typeof a=="string"&&(a=(f?f.$:document).createTextNode(a));this.$=a};CKEDITOR.dom.text.prototype=new CKEDITOR.dom.node;
+CKEDITOR.tools.extend(CKEDITOR.dom.text.prototype,{type:CKEDITOR.NODE_TEXT,getLength:function(){return this.$.nodeValue.length},getText:function(){return this.$.nodeValue},setText:function(a){this.$.nodeValue=a},split:function(a){var f=this.$.parentNode,b=f.childNodes.length,c=this.getLength(),e=this.getDocument(),d=new CKEDITOR.dom.text(this.$.splitText(a),e);if(f.childNodes.length==b)if(a>=c){d=e.createText("");d.insertAfter(this)}else{a=e.createText("");a.insertAfter(d);a.remove()}return d},substring:function(a,
+f){return typeof f!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,f)}});
+(function(){function a(a,c,e){var d=a.serializable,f=c[e?"endContainer":"startContainer"],k=e?"endOffset":"startOffset",j=d?c.document.getById(a.startNode):a.startNode,a=d?c.document.getById(a.endNode):a.endNode;if(f.equals(j.getPrevious())){c.startOffset=c.startOffset-f.getLength()-a.getPrevious().getLength();f=a.getNext()}else if(f.equals(a.getPrevious())){c.startOffset=c.startOffset-f.getLength();f=a.getNext()}f.equals(j.getParent())&&c[k]++;f.equals(a.getParent())&&c[k]++;c[e?"endContainer":"startContainer"]=
+f;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,f)};var f={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),e=[],d;return{getNextRange:function(f){d=d===void 0?0:d+1;var k=a[d];if(k&&a.length>1){if(!d)for(var j=a.length-1;j>=0;j--)e.unshift(a[j].createBookmark(true));if(f)for(var g=0;a[d+g+1];){for(var m=k.document,f=0,j=m.getById(e[g].endNode),m=m.getById(e[g+
+1].startNode);;){j=j.getNextSourceNode(false);if(m.equals(j))f=1;else if(c(j)||j.type==CKEDITOR.NODE_ELEMENT&&j.isBlockBoundary())continue;break}if(!f)break;g++}for(k.moveToBookmark(e.shift());g--;){j=a[++d];j.moveToBookmark(e.shift());k.setEnd(j.endContainer,j.endOffset)}}return k}}},createBookmarks:function(b){for(var c=[],e,d=0;d<this.length;d++){c.push(e=this[d].createBookmark(b,true));for(var f=d+1;f<this.length;f++){this[f]=a(e,this[f]);this[f]=a(e,this[f],true)}}return c},createBookmarks2:function(a){for(var c=
+[],e=0;e<this.length;e++)c.push(this[e].createBookmark2(a));return c},moveToBookmarks:function(a){for(var c=0;c<this.length;c++)this[c].moveToBookmark(a[c])}}})();
+(function(){function a(){return CKEDITOR.getUrl(CKEDITOR.skinName.split(",")[1]||"skins/"+CKEDITOR.skinName.split(",")[0]+"/")}function f(b){var c=CKEDITOR.skin["ua_"+b],d=CKEDITOR.env;if(c)for(var c=c.split(",").sort(function(a,b){return a>b?-1:1}),e=0,f;e<c.length;e++){f=c[e];if(d.ie&&(f.replace(/^ie/,"")==d.version||d.quirks&&f=="iequirks"))f="ie";if(d[f]){b=b+("_"+c[e]);break}}return CKEDITOR.getUrl(a()+b+".css")}function b(a,b){if(!d[a]){CKEDITOR.document.appendStyleSheet(f(a));d[a]=1}b&&b()}
+function c(a){var b=a.getById(h);if(!b){b=a.getHead().append("style");b.setAttribute("id",h);b.setAttribute("type","text/css")}return b}function e(a,b,c){var d,e,f;if(CKEDITOR.env.webkit){b=b.split("}").slice(0,-1);for(e=0;e<b.length;e++)b[e]=b[e].split("{")}for(var h=0;h<a.length;h++)if(CKEDITOR.env.webkit)for(e=0;e<b.length;e++){f=b[e][1];for(d=0;d<c.length;d++)f=f.replace(c[d][0],c[d][1]);a[h].$.sheet.addRule(b[e][0],f)}else{f=b;for(d=0;d<c.length;d++)f=f.replace(c[d][0],c[d][1]);CKEDITOR.env.ie&&
+CKEDITOR.env.version<11?a[h].$.styleSheet.cssText=a[h].$.styleSheet.cssText+f:a[h].$.innerHTML=a[h].$.innerHTML+f}}var d={};CKEDITOR.skin={path:a,loadPart:function(c,d){CKEDITOR.skin.name!=CKEDITOR.skinName.split(",")[0]?CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(a()+"skin.js"),function(){b(c,d)}):b(c,d)},getPath:function(a){return CKEDITOR.getUrl(f(a))},icons:{},addIcon:function(a,b,c,d){a=a.toLowerCase();this.icons[a]||(this.icons[a]={path:b,offset:c||0,bgsize:d||"16px"})},getIconStyle:function(a,
+b,c,d,e){var f;if(a){a=a.toLowerCase();b&&(f=this.icons[a+"-rtl"]);f||(f=this.icons[a])}a=c||f&&f.path||"";d=d||f&&f.offset;e=e||f&&f.bgsize||"16px";return a&&"background-image:url("+CKEDITOR.getUrl(a)+");background-position:0 "+d+"px;background-size:"+e+";"}};CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{getUiColor:function(){return this.uiColor},setUiColor:function(a){var b=c(CKEDITOR.document);return(this.setUiColor=function(a){this.uiColor=a;var c=CKEDITOR.skin.chameleon,d="",f="";if(typeof c==
+"function"){d=c(this,"editor");f=c(this,"panel")}a=[[j,a]];e([b],d,a);e(k,f,a)}).call(this,a)}});var h="cke_ui_color",k=[],j=/\$color/g;CKEDITOR.on("instanceLoaded",function(a){if(!CKEDITOR.env.ie||!CKEDITOR.env.quirks){var b=a.editor,a=function(a){a=(a.data[0]||a.data).element.getElementsByTag("iframe").getItem(0).getFrameDocument();if(!a.getById("cke_ui_color")){a=c(a);k.push(a);var d=b.getUiColor();d&&e([a],CKEDITOR.skin.chameleon(b,"panel"),[[j,d]])}};b.on("panelShow",a);b.on("menuShow",a);b.config.uiColor&&
+b.setUiColor(b.config.uiColor)}})})();
+(function(){if(CKEDITOR.env.webkit)CKEDITOR.env.hc=false;else{var a=CKEDITOR.dom.element.createFromHtml('<div style="width:0;height:0;position:absolute;left:-10000px;border:1px solid;border-color:red blue"></div>',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var f=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!!(f&&f==b)}catch(c){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc";
+CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(f=0;f<a.length;f++){CKEDITOR.editor.prototype.constructor.apply(a[f][0],a[f][1]);CKEDITOR.add(a[f][0])}}})();/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.skin.name="moono";CKEDITOR.skin.ua_editor="ie,iequirks,ie7,ie8,gecko";CKEDITOR.skin.ua_dialog="ie,iequirks,ie7,ie8";
+CKEDITOR.skin.chameleon=function(){var b=function(){return function(b,e){for(var a=b.match(/[^#]./g),c=0;3>c;c++){var f=a,h=c,d;d=parseInt(a[c],16);d=("0"+(0>e?0|d*(1+e):0|d+(255-d)*e).toString(16)).slice(-2);f[h]=d}return"#"+a.join("")}}(),c=function(){var b=new CKEDITOR.template("background:#{to};background-image:-webkit-gradient(linear,lefttop,leftbottom,from({from}),to({to}));background-image:-moz-linear-gradient(top,{from},{to});background-image:-webkit-linear-gradient(top,{from},{to});background-image:-o-linear-gradient(top,{from},{to});background-image:-ms-linear-gradient(top,{from},{to});background-image:linear-gradient(top,{from},{to});filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='{from}',endColorstr='{to}');");return function(c,
+a){return b.output({from:c,to:a})}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ {defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_bottom [{defaultGradient}border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [{defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [{defaultGradient}outline-color:{defaultBorder};border-top-color:{defaultBorder};] {id} .cke_dialog_tab [{lightGradient}border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [{mediumGradient}] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} .cke_toolgroup [{lightGradient}border-color:{defaultBorder};] {id} a.cke_button_off:hover, {id} a.cke_button_off:focus, {id} a.cke_button_off:active [{mediumGradient}] {id} .cke_button_on [{ckeButtonOn}] {id} .cke_toolbar_separator [background-color: {ckeToolbarSeparator};] {id} .cke_combo_button [border-color:{defaultBorder};{lightGradient}] {id} a.cke_combo_button:hover, {id} a.cke_combo_button:focus, {id} .cke_combo_on a.cke_combo_button [border-color:{defaultBorder};{mediumGradient}] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover, {id} a.cke_path_item:focus, {id} a.cke_path_item:active [background-color:{elementsPathBg};] {id}.cke_panel [border-color:{defaultBorder};] "),
+panel:new CKEDITOR.template(".cke_panel_grouptitle [{lightGradient}border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:focus.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:focus.cke_colorauto, a:focus.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")};
+return function(g,e){var a=g.uiColor,a={id:"."+g.id,defaultBorder:b(a,-0.1),defaultGradient:c(b(a,0.9),a),lightGradient:c(b(a,1),b(a,0.7)),mediumGradient:c(b(a,0.8),b(a,0.5)),ckeButtonOn:c(b(a,0.6),b(a,0.7)),ckeResizer:b(a,-0.4),ckeToolbarSeparator:b(a,0.5),ckeColorauto:b(a,0.8),dialogBody:b(a,0.7),dialogTabSelected:c("#FFFFFF","#FFFFFF"),dialogTabSelectedBorder:"#FFF",elementsPathColor:b(a,-0.6),elementsPathBg:a,menubuttonIcon:b(a,0.5),menubuttonIconHover:b(a,0.3)};return f[e].output(a).replace(/\[/g,
+"{").replace(/\]/g,"}")}}();CKEDITOR.plugins.add("dialogui",{onLoad:function(){var h=function(b){this._||(this._={});this._["default"]=this._.initValue=b["default"]||"";this._.required=b.required||!1;for(var a=[this._],d=1;d<arguments.length;d++)a.push(arguments[d]);a.push(!0);CKEDITOR.tools.extend.apply(CKEDITOR.tools,a);return this._},r={build:function(b,a,d){return new CKEDITOR.ui.dialog.textInput(b,a,d)}},l={build:function(b,a,d){return new CKEDITOR.ui.dialog[a.type](b,a,d)}},n={isChanged:function(){return this.getValue()!=
+this.getInitValue()},reset:function(b){this.setValue(this.getInitValue(),b)},setInitValue:function(){this._.initValue=this.getValue()},resetInitValue:function(){this._.initValue=this._["default"]},getInitValue:function(){return this._.initValue}},o=CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onChange:function(b,a){this._.domOnChangeRegistered||(b.on("load",function(){this.getInputElement().on("change",function(){b.parts.dialog.isVisible()&&this.fire("change",{value:this.getValue()})},
+this)},this),this._.domOnChangeRegistered=!0);this.on("change",a)}},!0),s=/^on([A-Z]\w+)/,p=function(b){for(var a in b)(s.test(a)||"title"==a||"type"==a)&&delete b[a];return b};CKEDITOR.tools.extend(CKEDITOR.ui.dialog,{labeledElement:function(b,a,d,f){if(!(4>arguments.length)){var c=h.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+"_label";this._.children=[];var e={role:a.role||"presentation"};a.includeLabel&&(e["aria-labelledby"]=c.labelId);CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null,
+e,function(){var e=[],g=a.required?" cke_required":"";if(a.labelLayout!="horizontal")e.push('<label class="cke_dialog_ui_labeled_label'+g+'" ',' id="'+c.labelId+'"',c.inputId?' for="'+c.inputId+'"':"",(a.labelStyle?' style="'+a.labelStyle+'"':"")+">",a.label,"</label>",'<div class="cke_dialog_ui_labeled_content"',a.controlStyle?' style="'+a.controlStyle+'"':"",' role="presentation">',f.call(this,b,a),"</div>");else{g={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'<label class="cke_dialog_ui_labeled_label'+
+g+'" id="'+c.labelId+'" for="'+c.inputId+'"'+(a.labelStyle?' style="'+a.labelStyle+'"':"")+">"+CKEDITOR.tools.htmlEncode(a.label)+"</span>"},{type:"html",html:'<span class="cke_dialog_ui_labeled_content"'+(a.controlStyle?' style="'+a.controlStyle+'"':"")+">"+f.call(this,b,a)+"</span>"}]};CKEDITOR.dialog._.uiElementBuilders.hbox.build(b,g,e)}return e.join("")})}},textInput:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);var f=this._.inputId=CKEDITOR.tools.getNextId()+"_textInput",c={"class":"cke_dialog_ui_input_"+
+a.type,id:f,type:a.type};a.validate&&(this.validate=a.validate);a.maxLength&&(c.maxlength=a.maxLength);a.size&&(c.size=a.size);a.inputStyle&&(c.style=a.inputStyle);var e=this,k=!1;b.on("load",function(){e.getInputElement().on("keydown",function(a){a.data.getKeystroke()==13&&(k=true)});e.getInputElement().on("keyup",function(a){if(a.data.getKeystroke()==13&&k){b.getButton("ok")&&setTimeout(function(){b.getButton("ok").click()},0);k=false}},null,null,1E3)});CKEDITOR.ui.dialog.labeledElement.call(this,
+b,a,d,function(){var b=['<div class="cke_dialog_ui_input_',a.type,'" role="presentation"'];a.width&&b.push('style="width:'+a.width+'" ');b.push("><input ");c["aria-labelledby"]=this._.labelId;this._.required&&(c["aria-required"]=this._.required);for(var e in c)b.push(e+'="'+c[e]+'" ');b.push(" /></div>");return b.join("")})}},textarea:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);var f=this,c=this._.inputId=CKEDITOR.tools.getNextId()+"_textarea",e={};a.validate&&(this.validate=a.validate);
+e.rows=a.rows||5;e.cols=a.cols||20;e["class"]="cke_dialog_ui_input_textarea "+(a["class"]||"");"undefined"!=typeof a.inputStyle&&(e.style=a.inputStyle);a.dir&&(e.dir=a.dir);CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){e["aria-labelledby"]=this._.labelId;this._.required&&(e["aria-required"]=this._.required);var a=['<div class="cke_dialog_ui_input_textarea" role="presentation"><textarea id="',c,'" '],b;for(b in e)a.push(b+'="'+CKEDITOR.tools.htmlEncode(e[b])+'" ');a.push(">",CKEDITOR.tools.htmlEncode(f._["default"]),
+"</textarea></div>");return a.join("")})}},checkbox:function(b,a,d){if(!(3>arguments.length)){var f=h.call(this,a,{"default":!!a["default"]});a.validate&&(this.validate=a.validate);CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"span",null,null,function(){var c=CKEDITOR.tools.extend({},a,{id:a.id?a.id+"_checkbox":CKEDITOR.tools.getNextId()+"_checkbox"},true),e=[],d=CKEDITOR.tools.getNextId()+"_label",g={"class":"cke_dialog_ui_checkbox_input",type:"checkbox","aria-labelledby":d};p(c);if(a["default"])g.checked=
+"checked";if(typeof c.inputStyle!="undefined")c.style=c.inputStyle;f.checkbox=new CKEDITOR.ui.dialog.uiElement(b,c,e,"input",null,g);e.push(' <label id="',d,'" for="',g.id,'"'+(a.labelStyle?' style="'+a.labelStyle+'"':"")+">",CKEDITOR.tools.htmlEncode(a.label),"</label>");return e.join("")})}},radio:function(b,a,d){if(!(3>arguments.length)){h.call(this,a);this._["default"]||(this._["default"]=this._.initValue=a.items[0][1]);a.validate&&(this.validate=a.valdiate);var f=[],c=this;a.role="radiogroup";
+a.includeLabel=!0;CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){for(var e=[],d=[],g=(a.id?a.id:CKEDITOR.tools.getNextId())+"_radio",i=0;i<a.items.length;i++){var j=a.items[i],h=j[2]!==void 0?j[2]:j[0],l=j[1]!==void 0?j[1]:j[0],m=CKEDITOR.tools.getNextId()+"_radio_input",n=m+"_label",m=CKEDITOR.tools.extend({},a,{id:m,title:null,type:null},true),h=CKEDITOR.tools.extend({},m,{title:h},true),o={type:"radio","class":"cke_dialog_ui_radio_input",name:g,value:l,"aria-labelledby":n},q=[];if(c._["default"]==
+l)o.checked="checked";p(m);p(h);if(typeof m.inputStyle!="undefined")m.style=m.inputStyle;m.keyboardFocusable=true;f.push(new CKEDITOR.ui.dialog.uiElement(b,m,q,"input",null,o));q.push(" ");new CKEDITOR.ui.dialog.uiElement(b,h,q,"label",null,{id:n,"for":o.id},j[0]);e.push(q.join(""))}new CKEDITOR.ui.dialog.hbox(b,f,e,d);return d.join("")});this._.children=f}},button:function(b,a,d){if(arguments.length){"function"==typeof a&&(a=a(b.getParentEditor()));h.call(this,a,{disabled:a.disabled||!1});CKEDITOR.event.implementOn(this);
+var f=this;b.on("load",function(){var a=this.getElement();(function(){a.on("click",function(a){f.click();a.data.preventDefault()});a.on("keydown",function(a){a.data.getKeystroke()in{32:1}&&(f.click(),a.data.preventDefault())})})();a.unselectable()},this);var c=CKEDITOR.tools.extend({},a);delete c.style;var e=CKEDITOR.tools.getNextId()+"_label";CKEDITOR.ui.dialog.uiElement.call(this,b,c,d,"a",null,{style:a.style,href:"javascript:void(0)",title:a.label,hidefocus:"true","class":a["class"],role:"button",
+"aria-labelledby":e},'<span id="'+e+'" class="cke_dialog_ui_button">'+CKEDITOR.tools.htmlEncode(a.label)+"</span>")}},select:function(b,a,d){if(!(3>arguments.length)){var f=h.call(this,a);a.validate&&(this.validate=a.validate);f.inputId=CKEDITOR.tools.getNextId()+"_select";CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){var c=CKEDITOR.tools.extend({},a,{id:a.id?a.id+"_select":CKEDITOR.tools.getNextId()+"_select"},true),e=[],d=[],g={id:f.inputId,"class":"cke_dialog_ui_input_select","aria-labelledby":this._.labelId};
+e.push('<div class="cke_dialog_ui_input_',a.type,'" role="presentation"');a.width&&e.push('style="width:'+a.width+'" ');e.push(">");if(a.size!==void 0)g.size=a.size;if(a.multiple!==void 0)g.multiple=a.multiple;p(c);for(var i=0,j;i<a.items.length&&(j=a.items[i]);i++)d.push('<option value="',CKEDITOR.tools.htmlEncode(j[1]!==void 0?j[1]:j[0]).replace(/"/g,"&quot;"),'" /> ',CKEDITOR.tools.htmlEncode(j[0]));if(typeof c.inputStyle!="undefined")c.style=c.inputStyle;f.select=new CKEDITOR.ui.dialog.uiElement(b,
+c,e,"select",null,g,d.join(""));e.push("</div>");return e.join("")})}},file:function(b,a,d){if(!(3>arguments.length)){void 0===a["default"]&&(a["default"]="");var f=CKEDITOR.tools.extend(h.call(this,a),{definition:a,buttons:[]});a.validate&&(this.validate=a.validate);b.on("load",function(){CKEDITOR.document.getById(f.frameId).getParent().addClass("cke_dialog_ui_input_file")});CKEDITOR.ui.dialog.labeledElement.call(this,b,a,d,function(){f.frameId=CKEDITOR.tools.getNextId()+"_fileInput";var b=['<iframe frameborder="0" allowtransparency="0" class="cke_dialog_ui_input_file" role="presentation" id="',
+f.frameId,'" title="',a.label,'" src="javascript:void('];b.push(CKEDITOR.env.ie?"(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"})()":"0");b.push(')"></iframe>');return b.join("")})}},fileButton:function(b,a,d){var f=this;if(!(3>arguments.length)){h.call(this,a);a.validate&&(this.validate=a.validate);var c=CKEDITOR.tools.extend({},a),e=c.onClick;c.className=(c.className?c.className+" ":"")+"cke_dialog_ui_button";c.onClick=function(c){var d=
+a["for"];if(!e||e.call(this,c)!==false){b.getContentElement(d[0],d[1]).submit();this.disable()}};b.on("load",function(){b.getContentElement(a["for"][0],a["for"][1])._.buttons.push(f)});CKEDITOR.ui.dialog.button.call(this,b,c,d)}},html:function(){var b=/^\s*<[\w:]+\s+([^>]*)?>/,a=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,d=/\/$/;return function(f,c,e){if(!(3>arguments.length)){var k=[],g=c.html;"<"!=g.charAt(0)&&(g="<span>"+g+"</span>");var i=c.focus;if(i){var j=this.focus;this.focus=function(){("function"==
+typeof i?i:j).call(this);this.fire("focus")};c.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,f,c,k,"span",null,null,"");k=k.join("").match(b);g=g.match(a)||["","",""];d.test(g[1])&&(g[1]=g[1].slice(0,-1),g[2]="/"+g[2]);e.push([g[1]," ",k[1]||"",g[2]].join(""))}}}(),fieldset:function(b,a,d,f,c){var e=c.label;this._={children:a};CKEDITOR.ui.dialog.uiElement.call(this,b,c,f,"fieldset",null,null,function(){var a=[];e&&a.push("<legend"+
+(c.labelStyle?' style="'+c.labelStyle+'"':"")+">"+e+"</legend>");for(var b=0;b<d.length;b++)a.push(d[b]);return a.join("")})}},!0);CKEDITOR.ui.dialog.html.prototype=new CKEDITOR.ui.dialog.uiElement;CKEDITOR.ui.dialog.labeledElement.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{setLabel:function(b){var a=CKEDITOR.document.getById(this._.labelId);1>a.getChildCount()?(new CKEDITOR.dom.text(b,CKEDITOR.document)).appendTo(a):a.getChild(0).$.nodeValue=b;return this},getLabel:function(){var b=
+CKEDITOR.document.getById(this._.labelId);return!b||1>b.getChildCount()?"":b.getChild(0).getText()},eventProcessors:o},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return!this._.disabled?this.fire("click",{dialog:this._.dialog}):!1},enable:function(){this._.disabled=!1;var b=this.getElement();b&&b.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")},isVisible:function(){return this.getElement().getFirst().isVisible()},
+isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(b,a){this.on("click",function(){a.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)},
+focus:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&a.$.focus()},0)},select:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&(a.$.focus(),a.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(b){!b&&(b="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)},keyboardFocusable:!0},n,!0);CKEDITOR.ui.dialog.textarea.prototype=new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=
+CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(b,a,d){var f=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),c=this.getInputElement().$;f.$.text=b;f.$.value=void 0===a||null===a?b:a;void 0===d||null===d?CKEDITOR.env.ie?c.add(f.$):c.add(f.$,null):c.add(f.$,d);return this},remove:function(b){this.getInputElement().$.remove(b);return this},clear:function(){for(var b=this.getInputElement().$;0<
+b.length;)b.remove(0);return this},keyboardFocusable:!0},n,!0);CKEDITOR.ui.dialog.checkbox.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{getInputElement:function(){return this._.checkbox.getElement()},setValue:function(b,a){this.getInputElement().$.checked=b;!a&&this.fire("change",{value:b})},getValue:function(){return this.getInputElement().$.checked},accessKeyUp:function(){this.setValue(!this.getValue())},eventProcessors:{onChange:function(b,a){if(!CKEDITOR.env.ie||8<CKEDITOR.env.version)return o.onChange.apply(this,
+arguments);b.on("load",function(){var a=this._.checkbox.getElement();a.on("propertychange",function(b){b=b.data.$;"checked"==b.propertyName&&this.fire("change",{value:a.$.checked})},this)},this);this.on("change",a);return null}},keyboardFocusable:!0},n,!0);CKEDITOR.ui.dialog.radio.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{setValue:function(b,a){for(var d=this._.children,f,c=0;c<d.length&&(f=d[c]);c++)f.getElement().$.checked=f.getValue()==b;!a&&this.fire("change",{value:b})},
+getValue:function(){for(var b=this._.children,a=0;a<b.length;a++)if(b[a].getElement().$.checked)return b[a].getValue();return null},accessKeyUp:function(){var b=this._.children,a;for(a=0;a<b.length;a++)if(b[a].getElement().$.checked){b[a].getElement().focus();return}b[0].getElement().focus()},eventProcessors:{onChange:function(b,a){if(CKEDITOR.env.ie)b.on("load",function(){for(var a=this._.children,b=this,c=0;c<a.length;c++)a[c].getElement().on("propertychange",function(a){a=a.data.$;"checked"==a.propertyName&&
+this.$.checked&&b.fire("change",{value:this.getAttribute("value")})})},this),this.on("change",a);else return o.onChange.apply(this,arguments);return null}}},n,!0);CKEDITOR.ui.dialog.file.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,n,{getInputElement:function(){var b=CKEDITOR.document.getById(this._.frameId).getFrameDocument();return 0<b.$.forms.length?new CKEDITOR.dom.element(b.$.forms[0].elements[0]):this.getElement()},submit:function(){this.getInputElement().getParent().$.submit();
+return this},getAction:function(){return this.getInputElement().getParent().$.action},registerEvents:function(b){var a=/^on([A-Z]\w+)/,d,f=function(a,b,c,d){a.on("formLoaded",function(){a.getInputElement().on(c,d,a)})},c;for(c in b)if(d=c.match(a))this.eventProcessors[c]?this.eventProcessors[c].call(this,this._.dialog,b[c]):f(this,this._.dialog,d[1].toLowerCase(),b[c]);return this},reset:function(){function b(){d.$.open();var b="";f.size&&(b=f.size-(CKEDITOR.env.ie?7:0));var h=a.frameId+"_input";
+d.$.write(['<html dir="'+g+'" lang="'+i+'"><head><title></title></head><body style="margin: 0; overflow: hidden; background: transparent;">','<form enctype="multipart/form-data" method="POST" dir="'+g+'" lang="'+i+'" action="',CKEDITOR.tools.htmlEncode(f.action),'"><label id="',a.labelId,'" for="',h,'" style="display:none">',CKEDITOR.tools.htmlEncode(f.label),'</label><input style="width:100%" id="',h,'" aria-labelledby="',a.labelId,'" type="file" name="',CKEDITOR.tools.htmlEncode(f.id||"cke_upload"),
+'" size="',CKEDITOR.tools.htmlEncode(0<b?b:""),'" /></form></body></html><script>',CKEDITOR.env.ie?"("+CKEDITOR.tools.fixDomain+")();":"","window.parent.CKEDITOR.tools.callFunction("+e+");","window.onbeforeunload = function() {window.parent.CKEDITOR.tools.callFunction("+k+")}","<\/script>"].join(""));d.$.close();for(b=0;b<c.length;b++)c[b].enable()}var a=this._,d=CKEDITOR.document.getById(a.frameId).getFrameDocument(),f=a.definition,c=a.buttons,e=this.formLoadedNumber,k=this.formUnloadNumber,g=a.dialog._.editor.lang.dir,
+i=a.dialog._.editor.langCode;e||(e=this.formLoadedNumber=CKEDITOR.tools.addFunction(function(){this.fire("formLoaded")},this),k=this.formUnloadNumber=CKEDITOR.tools.addFunction(function(){this.getInputElement().clearCustomData()},this),this.getDialog()._.editor.on("destroy",function(){CKEDITOR.tools.removeFunction(e);CKEDITOR.tools.removeFunction(k)}));CKEDITOR.env.gecko?setTimeout(b,500):b()},getValue:function(){return this.getInputElement().$.value||""},setInitValue:function(){this._.initValue=
+""},eventProcessors:{onChange:function(b,a){this._.domOnChangeRegistered||(this.on("formLoaded",function(){this.getInputElement().on("change",function(){this.fire("change",{value:this.getValue()})},this)},this),this._.domOnChangeRegistered=!0);this.on("change",a)}},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.fileButton.prototype=new CKEDITOR.ui.dialog.button;CKEDITOR.ui.dialog.fieldset.prototype=CKEDITOR.tools.clone(CKEDITOR.ui.dialog.hbox.prototype);CKEDITOR.dialog.addUIElement("text",r);CKEDITOR.dialog.addUIElement("password",
+r);CKEDITOR.dialog.addUIElement("textarea",l);CKEDITOR.dialog.addUIElement("checkbox",l);CKEDITOR.dialog.addUIElement("radio",l);CKEDITOR.dialog.addUIElement("button",l);CKEDITOR.dialog.addUIElement("select",l);CKEDITOR.dialog.addUIElement("file",l);CKEDITOR.dialog.addUIElement("fileButton",l);CKEDITOR.dialog.addUIElement("html",l);CKEDITOR.dialog.addUIElement("fieldset",{build:function(b,a,d){for(var f=a.children,c,e=[],h=[],g=0;g<f.length&&(c=f[g]);g++){var i=[];e.push(i);h.push(CKEDITOR.dialog._.uiElementBuilders[c.type].build(b,
+c,i))}return new CKEDITOR.ui.dialog[a.type](b,h,e,d,a)}})}});CKEDITOR.DIALOG_RESIZE_NONE=0;CKEDITOR.DIALOG_RESIZE_WIDTH=1;CKEDITOR.DIALOG_RESIZE_HEIGHT=2;CKEDITOR.DIALOG_RESIZE_BOTH=3;
+(function(){function t(){for(var a=this._.tabIdList.length,b=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId)+a,c=b-1;c>b-a;c--)if(this._.tabs[this._.tabIdList[c%a]][0].$.offsetHeight)return this._.tabIdList[c%a];return null}function u(){for(var a=this._.tabIdList.length,b=CKEDITOR.tools.indexOf(this._.tabIdList,this._.currentTabId),c=b+1;c<b+a;c++)if(this._.tabs[this._.tabIdList[c%a]][0].$.offsetHeight)return this._.tabIdList[c%a];return null}function G(a,b){for(var c=a.$.getElementsByTagName("input"),
+e=0,d=c.length;e<d;e++){var g=new CKEDITOR.dom.element(c[e]);"text"==g.getAttribute("type").toLowerCase()&&(b?(g.setAttribute("value",g.getCustomData("fake_value")||""),g.removeCustomData("fake_value")):(g.setCustomData("fake_value",g.getAttribute("value")),g.setAttribute("value","")))}}function P(a,b){var c=this.getInputElement();c&&(a?c.removeAttribute("aria-invalid"):c.setAttribute("aria-invalid",!0));a||(this.select?this.select():this.focus());b&&alert(b);this.fire("validated",{valid:a,msg:b})}
+function Q(){var a=this.getInputElement();a&&a.removeAttribute("aria-invalid")}function R(a){var a=CKEDITOR.dom.element.createFromHtml(CKEDITOR.addTemplate("dialog",S).output({id:CKEDITOR.tools.getNextNumber(),editorId:a.id,langDir:a.lang.dir,langCode:a.langCode,editorDialogClass:"cke_editor_"+a.name.replace(/\./g,"\\.")+"_dialog",closeTitle:a.lang.common.close,hidpi:CKEDITOR.env.hidpi?"cke_hidpi":""})),b=a.getChild([0,0,0,0,0]),c=b.getChild(0),e=b.getChild(1);if(CKEDITOR.env.ie&&!CKEDITOR.env.quirks){var d=
+"javascript:void(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"}())";CKEDITOR.dom.element.createFromHtml('<iframe frameBorder="0" class="cke_iframe_shim" src="'+d+'" tabIndex="-1"></iframe>').appendTo(b.getParent())}c.unselectable();e.unselectable();return{element:a,parts:{dialog:a.getChild(0),title:c,close:e,tabs:b.getChild(2),contents:b.getChild([3,0,0,0]),footer:b.getChild([3,0,1,0])}}}function H(a,b,c){this.element=b;this.focusIndex=c;this.tabIndex=
+0;this.isFocusable=function(){return!b.getAttribute("disabled")&&b.isVisible()};this.focus=function(){a._.currentFocusIndex=this.focusIndex;this.element.focus()};b.on("keydown",function(a){a.data.getKeystroke()in{32:1,13:1}&&this.fire("click")});b.on("focus",function(){this.fire("mouseover")});b.on("blur",function(){this.fire("mouseout")})}function T(a){function b(){a.layout()}var c=CKEDITOR.document.getWindow();c.on("resize",b);a.on("hide",function(){c.removeListener("resize",b)})}function I(a,b){this._=
+{dialog:a};CKEDITOR.tools.extend(this,b)}function U(a){function b(b){var c=a.getSize(),i=CKEDITOR.document.getWindow().getViewPaneSize(),o=b.data.$.screenX,j=b.data.$.screenY,n=o-e.x,l=j-e.y;e={x:o,y:j};d.x+=n;d.y+=l;a.move(d.x+h[3]<f?-h[3]:d.x-h[1]>i.width-c.width-f?i.width-c.width+("rtl"==g.lang.dir?0:h[1]):d.x,d.y+h[0]<f?-h[0]:d.y-h[2]>i.height-c.height-f?i.height-c.height+h[2]:d.y,1);b.data.preventDefault()}function c(){CKEDITOR.document.removeListener("mousemove",b);CKEDITOR.document.removeListener("mouseup",
+c);if(CKEDITOR.env.ie6Compat){var a=q.getChild(0).getFrameDocument();a.removeListener("mousemove",b);a.removeListener("mouseup",c)}}var e=null,d=null,g=a.getParentEditor(),f=g.config.dialog_magnetDistance,h=CKEDITOR.skin.margins||[0,0,0,0];"undefined"==typeof f&&(f=20);a.parts.title.on("mousedown",function(f){e={x:f.data.$.screenX,y:f.data.$.screenY};CKEDITOR.document.on("mousemove",b);CKEDITOR.document.on("mouseup",c);d=a.getPosition();if(CKEDITOR.env.ie6Compat){var h=q.getChild(0).getFrameDocument();
+h.on("mousemove",b);h.on("mouseup",c)}f.data.preventDefault()},a)}function V(a){var b,c;function e(d){var e="rtl"==h.lang.dir,j=o.width,C=o.height,D=j+(d.data.$.screenX-b)*(e?-1:1)*(a._.moved?1:2),n=C+(d.data.$.screenY-c)*(a._.moved?1:2),x=a._.element.getFirst(),x=e&&x.getComputedStyle("right"),y=a.getPosition();y.y+n>i.height&&(n=i.height-y.y);if((e?x:y.x)+D>i.width)D=i.width-(e?x:y.x);if(f==CKEDITOR.DIALOG_RESIZE_WIDTH||f==CKEDITOR.DIALOG_RESIZE_BOTH)j=Math.max(g.minWidth||0,D-m);if(f==CKEDITOR.DIALOG_RESIZE_HEIGHT||
+f==CKEDITOR.DIALOG_RESIZE_BOTH)C=Math.max(g.minHeight||0,n-k);a.resize(j,C);a._.moved||a.layout();d.data.preventDefault()}function d(){CKEDITOR.document.removeListener("mouseup",d);CKEDITOR.document.removeListener("mousemove",e);j&&(j.remove(),j=null);if(CKEDITOR.env.ie6Compat){var a=q.getChild(0).getFrameDocument();a.removeListener("mouseup",d);a.removeListener("mousemove",e)}}var g=a.definition,f=g.resizable;if(f!=CKEDITOR.DIALOG_RESIZE_NONE){var h=a.getParentEditor(),m,k,i,o,j,n=CKEDITOR.tools.addFunction(function(f){o=
+a.getSize();var h=a.parts.contents;h.$.getElementsByTagName("iframe").length&&(j=CKEDITOR.dom.element.createFromHtml('<div class="cke_dialog_resize_cover" style="height: 100%; position: absolute; width: 100%;"></div>'),h.append(j));k=o.height-a.parts.contents.getSize("height",!(CKEDITOR.env.gecko||CKEDITOR.env.ie&&CKEDITOR.env.quirks));m=o.width-a.parts.contents.getSize("width",1);b=f.screenX;c=f.screenY;i=CKEDITOR.document.getWindow().getViewPaneSize();CKEDITOR.document.on("mousemove",e);CKEDITOR.document.on("mouseup",
+d);CKEDITOR.env.ie6Compat&&(h=q.getChild(0).getFrameDocument(),h.on("mousemove",e),h.on("mouseup",d));f.preventDefault&&f.preventDefault()});a.on("load",function(){var b="";f==CKEDITOR.DIALOG_RESIZE_WIDTH?b=" cke_resizer_horizontal":f==CKEDITOR.DIALOG_RESIZE_HEIGHT&&(b=" cke_resizer_vertical");b=CKEDITOR.dom.element.createFromHtml('<div class="cke_resizer'+b+" cke_resizer_"+h.lang.dir+'" title="'+CKEDITOR.tools.htmlEncode(h.lang.common.resize)+'" onmousedown="CKEDITOR.tools.callFunction('+n+', event )">'+
+("ltr"==h.lang.dir?"â—¢":"â—£")+"</div>");a.parts.footer.append(b,1)});h.on("destroy",function(){CKEDITOR.tools.removeFunction(n)})}}function E(a){a.data.preventDefault(1)}function J(a){var b=CKEDITOR.document.getWindow(),c=a.config,e=c.dialog_backgroundCoverColor||"white",d=c.dialog_backgroundCoverOpacity,g=c.baseFloatZIndex,c=CKEDITOR.tools.genKey(e,d,g),f=w[c];f?f.show():(g=['<div tabIndex="-1" style="position: ',CKEDITOR.env.ie6Compat?"absolute":"fixed","; z-index: ",g,"; top: 0px; left: 0px; ",!CKEDITOR.env.ie6Compat?
+"background-color: "+e:"",'" class="cke_dialog_background_cover">'],CKEDITOR.env.ie6Compat&&(e="<html><body style=\\'background-color:"+e+";\\'></body></html>",g.push('<iframe hidefocus="true" frameborder="0" id="cke_dialog_background_iframe" src="javascript:'),g.push("void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.write( '"+e+"' );document.close();")+"})())"),g.push('" style="position:absolute;left:0;top:0;width:100%;height: 100%;filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0)"></iframe>')),
+g.push("</div>"),f=CKEDITOR.dom.element.createFromHtml(g.join("")),f.setOpacity(void 0!==d?d:0.5),f.on("keydown",E),f.on("keypress",E),f.on("keyup",E),f.appendTo(CKEDITOR.document.getBody()),w[c]=f);a.focusManager.add(f);q=f;var a=function(){var a=b.getViewPaneSize();f.setStyles({width:a.width+"px",height:a.height+"px"})},h=function(){var a=b.getScrollPosition(),c=CKEDITOR.dialog._.currentTop;f.setStyles({left:a.x+"px",top:a.y+"px"});if(c){do{a=c.getPosition();c.move(a.x,a.y)}while(c=c._.parentDialog)
+}};F=a;b.on("resize",a);a();(!CKEDITOR.env.mac||!CKEDITOR.env.webkit)&&f.focus();if(CKEDITOR.env.ie6Compat){var m=function(){h();arguments.callee.prevScrollHandler.apply(this,arguments)};b.$.setTimeout(function(){m.prevScrollHandler=window.onscroll||function(){};window.onscroll=m},0);h()}}function K(a){q&&(a.focusManager.remove(q),a=CKEDITOR.document.getWindow(),q.hide(),a.removeListener("resize",F),CKEDITOR.env.ie6Compat&&a.$.setTimeout(function(){window.onscroll=window.onscroll&&window.onscroll.prevScrollHandler||
+null},0),F=null)}var r=CKEDITOR.tools.cssLength,S='<div class="cke_reset_all {editorId} {editorDialogClass} {hidpi}" dir="{langDir}" lang="{langCode}" role="dialog" aria-labelledby="cke_dialog_title_{id}"><table class="cke_dialog '+CKEDITOR.env.cssClass+' cke_{langDir}" style="position:absolute" role="presentation"><tr><td role="presentation"><div class="cke_dialog_body" role="presentation"><div id="cke_dialog_title_{id}" class="cke_dialog_title" role="presentation"></div><a id="cke_dialog_close_button_{id}" class="cke_dialog_close_button" href="javascript:void(0)" title="{closeTitle}" role="button"><span class="cke_label">X</span></a><div id="cke_dialog_tabs_{id}" class="cke_dialog_tabs" role="tablist"></div><table class="cke_dialog_contents" role="presentation"><tr><td id="cke_dialog_contents_{id}" class="cke_dialog_contents_body" role="presentation"></td></tr><tr><td id="cke_dialog_footer_{id}" class="cke_dialog_footer" role="presentation"></td></tr></table></div></td></tr></table></div>';
+CKEDITOR.dialog=function(a,b){function c(){var a=l._.focusList;a.sort(function(a,b){return a.tabIndex!=b.tabIndex?b.tabIndex-a.tabIndex:a.focusIndex-b.focusIndex});for(var b=a.length,c=0;c<b;c++)a[c].focusIndex=c}function e(a){var b=l._.focusList,a=a||0;if(!(1>b.length)){var c=l._.currentFocusIndex;try{b[c].getInputElement().$.blur()}catch(f){}for(var d=c=(c+a+b.length)%b.length;a&&!b[d].isFocusable()&&!(d=(d+a+b.length)%b.length,d==c););b[d].focus();"text"==b[d].type&&b[d].select()}}function d(b){if(l==
+CKEDITOR.dialog._.currentTop){var c=b.data.getKeystroke(),d="rtl"==a.lang.dir;o=j=0;if(9==c||c==CKEDITOR.SHIFT+9)c=c==CKEDITOR.SHIFT+9,l._.tabBarMode?(c=c?t.call(l):u.call(l),l.selectPage(c),l._.tabs[c][0].focus()):e(c?-1:1),o=1;else if(c==CKEDITOR.ALT+121&&!l._.tabBarMode&&1<l.getPageCount())l._.tabBarMode=!0,l._.tabs[l._.currentTabId][0].focus(),o=1;else if((37==c||39==c)&&l._.tabBarMode)c=c==(d?39:37)?t.call(l):u.call(l),l.selectPage(c),l._.tabs[c][0].focus(),o=1;else if((13==c||32==c)&&l._.tabBarMode)this.selectPage(this._.currentTabId),
+this._.tabBarMode=!1,this._.currentFocusIndex=-1,e(1),o=1;else if(13==c){c=b.data.getTarget();if(!c.is("a","button","select","textarea")&&(!c.is("input")||"button"!=c.$.type))(c=this.getButton("ok"))&&CKEDITOR.tools.setTimeout(c.click,0,c),o=1;j=1}else if(27==c)(c=this.getButton("cancel"))?CKEDITOR.tools.setTimeout(c.click,0,c):!1!==this.fire("cancel",{hide:!0}).hide&&this.hide(),j=1;else return;g(b)}}function g(a){o?a.data.preventDefault(1):j&&a.data.stopPropagation()}var f=CKEDITOR.dialog._.dialogDefinitions[b],
+h=CKEDITOR.tools.clone(W),m=a.config.dialog_buttonsOrder||"OS",k=a.lang.dir,i={},o,j;("OS"==m&&CKEDITOR.env.mac||"rtl"==m&&"ltr"==k||"ltr"==m&&"rtl"==k)&&h.buttons.reverse();f=CKEDITOR.tools.extend(f(a),h);f=CKEDITOR.tools.clone(f);f=new L(this,f);h=R(a);this._={editor:a,element:h.element,name:b,contentSize:{width:0,height:0},size:{width:0,height:0},contents:{},buttons:{},accessKeyMap:{},tabs:{},tabIdList:[],currentTabId:null,currentTabIndex:null,pageCount:0,lastTab:null,tabBarMode:!1,focusList:[],
+currentFocusIndex:0,hasFocus:!1};this.parts=h.parts;CKEDITOR.tools.setTimeout(function(){a.fire("ariaWidget",this.parts.contents)},0,this);h={position:CKEDITOR.env.ie6Compat?"absolute":"fixed",top:0,visibility:"hidden"};h["rtl"==k?"right":"left"]=0;this.parts.dialog.setStyles(h);CKEDITOR.event.call(this);this.definition=f=CKEDITOR.fire("dialogDefinition",{name:b,definition:f},a).definition;if(!("removeDialogTabs"in a._)&&a.config.removeDialogTabs){h=a.config.removeDialogTabs.split(";");for(k=0;k<
+h.length;k++)if(m=h[k].split(":"),2==m.length){var n=m[0];i[n]||(i[n]=[]);i[n].push(m[1])}a._.removeDialogTabs=i}if(a._.removeDialogTabs&&(i=a._.removeDialogTabs[b]))for(k=0;k<i.length;k++)f.removeContents(i[k]);if(f.onLoad)this.on("load",f.onLoad);if(f.onShow)this.on("show",f.onShow);if(f.onHide)this.on("hide",f.onHide);if(f.onOk)this.on("ok",function(b){a.fire("saveSnapshot");setTimeout(function(){a.fire("saveSnapshot")},0);!1===f.onOk.call(this,b)&&(b.data.hide=!1)});if(f.onCancel)this.on("cancel",
+function(a){!1===f.onCancel.call(this,a)&&(a.data.hide=!1)});var l=this,p=function(a){var b=l._.contents,c=!1,d;for(d in b)for(var f in b[d])if(c=a.call(this,b[d][f]))return};this.on("ok",function(a){p(function(b){if(b.validate){var c=b.validate(this),d="string"==typeof c||!1===c;d&&(a.data.hide=!1,a.stop());P.call(b,!d,"string"==typeof c?c:void 0);return d}})},this,null,0);this.on("cancel",function(b){p(function(c){if(c.isChanged())return!a.config.dialog_noConfirmCancel&&!confirm(a.lang.common.confirmCancel)&&
+(b.data.hide=!1),!0})},this,null,0);this.parts.close.on("click",function(a){!1!==this.fire("cancel",{hide:!0}).hide&&this.hide();a.data.preventDefault()},this);this.changeFocus=e;var v=this._.element;a.focusManager.add(v,1);this.on("show",function(){v.on("keydown",d,this);if(CKEDITOR.env.gecko)v.on("keypress",g,this)});this.on("hide",function(){v.removeListener("keydown",d);CKEDITOR.env.gecko&&v.removeListener("keypress",g);p(function(a){Q.apply(a)})});this.on("iframeAdded",function(a){(new CKEDITOR.dom.document(a.data.iframe.$.contentWindow.document)).on("keydown",
+d,this,null,0)});this.on("show",function(){c();if(a.config.dialog_startupFocusTab&&1<l._.pageCount)l._.tabBarMode=!0,l._.tabs[l._.currentTabId][0].focus();else if(!this._.hasFocus)if(this._.currentFocusIndex=-1,f.onFocus){var b=f.onFocus.call(this);b&&b.focus()}else e(1)},this,null,4294967295);if(CKEDITOR.env.ie6Compat)this.on("load",function(){var a=this.getElement(),b=a.getFirst();b.remove();b.appendTo(a)},this);U(this);V(this);(new CKEDITOR.dom.text(f.title,CKEDITOR.document)).appendTo(this.parts.title);
+for(k=0;k<f.contents.length;k++)(i=f.contents[k])&&this.addPage(i);this.parts.tabs.on("click",function(a){var b=a.data.getTarget();b.hasClass("cke_dialog_tab")&&(b=b.$.id,this.selectPage(b.substring(4,b.lastIndexOf("_"))),this._.tabBarMode&&(this._.tabBarMode=!1,this._.currentFocusIndex=-1,e(1)),a.data.preventDefault())},this);k=[];i=CKEDITOR.dialog._.uiElementBuilders.hbox.build(this,{type:"hbox",className:"cke_dialog_footer_buttons",widths:[],children:f.buttons},k).getChild();this.parts.footer.setHtml(k.join(""));
+for(k=0;k<i.length;k++)this._.buttons[i[k].id]=i[k]};CKEDITOR.dialog.prototype={destroy:function(){this.hide();this._.element.remove()},resize:function(){return function(a,b){if(!this._.contentSize||!(this._.contentSize.width==a&&this._.contentSize.height==b))CKEDITOR.dialog.fire("resize",{dialog:this,width:a,height:b},this._.editor),this.fire("resize",{width:a,height:b},this._.editor),this.parts.contents.setStyles({width:a+"px",height:b+"px"}),"rtl"==this._.editor.lang.dir&&this._.position&&(this._.position.x=
+CKEDITOR.document.getWindow().getViewPaneSize().width-this._.contentSize.width-parseInt(this._.element.getFirst().getStyle("right"),10)),this._.contentSize={width:a,height:b}}}(),getSize:function(){var a=this._.element.getFirst();return{width:a.$.offsetWidth||0,height:a.$.offsetHeight||0}},move:function(a,b,c){var e=this._.element.getFirst(),d="rtl"==this._.editor.lang.dir,g="fixed"==e.getComputedStyle("position");CKEDITOR.env.ie&&e.setStyle("zoom","100%");if(!g||!this._.position||!(this._.position.x==
+a&&this._.position.y==b))this._.position={x:a,y:b},g||(g=CKEDITOR.document.getWindow().getScrollPosition(),a+=g.x,b+=g.y),d&&(g=this.getSize(),a=CKEDITOR.document.getWindow().getViewPaneSize().width-g.width-a),b={top:(0<b?b:0)+"px"},b[d?"right":"left"]=(0<a?a:0)+"px",e.setStyles(b),c&&(this._.moved=1)},getPosition:function(){return CKEDITOR.tools.extend({},this._.position)},show:function(){var a=this._.element,b=this.definition;!a.getParent()||!a.getParent().equals(CKEDITOR.document.getBody())?a.appendTo(CKEDITOR.document.getBody()):
+a.setStyle("display","block");this.resize(this._.contentSize&&this._.contentSize.width||b.width||b.minWidth,this._.contentSize&&this._.contentSize.height||b.height||b.minHeight);this.reset();this.selectPage(this.definition.contents[0].id);null===CKEDITOR.dialog._.currentZIndex&&(CKEDITOR.dialog._.currentZIndex=this._.editor.config.baseFloatZIndex);this._.element.getFirst().setStyle("z-index",CKEDITOR.dialog._.currentZIndex+=10);null===CKEDITOR.dialog._.currentTop?(CKEDITOR.dialog._.currentTop=this,
+this._.parentDialog=null,J(this._.editor)):(this._.parentDialog=CKEDITOR.dialog._.currentTop,this._.parentDialog.getElement().getFirst().$.style.zIndex-=Math.floor(this._.editor.config.baseFloatZIndex/2),CKEDITOR.dialog._.currentTop=this);a.on("keydown",M);a.on("keyup",N);this._.hasFocus=!1;for(var c in b.contents)if(b.contents[c]){var a=b.contents[c],e=this._.tabs[a.id],d=a.requiredContent,g=0;if(e){for(var f in this._.contents[a.id]){var h=this._.contents[a.id][f];"hbox"==h.type||("vbox"==h.type||
+!h.getInputElement())||(h.requiredContent&&!this._.editor.activeFilter.check(h.requiredContent)?h.disable():(h.enable(),g++))}!g||d&&!this._.editor.activeFilter.check(d)?e[0].addClass("cke_dialog_tab_disabled"):e[0].removeClass("cke_dialog_tab_disabled")}}CKEDITOR.tools.setTimeout(function(){this.layout();T(this);this.parts.dialog.setStyle("visibility","");this.fireOnce("load",{});CKEDITOR.ui.fire("ready",this);this.fire("show",{});this._.editor.fire("dialogShow",this);this._.parentDialog||this._.editor.focusManager.lock();
+this.foreach(function(a){a.setInitValue&&a.setInitValue()})},100,this)},layout:function(){var a=this.parts.dialog,b=this.getSize(),c=CKEDITOR.document.getWindow().getViewPaneSize(),e=(c.width-b.width)/2,d=(c.height-b.height)/2;CKEDITOR.env.ie6Compat||(b.height+(0<d?d:0)>c.height||b.width+(0<e?e:0)>c.width?a.setStyle("position","absolute"):a.setStyle("position","fixed"));this.move(this._.moved?this._.position.x:e,this._.moved?this._.position.y:d)},foreach:function(a){for(var b in this._.contents)for(var c in this._.contents[b])a.call(this,
+this._.contents[b][c]);return this},reset:function(){var a=function(a){a.reset&&a.reset(1)};return function(){this.foreach(a);return this}}(),setupContent:function(){var a=arguments;this.foreach(function(b){b.setup&&b.setup.apply(b,a)})},commitContent:function(){var a=arguments;this.foreach(function(b){CKEDITOR.env.ie&&this._.currentFocusIndex==b.focusIndex&&b.getInputElement().$.blur();b.commit&&b.commit.apply(b,a)})},hide:function(){if(this.parts.dialog.isVisible()){this.fire("hide",{});this._.editor.fire("dialogHide",
+this);this.selectPage(this._.tabIdList[0]);var a=this._.element;a.setStyle("display","none");this.parts.dialog.setStyle("visibility","hidden");for(X(this);CKEDITOR.dialog._.currentTop!=this;)CKEDITOR.dialog._.currentTop.hide();if(this._.parentDialog){var b=this._.parentDialog.getElement().getFirst();b.setStyle("z-index",parseInt(b.$.style.zIndex,10)+Math.floor(this._.editor.config.baseFloatZIndex/2))}else K(this._.editor);if(CKEDITOR.dialog._.currentTop=this._.parentDialog)CKEDITOR.dialog._.currentZIndex-=
+10;else{CKEDITOR.dialog._.currentZIndex=null;a.removeListener("keydown",M);a.removeListener("keyup",N);var c=this._.editor;c.focus();setTimeout(function(){c.focusManager.unlock();CKEDITOR.env.iOS&&c.window.focus()},0)}delete this._.parentDialog;this.foreach(function(a){a.resetInitValue&&a.resetInitValue()})}},addPage:function(a){if(!a.requiredContent||this._.editor.filter.check(a.requiredContent)){for(var b=[],c=a.label?' title="'+CKEDITOR.tools.htmlEncode(a.label)+'"':"",e=CKEDITOR.dialog._.uiElementBuilders.vbox.build(this,
+{type:"vbox",className:"cke_dialog_page_contents",children:a.elements,expand:!!a.expand,padding:a.padding,style:a.style||"width: 100%;"},b),d=this._.contents[a.id]={},g=e.getChild(),f=0;e=g.shift();)!e.notAllowed&&("hbox"!=e.type&&"vbox"!=e.type)&&f++,d[e.id]=e,"function"==typeof e.getChild&&g.push.apply(g,e.getChild());f||(a.hidden=!0);b=CKEDITOR.dom.element.createFromHtml(b.join(""));b.setAttribute("role","tabpanel");e=CKEDITOR.env;d="cke_"+a.id+"_"+CKEDITOR.tools.getNextNumber();c=CKEDITOR.dom.element.createFromHtml(['<a class="cke_dialog_tab"',
+0<this._.pageCount?" cke_last":"cke_first",c,a.hidden?' style="display:none"':"",' id="',d,'"',e.gecko&&!e.hc?"":' href="javascript:void(0)"',' tabIndex="-1" hidefocus="true" role="tab">',a.label,"</a>"].join(""));b.setAttribute("aria-labelledby",d);this._.tabs[a.id]=[c,b];this._.tabIdList.push(a.id);!a.hidden&&this._.pageCount++;this._.lastTab=c;this.updateStyle();b.setAttribute("name",a.id);b.appendTo(this.parts.contents);c.unselectable();this.parts.tabs.append(c);a.accessKey&&(O(this,this,"CTRL+"+
+a.accessKey,Y,Z),this._.accessKeyMap["CTRL+"+a.accessKey]=a.id)}},selectPage:function(a){if(this._.currentTabId!=a&&!this._.tabs[a][0].hasClass("cke_dialog_tab_disabled")&&!1!==this.fire("selectPage",{page:a,currentPage:this._.currentTabId})){for(var b in this._.tabs){var c=this._.tabs[b][0],e=this._.tabs[b][1];b!=a&&(c.removeClass("cke_dialog_tab_selected"),e.hide());e.setAttribute("aria-hidden",b!=a)}var d=this._.tabs[a];d[0].addClass("cke_dialog_tab_selected");CKEDITOR.env.ie6Compat||CKEDITOR.env.ie7Compat?
+(G(d[1]),d[1].show(),setTimeout(function(){G(d[1],1)},0)):d[1].show();this._.currentTabId=a;this._.currentTabIndex=CKEDITOR.tools.indexOf(this._.tabIdList,a)}},updateStyle:function(){this.parts.dialog[(1===this._.pageCount?"add":"remove")+"Class"]("cke_single_page")},hidePage:function(a){var b=this._.tabs[a]&&this._.tabs[a][0];b&&(1!=this._.pageCount&&b.isVisible())&&(a==this._.currentTabId&&this.selectPage(t.call(this)),b.hide(),this._.pageCount--,this.updateStyle())},showPage:function(a){if(a=this._.tabs[a]&&
+this._.tabs[a][0])a.show(),this._.pageCount++,this.updateStyle()},getElement:function(){return this._.element},getName:function(){return this._.name},getContentElement:function(a,b){var c=this._.contents[a];return c&&c[b]},getValueOf:function(a,b){return this.getContentElement(a,b).getValue()},setValueOf:function(a,b,c){return this.getContentElement(a,b).setValue(c)},getButton:function(a){return this._.buttons[a]},click:function(a){return this._.buttons[a].click()},disableButton:function(a){return this._.buttons[a].disable()},
+enableButton:function(a){return this._.buttons[a].enable()},getPageCount:function(){return this._.pageCount},getParentEditor:function(){return this._.editor},getSelectedElement:function(){return this.getParentEditor().getSelection().getSelectedElement()},addFocusable:function(a,b){if("undefined"==typeof b)b=this._.focusList.length,this._.focusList.push(new H(this,a,b));else{this._.focusList.splice(b,0,new H(this,a,b));for(var c=b+1;c<this._.focusList.length;c++)this._.focusList[c].focusIndex++}}};
+CKEDITOR.tools.extend(CKEDITOR.dialog,{add:function(a,b){if(!this._.dialogDefinitions[a]||"function"==typeof b)this._.dialogDefinitions[a]=b},exists:function(a){return!!this._.dialogDefinitions[a]},getCurrent:function(){return CKEDITOR.dialog._.currentTop},isTabEnabled:function(a,b,c){a=a.config.removeDialogTabs;return!(a&&a.match(RegExp("(?:^|;)"+b+":"+c+"(?:$|;)","i")))},okButton:function(){var a=function(a,c){c=c||{};return CKEDITOR.tools.extend({id:"ok",type:"button",label:a.lang.common.ok,"class":"cke_dialog_ui_button_ok",
+onClick:function(a){a=a.data.dialog;!1!==a.fire("ok",{hide:!0}).hide&&a.hide()}},c,!0)};a.type="button";a.override=function(b){return CKEDITOR.tools.extend(function(c){return a(c,b)},{type:"button"},!0)};return a}(),cancelButton:function(){var a=function(a,c){c=c||{};return CKEDITOR.tools.extend({id:"cancel",type:"button",label:a.lang.common.cancel,"class":"cke_dialog_ui_button_cancel",onClick:function(a){a=a.data.dialog;!1!==a.fire("cancel",{hide:!0}).hide&&a.hide()}},c,!0)};a.type="button";a.override=
+function(b){return CKEDITOR.tools.extend(function(c){return a(c,b)},{type:"button"},!0)};return a}(),addUIElement:function(a,b){this._.uiElementBuilders[a]=b}});CKEDITOR.dialog._={uiElementBuilders:{},dialogDefinitions:{},currentTop:null,currentZIndex:null};CKEDITOR.event.implementOn(CKEDITOR.dialog);CKEDITOR.event.implementOn(CKEDITOR.dialog.prototype);var W={resizable:CKEDITOR.DIALOG_RESIZE_BOTH,minWidth:600,minHeight:400,buttons:[CKEDITOR.dialog.okButton,CKEDITOR.dialog.cancelButton]},z=function(a,
+b,c){for(var e=0,d;d=a[e];e++)if(d.id==b||c&&d[c]&&(d=z(d[c],b,c)))return d;return null},A=function(a,b,c,e,d){if(c){for(var g=0,f;f=a[g];g++){if(f.id==c)return a.splice(g,0,b),b;if(e&&f[e]&&(f=A(f[e],b,c,e,!0)))return f}if(d)return null}a.push(b);return b},B=function(a,b,c){for(var e=0,d;d=a[e];e++){if(d.id==b)return a.splice(e,1);if(c&&d[c]&&(d=B(d[c],b,c)))return d}return null},L=function(a,b){this.dialog=a;for(var c=b.contents,e=0,d;d=c[e];e++)c[e]=d&&new I(a,d);CKEDITOR.tools.extend(this,b)};
+L.prototype={getContents:function(a){return z(this.contents,a)},getButton:function(a){return z(this.buttons,a)},addContents:function(a,b){return A(this.contents,a,b)},addButton:function(a,b){return A(this.buttons,a,b)},removeContents:function(a){B(this.contents,a)},removeButton:function(a){B(this.buttons,a)}};I.prototype={get:function(a){return z(this.elements,a,"children")},add:function(a,b){return A(this.elements,a,b,"children")},remove:function(a){B(this.elements,a,"children")}};var F,w={},q,s=
+{},M=function(a){var b=a.data.$.ctrlKey||a.data.$.metaKey,c=a.data.$.altKey,e=a.data.$.shiftKey,d=String.fromCharCode(a.data.$.keyCode);if((b=s[(b?"CTRL+":"")+(c?"ALT+":"")+(e?"SHIFT+":"")+d])&&b.length)b=b[b.length-1],b.keydown&&b.keydown.call(b.uiElement,b.dialog,b.key),a.data.preventDefault()},N=function(a){var b=a.data.$.ctrlKey||a.data.$.metaKey,c=a.data.$.altKey,e=a.data.$.shiftKey,d=String.fromCharCode(a.data.$.keyCode);if((b=s[(b?"CTRL+":"")+(c?"ALT+":"")+(e?"SHIFT+":"")+d])&&b.length)b=b[b.length-
+1],b.keyup&&(b.keyup.call(b.uiElement,b.dialog,b.key),a.data.preventDefault())},O=function(a,b,c,e,d){(s[c]||(s[c]=[])).push({uiElement:a,dialog:b,key:c,keyup:d||a.accessKeyUp,keydown:e||a.accessKeyDown})},X=function(a){for(var b in s){for(var c=s[b],e=c.length-1;0<=e;e--)(c[e].dialog==a||c[e].uiElement==a)&&c.splice(e,1);0===c.length&&delete s[b]}},Z=function(a,b){a._.accessKeyMap[b]&&a.selectPage(a._.accessKeyMap[b])},Y=function(){};(function(){CKEDITOR.ui.dialog={uiElement:function(a,b,c,e,d,g,
+f){if(!(4>arguments.length)){var h=(e.call?e(b):e)||"div",m=["<",h," "],k=(d&&d.call?d(b):d)||{},i=(g&&g.call?g(b):g)||{},o=(f&&f.call?f.call(this,a,b):f)||"",j=this.domId=i.id||CKEDITOR.tools.getNextId()+"_uiElement";b.requiredContent&&!a.getParentEditor().filter.check(b.requiredContent)&&(k.display="none",this.notAllowed=!0);i.id=j;var n={};b.type&&(n["cke_dialog_ui_"+b.type]=1);b.className&&(n[b.className]=1);b.disabled&&(n.cke_disabled=1);for(var l=i["class"]&&i["class"].split?i["class"].split(" "):
+[],j=0;j<l.length;j++)l[j]&&(n[l[j]]=1);l=[];for(j in n)l.push(j);i["class"]=l.join(" ");b.title&&(i.title=b.title);n=(b.style||"").split(";");b.align&&(l=b.align,k["margin-left"]="left"==l?0:"auto",k["margin-right"]="right"==l?0:"auto");for(j in k)n.push(j+":"+k[j]);b.hidden&&n.push("display:none");for(j=n.length-1;0<=j;j--)""===n[j]&&n.splice(j,1);0<n.length&&(i.style=(i.style?i.style+"; ":"")+n.join("; "));for(j in i)m.push(j+'="'+CKEDITOR.tools.htmlEncode(i[j])+'" ');m.push(">",o,"</",h,">");
+c.push(m.join(""));(this._||(this._={})).dialog=a;"boolean"==typeof b.isChanged&&(this.isChanged=function(){return b.isChanged});"function"==typeof b.isChanged&&(this.isChanged=b.isChanged);"function"==typeof b.setValue&&(this.setValue=CKEDITOR.tools.override(this.setValue,function(a){return function(c){a.call(this,b.setValue.call(this,c))}}));"function"==typeof b.getValue&&(this.getValue=CKEDITOR.tools.override(this.getValue,function(a){return function(){return b.getValue.call(this,a.call(this))}}));
+CKEDITOR.event.implementOn(this);this.registerEvents(b);this.accessKeyUp&&(this.accessKeyDown&&b.accessKey)&&O(this,a,"CTRL+"+b.accessKey);var p=this;a.on("load",function(){var b=p.getInputElement();if(b){var c=p.type in{checkbox:1,ratio:1}&&CKEDITOR.env.ie&&CKEDITOR.env.version<8?"cke_dialog_ui_focused":"";b.on("focus",function(){a._.tabBarMode=false;a._.hasFocus=true;p.fire("focus");c&&this.addClass(c)});b.on("blur",function(){p.fire("blur");c&&this.removeClass(c)})}});CKEDITOR.tools.extend(this,
+b);this.keyboardFocusable&&(this.tabIndex=b.tabIndex||0,this.focusIndex=a._.focusList.push(this)-1,this.on("focus",function(){a._.currentFocusIndex=p.focusIndex}))}},hbox:function(a,b,c,e,d){if(!(4>arguments.length)){this._||(this._={});var g=this._.children=b,f=d&&d.widths||null,h=d&&d.height||null,m,k={role:"presentation"};d&&d.align&&(k.align=d.align);CKEDITOR.ui.dialog.uiElement.call(this,a,d||{type:"hbox"},e,"table",{},k,function(){var a=['<tbody><tr class="cke_dialog_ui_hbox">'];for(m=0;m<c.length;m++){var b=
+"cke_dialog_ui_hbox_child",e=[];0===m&&(b="cke_dialog_ui_hbox_first");m==c.length-1&&(b="cke_dialog_ui_hbox_last");a.push('<td class="',b,'" role="presentation" ');f?f[m]&&e.push("width:"+r(f[m])):e.push("width:"+Math.floor(100/c.length)+"%");h&&e.push("height:"+r(h));d&&void 0!==d.padding&&e.push("padding:"+r(d.padding));CKEDITOR.env.ie&&(CKEDITOR.env.quirks&&g[m].align)&&e.push("text-align:"+g[m].align);0<e.length&&a.push('style="'+e.join("; ")+'" ');a.push(">",c[m],"</td>")}a.push("</tr></tbody>");
+return a.join("")})}},vbox:function(a,b,c,e,d){if(!(3>arguments.length)){this._||(this._={});var g=this._.children=b,f=d&&d.width||null,h=d&&d.heights||null;CKEDITOR.ui.dialog.uiElement.call(this,a,d||{type:"vbox"},e,"div",null,{role:"presentation"},function(){var b=['<table role="presentation" cellspacing="0" border="0" '];b.push('style="');d&&d.expand&&b.push("height:100%;");b.push("width:"+r(f||"100%"),";");CKEDITOR.env.webkit&&b.push("float:none;");b.push('"');b.push('align="',CKEDITOR.tools.htmlEncode(d&&
+d.align||("ltr"==a.getParentEditor().lang.dir?"left":"right")),'" ');b.push("><tbody>");for(var e=0;e<c.length;e++){var i=[];b.push('<tr><td role="presentation" ');f&&i.push("width:"+r(f||"100%"));h?i.push("height:"+r(h[e])):d&&d.expand&&i.push("height:"+Math.floor(100/c.length)+"%");d&&void 0!==d.padding&&i.push("padding:"+r(d.padding));CKEDITOR.env.ie&&(CKEDITOR.env.quirks&&g[e].align)&&i.push("text-align:"+g[e].align);0<i.length&&b.push('style="',i.join("; "),'" ');b.push(' class="cke_dialog_ui_vbox_child">',
+c[e],"</td></tr>")}b.push("</tbody></table>");return b.join("")})}}}})();CKEDITOR.ui.dialog.uiElement.prototype={getElement:function(){return CKEDITOR.document.getById(this.domId)},getInputElement:function(){return this.getElement()},getDialog:function(){return this._.dialog},setValue:function(a,b){this.getInputElement().setValue(a);!b&&this.fire("change",{value:a});return this},getValue:function(){return this.getInputElement().getValue()},isChanged:function(){return!1},selectParentTab:function(){for(var a=
+this.getInputElement();(a=a.getParent())&&-1==a.$.className.search("cke_dialog_page_contents"););if(!a)return this;a=a.getAttribute("name");this._.dialog._.currentTabId!=a&&this._.dialog.selectPage(a);return this},focus:function(){this.selectParentTab().getInputElement().focus();return this},registerEvents:function(a){var b=/^on([A-Z]\w+)/,c,e=function(a,b,c,d){b.on("load",function(){a.getInputElement().on(c,d,a)})},d;for(d in a)if(c=d.match(b))this.eventProcessors[d]?this.eventProcessors[d].call(this,
+this._.dialog,a[d]):e(this,this._.dialog,c[1].toLowerCase(),a[d]);return this},eventProcessors:{onLoad:function(a,b){a.on("load",b,this)},onShow:function(a,b){a.on("show",b,this)},onHide:function(a,b){a.on("hide",b,this)}},accessKeyDown:function(){this.focus()},accessKeyUp:function(){},disable:function(){var a=this.getElement();this.getInputElement().setAttribute("disabled","true");a.addClass("cke_disabled")},enable:function(){var a=this.getElement();this.getInputElement().removeAttribute("disabled");
+a.removeClass("cke_disabled")},isEnabled:function(){return!this.getElement().hasClass("cke_disabled")},isVisible:function(){return this.getInputElement().isVisible()},isFocusable:function(){return!this.isEnabled()||!this.isVisible()?!1:!0}};CKEDITOR.ui.dialog.hbox.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{getChild:function(a){if(1>arguments.length)return this._.children.concat();a.splice||(a=[a]);return 2>a.length?this._.children[a[0]]:this._.children[a[0]]&&this._.children[a[0]].getChild?
+this._.children[a[0]].getChild(a.slice(1,a.length)):null}},!0);CKEDITOR.ui.dialog.vbox.prototype=new CKEDITOR.ui.dialog.hbox;(function(){var a={build:function(a,c,e){for(var d=c.children,g,f=[],h=[],m=0;m<d.length&&(g=d[m]);m++){var k=[];f.push(k);h.push(CKEDITOR.dialog._.uiElementBuilders[g.type].build(a,g,k))}return new CKEDITOR.ui.dialog[c.type](a,h,f,e,c)}};CKEDITOR.dialog.addUIElement("hbox",a);CKEDITOR.dialog.addUIElement("vbox",a)})();CKEDITOR.dialogCommand=function(a,b){this.dialogName=a;
+CKEDITOR.tools.extend(this,b,!0)};CKEDITOR.dialogCommand.prototype={exec:function(a){a.openDialog(this.dialogName)},canUndo:!1,editorFocus:1};(function(){var a=/^([a]|[^a])+$/,b=/^\d*$/,c=/^\d*(?:\.\d+)?$/,e=/^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/,d=/^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i,g=/^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/;CKEDITOR.VALIDATE_OR=1;CKEDITOR.VALIDATE_AND=2;CKEDITOR.dialog.validate={functions:function(){var a=arguments;return function(){var b=this&&this.getValue?this.getValue():
+a[0],c,d=CKEDITOR.VALIDATE_AND,e=[],g;for(g=0;g<a.length;g++)if("function"==typeof a[g])e.push(a[g]);else break;g<a.length&&"string"==typeof a[g]&&(c=a[g],g++);g<a.length&&"number"==typeof a[g]&&(d=a[g]);var j=d==CKEDITOR.VALIDATE_AND?!0:!1;for(g=0;g<e.length;g++)j=d==CKEDITOR.VALIDATE_AND?j&&e[g](b):j||e[g](b);return!j?c:!0}},regex:function(a,b){return function(c){c=this&&this.getValue?this.getValue():c;return!a.test(c)?b:!0}},notEmpty:function(b){return this.regex(a,b)},integer:function(a){return this.regex(b,
+a)},number:function(a){return this.regex(c,a)},cssLength:function(a){return this.functions(function(a){return d.test(CKEDITOR.tools.trim(a))},a)},htmlLength:function(a){return this.functions(function(a){return e.test(CKEDITOR.tools.trim(a))},a)},inlineStyle:function(a){return this.functions(function(a){return g.test(CKEDITOR.tools.trim(a))},a)},equals:function(a,b){return this.functions(function(b){return b==a},b)},notEqual:function(a,b){return this.functions(function(b){return b!=a},b)}};CKEDITOR.on("instanceDestroyed",
+function(a){if(CKEDITOR.tools.isEmpty(CKEDITOR.instances)){for(var b;b=CKEDITOR.dialog._.currentTop;)b.hide();for(var c in w)w[c].remove();w={}}var a=a.editor._.storedDialogs,d;for(d in a)a[d].destroy()})})();CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{openDialog:function(a,b){var c=null,e=CKEDITOR.dialog._.dialogDefinitions[a];null===CKEDITOR.dialog._.currentTop&&J(this);if("function"==typeof e)c=this._.storedDialogs||(this._.storedDialogs={}),c=c[a]||(c[a]=new CKEDITOR.dialog(this,a)),b&&b.call(c,
+c),c.show();else{if("failed"==e)throw K(this),Error('[CKEDITOR.dialog.openDialog] Dialog "'+a+'" failed when loading definition.');"string"==typeof e&&CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(e),function(){"function"!=typeof CKEDITOR.dialog._.dialogDefinitions[a]&&(CKEDITOR.dialog._.dialogDefinitions[a]="failed");this.openDialog(a,b)},this,0,1)}CKEDITOR.skin.loadPart("dialog");return c}})})();
+CKEDITOR.plugins.add("dialog",{requires:"dialogui",init:function(t){t.on("doubleclick",function(u){u.data.dialog&&t.openDialog(u.data.dialog)},null,null,999)}});CKEDITOR.plugins.add("about",{requires:"dialog",init:function(a){var b=a.addCommand("about",new CKEDITOR.dialogCommand("about"));b.modes={wysiwyg:1,source:1};b.canUndo=!1;b.readOnly=1;a.ui.addButton&&a.ui.addButton("About",{label:a.lang.about.title,command:"about",toolbar:"about"});CKEDITOR.dialog.add("about",this.path+"dialogs/about.js")}});(function(){CKEDITOR.plugins.add("a11yhelp",{requires:"dialog",availableLangs:{af:1,ar:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,el:1,en:1,"en-gb":1,eo:1,es:1,et:1,fa:1,fi:1,fr:1,"fr-ca":1,gl:1,gu:1,he:1,hi:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ko:1,ku:1,lt:1,lv:1,mk:1,mn:1,nb:1,nl:1,no:1,pl:1,pt:1,"pt-br":1,ro:1,ru:1,si:1,sk:1,sl:1,sq:1,sr:1,"sr-latn":1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,zh:1,"zh-cn":1},init:function(b){var c=this;b.addCommand("a11yHelp",{exec:function(){var a=b.langCode,a=c.availableLangs[a]?a:
+c.availableLangs[a.replace(/-.*/,"")]?a.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(c.path+"dialogs/lang/"+a+".js"),function(){b.lang.a11yhelp=c.langEntries[a];b.openDialog("a11yHelp")})},modes:{wysiwyg:1,source:1},readOnly:1,canUndo:!1});b.setKeystroke(CKEDITOR.ALT+48,"a11yHelp");CKEDITOR.dialog.add("a11yHelp",this.path+"dialogs/a11yhelp.js");b.on("ariaEditorHelpLabel",function(a){a.data.label=b.lang.common.editorHelp})}})})();CKEDITOR.plugins.add("basicstyles",{init:function(c){var e=0,d=function(g,d,b,a){if(a){var a=new CKEDITOR.style(a),f=h[b];f.unshift(a);c.attachStyleStateChange(a,function(a){!c.readOnly&&c.getCommand(b).setState(a)});c.addCommand(b,new CKEDITOR.styleCommand(a,{contentForms:f}));c.ui.addButton&&c.ui.addButton(g,{label:d,command:b,toolbar:"basicstyles,"+(e+=10)})}},h={bold:["strong","b",["span",function(a){a=a.styles["font-weight"];return"bold"==a||700<=+a}]],italic:["em","i",["span",function(a){return"italic"==
+a.styles["font-style"]}]],underline:["u",["span",function(a){return"underline"==a.styles["text-decoration"]}]],strike:["s","strike",["span",function(a){return"line-through"==a.styles["text-decoration"]}]],subscript:["sub"],superscript:["sup"]},b=c.config,a=c.lang.basicstyles;d("Bold",a.bold,"bold",b.coreStyles_bold);d("Italic",a.italic,"italic",b.coreStyles_italic);d("Underline",a.underline,"underline",b.coreStyles_underline);d("Strike",a.strike,"strike",b.coreStyles_strike);d("Subscript",a.subscript,
+"subscript",b.coreStyles_subscript);d("Superscript",a.superscript,"superscript",b.coreStyles_superscript);c.setKeystroke([[CKEDITOR.CTRL+66,"bold"],[CKEDITOR.CTRL+73,"italic"],[CKEDITOR.CTRL+85,"underline"]])}});CKEDITOR.config.coreStyles_bold={element:"strong",overrides:"b"};CKEDITOR.config.coreStyles_italic={element:"em",overrides:"i"};CKEDITOR.config.coreStyles_underline={element:"u"};CKEDITOR.config.coreStyles_strike={element:"s",overrides:"strike"};CKEDITOR.config.coreStyles_subscript={element:"sub"};
+CKEDITOR.config.coreStyles_superscript={element:"sup"};(function(){var k={exec:function(g){var a=g.getCommand("blockquote").state,i=g.getSelection(),c=i&&i.getRanges()[0];if(c){var h=i.createBookmarks();if(CKEDITOR.env.ie){var e=h[0].startNode,b=h[0].endNode,d;if(e&&"blockquote"==e.getParent().getName())for(d=e;d=d.getNext();)if(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()){e.move(d,!0);break}if(b&&"blockquote"==b.getParent().getName())for(d=b;d=d.getPrevious();)if(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()){b.move(d);break}}var f=c.createIterator();
+f.enlargeBr=g.config.enterMode!=CKEDITOR.ENTER_BR;if(a==CKEDITOR.TRISTATE_OFF){for(e=[];a=f.getNextParagraph();)e.push(a);1>e.length&&(a=g.document.createElement(g.config.enterMode==CKEDITOR.ENTER_P?"p":"div"),b=h.shift(),c.insertNode(a),a.append(new CKEDITOR.dom.text("",g.document)),c.moveToBookmark(b),c.selectNodeContents(a),c.collapse(!0),b=c.createBookmark(),e.push(a),h.unshift(b));d=e[0].getParent();c=[];for(b=0;b<e.length;b++)a=e[b],d=d.getCommonAncestor(a.getParent());for(a={table:1,tbody:1,
+tr:1,ol:1,ul:1};a[d.getName()];)d=d.getParent();for(b=null;0<e.length;){for(a=e.shift();!a.getParent().equals(d);)a=a.getParent();a.equals(b)||c.push(a);b=a}for(;0<c.length;)if(a=c.shift(),"blockquote"==a.getName()){for(b=new CKEDITOR.dom.documentFragment(g.document);a.getFirst();)b.append(a.getFirst().remove()),e.push(b.getLast());b.replace(a)}else e.push(a);c=g.document.createElement("blockquote");for(c.insertBefore(e[0]);0<e.length;)a=e.shift(),c.append(a)}else if(a==CKEDITOR.TRISTATE_ON){b=[];
+for(d={};a=f.getNextParagraph();){for(e=c=null;a.getParent();){if("blockquote"==a.getParent().getName()){c=a.getParent();e=a;break}a=a.getParent()}c&&(e&&!e.getCustomData("blockquote_moveout"))&&(b.push(e),CKEDITOR.dom.element.setMarker(d,e,"blockquote_moveout",!0))}CKEDITOR.dom.element.clearAllMarkers(d);a=[];e=[];for(d={};0<b.length;)f=b.shift(),c=f.getParent(),f.getPrevious()?f.getNext()?(f.breakParent(f.getParent()),e.push(f.getNext())):f.remove().insertAfter(c):f.remove().insertBefore(c),c.getCustomData("blockquote_processed")||
+(e.push(c),CKEDITOR.dom.element.setMarker(d,c,"blockquote_processed",!0)),a.push(f);CKEDITOR.dom.element.clearAllMarkers(d);for(b=e.length-1;0<=b;b--){c=e[b];a:{d=c;for(var f=0,k=d.getChildCount(),j=void 0;f<k&&(j=d.getChild(f));f++)if(j.type==CKEDITOR.NODE_ELEMENT&&j.isBlockBoundary()){d=!1;break a}d=!0}d&&c.remove()}if(g.config.enterMode==CKEDITOR.ENTER_BR)for(c=!0;a.length;)if(f=a.shift(),"div"==f.getName()){b=new CKEDITOR.dom.documentFragment(g.document);c&&(f.getPrevious()&&!(f.getPrevious().type==
+CKEDITOR.NODE_ELEMENT&&f.getPrevious().isBlockBoundary()))&&b.append(g.document.createElement("br"));for(c=f.getNext()&&!(f.getNext().type==CKEDITOR.NODE_ELEMENT&&f.getNext().isBlockBoundary());f.getFirst();)f.getFirst().remove().appendTo(b);c&&b.append(g.document.createElement("br"));b.replace(f);c=!1}}i.selectBookmarks(h);g.focus()}},refresh:function(g,a){this.setState(g.elementPath(a.block||a.blockLimit).contains("blockquote",1)?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF)},context:"blockquote",
+allowedContent:"blockquote",requiredContent:"blockquote"};CKEDITOR.plugins.add("blockquote",{init:function(g){g.blockless||(g.addCommand("blockquote",k),g.ui.addButton&&g.ui.addButton("Blockquote",{label:g.lang.blockquote.toolbar,command:"blockquote",toolbar:"blocks,10"}))}})})();(function(){function v(b){function a(){var e=b.editable();e.on(p,function(b){(!CKEDITOR.env.ie||!n)&&u(b)});CKEDITOR.env.ie&&e.on("paste",function(e){q||(g(),e.data.preventDefault(),u(e),h("paste")||b.openDialog("paste"))});CKEDITOR.env.ie&&(e.on("contextmenu",i,null,null,0),e.on("beforepaste",function(b){b.data&&(!b.data.$.ctrlKey&&!b.data.$.shiftKey)&&i()},null,null,0));e.on("beforecut",function(){!n&&j(b)});var a;e.attachListener(CKEDITOR.env.ie?e:b.document.getDocumentElement(),"mouseup",function(){a=
+setTimeout(function(){r()},0)});b.on("destroy",function(){clearTimeout(a)});e.on("keyup",r)}function c(e){return{type:e,canUndo:"cut"==e,startDisabled:!0,exec:function(){"cut"==this.type&&j();var e;var a=this.type;if(CKEDITOR.env.ie)e=h(a);else try{e=b.document.$.execCommand(a,!1,null)}catch(d){e=!1}e||alert(b.lang.clipboard[this.type+"Error"]);return e}}}function d(){return{canUndo:!1,async:!0,exec:function(b,a){var d=function(a,d){a&&f(a.type,a.dataValue,!!d);b.fire("afterCommandExec",{name:"paste",
+command:c,returnValue:!!a})},c=this;"string"==typeof a?d({type:"auto",dataValue:a},1):b.getClipboardData(d)}}}function g(){q=1;setTimeout(function(){q=0},100)}function i(){n=1;setTimeout(function(){n=0},10)}function h(e){var a=b.document,d=a.getBody(),c=!1,j=function(){c=!0};d.on(e,j);(7<CKEDITOR.env.version?a.$:a.$.selection.createRange()).execCommand(e);d.removeListener(e,j);return c}function f(e,a,d){e={type:e};if(d&&!1===b.fire("beforePaste",e)||!a)return!1;e.dataValue=a;return b.fire("paste",
+e)}function j(){if(CKEDITOR.env.ie&&!CKEDITOR.env.quirks){var e=b.getSelection(),a,d,c;if(e.getType()==CKEDITOR.SELECTION_ELEMENT&&(a=e.getSelectedElement()))d=e.getRanges()[0],c=b.document.createText(""),c.insertBefore(a),d.setStartBefore(c),d.setEndAfter(a),e.selectRanges([d]),setTimeout(function(){a.getParent()&&(c.remove(),e.selectElement(a))},0)}}function l(a,d){var c=b.document,j=b.editable(),l=function(b){b.cancel()},g;if(!c.getById("cke_pastebin")){var i=b.getSelection(),s=i.createBookmarks();
+CKEDITOR.env.ie&&i.root.fire("selectionchange");var k=new CKEDITOR.dom.element((CKEDITOR.env.webkit||j.is("body"))&&!CKEDITOR.env.ie?"body":"div",c);k.setAttributes({id:"cke_pastebin","data-cke-temp":"1"});var f=0,c=c.getWindow();CKEDITOR.env.webkit?(j.append(k),k.addClass("cke_editable"),j.is("body")||(f="static"!=j.getComputedStyle("position")?j:CKEDITOR.dom.element.get(j.$.offsetParent),f=f.getDocumentPosition().y)):j.getAscendant(CKEDITOR.env.ie?"body":"html",1).append(k);k.setStyles({position:"absolute",
+top:c.getScrollPosition().y-f+10+"px",width:"1px",height:Math.max(1,c.getViewPaneSize().height-20)+"px",overflow:"hidden",margin:0,padding:0});CKEDITOR.env.safari&&k.setStyles(CKEDITOR.tools.cssVendorPrefix("user-select","text"));(f=k.getParent().isReadOnly())?(k.setOpacity(0),k.setAttribute("contenteditable",!0)):k.setStyle("ltr"==b.config.contentsLangDirection?"left":"right","-1000px");b.on("selectionChange",l,null,null,0);if(CKEDITOR.env.webkit||CKEDITOR.env.gecko)g=j.once("blur",l,null,null,-100);
+f&&k.focus();f=new CKEDITOR.dom.range(k);f.selectNodeContents(k);var h=f.select();CKEDITOR.env.ie&&(g=j.once("blur",function(){b.lockSelection(h)}));var m=CKEDITOR.document.getWindow().getScrollPosition().y;setTimeout(function(){if(CKEDITOR.env.webkit)CKEDITOR.document.getBody().$.scrollTop=m;g&&g.removeListener();CKEDITOR.env.ie&&j.focus();i.selectBookmarks(s);k.remove();var a;if(CKEDITOR.env.webkit&&(a=k.getFirst())&&a.is&&a.hasClass("Apple-style-span"))k=a;b.removeListener("selectionChange",l);
+d(k.getHtml())},0)}}function s(){if(CKEDITOR.env.ie){b.focus();g();var a=b.focusManager;a.lock();if(b.editable().fire(p)&&!h("paste"))return a.unlock(),!1;a.unlock()}else try{if(b.editable().fire(p)&&!b.document.$.execCommand("Paste",!1,null))throw 0;}catch(d){return!1}return!0}function o(a){if("wysiwyg"==b.mode)switch(a.data.keyCode){case CKEDITOR.CTRL+86:case CKEDITOR.SHIFT+45:a=b.editable();g();!CKEDITOR.env.ie&&a.fire("beforepaste");break;case CKEDITOR.CTRL+88:case CKEDITOR.SHIFT+46:b.fire("saveSnapshot"),
+setTimeout(function(){b.fire("saveSnapshot")},50)}}function u(a){var d={type:"auto"},c=b.fire("beforePaste",d);l(a,function(b){b=b.replace(/<span[^>]+data-cke-bookmark[^<]*?<\/span>/ig,"");c&&f(d.type,b,0,1)})}function r(){if("wysiwyg"==b.mode){var a=m("paste");b.getCommand("cut").setState(m("cut"));b.getCommand("copy").setState(m("copy"));b.getCommand("paste").setState(a);b.fire("pasteState",a)}}function m(a){if(t&&a in{paste:1,cut:1})return CKEDITOR.TRISTATE_DISABLED;if("paste"==a)return CKEDITOR.TRISTATE_OFF;
+var a=b.getSelection(),d=a.getRanges();return a.getType()==CKEDITOR.SELECTION_NONE||1==d.length&&d[0].collapsed?CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_OFF}var n=0,q=0,t=0,p=CKEDITOR.env.ie?"beforepaste":"paste";(function(){b.on("key",o);b.on("contentDom",a);b.on("selectionChange",function(b){t=b.data.selection.getRanges()[0].checkReadOnly();r()});b.contextMenu&&b.contextMenu.addListener(function(b,a){t=a.getRanges()[0].checkReadOnly();return{cut:m("cut"),copy:m("copy"),paste:m("paste")}})})();
+(function(){function a(d,c,j,e,l){var g=b.lang.clipboard[c];b.addCommand(c,j);b.ui.addButton&&b.ui.addButton(d,{label:g,command:c,toolbar:"clipboard,"+e});b.addMenuItems&&b.addMenuItem(c,{label:g,command:c,group:"clipboard",order:l})}a("Cut","cut",c("cut"),10,1);a("Copy","copy",c("copy"),20,4);a("Paste","paste",d(),30,8)})();b.getClipboardData=function(a,d){function c(a){a.removeListener();a.cancel();d(a.data)}function j(a){a.removeListener();a.cancel();i=!0;d({type:f,dataValue:a.data})}function l(){this.customTitle=
+a&&a.title}var g=!1,f="auto",i=!1;d||(d=a,a=null);b.on("paste",c,null,null,0);b.on("beforePaste",function(a){a.removeListener();g=true;f=a.data.type},null,null,1E3);!1===s()&&(b.removeListener("paste",c),g&&b.fire("pasteDialog",l)?(b.on("pasteDialogCommit",j),b.on("dialogHide",function(a){a.removeListener();a.data.removeListener("pasteDialogCommit",j);setTimeout(function(){i||d(null)},10)})):d(null))}}function w(b){if(CKEDITOR.env.webkit){if(!b.match(/^[^<]*$/g)&&!b.match(/^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi))return"html"}else if(CKEDITOR.env.ie){if(!b.match(/^([^<]|<br( ?\/)?>)*$/gi)&&
+!b.match(/^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi))return"html"}else if(CKEDITOR.env.gecko){if(!b.match(/^([^<]|<br( ?\/)?>)*$/gi))return"html"}else return"html";return"htmlifiedtext"}function x(b,a){function c(a){return CKEDITOR.tools.repeat("</p><p>",~~(a/2))+(1==a%2?"<br>":"")}a=a.replace(/\s+/g," ").replace(/> +</g,"><").replace(/<br ?\/>/gi,"<br>");a=a.replace(/<\/?[A-Z]+>/g,function(a){return a.toLowerCase()});if(a.match(/^[^<]$/))return a;CKEDITOR.env.webkit&&-1<a.indexOf("<div>")&&(a=a.replace(/^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g,
+"<br>").replace(/^(<div>(<br>|)<\/div>){2}(?!$)/g,"<div></div>"),a.match(/<div>(<br>|)<\/div>/)&&(a="<p>"+a.replace(/(<div>(<br>|)<\/div>)+/g,function(a){return c(a.split("</div><div>").length+1)})+"</p>"),a=a.replace(/<\/div><div>/g,"<br>"),a=a.replace(/<\/?div>/g,""));CKEDITOR.env.gecko&&b.enterMode!=CKEDITOR.ENTER_BR&&(CKEDITOR.env.gecko&&(a=a.replace(/^<br><br>$/,"<br>")),-1<a.indexOf("<br><br>")&&(a="<p>"+a.replace(/(<br>){2,}/g,function(a){return c(a.length/4)})+"</p>"));return o(b,a)}function y(){var b=
+new CKEDITOR.htmlParser.filter,a={blockquote:1,dl:1,fieldset:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,ol:1,p:1,table:1,ul:1},c=CKEDITOR.tools.extend({br:0},CKEDITOR.dtd.$inline),d={p:1,br:1,"cke:br":1},g=CKEDITOR.dtd,i=CKEDITOR.tools.extend({area:1,basefont:1,embed:1,iframe:1,map:1,object:1,param:1},CKEDITOR.dtd.$nonBodyContent,CKEDITOR.dtd.$cdata),h=function(a){delete a.name;a.add(new CKEDITOR.htmlParser.text(" "))},f=function(a){for(var b=a,c;(b=b.next)&&b.name&&b.name.match(/^h\d$/);){c=new CKEDITOR.htmlParser.element("cke:br");
+c.isEmpty=!0;for(a.add(c);c=b.children.shift();)a.add(c)}};b.addRules({elements:{h1:f,h2:f,h3:f,h4:f,h5:f,h6:f,img:function(a){var a=CKEDITOR.tools.trim(a.attributes.alt||""),b=" ";a&&!a.match(/(^http|\.(jpe?g|gif|png))/i)&&(b=" ["+a+"] ");return new CKEDITOR.htmlParser.text(b)},td:h,th:h,$:function(b){var f=b.name,h;if(i[f])return!1;b.attributes={};if("br"==f)return b;if(a[f])b.name="p";else if(c[f])delete b.name;else if(g[f]){h=new CKEDITOR.htmlParser.element("cke:br");h.isEmpty=!0;if(CKEDITOR.dtd.$empty[f])return h;
+b.add(h,0);h=h.clone();h.isEmpty=!0;b.add(h);delete b.name}d[b.name]||delete b.name;return b}}},{applyToAll:!0});return b}function z(b,a,c){var a=new CKEDITOR.htmlParser.fragment.fromHtml(a),d=new CKEDITOR.htmlParser.basicWriter;a.writeHtml(d,c);var a=d.getHtml(),a=a.replace(/\s*(<\/?[a-z:]+ ?\/?>)\s*/g,"$1").replace(/(<cke:br \/>){2,}/g,"<cke:br />").replace(/(<cke:br \/>)(<\/?p>|<br \/>)/g,"$2").replace(/(<\/?p>|<br \/>)(<cke:br \/>)/g,"$1").replace(/<(cke:)?br( \/)?>/g,"<br>").replace(/<p><\/p>/g,
+""),g=0,a=a.replace(/<\/?p>/g,function(a){if("<p>"==a){if(1<++g)return"</p><p>"}else if(0<--g)return"</p><p>";return a}).replace(/<p><\/p>/g,"");return o(b,a)}function o(b,a){b.enterMode==CKEDITOR.ENTER_BR?a=a.replace(/(<\/p><p>)+/g,function(a){return CKEDITOR.tools.repeat("<br>",2*(a.length/7))}).replace(/<\/?p>/g,""):b.enterMode==CKEDITOR.ENTER_DIV&&(a=a.replace(/<(\/)?p>/g,"<$1div>"));return a}CKEDITOR.plugins.add("clipboard",{requires:"dialog",init:function(b){var a;v(b);CKEDITOR.dialog.add("paste",
+CKEDITOR.getUrl(this.path+"dialogs/paste.js"));b.on("paste",function(a){var b=a.data.dataValue,g=CKEDITOR.dtd.$block;-1<b.indexOf("Apple-")&&(b=b.replace(/<span class="Apple-converted-space">&nbsp;<\/span>/gi," "),"html"!=a.data.type&&(b=b.replace(/<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi,function(a,b){return b.replace(/\t/g,"&nbsp;&nbsp; &nbsp;")})),-1<b.indexOf('<br class="Apple-interchange-newline">')&&(a.data.startsWithEOL=1,a.data.preSniffing="html",b=b.replace(/<br class="Apple-interchange-newline">/,
+"")),b=b.replace(/(<[^>]+) class="Apple-[^"]*"/gi,"$1"));if(b.match(/^<[^<]+cke_(editable|contents)/i)){var i,h,f=new CKEDITOR.dom.element("div");for(f.setHtml(b);1==f.getChildCount()&&(i=f.getFirst())&&i.type==CKEDITOR.NODE_ELEMENT&&(i.hasClass("cke_editable")||i.hasClass("cke_contents"));)f=h=i;h&&(b=h.getHtml().replace(/<br>$/i,""))}CKEDITOR.env.ie?b=b.replace(/^&nbsp;(?: |\r\n)?<(\w+)/g,function(b,d){if(d.toLowerCase()in g){a.data.preSniffing="html";return"<"+d}return b}):CKEDITOR.env.webkit?
+b=b.replace(/<\/(\w+)><div><br><\/div>$/,function(b,d){if(d in g){a.data.endsWithEOL=1;return"</"+d+">"}return b}):CKEDITOR.env.gecko&&(b=b.replace(/(\s)<br>$/,"$1"));a.data.dataValue=b},null,null,3);b.on("paste",function(c){var c=c.data,d=c.type,g=c.dataValue,i,h=b.config.clipboard_defaultContentType||"html";i="html"==d||"html"==c.preSniffing?"html":w(g);"htmlifiedtext"==i?g=x(b.config,g):"text"==d&&"html"==i&&(g=z(b.config,g,a||(a=y(b))));c.startsWithEOL&&(g='<br data-cke-eol="1">'+g);c.endsWithEOL&&
+(g+='<br data-cke-eol="1">');"auto"==d&&(d="html"==i||"html"==h?"html":"text");c.type=d;c.dataValue=g;delete c.preSniffing;delete c.startsWithEOL;delete c.endsWithEOL},null,null,6);b.on("paste",function(a){a=a.data;b.insertHtml(a.dataValue,a.type);setTimeout(function(){b.fire("afterPaste")},0)},null,null,1E3);b.on("pasteDialog",function(a){setTimeout(function(){b.openDialog("paste",a.data)},0)})}})})();(function(){CKEDITOR.plugins.add("panel",{beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_PANEL,CKEDITOR.ui.panel.handler)}});CKEDITOR.UI_PANEL="panel";CKEDITOR.ui.panel=function(a,b){b&&CKEDITOR.tools.extend(this,b);CKEDITOR.tools.extend(this,{className:"",css:[]});this.id=CKEDITOR.tools.getNextId();this.document=a;this.isFramed=this.forceIFrame||this.css.length;this._={blocks:{}}};CKEDITOR.ui.panel.handler={create:function(a){return new CKEDITOR.ui.panel(a)}};var f=CKEDITOR.addTemplate("panel",
+'<div lang="{langCode}" id="{id}" dir={dir} class="cke cke_reset_all {editorId} cke_panel cke_panel {cls} cke_{dir}" style="z-index:{z-index}" role="presentation">{frame}</div>'),g=CKEDITOR.addTemplate("panel-frame",'<iframe id="{id}" class="cke_panel_frame" role="presentation" frameborder="0" src="{src}"></iframe>'),h=CKEDITOR.addTemplate("panel-frame-inner",'<!DOCTYPE html><html class="cke_panel_container {env}" dir="{dir}" lang="{langCode}"><head>{css}</head><body class="cke_{dir}" style="margin:0;padding:0" onload="{onload}"></body></html>');
+CKEDITOR.ui.panel.prototype={render:function(a,b){this.getHolderElement=function(){var a=this._.holder;if(!a){if(this.isFramed){var a=this.document.getById(this.id+"_frame"),b=a.getParent(),a=a.getFrameDocument();CKEDITOR.env.iOS&&b.setStyles({overflow:"scroll","-webkit-overflow-scrolling":"touch"});b=CKEDITOR.tools.addFunction(CKEDITOR.tools.bind(function(){this.isLoaded=!0;if(this.onLoad)this.onLoad()},this));a.write(h.output(CKEDITOR.tools.extend({css:CKEDITOR.tools.buildStyleHtml(this.css),onload:"window.parent.CKEDITOR.tools.callFunction("+
+b+");"},d)));a.getWindow().$.CKEDITOR=CKEDITOR;a.on("keydown",function(a){var b=a.data.getKeystroke(),c=this.document.getById(this.id).getAttribute("dir");this._.onKeyDown&&!1===this._.onKeyDown(b)?a.data.preventDefault():(27==b||b==("rtl"==c?39:37))&&this.onEscape&&!1===this.onEscape(b)&&a.data.preventDefault()},this);a=a.getBody();a.unselectable();CKEDITOR.env.air&&CKEDITOR.tools.callFunction(b)}else a=this.document.getById(this.id);this._.holder=a}return a};var d={editorId:a.id,id:this.id,langCode:a.langCode,
+dir:a.lang.dir,cls:this.className,frame:"",env:CKEDITOR.env.cssClass,"z-index":a.config.baseFloatZIndex+1};if(this.isFramed){var e=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie?"javascript:void(function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+"}())":"";d.frame=g.output({id:this.id+"_frame",src:e})}e=f.output(d);b&&b.push(e);return e},addBlock:function(a,b){b=this._.blocks[a]=b instanceof CKEDITOR.ui.panel.block?b:new CKEDITOR.ui.panel.block(this.getHolderElement(),
+b);this._.currentBlock||this.showBlock(a);return b},getBlock:function(a){return this._.blocks[a]},showBlock:function(a){var a=this._.blocks[a],b=this._.currentBlock,d=!this.forceIFrame||CKEDITOR.env.ie?this._.holder:this.document.getById(this.id+"_frame");b&&b.hide();this._.currentBlock=a;CKEDITOR.fire("ariaWidget",d);a._.focusIndex=-1;this._.onKeyDown=a.onKeyDown&&CKEDITOR.tools.bind(a.onKeyDown,a);a.show();return a},destroy:function(){this.element&&this.element.remove()}};CKEDITOR.ui.panel.block=
+CKEDITOR.tools.createClass({$:function(a,b){this.element=a.append(a.getDocument().createElement("div",{attributes:{tabindex:-1,"class":"cke_panel_block"},styles:{display:"none"}}));b&&CKEDITOR.tools.extend(this,b);this.element.setAttributes({role:this.attributes.role||"presentation","aria-label":this.attributes["aria-label"],title:this.attributes.title||this.attributes["aria-label"]});this.keys={};this._.focusIndex=-1;this.element.disableContextMenu()},_:{markItem:function(a){-1!=a&&(a=this.element.getElementsByTag("a").getItem(this._.focusIndex=
+a),CKEDITOR.env.webkit&&a.getDocument().getWindow().focus(),a.focus(),this.onMark&&this.onMark(a))}},proto:{show:function(){this.element.setStyle("display","")},hide:function(){(!this.onHide||!0!==this.onHide.call(this))&&this.element.setStyle("display","none")},onKeyDown:function(a,b){var d=this.keys[a];switch(d){case "next":for(var e=this._.focusIndex,d=this.element.getElementsByTag("a"),c;c=d.getItem(++e);)if(c.getAttribute("_cke_focus")&&c.$.offsetWidth){this._.focusIndex=e;c.focus();break}return!c&&
+!b?(this._.focusIndex=-1,this.onKeyDown(a,1)):!1;case "prev":e=this._.focusIndex;for(d=this.element.getElementsByTag("a");0<e&&(c=d.getItem(--e));){if(c.getAttribute("_cke_focus")&&c.$.offsetWidth){this._.focusIndex=e;c.focus();break}c=null}return!c&&!b?(this._.focusIndex=d.count(),this.onKeyDown(a,1)):!1;case "click":case "mouseup":return e=this._.focusIndex,(c=0<=e&&this.element.getElementsByTag("a").getItem(e))&&(c.$[d]?c.$[d]():c.$["on"+d]()),!1}return!0}}})})();CKEDITOR.plugins.add("floatpanel",{requires:"panel"});
+(function(){function r(a,b,c,i,f){var f=CKEDITOR.tools.genKey(b.getUniqueId(),c.getUniqueId(),a.lang.dir,a.uiColor||"",i.css||"",f||""),h=g[f];h||(h=g[f]=new CKEDITOR.ui.panel(b,i),h.element=c.append(CKEDITOR.dom.element.createFromHtml(h.render(a),b)),h.element.setStyles({display:"none",position:"absolute"}));return h}var g={};CKEDITOR.ui.floatPanel=CKEDITOR.tools.createClass({$:function(a,b,c,i){function f(){d.hide()}c.forceIFrame=1;c.toolbarRelated&&a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE&&
+(b=CKEDITOR.document.getById("cke_"+a.name));var h=b.getDocument(),i=r(a,h,b,c,i||0),j=i.element,l=j.getFirst(),d=this;j.disableContextMenu();this.element=j;this._={editor:a,panel:i,parentElement:b,definition:c,document:h,iframe:l,children:[],dir:a.lang.dir};a.on("mode",f);a.on("resize",f);if(!CKEDITOR.env.iOS)h.getWindow().on("resize",f)},proto:{addBlock:function(a,b){return this._.panel.addBlock(a,b)},addListBlock:function(a,b){return this._.panel.addListBlock(a,b)},getBlock:function(a){return this._.panel.getBlock(a)},
+showBlock:function(a,b,c,i,f,h){var j=this._.panel,l=j.showBlock(a);this.allowBlur(!1);a=this._.editor.editable();this._.returnFocus=a.hasFocus?a:new CKEDITOR.dom.element(CKEDITOR.document.$.activeElement);this._.hideTimeout=0;var d=this.element,a=this._.iframe,a=CKEDITOR.env.ie?a:new CKEDITOR.dom.window(a.$.contentWindow),g=d.getDocument(),o=this._.parentElement.getPositionedAncestor(),p=b.getDocumentPosition(g),g=o?o.getDocumentPosition(g):{x:0,y:0},m="rtl"==this._.dir,e=p.x+(i||0)-g.x,k=p.y+(f||
+0)-g.y;if(m&&(1==c||4==c))e+=b.$.offsetWidth;else if(!m&&(2==c||3==c))e+=b.$.offsetWidth-1;if(3==c||4==c)k+=b.$.offsetHeight-1;this._.panel._.offsetParentId=b.getId();d.setStyles({top:k+"px",left:0,display:""});d.setOpacity(0);d.getFirst().removeStyle("width");this._.editor.focusManager.add(a);this._.blurSet||(CKEDITOR.event.useCapture=!0,a.on("blur",function(a){function q(){delete this._.returnFocus;this.hide()}this.allowBlur()&&a.data.getPhase()==CKEDITOR.EVENT_PHASE_AT_TARGET&&(this.visible&&!this._.activeChild)&&
+(CKEDITOR.env.iOS?this._.hideTimeout||(this._.hideTimeout=CKEDITOR.tools.setTimeout(q,0,this)):q.call(this))},this),a.on("focus",function(){this._.focused=!0;this.hideChild();this.allowBlur(!0)},this),CKEDITOR.env.iOS&&(a.on("touchstart",function(){clearTimeout(this._.hideTimeout)},this),a.on("touchend",function(){this._.hideTimeout=0;this.focus()},this)),CKEDITOR.event.useCapture=!1,this._.blurSet=1);j.onEscape=CKEDITOR.tools.bind(function(a){if(this.onEscape&&this.onEscape(a)===false)return false},
+this);CKEDITOR.tools.setTimeout(function(){var a=CKEDITOR.tools.bind(function(){d.removeStyle("width");if(l.autoSize){var a=l.element.getDocument(),a=(CKEDITOR.env.webkit?l.element:a.getBody()).$.scrollWidth;CKEDITOR.env.ie&&(CKEDITOR.env.quirks&&a>0)&&(a=a+((d.$.offsetWidth||0)-(d.$.clientWidth||0)+3));d.setStyle("width",a+10+"px");a=l.element.$.scrollHeight;CKEDITOR.env.ie&&(CKEDITOR.env.quirks&&a>0)&&(a=a+((d.$.offsetHeight||0)-(d.$.clientHeight||0)+3));d.setStyle("height",a+"px");j._.currentBlock.element.setStyle("display",
+"none").removeStyle("display")}else d.removeStyle("height");m&&(e=e-d.$.offsetWidth);d.setStyle("left",e+"px");var b=j.element.getWindow(),a=d.$.getBoundingClientRect(),b=b.getViewPaneSize(),c=a.width||a.right-a.left,f=a.height||a.bottom-a.top,i=m?a.right:b.width-a.left,g=m?b.width-a.right:a.left;m?i<c&&(e=g>c?e+c:b.width>c?e-a.left:e-a.right+b.width):i<c&&(e=g>c?e-c:b.width>c?e-a.right+b.width:e-a.left);c=a.top;b.height-a.top<f&&(k=c>f?k-f:b.height>f?k-a.bottom+b.height:k-a.top);if(CKEDITOR.env.ie){b=
+a=new CKEDITOR.dom.element(d.$.offsetParent);b.getName()=="html"&&(b=b.getDocument().getBody());b.getComputedStyle("direction")=="rtl"&&(e=CKEDITOR.env.ie8Compat?e-d.getDocument().getDocumentElement().$.scrollLeft*2:e-(a.$.scrollWidth-a.$.clientWidth))}var a=d.getFirst(),n;(n=a.getCustomData("activePanel"))&&n.onHide&&n.onHide.call(this,1);a.setCustomData("activePanel",this);d.setStyles({top:k+"px",left:e+"px"});d.setOpacity(1);h&&h()},this);j.isLoaded?a():j.onLoad=a;CKEDITOR.tools.setTimeout(function(){var a=
+CKEDITOR.env.webkit&&CKEDITOR.document.getWindow().getScrollPosition().y;this.focus();l.element.focus();if(CKEDITOR.env.webkit)CKEDITOR.document.getBody().$.scrollTop=a;this.allowBlur(true);this._.editor.fire("panelShow",this)},0,this)},CKEDITOR.env.air?200:0,this);this.visible=1;this.onShow&&this.onShow.call(this)},focus:function(){if(CKEDITOR.env.webkit){var a=CKEDITOR.document.getActive();a&&!a.equals(this._.iframe)&&a.$.blur()}(this._.lastFocused||this._.iframe.getFrameDocument().getWindow()).focus()},
+blur:function(){var a=this._.iframe.getFrameDocument().getActive();a&&a.is("a")&&(this._.lastFocused=a)},hide:function(a){if(this.visible&&(!this.onHide||!0!==this.onHide.call(this))){this.hideChild();CKEDITOR.env.gecko&&this._.iframe.getFrameDocument().$.activeElement.blur();this.element.setStyle("display","none");this.visible=0;this.element.getFirst().removeCustomData("activePanel");if(a=a&&this._.returnFocus)CKEDITOR.env.webkit&&a.type&&a.getWindow().$.focus(),a.focus();delete this._.lastFocused;
+this._.editor.fire("panelHide",this)}},allowBlur:function(a){var b=this._.panel;void 0!==a&&(b.allowBlur=a);return b.allowBlur},showAsChild:function(a,b,c,g,f,h){this._.activeChild==a&&a._.panel._.offsetParentId==c.getId()||(this.hideChild(),a.onHide=CKEDITOR.tools.bind(function(){CKEDITOR.tools.setTimeout(function(){this._.focused||this.hide()},0,this)},this),this._.activeChild=a,this._.focused=!1,a.showBlock(b,c,g,f,h),this.blur(),(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&setTimeout(function(){a.element.getChild(0).$.style.cssText+=
+""},100))},hideChild:function(a){var b=this._.activeChild;b&&(delete b.onHide,delete this._.activeChild,b.hide(),a&&this.focus())}}});CKEDITOR.on("instanceDestroyed",function(){var a=CKEDITOR.tools.isEmpty(CKEDITOR.instances),b;for(b in g){var c=g[b];a?c.destroy():c.element.hide()}a&&(g={})})})();CKEDITOR.plugins.add("menu",{requires:"floatpanel",beforeInit:function(g){for(var h=g.config.menu_groups.split(","),m=g._.menuGroups={},l=g._.menuItems={},a=0;a<h.length;a++)m[h[a]]=a+1;g.addMenuGroup=function(b,a){m[b]=a||100};g.addMenuItem=function(a,c){m[c.group]&&(l[a]=new CKEDITOR.menuItem(this,a,c))};g.addMenuItems=function(a){for(var c in a)this.addMenuItem(c,a[c])};g.getMenuItem=function(a){return l[a]};g.removeMenuItem=function(a){delete l[a]}}});
+(function(){function g(a){a.sort(function(a,c){return a.group<c.group?-1:a.group>c.group?1:a.order<c.order?-1:a.order>c.order?1:0})}var h='<span class="cke_menuitem"><a id="{id}" class="cke_menubutton cke_menubutton__{name} cke_menubutton_{state} {cls}" href="{href}" title="{title}" tabindex="-1"_cke_focus=1 hidefocus="true" role="{role}" aria-haspopup="{hasPopup}" aria-disabled="{disabled}" {ariaChecked}';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(h+=' onkeypress="return false;"');CKEDITOR.env.gecko&&
+(h+=' onblur="this.style.cssText = this.style.cssText;"');var h=h+(' onmouseover="CKEDITOR.tools.callFunction({hoverFn},{index});" onmouseout="CKEDITOR.tools.callFunction({moveOutFn},{index});" '+(CKEDITOR.env.ie?'onclick="return false;" onmouseup':"onclick")+'="CKEDITOR.tools.callFunction({clickFn},{index}); return false;">'),m=CKEDITOR.addTemplate("menuItem",h+'<span class="cke_menubutton_inner"><span class="cke_menubutton_icon"><span class="cke_button_icon cke_button__{iconName}_icon" style="{iconStyle}"></span></span><span class="cke_menubutton_label">{label}</span>{arrowHtml}</span></a></span>'),
+l=CKEDITOR.addTemplate("menuArrow",'<span class="cke_menuarrow"><span>{label}</span></span>');CKEDITOR.menu=CKEDITOR.tools.createClass({$:function(a,b){b=this._.definition=b||{};this.id=CKEDITOR.tools.getNextId();this.editor=a;this.items=[];this._.listeners=[];this._.level=b.level||1;var c=CKEDITOR.tools.extend({},b.panel,{css:[CKEDITOR.skin.getPath("editor")],level:this._.level-1,block:{}}),k=c.block.attributes=c.attributes||{};!k.role&&(k.role="menu");this._.panelDefinition=c},_:{onShow:function(){var a=
+this.editor.getSelection(),b=a&&a.getStartElement(),c=this.editor.elementPath(),k=this._.listeners;this.removeAll();for(var e=0;e<k.length;e++){var j=k[e](b,a,c);if(j)for(var i in j){var f=this.editor.getMenuItem(i);if(f&&(!f.command||this.editor.getCommand(f.command).state))f.state=j[i],this.add(f)}}},onClick:function(a){this.hide();if(a.onClick)a.onClick();else a.command&&this.editor.execCommand(a.command)},onEscape:function(a){var b=this.parent;b?b._.panel.hideChild(1):27==a&&this.hide(1);return!1},
+onHide:function(){this.onHide&&this.onHide()},showSubMenu:function(a){var b=this._.subMenu,c=this.items[a];if(c=c.getItems&&c.getItems()){b?b.removeAll():(b=this._.subMenu=new CKEDITOR.menu(this.editor,CKEDITOR.tools.extend({},this._.definition,{level:this._.level+1},!0)),b.parent=this,b._.onClick=CKEDITOR.tools.bind(this._.onClick,this));for(var k in c){var e=this.editor.getMenuItem(k);e&&(e.state=c[k],b.add(e))}var j=this._.panel.getBlock(this.id).element.getDocument().getById(this.id+(""+a));setTimeout(function(){b.show(j,
+2)},0)}else this._.panel.hideChild(1)}},proto:{add:function(a){a.order||(a.order=this.items.length);this.items.push(a)},removeAll:function(){this.items=[]},show:function(a,b,c,k){if(!this.parent&&(this._.onShow(),!this.items.length))return;var b=b||("rtl"==this.editor.lang.dir?2:1),e=this.items,j=this.editor,i=this._.panel,f=this._.element;if(!i){i=this._.panel=new CKEDITOR.ui.floatPanel(this.editor,CKEDITOR.document.getBody(),this._.panelDefinition,this._.level);i.onEscape=CKEDITOR.tools.bind(function(a){if(!1===
+this._.onEscape(a))return!1},this);i.onShow=function(){i._.panel.getHolderElement().getParent().addClass("cke cke_reset_all")};i.onHide=CKEDITOR.tools.bind(function(){this._.onHide&&this._.onHide()},this);f=i.addBlock(this.id,this._.panelDefinition.block);f.autoSize=!0;var d=f.keys;d[40]="next";d[9]="next";d[38]="prev";d[CKEDITOR.SHIFT+9]="prev";d["rtl"==j.lang.dir?37:39]=CKEDITOR.env.ie?"mouseup":"click";d[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(d[13]="mouseup");f=this._.element=
+f.element;d=f.getDocument();d.getBody().setStyle("overflow","hidden");d.getElementsByTag("html").getItem(0).setStyle("overflow","hidden");this._.itemOverFn=CKEDITOR.tools.addFunction(function(a){clearTimeout(this._.showSubTimeout);this._.showSubTimeout=CKEDITOR.tools.setTimeout(this._.showSubMenu,j.config.menu_subMenuDelay||400,this,[a])},this);this._.itemOutFn=CKEDITOR.tools.addFunction(function(){clearTimeout(this._.showSubTimeout)},this);this._.itemClickFn=CKEDITOR.tools.addFunction(function(a){var b=
+this.items[a];if(b.state==CKEDITOR.TRISTATE_DISABLED)this.hide(1);else if(b.getItems)this._.showSubMenu(a);else this._.onClick(b)},this)}g(e);for(var d=j.elementPath(),d=['<div class="cke_menu'+(d&&d.direction()!=j.lang.dir?" cke_mixed_dir_content":"")+'" role="presentation">'],h=e.length,m=h&&e[0].group,l=0;l<h;l++){var n=e[l];m!=n.group&&(d.push('<div class="cke_menuseparator" role="separator"></div>'),m=n.group);n.render(this,l,d)}d.push("</div>");f.setHtml(d.join(""));CKEDITOR.ui.fire("ready",
+this);this.parent?this.parent._.panel.showAsChild(i,this.id,a,b,c,k):i.showBlock(this.id,a,b,c,k);j.fire("menuShow",[i])},addListener:function(a){this._.listeners.push(a)},hide:function(a){this._.onHide&&this._.onHide();this._.panel&&this._.panel.hide(a)}}});CKEDITOR.menuItem=CKEDITOR.tools.createClass({$:function(a,b,c){CKEDITOR.tools.extend(this,c,{order:0,className:"cke_menubutton__"+b});this.group=a._.menuGroups[this.group];this.editor=a;this.name=b},proto:{render:function(a,b,c){var h=a.id+(""+
+b),e="undefined"==typeof this.state?CKEDITOR.TRISTATE_OFF:this.state,j="",i=e==CKEDITOR.TRISTATE_ON?"on":e==CKEDITOR.TRISTATE_DISABLED?"disabled":"off";this.role in{menuitemcheckbox:1,menuitemradio:1}&&(j=' aria-checked="'+(e==CKEDITOR.TRISTATE_ON?"true":"false")+'"');var f=this.getItems,d="&#"+("rtl"==this.editor.lang.dir?"9668":"9658")+";",g=this.name;this.icon&&!/\./.test(this.icon)&&(g=this.icon);a={id:h,name:this.name,iconName:g,label:this.label,cls:this.className||"",state:i,hasPopup:f?"true":
+"false",disabled:e==CKEDITOR.TRISTATE_DISABLED,title:this.label,href:"javascript:void('"+(this.label||"").replace("'")+"')",hoverFn:a._.itemOverFn,moveOutFn:a._.itemOutFn,clickFn:a._.itemClickFn,index:b,iconStyle:CKEDITOR.skin.getIconStyle(g,"rtl"==this.editor.lang.dir,g==this.icon?null:this.icon,this.iconOffset),arrowHtml:f?l.output({label:d}):"",role:this.role?this.role:"menuitem",ariaChecked:j};m.output(a,c)}}})})();CKEDITOR.config.menu_groups="clipboard,form,tablecell,tablecellproperties,tablerow,tablecolumn,table,anchor,link,image,flash,checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div";CKEDITOR.plugins.add("contextmenu",{requires:"menu",onLoad:function(){CKEDITOR.plugins.contextMenu=CKEDITOR.tools.createClass({base:CKEDITOR.menu,$:function(a){this.base.call(this,a,{panel:{className:"cke_menu_panel",attributes:{"aria-label":a.lang.contextmenu.options}}})},proto:{addTarget:function(a,e){a.on("contextmenu",function(a){var a=a.data,c=CKEDITOR.env.webkit?f:CKEDITOR.env.mac?a.$.metaKey:a.$.ctrlKey;if(!e||!c){a.preventDefault();if(CKEDITOR.env.mac&&CKEDITOR.env.webkit){var c=this.editor,
+b=(new CKEDITOR.dom.elementPath(a.getTarget(),c.editable())).contains(function(a){return a.hasAttribute("contenteditable")},!0);b&&"false"==b.getAttribute("contenteditable")&&c.getSelection().fake(b)}var b=a.getTarget().getDocument(),d=a.getTarget().getDocument().getDocumentElement(),c=!b.equals(CKEDITOR.document),b=b.getWindow().getScrollPosition(),g=c?a.$.clientX:a.$.pageX||b.x+a.$.clientX,h=c?a.$.clientY:a.$.pageY||b.y+a.$.clientY;CKEDITOR.tools.setTimeout(function(){this.open(d,null,g,h)},CKEDITOR.env.ie?
+200:0,this)}},this);if(CKEDITOR.env.webkit){var f,d=function(){f=0};a.on("keydown",function(a){f=CKEDITOR.env.mac?a.data.$.metaKey:a.data.$.ctrlKey});a.on("keyup",d);a.on("contextmenu",d)}},open:function(a,e,f,d){this.editor.focus();a=a||CKEDITOR.document.getDocumentElement();this.editor.selectionChange(1);this.show(a,e,f,d)}}})},beforeInit:function(a){var e=a.contextMenu=new CKEDITOR.plugins.contextMenu(a);a.on("contentDom",function(){e.addTarget(a.editable(),!1!==a.config.browserContextMenuOnCtrl)});
+a.addCommand("contextMenu",{exec:function(){a.contextMenu.open(a.document.getBody())}});a.setKeystroke(CKEDITOR.SHIFT+121,"contextMenu");a.setKeystroke(CKEDITOR.CTRL+CKEDITOR.SHIFT+121,"contextMenu")}});CKEDITOR.plugins.add("resize",{init:function(b){var f,g,n,o;function c(d){var e=f,l=g,c=e+(d.data.$.screenX-n)*("rtl"==h?-1:1),d=l+(d.data.$.screenY-o);i&&(e=Math.max(a.resize_minWidth,Math.min(c,a.resize_maxWidth)));m&&(l=Math.max(a.resize_minHeight,Math.min(d,a.resize_maxHeight)));b.resize(i?e:null,l)}function j(){CKEDITOR.document.removeListener("mousemove",c);CKEDITOR.document.removeListener("mouseup",j);b.document&&(b.document.removeListener("mousemove",c),b.document.removeListener("mouseup",
+j))}var a=b.config,q=b.ui.spaceId("resizer"),h=b.element?b.element.getDirection(1):"ltr";!a.resize_dir&&(a.resize_dir="vertical");void 0===a.resize_maxWidth&&(a.resize_maxWidth=3E3);void 0===a.resize_maxHeight&&(a.resize_maxHeight=3E3);void 0===a.resize_minWidth&&(a.resize_minWidth=750);void 0===a.resize_minHeight&&(a.resize_minHeight=250);if(!1!==a.resize_enabled){var k=null,i=("both"==a.resize_dir||"horizontal"==a.resize_dir)&&a.resize_minWidth!=a.resize_maxWidth,m=("both"==a.resize_dir||"vertical"==
+a.resize_dir)&&a.resize_minHeight!=a.resize_maxHeight,p=CKEDITOR.tools.addFunction(function(d){k||(k=b.getResizable());f=k.$.offsetWidth||0;g=k.$.offsetHeight||0;n=d.screenX;o=d.screenY;a.resize_minWidth>f&&(a.resize_minWidth=f);a.resize_minHeight>g&&(a.resize_minHeight=g);CKEDITOR.document.on("mousemove",c);CKEDITOR.document.on("mouseup",j);b.document&&(b.document.on("mousemove",c),b.document.on("mouseup",j));d.preventDefault&&d.preventDefault()});b.on("destroy",function(){CKEDITOR.tools.removeFunction(p)});
+b.on("uiSpace",function(a){if("bottom"==a.data.space){var e="";i&&!m&&(e=" cke_resizer_horizontal");!i&&m&&(e=" cke_resizer_vertical");var c='<span id="'+q+'" class="cke_resizer'+e+" cke_resizer_"+h+'" title="'+CKEDITOR.tools.htmlEncode(b.lang.common.resize)+'" onmousedown="CKEDITOR.tools.callFunction('+p+', event)">'+("ltr"==h?"â—¢":"â—£")+"</span>";"ltr"==h&&"ltr"==e?a.data.html+=c:a.data.html=c+a.data.html}},b,null,100);b.on("maximize",function(a){b.ui.space("resizer")[a.data==CKEDITOR.TRISTATE_ON?
+"hide":"show"]()})}}});(function(){var c='<a id="{id}" class="cke_button cke_button__{name} cke_button_{state} {cls}"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href=\"javascript:void('{titleJs}')\"")+' title="{title}" tabindex="-1" hidefocus="true" role="button" aria-labelledby="{id}_label" aria-haspopup="{hasArrow}" aria-disabled="{ariaDisabled}"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(c+=' onkeypress="return false;"');CKEDITOR.env.gecko&&(c+=' onblur="this.style.cssText = this.style.cssText;"');var c=c+(' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event);" onfocus="return CKEDITOR.tools.callFunction({focusFn},event);" '+
+(CKEDITOR.env.ie?'onclick="return false;" onmouseup':"onclick")+'="CKEDITOR.tools.callFunction({clickFn},this);return false;"><span class="cke_button_icon cke_button__{iconName}_icon" style="{style}"'),c=c+'>&nbsp;</span><span id="{id}_label" class="cke_button_label cke_button__{name}_label" aria-hidden="false">{label}</span>{arrowHtml}</a>',o=CKEDITOR.addTemplate("buttonArrow",'<span class="cke_button_arrow">'+(CKEDITOR.env.hc?"&#9660;":"")+"</span>"),p=CKEDITOR.addTemplate("button",c);CKEDITOR.plugins.add("button",
+{beforeInit:function(a){a.ui.addHandler(CKEDITOR.UI_BUTTON,CKEDITOR.ui.button.handler)}});CKEDITOR.UI_BUTTON="button";CKEDITOR.ui.button=function(a){CKEDITOR.tools.extend(this,a,{title:a.label,click:a.click||function(b){b.execCommand(a.command)}});this._={}};CKEDITOR.ui.button.handler={create:function(a){return new CKEDITOR.ui.button(a)}};CKEDITOR.ui.button.prototype={render:function(a,b){function c(){var e=a.mode;e&&(e=this.modes[e]?void 0!==i[e]?i[e]:CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,
+e=a.readOnly&&!this.readOnly?CKEDITOR.TRISTATE_DISABLED:e,this.setState(e),this.refresh&&this.refresh())}var j=CKEDITOR.env,k=this._.id=CKEDITOR.tools.getNextId(),f="",g=this.command,l;this._.editor=a;var d={id:k,button:this,editor:a,focus:function(){CKEDITOR.document.getById(k).focus()},execute:function(){this.button.click(a)},attach:function(a){this.button.attach(a)}},q=CKEDITOR.tools.addFunction(function(a){if(d.onkey)return a=new CKEDITOR.dom.event(a),!1!==d.onkey(d,a.getKeystroke())}),r=CKEDITOR.tools.addFunction(function(a){var b;
+d.onfocus&&(b=!1!==d.onfocus(d,new CKEDITOR.dom.event(a)));return b}),m=0;d.clickFn=l=CKEDITOR.tools.addFunction(function(){m&&(a.unlockSelection(1),m=0);d.execute();j.iOS&&a.focus()});if(this.modes){var i={};a.on("beforeModeUnload",function(){a.mode&&this._.state!=CKEDITOR.TRISTATE_DISABLED&&(i[a.mode]=this._.state)},this);a.on("activeFilterChange",c,this);a.on("mode",c,this);!this.readOnly&&a.on("readOnly",c,this)}else if(g&&(g=a.getCommand(g)))g.on("state",function(){this.setState(g.state)},this),
+f+=g.state==CKEDITOR.TRISTATE_ON?"on":g.state==CKEDITOR.TRISTATE_DISABLED?"disabled":"off";if(this.directional)a.on("contentDirChanged",function(b){var c=CKEDITOR.document.getById(this._.id),d=c.getFirst(),b=b.data;b!=a.lang.dir?c.addClass("cke_"+b):c.removeClass("cke_ltr").removeClass("cke_rtl");d.setAttribute("style",CKEDITOR.skin.getIconStyle(h,"rtl"==b,this.icon,this.iconOffset))},this);g||(f+="off");var n=this.name||this.command,h=n;this.icon&&!/\./.test(this.icon)&&(h=this.icon,this.icon=null);
+f={id:k,name:n,iconName:h,label:this.label,cls:this.className||"",state:f,ariaDisabled:"disabled"==f?"true":"false",title:this.title,titleJs:j.gecko&&!j.hc?"":(this.title||"").replace("'",""),hasArrow:this.hasArrow?"true":"false",keydownFn:q,focusFn:r,clickFn:l,style:CKEDITOR.skin.getIconStyle(h,"rtl"==a.lang.dir,this.icon,this.iconOffset),arrowHtml:this.hasArrow?o.output():""};p.output(f,b);if(this.onRender)this.onRender();return d},setState:function(a){if(this._.state==a)return!1;this._.state=a;
+var b=CKEDITOR.document.getById(this._.id);return b?(b.setState(a,"cke_button"),a==CKEDITOR.TRISTATE_DISABLED?b.setAttribute("aria-disabled",!0):b.removeAttribute("aria-disabled"),this.hasArrow?(a=a==CKEDITOR.TRISTATE_ON?this._.editor.lang.button.selectedLabel.replace(/%1/g,this.label):this.label,CKEDITOR.document.getById(this._.id+"_label").setText(a)):a==CKEDITOR.TRISTATE_ON?b.setAttribute("aria-pressed",!0):b.removeAttribute("aria-pressed"),!0):!1},getState:function(){return this._.state},toFeature:function(a){if(this._.feature)return this._.feature;
+var b=this;!this.allowedContent&&(!this.requiredContent&&this.command)&&(b=a.getCommand(this.command)||b);return this._.feature=b}};CKEDITOR.ui.prototype.addButton=function(a,b){this.add(a,CKEDITOR.UI_BUTTON,b)}})();(function(){function w(a){function d(){for(var b=g(),e=CKEDITOR.tools.clone(a.config.toolbarGroups)||n(a),f=0;f<e.length;f++){var k=e[f];if("/"!=k){"string"==typeof k&&(k=e[f]={name:k});var i,d=k.groups;if(d)for(var h=0;h<d.length;h++)i=d[h],(i=b[i])&&c(k,i);(i=b[k.name])&&c(k,i)}}return e}function g(){var b={},c,f,e;for(c in a.ui.items)f=a.ui.items[c],e=f.toolbar||"others",e=e.split(","),f=e[0],e=parseInt(e[1]||-1,10),b[f]||(b[f]=[]),b[f].push({name:c,order:e});for(f in b)b[f]=b[f].sort(function(b,
+a){return b.order==a.order?0:0>a.order?-1:0>b.order?1:b.order<a.order?-1:1});return b}function c(c,e){if(e.length){c.items?c.items.push(a.ui.create("-")):c.items=[];for(var f;f=e.shift();)if(f="string"==typeof f?f:f.name,!b||-1==CKEDITOR.tools.indexOf(b,f))(f=a.ui.create(f))&&a.addFeature(f)&&c.items.push(f)}}function h(b){var a=[],e,d,h;for(e=0;e<b.length;++e)d=b[e],h={},"/"==d?a.push(d):CKEDITOR.tools.isArray(d)?(c(h,CKEDITOR.tools.clone(d)),a.push(h)):d.items&&(c(h,CKEDITOR.tools.clone(d.items)),
+h.name=d.name,a.push(h));return a}var b=a.config.removeButtons,b=b&&b.split(","),e=a.config.toolbar;"string"==typeof e&&(e=a.config["toolbar_"+e]);return a.toolbar=e?h(e):d()}function n(a){return a._.toolbarGroups||(a._.toolbarGroups=[{name:"document",groups:["mode","document","doctools"]},{name:"clipboard",groups:["clipboard","undo"]},{name:"editing",groups:["find","selection","spellchecker"]},{name:"forms"},"/",{name:"basicstyles",groups:["basicstyles","cleanup"]},{name:"paragraph",groups:["list",
+"indent","blocks","align","bidi"]},{name:"links"},{name:"insert"},"/",{name:"styles"},{name:"colors"},{name:"tools"},{name:"others"},{name:"about"}])}var u=function(){this.toolbars=[];this.focusCommandExecuted=!1};u.prototype.focus=function(){for(var a=0,d;d=this.toolbars[a++];)for(var g=0,c;c=d.items[g++];)if(c.focus){c.focus();return}};var x={modes:{wysiwyg:1,source:1},readOnly:1,exec:function(a){a.toolbox&&(a.toolbox.focusCommandExecuted=!0,CKEDITOR.env.ie||CKEDITOR.env.air?setTimeout(function(){a.toolbox.focus()},
+100):a.toolbox.focus())}};CKEDITOR.plugins.add("toolbar",{requires:"button",init:function(a){var d,g=function(c,h){var b,e="rtl"==a.lang.dir,j=a.config.toolbarGroupCycling,o=e?37:39,e=e?39:37,j=void 0===j||j;switch(h){case 9:case CKEDITOR.SHIFT+9:for(;!b||!b.items.length;)if(b=9==h?(b?b.next:c.toolbar.next)||a.toolbox.toolbars[0]:(b?b.previous:c.toolbar.previous)||a.toolbox.toolbars[a.toolbox.toolbars.length-1],b.items.length)for(c=b.items[d?b.items.length-1:0];c&&!c.focus;)(c=d?c.previous:c.next)||
+(b=0);c&&c.focus();return!1;case o:b=c;do b=b.next,!b&&j&&(b=c.toolbar.items[0]);while(b&&!b.focus);b?b.focus():g(c,9);return!1;case 40:return c.button&&c.button.hasArrow?(a.once("panelShow",function(b){b.data._.panel._.currentBlock.onKeyDown(40)}),c.execute()):g(c,40==h?o:e),!1;case e:case 38:b=c;do b=b.previous,!b&&j&&(b=c.toolbar.items[c.toolbar.items.length-1]);while(b&&!b.focus);b?b.focus():(d=1,g(c,CKEDITOR.SHIFT+9),d=0);return!1;case 27:return a.focus(),!1;case 13:case 32:return c.execute(),
+!1}return!0};a.on("uiSpace",function(c){if(c.data.space==a.config.toolbarLocation){c.removeListener();a.toolbox=new u;var d=CKEDITOR.tools.getNextId(),b=['<span id="',d,'" class="cke_voice_label">',a.lang.toolbar.toolbars,"</span>",'<span id="'+a.ui.spaceId("toolbox")+'" class="cke_toolbox" role="group" aria-labelledby="',d,'" onmousedown="return false;">'],d=!1!==a.config.toolbarStartupExpanded,e,j;a.config.toolbarCanCollapse&&a.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE&&b.push('<span class="cke_toolbox_main"'+
+(d?">":' style="display:none">'));for(var o=a.toolbox.toolbars,f=w(a),k=0;k<f.length;k++){var i,l=0,r,m=f[k],s;if(m)if(e&&(b.push("</span>"),j=e=0),"/"===m)b.push('<span class="cke_toolbar_break"></span>');else{s=m.items||m;for(var t=0;t<s.length;t++){var p=s[t],n;if(p)if(p.type==CKEDITOR.UI_SEPARATOR)j=e&&p;else{n=!1!==p.canGroup;if(!l){i=CKEDITOR.tools.getNextId();l={id:i,items:[]};r=m.name&&(a.lang.toolbar.toolbarGroups[m.name]||m.name);b.push('<span id="',i,'" class="cke_toolbar"',r?' aria-labelledby="'+
+i+'_label"':"",' role="toolbar">');r&&b.push('<span id="',i,'_label" class="cke_voice_label">',r,"</span>");b.push('<span class="cke_toolbar_start"></span>');var q=o.push(l)-1;0<q&&(l.previous=o[q-1],l.previous.next=l)}n?e||(b.push('<span class="cke_toolgroup" role="presentation">'),e=1):e&&(b.push("</span>"),e=0);i=function(c){c=c.render(a,b);q=l.items.push(c)-1;if(q>0){c.previous=l.items[q-1];c.previous.next=c}c.toolbar=l;c.onkey=g;c.onfocus=function(){a.toolbox.focusCommandExecuted||a.focus()}};
+j&&(i(j),j=0);i(p)}}e&&(b.push("</span>"),j=e=0);l&&b.push('<span class="cke_toolbar_end"></span></span>')}}a.config.toolbarCanCollapse&&b.push("</span>");if(a.config.toolbarCanCollapse&&a.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var v=CKEDITOR.tools.addFunction(function(){a.execCommand("toolbarCollapse")});a.on("destroy",function(){CKEDITOR.tools.removeFunction(v)});a.addCommand("toolbarCollapse",{readOnly:1,exec:function(b){var a=b.ui.space("toolbar_collapser"),c=a.getPrevious(),e=b.ui.space("contents"),
+d=c.getParent(),f=parseInt(e.$.style.height,10),h=d.$.offsetHeight,g=a.hasClass("cke_toolbox_collapser_min");g?(c.show(),a.removeClass("cke_toolbox_collapser_min"),a.setAttribute("title",b.lang.toolbar.toolbarCollapse)):(c.hide(),a.addClass("cke_toolbox_collapser_min"),a.setAttribute("title",b.lang.toolbar.toolbarExpand));a.getFirst().setText(g?"â–²":"â—€");e.setStyle("height",f-(d.$.offsetHeight-h)+"px");b.fire("resize")},modes:{wysiwyg:1,source:1}});a.setKeystroke(CKEDITOR.ALT+(CKEDITOR.env.ie||CKEDITOR.env.webkit?
+189:109),"toolbarCollapse");b.push('<a title="'+(d?a.lang.toolbar.toolbarCollapse:a.lang.toolbar.toolbarExpand)+'" id="'+a.ui.spaceId("toolbar_collapser")+'" tabIndex="-1" class="cke_toolbox_collapser');d||b.push(" cke_toolbox_collapser_min");b.push('" onclick="CKEDITOR.tools.callFunction('+v+')">','<span class="cke_arrow">&#9650;</span>',"</a>")}b.push("</span>");c.data.html+=b.join("")}});a.on("destroy",function(){if(this.toolbox){var a,d=0,b,e,g;for(a=this.toolbox.toolbars;d<a.length;d++){e=a[d].items;
+for(b=0;b<e.length;b++)g=e[b],g.clickFn&&CKEDITOR.tools.removeFunction(g.clickFn),g.keyDownFn&&CKEDITOR.tools.removeFunction(g.keyDownFn)}}});a.on("uiReady",function(){var c=a.ui.space("toolbox");c&&a.focusManager.add(c,1)});a.addCommand("toolbarFocus",x);a.setKeystroke(CKEDITOR.ALT+121,"toolbarFocus");a.ui.add("-",CKEDITOR.UI_SEPARATOR,{});a.ui.addHandler(CKEDITOR.UI_SEPARATOR,{create:function(){return{render:function(a,d){d.push('<span class="cke_toolbar_separator" role="separator"></span>');return{}}}}})}});
+CKEDITOR.ui.prototype.addToolbarGroup=function(a,d,g){var c=n(this.editor),h=0===d,b={name:a};if(g){if(g=CKEDITOR.tools.search(c,function(a){return a.name==g})){!g.groups&&(g.groups=[]);if(d&&(d=CKEDITOR.tools.indexOf(g.groups,d),0<=d)){g.groups.splice(d+1,0,a);return}h?g.groups.splice(0,0,a):g.groups.push(a);return}d=null}d&&(d=CKEDITOR.tools.indexOf(c,function(a){return a.name==d}));h?c.splice(0,0,a):"number"==typeof d?c.splice(d+1,0,b):c.push(a)}})();CKEDITOR.UI_SEPARATOR="separator";
+CKEDITOR.config.toolbarLocation="top";(function(){var k;function n(a,c){function j(d){d=i.list[d];if(d.equals(a.editable())||"true"==d.getAttribute("contenteditable")){var e=a.createRange();e.selectNodeContents(d);e.select()}else a.getSelection().selectElement(d);a.focus()}function s(){l&&l.setHtml(o);delete i.list}var m=a.ui.spaceId("path"),l,i=a._.elementsPath,n=i.idBase;c.html+='<span id="'+m+'_label" class="cke_voice_label">'+a.lang.elementspath.eleLabel+'</span><span id="'+m+'" class="cke_path" role="group" aria-labelledby="'+m+
+'_label">'+o+"</span>";a.on("uiReady",function(){var d=a.ui.space("path");d&&a.focusManager.add(d,1)});i.onClick=j;var t=CKEDITOR.tools.addFunction(j),u=CKEDITOR.tools.addFunction(function(d,e){var g=i.idBase,b,e=new CKEDITOR.dom.event(e);b="rtl"==a.lang.dir;switch(e.getKeystroke()){case b?39:37:case 9:return(b=CKEDITOR.document.getById(g+(d+1)))||(b=CKEDITOR.document.getById(g+"0")),b.focus(),!1;case b?37:39:case CKEDITOR.SHIFT+9:return(b=CKEDITOR.document.getById(g+(d-1)))||(b=CKEDITOR.document.getById(g+
+(i.list.length-1))),b.focus(),!1;case 27:return a.focus(),!1;case 13:case 32:return j(d),!1}return!0});a.on("selectionChange",function(){for(var d=[],e=i.list=[],g=[],b=i.filters,c=!0,j=a.elementPath().elements,f,k=j.length;k--;){var h=j[k],p=0;f=h.data("cke-display-name")?h.data("cke-display-name"):h.data("cke-real-element-type")?h.data("cke-real-element-type"):h.getName();c=h.hasAttribute("contenteditable")?"true"==h.getAttribute("contenteditable"):c;!c&&!h.hasAttribute("contenteditable")&&(p=1);
+for(var q=0;q<b.length;q++){var r=b[q](h,f);if(!1===r){p=1;break}f=r||f}p||(e.unshift(h),g.unshift(f))}e=e.length;for(b=0;b<e;b++)f=g[b],c=a.lang.elementspath.eleTitle.replace(/%1/,f),f=v.output({id:n+b,label:c,text:f,jsTitle:"javascript:void('"+f+"')",index:b,keyDownFn:u,clickFn:t}),d.unshift(f);l||(l=CKEDITOR.document.getById(m));g=l;g.setHtml(d.join("")+o);a.fire("elementsPathUpdate",{space:g})});a.on("readOnly",s);a.on("contentDomUnload",s);a.addCommand("elementsPathFocus",k);a.setKeystroke(CKEDITOR.ALT+
+122,"elementsPathFocus")}k={editorFocus:!1,readOnly:1,exec:function(a){(a=CKEDITOR.document.getById(a._.elementsPath.idBase+"0"))&&a.focus(CKEDITOR.env.ie||CKEDITOR.env.air)}};var o='<span class="cke_path_empty">&nbsp;</span>',c="";CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(c+=' onkeypress="return false;"');CKEDITOR.env.gecko&&(c+=' onblur="this.style.cssText = this.style.cssText;"');var v=CKEDITOR.addTemplate("pathItem",'<a id="{id}" href="{jsTitle}" tabindex="-1" class="cke_path_item" title="{label}"'+
+c+' hidefocus="true" onkeydown="return CKEDITOR.tools.callFunction({keyDownFn},{index}, event );" onclick="CKEDITOR.tools.callFunction({clickFn},{index}); return false;" role="button" aria-label="{label}">{text}</a>');CKEDITOR.plugins.add("elementspath",{init:function(a){a._.elementsPath={idBase:"cke_elementspath_"+CKEDITOR.tools.getNextNumber()+"_",filters:[]};a.on("uiSpace",function(c){"bottom"==c.data.space&&n(a,c.data)})}})})();(function(){function m(b,d,a){a=b.config.forceEnterMode||a;"wysiwyg"==b.mode&&(d||(d=b.activeEnterMode),b.elementPath().isContextFor("p")||(d=CKEDITOR.ENTER_BR,a=1),b.fire("saveSnapshot"),d==CKEDITOR.ENTER_BR?p(b,d,null,a):q(b,d,null,a),b.fire("saveSnapshot"))}function r(b){for(var b=b.getSelection().getRanges(!0),d=b.length-1;0<d;d--)b[d].deleteContents();return b[0]}function u(b){var d=b.startContainer.getAscendant(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&"true"==a.getAttribute("contenteditable")},
+!0);if(b.root.equals(d))return b;d=new CKEDITOR.dom.range(d);d.moveToRange(b);return d}CKEDITOR.plugins.add("enterkey",{init:function(b){b.addCommand("enter",{modes:{wysiwyg:1},editorFocus:!1,exec:function(b){m(b)}});b.addCommand("shiftEnter",{modes:{wysiwyg:1},editorFocus:!1,exec:function(b){m(b,b.activeShiftEnterMode,1)}});b.setKeystroke([[13,"enter"],[CKEDITOR.SHIFT+13,"shiftEnter"]])}});var v=CKEDITOR.dom.walker.whitespaces(),w=CKEDITOR.dom.walker.bookmark();CKEDITOR.plugins.enterkey={enterBlock:function(b,
+d,a,h){if(a=a||r(b)){var a=u(a),f=a.document,i=a.checkStartOfBlock(),k=a.checkEndOfBlock(),j=b.elementPath(a.startContainer),c=j.block,l=d==CKEDITOR.ENTER_DIV?"div":"p",e;if(i&&k){if(c&&(c.is("li")||c.getParent().is("li"))){c.is("li")||(c=c.getParent());a=c.getParent();e=a.getParent();var h=!c.hasPrevious(),n=!c.hasNext(),l=b.getSelection(),g=l.createBookmarks(),i=c.getDirection(1),k=c.getAttribute("class"),o=c.getAttribute("style"),m=e.getDirection(1)!=i,b=b.enterMode!=CKEDITOR.ENTER_BR||m||o||k;
+if(e.is("li"))if(h||n)c[h?"insertBefore":"insertAfter"](e);else c.breakParent(e);else{if(b)if(j.block.is("li")?(e=f.createElement(d==CKEDITOR.ENTER_P?"p":"div"),m&&e.setAttribute("dir",i),o&&e.setAttribute("style",o),k&&e.setAttribute("class",k),c.moveChildren(e)):e=j.block,h||n)e[h?"insertBefore":"insertAfter"](a);else c.breakParent(a),e.insertAfter(a);else if(c.appendBogus(!0),h||n)for(;f=c[h?"getFirst":"getLast"]();)f[h?"insertBefore":"insertAfter"](a);else for(c.breakParent(a);f=c.getLast();)f.insertAfter(a);
+c.remove()}l.selectBookmarks(g);return}if(c&&c.getParent().is("blockquote")){c.breakParent(c.getParent());c.getPrevious().getFirst(CKEDITOR.dom.walker.invisible(1))||c.getPrevious().remove();c.getNext().getFirst(CKEDITOR.dom.walker.invisible(1))||c.getNext().remove();a.moveToElementEditStart(c);a.select();return}}else if(c&&c.is("pre")&&!k){p(b,d,a,h);return}if(i=a.splitBlock(l)){d=i.previousBlock;c=i.nextBlock;j=i.wasStartOfBlock;b=i.wasEndOfBlock;if(c)g=c.getParent(),g.is("li")&&(c.breakParent(g),
+c.move(c.getNext(),1));else if(d&&(g=d.getParent())&&g.is("li"))d.breakParent(g),g=d.getNext(),a.moveToElementEditStart(g),d.move(d.getPrevious());if(!j&&!b)c.is("li")&&(e=a.clone(),e.selectNodeContents(c),e=new CKEDITOR.dom.walker(e),e.evaluator=function(a){return!(w(a)||v(a)||a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in CKEDITOR.dtd.$inline&&!(a.getName()in CKEDITOR.dtd.$empty))},(g=e.next())&&(g.type==CKEDITOR.NODE_ELEMENT&&g.is("ul","ol"))&&(CKEDITOR.env.needsBrFiller?f.createElement("br"):f.createText(" ")).insertBefore(g)),
+c&&a.moveToElementEditStart(c);else{if(d){if(d.is("li")||!s.test(d.getName())&&!d.is("pre"))e=d.clone()}else c&&(e=c.clone());e?h&&!e.is("li")&&e.renameNode(l):g&&g.is("li")?e=g:(e=f.createElement(l),d&&(n=d.getDirection())&&e.setAttribute("dir",n));if(f=i.elementPath){h=0;for(l=f.elements.length;h<l;h++){g=f.elements[h];if(g.equals(f.block)||g.equals(f.blockLimit))break;CKEDITOR.dtd.$removeEmpty[g.getName()]&&(g=g.clone(),e.moveChildren(g),e.append(g))}}e.appendBogus();e.getParent()||a.insertNode(e);
+e.is("li")&&e.removeAttribute("value");if(CKEDITOR.env.ie&&j&&(!b||!d.getChildCount()))a.moveToElementEditStart(b?d:e),a.select();a.moveToElementEditStart(j&&!b?c:e)}a.select();a.scrollIntoView()}}},enterBr:function(b,d,a,h){if(a=a||r(b)){var f=a.document,i=a.checkEndOfBlock(),k=new CKEDITOR.dom.elementPath(b.getSelection().getStartElement()),j=k.block,c=j&&k.block.getName();!h&&"li"==c?q(b,d,a,h):(!h&&i&&s.test(c)?(i=j.getDirection())?(f=f.createElement("div"),f.setAttribute("dir",i),f.insertAfter(j),
+a.setStart(f,0)):(f.createElement("br").insertAfter(j),CKEDITOR.env.gecko&&f.createText("").insertAfter(j),a.setStartAt(j.getNext(),CKEDITOR.env.ie?CKEDITOR.POSITION_BEFORE_START:CKEDITOR.POSITION_AFTER_START)):(b="pre"==c&&CKEDITOR.env.ie&&8>CKEDITOR.env.version?f.createText("\r"):f.createElement("br"),a.deleteContents(),a.insertNode(b),CKEDITOR.env.needsBrFiller?(f.createText("").insertAfter(b),i&&(j||k.blockLimit).appendBogus(),b.getNext().$.nodeValue="",a.setStartAt(b.getNext(),CKEDITOR.POSITION_AFTER_START)):
+a.setStartAt(b,CKEDITOR.POSITION_AFTER_END)),a.collapse(!0),a.select(),a.scrollIntoView())}}};var t=CKEDITOR.plugins.enterkey,p=t.enterBr,q=t.enterBlock,s=/^h[1-6]$/})();(function(){function i(b,f){var g={},c=[],e={nbsp:" ",shy:"­",gt:">",lt:"<",amp:"&",apos:"'",quot:'"'},b=b.replace(/\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g,function(b,a){var d=f?"&"+a+";":e[a];g[d]=f?e[a]:"&"+a+";";c.push(d);return""});if(!f&&b){var b=b.split(","),a=document.createElement("div"),d;a.innerHTML="&"+b.join(";&")+";";d=a.innerHTML;a=null;for(a=0;a<d.length;a++){var h=d.charAt(a);g[h]="&"+b[a]+";";c.push(h)}}g.regex=c.join(f?"|":"");return g}CKEDITOR.plugins.add("entities",{afterInit:function(b){function f(a){return h[a]}
+function g(b){return"force"==c.entities_processNumerical||!a[b]?"&#"+b.charCodeAt(0)+";":a[b]}var c=b.config;if(b=(b=b.dataProcessor)&&b.htmlFilter){var e=[];!1!==c.basicEntities&&e.push("nbsp,gt,lt,amp");c.entities&&(e.length&&e.push("quot,iexcl,cent,pound,curren,yen,brvbar,sect,uml,copy,ordf,laquo,not,shy,reg,macr,deg,plusmn,sup2,sup3,acute,micro,para,middot,cedil,sup1,ordm,raquo,frac14,frac12,frac34,iquest,times,divide,fnof,bull,hellip,prime,Prime,oline,frasl,weierp,image,real,trade,alefsym,larr,uarr,rarr,darr,harr,crarr,lArr,uArr,rArr,dArr,hArr,forall,part,exist,empty,nabla,isin,notin,ni,prod,sum,minus,lowast,radic,prop,infin,ang,and,or,cap,cup,int,there4,sim,cong,asymp,ne,equiv,le,ge,sub,sup,nsub,sube,supe,oplus,otimes,perp,sdot,lceil,rceil,lfloor,rfloor,lang,rang,loz,spades,clubs,hearts,diams,circ,tilde,ensp,emsp,thinsp,zwnj,zwj,lrm,rlm,ndash,mdash,lsquo,rsquo,sbquo,ldquo,rdquo,bdquo,dagger,Dagger,permil,lsaquo,rsaquo,euro"),
+c.entities_latin&&e.push("Agrave,Aacute,Acirc,Atilde,Auml,Aring,AElig,Ccedil,Egrave,Eacute,Ecirc,Euml,Igrave,Iacute,Icirc,Iuml,ETH,Ntilde,Ograve,Oacute,Ocirc,Otilde,Ouml,Oslash,Ugrave,Uacute,Ucirc,Uuml,Yacute,THORN,szlig,agrave,aacute,acirc,atilde,auml,aring,aelig,ccedil,egrave,eacute,ecirc,euml,igrave,iacute,icirc,iuml,eth,ntilde,ograve,oacute,ocirc,otilde,ouml,oslash,ugrave,uacute,ucirc,uuml,yacute,thorn,yuml,OElig,oelig,Scaron,scaron,Yuml"),c.entities_greek&&e.push("Alpha,Beta,Gamma,Delta,Epsilon,Zeta,Eta,Theta,Iota,Kappa,Lambda,Mu,Nu,Xi,Omicron,Pi,Rho,Sigma,Tau,Upsilon,Phi,Chi,Psi,Omega,alpha,beta,gamma,delta,epsilon,zeta,eta,theta,iota,kappa,lambda,mu,nu,xi,omicron,pi,rho,sigmaf,sigma,tau,upsilon,phi,chi,psi,omega,thetasym,upsih,piv"),
+c.entities_additional&&e.push(c.entities_additional));var a=i(e.join(",")),d=a.regex?"["+a.regex+"]":"a^";delete a.regex;c.entities&&c.entities_processNumerical&&(d="[^ -~]|"+d);var d=RegExp(d,"g"),h=i("nbsp,gt,lt,amp,shy",!0),j=RegExp(h.regex,"g");b.addRules({text:function(a){return a.replace(j,f).replace(d,g)}},{applyToAll:!0,excludeNestedEditable:!0})}}})})();CKEDITOR.config.basicEntities=!0;CKEDITOR.config.entities=!0;CKEDITOR.config.entities_latin=!0;CKEDITOR.config.entities_greek=!0;
+CKEDITOR.config.entities_additional="#39";CKEDITOR.plugins.add("popup");
+CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{popup:function(e,a,b,d){a=a||"80%";b=b||"70%";"string"==typeof a&&(1<a.length&&"%"==a.substr(a.length-1,1))&&(a=parseInt(window.screen.width*parseInt(a,10)/100,10));"string"==typeof b&&(1<b.length&&"%"==b.substr(b.length-1,1))&&(b=parseInt(window.screen.height*parseInt(b,10)/100,10));640>a&&(a=640);420>b&&(b=420);var f=parseInt((window.screen.height-b)/2,10),g=parseInt((window.screen.width-a)/2,10),d=(d||"location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes")+",width="+
+a+",height="+b+",top="+f+",left="+g,c=window.open("",null,d,!0);if(!c)return!1;try{-1==navigator.userAgent.toLowerCase().indexOf(" chrome/")&&(c.moveTo(g,f),c.resizeTo(a,b)),c.focus(),c.location.href=e}catch(h){window.open(e,null,d,!0)}return!0}});(function(){function g(a,c){var d=[];if(c)for(var b in c)d.push(b+"="+encodeURIComponent(c[b]));else return a;return a+(-1!=a.indexOf("?")?"&":"?")+d.join("&")}function i(a){a+="";return a.charAt(0).toUpperCase()+a.substr(1)}function k(){var a=this.getDialog(),c=a.getParentEditor();c._.filebrowserSe=this;var d=c.config["filebrowser"+i(a.getName())+"WindowWidth"]||c.config.filebrowserWindowWidth||"80%",a=c.config["filebrowser"+i(a.getName())+"WindowHeight"]||c.config.filebrowserWindowHeight||"70%",
+b=this.filebrowser.params||{};b.CKEditor=c.name;b.CKEditorFuncNum=c._.filebrowserFn;b.langCode||(b.langCode=c.langCode);b=g(this.filebrowser.url,b);c.popup(b,d,a,c.config.filebrowserWindowFeatures||c.config.fileBrowserWindowFeatures)}function l(){var a=this.getDialog();a.getParentEditor()._.filebrowserSe=this;return!a.getContentElement(this["for"][0],this["for"][1]).getInputElement().$.value||!a.getContentElement(this["for"][0],this["for"][1]).getAction()?!1:!0}function m(a,c,d){var b=d.params||{};
+b.CKEditor=a.name;b.CKEditorFuncNum=a._.filebrowserFn;b.langCode||(b.langCode=a.langCode);c.action=g(d.url,b);c.filebrowser=d}function j(a,c,d,b){if(b&&b.length)for(var e,g=b.length;g--;)if(e=b[g],("hbox"==e.type||"vbox"==e.type||"fieldset"==e.type)&&j(a,c,d,e.children),e.filebrowser)if("string"==typeof e.filebrowser&&(e.filebrowser={action:"fileButton"==e.type?"QuickUpload":"Browse",target:e.filebrowser}),"Browse"==e.filebrowser.action){var f=e.filebrowser.url;void 0===f&&(f=a.config["filebrowser"+
+i(c)+"BrowseUrl"],void 0===f&&(f=a.config.filebrowserBrowseUrl));f&&(e.onClick=k,e.filebrowser.url=f,e.hidden=!1)}else if("QuickUpload"==e.filebrowser.action&&e["for"]&&(f=e.filebrowser.url,void 0===f&&(f=a.config["filebrowser"+i(c)+"UploadUrl"],void 0===f&&(f=a.config.filebrowserUploadUrl)),f)){var h=e.onClick;e.onClick=function(a){var b=a.sender;return h&&h.call(b,a)===false?false:l.call(b,a)};e.filebrowser.url=f;e.hidden=!1;m(a,d.getContents(e["for"][0]).get(e["for"][1]),e.filebrowser)}}function h(a,
+c,d){if(-1!==d.indexOf(";")){for(var d=d.split(";"),b=0;b<d.length;b++)if(h(a,c,d[b]))return!0;return!1}return(a=a.getContents(c).get(d).filebrowser)&&a.url}function n(a,c){var d=this._.filebrowserSe.getDialog(),b=this._.filebrowserSe["for"],e=this._.filebrowserSe.filebrowser.onSelect;b&&d.getContentElement(b[0],b[1]).reset();if(!("function"==typeof c&&!1===c.call(this._.filebrowserSe))&&!(e&&!1===e.call(this._.filebrowserSe,a,c))&&("string"==typeof c&&c&&alert(c),a&&(b=this._.filebrowserSe,d=b.getDialog(),
+b=b.filebrowser.target||null)))if(b=b.split(":"),e=d.getContentElement(b[0],b[1]))e.setValue(a),d.selectPage(b[0])}CKEDITOR.plugins.add("filebrowser",{requires:"popup",init:function(a){a._.filebrowserFn=CKEDITOR.tools.addFunction(n,a);a.on("destroy",function(){CKEDITOR.tools.removeFunction(this._.filebrowserFn)})}});CKEDITOR.on("dialogDefinition",function(a){if(a.editor.plugins.filebrowser)for(var c=a.data.definition,d,b=0;b<c.contents.length;++b)if(d=c.contents[b])j(a.editor,a.data.name,c,d.elements),
+d.hidden&&d.filebrowser&&(d.hidden=!h(c,d.id,d.filebrowser))})})();(function(){function i(a){var j=a.config,m=a.fire("uiSpace",{space:"top",html:""}).html,p=function(){function f(a,c,e){b.setStyle(c,s(e));b.setStyle("position",a)}function e(a){var b=i.getDocumentPosition();switch(a){case "top":f("absolute","top",b.y-n-o);break;case "pin":f("fixed","top",t);break;case "bottom":f("absolute","top",b.y+(c.height||c.bottom-c.top)+o)}k=a}var k,i,l,c,h,n,r,m=j.floatSpaceDockedOffsetX||0,o=j.floatSpaceDockedOffsetY||0,q=j.floatSpacePinnedOffsetX||0,t=j.floatSpacePinnedOffsetY||
+0;return function(d){if(i=a.editable())if(d&&"focus"==d.name&&b.show(),b.removeStyle("left"),b.removeStyle("right"),l=b.getClientRect(),c=i.getClientRect(),h=g.getViewPaneSize(),n=l.height,r="pageXOffset"in g.$?g.$.pageXOffset:CKEDITOR.document.$.documentElement.scrollLeft,k){n+o<=c.top?e("top"):n+o>h.height-c.bottom?e("pin"):e("bottom");var d=h.width/2,d=0<c.left&&c.right<h.width&&c.width>l.width?"rtl"==a.config.contentsLangDirection?"right":"left":d-c.left>c.right-d?"left":"right",f;l.width>h.width?
+(d="left",f=0):(f="left"==d?0<c.left?c.left:0:c.right<h.width?h.width-c.right:0,f+l.width>h.width&&(d="left"==d?"right":"left",f=0));b.setStyle(d,s(("pin"==k?q:m)+f+("pin"==k?0:"left"==d?r:-r)))}else k="pin",e("pin"),p(d)}}();if(m){var i=new CKEDITOR.template('<div id="cke_{name}" class="cke {id} cke_reset_all cke_chrome cke_editor_{name} cke_float cke_{langDir} '+CKEDITOR.env.cssClass+'" dir="{langDir}" title="'+(CKEDITOR.env.gecko?" ":"")+'" lang="{langCode}" role="application" style="{style}"'+
+(a.title?' aria-labelledby="cke_{name}_arialbl"':" ")+">"+(a.title?'<span id="cke_{name}_arialbl" class="cke_voice_label">{voiceLabel}</span>':" ")+'<div class="cke_inner"><div id="{topId}" class="cke_top" role="presentation">{content}</div></div></div>'),b=CKEDITOR.document.getBody().append(CKEDITOR.dom.element.createFromHtml(i.output({content:m,id:a.id,langDir:a.lang.dir,langCode:a.langCode,name:a.name,style:"display:none;z-index:"+(j.baseFloatZIndex-1),topId:a.ui.spaceId("top"),voiceLabel:a.title}))),
+q=CKEDITOR.tools.eventsBuffer(500,p),e=CKEDITOR.tools.eventsBuffer(100,p);b.unselectable();b.on("mousedown",function(a){a=a.data;a.getTarget().hasAscendant("a",1)||a.preventDefault()});a.on("focus",function(b){p(b);a.on("change",q.input);g.on("scroll",e.input);g.on("resize",e.input)});a.on("blur",function(){b.hide();a.removeListener("change",q.input);g.removeListener("scroll",e.input);g.removeListener("resize",e.input)});a.on("destroy",function(){g.removeListener("scroll",e.input);g.removeListener("resize",
+e.input);b.clearCustomData();b.remove()});a.focusManager.hasFocus&&b.show();a.focusManager.add(b,1)}}var g=CKEDITOR.document.getWindow(),s=CKEDITOR.tools.cssLength;CKEDITOR.plugins.add("floatingspace",{init:function(a){a.on("loaded",function(){i(this)},null,null,20)}})})();CKEDITOR.plugins.add("listblock",{requires:"panel",onLoad:function(){var f=CKEDITOR.addTemplate("panel-list",'<ul role="presentation" class="cke_panel_list">{items}</ul>'),g=CKEDITOR.addTemplate("panel-list-item",'<li id="{id}" class="cke_panel_listItem" role=presentation><a id="{id}_option" _cke_focus=1 hidefocus=true title="{title}" href="javascript:void(\'{val}\')" {onclick}="CKEDITOR.tools.callFunction({clickFn},\'{val}\'); return false;" role="option">{text}</a></li>'),h=CKEDITOR.addTemplate("panel-list-group",
+'<h1 id="{id}" class="cke_panel_grouptitle" role="presentation" >{label}</h1>'),i=/\'/g;CKEDITOR.ui.panel.prototype.addListBlock=function(a,b){return this.addBlock(a,new CKEDITOR.ui.listBlock(this.getHolderElement(),b))};CKEDITOR.ui.listBlock=CKEDITOR.tools.createClass({base:CKEDITOR.ui.panel.block,$:function(a,b){var b=b||{},c=b.attributes||(b.attributes={});(this.multiSelect=!!b.multiSelect)&&(c["aria-multiselectable"]=!0);!c.role&&(c.role="listbox");this.base.apply(this,arguments);this.element.setAttribute("role",
+c.role);c=this.keys;c[40]="next";c[9]="next";c[38]="prev";c[CKEDITOR.SHIFT+9]="prev";c[32]=CKEDITOR.env.ie?"mouseup":"click";CKEDITOR.env.ie&&(c[13]="mouseup");this._.pendingHtml=[];this._.pendingList=[];this._.items={};this._.groups={}},_:{close:function(){if(this._.started){var a=f.output({items:this._.pendingList.join("")});this._.pendingList=[];this._.pendingHtml.push(a);delete this._.started}},getClick:function(){this._.click||(this._.click=CKEDITOR.tools.addFunction(function(a){var b=this.toggle(a);
+if(this.onClick)this.onClick(a,b)},this));return this._.click}},proto:{add:function(a,b,c){var d=CKEDITOR.tools.getNextId();this._.started||(this._.started=1,this._.size=this._.size||0);this._.items[a]=d;var e;e=CKEDITOR.tools.htmlEncodeAttr(a).replace(i,"\\'");a={id:d,val:e,onclick:CKEDITOR.env.ie?'onclick="return false;" onmouseup':"onclick",clickFn:this._.getClick(),title:CKEDITOR.tools.htmlEncodeAttr(c||a),text:b||a};this._.pendingList.push(g.output(a))},startGroup:function(a){this._.close();
+var b=CKEDITOR.tools.getNextId();this._.groups[a]=b;this._.pendingHtml.push(h.output({id:b,label:a}))},commit:function(){this._.close();this.element.appendHtml(this._.pendingHtml.join(""));delete this._.size;this._.pendingHtml=[]},toggle:function(a){var b=this.isMarked(a);b?this.unmark(a):this.mark(a);return!b},hideGroup:function(a){var b=(a=this.element.getDocument().getById(this._.groups[a]))&&a.getNext();a&&(a.setStyle("display","none"),b&&"ul"==b.getName()&&b.setStyle("display","none"))},hideItem:function(a){this.element.getDocument().getById(this._.items[a]).setStyle("display",
+"none")},showAll:function(){var a=this._.items,b=this._.groups,c=this.element.getDocument(),d;for(d in a)c.getById(a[d]).setStyle("display","");for(var e in b)a=c.getById(b[e]),d=a.getNext(),a.setStyle("display",""),d&&"ul"==d.getName()&&d.setStyle("display","")},mark:function(a){this.multiSelect||this.unmarkAll();var a=this._.items[a],b=this.element.getDocument().getById(a);b.addClass("cke_selected");this.element.getDocument().getById(a+"_option").setAttribute("aria-selected",!0);this.onMark&&this.onMark(b)},
+unmark:function(a){var b=this.element.getDocument(),a=this._.items[a],c=b.getById(a);c.removeClass("cke_selected");b.getById(a+"_option").removeAttribute("aria-selected");this.onUnmark&&this.onUnmark(c)},unmarkAll:function(){var a=this._.items,b=this.element.getDocument(),c;for(c in a){var d=a[c];b.getById(d).removeClass("cke_selected");b.getById(d+"_option").removeAttribute("aria-selected")}this.onUnmark&&this.onUnmark()},isMarked:function(a){return this.element.getDocument().getById(this._.items[a]).hasClass("cke_selected")},
+focus:function(a){this._.focusIndex=-1;var b=this.element.getElementsByTag("a"),c,d=-1;if(a)for(c=this.element.getDocument().getById(this._.items[a]).getFirst();a=b.getItem(++d);){if(a.equals(c)){this._.focusIndex=d;break}}else this.element.focus();c&&setTimeout(function(){c.focus()},0)}}})}});CKEDITOR.plugins.add("richcombo",{requires:"floatpanel,listblock,button",beforeInit:function(d){d.ui.addHandler(CKEDITOR.UI_RICHCOMBO,CKEDITOR.ui.richCombo.handler)}});
+(function(){var d='<span id="{id}" class="cke_combo cke_combo__{name} {cls}" role="presentation"><span id="{id}_label" class="cke_combo_label">{label}</span><a class="cke_combo_button" title="{title}" tabindex="-1"'+(CKEDITOR.env.gecko&&!CKEDITOR.env.hc?"":" href=\"javascript:void('{titleJs}')\"")+' hidefocus="true" role="button" aria-labelledby="{id}_label" aria-haspopup="true"';CKEDITOR.env.gecko&&CKEDITOR.env.mac&&(d+=' onkeypress="return false;"');CKEDITOR.env.gecko&&(d+=' onblur="this.style.cssText = this.style.cssText;"');
+var d=d+(' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event,this);" onfocus="return CKEDITOR.tools.callFunction({focusFn},event);" '+(CKEDITOR.env.ie?'onclick="return false;" onmouseup':"onclick")+'="CKEDITOR.tools.callFunction({clickFn},this);return false;"><span id="{id}_text" class="cke_combo_text cke_combo_inlinelabel">{label}</span><span class="cke_combo_open"><span class="cke_combo_arrow">'+(CKEDITOR.env.hc?"&#9660;":CKEDITOR.env.air?"&nbsp;":"")+"</span></span></a></span>"),
+i=CKEDITOR.addTemplate("combo",d);CKEDITOR.UI_RICHCOMBO="richcombo";CKEDITOR.ui.richCombo=CKEDITOR.tools.createClass({$:function(a){CKEDITOR.tools.extend(this,a,{canGroup:!1,title:a.label,modes:{wysiwyg:1},editorFocus:1});a=this.panel||{};delete this.panel;this.id=CKEDITOR.tools.getNextNumber();this.document=a.parent&&a.parent.getDocument()||CKEDITOR.document;a.className="cke_combopanel";a.block={multiSelect:a.multiSelect,attributes:a.attributes};a.toolbarRelated=!0;this._={panelDefinition:a,items:{}}},
+proto:{renderHtml:function(a){var b=[];this.render(a,b);return b.join("")},render:function(a,b){function g(){if(this.getState()!=CKEDITOR.TRISTATE_ON){var c=this.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED;a.readOnly&&!this.readOnly&&(c=CKEDITOR.TRISTATE_DISABLED);this.setState(c);this.setValue("");c!=CKEDITOR.TRISTATE_DISABLED&&this.refresh&&this.refresh()}}var d=CKEDITOR.env,h="cke_"+this.id,e=CKEDITOR.tools.addFunction(function(b){j&&(a.unlockSelection(1),j=0);c.execute(b)},
+this),f=this,c={id:h,combo:this,focus:function(){CKEDITOR.document.getById(h).getChild(1).focus()},execute:function(c){var b=f._;if(b.state!=CKEDITOR.TRISTATE_DISABLED)if(f.createPanel(a),b.on)b.panel.hide();else{f.commit();var d=f.getValue();d?b.list.mark(d):b.list.unmarkAll();b.panel.showBlock(f.id,new CKEDITOR.dom.element(c),4)}},clickFn:e};a.on("activeFilterChange",g,this);a.on("mode",g,this);a.on("selectionChange",g,this);!this.readOnly&&a.on("readOnly",g,this);var k=CKEDITOR.tools.addFunction(function(b,
+d){var b=new CKEDITOR.dom.event(b),g=b.getKeystroke();if(40==g)a.once("panelShow",function(a){a.data._.panel._.currentBlock.onKeyDown(40)});switch(g){case 13:case 32:case 40:CKEDITOR.tools.callFunction(e,d);break;default:c.onkey(c,g)}b.preventDefault()}),l=CKEDITOR.tools.addFunction(function(){c.onfocus&&c.onfocus()}),j=0;c.keyDownFn=k;d={id:h,name:this.name||this.command,label:this.label,title:this.title,cls:this.className||"",titleJs:d.gecko&&!d.hc?"":(this.title||"").replace("'",""),keydownFn:k,
+focusFn:l,clickFn:e};i.output(d,b);if(this.onRender)this.onRender();return c},createPanel:function(a){if(!this._.panel){var b=this._.panelDefinition,d=this._.panelDefinition.block,i=b.parent||CKEDITOR.document.getBody(),h="cke_combopanel__"+this.name,e=new CKEDITOR.ui.floatPanel(a,i,b),f=e.addListBlock(this.id,d),c=this;e.onShow=function(){this.element.addClass(h);c.setState(CKEDITOR.TRISTATE_ON);c._.on=1;c.editorFocus&&!a.focusManager.hasFocus&&a.focus();if(c.onOpen)c.onOpen();a.once("panelShow",
+function(){f.focus(!f.multiSelect&&c.getValue())})};e.onHide=function(b){this.element.removeClass(h);c.setState(c.modes&&c.modes[a.mode]?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);c._.on=0;if(!b&&c.onClose)c.onClose()};e.onEscape=function(){e.hide(1)};f.onClick=function(a,b){c.onClick&&c.onClick.call(c,a,b);e.hide()};this._.panel=e;this._.list=f;e.getBlock(this.id).onHide=function(){c._.on=0;c.setState(CKEDITOR.TRISTATE_OFF)};this.init&&this.init()}},setValue:function(a,b){this._.value=a;var d=
+this.document.getById("cke_"+this.id+"_text");d&&(!a&&!b?(b=this.label,d.addClass("cke_combo_inlinelabel")):d.removeClass("cke_combo_inlinelabel"),d.setText("undefined"!=typeof b?b:a))},getValue:function(){return this._.value||""},unmarkAll:function(){this._.list.unmarkAll()},mark:function(a){this._.list.mark(a)},hideItem:function(a){this._.list.hideItem(a)},hideGroup:function(a){this._.list.hideGroup(a)},showAll:function(){this._.list.showAll()},add:function(a,b,d){this._.items[a]=d||a;this._.list.add(a,
+b,d)},startGroup:function(a){this._.list.startGroup(a)},commit:function(){this._.committed||(this._.list.commit(),this._.committed=1,CKEDITOR.ui.fire("ready",this));this._.committed=1},setState:function(a){if(this._.state!=a){var b=this.document.getById("cke_"+this.id);b.setState(a,"cke_combo");a==CKEDITOR.TRISTATE_DISABLED?b.setAttribute("aria-disabled",!0):b.removeAttribute("aria-disabled");this._.state=a}},getState:function(){return this._.state},enable:function(){this._.state==CKEDITOR.TRISTATE_DISABLED&&
+this.setState(this._.lastState)},disable:function(){this._.state!=CKEDITOR.TRISTATE_DISABLED&&(this._.lastState=this._.state,this.setState(CKEDITOR.TRISTATE_DISABLED))}},statics:{handler:{create:function(a){return new CKEDITOR.ui.richCombo(a)}}}});CKEDITOR.ui.prototype.addRichCombo=function(a,b){this.add(a,CKEDITOR.UI_RICHCOMBO,b)}})();CKEDITOR.plugins.add("format",{requires:"richcombo",init:function(a){if(!a.blockless){for(var f=a.config,c=a.lang.format,j=f.format_tags.split(";"),d={},k=0,l=[],g=0;g<j.length;g++){var h=j[g],i=new CKEDITOR.style(f["format_"+h]);if(!a.filter.customConfig||a.filter.check(i))k++,d[h]=i,d[h]._.enterMode=a.config.enterMode,l.push(i)}0!==k&&a.ui.addRichCombo("Format",{label:c.label,title:c.panelTitle,toolbar:"styles,20",allowedContent:l,panel:{css:[CKEDITOR.skin.getPath("editor")].concat(f.contentsCss),
+multiSelect:!1,attributes:{"aria-label":c.panelTitle}},init:function(){this.startGroup(c.panelTitle);for(var a in d){var e=c["tag_"+a];this.add(a,d[a].buildPreview(e),e)}},onClick:function(b){a.focus();a.fire("saveSnapshot");var b=d[b],e=a.elementPath();a[b.checkActive(e,a)?"removeStyle":"applyStyle"](b);setTimeout(function(){a.fire("saveSnapshot")},0)},onRender:function(){a.on("selectionChange",function(b){var e=this.getValue(),b=b.data.path;this.refresh();for(var c in d)if(d[c].checkActive(b,a)){c!=
+e&&this.setValue(c,a.lang.format["tag_"+c]);return}this.setValue("")},this)},onOpen:function(){this.showAll();for(var b in d)a.activeFilter.check(d[b])||this.hideItem(b)},refresh:function(){var b=a.elementPath();if(b){if(b.isContextFor("p"))for(var c in d)if(a.activeFilter.check(d[c]))return;this.setState(CKEDITOR.TRISTATE_DISABLED)}}})}}});CKEDITOR.config.format_tags="p;h1;h2;h3;h4;h5;h6;pre;address;div";CKEDITOR.config.format_p={element:"p"};CKEDITOR.config.format_div={element:"div"};
+CKEDITOR.config.format_pre={element:"pre"};CKEDITOR.config.format_address={element:"address"};CKEDITOR.config.format_h1={element:"h1"};CKEDITOR.config.format_h2={element:"h2"};CKEDITOR.config.format_h3={element:"h3"};CKEDITOR.config.format_h4={element:"h4"};CKEDITOR.config.format_h5={element:"h5"};CKEDITOR.config.format_h6={element:"h6"};(function(){var b={canUndo:!1,exec:function(a){var b=a.document.createElement("hr");a.insertElement(b)},allowedContent:"hr",requiredContent:"hr"};CKEDITOR.plugins.add("horizontalrule",{init:function(a){a.blockless||(a.addCommand("horizontalrule",b),a.ui.addButton&&a.ui.addButton("HorizontalRule",{label:a.lang.horizontalrule.toolbar,command:"horizontalrule",toolbar:"insert,40"}))}})})();CKEDITOR.plugins.add("htmlwriter",{init:function(b){var a=new CKEDITOR.htmlWriter;a.forceSimpleAmpersand=b.config.forceSimpleAmpersand;a.indentationChars=b.config.dataIndentationChars||"\t";b.dataProcessor.writer=a}});
+CKEDITOR.htmlWriter=CKEDITOR.tools.createClass({base:CKEDITOR.htmlParser.basicWriter,$:function(){this.base();this.indentationChars="\t";this.selfClosingEnd=" />";this.lineBreakChars="\n";this.sortAttributes=1;this._.indent=0;this._.indentation="";this._.inPre=0;this._.rules={};var b=CKEDITOR.dtd,a;for(a in CKEDITOR.tools.extend({},b.$nonBodyContent,b.$block,b.$listItem,b.$tableContent))this.setRules(a,{indent:!b[a]["#"],breakBeforeOpen:1,breakBeforeClose:!b[a]["#"],breakAfterClose:1,needsSpace:a in
+b.$block&&!(a in{li:1,dt:1,dd:1})});this.setRules("br",{breakAfterOpen:1});this.setRules("title",{indent:0,breakAfterOpen:0});this.setRules("style",{indent:0,breakBeforeClose:1});this.setRules("pre",{breakAfterOpen:1,indent:0})},proto:{openTag:function(b){var a=this._.rules[b];this._.afterCloser&&(a&&a.needsSpace&&this._.needsSpace)&&this._.output.push("\n");this._.indent?this.indentation():a&&a.breakBeforeOpen&&(this.lineBreak(),this.indentation());this._.output.push("<",b);this._.afterCloser=0},
+openTagClose:function(b,a){var c=this._.rules[b];a?(this._.output.push(this.selfClosingEnd),c&&c.breakAfterClose&&(this._.needsSpace=c.needsSpace)):(this._.output.push(">"),c&&c.indent&&(this._.indentation+=this.indentationChars));c&&c.breakAfterOpen&&this.lineBreak();"pre"==b&&(this._.inPre=1)},attribute:function(b,a){"string"==typeof a&&(this.forceSimpleAmpersand&&(a=a.replace(/&amp;/g,"&")),a=CKEDITOR.tools.htmlEncodeAttr(a));this._.output.push(" ",b,'="',a,'"')},closeTag:function(b){var a=this._.rules[b];
+a&&a.indent&&(this._.indentation=this._.indentation.substr(this.indentationChars.length));this._.indent?this.indentation():a&&a.breakBeforeClose&&(this.lineBreak(),this.indentation());this._.output.push("</",b,">");"pre"==b&&(this._.inPre=0);a&&a.breakAfterClose&&(this.lineBreak(),this._.needsSpace=a.needsSpace);this._.afterCloser=1},text:function(b){this._.indent&&(this.indentation(),!this._.inPre&&(b=CKEDITOR.tools.ltrim(b)));this._.output.push(b)},comment:function(b){this._.indent&&this.indentation();
+this._.output.push("<\!--",b,"--\>")},lineBreak:function(){!this._.inPre&&0<this._.output.length&&this._.output.push(this.lineBreakChars);this._.indent=1},indentation:function(){!this._.inPre&&this._.indentation&&this._.output.push(this._.indentation);this._.indent=0},reset:function(){this._.output=[];this._.indent=0;this._.indentation="";this._.afterCloser=0;this._.inPre=0},setRules:function(b,a){var c=this._.rules[b];c?CKEDITOR.tools.extend(c,a,!0):this._.rules[b]=a}}});(function(){function k(a){var e=this.editor,b=a.document,c=b.body,d=b.getElementById("cke_actscrpt");d&&d.parentNode.removeChild(d);(d=b.getElementById("cke_shimscrpt"))&&d.parentNode.removeChild(d);(d=b.getElementById("cke_basetagscrpt"))&&d.parentNode.removeChild(d);c.contentEditable=!0;CKEDITOR.env.ie&&(c.hideFocus=!0,c.disabled=!0,c.removeAttribute("disabled"));delete this._.isLoadingData;this.$=c;b=new CKEDITOR.dom.document(b);this.setup();this.fixInitialSelection();CKEDITOR.env.ie&&(b.getDocumentElement().addClass(b.$.compatMode),
+e.config.enterMode!=CKEDITOR.ENTER_P&&this.attachListener(b,"selectionchange",function(){var a=b.getBody(),c=e.getSelection(),d=c&&c.getRanges()[0];d&&(a.getHtml().match(/^<p>(?:&nbsp;|<br>)<\/p>$/i)&&d.startContainer.equals(a))&&setTimeout(function(){d=e.getSelection().getRanges()[0];if(!d.startContainer.equals("body")){a.getFirst().remove(1);d.moveToElementEditEnd(a);d.select()}},0)}));if(CKEDITOR.env.webkit||CKEDITOR.env.ie&&10<CKEDITOR.env.version)b.getDocumentElement().on("mousedown",function(a){a.data.getTarget().is("html")&&
+setTimeout(function(){e.editable().focus()})});l(e);try{e.document.$.execCommand("2D-position",!1,!0)}catch(g){}(CKEDITOR.env.gecko||CKEDITOR.env.ie&&"CSS1Compat"==e.document.$.compatMode)&&this.attachListener(this,"keydown",function(a){var b=a.data.getKeystroke();if(b==33||b==34)if(CKEDITOR.env.ie)setTimeout(function(){e.getSelection().scrollIntoView()},0);else if(e.window.$.innerHeight>this.$.offsetHeight){var c=e.createRange();c[b==33?"moveToElementEditStart":"moveToElementEditEnd"](this);c.select();
+a.data.preventDefault()}});CKEDITOR.env.ie&&this.attachListener(b,"blur",function(){try{b.$.selection.empty()}catch(a){}});CKEDITOR.env.iOS&&this.attachListener(b,"touchend",function(){a.focus()});c=e.document.getElementsByTag("title").getItem(0);c.data("cke-title",c.getText());CKEDITOR.env.ie&&(e.document.$.title=this._.docTitle);CKEDITOR.tools.setTimeout(function(){if(this.status=="unloaded")this.status="ready";e.fire("contentDom");if(this._.isPendingFocus){e.focus();this._.isPendingFocus=false}setTimeout(function(){e.fire("dataReady")},
+0);CKEDITOR.env.ie&&setTimeout(function(){if(e.document){var a=e.document.$.body;a.runtimeStyle.marginBottom="0px";a.runtimeStyle.marginBottom=""}},1E3)},0,this)}function l(a){function e(){var c;a.editable().attachListener(a,"selectionChange",function(){var d=a.getSelection().getSelectedElement();d&&(c&&(c.detachEvent("onresizestart",b),c=null),d.$.attachEvent("onresizestart",b),c=d.$)})}function b(a){a.returnValue=!1}if(CKEDITOR.env.gecko)try{var c=a.document.$;c.execCommand("enableObjectResizing",
+!1,!a.config.disableObjectResizing);c.execCommand("enableInlineTableEditing",!1,!a.config.disableNativeTableHandles)}catch(d){}else CKEDITOR.env.ie&&(11>CKEDITOR.env.version&&a.config.disableObjectResizing)&&e(a)}function m(){var a=[];if(8<=CKEDITOR.document.$.documentMode){a.push("html.CSS1Compat [contenteditable=false]{min-height:0 !important}");var e=[],b;for(b in CKEDITOR.dtd.$removeEmpty)e.push("html.CSS1Compat "+b+"[contenteditable=false]");a.push(e.join(",")+"{display:inline-block}")}else CKEDITOR.env.gecko&&
+(a.push("html{height:100% !important}"),a.push("img:-moz-broken{-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}"));a.push("html{cursor:text;*cursor:auto}");a.push("img,input,textarea{cursor:default}");return a.join("\n")}CKEDITOR.plugins.add("wysiwygarea",{init:function(a){a.config.fullPage&&a.addFeature({allowedContent:"html head title; style [media,type]; body (*)[id]; meta link [*]",requiredContent:"body"});a.addMode("wysiwyg",function(e){function b(b){b&&b.removeListener();a.editable(new j(a,
+d.$.contentWindow.document.body));a.setData(a.getData(1),e)}var c="document.open();"+(CKEDITOR.env.ie?"("+CKEDITOR.tools.fixDomain+")();":"")+"document.close();",c=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie?"javascript:void(function(){"+encodeURIComponent(c)+"}())":"",d=CKEDITOR.dom.element.createFromHtml('<iframe src="'+c+'" frameBorder="0"></iframe>');d.setStyles({width:"100%",height:"100%"});d.addClass("cke_wysiwyg_frame cke_reset");var g=a.ui.space("contents");g.append(d);if(c=CKEDITOR.env.ie||
+CKEDITOR.env.gecko)d.on("load",b);var f=a.title,h=a.fire("ariaEditorHelpLabel",{}).label;f&&(CKEDITOR.env.ie&&h&&(f+=", "+h),d.setAttribute("title",f));if(h){var f=CKEDITOR.tools.getNextId(),i=CKEDITOR.dom.element.createFromHtml('<span id="'+f+'" class="cke_voice_label">'+h+"</span>");g.append(i,1);d.setAttribute("aria-describedby",f)}a.on("beforeModeUnload",function(a){a.removeListener();i&&i.remove()});d.setAttributes({tabIndex:a.tabIndex,allowTransparency:"true"});!c&&b();CKEDITOR.env.webkit&&
+(c=function(){g.setStyle("width","100%");d.hide();d.setSize("width",g.getSize("width"));g.removeStyle("width");d.show()},d.setCustomData("onResize",c),CKEDITOR.document.getWindow().on("resize",c));a.fire("ariaWidget",d)})}});CKEDITOR.editor.prototype.addContentsCss=function(a){var e=this.config,b=e.contentsCss;CKEDITOR.tools.isArray(b)||(e.contentsCss=b?[b]:[]);e.contentsCss.push(a)};var j=CKEDITOR.tools.createClass({$:function(){this.base.apply(this,arguments);this._.frameLoadedHandler=CKEDITOR.tools.addFunction(function(a){CKEDITOR.tools.setTimeout(k,
+0,this,a)},this);this._.docTitle=this.getWindow().getFrame().getAttribute("title")},base:CKEDITOR.editable,proto:{setData:function(a,e){var b=this.editor;if(e)this.setHtml(a),this.fixInitialSelection(),b.fire("dataReady");else{this._.isLoadingData=!0;b._.dataStore={id:1};var c=b.config,d=c.fullPage,g=c.docType,f=CKEDITOR.tools.buildStyleHtml(m()).replace(/<style>/,'<style data-cke-temp="1">');d||(f+=CKEDITOR.tools.buildStyleHtml(b.config.contentsCss));var h=c.baseHref?'<base href="'+c.baseHref+'" data-cke-temp="1" />':
+"";d&&(a=a.replace(/<!DOCTYPE[^>]*>/i,function(a){b.docType=g=a;return""}).replace(/<\?xml\s[^\?]*\?>/i,function(a){b.xmlDeclaration=a;return""}));a=b.dataProcessor.toHtml(a);d?(/<body[\s|>]/.test(a)||(a="<body>"+a),/<html[\s|>]/.test(a)||(a="<html>"+a+"</html>"),/<head[\s|>]/.test(a)?/<title[\s|>]/.test(a)||(a=a.replace(/<head[^>]*>/,"$&<title></title>")):a=a.replace(/<html[^>]*>/,"$&<head><title></title></head>"),h&&(a=a.replace(/<head[^>]*?>/,"$&"+h)),a=a.replace(/<\/head\s*>/,f+"$&"),a=g+a):a=
+c.docType+'<html dir="'+c.contentsLangDirection+'" lang="'+(c.contentsLanguage||b.langCode)+'"><head><title>'+this._.docTitle+"</title>"+h+f+"</head><body"+(c.bodyId?' id="'+c.bodyId+'"':"")+(c.bodyClass?' class="'+c.bodyClass+'"':"")+">"+a+"</body></html>";CKEDITOR.env.gecko&&(a=a.replace(/<body/,'<body contenteditable="true" '),2E4>CKEDITOR.env.version&&(a=a.replace(/<body[^>]*>/,"$&<\!-- cke-content-start --\>")));c='<script id="cke_actscrpt" type="text/javascript"'+(CKEDITOR.env.ie?' defer="defer" ':
+"")+">var wasLoaded=0;function onload(){if(!wasLoaded)window.parent.CKEDITOR.tools.callFunction("+this._.frameLoadedHandler+",window);wasLoaded=1;}"+(CKEDITOR.env.ie?"onload();":'document.addEventListener("DOMContentLoaded", onload, false );')+"<\/script>";CKEDITOR.env.ie&&9>CKEDITOR.env.version&&(c+='<script id="cke_shimscrpt">window.parent.CKEDITOR.tools.enableHtml5Elements(document)<\/script>');h&&(CKEDITOR.env.ie&&10>CKEDITOR.env.version)&&(c+='<script id="cke_basetagscrpt">var baseTag = document.querySelector( "base" );baseTag.href = baseTag.href;<\/script>');
+a=a.replace(/(?=\s*<\/(:?head)>)/,c);this.clearCustomData();this.clearListeners();b.fire("contentDomUnload");var i=this.getDocument();try{i.write(a)}catch(j){setTimeout(function(){i.write(a)},0)}}},getData:function(a){if(a)return this.getHtml();var a=this.editor,e=a.config,b=e.fullPage,c=b&&a.docType,d=b&&a.xmlDeclaration,g=this.getDocument(),b=b?g.getDocumentElement().getOuterHtml():g.getBody().getHtml();CKEDITOR.env.gecko&&e.enterMode!=CKEDITOR.ENTER_BR&&(b=b.replace(/<br>(?=\s*(:?$|<\/body>))/,
+""));b=a.dataProcessor.toDataFormat(b);d&&(b=d+"\n"+b);c&&(b=c+"\n"+b);return b},focus:function(){this._.isLoadingData?this._.isPendingFocus=!0:j.baseProto.focus.call(this)},detach:function(){var a=this.editor,e=a.document,a=a.window.getFrame();j.baseProto.detach.call(this);this.clearCustomData();e.getDocumentElement().clearCustomData();a.clearCustomData();CKEDITOR.tools.removeFunction(this._.frameLoadedHandler);(e=a.removeCustomData("onResize"))&&e.removeListener();a.remove()}}})})();
+CKEDITOR.config.disableObjectResizing=!1;CKEDITOR.config.disableNativeTableHandles=!0;CKEDITOR.config.disableNativeSpellChecker=!0;CKEDITOR.config.contentsCss=CKEDITOR.getUrl("contents.css");(function(){function e(b,a){a||(a=b.getSelection().getSelectedElement());if(a&&a.is("img")&&!a.data("cke-realelement")&&!a.isReadOnly())return a}function f(b){var a=b.getStyle("float");if("inherit"==a||"none"==a)a=0;a||(a=b.getAttribute("align"));return a}CKEDITOR.plugins.add("image",{requires:"dialog",init:function(b){if(!b.plugins.image2){CKEDITOR.dialog.add("image",this.path+"dialogs/image.js");var a="img[alt,!src]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}";
+CKEDITOR.dialog.isTabEnabled(b,"image","advanced")&&(a="img[alt,dir,id,lang,longdesc,!src,title]{*}(*)");b.addCommand("image",new CKEDITOR.dialogCommand("image",{allowedContent:a,requiredContent:"img[alt,src]",contentTransformations:[["img{width}: sizeToStyle","img[width]: sizeToAttribute"],["img{float}: alignmentToStyle","img[align]: alignmentToAttribute"]]}));b.ui.addButton&&b.ui.addButton("Image",{label:b.lang.common.image,command:"image",toolbar:"insert,10"});b.on("doubleclick",function(b){var a=
+b.data.element;a.is("img")&&(!a.data("cke-realelement")&&!a.isReadOnly())&&(b.data.dialog="image")});b.addMenuItems&&b.addMenuItems({image:{label:b.lang.image.menu,command:"image",group:"image"}});b.contextMenu&&b.contextMenu.addListener(function(a){if(e(b,a))return{image:CKEDITOR.TRISTATE_OFF}})}},afterInit:function(b){function a(a){var d=b.getCommand("justify"+a);if(d){if("left"==a||"right"==a)d.on("exec",function(d){var c=e(b),g;c&&(g=f(c),g==a?(c.removeStyle("float"),a==f(c)&&c.removeAttribute("align")):
+c.setStyle("float",a),d.cancel())});d.on("refresh",function(d){var c=e(b);c&&(c=f(c),this.setState(c==a?CKEDITOR.TRISTATE_ON:"right"==a||"left"==a?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED),d.cancel())})}}b.plugins.image2||(a("left"),a("right"),a("center"),a("block"))}})})();CKEDITOR.config.image_removeLinkByEmptyURL=!0;(function(){function k(a,b){var e,f;b.on("refresh",function(a){var b=[i],c;for(c in a.data.states)b.push(a.data.states[c]);this.setState(CKEDITOR.tools.search(b,m)?m:i)},b,null,100);b.on("exec",function(b){e=a.getSelection();f=e.createBookmarks(1);b.data||(b.data={});b.data.done=!1},b,null,0);b.on("exec",function(){a.forceNextSelectionCheck();e.selectBookmarks(f)},b,null,100)}var i=CKEDITOR.TRISTATE_DISABLED,m=CKEDITOR.TRISTATE_OFF;CKEDITOR.plugins.add("indent",{init:function(a){var b=CKEDITOR.plugins.indent.genericDefinition;
+k(a,a.addCommand("indent",new b(!0)));k(a,a.addCommand("outdent",new b));a.ui.addButton&&(a.ui.addButton("Indent",{label:a.lang.indent.indent,command:"indent",directional:!0,toolbar:"indent,20"}),a.ui.addButton("Outdent",{label:a.lang.indent.outdent,command:"outdent",directional:!0,toolbar:"indent,10"}));a.on("dirChanged",function(b){var f=a.createRange(),j=b.data.node;f.setStartBefore(j);f.setEndAfter(j);for(var l=new CKEDITOR.dom.walker(f),c;c=l.next();)if(c.type==CKEDITOR.NODE_ELEMENT)if(!c.equals(j)&&
+c.getDirection()){f.setStartAfter(c);l=new CKEDITOR.dom.walker(f)}else{var d=a.config.indentClasses;if(d)for(var g=b.data.dir=="ltr"?["_rtl",""]:["","_rtl"],h=0;h<d.length;h++)if(c.hasClass(d[h]+g[0])){c.removeClass(d[h]+g[0]);c.addClass(d[h]+g[1])}d=c.getStyle("margin-right");g=c.getStyle("margin-left");d?c.setStyle("margin-left",d):c.removeStyle("margin-left");g?c.setStyle("margin-right",g):c.removeStyle("margin-right")}})}});CKEDITOR.plugins.indent={genericDefinition:function(a){this.isIndent=
+!!a;this.startDisabled=!this.isIndent},specificDefinition:function(a,b,e){this.name=b;this.editor=a;this.jobs={};this.enterBr=a.config.enterMode==CKEDITOR.ENTER_BR;this.isIndent=!!e;this.relatedGlobal=e?"indent":"outdent";this.indentKey=e?9:CKEDITOR.SHIFT+9;this.database={}},registerCommands:function(a,b){a.on("pluginsLoaded",function(){for(var a in b)(function(a,b){var e=a.getCommand(b.relatedGlobal),c;for(c in b.jobs)e.on("exec",function(d){d.data.done||(a.fire("lockSnapshot"),b.execJob(a,c)&&(d.data.done=
+!0),a.fire("unlockSnapshot"),CKEDITOR.dom.element.clearAllMarkers(b.database))},this,null,c),e.on("refresh",function(d){d.data.states||(d.data.states={});d.data.states[b.name+"@"+c]=b.refreshJob(a,c,d.data.path)},this,null,c);a.addFeature(b)})(this,b[a])})}};CKEDITOR.plugins.indent.genericDefinition.prototype={context:"p",exec:function(){}};CKEDITOR.plugins.indent.specificDefinition.prototype={execJob:function(a,b){var e=this.jobs[b];if(e.state!=i)return e.exec.call(this,a)},refreshJob:function(a,
+b,e){b=this.jobs[b];b.state=a.activeFilter.checkFeature(this)?b.refresh.call(this,a,e):i;return b.state},getContext:function(a){return a.contains(this.context)}}})();(function(){function s(c){function f(b){for(var e=d.startContainer,a=d.endContainer;e&&!e.getParent().equals(b);)e=e.getParent();for(;a&&!a.getParent().equals(b);)a=a.getParent();if(!e||!a)return!1;for(var g=e,e=[],i=!1;!i;)g.equals(a)&&(i=!0),e.push(g),g=g.getNext();if(1>e.length)return!1;g=b.getParents(!0);for(a=0;a<g.length;a++)if(g[a].getName&&m[g[a].getName()]){b=g[a];break}for(var g=j.isIndent?1:-1,a=e[0],e=e[e.length-1],i=CKEDITOR.plugins.list.listToArray(b,n),l=i[e.getCustomData("listarray_index")].indent,
+a=a.getCustomData("listarray_index");a<=e.getCustomData("listarray_index");a++)if(i[a].indent+=g,0<g){var h=i[a].parent;i[a].parent=new CKEDITOR.dom.element(h.getName(),h.getDocument())}for(a=e.getCustomData("listarray_index")+1;a<i.length&&i[a].indent>l;a++)i[a].indent+=g;e=CKEDITOR.plugins.list.arrayToList(i,n,null,c.config.enterMode,b.getDirection());if(!j.isIndent){var f;if((f=b.getParent())&&f.is("li"))for(var g=e.listNode.getChildren(),o=[],k,a=g.count()-1;0<=a;a--)(k=g.getItem(a))&&(k.is&&
+k.is("li"))&&o.push(k)}e&&e.listNode.replace(b);if(o&&o.length)for(a=0;a<o.length;a++){for(k=b=o[a];(k=k.getNext())&&k.is&&k.getName()in m;)CKEDITOR.env.needsNbspFiller&&!b.getFirst(t)&&b.append(d.document.createText(" ")),b.append(k);b.insertAfter(f)}e&&c.fire("contentDomInvalidated");return!0}for(var j=this,n=this.database,m=this.context,l=c.getSelection(),l=(l&&l.getRanges()).createIterator(),d;d=l.getNextRange();){for(var b=d.getCommonAncestor();b&&!(b.type==CKEDITOR.NODE_ELEMENT&&m[b.getName()]);)b=
+b.getParent();b||(b=d.startPath().contains(m))&&d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);if(!b){var h=d.getEnclosedNode();h&&(h.type==CKEDITOR.NODE_ELEMENT&&h.getName()in m)&&(d.setStartAt(h,CKEDITOR.POSITION_AFTER_START),d.setEndAt(h,CKEDITOR.POSITION_BEFORE_END),b=h)}b&&(d.startContainer.type==CKEDITOR.NODE_ELEMENT&&d.startContainer.getName()in m)&&(h=new CKEDITOR.dom.walker(d),h.evaluator=p,d.startContainer=h.next());b&&(d.endContainer.type==CKEDITOR.NODE_ELEMENT&&d.endContainer.getName()in m)&&
+(h=new CKEDITOR.dom.walker(d),h.evaluator=p,d.endContainer=h.previous());if(b)return f(b)}return 0}function p(c){return c.type==CKEDITOR.NODE_ELEMENT&&c.is("li")}function t(c){return u(c)&&v(c)}var u=CKEDITOR.dom.walker.whitespaces(!0),v=CKEDITOR.dom.walker.bookmark(!1,!0),q=CKEDITOR.TRISTATE_DISABLED,r=CKEDITOR.TRISTATE_OFF;CKEDITOR.plugins.add("indentlist",{requires:"indent",init:function(c){function f(c){j.specificDefinition.apply(this,arguments);this.requiredContent=["ul","ol"];c.on("key",function(f){if("wysiwyg"==
+c.mode&&f.data.keyCode==this.indentKey){var l=this.getContext(c.elementPath());if(l&&(!this.isIndent||!CKEDITOR.plugins.indentList.firstItemInPath(this.context,c.elementPath(),l)))c.execCommand(this.relatedGlobal),f.cancel()}},this);this.jobs[this.isIndent?10:30]={refresh:this.isIndent?function(c,f){var d=this.getContext(f),b=CKEDITOR.plugins.indentList.firstItemInPath(this.context,f,d);return!d||!this.isIndent||b?q:r}:function(c,f){return!this.getContext(f)||this.isIndent?q:r},exec:CKEDITOR.tools.bind(s,
+this)}}var j=CKEDITOR.plugins.indent;j.registerCommands(c,{indentlist:new f(c,"indentlist",!0),outdentlist:new f(c,"outdentlist")});CKEDITOR.tools.extend(f.prototype,j.specificDefinition.prototype,{context:{ol:1,ul:1}})}});CKEDITOR.plugins.indentList={};CKEDITOR.plugins.indentList.firstItemInPath=function(c,f,j){var n=f.contains(p);j||(j=f.contains(c));return j&&n&&n.equals(j.getFirst(p))}})();(function(){function g(a,b){var c=j.exec(a),d=j.exec(b);if(c){if(!c[2]&&"px"==d[2])return d[1];if("px"==c[2]&&!d[2])return d[1]+"px"}return b}var i=CKEDITOR.htmlParser.cssStyle,h=CKEDITOR.tools.cssLength,j=/^((?:\d*(?:\.\d+))|(?:\d+))(.*)?$/i,k={elements:{$:function(a){var b=a.attributes;if((b=(b=(b=b&&b["data-cke-realelement"])&&new CKEDITOR.htmlParser.fragment.fromHtml(decodeURIComponent(b)))&&b.children[0])&&a.attributes["data-cke-resizable"]){var c=(new i(a)).rules,a=b.attributes,d=c.width,c=
+c.height;d&&(a.width=g(a.width,d));c&&(a.height=g(a.height,c))}return b}}};CKEDITOR.plugins.add("fakeobjects",{init:function(a){a.filter.allow("img[!data-cke-realelement,src,alt,title](*){*}","fakeobjects")},afterInit:function(a){(a=(a=a.dataProcessor)&&a.htmlFilter)&&a.addRules(k,{applyToAll:!0})}});CKEDITOR.editor.prototype.createFakeElement=function(a,b,c,d){var e=this.lang.fakeobjects,e=e[c]||e.unknown,b={"class":b,"data-cke-realelement":encodeURIComponent(a.getOuterHtml()),"data-cke-real-node-type":a.type,
+alt:e,title:e,align:a.getAttribute("align")||""};CKEDITOR.env.hc||(b.src=CKEDITOR.tools.transparentImageData);c&&(b["data-cke-real-element-type"]=c);d&&(b["data-cke-resizable"]=d,c=new i,d=a.getAttribute("width"),a=a.getAttribute("height"),d&&(c.rules.width=h(d)),a&&(c.rules.height=h(a)),c.populate(b));return this.document.createElement("img",{attributes:b})};CKEDITOR.editor.prototype.createFakeParserElement=function(a,b,c,d){var e=this.lang.fakeobjects,e=e[c]||e.unknown,f;f=new CKEDITOR.htmlParser.basicWriter;
+a.writeHtml(f);f=f.getHtml();b={"class":b,"data-cke-realelement":encodeURIComponent(f),"data-cke-real-node-type":a.type,alt:e,title:e,align:a.attributes.align||""};CKEDITOR.env.hc||(b.src=CKEDITOR.tools.transparentImageData);c&&(b["data-cke-real-element-type"]=c);d&&(b["data-cke-resizable"]=d,d=a.attributes,a=new i,c=d.width,d=d.height,void 0!==c&&(a.rules.width=h(c)),void 0!==d&&(a.rules.height=h(d)),a.populate(b));return new CKEDITOR.htmlParser.element("img",b)};CKEDITOR.editor.prototype.restoreRealElement=
+function(a){if(a.data("cke-real-node-type")!=CKEDITOR.NODE_ELEMENT)return null;var b=CKEDITOR.dom.element.createFromHtml(decodeURIComponent(a.data("cke-realelement")),this.document);if(a.data("cke-resizable")){var c=a.getStyle("width"),a=a.getStyle("height");c&&b.setAttribute("width",g(b.getAttribute("width"),c));a&&b.setAttribute("height",g(b.getAttribute("height"),a))}return b}})();(function(){function m(c){return c.replace(/'/g,"\\$&")}function n(c){for(var b,a=c.length,f=[],e=0;e<a;e++)b=c.charCodeAt(e),f.push(b);return"String.fromCharCode("+f.join(",")+")"}function o(c,b){var a=c.plugins.link,f=a.compiledProtectionFunction.params,e,d;d=[a.compiledProtectionFunction.name,"("];for(var g=0;g<f.length;g++)a=f[g].toLowerCase(),e=b[a],0<g&&d.push(","),d.push("'",e?m(encodeURIComponent(b[a])):"","'");d.push(")");return d.join("")}function l(c){var c=c.config.emailProtection||"",
+b;c&&"encode"!=c&&(b={},c.replace(/^([^(]+)\(([^)]+)\)$/,function(a,c,e){b.name=c;b.params=[];e.replace(/[^,\s]+/g,function(a){b.params.push(a)})}));return b}CKEDITOR.plugins.add("link",{requires:"dialog,fakeobjects",onLoad:function(){function c(b){return a.replace(/%1/g,"rtl"==b?"right":"left").replace(/%2/g,"cke_contents_"+b)}var b="background:url("+CKEDITOR.getUrl(this.path+"images"+(CKEDITOR.env.hidpi?"/hidpi":"")+"/anchor.png")+") no-repeat %1 center;border:1px dotted #00f;background-size:16px;",
+a=".%2 a.cke_anchor,.%2 a.cke_anchor_empty,.cke_editable.%2 a[name],.cke_editable.%2 a[data-cke-saved-name]{"+b+"padding-%1:18px;cursor:auto;}.%2 img.cke_anchor{"+b+"width:16px;min-height:15px;height:1.15em;vertical-align:text-bottom;}";CKEDITOR.addCss(c("ltr")+c("rtl"))},init:function(c){var b="a[!href]";CKEDITOR.dialog.isTabEnabled(c,"link","advanced")&&(b=b.replace("]",",accesskey,charset,dir,id,lang,name,rel,tabindex,title,type]{*}(*)"));CKEDITOR.dialog.isTabEnabled(c,"link","target")&&(b=b.replace("]",
+",target,onclick]"));c.addCommand("link",new CKEDITOR.dialogCommand("link",{allowedContent:b,requiredContent:"a[href]"}));c.addCommand("anchor",new CKEDITOR.dialogCommand("anchor",{allowedContent:"a[!name,id]",requiredContent:"a[name]"}));c.addCommand("unlink",new CKEDITOR.unlinkCommand);c.addCommand("removeAnchor",new CKEDITOR.removeAnchorCommand);c.setKeystroke(CKEDITOR.CTRL+76,"link");c.ui.addButton&&(c.ui.addButton("Link",{label:c.lang.link.toolbar,command:"link",toolbar:"links,10"}),c.ui.addButton("Unlink",
+{label:c.lang.link.unlink,command:"unlink",toolbar:"links,20"}),c.ui.addButton("Anchor",{label:c.lang.link.anchor.toolbar,command:"anchor",toolbar:"links,30"}));CKEDITOR.dialog.add("link",this.path+"dialogs/link.js");CKEDITOR.dialog.add("anchor",this.path+"dialogs/anchor.js");c.on("doubleclick",function(a){var b=CKEDITOR.plugins.link.getSelectedLink(c)||a.data.element;if(!b.isReadOnly())if(b.is("a")){a.data.dialog=b.getAttribute("name")&&(!b.getAttribute("href")||!b.getChildCount())?"anchor":"link";
+a.data.link=b}else if(CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,b))a.data.dialog="anchor"},null,null,0);c.on("doubleclick",function(a){a.data.dialog in{link:1,anchor:1}&&a.data.link&&c.getSelection().selectElement(a.data.link)},null,null,20);c.addMenuItems&&c.addMenuItems({anchor:{label:c.lang.link.anchor.menu,command:"anchor",group:"anchor",order:1},removeAnchor:{label:c.lang.link.anchor.remove,command:"removeAnchor",group:"anchor",order:5},link:{label:c.lang.link.menu,command:"link",group:"link",
+order:1},unlink:{label:c.lang.link.unlink,command:"unlink",group:"link",order:5}});c.contextMenu&&c.contextMenu.addListener(function(a){if(!a||a.isReadOnly())return null;a=CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,a);if(!a&&!(a=CKEDITOR.plugins.link.getSelectedLink(c)))return null;var b={};a.getAttribute("href")&&a.getChildCount()&&(b={link:CKEDITOR.TRISTATE_OFF,unlink:CKEDITOR.TRISTATE_OFF});if(a&&a.hasAttribute("name"))b.anchor=b.removeAnchor=CKEDITOR.TRISTATE_OFF;return b});this.compiledProtectionFunction=
+l(c)},afterInit:function(c){c.dataProcessor.dataFilter.addRules({elements:{a:function(a){return!a.attributes.name?null:!a.children.length?c.createFakeParserElement(a,"cke_anchor","anchor"):null}}});var b=c._.elementsPath&&c._.elementsPath.filters;b&&b.push(function(a,b){if("a"==b&&(CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,a)||a.getAttribute("name")&&(!a.getAttribute("href")||!a.getChildCount())))return"anchor"})}});var p=/^javascript:/,q=/^mailto:([^?]+)(?:\?(.+))?$/,r=/subject=([^;?:@&=$,\/]*)/,
+s=/body=([^;?:@&=$,\/]*)/,t=/^#(.*)$/,u=/^((?:http|https|ftp|news):\/\/)?(.*)$/,v=/^(_(?:self|top|parent|blank))$/,w=/^javascript:void\(location\.href='mailto:'\+String\.fromCharCode\(([^)]+)\)(?:\+'(.*)')?\)$/,x=/^javascript:([^(]+)\(([^)]+)\)$/,y=/\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/,z=/(?:^|,)([^=]+)=(\d+|yes|no)/gi,j={id:"advId",dir:"advLangDir",accessKey:"advAccessKey",name:"advName",lang:"advLangCode",tabindex:"advTabIndex",title:"advTitle",
+type:"advContentType","class":"advCSSClasses",charset:"advCharset",style:"advStyles",rel:"advRel"};CKEDITOR.plugins.link={getSelectedLink:function(c){var b=c.getSelection(),a=b.getSelectedElement();return a&&a.is("a")?a:(b=b.getRanges()[0])?(b.shrink(CKEDITOR.SHRINK_TEXT),c.elementPath(b.getCommonAncestor()).contains("a",1)):null},getEditorAnchors:function(c){for(var b=c.editable(),a=b.isInline()&&!c.plugins.divarea?c.document:b,b=a.getElementsByTag("a"),a=a.getElementsByTag("img"),f=[],e=0,d;d=b.getItem(e++);)if(d.data("cke-saved-name")||
+d.hasAttribute("name"))f.push({name:d.data("cke-saved-name")||d.getAttribute("name"),id:d.getAttribute("id")});for(e=0;d=a.getItem(e++);)(d=this.tryRestoreFakeAnchor(c,d))&&f.push({name:d.getAttribute("name"),id:d.getAttribute("id")});return f},fakeAnchor:!0,tryRestoreFakeAnchor:function(c,b){if(b&&b.data("cke-real-element-type")&&"anchor"==b.data("cke-real-element-type")){var a=c.restoreRealElement(b);if(a.data("cke-saved-name"))return a}},parseLinkAttributes:function(c,b){var a=b&&(b.data("cke-saved-href")||
+b.getAttribute("href"))||"",f=c.plugins.link.compiledProtectionFunction,e=c.config.emailProtection,d,g={};a.match(p)&&("encode"==e?a=a.replace(w,function(a,b,c){return"mailto:"+String.fromCharCode.apply(String,b.split(","))+(c&&c.replace(/\\'/g,"'"))}):e&&a.replace(x,function(a,b,c){if(b==f.name){g.type="email";for(var a=g.email={},b=/(^')|('$)/g,c=c.match(/[^,\s]+/g),d=c.length,e,h,i=0;i<d;i++)e=decodeURIComponent,h=c[i].replace(b,"").replace(/\\'/g,"'"),h=e(h),e=f.params[i].toLowerCase(),a[e]=h;
+a.address=[a.name,a.domain].join("@")}}));if(!g.type)if(e=a.match(t))g.type="anchor",g.anchor={},g.anchor.name=g.anchor.id=e[1];else if(e=a.match(q)){d=a.match(r);a=a.match(s);g.type="email";var i=g.email={};i.address=e[1];d&&(i.subject=decodeURIComponent(d[1]));a&&(i.body=decodeURIComponent(a[1]))}else if(a&&(d=a.match(u)))g.type="url",g.url={},g.url.protocol=d[1],g.url.url=d[2];if(b){if(a=b.getAttribute("target"))g.target={type:a.match(v)?a:"frame",name:a};else if(a=(a=b.data("cke-pa-onclick")||
+b.getAttribute("onclick"))&&a.match(y))for(g.target={type:"popup",name:a[1]};e=z.exec(a[2]);)("yes"==e[2]||"1"==e[2])&&!(e[1]in{height:1,width:1,top:1,left:1})?g.target[e[1]]=!0:isFinite(e[2])&&(g.target[e[1]]=e[2]);var a={},h;for(h in j)(e=b.getAttribute(h))&&(a[j[h]]=e);if(h=b.data("cke-saved-name")||a.advName)a.advName=h;CKEDITOR.tools.isEmpty(a)||(g.advanced=a)}return g},getLinkAttributes:function(c,b){var a=c.config.emailProtection||"",f={};switch(b.type){case "url":var a=b.url&&void 0!==b.url.protocol?
+b.url.protocol:"http://",e=b.url&&CKEDITOR.tools.trim(b.url.url)||"";f["data-cke-saved-href"]=0===e.indexOf("/")?e:a+e;break;case "anchor":a=b.anchor&&b.anchor.id;f["data-cke-saved-href"]="#"+(b.anchor&&b.anchor.name||a||"");break;case "email":var d=b.email,e=d.address;switch(a){case "":case "encode":var g=encodeURIComponent(d.subject||""),i=encodeURIComponent(d.body||""),d=[];g&&d.push("subject="+g);i&&d.push("body="+i);d=d.length?"?"+d.join("&"):"";"encode"==a?(a=["javascript:void(location.href='mailto:'+",
+n(e)],d&&a.push("+'",m(d),"'"),a.push(")")):a=["mailto:",e,d];break;default:a=e.split("@",2),d.name=a[0],d.domain=a[1],a=["javascript:",o(c,d)]}f["data-cke-saved-href"]=a.join("")}if(b.target)if("popup"==b.target.type){for(var a=["window.open(this.href, '",b.target.name||"","', '"],h="resizable status location toolbar menubar fullscreen scrollbars dependent".split(" "),e=h.length,g=function(a){b.target[a]&&h.push(a+"="+b.target[a])},d=0;d<e;d++)h[d]+=b.target[h[d]]?"=yes":"=no";g("width");g("left");
+g("height");g("top");a.push(h.join(","),"'); return false;");f["data-cke-pa-onclick"]=a.join("")}else"notSet"!=b.target.type&&b.target.name&&(f.target=b.target.name);if(b.advanced){for(var k in j)(a=b.advanced[j[k]])&&(f[k]=a);f.name&&(f["data-cke-saved-name"]=f.name)}f["data-cke-saved-href"]&&(f.href=f["data-cke-saved-href"]);k=CKEDITOR.tools.extend({target:1,onclick:1,"data-cke-pa-onclick":1,"data-cke-saved-name":1},j);for(var l in f)delete k[l];return{set:f,removed:CKEDITOR.tools.objectKeys(k)}}};
+CKEDITOR.unlinkCommand=function(){};CKEDITOR.unlinkCommand.prototype={exec:function(c){var b=new CKEDITOR.style({element:"a",type:CKEDITOR.STYLE_INLINE,alwaysRemoveElement:1});c.removeStyle(b)},refresh:function(c,b){var a=b.lastElement&&b.lastElement.getAscendant("a",!0);a&&"a"==a.getName()&&a.getAttribute("href")&&a.getChildCount()?this.setState(CKEDITOR.TRISTATE_OFF):this.setState(CKEDITOR.TRISTATE_DISABLED)},contextSensitive:1,startDisabled:1,requiredContent:"a[href]"};CKEDITOR.removeAnchorCommand=
+function(){};CKEDITOR.removeAnchorCommand.prototype={exec:function(c){var b=c.getSelection(),a=b.createBookmarks(),f;if(b&&(f=b.getSelectedElement())&&(!f.getChildCount()?CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,f):f.is("a")))f.remove(1);else if(f=CKEDITOR.plugins.link.getSelectedLink(c))f.hasAttribute("href")?(f.removeAttributes({name:1,"data-cke-saved-name":1}),f.removeClass("cke_anchor")):f.remove(1);b.selectBookmarks(a)},requiredContent:"a[name]"};CKEDITOR.tools.extend(CKEDITOR.config,{linkShowAdvancedTab:!0,
+linkShowTargetTab:!0})})();(function(){function E(b,k,e){function d(d){if((a=c[d?"getFirst":"getLast"]())&&(!a.is||!a.isBlockBoundary())&&(m=k.root[d?"getPrevious":"getNext"](CKEDITOR.dom.walker.invisible(!0)))&&(!m.is||!m.isBlockBoundary({br:1})))b.document.createElement("br")[d?"insertBefore":"insertAfter"](a)}for(var f=CKEDITOR.plugins.list.listToArray(k.root,e),g=[],i=0;i<k.contents.length;i++){var h=k.contents[i];if((h=h.getAscendant("li",!0))&&!h.getCustomData("list_item_processed"))g.push(h),CKEDITOR.dom.element.setMarker(e,
+h,"list_item_processed",!0)}h=null;for(i=0;i<g.length;i++)h=g[i].getCustomData("listarray_index"),f[h].indent=-1;for(i=h+1;i<f.length;i++)if(f[i].indent>f[i-1].indent+1){g=f[i-1].indent+1-f[i].indent;for(h=f[i].indent;f[i]&&f[i].indent>=h;)f[i].indent+=g,i++;i--}var c=CKEDITOR.plugins.list.arrayToList(f,e,null,b.config.enterMode,k.root.getAttribute("dir")).listNode,a,m;d(!0);d();c.replace(k.root);b.fire("contentDomInvalidated")}function x(b,k){this.name=b;this.context=this.type=k;this.allowedContent=
+k+" li";this.requiredContent=k}function A(b,k,e,d){for(var f,g;f=b[d?"getLast":"getFirst"](F);)(g=f.getDirection(1))!==k.getDirection(1)&&f.setAttribute("dir",g),f.remove(),e?f[d?"insertBefore":"insertAfter"](e):k.append(f,d)}function B(b){function k(e){var d=b[e?"getPrevious":"getNext"](q);d&&(d.type==CKEDITOR.NODE_ELEMENT&&d.is(b.getName()))&&(A(b,d,null,!e),b.remove(),b=d)}k();k(1)}function C(b){return b.type==CKEDITOR.NODE_ELEMENT&&(b.getName()in CKEDITOR.dtd.$block||b.getName()in CKEDITOR.dtd.$listItem)&&
+CKEDITOR.dtd[b.getName()]["#"]}function y(b,k,e){b.fire("saveSnapshot");e.enlarge(CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS);var d=e.extractContents();k.trim(!1,!0);var f=k.createBookmark(),g=new CKEDITOR.dom.elementPath(k.startContainer),i=g.block,g=g.lastElement.getAscendant("li",1)||i,h=new CKEDITOR.dom.elementPath(e.startContainer),c=h.contains(CKEDITOR.dtd.$listItem),h=h.contains(CKEDITOR.dtd.$list);i?(i=i.getBogus())&&i.remove():h&&(i=h.getPrevious(q))&&v(i)&&i.remove();(i=d.getLast())&&(i.type==
+CKEDITOR.NODE_ELEMENT&&i.is("br"))&&i.remove();(i=k.startContainer.getChild(k.startOffset))?d.insertBefore(i):k.startContainer.append(d);if(c&&(d=w(c)))g.contains(c)?(A(d,c.getParent(),c),d.remove()):g.append(d);for(;e.checkStartOfBlock()&&e.checkEndOfBlock();){h=e.startPath();d=h.block;if(!d)break;d.is("li")&&(g=d.getParent(),d.equals(g.getLast(q))&&d.equals(g.getFirst(q))&&(d=g));e.moveToPosition(d,CKEDITOR.POSITION_BEFORE_START);d.remove()}e=e.clone();d=b.editable();e.setEndAt(d,CKEDITOR.POSITION_BEFORE_END);
+e=new CKEDITOR.dom.walker(e);e.evaluator=function(a){return q(a)&&!v(a)};(e=e.next())&&(e.type==CKEDITOR.NODE_ELEMENT&&e.getName()in CKEDITOR.dtd.$list)&&B(e);k.moveToBookmark(f);k.select();b.fire("saveSnapshot")}function w(b){return(b=b.getLast(q))&&b.type==CKEDITOR.NODE_ELEMENT&&b.getName()in r?b:null}var r={ol:1,ul:1},G=CKEDITOR.dom.walker.whitespaces(),D=CKEDITOR.dom.walker.bookmark(),q=function(b){return!(G(b)||D(b))},v=CKEDITOR.dom.walker.bogus();CKEDITOR.plugins.list={listToArray:function(b,
+k,e,d,f){if(!r[b.getName()])return[];d||(d=0);e||(e=[]);for(var g=0,i=b.getChildCount();g<i;g++){var h=b.getChild(g);h.type==CKEDITOR.NODE_ELEMENT&&h.getName()in CKEDITOR.dtd.$list&&CKEDITOR.plugins.list.listToArray(h,k,e,d+1);if("li"==h.$.nodeName.toLowerCase()){var c={parent:b,indent:d,element:h,contents:[]};f?c.grandparent=f:(c.grandparent=b.getParent(),c.grandparent&&"li"==c.grandparent.$.nodeName.toLowerCase()&&(c.grandparent=c.grandparent.getParent()));k&&CKEDITOR.dom.element.setMarker(k,h,
+"listarray_index",e.length);e.push(c);for(var a=0,m=h.getChildCount(),j;a<m;a++)j=h.getChild(a),j.type==CKEDITOR.NODE_ELEMENT&&r[j.getName()]?CKEDITOR.plugins.list.listToArray(j,k,e,d+1,c.grandparent):c.contents.push(j)}}return e},arrayToList:function(b,k,e,d,f){e||(e=0);if(!b||b.length<e+1)return null;for(var g,i=b[e].parent.getDocument(),h=new CKEDITOR.dom.documentFragment(i),c=null,a=e,m=Math.max(b[e].indent,0),j=null,n,l,p=d==CKEDITOR.ENTER_P?"p":"div";;){var o=b[a];g=o.grandparent;n=o.element.getDirection(1);
+if(o.indent==m){if(!c||b[a].parent.getName()!=c.getName())c=b[a].parent.clone(!1,1),f&&c.setAttribute("dir",f),h.append(c);j=c.append(o.element.clone(0,1));n!=c.getDirection(1)&&j.setAttribute("dir",n);for(g=0;g<o.contents.length;g++)j.append(o.contents[g].clone(1,1));a++}else if(o.indent==Math.max(m,0)+1)o=b[a-1].element.getDirection(1),a=CKEDITOR.plugins.list.arrayToList(b,null,a,d,o!=n?n:null),!j.getChildCount()&&(CKEDITOR.env.needsNbspFiller&&7>=i.$.documentMode)&&j.append(i.createText(" ")),
+j.append(a.listNode),a=a.nextIndex;else if(-1==o.indent&&!e&&g){r[g.getName()]?(j=o.element.clone(!1,!0),n!=g.getDirection(1)&&j.setAttribute("dir",n)):j=new CKEDITOR.dom.documentFragment(i);var c=g.getDirection(1)!=n,u=o.element,z=u.getAttribute("class"),v=u.getAttribute("style"),w=j.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&(d!=CKEDITOR.ENTER_BR||c||v||z),s,x=o.contents.length,t;for(g=0;g<x;g++)if(s=o.contents[g],D(s)&&1<x)w?t=s.clone(1,1):j.append(s.clone(1,1));else if(s.type==CKEDITOR.NODE_ELEMENT&&
+s.isBlockBoundary()){c&&!s.getDirection()&&s.setAttribute("dir",n);l=s;var y=u.getAttribute("style");y&&l.setAttribute("style",y.replace(/([^;])$/,"$1;")+(l.getAttribute("style")||""));z&&s.addClass(z);l=null;t&&(j.append(t),t=null);j.append(s.clone(1,1))}else w?(l||(l=i.createElement(p),j.append(l),c&&l.setAttribute("dir",n)),v&&l.setAttribute("style",v),z&&l.setAttribute("class",z),t&&(l.append(t),t=null),l.append(s.clone(1,1))):j.append(s.clone(1,1));t&&((l||j).append(t),t=null);j.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT&&
+a!=b.length-1&&(CKEDITOR.env.needsBrFiller&&(n=j.getLast())&&(n.type==CKEDITOR.NODE_ELEMENT&&n.is("br"))&&n.remove(),n=j.getLast(q),(!n||!(n.type==CKEDITOR.NODE_ELEMENT&&n.is(CKEDITOR.dtd.$block)))&&j.append(i.createElement("br")));n=j.$.nodeName.toLowerCase();("div"==n||"p"==n)&&j.appendBogus();h.append(j);c=null;a++}else return null;l=null;if(b.length<=a||Math.max(b[a].indent,0)<m)break}if(k)for(b=h.getFirst();b;){if(b.type==CKEDITOR.NODE_ELEMENT&&(CKEDITOR.dom.element.clearMarkers(k,b),b.getName()in
+CKEDITOR.dtd.$listItem&&(e=b,i=f=d=void 0,d=e.getDirection()))){for(f=e.getParent();f&&!(i=f.getDirection());)f=f.getParent();d==i&&e.removeAttribute("dir")}b=b.getNextSourceNode()}return{listNode:h,nextIndex:a}}};var H=/^h[1-6]$/,F=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT);x.prototype={exec:function(b){this.refresh(b,b.elementPath());var k=b.config,e=b.getSelection(),d=e&&e.getRanges();if(this.state==CKEDITOR.TRISTATE_OFF){var f=b.editable();if(f.getFirst(q)){var g=1==d.length&&d[0];(k=
+g&&g.getEnclosedNode())&&(k.is&&this.type==k.getName())&&this.setState(CKEDITOR.TRISTATE_ON)}else k.enterMode==CKEDITOR.ENTER_BR?f.appendBogus():d[0].fixBlock(1,k.enterMode==CKEDITOR.ENTER_P?"p":"div"),e.selectRanges(d)}for(var k=e.createBookmarks(!0),f=[],i={},d=d.createIterator(),h=0;(g=d.getNextRange())&&++h;){var c=g.getBoundaryNodes(),a=c.startNode,m=c.endNode;a.type==CKEDITOR.NODE_ELEMENT&&"td"==a.getName()&&g.setStartAt(c.startNode,CKEDITOR.POSITION_AFTER_START);m.type==CKEDITOR.NODE_ELEMENT&&
+"td"==m.getName()&&g.setEndAt(c.endNode,CKEDITOR.POSITION_BEFORE_END);g=g.createIterator();for(g.forceBrBreak=this.state==CKEDITOR.TRISTATE_OFF;c=g.getNextParagraph();)if(!c.getCustomData("list_block")){CKEDITOR.dom.element.setMarker(i,c,"list_block",1);for(var j=b.elementPath(c),a=j.elements,m=0,j=j.blockLimit,n,l=a.length-1;0<=l&&(n=a[l]);l--)if(r[n.getName()]&&j.contains(n)){j.removeCustomData("list_group_object_"+h);(a=n.getCustomData("list_group_object"))?a.contents.push(c):(a={root:n,contents:[c]},
+f.push(a),CKEDITOR.dom.element.setMarker(i,n,"list_group_object",a));m=1;break}m||(m=j,m.getCustomData("list_group_object_"+h)?m.getCustomData("list_group_object_"+h).contents.push(c):(a={root:m,contents:[c]},CKEDITOR.dom.element.setMarker(i,m,"list_group_object_"+h,a),f.push(a)))}}for(n=[];0<f.length;)if(a=f.shift(),this.state==CKEDITOR.TRISTATE_OFF)if(r[a.root.getName()]){d=b;h=a;a=i;g=n;m=CKEDITOR.plugins.list.listToArray(h.root,a);j=[];for(c=0;c<h.contents.length;c++)if(l=h.contents[c],(l=l.getAscendant("li",
+!0))&&!l.getCustomData("list_item_processed"))j.push(l),CKEDITOR.dom.element.setMarker(a,l,"list_item_processed",!0);for(var l=h.root.getDocument(),p=void 0,o=void 0,c=0;c<j.length;c++){var u=j[c].getCustomData("listarray_index"),p=m[u].parent;p.is(this.type)||(o=l.createElement(this.type),p.copyAttributes(o,{start:1,type:1}),o.removeStyle("list-style-type"),m[u].parent=o)}a=CKEDITOR.plugins.list.arrayToList(m,a,null,d.config.enterMode);m=void 0;j=a.listNode.getChildCount();for(c=0;c<j&&(m=a.listNode.getChild(c));c++)m.getName()==
+this.type&&g.push(m);a.listNode.replace(h.root);d.fire("contentDomInvalidated")}else{m=b;c=a;g=n;j=c.contents;d=c.root.getDocument();h=[];1==j.length&&j[0].equals(c.root)&&(a=d.createElement("div"),j[0].moveChildren&&j[0].moveChildren(a),j[0].append(a),j[0]=a);c=c.contents[0].getParent();for(l=0;l<j.length;l++)c=c.getCommonAncestor(j[l].getParent());p=m.config.useComputedState;m=a=void 0;p=void 0===p||p;for(l=0;l<j.length;l++)for(o=j[l];u=o.getParent();){if(u.equals(c)){h.push(o);!m&&o.getDirection()&&
+(m=1);o=o.getDirection(p);null!==a&&(a=a&&a!=o?null:o);break}o=u}if(!(1>h.length)){j=h[h.length-1].getNext();l=d.createElement(this.type);g.push(l);for(p=g=void 0;h.length;)g=h.shift(),p=d.createElement("li"),g.is("pre")||H.test(g.getName())||"false"==g.getAttribute("contenteditable")?g.appendTo(p):(g.copyAttributes(p),a&&g.getDirection()&&(p.removeStyle("direction"),p.removeAttribute("dir")),g.moveChildren(p),g.remove()),p.appendTo(l);a&&m&&l.setAttribute("dir",a);j?l.insertBefore(j):l.appendTo(c)}}else this.state==
+CKEDITOR.TRISTATE_ON&&r[a.root.getName()]&&E.call(this,b,a,i);for(l=0;l<n.length;l++)B(n[l]);CKEDITOR.dom.element.clearAllMarkers(i);e.selectBookmarks(k);b.focus()},refresh:function(b,k){var e=k.contains(r,1),d=k.blockLimit||k.root;e&&d.contains(e)?this.setState(e.is(this.type)?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF):this.setState(CKEDITOR.TRISTATE_OFF)}};CKEDITOR.plugins.add("list",{requires:"indentlist",init:function(b){b.blockless||(b.addCommand("numberedlist",new x("numberedlist","ol")),b.addCommand("bulletedlist",
+new x("bulletedlist","ul")),b.ui.addButton&&(b.ui.addButton("NumberedList",{label:b.lang.list.numberedlist,command:"numberedlist",directional:!0,toolbar:"list,10"}),b.ui.addButton("BulletedList",{label:b.lang.list.bulletedlist,command:"bulletedlist",directional:!0,toolbar:"list,20"})),b.on("key",function(k){var e=k.data.domEvent.getKey(),d;if(b.mode=="wysiwyg"&&e in{8:1,46:1}){var f=b.getSelection().getRanges()[0],g=f&&f.startPath();if(f&&f.collapsed){var i=e==8,h=b.editable(),c=new CKEDITOR.dom.walker(f.clone());
+c.evaluator=function(a){return q(a)&&!v(a)};c.guard=function(a,b){return!(b&&a.type==CKEDITOR.NODE_ELEMENT&&a.is("table"))};e=f.clone();if(i){var a;if((a=g.contains(r))&&f.checkBoundaryOfElement(a,CKEDITOR.START)&&(a=a.getParent())&&a.is("li")&&(a=w(a))){d=a;a=a.getPrevious(q);e.moveToPosition(a&&v(a)?a:d,CKEDITOR.POSITION_BEFORE_START)}else{c.range.setStartAt(h,CKEDITOR.POSITION_AFTER_START);c.range.setEnd(f.startContainer,f.startOffset);if((a=c.previous())&&a.type==CKEDITOR.NODE_ELEMENT&&(a.getName()in
+r||a.is("li"))){if(!a.is("li")){c.range.selectNodeContents(a);c.reset();c.evaluator=C;a=c.previous()}d=a;e.moveToElementEditEnd(d)}}if(d){y(b,e,f);k.cancel()}else if((e=g.contains(r))&&f.checkBoundaryOfElement(e,CKEDITOR.START)){d=e.getFirst(q);if(f.checkBoundaryOfElement(d,CKEDITOR.START)){a=e.getPrevious(q);if(w(d)){if(a){f.moveToElementEditEnd(a);f.select()}}else b.execCommand("outdent");k.cancel()}}}else if(d=g.contains("li")){c.range.setEndAt(h,CKEDITOR.POSITION_BEFORE_END);d=(g=d.getLast(q))&&
+C(g)?g:d;h=0;if((a=c.next())&&a.type==CKEDITOR.NODE_ELEMENT&&a.getName()in r&&a.equals(g)){h=1;a=c.next()}else f.checkBoundaryOfElement(d,CKEDITOR.END)&&(h=1);if(h&&a){f=f.clone();f.moveToElementEditStart(a);y(b,e,f);k.cancel()}}else{c.range.setEndAt(h,CKEDITOR.POSITION_BEFORE_END);if((a=c.next())&&a.type==CKEDITOR.NODE_ELEMENT&&a.is(r)){a=a.getFirst(q);if(g.block&&f.checkStartOfBlock()&&f.checkEndOfBlock()){g.block.remove();f.moveToElementEditStart(a);f.select()}else if(w(a)){f.moveToElementEditStart(a);
+f.select()}else{f=f.clone();f.moveToElementEditStart(a);y(b,e,f)}k.cancel()}}setTimeout(function(){b.selectionChange(1)})}}}))}})})();(function(){function Q(a,c,d){return m(c)&&m(d)&&d.equals(c.getNext(function(a){return!(z(a)||A(a)||p(a))}))}function u(a){this.upper=a[0];this.lower=a[1];this.set.apply(this,a.slice(2))}function J(a){var c=a.element;if(c&&m(c)&&(c=c.getAscendant(a.triggers,!0))&&a.editable.contains(c)){var d=K(c);if("true"==d.getAttribute("contenteditable"))return c;if(d.is(a.triggers))return d}return null}function ga(a,c,d){o(a,c);o(a,d);a=c.size.bottom;d=d.size.top;return a&&d?0|(a+d)/2:a||d}function r(a,c,d){return c=
+c[d?"getPrevious":"getNext"](function(b){return b&&b.type==CKEDITOR.NODE_TEXT&&!z(b)||m(b)&&!p(b)&&!v(a,b)})}function K(a,c){if(a.data("cke-editable"))return null;for(c||(a=a.getParent());a&&!a.data("cke-editable");){if(a.hasAttribute("contenteditable"))return a;a=a.getParent()}return null}function ha(a){var c=a.doc,d=B('<span contenteditable="false" style="'+L+"position:absolute;border-top:1px dashed "+a.boxColor+'"></span>',c),b=CKEDITOR.getUrl(this.path+"images/"+(n.hidpi?"hidpi/":"")+"icon"+(a.rtl?
+"-rtl":"")+".png");q(d,{attach:function(){this.wrap.getParent()||this.wrap.appendTo(a.editable,!0);return this},lineChildren:[q(B('<span title="'+a.editor.lang.magicline.title+'" contenteditable="false">&#8629;</span>',c),{base:L+"height:17px;width:17px;"+(a.rtl?"left":"right")+":17px;background:url("+b+") center no-repeat "+a.boxColor+";cursor:pointer;"+(n.hc?"font-size: 15px;line-height:14px;border:1px solid #fff;text-align:center;":"")+(n.hidpi?"background-size: 9px 10px;":""),looks:["top:-8px;"+
+CKEDITOR.tools.cssVendorPrefix("border-radius","2px",1),"top:-17px;"+CKEDITOR.tools.cssVendorPrefix("border-radius","2px 2px 0px 0px",1),"top:-1px;"+CKEDITOR.tools.cssVendorPrefix("border-radius","0px 0px 2px 2px",1)]}),q(B(R,c),{base:S+"left:0px;border-left-color:"+a.boxColor+";",looks:["border-width:8px 0 8px 8px;top:-8px","border-width:8px 0 0 8px;top:-8px","border-width:0 0 8px 8px;top:0px"]}),q(B(R,c),{base:S+"right:0px;border-right-color:"+a.boxColor+";",looks:["border-width:8px 8px 8px 0;top:-8px",
+"border-width:8px 8px 0 0;top:-8px","border-width:0 8px 8px 0;top:0px"]})],detach:function(){this.wrap.getParent()&&this.wrap.remove();return this},mouseNear:function(){o(a,this);var b=a.holdDistance,c=this.size;return c&&a.mouse.y>c.top-b&&a.mouse.y<c.bottom+b&&a.mouse.x>c.left-b&&a.mouse.x<c.right+b?!0:!1},place:function(){var b=a.view,c=a.editable,d=a.trigger,i=d.upper,h=d.lower,j=i||h,l=j.getParent(),k={};this.trigger=d;i&&o(a,i,!0);h&&o(a,h,!0);o(a,l,!0);a.inInlineMode&&C(a,!0);l.equals(c)?(k.left=
+b.scroll.x,k.right=-b.scroll.x,k.width=""):(k.left=j.size.left-j.size.margin.left+b.scroll.x-(a.inInlineMode?b.editable.left+b.editable.border.left:0),k.width=j.size.outerWidth+j.size.margin.left+j.size.margin.right+b.scroll.x,k.right="");i&&h?k.top=i.size.margin.bottom===h.size.margin.top?0|i.size.bottom+i.size.margin.bottom/2:i.size.margin.bottom<h.size.margin.top?i.size.bottom+i.size.margin.bottom:i.size.bottom+i.size.margin.bottom-h.size.margin.top:i?h||(k.top=i.size.bottom+i.size.margin.bottom):
+k.top=h.size.top-h.size.margin.top;d.is(x)||k.top>b.scroll.y-15&&k.top<b.scroll.y+5?(k.top=a.inInlineMode?0:b.scroll.y,this.look(x)):d.is(y)||k.top>b.pane.bottom-5&&k.top<b.pane.bottom+15?(k.top=a.inInlineMode?b.editable.height+b.editable.padding.top+b.editable.padding.bottom:b.pane.bottom-1,this.look(y)):(a.inInlineMode&&(k.top-=b.editable.top+b.editable.border.top),this.look(s));a.inInlineMode&&(k.top--,k.top+=b.editable.scroll.top,k.left+=b.editable.scroll.left);for(var T in k)k[T]=CKEDITOR.tools.cssLength(k[T]);
+this.setStyles(k)},look:function(a){if(this.oldLook!=a){for(var b=this.lineChildren.length,c;b--;)(c=this.lineChildren[b]).setAttribute("style",c.base+c.looks[0|a/2]);this.oldLook=a}},wrap:new M("span",a.doc)});for(c=d.lineChildren.length;c--;)d.lineChildren[c].appendTo(d);d.look(s);d.appendTo(d.wrap);d.unselectable();d.lineChildren[0].on("mouseup",function(b){d.detach();N(a,function(b){var c=a.line.trigger;b[c.is(D)?"insertBefore":"insertAfter"](c.is(D)?c.lower:c.upper)},!0);a.editor.focus();!n.ie&&
+a.enterMode!=CKEDITOR.ENTER_BR&&a.hotNode.scrollIntoView();b.data.preventDefault(!0)});d.on("mousedown",function(a){a.data.preventDefault(!0)});a.line=d}function N(a,c,d){var b=new CKEDITOR.dom.range(a.doc),e=a.editor,f;n.ie&&a.enterMode==CKEDITOR.ENTER_BR?f=a.doc.createText(E):(f=(f=K(a.element,!0))&&f.data("cke-enter-mode")||a.enterMode,f=new M(F[f],a.doc),f.is("br")||a.doc.createText(E).appendTo(f));d&&e.fire("saveSnapshot");c(f);b.moveToPosition(f,CKEDITOR.POSITION_AFTER_START);e.getSelection().selectRanges([b]);
+a.hotNode=f;d&&e.fire("saveSnapshot")}function U(a,c){return{canUndo:!0,modes:{wysiwyg:1},exec:function(){function d(b){var d=n.ie&&9>n.version?" ":E,f=a.hotNode&&a.hotNode.getText()==d&&a.element.equals(a.hotNode)&&a.lastCmdDirection===!!c;N(a,function(d){f&&a.hotNode&&a.hotNode.remove();d[c?"insertAfter":"insertBefore"](b);d.setAttributes({"data-cke-magicline-hot":1,"data-cke-magicline-dir":!!c});a.lastCmdDirection=!!c});!n.ie&&a.enterMode!=CKEDITOR.ENTER_BR&&a.hotNode.scrollIntoView();a.line.detach()}
+return function(b){var b=b.getSelection().getStartElement(),e,b=b.getAscendant(V,1);if(!W(a,b)&&b&&!b.equals(a.editable)&&!b.contains(a.editable)){if((e=K(b))&&"false"==e.getAttribute("contenteditable"))b=e;a.element=b;e=r(a,b,!c);var f;m(e)&&e.is(a.triggers)&&e.is(ia)&&(!r(a,e,!c)||(f=r(a,e,!c))&&m(f)&&f.is(a.triggers))?d(e):(f=J(a,b),m(f)&&(r(a,f,!c)?(b=r(a,f,!c))&&(m(b)&&b.is(a.triggers))&&d(f):d(f)))}}}()}}function v(a,c){if(!c||!(c.type==CKEDITOR.NODE_ELEMENT&&c.$))return!1;var d=a.line;return d.wrap.equals(c)||
+d.wrap.contains(c)}function m(a){return a&&a.type==CKEDITOR.NODE_ELEMENT&&a.$}function p(a){if(!m(a))return!1;var c;if(!(c=X(a)))m(a)?(c={left:1,right:1,center:1},c=!(!c[a.getComputedStyle("float")]&&!c[a.getAttribute("align")])):c=!1;return c}function X(a){return!!{absolute:1,fixed:1}[a.getComputedStyle("position")]}function G(a,c){return m(c)?c.is(a.triggers):null}function W(a,c){if(!c)return!1;for(var d=c.getParents(1),b=d.length;b--;)for(var e=a.tabuList.length;e--;)if(d[b].hasAttribute(a.tabuList[e]))return!0;
+return!1}function ja(a,c,d){c=c[d?"getLast":"getFirst"](function(b){return a.isRelevant(b)&&!b.is(ka)});if(!c)return!1;o(a,c);return d?c.size.top>a.mouse.y:c.size.bottom<a.mouse.y}function Y(a){var c=a.editable,d=a.mouse,b=a.view,e=a.triggerOffset;C(a);var f=d.y>(a.inInlineMode?b.editable.top+b.editable.height/2:Math.min(b.editable.height,b.pane.height)/2),c=c[f?"getLast":"getFirst"](function(a){return!(z(a)||A(a))});if(!c)return null;v(a,c)&&(c=a.line.wrap[f?"getPrevious":"getNext"](function(a){return!(z(a)||
+A(a))}));if(!m(c)||p(c)||!G(a,c))return null;o(a,c);return!f&&0<=c.size.top&&0<d.y&&d.y<c.size.top+e?(a=a.inInlineMode||0===b.scroll.y?x:s,new u([null,c,D,H,a])):f&&c.size.bottom<=b.pane.height&&d.y>c.size.bottom-e&&d.y<b.pane.height?(a=a.inInlineMode||c.size.bottom>b.pane.height-e&&c.size.bottom<b.pane.height?y:s,new u([c,null,Z,H,a])):null}function $(a){var c=a.mouse,d=a.view,b=a.triggerOffset,e=J(a);if(!e)return null;o(a,e);var b=Math.min(b,0|e.size.outerHeight/2),f=[],g,i;if(c.y>e.size.top-1&&
+c.y<e.size.top+b)i=!1;else if(c.y>e.size.bottom-b&&c.y<e.size.bottom+1)i=!0;else return null;if(p(e)||ja(a,e,i)||e.getParent().is(aa))return null;var h=r(a,e,!i);if(h){if(h&&h.type==CKEDITOR.NODE_TEXT)return null;if(m(h)){if(p(h)||!G(a,h)||h.getParent().is(aa))return null;f=[h,e][i?"reverse":"concat"]().concat([O,H])}}else e.equals(a.editable[i?"getLast":"getFirst"](a.isRelevant))?(C(a),i&&c.y>e.size.bottom-b&&c.y<d.pane.height&&e.size.bottom>d.pane.height-b&&e.size.bottom<d.pane.height?g=y:0<c.y&&
+c.y<e.size.top+b&&(g=x)):g=s,f=[null,e][i?"reverse":"concat"]().concat([i?Z:D,H,g,e.equals(a.editable[i?"getLast":"getFirst"](a.isRelevant))?i?y:x:s]);return 0 in f?new u(f):null}function P(a,c,d,b){for(var e=function(){var b=n.ie?c.$.currentStyle:a.win.$.getComputedStyle(c.$,"");return n.ie?function(a){return b[CKEDITOR.tools.cssStyleToDomStyle(a)]}:function(a){return b.getPropertyValue(a)}}(),f=c.getDocumentPosition(),g={},i={},h={},j={},l=t.length;l--;)g[t[l]]=parseInt(e("border-"+t[l]+"-width"),
+10)||0,h[t[l]]=parseInt(e("padding-"+t[l]),10)||0,i[t[l]]=parseInt(e("margin-"+t[l]),10)||0;(!d||b)&&I(a,b);j.top=f.y-(d?0:a.view.scroll.y);j.left=f.x-(d?0:a.view.scroll.x);j.outerWidth=c.$.offsetWidth;j.outerHeight=c.$.offsetHeight;j.height=j.outerHeight-(h.top+h.bottom+g.top+g.bottom);j.width=j.outerWidth-(h.left+h.right+g.left+g.right);j.bottom=j.top+j.outerHeight;j.right=j.left+j.outerWidth;a.inInlineMode&&(j.scroll={top:c.$.scrollTop,left:c.$.scrollLeft});return q({border:g,padding:h,margin:i,
+ignoreScroll:d},j,!0)}function o(a,c,d){if(!m(c))return c.size=null;if(c.size){if(c.size.ignoreScroll==d&&c.size.date>new Date-ba)return null}else c.size={};return q(c.size,P(a,c,d),{date:+new Date},!0)}function C(a,c){a.view.editable=P(a,a.editable,c,!0)}function I(a,c){a.view||(a.view={});var d=a.view;if(c||!(d&&d.date>new Date-ba)){var b=a.win,d=b.getScrollPosition(),b=b.getViewPaneSize();q(a.view,{scroll:{x:d.x,y:d.y,width:a.doc.$.documentElement.scrollWidth-b.width,height:a.doc.$.documentElement.scrollHeight-
+b.height},pane:{width:b.width,height:b.height,bottom:b.height+d.y},date:+new Date},!0)}}function la(a,c,d,b){for(var e=b,f=b,g=0,i=!1,h=!1,j=a.view.pane.height,l=a.mouse;l.y+g<j&&0<l.y-g;){i||(i=c(e,b));h||(h=c(f,b));!i&&0<l.y-g&&(e=d(a,{x:l.x,y:l.y-g}));!h&&l.y+g<j&&(f=d(a,{x:l.x,y:l.y+g}));if(i&&h)break;g+=2}return new u([e,f,null,null])}CKEDITOR.plugins.add("magicline",{init:function(a){var c=a.config,d=c.magicline_triggerOffset||30,b={editor:a,enterMode:c.enterMode,triggerOffset:d,holdDistance:0|
+d*(c.magicline_holdDistance||0.5),boxColor:c.magicline_color||"#ff0000",rtl:"rtl"==c.contentsLangDirection,tabuList:["data-cke-hidden-sel"].concat(c.magicline_tabuList||[]),triggers:c.magicline_everywhere?V:{table:1,hr:1,div:1,ul:1,ol:1,dl:1,form:1,blockquote:1}},e,f,g;b.isRelevant=function(a){return m(a)&&!v(b,a)&&!p(a)};a.on("contentDom",function(){var d=a.editable(),h=a.document,j=a.window;q(b,{editable:d,inInlineMode:d.isInline(),doc:h,win:j,hotNode:null},!0);b.boundary=b.inInlineMode?b.editable:
+b.doc.getDocumentElement();d.is(w.$inline)||(b.inInlineMode&&!X(d)&&d.setStyles({position:"relative",top:null,left:null}),ha.call(this,b),I(b),d.attachListener(a,"beforeUndoImage",function(){b.line.detach()}),d.attachListener(a,"beforeGetData",function(){b.line.wrap.getParent()&&(b.line.detach(),a.once("getData",function(){b.line.attach()},null,null,1E3))},null,null,0),d.attachListener(b.inInlineMode?h:h.getWindow().getFrame(),"mouseout",function(c){if("wysiwyg"==a.mode)if(b.inInlineMode){var d=c.data.$.clientX,
+c=c.data.$.clientY;I(b);C(b,!0);var e=b.view.editable,f=b.view.scroll;if(!(d>e.left-f.x&&d<e.right-f.x)||!(c>e.top-f.y&&c<e.bottom-f.y))clearTimeout(g),g=null,b.line.detach()}else clearTimeout(g),g=null,b.line.detach()}),d.attachListener(d,"keyup",function(){b.hiddenMode=0}),d.attachListener(d,"keydown",function(c){if("wysiwyg"==a.mode)switch(c.data.getKeystroke()){case 2228240:case 16:b.hiddenMode=1,b.line.detach()}}),d.attachListener(b.inInlineMode?d:h,"mousemove",function(c){f=!0;if(!("wysiwyg"!=
+a.mode||a.readOnly||g)){var d={x:c.data.$.clientX,y:c.data.$.clientY};g=setTimeout(function(){b.mouse=d;g=b.trigger=null;I(b);if(f&&!b.hiddenMode&&a.focusManager.hasFocus&&!b.line.mouseNear()&&(b.element=ca(b,!0)))(b.trigger=Y(b)||$(b)||da(b))&&!W(b,b.trigger.upper||b.trigger.lower)?b.line.attach().place():(b.trigger=null,b.line.detach()),f=!1},30)}}),d.attachListener(j,"scroll",function(){"wysiwyg"==a.mode&&(b.line.detach(),n.webkit&&(b.hiddenMode=1,clearTimeout(e),e=setTimeout(function(){b.mouseDown||
+(b.hiddenMode=0)},50)))}),d.attachListener(ea?h:j,"mousedown",function(){"wysiwyg"==a.mode&&(b.line.detach(),b.hiddenMode=1,b.mouseDown=1)}),d.attachListener(ea?h:j,"mouseup",function(){b.hiddenMode=0;b.mouseDown=0}),a.addCommand("accessPreviousSpace",U(b)),a.addCommand("accessNextSpace",U(b,!0)),a.setKeystroke([[c.magicline_keystrokePrevious,"accessPreviousSpace"],[c.magicline_keystrokeNext,"accessNextSpace"]]),a.on("loadSnapshot",function(){var c,d,e,f;for(f in{p:1,br:1,div:1}){c=a.document.getElementsByTag(f);
+for(e=c.count();e--;)if((d=c.getItem(e)).data("cke-magicline-hot")){b.hotNode=d;b.lastCmdDirection="true"===d.data("cke-magicline-dir")?!0:!1;return}}}),this.backdoor={accessFocusSpace:N,boxTrigger:u,isLine:v,getAscendantTrigger:J,getNonEmptyNeighbour:r,getSize:P,that:b,triggerEdge:$,triggerEditable:Y,triggerExpand:da})},this)}});var q=CKEDITOR.tools.extend,M=CKEDITOR.dom.element,B=M.createFromHtml,n=CKEDITOR.env,ea=CKEDITOR.env.ie&&9>CKEDITOR.env.version,w=CKEDITOR.dtd,F={},D=128,Z=64,O=32,H=16,
+fa=8,x=4,y=2,s=1,E=" ",aa=w.$listItem,ka=w.$tableContent,ia=q({},w.$nonEditable,w.$empty),V=w.$block,ba=100,L="width:0px;height:0px;padding:0px;margin:0px;display:block;z-index:9999;color:#fff;position:absolute;font-size: 0px;line-height:0px;",S=L+"border-color:transparent;display:block;border-style:solid;",R="<span>"+E+"</span>";F[CKEDITOR.ENTER_BR]="br";F[CKEDITOR.ENTER_P]="p";F[CKEDITOR.ENTER_DIV]="div";u.prototype={set:function(a,c,d){this.properties=a+c+(d||s);return this},is:function(a){return(this.properties&
+a)==a}};var ca=function(){function a(a,d){var b=a.$.elementFromPoint(d.x,d.y);return b&&b.nodeType?new CKEDITOR.dom.element(b):null}return function(c,d,b){if(!c.mouse)return null;var e=c.doc,f=c.line.wrap,b=b||c.mouse,g=a(e,b);d&&v(c,g)&&(f.hide(),g=a(e,b),f.show());return!g||!(g.type==CKEDITOR.NODE_ELEMENT&&g.$)||n.ie&&9>n.version&&!c.boundary.equals(g)&&!c.boundary.contains(g)?null:g}}(),z=CKEDITOR.dom.walker.whitespaces(),A=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_COMMENT),da=function(){function a(a){var b=
+a.element,e,f,g;if(!m(b)||b.contains(a.editable)||b.isReadOnly())return null;g=la(a,function(a,b){return!b.equals(a)},function(a,b){return ca(a,!0,b)},b);e=g.upper;f=g.lower;if(Q(a,e,f))return g.set(O,fa);if(e&&b.contains(e))for(;!e.getParent().equals(b);)e=e.getParent();else e=b.getFirst(function(b){return c(a,b)});if(f&&b.contains(f))for(;!f.getParent().equals(b);)f=f.getParent();else f=b.getLast(function(b){return c(a,b)});if(!e||!f)return null;o(a,e);o(a,f);if(!(a.mouse.y>e.size.top&&a.mouse.y<
+f.size.bottom))return null;for(var b=Number.MAX_VALUE,i,h,j,l;f&&!f.equals(e)&&(h=e.getNext(a.isRelevant));)i=Math.abs(ga(a,e,h)-a.mouse.y),i<b&&(b=i,j=e,l=h),e=h,o(a,e);if(!j||!l||!(a.mouse.y>j.size.top&&a.mouse.y<l.size.bottom))return null;g.upper=j;g.lower=l;return g.set(O,fa)}function c(a,b){return!(b&&b.type==CKEDITOR.NODE_TEXT||A(b)||p(b)||v(a,b)||b.type==CKEDITOR.NODE_ELEMENT&&b.$&&b.is("br"))}return function(c){var b=a(c),e;if(e=b){e=b.upper;var f=b.lower;e=!e||!f||p(f)||p(e)||f.equals(e)||
+e.equals(f)||f.contains(e)||e.contains(f)?!1:G(c,e)&&G(c,f)&&Q(c,e,f)?!0:!1}return e?b:null}}(),t=["top","left","right","bottom"]})();CKEDITOR.config.magicline_keystrokePrevious=CKEDITOR.CTRL+CKEDITOR.SHIFT+51;CKEDITOR.config.magicline_keystrokeNext=CKEDITOR.CTRL+CKEDITOR.SHIFT+52;(function(){function l(a){if(!a||a.type!=CKEDITOR.NODE_ELEMENT||"form"!=a.getName())return[];for(var e=[],f=["style","className"],b=0;b<f.length;b++){var d=a.$.elements.namedItem(f[b]);d&&(d=new CKEDITOR.dom.element(d),e.push([d,d.nextSibling]),d.remove())}return e}function o(a,e){if(a&&!(a.type!=CKEDITOR.NODE_ELEMENT||"form"!=a.getName())&&0<e.length)for(var f=e.length-1;0<=f;f--){var b=e[f][0],d=e[f][1];d?b.insertBefore(d):b.appendTo(a)}}function n(a,e){var f=l(a),b={},d=a.$;e||(b["class"]=d.className||
+"",d.className="");b.inline=d.style.cssText||"";e||(d.style.cssText="position: static; overflow: visible");o(f);return b}function p(a,e){var f=l(a),b=a.$;"class"in e&&(b.className=e["class"]);"inline"in e&&(b.style.cssText=e.inline);o(f)}function q(a){if(!a.editable().isInline()){var e=CKEDITOR.instances,f;for(f in e){var b=e[f];"wysiwyg"==b.mode&&!b.readOnly&&(b=b.document.getBody(),b.setAttribute("contentEditable",!1),b.setAttribute("contentEditable",!0))}a.editable().hasFocus&&(a.toolbox.focus(),
+a.focus())}}CKEDITOR.plugins.add("maximize",{init:function(a){function e(){var b=d.getViewPaneSize();a.resize(b.width,b.height,null,!0)}if(a.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var f=a.lang,b=CKEDITOR.document,d=b.getWindow(),j,k,m,l=CKEDITOR.TRISTATE_OFF;a.addCommand("maximize",{modes:{wysiwyg:!CKEDITOR.env.iOS,source:!CKEDITOR.env.iOS},readOnly:1,editorFocus:!1,exec:function(){var h=a.container.getFirst(function(a){return a.type==CKEDITOR.NODE_ELEMENT&&a.hasClass("cke_inner")}),g=a.ui.space("contents");
+if("wysiwyg"==a.mode){var c=a.getSelection();j=c&&c.getRanges();k=d.getScrollPosition()}else{var i=a.editable().$;j=!CKEDITOR.env.ie&&[i.selectionStart,i.selectionEnd];k=[i.scrollLeft,i.scrollTop]}if(this.state==CKEDITOR.TRISTATE_OFF){d.on("resize",e);m=d.getScrollPosition();for(c=a.container;c=c.getParent();)c.setCustomData("maximize_saved_styles",n(c)),c.setStyle("z-index",a.config.baseFloatZIndex-5);g.setCustomData("maximize_saved_styles",n(g,!0));h.setCustomData("maximize_saved_styles",n(h,!0));
+g={overflow:CKEDITOR.env.webkit?"":"hidden",width:0,height:0};b.getDocumentElement().setStyles(g);!CKEDITOR.env.gecko&&b.getDocumentElement().setStyle("position","fixed");(!CKEDITOR.env.gecko||!CKEDITOR.env.quirks)&&b.getBody().setStyles(g);CKEDITOR.env.ie?setTimeout(function(){d.$.scrollTo(0,0)},0):d.$.scrollTo(0,0);h.setStyle("position",CKEDITOR.env.gecko&&CKEDITOR.env.quirks?"fixed":"absolute");h.$.offsetLeft;h.setStyles({"z-index":a.config.baseFloatZIndex-5,left:"0px",top:"0px"});h.addClass("cke_maximized");
+e();g=h.getDocumentPosition();h.setStyles({left:-1*g.x+"px",top:-1*g.y+"px"});CKEDITOR.env.gecko&&q(a)}else if(this.state==CKEDITOR.TRISTATE_ON){d.removeListener("resize",e);g=[g,h];for(c=0;c<g.length;c++)p(g[c],g[c].getCustomData("maximize_saved_styles")),g[c].removeCustomData("maximize_saved_styles");for(c=a.container;c=c.getParent();)p(c,c.getCustomData("maximize_saved_styles")),c.removeCustomData("maximize_saved_styles");CKEDITOR.env.ie?setTimeout(function(){d.$.scrollTo(m.x,m.y)},0):d.$.scrollTo(m.x,
+m.y);h.removeClass("cke_maximized");CKEDITOR.env.webkit&&(h.setStyle("display","inline"),setTimeout(function(){h.setStyle("display","block")},0));a.fire("resize")}this.toggleState();if(c=this.uiItems[0])g=this.state==CKEDITOR.TRISTATE_OFF?f.maximize.maximize:f.maximize.minimize,c=CKEDITOR.document.getById(c._.id),c.getChild(1).setHtml(g),c.setAttribute("title",g),c.setAttribute("href",'javascript:void("'+g+'");');"wysiwyg"==a.mode?j?(CKEDITOR.env.gecko&&q(a),a.getSelection().selectRanges(j),(i=a.getSelection().getStartElement())&&
+i.scrollIntoView(!0)):d.$.scrollTo(k.x,k.y):(j&&(i.selectionStart=j[0],i.selectionEnd=j[1]),i.scrollLeft=k[0],i.scrollTop=k[1]);j=k=null;l=this.state;a.fire("maximize",this.state)},canUndo:!1});a.ui.addButton&&a.ui.addButton("Maximize",{label:f.maximize.maximize,command:"maximize",toolbar:"tools,10"});a.on("mode",function(){var b=a.getCommand("maximize");b.setState(b.state==CKEDITOR.TRISTATE_DISABLED?CKEDITOR.TRISTATE_DISABLED:l)},null,null,100)}}})})();(function(){var c={canUndo:!1,async:!0,exec:function(a){a.getClipboardData({title:a.lang.pastetext.title},function(b){b&&a.fire("paste",{type:"text",dataValue:b.dataValue});a.fire("afterCommandExec",{name:"pastetext",command:c,returnValue:!!b})})}};CKEDITOR.plugins.add("pastetext",{requires:"clipboard",init:function(a){a.addCommand("pastetext",c);a.ui.addButton&&a.ui.addButton("PasteText",{label:a.lang.pastetext.button,command:"pastetext",toolbar:"clipboard,40"});if(a.config.forcePasteAsPlainText)a.on("beforePaste",
+function(a){"html"!=a.data.type&&(a.data.type="text")});a.on("pasteState",function(b){a.getCommand("pastetext").setState(b.data)})}})})();(function(){function h(a,d,f){var b=CKEDITOR.cleanWord;b?f():(a=CKEDITOR.getUrl(a.config.pasteFromWordCleanupFile||d+"filter/default.js"),CKEDITOR.scriptLoader.load(a,f,null,!0));return!b}function i(a){a.data.type="html"}CKEDITOR.plugins.add("pastefromword",{requires:"clipboard",init:function(a){var d=0,f=this.path;a.addCommand("pastefromword",{canUndo:!1,async:!0,exec:function(a){var e=this;d=1;a.once("beforePaste",i);a.getClipboardData({title:a.lang.pastefromword.title},function(c){c&&a.fire("paste",
+{type:"html",dataValue:c.dataValue});a.fire("afterCommandExec",{name:"pastefromword",command:e,returnValue:!!c})})}});a.ui.addButton&&a.ui.addButton("PasteFromWord",{label:a.lang.pastefromword.toolbar,command:"pastefromword",toolbar:"clipboard,50"});a.on("pasteState",function(b){a.getCommand("pastefromword").setState(b.data)});a.on("paste",function(b){var e=b.data,c=e.dataValue;if(c&&(d||/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(c))){var g=h(a,f,function(){if(g)a.fire("paste",e);
+else if(!a.config.pasteFromWordPromptCleanup||d||confirm(a.lang.pastefromword.confirmCleanup))e.dataValue=CKEDITOR.cleanWord(c,a);d=0});g&&b.cancel()}},null,null,3)}})})();CKEDITOR.plugins.add("removeformat",{init:function(a){a.addCommand("removeFormat",CKEDITOR.plugins.removeformat.commands.removeformat);a.ui.addButton&&a.ui.addButton("RemoveFormat",{label:a.lang.removeformat.toolbar,command:"removeFormat",toolbar:"cleanup,10"})}});
+CKEDITOR.plugins.removeformat={commands:{removeformat:{exec:function(a){for(var h=a._.removeFormatRegex||(a._.removeFormatRegex=RegExp("^(?:"+a.config.removeFormatTags.replace(/,/g,"|")+")$","i")),e=a._.removeAttributes||(a._.removeAttributes=a.config.removeFormatAttributes.split(",")),f=CKEDITOR.plugins.removeformat.filter,k=a.getSelection().getRanges(),l=k.createIterator(),m=function(a){return a.type==CKEDITOR.NODE_ELEMENT},c;c=l.getNextRange();){c.collapsed||c.enlarge(CKEDITOR.ENLARGE_ELEMENT);
+var j=c.createBookmark(),b=j.startNode,d=j.endNode,i=function(b){for(var c=a.elementPath(b),e=c.elements,d=1,g;(g=e[d])&&!g.equals(c.block)&&!g.equals(c.blockLimit);d++)h.test(g.getName())&&f(a,g)&&b.breakParent(g)};i(b);if(d){i(d);for(b=b.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT);b&&!b.equals(d);)if(b.isReadOnly()){if(b.getPosition(d)&CKEDITOR.POSITION_CONTAINS)break;b=b.getNext(m)}else i=b.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT),!("img"==b.getName()&&b.data("cke-realelement"))&&f(a,b)&&(h.test(b.getName())?
+b.remove(1):(b.removeAttributes(e),a.fire("removeFormatCleanup",b))),b=i}c.moveToBookmark(j)}a.forceNextSelectionCheck();a.getSelection().selectRanges(k)}}},filter:function(a,h){for(var e=a._.removeFormatFilters||[],f=0;f<e.length;f++)if(!1===e[f](h))return!1;return!0}};CKEDITOR.editor.prototype.addRemoveFormatFilter=function(a){this._.removeFormatFilters||(this._.removeFormatFilters=[]);this._.removeFormatFilters.push(a)};CKEDITOR.config.removeFormatTags="b,big,cite,code,del,dfn,em,font,i,ins,kbd,q,s,samp,small,span,strike,strong,sub,sup,tt,u,var";
+CKEDITOR.config.removeFormatAttributes="class,style,lang,width,height,align,hspace,valign";(function(){var f={preserveState:!0,editorFocus:!1,readOnly:1,exec:function(a){this.toggleState();this.refresh(a)},refresh:function(a){if(a.document){var b=this.state==CKEDITOR.TRISTATE_ON?"attachClass":"removeClass";a.editable()[b]("cke_show_borders")}}};CKEDITOR.plugins.add("showborders",{modes:{wysiwyg:1},onLoad:function(){var a;a=(CKEDITOR.env.ie6Compat?[".%1 table.%2,",".%1 table.%2 td, .%1 table.%2 th","{","border : #d3d3d3 1px dotted","}"]:".%1 table.%2,;.%1 table.%2 > tr > td, .%1 table.%2 > tr > th,;.%1 table.%2 > tbody > tr > td, .%1 table.%2 > tbody > tr > th,;.%1 table.%2 > thead > tr > td, .%1 table.%2 > thead > tr > th,;.%1 table.%2 > tfoot > tr > td, .%1 table.%2 > tfoot > tr > th;{;border : #d3d3d3 1px dotted;}".split(";")).join("").replace(/%2/g,
+"cke_show_border").replace(/%1/g,"cke_show_borders ");CKEDITOR.addCss(a)},init:function(a){var b=a.addCommand("showborders",f);b.canUndo=!1;!1!==a.config.startupShowBorders&&b.setState(CKEDITOR.TRISTATE_ON);a.on("mode",function(){b.state!=CKEDITOR.TRISTATE_DISABLED&&b.refresh(a)},null,null,100);a.on("contentDom",function(){b.state!=CKEDITOR.TRISTATE_DISABLED&&b.refresh(a)});a.on("removeFormatCleanup",function(d){d=d.data;a.getCommand("showborders").state==CKEDITOR.TRISTATE_ON&&(d.is("table")&&(!d.hasAttribute("border")||
+0>=parseInt(d.getAttribute("border"),10)))&&d.addClass("cke_show_border")})},afterInit:function(a){var b=a.dataProcessor,a=b&&b.dataFilter,b=b&&b.htmlFilter;a&&a.addRules({elements:{table:function(a){var a=a.attributes,b=a["class"],c=parseInt(a.border,10);if((!c||0>=c)&&(!b||-1==b.indexOf("cke_show_border")))a["class"]=(b||"")+" cke_show_border"}}});b&&b.addRules({elements:{table:function(a){var a=a.attributes,b=a["class"];b&&(a["class"]=b.replace("cke_show_border","").replace(/\s{2}/," ").replace(/^\s+|\s+$/,
+""))}}})}});CKEDITOR.on("dialogDefinition",function(a){var b=a.data.name;if("table"==b||"tableProperties"==b)if(a=a.data.definition,b=a.getContents("info").get("txtBorder"),b.commit=CKEDITOR.tools.override(b.commit,function(a){return function(b,c){a.apply(this,arguments);var e=parseInt(this.getValue(),10);c[!e||0>=e?"addClass":"removeClass"]("cke_show_border")}}),a=(a=a.getContents("advanced"))&&a.get("advCSSClasses"))a.setup=CKEDITOR.tools.override(a.setup,function(a){return function(){a.apply(this,
+arguments);this.setValue(this.getValue().replace(/cke_show_border/,""))}}),a.commit=CKEDITOR.tools.override(a.commit,function(a){return function(b,c){a.apply(this,arguments);parseInt(c.getAttribute("border"),10)||c.addClass("cke_show_border")}})})})();(function(){CKEDITOR.plugins.add("sourcearea",{init:function(a){function d(){var a=e&&this.equals(CKEDITOR.document.getActive());this.hide();this.setStyle("height",this.getParent().$.clientHeight+"px");this.setStyle("width",this.getParent().$.clientWidth+"px");this.show();a&&this.focus()}if(a.elementMode!=CKEDITOR.ELEMENT_MODE_INLINE){var f=CKEDITOR.plugins.sourcearea;a.addMode("source",function(e){var b=a.ui.space("contents").getDocument().createElement("textarea");b.setStyles(CKEDITOR.tools.extend({width:CKEDITOR.env.ie7Compat?
+"99%":"100%",height:"100%",resize:"none",outline:"none","text-align":"left"},CKEDITOR.tools.cssVendorPrefix("tab-size",a.config.sourceAreaTabSize||4)));b.setAttribute("dir","ltr");b.addClass("cke_source cke_reset cke_enable_context_menu");a.ui.space("contents").append(b);b=a.editable(new c(a,b));b.setData(a.getData(1));CKEDITOR.env.ie&&(b.attachListener(a,"resize",d,b),b.attachListener(CKEDITOR.document.getWindow(),"resize",d,b),CKEDITOR.tools.setTimeout(d,0,b));a.fire("ariaWidget",this);e()});a.addCommand("source",
+f.commands.source);a.ui.addButton&&a.ui.addButton("Source",{label:a.lang.sourcearea.toolbar,command:"source",toolbar:"mode,10"});a.on("mode",function(){a.getCommand("source").setState("source"==a.mode?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF)});var e=CKEDITOR.env.ie&&9==CKEDITOR.env.version}}});var c=CKEDITOR.tools.createClass({base:CKEDITOR.editable,proto:{setData:function(a){this.setValue(a);this.status="ready";this.editor.fire("dataReady")},getData:function(){return this.getValue()},insertHtml:function(){},
+insertElement:function(){},insertText:function(){},setReadOnly:function(a){this[(a?"set":"remove")+"Attribute"]("readOnly","readonly")},detach:function(){c.baseProto.detach.call(this);this.clearCustomData();this.remove()}}})})();CKEDITOR.plugins.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:!1,readOnly:1,exec:function(c){"wysiwyg"==c.mode&&c.fire("saveSnapshot");c.getCommand("source").setState(CKEDITOR.TRISTATE_DISABLED);c.setMode("source"==c.mode?"wysiwyg":"source")},canUndo:!1}}};CKEDITOR.plugins.add("specialchar",{availableLangs:{af:1,ar:1,bg:1,ca:1,cs:1,cy:1,da:1,de:1,el:1,en:1,"en-gb":1,eo:1,es:1,et:1,fa:1,fi:1,fr:1,"fr-ca":1,gl:1,he:1,hr:1,hu:1,id:1,it:1,ja:1,km:1,ku:1,lt:1,lv:1,nb:1,nl:1,no:1,pl:1,pt:1,"pt-br":1,ru:1,si:1,sk:1,sl:1,sq:1,sv:1,th:1,tr:1,tt:1,ug:1,uk:1,vi:1,zh:1,"zh-cn":1},requires:"dialog",init:function(a){var c=this;CKEDITOR.dialog.add("specialchar",this.path+"dialogs/specialchar.js");a.addCommand("specialchar",{exec:function(){var b=a.langCode,b=c.availableLangs[b]?
+b:c.availableLangs[b.replace(/-.*/,"")]?b.replace(/-.*/,""):"en";CKEDITOR.scriptLoader.load(CKEDITOR.getUrl(c.path+"dialogs/lang/"+b+".js"),function(){CKEDITOR.tools.extend(a.lang.specialchar,c.langEntries[b]);a.openDialog("specialchar")})},modes:{wysiwyg:1},canUndo:!1});a.ui.addButton&&a.ui.addButton("SpecialChar",{label:a.lang.specialchar.toolbar,command:"specialchar",toolbar:"insert,50"})}});CKEDITOR.config.specialChars="! &quot; # $ % &amp; ' ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; &lt; = &gt; ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ &euro; &lsquo; &rsquo; &ldquo; &rdquo; &ndash; &mdash; &iexcl; &cent; &pound; &curren; &yen; &brvbar; &sect; &uml; &copy; &ordf; &laquo; &not; &reg; &macr; &deg; &sup2; &sup3; &acute; &micro; &para; &middot; &cedil; &sup1; &ordm; &raquo; &frac14; &frac12; &frac34; &iquest; &Agrave; &Aacute; &Acirc; &Atilde; &Auml; &Aring; &AElig; &Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Igrave; &Iacute; &Icirc; &Iuml; &ETH; &Ntilde; &Ograve; &Oacute; &Ocirc; &Otilde; &Ouml; &times; &Oslash; &Ugrave; &Uacute; &Ucirc; &Uuml; &Yacute; &THORN; &szlig; &agrave; &aacute; &acirc; &atilde; &auml; &aring; &aelig; &ccedil; &egrave; &eacute; &ecirc; &euml; &igrave; &iacute; &icirc; &iuml; &eth; &ntilde; &ograve; &oacute; &ocirc; &otilde; &ouml; &divide; &oslash; &ugrave; &uacute; &ucirc; &uuml; &yacute; &thorn; &yuml; &OElig; &oelig; &#372; &#374 &#373 &#375; &sbquo; &#8219; &bdquo; &hellip; &trade; &#9658; &bull; &rarr; &rArr; &hArr; &diams; &asymp;".split(" ");CKEDITOR.plugins.add("menubutton",{requires:"button,menu",onLoad:function(){var d=function(c){var a=this._,b=a.menu;a.state!==CKEDITOR.TRISTATE_DISABLED&&(a.on&&b?b.hide():(a.previousState=a.state,b||(b=a.menu=new CKEDITOR.menu(c,{panel:{className:"cke_menu_panel",attributes:{"aria-label":c.lang.common.options}}}),b.onHide=CKEDITOR.tools.bind(function(){var b=this.command?c.getCommand(this.command).modes:this.modes;this.setState(!b||b[c.mode]?a.previousState:CKEDITOR.TRISTATE_DISABLED);a.on=0},this),
+this.onMenu&&b.addListener(this.onMenu)),this.setState(CKEDITOR.TRISTATE_ON),a.on=1,setTimeout(function(){b.show(CKEDITOR.document.getById(a.id),4)},0)))};CKEDITOR.ui.menuButton=CKEDITOR.tools.createClass({base:CKEDITOR.ui.button,$:function(c){delete c.panel;this.base(c);this.hasArrow=!0;this.click=d},statics:{handler:{create:function(c){return new CKEDITOR.ui.menuButton(c)}}}})},beforeInit:function(d){d.ui.addHandler(CKEDITOR.UI_MENUBUTTON,CKEDITOR.ui.menuButton.handler)}});
+CKEDITOR.UI_MENUBUTTON="menubutton";CKEDITOR.plugins.add("scayt",{requires:"menubutton,dialog",tabToOpen:null,dialogName:"scaytDialog",init:function(a){var c=this,d=CKEDITOR.plugins.scayt;this.bindEvents(a);this.parseConfig(a);this.addRule(a);CKEDITOR.dialog.add(this.dialogName,CKEDITOR.getUrl(this.path+"dialogs/options.js"));this.addMenuItems(a);var b=a.lang.scayt,e=CKEDITOR.env;a.ui.add("Scayt",CKEDITOR.UI_MENUBUTTON,{label:b.text_title,title:a.plugins.wsc?a.lang.wsc.title:b.text_title,modes:{wysiwyg:!(e.ie&&(8>e.version||e.quirks))},
+toolbar:"spellchecker,20",refresh:function(){var b=a.ui.instances.Scayt.getState();a.scayt&&(b=d.state[a.name]?CKEDITOR.TRISTATE_ON:CKEDITOR.TRISTATE_OFF);a.fire("scaytButtonState",b)},onRender:function(){var d=this;a.on("scaytButtonState",function(a){void 0!==typeof a.data&&d.setState(a.data)})},onMenu:function(){var b=a.scayt;a.getMenuItem("scaytToggle").label=a.lang.scayt[b&&d.state[a.name]?"btn_disable":"btn_enable"];b={scaytToggle:CKEDITOR.TRISTATE_OFF,scaytOptions:b?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,
+scaytLangs:b?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,scaytDict:b?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,scaytAbout:b?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,WSC:a.plugins.wsc?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED};a.config.scayt_uiTabs[0]||delete b.scaytOptions;a.config.scayt_uiTabs[1]||delete b.scaytLangs;a.config.scayt_uiTabs[2]||delete b.scaytDict;return b}});a.contextMenu&&a.addMenuItems&&(a.contextMenu.addListener(function(){var d=a.scayt,b;if(d){var e=
+d.getSelectionNode();if(e=e?e.getAttribute(d.getNodeAttribute()):e)b=c.menuGenerator(a,e,c),d.showBanner("."+a.contextMenu._.definition.panel.className.split(" ").join(" ."))}return b}),a.contextMenu._.onHide=CKEDITOR.tools.override(a.contextMenu._.onHide,function(d){return function(){var b=a.scayt;b&&b.hideBanner();return d.apply(this)}}))},addMenuItems:function(a){var c=this,d=CKEDITOR.plugins.scayt;a.addMenuGroup("scaytButton");var b=a.config.scayt_contextMenuItemsOrder.split("|");if(b&&b.length)for(var e=
+0;e<b.length;e++)a.addMenuGroup("scayt_"+b[e],e-10);b={scaytToggle:{label:a.lang.scayt.btn_enable,group:"scaytButton",onClick:function(){var b=a.scayt;d.state[a.name]=!d.state[a.name];!0===d.state[a.name]?b||d.createScayt(a):b&&d.destroy(a)}},scaytAbout:{label:a.lang.scayt.btn_about,group:"scaytButton",onClick:function(){a.scayt.tabToOpen="about";a.lockSelection();a.openDialog(c.dialogName)}},scaytOptions:{label:a.lang.scayt.btn_options,group:"scaytButton",onClick:function(){a.scayt.tabToOpen="options";
+a.lockSelection();a.openDialog(c.dialogName)}},scaytLangs:{label:a.lang.scayt.btn_langs,group:"scaytButton",onClick:function(){a.scayt.tabToOpen="langs";a.lockSelection();a.openDialog(c.dialogName)}},scaytDict:{label:a.lang.scayt.btn_dictionaries,group:"scaytButton",onClick:function(){a.scayt.tabToOpen="dictionaries";a.lockSelection();a.openDialog(c.dialogName)}}};a.plugins.wsc&&(b.WSC={label:a.lang.wsc.toolbar,group:"scaytButton",onClick:function(){var d=CKEDITOR.plugins.scayt,b=a.scayt,c=a.elementMode==
+CKEDITOR.ELEMENT_MODE_INLINE?a.container.getText():a.document.getBody().getText();(c=c.replace(/\s/g,""))?(b&&(d.state[a.name]&&b.setMarkupPaused)&&b.setMarkupPaused(!0),a.lockSelection(),a.execCommand("checkspell")):alert("Nothing to check!")}});a.addMenuItems(b)},bindEvents:function(a){function c(){var d=a.scayt;d&&(d.removeMarkupInSelectionNode(),d.fire("startSpellCheck"))}var d=CKEDITOR.plugins.scayt,b=a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE;CKEDITOR.on("dialogDefinition",function(a){if("scaytDialog"===
+a.data.name)a.data.definition.dialog.on("cancel",function(){return!1},this,null,-1)});var e=function(){a.scayt&&d.destroy(a)},g=function(){d.state[a.name]&&!a.readOnly&&d.createScayt(a)},f=function(){var d=a.editable();d.attachListener(d,"focus",function(){var d=CKEDITOR.plugins.scayt&&CKEDITOR.plugins.scayt.state[a.name]&&a.scayt,c,e;if((b||d)&&a._.savedSelection)for(var d=a._.savedSelection.getSelectedElement(),d=!d&&a._.savedSelection.getRanges(),i=0;i<d.length;i++)e=d[i],c=e.startContainer.getText().length,
+(c<e.startOffset||c<e.endOffset)&&a.unlockSelection(!1)},this,null,-10)},h=function(){b?(a.on("blur",e),a.on("focus",g),a.focusManager.hasFocus&&g()):g();f()};a.on("contentDom",h);a.on("beforeCommandExec",function(b){var c;if(b.data.name in d.options.disablingCommandExec&&"wysiwyg"==a.mode){if(c=a.scayt)d.destroy(a),a.fire("scaytButtonState",CKEDITOR.TRISTATE_DISABLED)}else if("bold"===b.data.name||"italic"===b.data.name||"underline"===b.data.name||"strike"===b.data.name||"subscript"===b.data.name||
+"superscript"===b.data.name)if(c=a.scayt)c.removeMarkupInSelectionNode(),setTimeout(function(){c.fire("startSpellCheck")},0)});a.on("beforeSetMode",function(b){if("source"==b.data){if(b=a.scayt)d.destroy(a),a.fire("scaytButtonState",CKEDITOR.TRISTATE_DISABLED);a.document&&(a.document.getBody().removeAttribute("_jquid"),a.document.getBody().removeAttribute("dir"))}});a.on("afterCommandExec",function(d){var b;if("wysiwyg"==a.mode&&("undo"==d.data.name||"redo"==d.data.name))(b=a.scayt)&&setTimeout(function(){b.fire("startSpellCheck")},
+250)});a.on("readOnly",function(b){var c;b&&(c=a.scayt,!0===b.editor.readOnly?c&&c.fire("removeMarkupInDocument",{}):c?c.fire("startSpellCheck"):"wysiwyg"==b.editor.mode&&!0===d.state[b.editor.name]&&(d.createScayt(a),b.editor.fire("scaytButtonState",CKEDITOR.TRISTATE_ON)))});a.on("beforeDestroy",e);a.on("setData",function(){e();a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE&&h()},this,null,50);a.on("insertElement",function(){CKEDITOR.env.ie?setTimeout(function(){c()},50):c()},this,null,50);a.on("insertHtml",
+function(){c()},this,null,50);a.on("insertText",function(){c()},this,null,50);a.on("scaytDialogShown",function(d){d.data.selectPage(a.scayt.tabToOpen)})},parseConfig:function(a){var c=CKEDITOR.plugins.scayt;c.replaceOldOptionsNames(a.config);"boolean"!==typeof a.config.scayt_autoStartup&&(a.config.scayt_autoStartup=!1);c.state[a.name]=a.config.scayt_autoStartup;a.config.scayt_contextCommands||(a.config.scayt_contextCommands="ignore|ignoreall|add");a.config.scayt_contextMenuItemsOrder||(a.config.scayt_contextMenuItemsOrder=
+"suggest|moresuggest|control");a.config.scayt_sLang||(a.config.scayt_sLang="en_US");if(void 0===a.config.scayt_maxSuggestions||"number"!=typeof a.config.scayt_maxSuggestions||0>a.config.scayt_maxSuggestions)a.config.scayt_maxSuggestions=5;if(void 0===a.config.scayt_customDictionaryIds||"string"!==typeof a.config.scayt_customDictionaryIds)a.config.scayt_customDictionaryIds="";if(void 0===a.config.scayt_userDictionaryName||"string"!==typeof a.config.scayt_userDictionaryName)a.config.scayt_userDictionaryName=
+null;if("string"===typeof a.config.scayt_uiTabs&&3===a.config.scayt_uiTabs.split(",").length){var d=[],b=[];a.config.scayt_uiTabs=a.config.scayt_uiTabs.split(",");CKEDITOR.tools.search(a.config.scayt_uiTabs,function(a){if(Number(a)===1||Number(a)===0){b.push(true);d.push(Number(a))}else b.push(false)});a.config.scayt_uiTabs=null===CKEDITOR.tools.search(b,!1)?d:[1,1,1]}else a.config.scayt_uiTabs=[1,1,1];"string"!=typeof a.config.scayt_serviceProtocol&&(a.config.scayt_serviceProtocol=null);"string"!=
+typeof a.config.scayt_serviceHost&&(a.config.scayt_serviceHost=null);"string"!=typeof a.config.scayt_servicePort&&(a.config.scayt_servicePort=null);"string"!=typeof a.config.scayt_servicePath&&(a.config.scayt_servicePath=null);a.config.scayt_moreSuggestions||(a.config.scayt_moreSuggestions="on");"string"!==typeof a.config.scayt_customerId&&(a.config.scayt_customerId="1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2");"string"!==typeof a.config.scayt_srcUrl&&(c=document.location.protocol,
+c=-1!=c.search(/https?:/)?c:"http:",a.config.scayt_srcUrl=c+"//svc.webspellchecker.net/spellcheck31/lf/scayt3/ckscayt/ckscayt.js");"boolean"!==typeof CKEDITOR.config.scayt_handleCheckDirty&&(CKEDITOR.config.scayt_handleCheckDirty=!0);"boolean"!==typeof CKEDITOR.config.scayt_handleUndoRedo&&(CKEDITOR.config.scayt_handleUndoRedo=!0);if(a.config.scayt_disableOptionsStorage){var c=CKEDITOR.tools.isArray(a.config.scayt_disableOptionsStorage)?a.config.scayt_disableOptionsStorage:"string"===typeof a.config.scayt_disableOptionsStorage?
+[a.config.scayt_disableOptionsStorage]:void 0,e="all options lang ignore-all-caps-words ignore-domain-names ignore-words-with-mixed-cases ignore-words-with-numbers".split(" "),g=["lang","ignore-all-caps-words","ignore-domain-names","ignore-words-with-mixed-cases","ignore-words-with-numbers"],f=CKEDITOR.tools.search,h=CKEDITOR.tools.indexOf;a.config.scayt_disableOptionsStorage=function(a){for(var d=[],b=0;b<a.length;b++){var c=a[b],j=!!f(a,"options");if(!f(e,c)||j&&f(g,function(a){if(a==="lang")return false}))return;
+f(g,c)&&g.splice(h(g,c),1);if(c==="all"||j&&f(a,"lang"))return[];c==="options"&&(g=["lang"])}return d=d.concat(g)}(c)}},addRule:function(a){var c=a.dataProcessor,d=c&&c.htmlFilter,b=a._.elementsPath&&a._.elementsPath.filters,c=c&&c.dataFilter,e=a.addRemoveFormatFilter,g=function(d){var b=CKEDITOR.plugins.scayt;if(a.scayt&&d.hasAttribute(b.options.data_attribute_name))return!1},f=function(d){var b=CKEDITOR.plugins.scayt,c=!0;a.scayt&&d.hasAttribute(b.options.data_attribute_name)&&(c=!1);return c};
+b&&b.push(g);c&&c.addRules({elements:{span:function(d){var b=CKEDITOR.plugins.scayt;b&&(b.state[a.name]&&d.classes&&CKEDITOR.tools.search(d.classes,b.options.misspelled_word_class))&&(d.classes&&d.parent.type===CKEDITOR.NODE_DOCUMENT_FRAGMENT?(delete d.attributes.style,delete d.name):delete d.classes[CKEDITOR.tools.indexOf(d.classes,b.options.misspelled_word_class)]);return d}}});d&&d.addRules({elements:{span:function(d){var b=CKEDITOR.plugins.scayt;b&&(b.state[a.name]&&d.hasClass(b.options.misspelled_word_class)&&
+d.attributes[b.options.data_attribute_name])&&(d.removeClass(b.options.misspelled_word_class),delete d.attributes[b.options.data_attribute_name],delete d.name);return d}}});e&&e.call(a,f)},scaytMenuDefinition:function(a){var c=this,a=a.scayt;return{scayt_ignore:{label:a.getLocal("btn_ignore"),group:"scayt_control",order:1,exec:function(a){a.scayt.ignoreWord()}},scayt_ignoreall:{label:a.getLocal("btn_ignoreAll"),group:"scayt_control",order:2,exec:function(a){a.scayt.ignoreAllWords()}},scayt_add:{label:a.getLocal("btn_addWord"),
+group:"scayt_control",order:3,exec:function(a){var b=a.scayt;setTimeout(function(){b.addWordToUserDictionary()},10)}},option:{label:a.getLocal("btn_options"),group:"scayt_control",order:4,exec:function(a){a.scayt.tabToOpen="options";a.lockSelection();a.openDialog(c.dialogName)},verification:function(a){return 1==a.config.scayt_uiTabs[0]?!0:!1}},language:{label:a.getLocal("btn_langs"),group:"scayt_control",order:5,exec:function(a){a.scayt.tabToOpen="langs";a.lockSelection();a.openDialog(c.dialogName)},
+verification:function(a){return 1==a.config.scayt_uiTabs[1]?!0:!1}},dictionary:{label:a.getLocal("btn_dictionaries"),group:"scayt_control",order:6,exec:function(a){a.scayt.tabToOpen="dictionaries";a.lockSelection();a.openDialog(c.dialogName)},verification:function(a){return 1==a.config.scayt_uiTabs[2]?!0:!1}},about:{label:a.getLocal("btn_about"),group:"scayt_control",order:7,exec:function(a){a.scayt.tabToOpen="about";a.lockSelection();a.openDialog(c.dialogName)}}}},buildSuggestionMenuItems:function(a,
+c){var d={},b={},e=a.scayt;if(0<c.length&&"no_any_suggestions"!==c[0])for(var g=0;g<c.length;g++){var f="scayt_suggest_"+CKEDITOR.plugins.scayt.suggestions[g].replace(" ","_");a.addCommand(f,this.createCommand(CKEDITOR.plugins.scayt.suggestions[g]));g<a.config.scayt_maxSuggestions?(a.addMenuItem(f,{label:c[g],command:f,group:"scayt_suggest",order:g+1}),d[f]=CKEDITOR.TRISTATE_OFF):(a.addMenuItem(f,{label:c[g],command:f,group:"scayt_moresuggest",order:g+1}),b[f]=CKEDITOR.TRISTATE_OFF,"on"===a.config.scayt_moreSuggestions&&
+(a.addMenuItem("scayt_moresuggest",{label:e.getLocal("btn_moreSuggestions"),group:"scayt_moresuggest",order:10,getItems:function(){return b}}),d.scayt_moresuggest=CKEDITOR.TRISTATE_OFF))}else d.no_scayt_suggest=CKEDITOR.TRISTATE_DISABLED,a.addCommand("no_scayt_suggest",{exec:function(){}}),a.addMenuItem("no_scayt_suggest",{label:e.getLocal("btn_noSuggestions")||"no_scayt_suggest",command:"no_scayt_suggest",group:"scayt_suggest",order:0});return d},menuGenerator:function(a,c){var d=a.scayt,b=this.scaytMenuDefinition(a),
+e={},g=a.config.scayt_contextCommands.split("|");d.fire("getSuggestionsList",{lang:d.getLang(),word:c});e=this.buildSuggestionMenuItems(a,CKEDITOR.plugins.scayt.suggestions);if("off"==a.config.scayt_contextCommands)return e;for(var f in b)-1==CKEDITOR.tools.indexOf(g,f.replace("scayt_",""))&&"all"!=a.config.scayt_contextCommands||(e[f]=CKEDITOR.TRISTATE_OFF,"function"===typeof b[f].verification&&!b[f].verification(a)&&delete e[f],a.addCommand(f,{exec:b[f].exec}),a.addMenuItem(f,{label:a.lang.scayt[b[f].label]||
+b[f].label,command:f,group:b[f].group,order:b[f].order}));return e},createCommand:function(a){return{exec:function(c){c.scayt.replaceSelectionNode({word:a})}}}});
+CKEDITOR.plugins.scayt={state:{},suggestions:[],loadingHelper:{loadOrder:[]},isLoading:!1,options:{disablingCommandExec:{source:!0,newpage:!0,templates:!0},data_attribute_name:"data-scayt-word",misspelled_word_class:"scayt-misspell-word"},backCompatibilityMap:{scayt_service_protocol:"scayt_serviceProtocol",scayt_service_host:"scayt_serviceHost",scayt_service_port:"scayt_servicePort",scayt_service_path:"scayt_servicePath",scayt_customerid:"scayt_customerId"},replaceOldOptionsNames:function(a){for(var c in a)c in
+this.backCompatibilityMap&&(a[this.backCompatibilityMap[c]]=a[c],delete a[c])},createScayt:function(a){var c=this;this.loadScaytLibrary(a,function(a){var b={lang:a.config.scayt_sLang,container:"BODY"==a.editable().$.nodeName?a.document.getWindow().$.frameElement:a.editable().$,customDictionary:a.config.scayt_customDictionaryIds,userDictionaryName:a.config.scayt_userDictionaryName,localization:a.langCode,customer_id:a.config.scayt_customerId,debug:a.config.scayt_debug,data_attribute_name:c.options.data_attribute_name,
+misspelled_word_class:c.options.misspelled_word_class,"options-to-restore":a.config.scayt_disableOptionsStorage,focused:a.editable().hasFocus,ignoreElementsRegex:a.config.scayt_elementsToIgnore};a.config.scayt_serviceProtocol&&(b.service_protocol=a.config.scayt_serviceProtocol);a.config.scayt_serviceHost&&(b.service_host=a.config.scayt_serviceHost);a.config.scayt_servicePort&&(b.service_port=a.config.scayt_servicePort);a.config.scayt_servicePath&&(b.service_path=a.config.scayt_servicePath);b=new SCAYT.CKSCAYT(b,
+function(){},function(){});b.subscribe("suggestionListSend",function(a){for(var b={},d=[],c=0;c<a.suggestionList.length;c++)if(!b["word_"+a.suggestionList[c]]){b["word_"+a.suggestionList[c]]=a.suggestionList[c];d.push(a.suggestionList[c])}CKEDITOR.plugins.scayt.suggestions=d});a.scayt=b;a.fire("scaytButtonState",a.readOnly?CKEDITOR.TRISTATE_DISABLED:CKEDITOR.TRISTATE_ON)})},destroy:function(a){a.scayt&&a.scayt.destroy();delete a.scayt;a.fire("scaytButtonState",CKEDITOR.TRISTATE_OFF)},loadScaytLibrary:function(a,
+c){var d=this;"undefined"===typeof window.SCAYT||"function"!==typeof window.SCAYT.CKSCAYT?(this.loadingHelper[a.name]=c,this.loadingHelper.loadOrder.push(a.name),CKEDITOR.scriptLoader.load(a.config.scayt_srcUrl,function(){var a;CKEDITOR.fireOnce("scaytReady");for(var c=0;c<d.loadingHelper.loadOrder.length;c++){a=d.loadingHelper.loadOrder[c];if("function"===typeof d.loadingHelper[a])d.loadingHelper[a](CKEDITOR.instances[a]);delete d.loadingHelper[a]}d.loadingHelper.loadOrder=[]})):window.SCAYT&&"function"===
+typeof window.SCAYT.CKSCAYT&&(CKEDITOR.fireOnce("scaytReady"),a.scayt||"function"===typeof c&&c(a))}};
+CKEDITOR.on("scaytReady",function(){if(!0===CKEDITOR.config.scayt_handleCheckDirty){var a=CKEDITOR.editor.prototype;a.checkDirty=CKEDITOR.tools.override(a.checkDirty,function(a){return function(){var b=null,c=this.scayt;if(!CKEDITOR.plugins.scayt||!CKEDITOR.plugins.scayt.state[this.name]||!this.scayt)b=a.call(this);else if(b="ready"==this.status)var g=c.removeMarkupFromString(this.getSnapshot()),c=c.removeMarkupFromString(this._.previousValue),b=b&&c!==g;return b}});a.resetDirty=CKEDITOR.tools.override(a.resetDirty,
+function(a){return function(){var b=this.scayt;!CKEDITOR.plugins.scayt||!CKEDITOR.plugins.scayt.state[this.name]||!this.scayt?a.call(this):this._.previousValue=b.removeMarkupFromString(this.getSnapshot())}})}if(!0===CKEDITOR.config.scayt_handleUndoRedo){var a=CKEDITOR.plugins.undo.Image.prototype,c="function"==typeof a.equalsContent?"equalsContent":"equals";a[c]=CKEDITOR.tools.override(a[c],function(a){return function(b){var c=b.editor.scayt,g=this.contents,f=b.contents,h=null;CKEDITOR.plugins.scayt&&
+(CKEDITOR.plugins.scayt.state[b.editor.name]&&b.editor.scayt)&&(this.contents=c.removeMarkupFromString(g)||"",b.contents=c.removeMarkupFromString(f)||"");h=a.apply(this,arguments);this.contents=g;b.contents=f;return h}})}});(function(){CKEDITOR.plugins.add("stylescombo",{requires:"richcombo",init:function(c){var j=c.config,g=c.lang.stylescombo,f={},i=[],k=[];c.on("stylesSet",function(b){if(b=b.data.styles){for(var a,h,d,e=0,l=b.length;e<l;e++)if(a=b[e],!(c.blockless&&a.element in CKEDITOR.dtd.$block)&&(h=a.name,a=new CKEDITOR.style(a),!c.filter.customConfig||c.filter.check(a)))a._name=h,a._.enterMode=j.enterMode,a._.type=d=a.assignedTo||a.type,a._.weight=e+1E3*(d==CKEDITOR.STYLE_OBJECT?1:d==CKEDITOR.STYLE_BLOCK?2:3),
+f[h]=a,i.push(a),k.push(a);i.sort(function(a,b){return a._.weight-b._.weight})}});c.ui.addRichCombo("Styles",{label:g.label,title:g.panelTitle,toolbar:"styles,10",allowedContent:k,panel:{css:[CKEDITOR.skin.getPath("editor")].concat(j.contentsCss),multiSelect:!0,attributes:{"aria-label":g.panelTitle}},init:function(){var b,a,c,d,e,f;e=0;for(f=i.length;e<f;e++)b=i[e],a=b._name,d=b._.type,d!=c&&(this.startGroup(g["panelTitle"+d]),c=d),this.add(a,b.type==CKEDITOR.STYLE_OBJECT?a:b.buildPreview(),a);this.commit()},
+onClick:function(b){c.focus();c.fire("saveSnapshot");var b=f[b],a=c.elementPath();c[b.checkActive(a,c)?"removeStyle":"applyStyle"](b);c.fire("saveSnapshot")},onRender:function(){c.on("selectionChange",function(b){for(var a=this.getValue(),b=b.data.path.elements,h=0,d=b.length,e;h<d;h++){e=b[h];for(var g in f)if(f[g].checkElementRemovable(e,!0,c)){g!=a&&this.setValue(g);return}}this.setValue("")},this)},onOpen:function(){var b=c.getSelection().getSelectedElement(),b=c.elementPath(b),a=[0,0,0,0];this.showAll();
+this.unmarkAll();for(var h in f){var d=f[h],e=d._.type;d.checkApplicable(b,c,c.activeFilter)?a[e]++:this.hideItem(h);d.checkActive(b,c)&&this.mark(h)}a[CKEDITOR.STYLE_BLOCK]||this.hideGroup(g["panelTitle"+CKEDITOR.STYLE_BLOCK]);a[CKEDITOR.STYLE_INLINE]||this.hideGroup(g["panelTitle"+CKEDITOR.STYLE_INLINE]);a[CKEDITOR.STYLE_OBJECT]||this.hideGroup(g["panelTitle"+CKEDITOR.STYLE_OBJECT])},refresh:function(){var b=c.elementPath();if(b){for(var a in f)if(f[a].checkApplicable(b,c,c.activeFilter))return;
+this.setState(CKEDITOR.TRISTATE_DISABLED)}},reset:function(){f={};i=[]}})}})})();(function(){function i(c){return{editorFocus:!1,canUndo:!1,modes:{wysiwyg:1},exec:function(d){if(d.editable().hasFocus){var e=d.getSelection(),b;if(b=(new CKEDITOR.dom.elementPath(e.getCommonAncestor(),e.root)).contains({td:1,th:1},1)){var e=d.createRange(),a=CKEDITOR.tools.tryThese(function(){var a=b.getParent().$.cells[b.$.cellIndex+(c?-1:1)];a.parentNode.parentNode;return a},function(){var a=b.getParent(),a=a.getAscendant("table").$.rows[a.$.rowIndex+(c?-1:1)];return a.cells[c?a.cells.length-1:
+0]});if(!a&&!c){for(var f=b.getAscendant("table").$,a=b.getParent().$.cells,f=new CKEDITOR.dom.element(f.insertRow(-1),d.document),g=0,h=a.length;g<h;g++)f.append((new CKEDITOR.dom.element(a[g],d.document)).clone(!1,!1)).appendBogus();e.moveToElementEditStart(f)}else if(a)a=new CKEDITOR.dom.element(a),e.moveToElementEditStart(a),(!e.checkStartOfBlock()||!e.checkEndOfBlock())&&e.selectNodeContents(a);else return!0;e.select(!0);return!0}}return!1}}}var h={editorFocus:!1,modes:{wysiwyg:1,source:1}},
+g={exec:function(c){c.container.focusNext(!0,c.tabIndex)}},f={exec:function(c){c.container.focusPrevious(!0,c.tabIndex)}};CKEDITOR.plugins.add("tab",{init:function(c){for(var d=!1!==c.config.enableTabKeyTools,e=c.config.tabSpaces||0,b="";e--;)b+=" ";if(b)c.on("key",function(a){9==a.data.keyCode&&(c.insertText(b),a.cancel())});if(d)c.on("key",function(a){(9==a.data.keyCode&&c.execCommand("selectNextCell")||a.data.keyCode==CKEDITOR.SHIFT+9&&c.execCommand("selectPreviousCell"))&&a.cancel()});c.addCommand("blur",
+CKEDITOR.tools.extend(g,h));c.addCommand("blurBack",CKEDITOR.tools.extend(f,h));c.addCommand("selectNextCell",i());c.addCommand("selectPreviousCell",i(!0))}})})();
+CKEDITOR.dom.element.prototype.focusNext=function(i,h){var g=void 0===h?this.getTabIndex():h,f,c,d,e,b,a;if(0>=g)for(b=this.getNextSourceNode(i,CKEDITOR.NODE_ELEMENT);b;){if(b.isVisible()&&0===b.getTabIndex()){d=b;break}b=b.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT)}else for(b=this.getDocument().getBody().getFirst();b=b.getNextSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!f)if(!c&&b.equals(this)){if(c=!0,i){if(!(b=b.getNextSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;f=1}}else c&&!this.contains(b)&&
+(f=1);if(b.isVisible()&&!(0>(a=b.getTabIndex()))){if(f&&a==g){d=b;break}a>g&&(!d||!e||a<e)?(d=b,e=a):!d&&0===a&&(d=b,e=a)}}d&&d.focus()};
+CKEDITOR.dom.element.prototype.focusPrevious=function(i,h){for(var g=void 0===h?this.getTabIndex():h,f,c,d,e=0,b,a=this.getDocument().getBody().getLast();a=a.getPreviousSourceNode(!1,CKEDITOR.NODE_ELEMENT);){if(!f)if(!c&&a.equals(this)){if(c=!0,i){if(!(a=a.getPreviousSourceNode(!0,CKEDITOR.NODE_ELEMENT)))break;f=1}}else c&&!this.contains(a)&&(f=1);if(a.isVisible()&&!(0>(b=a.getTabIndex())))if(0>=g){if(f&&0===b){d=a;break}b>e&&(d=a,e=b)}else{if(f&&b==g){d=a;break}if(b<g&&(!d||b>e))d=a,e=b}}d&&d.focus()};CKEDITOR.plugins.add("table",{requires:"dialog",init:function(a){function e(a){return CKEDITOR.tools.extend(a||{},{contextSensitive:1,refresh:function(a,f){this.setState(f.contains("table",1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}if(!a.blockless){var c=a.lang.table;a.addCommand("table",new CKEDITOR.dialogCommand("table",{context:"table",allowedContent:"table{width,height}[align,border,cellpadding,cellspacing,summary];caption tbody thead tfoot;th td tr[scope];"+(a.plugins.dialogadvtab?
+"table"+a.plugins.dialogadvtab.allowedContent():""),requiredContent:"table",contentTransformations:[["table{width}: sizeToStyle","table[width]: sizeToAttribute"]]}));a.addCommand("tableProperties",new CKEDITOR.dialogCommand("tableProperties",e()));a.addCommand("tableDelete",e({exec:function(a){var b=a.elementPath().contains("table",1);if(b){var d=b.getParent(),c=a.editable();1==d.getChildCount()&&(!d.is("td","th")&&!d.equals(c))&&(b=d);a=a.createRange();a.moveToPosition(b,CKEDITOR.POSITION_BEFORE_START);
+b.remove();a.select()}}}));a.ui.addButton&&a.ui.addButton("Table",{label:c.toolbar,command:"table",toolbar:"insert,30"});CKEDITOR.dialog.add("table",this.path+"dialogs/table.js");CKEDITOR.dialog.add("tableProperties",this.path+"dialogs/table.js");a.addMenuItems&&a.addMenuItems({table:{label:c.menu,command:"tableProperties",group:"table",order:5},tabledelete:{label:c.deleteTable,command:"tableDelete",group:"table",order:1}});a.on("doubleclick",function(a){a.data.element.is("table")&&(a.data.dialog=
+"tableProperties")});a.contextMenu&&a.contextMenu.addListener(function(){return{tabledelete:CKEDITOR.TRISTATE_OFF,table:CKEDITOR.TRISTATE_OFF}})}}});(function(){function p(e){function d(a){!(0<b.length)&&(a.type==CKEDITOR.NODE_ELEMENT&&y.test(a.getName())&&!a.getCustomData("selected_cell"))&&(CKEDITOR.dom.element.setMarker(c,a,"selected_cell",!0),b.push(a))}for(var e=e.getRanges(),b=[],c={},a=0;a<e.length;a++){var f=e[a];if(f.collapsed)f=f.getCommonAncestor(),(f=f.getAscendant("td",!0)||f.getAscendant("th",!0))&&b.push(f);else{var f=new CKEDITOR.dom.walker(f),g;for(f.guard=d;g=f.next();)if(g.type!=CKEDITOR.NODE_ELEMENT||!g.is(CKEDITOR.dtd.table))if((g=
+g.getAscendant("td",!0)||g.getAscendant("th",!0))&&!g.getCustomData("selected_cell"))CKEDITOR.dom.element.setMarker(c,g,"selected_cell",!0),b.push(g)}}CKEDITOR.dom.element.clearAllMarkers(c);return b}function o(e,d){for(var b=p(e),c=b[0],a=c.getAscendant("table"),c=c.getDocument(),f=b[0].getParent(),g=f.$.rowIndex,b=b[b.length-1],h=b.getParent().$.rowIndex+b.$.rowSpan-1,b=new CKEDITOR.dom.element(a.$.rows[h]),g=d?g:h,f=d?f:b,b=CKEDITOR.tools.buildTableMap(a),a=b[g],g=d?b[g-1]:b[g+1],b=b[0].length,
+c=c.createElement("tr"),h=0;a[h]&&h<b;h++){var i;1<a[h].rowSpan&&g&&a[h]==g[h]?(i=a[h],i.rowSpan+=1):(i=(new CKEDITOR.dom.element(a[h])).clone(),i.removeAttribute("rowSpan"),i.appendBogus(),c.append(i),i=i.$);h+=i.colSpan-1}d?c.insertBefore(f):c.insertAfter(f)}function q(e){if(e instanceof CKEDITOR.dom.selection){for(var d=p(e),b=d[0].getAscendant("table"),c=CKEDITOR.tools.buildTableMap(b),e=d[0].getParent().$.rowIndex,d=d[d.length-1],a=d.getParent().$.rowIndex+d.$.rowSpan-1,d=[],f=e;f<=a;f++){for(var g=
+c[f],h=new CKEDITOR.dom.element(b.$.rows[f]),i=0;i<g.length;i++){var j=new CKEDITOR.dom.element(g[i]),l=j.getParent().$.rowIndex;1==j.$.rowSpan?j.remove():(j.$.rowSpan-=1,l==f&&(l=c[f+1],l[i-1]?j.insertAfter(new CKEDITOR.dom.element(l[i-1])):(new CKEDITOR.dom.element(b.$.rows[f+1])).append(j,1)));i+=j.$.colSpan-1}d.push(h)}c=b.$.rows;b=new CKEDITOR.dom.element(c[a+1]||(0<e?c[e-1]:null)||b.$.parentNode);for(f=d.length;0<=f;f--)q(d[f]);return b}e instanceof CKEDITOR.dom.element&&(b=e.getAscendant("table"),
+1==b.$.rows.length?b.remove():e.remove());return null}function r(e,d){for(var b=d?Infinity:0,c=0;c<e.length;c++){var a;a=e[c];for(var f=d,g=a.getParent().$.cells,h=0,i=0;i<g.length;i++){var j=g[i],h=h+(f?1:j.colSpan);if(j==a.$)break}a=h-1;if(d?a<b:a>b)b=a}return b}function k(e,d){for(var b=p(e),c=b[0].getAscendant("table"),a=r(b,1),b=r(b),a=d?a:b,f=CKEDITOR.tools.buildTableMap(c),c=[],b=[],g=f.length,h=0;h<g;h++)c.push(f[h][a]),b.push(d?f[h][a-1]:f[h][a+1]);for(h=0;h<g;h++)c[h]&&(1<c[h].colSpan&&
+b[h]==c[h]?(a=c[h],a.colSpan+=1):(a=(new CKEDITOR.dom.element(c[h])).clone(),a.removeAttribute("colSpan"),a.appendBogus(),a[d?"insertBefore":"insertAfter"].call(a,new CKEDITOR.dom.element(c[h])),a=a.$),h+=a.rowSpan-1)}function u(e,d){var b=e.getStartElement();if(b=b.getAscendant("td",1)||b.getAscendant("th",1)){var c=b.clone();c.appendBogus();d?c.insertBefore(b):c.insertAfter(b)}}function t(e){if(e instanceof CKEDITOR.dom.selection){var e=p(e),d=e[0]&&e[0].getAscendant("table"),b;a:{var c=0;b=e.length-
+1;for(var a={},f,g;f=e[c++];)CKEDITOR.dom.element.setMarker(a,f,"delete_cell",!0);for(c=0;f=e[c++];)if((g=f.getPrevious())&&!g.getCustomData("delete_cell")||(g=f.getNext())&&!g.getCustomData("delete_cell")){CKEDITOR.dom.element.clearAllMarkers(a);b=g;break a}CKEDITOR.dom.element.clearAllMarkers(a);g=e[0].getParent();(g=g.getPrevious())?b=g.getLast():(g=e[b].getParent(),b=(g=g.getNext())?g.getChild(0):null)}for(g=e.length-1;0<=g;g--)t(e[g]);b?m(b,!0):d&&d.remove()}else e instanceof CKEDITOR.dom.element&&
+(d=e.getParent(),1==d.getChildCount()?d.remove():e.remove())}function m(e,d){var b=e.getDocument(),c=CKEDITOR.document;CKEDITOR.env.ie&&10==CKEDITOR.env.version&&(c.focus(),b.focus());b=new CKEDITOR.dom.range(b);if(!b["moveToElementEdit"+(d?"End":"Start")](e))b.selectNodeContents(e),b.collapse(d?!1:!0);b.select(!0)}function v(e,d,b){e=e[d];if("undefined"==typeof b)return e;for(d=0;e&&d<e.length;d++){if(b.is&&e[d]==b.$)return d;if(d==b)return new CKEDITOR.dom.element(e[d])}return b.is?-1:null}function s(e,
+d,b){var c=p(e),a;if((d?1!=c.length:2>c.length)||(a=e.getCommonAncestor())&&a.type==CKEDITOR.NODE_ELEMENT&&a.is("table"))return!1;var f,e=c[0];a=e.getAscendant("table");var g=CKEDITOR.tools.buildTableMap(a),h=g.length,i=g[0].length,j=e.getParent().$.rowIndex,l=v(g,j,e);if(d){var n;try{var m=parseInt(e.getAttribute("rowspan"),10)||1;f=parseInt(e.getAttribute("colspan"),10)||1;n=g["up"==d?j-m:"down"==d?j+m:j]["left"==d?l-f:"right"==d?l+f:l]}catch(z){return!1}if(!n||e.$==n)return!1;c["up"==d||"left"==
+d?"unshift":"push"](new CKEDITOR.dom.element(n))}for(var d=e.getDocument(),o=j,m=n=0,q=!b&&new CKEDITOR.dom.documentFragment(d),s=0,d=0;d<c.length;d++){f=c[d];var k=f.getParent(),t=f.getFirst(),r=f.$.colSpan,u=f.$.rowSpan,k=k.$.rowIndex,w=v(g,k,f),s=s+r*u,m=Math.max(m,w-l+r);n=Math.max(n,k-j+u);if(!b){r=f;(u=r.getBogus())&&u.remove();r.trim();if(f.getChildren().count()){if(k!=o&&t&&(!t.isBlockBoundary||!t.isBlockBoundary({br:1})))(o=q.getLast(CKEDITOR.dom.walker.whitespaces(!0)))&&(!o.is||!o.is("br"))&&
+q.append("br");f.moveChildren(q)}d?f.remove():f.setHtml("")}o=k}if(b)return n*m==s;q.moveChildren(e);e.appendBogus();m>=i?e.removeAttribute("rowSpan"):e.$.rowSpan=n;n>=h?e.removeAttribute("colSpan"):e.$.colSpan=m;b=new CKEDITOR.dom.nodeList(a.$.rows);c=b.count();for(d=c-1;0<=d;d--)a=b.getItem(d),a.$.cells.length||(a.remove(),c++);return e}function w(e,d){var b=p(e);if(1<b.length)return!1;if(d)return!0;var b=b[0],c=b.getParent(),a=c.getAscendant("table"),f=CKEDITOR.tools.buildTableMap(a),g=c.$.rowIndex,
+h=v(f,g,b),i=b.$.rowSpan,j;if(1<i){j=Math.ceil(i/2);for(var i=Math.floor(i/2),c=g+j,a=new CKEDITOR.dom.element(a.$.rows[c]),f=v(f,c),l,c=b.clone(),g=0;g<f.length;g++)if(l=f[g],l.parentNode==a.$&&g>h){c.insertBefore(new CKEDITOR.dom.element(l));break}else l=null;l||a.append(c)}else{i=j=1;a=c.clone();a.insertAfter(c);a.append(c=b.clone());l=v(f,g);for(h=0;h<l.length;h++)l[h].rowSpan++}c.appendBogus();b.$.rowSpan=j;c.$.rowSpan=i;1==j&&b.removeAttribute("rowSpan");1==i&&c.removeAttribute("rowSpan");return c}
+function x(e,d){var b=p(e);if(1<b.length)return!1;if(d)return!0;var b=b[0],c=b.getParent(),a=c.getAscendant("table"),a=CKEDITOR.tools.buildTableMap(a),f=v(a,c.$.rowIndex,b),g=b.$.colSpan;if(1<g)c=Math.ceil(g/2),g=Math.floor(g/2);else{for(var g=c=1,h=[],i=0;i<a.length;i++){var j=a[i];h.push(j[f]);1<j[f].rowSpan&&(i+=j[f].rowSpan-1)}for(a=0;a<h.length;a++)h[a].colSpan++}a=b.clone();a.insertAfter(b);a.appendBogus();b.$.colSpan=c;a.$.colSpan=g;1==c&&b.removeAttribute("colSpan");1==g&&a.removeAttribute("colSpan");
+return a}var y=/^(?:td|th)$/;CKEDITOR.plugins.tabletools={requires:"table,dialog,contextmenu",init:function(e){function d(a){return CKEDITOR.tools.extend(a||{},{contextSensitive:1,refresh:function(a,b){this.setState(b.contains({td:1,th:1},1)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)}})}function b(a,b){var c=e.addCommand(a,b);e.addFeature(c)}var c=e.lang.table;b("cellProperties",new CKEDITOR.dialogCommand("cellProperties",d({allowedContent:"td th{width,height,border-color,background-color,white-space,vertical-align,text-align}[colspan,rowspan]",
+requiredContent:"table"})));CKEDITOR.dialog.add("cellProperties",this.path+"dialogs/tableCell.js");b("rowDelete",d({requiredContent:"table",exec:function(a){a=a.getSelection();m(q(a))}}));b("rowInsertBefore",d({requiredContent:"table",exec:function(a){a=a.getSelection();o(a,!0)}}));b("rowInsertAfter",d({requiredContent:"table",exec:function(a){a=a.getSelection();o(a)}}));b("columnDelete",d({requiredContent:"table",exec:function(a){for(var a=a.getSelection(),a=p(a),b=a[0],c=a[a.length-1],a=b.getAscendant("table"),
+d=CKEDITOR.tools.buildTableMap(a),e,j,l=[],n=0,o=d.length;n<o;n++)for(var k=0,q=d[n].length;k<q;k++)d[n][k]==b.$&&(e=k),d[n][k]==c.$&&(j=k);for(n=e;n<=j;n++)for(k=0;k<d.length;k++)c=d[k],b=new CKEDITOR.dom.element(a.$.rows[k]),c=new CKEDITOR.dom.element(c[n]),c.$&&(1==c.$.colSpan?c.remove():c.$.colSpan-=1,k+=c.$.rowSpan-1,b.$.cells.length||l.push(b));j=a.$.rows[0]&&a.$.rows[0].cells;e=new CKEDITOR.dom.element(j[e]||(e?j[e-1]:a.$.parentNode));l.length==o&&a.remove();e&&m(e,!0)}}));b("columnInsertBefore",
+d({requiredContent:"table",exec:function(a){a=a.getSelection();k(a,!0)}}));b("columnInsertAfter",d({requiredContent:"table",exec:function(a){a=a.getSelection();k(a)}}));b("cellDelete",d({requiredContent:"table",exec:function(a){a=a.getSelection();t(a)}}));b("cellMerge",d({allowedContent:"td[colspan,rowspan]",requiredContent:"td[colspan,rowspan]",exec:function(a){m(s(a.getSelection()),!0)}}));b("cellMergeRight",d({allowedContent:"td[colspan]",requiredContent:"td[colspan]",exec:function(a){m(s(a.getSelection(),
+"right"),!0)}}));b("cellMergeDown",d({allowedContent:"td[rowspan]",requiredContent:"td[rowspan]",exec:function(a){m(s(a.getSelection(),"down"),!0)}}));b("cellVerticalSplit",d({allowedContent:"td[rowspan]",requiredContent:"td[rowspan]",exec:function(a){m(w(a.getSelection()))}}));b("cellHorizontalSplit",d({allowedContent:"td[colspan]",requiredContent:"td[colspan]",exec:function(a){m(x(a.getSelection()))}}));b("cellInsertBefore",d({requiredContent:"table",exec:function(a){a=a.getSelection();u(a,!0)}}));
+b("cellInsertAfter",d({requiredContent:"table",exec:function(a){a=a.getSelection();u(a)}}));e.addMenuItems&&e.addMenuItems({tablecell:{label:c.cell.menu,group:"tablecell",order:1,getItems:function(){var a=e.getSelection(),b=p(a);return{tablecell_insertBefore:CKEDITOR.TRISTATE_OFF,tablecell_insertAfter:CKEDITOR.TRISTATE_OFF,tablecell_delete:CKEDITOR.TRISTATE_OFF,tablecell_merge:s(a,null,!0)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,tablecell_merge_right:s(a,"right",!0)?CKEDITOR.TRISTATE_OFF:
+CKEDITOR.TRISTATE_DISABLED,tablecell_merge_down:s(a,"down",!0)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,tablecell_split_vertical:w(a,!0)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,tablecell_split_horizontal:x(a,!0)?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED,tablecell_properties:0<b.length?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED}}},tablecell_insertBefore:{label:c.cell.insertBefore,group:"tablecell",command:"cellInsertBefore",order:5},tablecell_insertAfter:{label:c.cell.insertAfter,
+group:"tablecell",command:"cellInsertAfter",order:10},tablecell_delete:{label:c.cell.deleteCell,group:"tablecell",command:"cellDelete",order:15},tablecell_merge:{label:c.cell.merge,group:"tablecell",command:"cellMerge",order:16},tablecell_merge_right:{label:c.cell.mergeRight,group:"tablecell",command:"cellMergeRight",order:17},tablecell_merge_down:{label:c.cell.mergeDown,group:"tablecell",command:"cellMergeDown",order:18},tablecell_split_horizontal:{label:c.cell.splitHorizontal,group:"tablecell",
+command:"cellHorizontalSplit",order:19},tablecell_split_vertical:{label:c.cell.splitVertical,group:"tablecell",command:"cellVerticalSplit",order:20},tablecell_properties:{label:c.cell.title,group:"tablecellproperties",command:"cellProperties",order:21},tablerow:{label:c.row.menu,group:"tablerow",order:1,getItems:function(){return{tablerow_insertBefore:CKEDITOR.TRISTATE_OFF,tablerow_insertAfter:CKEDITOR.TRISTATE_OFF,tablerow_delete:CKEDITOR.TRISTATE_OFF}}},tablerow_insertBefore:{label:c.row.insertBefore,
+group:"tablerow",command:"rowInsertBefore",order:5},tablerow_insertAfter:{label:c.row.insertAfter,group:"tablerow",command:"rowInsertAfter",order:10},tablerow_delete:{label:c.row.deleteRow,group:"tablerow",command:"rowDelete",order:15},tablecolumn:{label:c.column.menu,group:"tablecolumn",order:1,getItems:function(){return{tablecolumn_insertBefore:CKEDITOR.TRISTATE_OFF,tablecolumn_insertAfter:CKEDITOR.TRISTATE_OFF,tablecolumn_delete:CKEDITOR.TRISTATE_OFF}}},tablecolumn_insertBefore:{label:c.column.insertBefore,
+group:"tablecolumn",command:"columnInsertBefore",order:5},tablecolumn_insertAfter:{label:c.column.insertAfter,group:"tablecolumn",command:"columnInsertAfter",order:10},tablecolumn_delete:{label:c.column.deleteColumn,group:"tablecolumn",command:"columnDelete",order:15}});e.contextMenu&&e.contextMenu.addListener(function(a,b,c){return(a=c.contains({td:1,th:1},1))&&!a.isReadOnly()?{tablecell:CKEDITOR.TRISTATE_OFF,tablerow:CKEDITOR.TRISTATE_OFF,tablecolumn:CKEDITOR.TRISTATE_OFF}:null})},getSelectedCells:p};
+CKEDITOR.plugins.add("tabletools",CKEDITOR.plugins.tabletools)})();CKEDITOR.tools.buildTableMap=function(p){for(var p=p.$.rows,o=-1,q=[],r=0;r<p.length;r++){o++;!q[o]&&(q[o]=[]);for(var k=-1,u=0;u<p[r].cells.length;u++){var t=p[r].cells[u];for(k++;q[o][k];)k++;for(var m=isNaN(t.colSpan)?1:t.colSpan,t=isNaN(t.rowSpan)?1:t.rowSpan,v=0;v<t;v++){q[o+v]||(q[o+v]=[]);for(var s=0;s<m;s++)q[o+v][k+s]=p[r].cells[u]}k+=m-1}}return q};(function(){var g=[CKEDITOR.CTRL+90,CKEDITOR.CTRL+89,CKEDITOR.CTRL+CKEDITOR.SHIFT+90],l={8:1,46:1};CKEDITOR.plugins.add("undo",{init:function(a){function b(a){d.enabled&&!1!==a.data.command.canUndo&&d.save()}function c(){d.enabled=a.readOnly?!1:"wysiwyg"==a.mode;d.onChange()}var d=a.undoManager=new e(a),j=d.editingHandler=new i(d),f=a.addCommand("undo",{exec:function(){d.undo()&&(a.selectionChange(),this.fire("afterUndo"))},startDisabled:!0,canUndo:!1}),h=a.addCommand("redo",{exec:function(){d.redo()&&
+(a.selectionChange(),this.fire("afterRedo"))},startDisabled:!0,canUndo:!1});a.setKeystroke([[g[0],"undo"],[g[1],"redo"],[g[2],"redo"]]);d.onChange=function(){f.setState(d.undoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED);h.setState(d.redoable()?CKEDITOR.TRISTATE_OFF:CKEDITOR.TRISTATE_DISABLED)};a.on("beforeCommandExec",b);a.on("afterCommandExec",b);a.on("saveSnapshot",function(a){d.save(a.data&&a.data.contentOnly)});a.on("contentDom",j.attachListeners,j);a.on("instanceReady",function(){a.fire("saveSnapshot")});
+a.on("beforeModeUnload",function(){"wysiwyg"==a.mode&&d.save(!0)});a.on("mode",c);a.on("readOnly",c);a.ui.addButton&&(a.ui.addButton("Undo",{label:a.lang.undo.undo,command:"undo",toolbar:"undo,10"}),a.ui.addButton("Redo",{label:a.lang.undo.redo,command:"redo",toolbar:"undo,20"}));a.resetUndo=function(){d.reset();a.fire("saveSnapshot")};a.on("updateSnapshot",function(){d.currentImage&&d.update()});a.on("lockSnapshot",function(a){a=a.data;d.lock(a&&a.dontUpdate,a&&a.forceUpdate)});a.on("unlockSnapshot",
+d.unlock,d)}});CKEDITOR.plugins.undo={};var e=CKEDITOR.plugins.undo.UndoManager=function(a){this.strokesRecorded=[0,0];this.locked=null;this.previousKeyGroup=-1;this.limit=a.config.undoStackSize||20;this.strokesLimit=25;this.editor=a;this.reset()};e.prototype={type:function(a,b){var c=e.getKeyGroup(a),d=this.strokesRecorded[c]+1,b=b||d>=this.strokesLimit;this.typing||(this.hasUndo=this.typing=!0,this.hasRedo=!1,this.onChange());b?(d=0,this.editor.fire("saveSnapshot")):this.editor.fire("change");this.strokesRecorded[c]=
+d;this.previousKeyGroup=c},keyGroupChanged:function(a){return e.getKeyGroup(a)!=this.previousKeyGroup},reset:function(){this.snapshots=[];this.index=-1;this.currentImage=null;this.hasRedo=this.hasUndo=!1;this.locked=null;this.resetType()},resetType:function(){this.strokesRecorded=[0,0];this.typing=!1;this.previousKeyGroup=-1},refreshState:function(){this.hasUndo=!!this.getNextImage(!0);this.hasRedo=!!this.getNextImage(!1);this.resetType();this.onChange()},save:function(a,b,c){var d=this.editor;if(this.locked||
+"ready"!=d.status||"wysiwyg"!=d.mode)return!1;var e=d.editable();if(!e||"ready"!=e.status)return!1;e=this.snapshots;b||(b=new f(d));if(!1===b.contents)return!1;if(this.currentImage)if(b.equalsContent(this.currentImage)){if(a||b.equalsSelection(this.currentImage))return!1}else!1!==c&&d.fire("change");e.splice(this.index+1,e.length-this.index-1);e.length==this.limit&&e.shift();this.index=e.push(b)-1;this.currentImage=b;!1!==c&&this.refreshState();return!0},restoreImage:function(a){var b=this.editor,
+c;a.bookmarks&&(b.focus(),c=b.getSelection());this.locked={level:999};this.editor.loadSnapshot(a.contents);a.bookmarks?c.selectBookmarks(a.bookmarks):CKEDITOR.env.ie&&(c=this.editor.document.getBody().$.createTextRange(),c.collapse(!0),c.select());this.locked=null;this.index=a.index;this.currentImage=this.snapshots[this.index];this.update();this.refreshState();b.fire("change")},getNextImage:function(a){var b=this.snapshots,c=this.currentImage,d;if(c)if(a)for(d=this.index-1;0<=d;d--){if(a=b[d],!c.equalsContent(a))return a.index=
+d,a}else for(d=this.index+1;d<b.length;d++)if(a=b[d],!c.equalsContent(a))return a.index=d,a;return null},redoable:function(){return this.enabled&&this.hasRedo},undoable:function(){return this.enabled&&this.hasUndo},undo:function(){if(this.undoable()){this.save(!0);var a=this.getNextImage(!0);if(a)return this.restoreImage(a),!0}return!1},redo:function(){if(this.redoable()&&(this.save(!0),this.redoable())){var a=this.getNextImage(!1);if(a)return this.restoreImage(a),!0}return!1},update:function(a){if(!this.locked){a||
+(a=new f(this.editor));for(var b=this.index,c=this.snapshots;0<b&&this.currentImage.equalsContent(c[b-1]);)b-=1;c.splice(b,this.index-b+1,a);this.index=b;this.currentImage=a}},updateSelection:function(a){if(!this.snapshots.length)return!1;var b=this.snapshots,c=b[b.length-1];return c.equalsContent(a)&&!c.equalsSelection(a)?(this.currentImage=b[b.length-1]=a,!0):!1},lock:function(a,b){if(this.locked)this.locked.level++;else if(a)this.locked={level:1};else{var c=null;if(b)c=!0;else{var d=new f(this.editor,
+!0);this.currentImage&&this.currentImage.equalsContent(d)&&(c=d)}this.locked={update:c,level:1}}},unlock:function(){if(this.locked&&!--this.locked.level){var a=this.locked.update;this.locked=null;if(!0===a)this.update();else if(a){var b=new f(this.editor,!0);a.equalsContent(b)||this.update()}}}};e.navigationKeyCodes={37:1,38:1,39:1,40:1,36:1,35:1,33:1,34:1};e.keyGroups={PRINTABLE:0,FUNCTIONAL:1};e.isNavigationKey=function(a){return!!e.navigationKeyCodes[a]};e.getKeyGroup=function(a){var b=e.keyGroups;
+return l[a]?b.FUNCTIONAL:b.PRINTABLE};e.getOppositeKeyGroup=function(a){var b=e.keyGroups;return a==b.FUNCTIONAL?b.PRINTABLE:b.FUNCTIONAL};e.ieFunctionalKeysBug=function(a){return CKEDITOR.env.ie&&e.getKeyGroup(a)==e.keyGroups.FUNCTIONAL};var f=CKEDITOR.plugins.undo.Image=function(a,b){this.editor=a;a.fire("beforeUndoImage");var c=a.getSnapshot();CKEDITOR.env.ie&&c&&(c=c.replace(/\s+data-cke-expando=".*?"/g,""));this.contents=c;b||(this.bookmarks=(c=c&&a.getSelection())&&c.createBookmarks2(!0));a.fire("afterUndoImage")},
+h=/\b(?:href|src|name)="[^"]*?"/gi;f.prototype={equalsContent:function(a){var b=this.contents,a=a.contents;if(CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks))b=b.replace(h,""),a=a.replace(h,"");return b!=a?!1:!0},equalsSelection:function(a){var b=this.bookmarks,a=a.bookmarks;if(b||a){if(!b||!a||b.length!=a.length)return!1;for(var c=0;c<b.length;c++){var d=b[c],e=a[c];if(d.startOffset!=e.startOffset||d.endOffset!=e.endOffset||!CKEDITOR.tools.arrayCompare(d.start,e.start)||!CKEDITOR.tools.arrayCompare(d.end,
+e.end))return!1}}return!0}};var i=CKEDITOR.plugins.undo.NativeEditingHandler=function(a){this.undoManager=a;this.ignoreInputEvent=!1;this.keyEventsStack=new k;this.lastKeydownImage=null};i.prototype={onKeydown:function(a){var b=a.data.getKey();if(229!==b)if(-1<CKEDITOR.tools.indexOf(g,a.data.getKeystroke()))a.data.preventDefault();else if(this.keyEventsStack.cleanUp(a),a=this.undoManager,this.keyEventsStack.getLast(b)||this.keyEventsStack.push(b),this.lastKeydownImage=new f(a.editor),e.isNavigationKey(b)||
+this.undoManager.keyGroupChanged(b))if(a.strokesRecorded[0]||a.strokesRecorded[1])a.save(!1,this.lastKeydownImage,!1),a.resetType()},onInput:function(){if(this.ignoreInputEvent)this.ignoreInputEvent=!1;else{var a=this.keyEventsStack.getLast();a||(a=this.keyEventsStack.push(0));this.keyEventsStack.increment(a.keyCode);this.keyEventsStack.getTotalInputs()>=this.undoManager.strokesLimit&&(this.undoManager.type(a.keyCode,!0),this.keyEventsStack.resetInputs())}},onKeyup:function(a){var b=this.undoManager,
+a=a.data.getKey(),c=this.keyEventsStack.getTotalInputs();this.keyEventsStack.remove(a);if(!e.ieFunctionalKeysBug(a)||!this.lastKeydownImage||!this.lastKeydownImage.equalsContent(new f(b.editor,!0)))if(0<c)b.type(a);else if(e.isNavigationKey(a))this.onNavigationKey(!0)},onNavigationKey:function(a){var b=this.undoManager;(a||!b.save(!0,null,!1))&&b.updateSelection(new f(b.editor));b.resetType()},ignoreInputEventListener:function(){this.ignoreInputEvent=!0},attachListeners:function(){var a=this.undoManager.editor,
+b=a.editable(),c=this;b.attachListener(b,"keydown",function(a){c.onKeydown(a);if(e.ieFunctionalKeysBug(a.data.getKey()))c.onInput()},null,null,999);b.attachListener(b,CKEDITOR.env.ie?"keypress":"input",c.onInput,c,null,999);b.attachListener(b,"keyup",c.onKeyup,c,null,999);b.attachListener(b,"paste",c.ignoreInputEventListener,c,null,999);b.attachListener(b,"drop",c.ignoreInputEventListener,c,null,999);b.attachListener(b.isInline()?b:a.document.getDocumentElement(),"click",function(){c.onNavigationKey()},
+null,null,999);b.attachListener(this.undoManager.editor,"blur",function(){c.keyEventsStack.remove(9)},null,null,999)}};var k=CKEDITOR.plugins.undo.KeyEventsStack=function(){this.stack=[]};k.prototype={push:function(a){return this.stack[this.stack.push({keyCode:a,inputs:0})-1]},getLastIndex:function(a){if("number"!=typeof a)return this.stack.length-1;for(var b=this.stack.length;b--;)if(this.stack[b].keyCode==a)return b;return-1},getLast:function(a){a=this.getLastIndex(a);return-1!=a?this.stack[a]:
+null},increment:function(a){this.getLast(a).inputs++},remove:function(a){a=this.getLastIndex(a);-1!=a&&this.stack.splice(a,1)},resetInputs:function(a){if("number"==typeof a)this.getLast(a).inputs=0;else for(a=this.stack.length;a--;)this.stack[a].inputs=0},getTotalInputs:function(){for(var a=this.stack.length,b=0;a--;)b+=this.stack[a].inputs;return b},cleanUp:function(a){a=a.data.$;!a.ctrlKey&&!a.metaKey&&this.remove(17);a.shiftKey||this.remove(16);a.altKey||this.remove(18)}}})();CKEDITOR.plugins.add("wsc",{requires:"dialog",parseApi:function(a){a.config.wsc_onFinish="function"===typeof a.config.wsc_onFinish?a.config.wsc_onFinish:function(){};a.config.wsc_onClose="function"===typeof a.config.wsc_onClose?a.config.wsc_onClose:function(){}},parseConfig:function(a){a.config.wsc_customerId=a.config.wsc_customerId||CKEDITOR.config.wsc_customerId||"1:ua3xw1-2XyGJ3-GWruD3-6OFNT1-oXcuB1-nR6Bp4-hgQHc-EcYng3-sdRXG3-NOfFk";a.config.wsc_customDictionaryIds=a.config.wsc_customDictionaryIds||
+CKEDITOR.config.wsc_customDictionaryIds||"";a.config.wsc_userDictionaryName=a.config.wsc_userDictionaryName||CKEDITOR.config.wsc_userDictionaryName||"";a.config.wsc_customLoaderScript=a.config.wsc_customLoaderScript||CKEDITOR.config.wsc_customLoaderScript;CKEDITOR.config.wsc_cmd=a.config.wsc_cmd||CKEDITOR.config.wsc_cmd||"spell";CKEDITOR.config.wsc_version="v4.3.0-26-g5bcf855";CKEDITOR.config.wsc_removeGlobalVariable=!0},init:function(a){var b=CKEDITOR.env;this.parseConfig(a);this.parseApi(a);a.addCommand("checkspell",
+new CKEDITOR.dialogCommand("checkspell")).modes={wysiwyg:!CKEDITOR.env.opera&&!CKEDITOR.env.air&&document.domain==window.location.hostname&&!(b.ie&&(8>b.version||b.quirks))};"undefined"==typeof a.plugins.scayt&&a.ui.addButton&&a.ui.addButton("SpellChecker",{label:a.lang.wsc.toolbar,click:function(a){var b=a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?a.container.getText():a.document.getBody().getText();(b=b.replace(/\s/g,""))?a.execCommand("checkspell"):alert("Nothing to check!")},toolbar:"spellchecker,10"});
+CKEDITOR.dialog.add("checkspell",this.path+(CKEDITOR.env.ie&&7>=CKEDITOR.env.version?"dialogs/wsc_ie.js":window.postMessage?"dialogs/wsc.js":"dialogs/wsc_ie.js"))}});CKEDITOR.config.plugins='dialogui,dialog,about,a11yhelp,basicstyles,blockquote,clipboard,panel,floatpanel,menu,contextmenu,resize,button,toolbar,elementspath,enterkey,entities,popup,filebrowser,floatingspace,listblock,richcombo,format,horizontalrule,htmlwriter,wysiwygarea,image,indent,indentlist,fakeobjects,link,list,magicline,maximize,pastetext,pastefromword,removeformat,showborders,sourcearea,specialchar,menubutton,scayt,stylescombo,tab,table,tabletools,undo,wsc';CKEDITOR.config.skin='moono';(function() {var setIcons = function(icons, strip) {var path = CKEDITOR.getUrl( 'plugins/' + strip );icons = icons.split( ',' );for ( var i = 0; i < icons.length; i++ )CKEDITOR.skin.icons[ icons[ i ] ] = { path: path, offset: -icons[ ++i ], bgsize : icons[ ++i ] };};if (CKEDITOR.env.hidpi) setIcons('about,0,,bold,24,,italic,48,,strike,72,,subscript,96,,superscript,120,,underline,144,,blockquote,168,,copy-rtl,192,,copy,216,,cut-rtl,240,,cut,264,,paste-rtl,288,,paste,312,,horizontalrule,336,,image,360,,indent-rtl,384,,indent,408,,outdent-rtl,432,,outdent,456,,anchor-rtl,480,,anchor,504,,link,528,,unlink,552,,bulletedlist-rtl,576,,bulletedlist,600,,numberedlist-rtl,624,,numberedlist,648,,maximize,672,,pastetext-rtl,696,,pastetext,720,,pastefromword-rtl,744,,pastefromword,768,,removeformat,792,,source-rtl,816,,source,840,,specialchar,864,,scayt,888,,table,912,,redo-rtl,936,,redo,960,,undo-rtl,984,,undo,1008,,spellchecker,1032,','icons_hidpi.png');else setIcons('about,0,auto,bold,24,auto,italic,48,auto,strike,72,auto,subscript,96,auto,superscript,120,auto,underline,144,auto,blockquote,168,auto,copy-rtl,192,auto,copy,216,auto,cut-rtl,240,auto,cut,264,auto,paste-rtl,288,auto,paste,312,auto,horizontalrule,336,auto,image,360,auto,indent-rtl,384,auto,indent,408,auto,outdent-rtl,432,auto,outdent,456,auto,anchor-rtl,480,auto,anchor,504,auto,link,528,auto,unlink,552,auto,bulletedlist-rtl,576,auto,bulletedlist,600,auto,numberedlist-rtl,624,auto,numberedlist,648,auto,maximize,672,auto,pastetext-rtl,696,auto,pastetext,720,auto,pastefromword-rtl,744,auto,pastefromword,768,auto,removeformat,792,auto,source-rtl,816,auto,source,840,auto,specialchar,864,auto,scayt,888,auto,table,912,auto,redo-rtl,936,auto,redo,960,auto,undo-rtl,984,auto,undo,1008,auto,spellchecker,1032,auto','icons.png');})();CKEDITOR.lang.languages={"af":1,"sq":1,"ar":1,"eu":1,"bn":1,"bs":1,"bg":1,"ca":1,"zh-cn":1,"zh":1,"hr":1,"cs":1,"da":1,"nl":1,"en":1,"en-au":1,"en-ca":1,"en-gb":1,"eo":1,"et":1,"fo":1,"fi":1,"fr":1,"fr-ca":1,"gl":1,"ka":1,"de":1,"el":1,"gu":1,"he":1,"hi":1,"hu":1,"is":1,"id":1,"it":1,"ja":1,"km":1,"ko":1,"ku":1,"lv":1,"lt":1,"mk":1,"ms":1,"mn":1,"no":1,"nb":1,"fa":1,"pl":1,"pt-br":1,"pt":1,"ro":1,"ru":1,"sr":1,"sr-latn":1,"si":1,"sk":1,"sl":1,"es":1,"sv":1,"tt":1,"th":1,"tr":1,"ug":1,"uk":1,"vi":1,"cy":1};}()); \ No newline at end of file
diff --git a/js/ckeditor/config.js b/js/ckeditor/config.js
new file mode 100644
index 0000000..9de2f1a
--- /dev/null
+++ b/js/ckeditor/config.js
@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+
+CKEDITOR.editorConfig = function( config ) {
+ // Define changes to default configuration here.
+ // For complete reference see:
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.config
+
+ // The toolbar groups arrangement, optimized for two toolbar rows.
+ config.toolbarGroups = [
+ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
+ { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
+ { name: 'links' },
+ { name: 'insert' },
+ { name: 'forms' },
+ { name: 'tools' },
+ { name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
+ { name: 'others' },
+ '/',
+ { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+ { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
+ { name: 'styles' },
+ { name: 'colors' },
+ { name: 'about' }
+ ];
+
+ // Remove some buttons provided by the standard plugins, which are
+ // not needed in the Standard(s) toolbar.
+ config.removeButtons = 'Underline,Subscript,Superscript';
+
+ // Set the most common block elements.
+ config.format_tags = 'p;h1;h2;h3;pre';
+
+ // Simplify the dialog windows.
+ config.removeDialogTabs = 'image:advanced;link:advanced';
+};
diff --git a/js/ckeditor/contents.css b/js/ckeditor/contents.css
new file mode 100644
index 0000000..b0efb0c
--- /dev/null
+++ b/js/ckeditor/contents.css
@@ -0,0 +1,134 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+
+body
+{
+ /* Font */
+ font-family: sans-serif, Arial, Verdana, "Trebuchet MS";
+ font-size: 12px;
+
+ /* Text color */
+ color: #333;
+
+ /* Remove the background color to make it transparent */
+ background-color: #fff;
+
+ margin: 20px;
+}
+
+.cke_editable
+{
+ font-size: 13px;
+ line-height: 1.6;
+}
+
+blockquote
+{
+ font-style: italic;
+ font-family: Georgia, Times, "Times New Roman", serif;
+ padding: 2px 0;
+ border-style: solid;
+ border-color: #ccc;
+ border-width: 0;
+}
+
+.cke_contents_ltr blockquote
+{
+ padding-left: 20px;
+ padding-right: 8px;
+ border-left-width: 5px;
+}
+
+.cke_contents_rtl blockquote
+{
+ padding-left: 8px;
+ padding-right: 20px;
+ border-right-width: 5px;
+}
+
+a
+{
+ color: #0782C1;
+}
+
+ol,ul,dl
+{
+ /* IE7: reset rtl list margin. (#7334) */
+ *margin-right: 0px;
+ /* preserved spaces for list items with text direction other than the list. (#6249,#8049)*/
+ padding: 0 40px;
+}
+
+h1,h2,h3,h4,h5,h6
+{
+ font-weight: normal;
+ line-height: 1.2;
+}
+
+hr
+{
+ border: 0px;
+ border-top: 1px solid #ccc;
+}
+
+img.right
+{
+ border: 1px solid #ccc;
+ float: right;
+ margin-left: 15px;
+ padding: 5px;
+}
+
+img.left
+{
+ border: 1px solid #ccc;
+ float: left;
+ margin-right: 15px;
+ padding: 5px;
+}
+
+pre
+{
+ white-space: pre-wrap; /* CSS 2.1 */
+ word-wrap: break-word; /* IE7 */
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ -webkit-tab-size: 4;
+ tab-size: 4;
+}
+
+.marker
+{
+ background-color: Yellow;
+}
+
+span[lang]
+{
+ font-style: italic;
+}
+
+figure
+{
+ text-align: center;
+ border: solid 1px #ccc;
+ border-radius: 2px;
+ background: rgba(0,0,0,0.05);
+ padding: 10px;
+ margin: 10px 20px;
+ display: inline-block;
+}
+
+figure > figcaption
+{
+ text-align: center;
+ display: block; /* For IE8 */
+}
+
+a > img {
+ padding: 1px;
+ margin: 1px;
+ border: none;
+ outline: 1px solid #0782C1;
+}
diff --git a/js/ckeditor/lang/af.js b/js/ckeditor/lang/af.js
new file mode 100644
index 0000000..c04d6d8
--- /dev/null
+++ b/js/ckeditor/lang/af.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['af']={"editor":"Woordverwerker","editorPanel":"Woordverwerkerpaneel","common":{"editorHelp":"Druk op ALT 0 vir hulp","browseServer":"Blaai op bediener","url":"URL","protocol":"Protokol","upload":"Oplaai","uploadSubmit":"Stuur aan die bediener","image":"Beeld","flash":"Flash","form":"Vorm","checkbox":"Merkhokkie","radio":"Radioknoppie","textField":"Teksveld","textarea":"Teksarea","hiddenField":"Versteekteveld","button":"Knop","select":"Keuseveld","imageButton":"Beeldknop","notSet":"<geen instelling>","id":"Id","name":"Naam","langDir":"Skryfrigting","langDirLtr":"Links na regs (LTR)","langDirRtl":"Regs na links (RTL)","langCode":"Taalkode","longDescr":"Lang beskrywing URL","cssClass":"CSS klasse","advisoryTitle":"Aanbevole titel","cssStyle":"Styl","ok":"OK","cancel":"Kanselleer","close":"Sluit","preview":"Voorbeeld","resize":"Skalierung","generalTab":"Algemeen","advancedTab":"Gevorderd","validateNumberFailed":"Hierdie waarde is nie 'n nommer nie.","confirmNewPage":"Alle wysiginge sal verlore gaan. Is jy seker dat jy 'n nuwe bladsy wil laai?","confirmCancel":"Sommige opsies is gewysig. Is jy seker dat jy hierdie dialoogvenster wil sluit?","options":"Opsies","target":"Teiken","targetNew":"Nuwe venster (_blank)","targetTop":"Boonste venster (_top)","targetSelf":"Selfde venster (_self)","targetParent":"Oorspronklike venster (_parent)","langDirLTR":"Links na Regs (LTR)","langDirRTL":"Regs na Links (RTL)","styles":"Styl","cssClasses":"CSS klasse","width":"Breedte","height":"Hoogte","align":"Orienteerung","alignLeft":"Links","alignRight":"Regs","alignCenter":"Middel","alignJustify":"Eweredig","alignTop":"Bo","alignMiddle":"Middel","alignBottom":"Onder","alignNone":"Geen","invalidValue":"Ongeldige waarde","invalidHeight":"Hoogte moet 'n getal wees","invalidWidth":"Breedte moet 'n getal wees.","invalidCssLength":"Die waarde vir die \"%1\" veld moet 'n posetiewe getal wees met of sonder 'n geldige CSS eenheid (px, %, in, cm, mm, em, ex, pt, of pc).","invalidHtmlLength":"Die waarde vir die \"%1\" veld moet 'n posetiewe getal wees met of sonder 'n geldige HTML eenheid (px of %).","invalidInlineStyle":"Ongeldige CSS. Formaat is een of meer sleutel-wert paare, \"naam : wert\" met kommapunte gesky.","cssLengthTooltip":"Voeg 'n getal wert in pixel in, of 'n waarde met geldige CSS eenheid (px, %, in, cm, mm, em, ex, pt, of pc).","unavailable":"%1<span class=\"cke_accessibility\">, nie beskikbaar nie</span>"},"about":{"copy":"Kopiereg &copy; $1. Alle regte voorbehou.","dlgTitle":"Meer oor CKEditor","help":"Slaan $1 na vir hulp.","moreInfo":"Vir lisensie-informasie, besoek asb. ons webwerf:","title":"Meer oor CKEditor","userGuide":"CKEditor Gebruikers gits"},"basicstyles":{"bold":"Vet","italic":"Skuins","strike":"Deurgestreep","subscript":"Onderskrif","superscript":"Bo-skrif","underline":"Onderstreep"},"blockquote":{"toolbar":"Sitaatblok"},"clipboard":{"copy":"Kopiëer","copyError":"U blaaier se sekuriteitsinstelling belet die kopiëringsaksie. Gebruik die sleutelbordkombinasie (Ctrl/Cmd+C).","cut":"Knip","cutError":"U blaaier se sekuriteitsinstelling belet die outomatiese knip-aksie. Gebruik die sleutelbordkombinasie (Ctrl/Cmd+X).","paste":"Plak","pasteArea":"Plak-area","pasteMsg":"Plak die teks in die volgende teks-area met die sleutelbordkombinasie (<STRONG>Ctrl/Cmd+V</STRONG>) en druk <STRONG>OK</STRONG>.","securityMsg":"Weens u blaaier se sekuriteitsinstelling is data op die knipbord nie toeganklik nie. U kan dit eers weer in hierdie venster plak.","title":"Byvoeg"},"contextmenu":{"options":"Konteks Spyskaart-opsies"},"button":{"selectedLabel":"%1 uitgekies"},"toolbar":{"toolbarCollapse":"Verklein werkbalk","toolbarExpand":"Vergroot werkbalk","toolbarGroups":{"document":"Dokument","clipboard":"Knipbord/Undo","editing":"Verander","forms":"Vorms","basicstyles":"Eenvoudige Styl","paragraph":"Paragraaf","links":"Skakels","insert":"Toevoeg","styles":"Style","colors":"Kleure","tools":"Gereedskap"},"toolbars":"Werkbalke"},"elementspath":{"eleLabel":"Elemente-pad","eleTitle":"%1 element"},"format":{"label":"Opmaak","panelTitle":"Opmaak","tag_address":"Adres","tag_div":"Normaal (DIV)","tag_h1":"Opskrif 1","tag_h2":"Opskrif 2","tag_h3":"Opskrif 3","tag_h4":"Opskrif 4","tag_h5":"Opskrif 5","tag_h6":"Opskrif 6","tag_p":"Normaal","tag_pre":"Opgemaak"},"horizontalrule":{"toolbar":"Horisontale lyn invoeg"},"image":{"alertUrl":"Gee URL van afbeelding.","alt":"Alternatiewe teks","border":"Rand","btnUpload":"Stuur na bediener","button2Img":"Wil u die geselekteerde afbeeldingsknop vervang met 'n eenvoudige afbeelding?","hSpace":"HSpasie","img2Button":"Wil u die geselekteerde afbeelding vervang met 'n afbeeldingsknop?","infoTab":"Afbeelding informasie","linkTab":"Skakel","lockRatio":"Vaste proporsie","menu":"Afbeelding eienskappe","resetSize":"Herstel grootte","title":"Afbeelding eienskappe","titleButton":"Afbeeldingsknop eienskappe","upload":"Oplaai","urlMissing":"Die URL na die afbeelding ontbreek.","vSpace":"VSpasie","validateBorder":"Rand moet 'n heelgetal wees.","validateHSpace":"HSpasie moet 'n heelgetal wees.","validateVSpace":"VSpasie moet 'n heelgetal wees."},"indent":{"indent":"Vergroot inspring","outdent":"Verklein inspring"},"fakeobjects":{"anchor":"Anker","flash":"Flash animasie","hiddenfield":"Verborge veld","iframe":"IFrame","unknown":"Onbekende objek"},"link":{"acccessKey":"Toegangsleutel","advanced":"Gevorderd","advisoryContentType":"Aanbevole inhoudstipe","advisoryTitle":"Aanbevole titel","anchor":{"toolbar":"Anker byvoeg/verander","menu":"Anker-eienskappe","title":"Anker-eienskappe","name":"Ankernaam","errorName":"Voltooi die ankernaam asseblief","remove":"Remove Anchor"},"anchorId":"Op element Id","anchorName":"Op ankernaam","charset":"Karakterstel van geskakelde bron","cssClasses":"CSS klasse","emailAddress":"E-posadres","emailBody":"Berig-inhoud","emailSubject":"Berig-onderwerp","id":"Id","info":"Skakel informasie","langCode":"Taalkode","langDir":"Skryfrigting","langDirLTR":"Links na regs (LTR)","langDirRTL":"Regs na links (RTL)","menu":"Wysig skakel","name":"Naam","noAnchors":"(Geen ankers beskikbaar in dokument)","noEmail":"Gee die e-posadres","noUrl":"Gee die skakel se URL","other":"<ander>","popupDependent":"Afhanklik (Netscape)","popupFeatures":"Eienskappe van opspringvenster","popupFullScreen":"Volskerm (IE)","popupLeft":"Posisie links","popupLocationBar":"Adresbalk","popupMenuBar":"Spyskaartbalk","popupResizable":"Herskaalbaar","popupScrollBars":"Skuifbalke","popupStatusBar":"Statusbalk","popupToolbar":"Werkbalk","popupTop":"Posisie bo","rel":"Relationship","selectAnchor":"Kies 'n anker","styles":"Styl","tabIndex":"Tab indeks","target":"Doel","targetFrame":"<raam>","targetFrameName":"Naam van doelraam","targetPopup":"<opspringvenster>","targetPopupName":"Naam van opspringvenster","title":"Skakel","toAnchor":"Anker in bladsy","toEmail":"E-pos","toUrl":"URL","toolbar":"Skakel invoeg/wysig","type":"Skakelsoort","unlink":"Verwyder skakel","upload":"Oplaai"},"list":{"bulletedlist":"Ongenommerde lys","numberedlist":"Genommerde lys"},"magicline":{"title":"Voeg paragraaf hier in"},"maximize":{"maximize":"Maksimaliseer","minimize":"Minimaliseer"},"pastetext":{"button":"Plak as eenvoudige teks","title":"Plak as eenvoudige teks"},"pastefromword":{"confirmCleanup":"Die teks wat u wil plak lyk asof dit uit Word gekopiëer is. Wil u dit eers skoonmaak voordat dit geplak word?","error":"Die geplakte teks kon nie skoongemaak word nie, weens 'n interne fout","title":"Plak vanuit Word","toolbar":"Plak vanuit Word"},"removeformat":{"toolbar":"Verwyder opmaak"},"sourcearea":{"toolbar":"Bron"},"specialchar":{"options":"Spesiale karakter-opsies","title":"Kies spesiale karakter","toolbar":"Voeg spesiaale karakter in"},"scayt":{"btn_about":"SCAYT info","btn_dictionaries":"Woordeboeke","btn_disable":"SCAYT af","btn_enable":"SCAYT aan","btn_langs":"Tale","btn_options":"Opsies","text_title":"Speltoets terwyl u tik"},"stylescombo":{"label":"Styl","panelTitle":"Vormaat style","panelTitle1":"Blok style","panelTitle2":"Inlyn style","panelTitle3":"Objek style"},"table":{"border":"Randbreedte","caption":"Naam","cell":{"menu":"Sel","insertBefore":"Voeg sel in voor","insertAfter":"Voeg sel in na","deleteCell":"Verwyder sel","merge":"Voeg selle saam","mergeRight":"Voeg saam na regs","mergeDown":"Voeg saam ondertoe","splitHorizontal":"Splits sel horisontaal","splitVertical":"Splits sel vertikaal","title":"Sel eienskappe","cellType":"Sel tipe","rowSpan":"Omspan rye","colSpan":"Omspan kolomme","wordWrap":"Woord terugloop","hAlign":"Horisontale oplyning","vAlign":"Vertikale oplyning","alignBaseline":"Basislyn","bgColor":"Agtergrondkleur","borderColor":"Randkleur","data":"Inhoud","header":"Opskrif","yes":"Ja","no":"Nee","invalidWidth":"Selbreedte moet 'n getal wees.","invalidHeight":"Selhoogte moet 'n getal wees.","invalidRowSpan":"Omspan rye moet 'n heelgetal wees.","invalidColSpan":"Omspan kolomme moet 'n heelgetal wees.","chooseColor":"Kies"},"cellPad":"Sel-spasie","cellSpace":"Sel-afstand","column":{"menu":"Kolom","insertBefore":"Voeg kolom in voor","insertAfter":"Voeg kolom in na","deleteColumn":"Verwyder kolom"},"columns":"Kolomme","deleteTable":"Verwyder tabel","headers":"Opskrifte","headersBoth":"Beide ","headersColumn":"Eerste kolom","headersNone":"Geen","headersRow":"Eerste ry","invalidBorder":"Randbreedte moet 'n getal wees.","invalidCellPadding":"Sel-spasie moet 'n getal wees.","invalidCellSpacing":"Sel-afstand moet 'n getal wees.","invalidCols":"Aantal kolomme moet 'n getal groter as 0 wees.","invalidHeight":"Tabelhoogte moet 'n getal wees.","invalidRows":"Aantal rye moet 'n getal groter as 0 wees.","invalidWidth":"Tabelbreedte moet 'n getal wees.","menu":"Tabel eienskappe","row":{"menu":"Ry","insertBefore":"Voeg ry in voor","insertAfter":"Voeg ry in na","deleteRow":"Verwyder ry"},"rows":"Rye","summary":"Opsomming","title":"Tabel eienskappe","toolbar":"Tabel","widthPc":"persent","widthPx":"piksels","widthUnit":"breedte-eenheid"},"undo":{"redo":"Oordoen","undo":"Ontdoen"},"wsc":{"btnIgnore":"Ignoreer","btnIgnoreAll":"Ignoreer alles","btnReplace":"Vervang","btnReplaceAll":"vervang alles","btnUndo":"Ontdoen","changeTo":"Verander na","errorLoading":"Fout by inlaai van diens: %s.","ieSpellDownload":"Speltoetser is nie geïnstalleer nie. Wil u dit nou aflaai?","manyChanges":"Klaar met speltoets: %1 woorde verander","noChanges":"Klaar met speltoets: Geen woorde verander nie","noMispell":"Klaar met speltoets: Geen foute nie","noSuggestions":"- Geen voorstel -","notAvailable":"Jammer, hierdie diens is nie nou beskikbaar nie.","notInDic":"Nie in woordeboek nie","oneChange":"Klaar met speltoets: Een woord verander","progress":"Spelling word getoets...","title":"Speltoetser","toolbar":"Speltoets"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ar.js b/js/ckeditor/lang/ar.js
new file mode 100644
index 0000000..62d097c
--- /dev/null
+++ b/js/ckeditor/lang/ar.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ar']={"editor":"محرر النص الغني","editorPanel":"لائحة محرر النص المنسق","common":{"editorHelp":"إضغط على ALT + 0 للحصول على المساعدة.","browseServer":"تصÙØ­","url":"الرابط","protocol":"البروتوكول","upload":"رÙع","uploadSubmit":"أرسل","image":"صورة","flash":"Ùلاش","form":"نموذج","checkbox":"خانة إختيار","radio":"زر اختيار","textField":"مربع نص","textarea":"مساحة نصية","hiddenField":"إدراج حقل Ø®ÙÙŠ","button":"زر ضغط","select":"اختار","imageButton":"زر صورة","notSet":"<بدون تحديد>","id":"الرقم","name":"إسم","langDir":"إتجاه النص","langDirLtr":"اليسار لليمين (LTR)","langDirRtl":"اليمين لليسار (RTL)","langCode":"رمز اللغة","longDescr":"الوص٠التÙصيلى","cssClass":"Ùئات التنسيق","advisoryTitle":"عنوان التقرير","cssStyle":"نمط","ok":"مواÙÙ‚","cancel":"إلغاء الأمر","close":"أغلق","preview":"استعراض","resize":"تغيير الحجم","generalTab":"عام","advancedTab":"متقدم","validateNumberFailed":"لايوجد نتيجة","confirmNewPage":"ستÙقد أي متغييرات اذا لم تقم بحÙظها اولا. هل أنت متأكد أنك تريد صÙحة جديدة؟","confirmCancel":"بعض الخيارات قد تغيرت. هل أنت متأكد من إغلاق مربع النص؟","options":"خيارات","target":"هد٠الرابط","targetNew":"ناÙذة جديدة","targetTop":"الناÙذة الأعلى","targetSelf":"داخل الناÙذة","targetParent":"الناÙذة الأم","langDirLTR":"اليسار لليمين (LTR)","langDirRTL":"اليمين لليسار (RTL)","styles":"نمط","cssClasses":"Ùئات التنسيق","width":"العرض","height":"الإرتÙاع","align":"محاذاة","alignLeft":"يسار","alignRight":"يمين","alignCenter":"وسط","alignJustify":"ضبط","alignTop":"أعلى","alignMiddle":"وسط","alignBottom":"أسÙÙ„","alignNone":"None","invalidValue":"قيمة غير Ù…Ùبولة.","invalidHeight":"الارتÙاع يجب أن يكون عدداً.","invalidWidth":"العرض يجب أن يكون عدداً.","invalidCssLength":"قيمة الخانة المخصصة لـ \"%1\" يجب أن تكون رقما موجبا، باستخدام أو من غير استخدام وحدة CSS قياس مقبولة (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"قيمة الخانة المخصصة لـ \"%1\" يجب أن تكون رقما موجبا، باستخدام أو من غير استخدام وحدة HTML قياس مقبولة (px or %).","invalidInlineStyle":"قيمة الخانة المخصصة لـ Inline Style يجب أن تختوي على مجموع واحد أو أكثر بالشكل التالي: \"name : value\", Ù…Ùصولة بÙاصلة منقزطة.","cssLengthTooltip":"أدخل رقما للقيمة بالبكسل أو رقما بوحدة CSS مقبولة (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, غير متاح</span>"},"about":{"copy":"حقوق النشر &copy; $1. جميع الحقوق محÙوظة.","dlgTitle":"عن CKEditor","help":"راجع $1 من أجل المساعدة","moreInfo":"للحصول على معلومات الترخيص ØŒ يرجى زيارة موقعنا:","title":"عن CKEditor","userGuide":"دليل مستخدم CKEditor."},"basicstyles":{"bold":"عريض","italic":"مائل","strike":"يتوسطه خط","subscript":"منخÙض","superscript":"مرتÙع","underline":"تسطير"},"blockquote":{"toolbar":"اقتباس"},"clipboard":{"copy":"نسخ","copyError":"الإعدادات الأمنية للمتصÙØ­ الذي تستخدمه تمنع عمليات النسخ التلقائي. Ùضلاً إستخدم لوحة المÙاتيح Ù„Ùعل ذلك (Ctrl/Cmd+C).","cut":"قص","cutError":"الإعدادات الأمنية للمتصÙØ­ الذي تستخدمه تمنع القص التلقائي. Ùضلاً إستخدم لوحة المÙاتيح Ù„Ùعل ذلك (Ctrl/Cmd+X).","paste":"لصق","pasteArea":"منطقة اللصق","pasteMsg":"الصق داخل الصندوق بإستخدام زرائر (<STRONG>Ctrl/Cmd+V</STRONG>) ÙÙŠ لوحة المÙاتيح، ثم اضغط زر <STRONG>مواÙÙ‚</STRONG>.","securityMsg":"نظراً لإعدادات الأمان الخاصة بمتصÙحك، لن يتمكن هذا المحرر من الوصول لمحتوى حاÙظتك، لذلك يجب عليك لصق المحتوى مرة أخرى ÙÙŠ هذه الناÙذة.","title":"لصق"},"contextmenu":{"options":"خصائص قائمة السياق"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"تقليص شريط الأدوت","toolbarExpand":"تمديد شريط الأدوات","toolbarGroups":{"document":"مستند","clipboard":"الحاÙظة/الرجوع","editing":"تحرير","forms":"نماذج","basicstyles":"نمط بسيط","paragraph":"Ùقرة","links":"روابط","insert":"إدراج","styles":"أنماط","colors":"ألوان","tools":"أدوات"},"toolbars":"أشرطة أدوات المحرر"},"elementspath":{"eleLabel":"مسار العنصر","eleTitle":"عنصر 1%"},"format":{"label":"تنسيق","panelTitle":"تنسيق الÙقرة","tag_address":"عنوان","tag_div":"عادي (DIV)","tag_h1":"العنوان 1","tag_h2":"العنوان 2","tag_h3":"العنوان 3","tag_h4":"العنوان 4","tag_h5":"العنوان 5","tag_h6":"العنوان 6","tag_p":"عادي","tag_pre":"منسّق"},"horizontalrule":{"toolbar":"خط Ùاصل"},"image":{"alertUrl":"Ùضلاً أكتب الموقع الذي توجد عليه هذه الصورة.","alt":"عنوان الصورة","border":"سمك الحدود","btnUpload":"أرسلها للخادم","button2Img":"هل تريد تحويل زر الصورة المختار إلى صورة بسيطة؟","hSpace":"تباعد Ø£Ùقي","img2Button":"هل تريد تحويل الصورة المختارة إلى زر صورة؟","infoTab":"معلومات الصورة","linkTab":"الرابط","lockRatio":"تناسق الحجم","menu":"خصائص الصورة","resetSize":"إستعادة الحجم الأصلي","title":"خصائص الصورة","titleButton":"خصائص زر الصورة","upload":"رÙع","urlMissing":"عنوان مصدر الصورة Ù…Ùقود","vSpace":"تباعد عمودي","validateBorder":"الإطار يجب أن يكون عددا","validateHSpace":"HSpace يجب أن يكون عدداً.","validateVSpace":"VSpace يجب أن يكون عدداً."},"indent":{"indent":"زيادة المساÙØ© البادئة","outdent":"إنقاص المساÙØ© البادئة"},"fakeobjects":{"anchor":"إرساء","flash":"رسم متحرك بالÙلاش","hiddenfield":"إدراج حقل Ø®ÙÙŠ","iframe":"iframe","unknown":"عنصر غير معروÙ"},"link":{"acccessKey":"Ù…Ùاتيح الإختصار","advanced":"متقدم","advisoryContentType":"نوع التقرير","advisoryTitle":"عنوان التقرير","anchor":{"toolbar":"إشارة مرجعية","menu":"تحرير الإشارة المرجعية","title":"خصائص الإشارة المرجعية","name":"اسم الإشارة المرجعية","errorName":"الرجاء كتابة اسم الإشارة المرجعية","remove":"إزالة الإشارة المرجعية"},"anchorId":"حسب رقم العنصر","anchorName":"حسب إسم الإشارة المرجعية","charset":"ترميز المادة المطلوبة","cssClasses":"Ùئات التنسيق","emailAddress":"البريد الإلكتروني","emailBody":"محتوى الرسالة","emailSubject":"موضوع الرسالة","id":"هوية","info":"معلومات الرابط","langCode":"رمز اللغة","langDir":"إتجاه نص اللغة","langDirLTR":"اليسار لليمين (LTR)","langDirRTL":"اليمين لليسار (RTL)","menu":"تحرير الرابط","name":"إسم","noAnchors":"(لا توجد علامات مرجعية ÙÙŠ هذا المستند)","noEmail":"الرجاء كتابة الريد الإلكتروني","noUrl":"الرجاء كتابة رابط الموقع","other":"<أخرى>","popupDependent":"تابع (Netscape)","popupFeatures":"خصائص الناÙذة المنبثقة","popupFullScreen":"ملئ الشاشة (IE)","popupLeft":"التمركز لليسار","popupLocationBar":"شريط العنوان","popupMenuBar":"القوائم الرئيسية","popupResizable":"قابلة التشكيل","popupScrollBars":"أشرطة التمرير","popupStatusBar":"شريط الحالة","popupToolbar":"شريط الأدوات","popupTop":"التمركز للأعلى","rel":"العلاقة","selectAnchor":"اختر علامة مرجعية","styles":"نمط","tabIndex":"الترتيب","target":"هد٠الرابط","targetFrame":"<إطار>","targetFrameName":"اسم الإطار المستهدÙ","targetPopup":"<ناÙذة منبثقة>","targetPopupName":"اسم الناÙذة المنبثقة","title":"رابط","toAnchor":"مكان ÙÙŠ هذا المستند","toEmail":"بريد إلكتروني","toUrl":"الرابط","toolbar":"رابط","type":"نوع الربط","unlink":"إزالة رابط","upload":"رÙع"},"list":{"bulletedlist":"ادخال/حذ٠تعداد نقطي","numberedlist":"ادخال/حذ٠تعداد رقمي"},"magicline":{"title":"إدراج Ùقرة هنا"},"maximize":{"maximize":"تكبير","minimize":"تصغير"},"pastetext":{"button":"لصق كنص بسيط","title":"لصق كنص بسيط"},"pastefromword":{"confirmCleanup":"يبدو أن النص المراد لصقه منسوخ من برنامج وورد. هل تود تنظيÙÙ‡ قبل الشروع ÙÙŠ عملية اللصق؟","error":"لم يتم مسح المعلومات الملصقة لخلل داخلي","title":"لصق من وورد","toolbar":"لصق من وورد"},"removeformat":{"toolbar":"إزالة التنسيقات"},"sourcearea":{"toolbar":"المصدر"},"specialchar":{"options":"خيارات الأحر٠الخاصة","title":"اختر حر٠خاص","toolbar":"إدراج حر٠خاص"},"scayt":{"btn_about":"عن SCAYT","btn_dictionaries":"قواميس","btn_disable":"تعطيل SCAYT","btn_enable":"تÙعيل SCAYT","btn_langs":"لغات","btn_options":"خيارات","text_title":"تدقيق إملائي أثناء الكتابة"},"stylescombo":{"label":"أنماط","panelTitle":"أنماط التنسيق","panelTitle1":"أنماط الÙقرة","panelTitle2":"أنماط مضمنة","panelTitle3":"أنماط الكائن"},"table":{"border":"الحدود","caption":"الوصÙ","cell":{"menu":"خلية","insertBefore":"إدراج خلية قبل","insertAfter":"إدراج خلية بعد","deleteCell":"حذ٠خلية","merge":"دمج خلايا","mergeRight":"دمج لليمين","mergeDown":"دمج للأسÙÙ„","splitHorizontal":"تقسيم الخلية Ø£Ùقياً","splitVertical":"تقسيم الخلية عمودياً","title":"خصائص الخلية","cellType":"نوع الخلية","rowSpan":"امتداد الصÙÙˆÙ","colSpan":"امتداد الأعمدة","wordWrap":"التÙا٠النص","hAlign":"محاذاة Ø£Ùقية","vAlign":"محاذاة رأسية","alignBaseline":"خط القاعدة","bgColor":"لون الخلÙية","borderColor":"لون الحدود","data":"بيانات","header":"عنوان","yes":"نعم","no":"لا","invalidWidth":"عرض الخلية يجب أن يكون عدداً.","invalidHeight":"ارتÙاع الخلية يجب أن يكون عدداً.","invalidRowSpan":"امتداد الصÙو٠يجب أن يكون عدداً صحيحاً.","invalidColSpan":"امتداد الأعمدة يجب أن يكون عدداً صحيحاً.","chooseColor":"اختر"},"cellPad":"المساÙØ© البادئة","cellSpace":"تباعد الخلايا","column":{"menu":"عمود","insertBefore":"إدراج عمود قبل","insertAfter":"إدراج عمود بعد","deleteColumn":"حذ٠أعمدة"},"columns":"أعمدة","deleteTable":"حذ٠الجدول","headers":"العناوين","headersBoth":"كلاهما","headersColumn":"العمود الأول","headersNone":"بدون","headersRow":"الص٠الأول","invalidBorder":"حجم الحد يجب أن يكون عدداً.","invalidCellPadding":"المساÙØ© البادئة يجب أن تكون عدداً","invalidCellSpacing":"المساÙØ© بين الخلايا يجب أن تكون عدداً.","invalidCols":"عدد الأعمدة يجب أن يكون عدداً أكبر من صÙر.","invalidHeight":"ارتÙاع الجدول يجب أن يكون عدداً.","invalidRows":"عدد الصÙو٠يجب أن يكون عدداً أكبر من صÙر.","invalidWidth":"عرض الجدول يجب أن يكون عدداً.","menu":"خصائص الجدول","row":{"menu":"صÙ","insertBefore":"إدراج ص٠قبل","insertAfter":"إدراج ص٠بعد","deleteRow":"حذ٠صÙÙˆÙ"},"rows":"صÙÙˆÙ","summary":"الخلاصة","title":"خصائص الجدول","toolbar":"جدول","widthPc":"بالمئة","widthPx":"بكسل","widthUnit":"وحدة العرض"},"undo":{"redo":"إعادة","undo":"تراجع"},"wsc":{"btnIgnore":"تجاهل","btnIgnoreAll":"تجاهل الكل","btnReplace":"تغيير","btnReplaceAll":"تغيير الكل","btnUndo":"تراجع","changeTo":"التغيير إلى","errorLoading":"خطأ ÙÙŠ تحميل تطبيق خدمة الاستضاÙØ©: %s.","ieSpellDownload":"المدقق الإملائي (الإنجليزي) غير مثبّت. هل تود تحميله الآن؟","manyChanges":"تم إكمال التدقيق الإملائي: تم تغيير %1 من كلمات","noChanges":"تم التدقيق الإملائي: لم يتم تغيير أي كلمة","noMispell":"تم التدقيق الإملائي: لم يتم العثور على أي أخطاء إملائية","noSuggestions":"- لا توجد إقتراحات -","notAvailable":"عÙواً، ولكن هذه الخدمة غير متاحة الان","notInDic":"ليست ÙÙŠ القاموس","oneChange":"تم التدقيق الإملائي: تم تغيير كلمة واحدة Ùقط","progress":"جاري التدقيق الاملائى","title":"التدقيق الإملائي","toolbar":"تدقيق إملائي"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/bg.js b/js/ckeditor/lang/bg.js
new file mode 100644
index 0000000..e5aaff2
--- /dev/null
+++ b/js/ckeditor/lang/bg.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['bg']={"editor":"ТекÑтов редактор за форматиран текÑÑ‚","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"натиÑнете ALT 0 за помощ","browseServer":"Избор от Ñървъра","url":"URL","protocol":"Протокол","upload":"Качване","uploadSubmit":"Изпращане към Ñървъра","image":"Снимка","flash":"Флаш","form":"Форма","checkbox":"Поле за избор","radio":"Радио бутон","textField":"ТекÑтово поле","textarea":"ТекÑтова зона","hiddenField":"Скрито поле","button":"Бутон","select":"Поле за избор","imageButton":"Бутон за Ñнимка","notSet":"<не е избрано>","id":"ID","name":"Име","langDir":"ПоÑока на езика","langDirLtr":"ЛÑво на дÑÑно (ЛнД)","langDirRtl":"ДÑÑно на лÑво (ДнЛ)","langCode":"Код на езика","longDescr":"Уеб Ð°Ð´Ñ€ÐµÑ Ð·Ð° дълго опиÑание","cssClass":"КлаÑове за CSS","advisoryTitle":"Препоръчително заглавие","cssStyle":"Стил","ok":"ОК","cancel":"Отказ","close":"Затвори","preview":"Преглед","resize":"Влачете за да оразмерите","generalTab":"Общи","advancedTab":"Разширено","validateNumberFailed":"Тази ÑтойноÑÑ‚ не е чиÑло","confirmNewPage":"Ð’Ñички незапазени промени ще бъдат изгубени. Сигурни ли Ñте, че желаете да заредите нова Ñтраница?","confirmCancel":"ÐÑкои от опциите Ñа променени. Сигурни ли Ñте, че желаете да затворите прозореца?","options":"Опции","target":"Цел","targetNew":"Ðов прозорец (_blank)","targetTop":"Горна Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ (_top)","targetSelf":"Ð¢ÐµÐºÑƒÑ‰Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ† (_self)","targetParent":"ОÑновен прозорец (_parent)","langDirLTR":"ЛÑво на дÑÑно (ЛнД)","langDirRTL":"ДÑÑно на лÑво (ДнЛ)","styles":"Стил","cssClasses":"КлаÑове за CSS","width":"Ширина","height":"ВиÑочина","align":"ПодравнÑване","alignLeft":"ЛÑво","alignRight":"ДÑÑно","alignCenter":"Център","alignJustify":"ДвуÑтранно подравнÑване","alignTop":"Горе","alignMiddle":"По Ñредата","alignBottom":"Долу","alignNone":"None","invalidValue":"Ðевалидна ÑтойноÑÑ‚.","invalidHeight":"ВиÑочината Ñ‚Ñ€Ñбва да е чиÑло.","invalidWidth":"Ширина требе да е чиÑло.","invalidCssLength":"СтойноÑтта на полето \"%1\" Ñ‚Ñ€Ñбва да бъде положително чиÑло Ñ Ð¸Ð»Ð¸ без валидна CSS измервателна единица (px, %, in, cm, mm, em, ex, pt, или pc).","invalidHtmlLength":"СтойноÑтта на полето \"%1\" Ñ‚Ñ€Ñбва да бъде положително чиÑло Ñ Ð¸Ð»Ð¸ без валидна HTML измервателна единица (px или %).","invalidInlineStyle":"СтойноÑтта на Ñтилa Ñ‚Ñ€Ñбва да Ñъдържат една или повече двойки във формат \"name : value\", разделени Ñ Ð´Ð²Ð¾ÐµÑ‚Ð¾Ñ‡Ð¸Ðµ.","cssLengthTooltip":"Въведете чиÑлена ÑтойноÑÑ‚ в пикÑели или друга валидна CSS единица (px, %, in, cm, mm, em, ex, pt, или pc).","unavailable":"%1<span class=\"cke_accessibility\">, недоÑтъпно</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"ОтноÑно CKEditor","help":"Проверете $1 за помощ.","moreInfo":"За лицензионна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð»Ñ Ð¿Ð¾Ñетете Ñайта ни:","title":"ОтноÑно CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Удебелен","italic":"Ðаклонен","strike":"Зачертан текÑÑ‚","subscript":"ИндекÑиран текÑÑ‚","superscript":"СуперÑкрипт","underline":"Подчертан"},"blockquote":{"toolbar":"Блок за цитат"},"clipboard":{"copy":"Копирай","copyError":"ÐаÑтройките за ÑигурноÑÑ‚ на Ð²Ð°ÑˆÐ¸Ñ Ð±Ñ€Ð°Ð·ÑƒÑŠÑ€ не разрешават на редактора да изпълни запаметÑването. За целта използвайте клавиатурата (Ctrl/Cmd+C).","cut":"Отрежи","cutError":"ÐаÑтройките за ÑигурноÑÑ‚ на Ð’Ð°ÑˆÐ¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÑŠÑ€ не позволÑват на редактора автоматично да изъплни дейÑтвиÑта за отрÑзване. ÐœÐ¾Ð»Ñ Ð¿Ð¾Ð»Ð·Ð²Ð°Ð¹Ñ‚Ðµ клавиатурните команди за целта (ctrl+x).","paste":"Вмъкни","pasteArea":"Зона за вмъкване","pasteMsg":"Вмъкнете тук Ñъдъжанието Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÐ°Ñ€Ð°Ñ‚Ð° (<STRONG>Ctrl/Cmd+V</STRONG>) и натиÑнете <STRONG>OK</STRONG>.","securityMsg":"Заради наÑтройките за ÑигурноÑÑ‚ на Ð’Ð°ÑˆÐ¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÑŠÑ€, редакторът не може да прочете данните от клипборда коректно.","title":"Вмъкни"},"contextmenu":{"options":"Опции на контекÑтното меню"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Свиване на лентата Ñ Ð¸Ð½Ñтрументи","toolbarExpand":"РазширÑване на лентата Ñ Ð¸Ð½Ñтрументи","toolbarGroups":{"document":"Документ","clipboard":"Clipboard/Undo","editing":"ПромÑна","forms":"Форми","basicstyles":"Базови Ñтилове","paragraph":"Параграф","links":"Връзки","insert":"Вмъкване","styles":"Стилове","colors":"Цветове","tools":"ИнÑтрументи"},"toolbars":"Ленти Ñ Ð¸Ð½Ñтрументи"},"elementspath":{"eleLabel":"Път за елементите","eleTitle":"%1 елемент"},"format":{"label":"Формат","panelTitle":"Формат","tag_address":"ÐдреÑ","tag_div":"Параграф (DIV)","tag_h1":"Заглавие 1","tag_h2":"Заглавие 2","tag_h3":"Заглавие 3","tag_h4":"Заглавие 4","tag_h5":"Заглавие 5","tag_h6":"Заглавие 6","tag_p":"Ðормален","tag_pre":"Форматиран"},"horizontalrule":{"toolbar":"Вмъкване на хоризонтална линиÑ"},"image":{"alertUrl":"МолÑ, въведете Ð¿ÑŠÐ»Ð½Ð¸Ñ Ð¿ÑŠÑ‚ до изображението","alt":"Ðлтернативен текÑÑ‚","border":"Рамка","btnUpload":"Изпрати Ñ Ð½Ð° Ñървъра","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"Хоризонтален отÑтъп","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Инфо за Ñнимка","linkTab":"Връзка","lockRatio":"Заключване на Ñъотношението","menu":"ÐаÑтройки за Ñнимка","resetSize":"Ðулиране на размер","title":"ÐаÑтройки за Ñнимка","titleButton":"ÐаÑтойки за бутон за Ñнимка","upload":"Качване","urlMissing":"Image source URL is missing.","vSpace":"Вертикален отÑтъп","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Увеличаване на отÑтъпа","outdent":"ÐамалÑване на отÑтъпа"},"fakeobjects":{"anchor":"Кука","flash":"Флаш анимациÑ","hiddenfield":"Скрито поле","iframe":"IFrame","unknown":"ÐеизвеÑтен обект"},"link":{"acccessKey":"Ключ за доÑтъп","advanced":"Разширено","advisoryContentType":"Препоръчителен тип на Ñъдържанието","advisoryTitle":"Препоръчително заглавие","anchor":{"toolbar":"Котва","menu":"ПромÑна на котва","title":"ÐаÑтройки на котва","name":"Име на котва","errorName":"ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ име на котвата","remove":"Премахване на котва"},"anchorId":"По ID на елемент","anchorName":"По име на котва","charset":"Тип на ÑÐ²ÑŠÑ€Ð·Ð°Ð½Ð¸Ñ Ñ€ÐµÑурÑ","cssClasses":"КлаÑове за CSS","emailAddress":"E-mail aдреÑ","emailBody":"Съдържание","emailSubject":"Тема","id":"ID","info":"Инфо за връзката","langCode":"Код за езика","langDir":"ПоÑока на езика","langDirLTR":"ЛÑво на ДÑÑно (ЛнД)","langDirRTL":"ДÑÑно на ЛÑво (ДнЛ)","menu":"ПромÑна на връзка","name":"Име","noAnchors":"(ÐÑма котви в Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚)","noEmail":"ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ e-mail aдреÑ","noUrl":"ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ URL адреÑа","other":"<друго>","popupDependent":"ЗавиÑимоÑÑ‚ (Netscape)","popupFeatures":"Функции на изкачащ прозорец","popupFullScreen":"ЦÑл екран (IE)","popupLeft":"ЛÑва позициÑ","popupLocationBar":"Лента Ñ Ð»Ð¾ÐºÐ°Ñ†Ð¸Ñта","popupMenuBar":"Лента за меню","popupResizable":"ОразмерÑем","popupScrollBars":"Скролери","popupStatusBar":"СтатуÑна лента","popupToolbar":"Лента Ñ Ð¸Ð½Ñтрументи","popupTop":"Горна позициÑ","rel":"Връзка","selectAnchor":"Изберете котва","styles":"Стил","tabIndex":"Ред на доÑтъп","target":"Цел","targetFrame":"<frame>","targetFrameName":"Име на целевиÑÑ‚ прозорец","targetPopup":"<изкачащ прозорец>","targetPopupName":"Име на изкачащ прозорец","title":"Връзка","toAnchor":"Връзка към котва в текÑта","toEmail":"E-mail","toUrl":"Уеб адреÑ","toolbar":"Връзка","type":"Тип на връзката","unlink":"Премахни връзката","upload":"Качване"},"list":{"bulletedlist":"Вмъкване/Премахване на точков ÑпиÑък","numberedlist":"Вмъкване/Премахване на номериран ÑпиÑък"},"magicline":{"title":"Вмъкнете параграф тук"},"maximize":{"maximize":"МакÑимизиране","minimize":"Минимизиране"},"pastetext":{"button":"Вмъкни като чиÑÑ‚ текÑÑ‚","title":"Вмъкни като чиÑÑ‚ текÑÑ‚"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Вмъкни от MS Word","toolbar":"Вмъкни от MS Word"},"removeformat":{"toolbar":"Премахване на форматирането"},"sourcearea":{"toolbar":"Източник"},"specialchar":{"options":"Опции за Ñпециален знак","title":"Избор на Ñпециален знак","toolbar":"Вмъкване на Ñпециален знак"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Речници","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Стилове","panelTitle":"Стилове за форматиране","panelTitle1":"Блокови Ñтилове","panelTitle2":"Вътрешни Ñтилове","panelTitle3":"Обектни Ñтилове"},"table":{"border":"Размер на рамката","caption":"Заглавие","cell":{"menu":"Клетка","insertBefore":"Вмъкване на клетка преди","insertAfter":"Вмъкване на клетка Ñлед","deleteCell":"Изтриване на клетки","merge":"Сливане на клетки","mergeRight":"Сливане в дÑÑно","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"ÐаÑтройки на клетката","cellType":"Тип на клетката","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Ðвто. преноÑ","hAlign":"Хоризонтално подравнÑване","vAlign":"Вертикално подравнÑване","alignBaseline":"Базова линиÑ","bgColor":"Фон","borderColor":"ЦвÑÑ‚ на рамката","data":"Данни","header":"Хедър","yes":"Да","no":"Ðе","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Изберете"},"cellPad":"ОтделÑне на клетките","cellSpace":"РазтоÑние между клетките","column":{"menu":"Колона","insertBefore":"Вмъкване на колона преди","insertAfter":"Вмъкване на колона Ñлед","deleteColumn":"Изтриване на колони"},"columns":"Колони","deleteTable":"Изтриване на таблица","headers":"Хедъри","headersBoth":"Заедно","headersColumn":"Първа колона","headersNone":"ÐÑма","headersRow":"Първи ред","invalidBorder":"Размерът на рамката Ñ‚Ñ€Ñбва да е чиÑло.","invalidCellPadding":"ОтÑтоÑнието на клетките Ñ‚Ñ€Ñбва да е позитивно чиÑло.","invalidCellSpacing":"Интервала в клетките Ñ‚Ñ€Ñбва да е позитивно чиÑло.","invalidCols":"БроÑÑ‚ колони Ñ‚Ñ€Ñбва да е по-голÑм от 0.","invalidHeight":"ВиÑочината на таблицата Ñ‚Ñ€Ñбва да е чиÑло.","invalidRows":"БроÑÑ‚ редове Ñ‚Ñ€Ñбва да е по-голÑм от 0.","invalidWidth":"Ширината на таблицата Ñ‚Ñ€Ñбва да е чиÑло.","menu":"ÐаÑтройки на таблицата","row":{"menu":"Ред","insertBefore":"Вмъкване на ред преди","insertAfter":"Вмъкване на ред Ñлед","deleteRow":"Изтриване на редове"},"rows":"Редове","summary":"Обща информациÑ","title":"ÐаÑтройки на таблицата","toolbar":"Таблица","widthPc":"процент","widthPx":"пикÑела","widthUnit":"единица за ширина"},"undo":{"redo":"Връщане на предишен ÑтатуÑ","undo":"Възтанови"},"wsc":{"btnIgnore":"Игнорирай","btnIgnoreAll":"Игнорирай вÑичко","btnReplace":"Препокриване","btnReplaceAll":"Препокрий вÑичко","btnUndo":"Възтанови","changeTo":"Промени на","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- ÐÑма препоръчани -","notAvailable":"СъжалÑваме, но уÑлугата не е доÑтъпна за момента","notInDic":"Ðе е в речника","oneChange":"Spell check complete: One word changed","progress":"ПроверÑва Ñе правопиÑа...","title":"Проверка на правопиÑ","toolbar":"Проверка на правопиÑ"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/bn.js b/js/ckeditor/lang/bn.js
new file mode 100644
index 0000000..2749a6a
--- /dev/null
+++ b/js/ckeditor/lang/bn.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['bn']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"বà§à¦°à¦¾à¦‰à¦œ সারà§à¦­à¦¾à¦°","url":"URL","protocol":"পà§à¦°à§‹à¦Ÿà§‹à¦•à¦²","upload":"আপলোড","uploadSubmit":"ইহাকে সারà§à¦­à¦¾à¦°à§‡ পà§à¦°à§‡à¦°à¦¨ কর","image":"ছবির লেবেল যà§à¦•à§à¦¤ কর","flash":"ফà§à¦²à¦¾à¦¶ লেবেল যà§à¦•à§à¦¤ কর","form":"ফরà§à¦®","checkbox":"চেক বাকà§à¦¸","radio":"রেডিও বাটন","textField":"টেকà§à¦¸à¦Ÿ ফীলà§à¦¡","textarea":"টেকà§à¦¸à¦Ÿ à¦à¦°à¦¿à§Ÿà¦¾","hiddenField":"গà§à¦ªà§à¦¤ ফীলà§à¦¡","button":"বাটন","select":"বাছাই ফীলà§à¦¡","imageButton":"ছবির বাটন","notSet":"<সেট নেই>","id":"আইডি","name":"নাম","langDir":"ভাষা লেখার দিক","langDirLtr":"বাম থেকে ডান (LTR)","langDirRtl":"ডান থেকে বাম (RTL)","langCode":"ভাষা কোড","longDescr":"URL à¦à¦° লমà§à¦¬à¦¾ বরà§à¦£à¦¨à¦¾","cssClass":"সà§à¦Ÿà¦¾à¦‡à¦²-শীট কà§à¦²à¦¾à¦¸","advisoryTitle":"পরামরà§à¦¶ শীরà§à¦·à¦•","cssStyle":"সà§à¦Ÿà¦¾à¦‡à¦²","ok":"ওকে","cancel":"বাতিল","close":"Close","preview":"পà§à¦°à¦¿à¦­à¦¿à¦‰","resize":"Resize","generalTab":"General","advancedTab":"à¦à¦¡à¦­à¦¾à¦¨à§à¦¸à¦¡","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"টারà§à¦—েট","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"বাম থেকে ডান (LTR)","langDirRTL":"ডান থেকে বাম (RTL)","styles":"সà§à¦Ÿà¦¾à¦‡à¦²","cssClasses":"সà§à¦Ÿà¦¾à¦‡à¦²-শীট কà§à¦²à¦¾à¦¸","width":"পà§à¦°à¦¸à§à¦¥","height":"দৈরà§à¦˜à§à¦¯","align":"à¦à¦²à¦¾à¦‡à¦¨","alignLeft":"বামে","alignRight":"ডানে","alignCenter":"মাà¦à¦–ানে","alignJustify":"বà§à¦²à¦• জাসà§à¦Ÿà¦¿à¦«à¦¾à¦‡","alignTop":"উপর","alignMiddle":"মধà§à¦¯","alignBottom":"নীচে","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"বোলà§à¦¡","italic":"ইটালিক","strike":"সà§à¦Ÿà§à¦°à¦¾à¦‡à¦• থà§à¦°à§","subscript":"অধোলেখ","superscript":"অভিলেখ","underline":"আনà§à¦¡à¦¾à¦°à¦²à¦¾à¦‡à¦¨"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"কপি","copyError":"আপনার বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° সà§à¦°à¦•à§à¦·à¦¾ সেটিংস à¦à¦¡à¦¿à¦Ÿà¦°à¦•à§‡ অটোমেটিক কপি করার অনà§à¦®à¦¤à¦¿ দেয়নি। দয়া করে à¦à¦‡ কাজের জনà§à¦¯ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ (Ctrl/Cmd+C)।","cut":"কাট","cutError":"আপনার বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡à¦° সà§à¦°à¦•à§à¦·à¦¾ সেটিংস à¦à¦¡à¦¿à¦Ÿà¦°à¦•à§‡ অটোমেটিক কাট করার অনà§à¦®à¦¤à¦¿ দেয়নি। দয়া করে à¦à¦‡ কাজের জনà§à¦¯ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ (Ctrl/Cmd+X)।","paste":"পেসà§à¦Ÿ","pasteArea":"Paste Area","pasteMsg":"অনà§à¦—à§à¦°à¦¹ করে নীচের বাকà§à¦¸à§‡ কিবোরà§à¦¡ বà§à¦¯à¦¬à¦¹à¦¾à¦° করে (<STRONG>Ctrl/Cmd+V</STRONG>) পেসà§à¦Ÿ করà§à¦¨ à¦à¦¬à¦‚ <STRONG>OK</STRONG> চাপ দিন","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"পেসà§à¦Ÿ"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"ফনà§à¦Ÿ ফরমেট","panelTitle":"ফনà§à¦Ÿ ফরমেট","tag_address":"ঠিকানা","tag_div":"শীরà§à¦·à¦• (DIV)","tag_h1":"শীরà§à¦·à¦• ১","tag_h2":"শীরà§à¦·à¦• ২","tag_h3":"শীরà§à¦·à¦• ৩","tag_h4":"শীরà§à¦·à¦• ৪","tag_h5":"শীরà§à¦·à¦• ৫","tag_h6":"শীরà§à¦·à¦• ৬","tag_p":"সাধারণ","tag_pre":"ফরà§à¦®à§‡à¦Ÿà§‡à¦¡"},"horizontalrule":{"toolbar":"রেখা যà§à¦•à§à¦¤ কর"},"image":{"alertUrl":"অনà§à¦—à§à¦°à¦¹à¦• করে ছবির URL টাইপ করà§à¦¨","alt":"বিকলà§à¦ª টেকà§à¦¸à¦Ÿ","border":"বরà§à¦¡à¦¾à¦°","btnUpload":"ইহাকে সারà§à¦­à¦¾à¦°à§‡ পà§à¦°à§‡à¦°à¦¨ কর","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"হরাইজনà§à¦Ÿà¦¾à¦² সà§à¦ªà§‡à¦¸","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"ছবির তথà§à¦¯","linkTab":"লিংক","lockRatio":"অনà§à¦ªà¦¾à¦¤ লক কর","menu":"ছবির পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","resetSize":"সাইজ পূরà§à¦¬à¦¾à¦¬à¦¸à§à¦¥à¦¾à§Ÿ ফিরিয়ে দাও","title":"ছবির পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","titleButton":"ছবি বাটন পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","upload":"আপলোড","urlMissing":"Image source URL is missing.","vSpace":"ভারà§à¦Ÿà¦¿à¦•à§‡à¦² সà§à¦ªà§‡à¦¸","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"ইনডেনà§à¦Ÿ বাড়াও","outdent":"ইনডেনà§à¦Ÿ কমাও"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"à¦à¦•à§à¦¸à§‡à¦¸ কী","advanced":"à¦à¦¡à¦­à¦¾à¦¨à§à¦¸à¦¡","advisoryContentType":"পরামরà§à¦¶ কনà§à¦Ÿà§‡à¦¨à§à¦Ÿà§‡à¦° পà§à¦°à¦•à¦¾à¦°","advisoryTitle":"পরামরà§à¦¶ শীরà§à¦·à¦•","anchor":{"toolbar":"নোঙà§à¦—র","menu":"নোঙর পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","title":"নোঙর পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","name":"নোঙরের নাম","errorName":"নোঙরের নাম টাইপ করà§à¦¨","remove":"Remove Anchor"},"anchorId":"নোঙরের আইডি দিয়ে","anchorName":"নোঙরের নাম দিয়ে","charset":"লিংক রিসোরà§à¦¸ কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦° সেট","cssClasses":"সà§à¦Ÿà¦¾à¦‡à¦²-শীট কà§à¦²à¦¾à¦¸","emailAddress":"ইমেইল ঠিকানা","emailBody":"মেসেজের দেহ","emailSubject":"মেসেজের বিষয়","id":"আইডি","info":"লিংক তথà§à¦¯","langCode":"ভাষা লেখার দিক","langDir":"ভাষা লেখার দিক","langDirLTR":"বাম থেকে ডান (LTR)","langDirRTL":"ডান থেকে বাম (RTL)","menu":"লিংক সমà§à¦ªà¦¾à¦¦à¦¨","name":"নাম","noAnchors":"(No anchors available in the document)","noEmail":"অনà§à¦—à§à¦°à¦¹ করে ইমেইল à¦à¦¡à§à¦°à§‡à¦¸ টাইপ করà§à¦¨","noUrl":"অনà§à¦—à§à¦°à¦¹ করে URL লিংক টাইপ করà§à¦¨","other":"<other>","popupDependent":"ডিপেনà§à¦¡à§‡à¦¨à§à¦Ÿ (Netscape)","popupFeatures":"পপআপ উইনà§à¦¡à§‹ ফীচার সমূহ","popupFullScreen":"পূরà§à¦£ পরà§à¦¦à¦¾ জà§à§œà§‡ (IE)","popupLeft":"বামের পজিশন","popupLocationBar":"লোকেশন বার","popupMenuBar":"মেনà§à¦¯à§ বার","popupResizable":"Resizable","popupScrollBars":"সà§à¦•à§à¦°à¦² বার","popupStatusBar":"সà§à¦Ÿà§à¦¯à¦¾à¦Ÿà¦¾à¦¸ বার","popupToolbar":"টà§à¦² বার","popupTop":"ডানের পজিশন","rel":"Relationship","selectAnchor":"নোঙর বাছাই","styles":"সà§à¦Ÿà¦¾à¦‡à¦²","tabIndex":"টà§à¦¯à¦¾à¦¬ ইনà§à¦¡à§‡à¦•à§à¦¸","target":"টারà§à¦—েট","targetFrame":"<ফà§à¦°à§‡à¦®>","targetFrameName":"টারà§à¦—েট ফà§à¦°à§‡à¦®à§‡à¦° নাম","targetPopup":"<পপআপ উইনà§à¦¡à§‹>","targetPopupName":"পপআপ উইনà§à¦¡à§‹à¦° নাম","title":"লিংক","toAnchor":"à¦à¦‡ পেজে নোঙর কর","toEmail":"ইমেইল","toUrl":"URL","toolbar":"লিংক যà§à¦•à§à¦¤ কর","type":"লিংক পà§à¦°à¦•à¦¾à¦°","unlink":"লিংক সরাও","upload":"আপলোড"},"list":{"bulletedlist":"বà§à¦²à§‡à¦Ÿ লিসà§à¦Ÿ লেবেল","numberedlist":"সাংখà§à¦¯à¦¿à¦• লিসà§à¦Ÿà§‡à¦° লেবেল"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"সাদা টেকà§à¦¸à¦Ÿ হিসেবে পেসà§à¦Ÿ কর","title":"সাদা টেকà§à¦¸à¦Ÿ হিসেবে পেসà§à¦Ÿ কর"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"পেসà§à¦Ÿ (শবà§à¦¦)","toolbar":"পেসà§à¦Ÿ (শবà§à¦¦)"},"removeformat":{"toolbar":"ফরমেট সরাও"},"sourcearea":{"toolbar":"সোরà§à¦¸"},"specialchar":{"options":"Special Character Options","title":"বিশেষ কà§à¦¯à¦¾à¦°à§‡à¦•à§à¦Ÿà¦¾à¦° বাছাই কর","toolbar":"বিশেষ অকà§à¦·à¦° যà§à¦•à§à¦¤ কর"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"সà§à¦Ÿà¦¾à¦‡à¦²","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"বরà§à¦¡à¦¾à¦° সাইজ","caption":"শীরà§à¦·à¦•","cell":{"menu":"সেল","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"সেল মà§à¦›à§‡ দাও","merge":"সেল জোড়া দাও","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"সেল পà§à¦¯à¦¾à¦¡à¦¿à¦‚","cellSpace":"সেল সà§à¦ªà§‡à¦¸","column":{"menu":"কলাম","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"কলাম মà§à¦›à§‡ দাও"},"columns":"কলাম","deleteTable":"টেবিল ডিলীট কর","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"টেবিল পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","row":{"menu":"রো","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"রো মà§à¦›à§‡ দাও"},"rows":"রো","summary":"সারাংশ","title":"টেবিল পà§à¦°à§‹à¦ªà¦¾à¦°à§à¦Ÿà¦¿","toolbar":"টেবিলের লেবেল যà§à¦•à§à¦¤ কর","widthPc":"শতকরা","widthPx":"পিকà§à¦¸à§‡à¦²","widthUnit":"width unit"},"undo":{"redo":"রি-ডà§","undo":"আনডà§"},"wsc":{"btnIgnore":"ইগনোর কর","btnIgnoreAll":"সব ইগনোর কর","btnReplace":"বদলে দাও","btnReplaceAll":"সব বদলে দাও","btnUndo":"আনà§à¦¡à§","changeTo":"à¦à¦¤à§‡ বদলাও","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"বানান পরীকà§à¦·à¦• ইনসà§à¦Ÿà¦² করা নেই। আপনি কি à¦à¦–নই à¦à¦Ÿà¦¾ ডাউনলোড করতে চান?","manyChanges":"বানান পরীকà§à¦·à¦¾ শেষ: %1 গà§à¦²à§‹ শবà§à¦¦ বদলে গà§à¦¯à¦¾à¦›à§‡","noChanges":"বানান পরীকà§à¦·à¦¾ শেষ: কোন শবà§à¦¦ পরিবরà§à¦¤à¦¨ করা হয়নি","noMispell":"বানান পরীকà§à¦·à¦¾ শেষ: কোন ভà§à¦² বানান পাওয়া যায়নি","noSuggestions":"- কোন সাজেশন নেই -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"শবà§à¦¦à¦•à§‹à¦·à§‡ নেই","oneChange":"বানান পরীকà§à¦·à¦¾ শেষ: à¦à¦•à¦Ÿà¦¿ মাতà§à¦° শবà§à¦¦ পরিবরà§à¦¤à¦¨ করা হয়েছে","progress":"বানান পরীকà§à¦·à¦¾ চলছে...","title":"Spell Checker","toolbar":"বানান চেক"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/bs.js b/js/ckeditor/lang/bs.js
new file mode 100644
index 0000000..e0d5eb8
--- /dev/null
+++ b/js/ckeditor/lang/bs.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['bs']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protokol","upload":"Šalji","uploadSubmit":"Šalji na server","image":"Slika","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<nije podešeno>","id":"Id","name":"Naziv","langDir":"Smjer pisanja","langDirLtr":"S lijeva na desno (LTR)","langDirRtl":"S desna na lijevo (RTL)","langCode":"Jezièni kôd","longDescr":"Dugaèki opis URL-a","cssClass":"Klase CSS stilova","advisoryTitle":"Advisory title","cssStyle":"Stil","ok":"OK","cancel":"Odustani","close":"Close","preview":"Prikaži","resize":"Resize","generalTab":"General","advancedTab":"Naprednije","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Prozor","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"S lijeva na desno (LTR)","langDirRTL":"S desna na lijevo (RTL)","styles":"Stil","cssClasses":"Klase CSS stilova","width":"Širina","height":"Visina","align":"Poravnanje","alignLeft":"Lijevo","alignRight":"Desno","alignCenter":"Centar","alignJustify":"Puno poravnanje","alignTop":"Vrh","alignMiddle":"Sredina","alignBottom":"Dno","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Boldiraj","italic":"Ukosi","strike":"Precrtaj","subscript":"Subscript","superscript":"Superscript","underline":"Podvuci"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Kopiraj","copyError":"Sigurnosne postavke Vašeg pretraživaèa ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tastaturi (Ctrl/Cmd+C).","cut":"Izreži","cutError":"Sigurnosne postavke vašeg pretraživaèa ne dozvoljavaju operacije automatskog rezanja. Molimo koristite kraticu na tastaturi (Ctrl/Cmd+X).","paste":"Zalijepi","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Zalijepi"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Ubaci horizontalnu liniju"},"image":{"alertUrl":"Molimo ukucajte URL od slike.","alt":"Tekst na slici","border":"Okvir","btnUpload":"Šalji na server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Info slike","linkTab":"Link","lockRatio":"Zakljuèaj odnos","menu":"Svojstva slike","resetSize":"Resetuj dimenzije","title":"Svojstva slike","titleButton":"Image Button Properties","upload":"Šalji","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Poveæaj uvod","outdent":"Smanji uvod"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Pristupna tipka","advanced":"Naprednije","advisoryContentType":"Advisory vrsta sadržaja","advisoryTitle":"Advisory title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"Po Id-u elementa","anchorName":"Po nazivu sidra","charset":"Linked Resource Charset","cssClasses":"Klase CSS stilova","emailAddress":"E-Mail Adresa","emailBody":"Poruka","emailSubject":"Subjekt poruke","id":"Id","info":"Link info","langCode":"Smjer pisanja","langDir":"Smjer pisanja","langDirLTR":"S lijeva na desno (LTR)","langDirRTL":"S desna na lijevo (RTL)","menu":"Izmjeni link","name":"Naziv","noAnchors":"(Nema dostupnih sidra na stranici)","noEmail":"Molimo ukucajte e-mail adresu","noUrl":"Molimo ukucajte URL link","other":"<other>","popupDependent":"Ovisno (Netscape)","popupFeatures":"Moguænosti popup prozora","popupFullScreen":"Cijeli ekran (IE)","popupLeft":"Lijeva pozicija","popupLocationBar":"Traka za lokaciju","popupMenuBar":"Izborna traka","popupResizable":"Resizable","popupScrollBars":"Scroll traka","popupStatusBar":"Statusna traka","popupToolbar":"Traka sa alatima","popupTop":"Gornja pozicija","rel":"Relationship","selectAnchor":"Izaberi sidro","styles":"Stil","tabIndex":"Tab indeks","target":"Prozor","targetFrame":"<frejm>","targetFrameName":"Target Frame Name","targetPopup":"<popup prozor>","targetPopupName":"Naziv popup prozora","title":"Link","toAnchor":"Sidro na ovoj stranici","toEmail":"E-Mail","toUrl":"URL","toolbar":"Ubaci/Izmjeni link","type":"Tip linka","unlink":"Izbriši link","upload":"Šalji"},"list":{"bulletedlist":"Lista","numberedlist":"Numerisana lista"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Zalijepi kao obièan tekst","title":"Zalijepi kao obièan tekst"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Zalijepi iz Word-a","toolbar":"Zalijepi iz Word-a"},"removeformat":{"toolbar":"Poništi format"},"sourcearea":{"toolbar":"HTML kôd"},"specialchar":{"options":"Special Character Options","title":"Izaberi specijalni karakter","toolbar":"Ubaci specijalni karater"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stil","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Okvir","caption":"Naslov","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Briši æelije","merge":"Spoji æelije","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Uvod æelija","cellSpace":"Razmak æelija","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Briši kolone"},"columns":"Kolona","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Svojstva tabele","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Briši redove"},"rows":"Redova","summary":"Summary","title":"Svojstva tabele","toolbar":"Tabela","widthPc":"posto","widthPx":"piksela","widthUnit":"width unit"},"undo":{"redo":"Ponovi","undo":"Vrati"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ca.js b/js/ckeditor/lang/ca.js
new file mode 100644
index 0000000..d52a596
--- /dev/null
+++ b/js/ckeditor/lang/ca.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ca']={"editor":"Editor de text enriquit","editorPanel":"Panell de l'editor de text enriquit","common":{"editorHelp":"Premeu ALT 0 per ajuda","browseServer":"Veure servidor","url":"URL","protocol":"Protocol","upload":"Puja","uploadSubmit":"Envia-la al servidor","image":"Imatge","flash":"Flash","form":"Formulari","checkbox":"Casella de verificació","radio":"Botó d'opció","textField":"Camp de text","textarea":"Àrea de text","hiddenField":"Camp ocult","button":"Botó","select":"Camp de selecció","imageButton":"Botó d'imatge","notSet":"<no definit>","id":"Id","name":"Nom","langDir":"Direcció de l'idioma","langDirLtr":"D'esquerra a dreta (LTR)","langDirRtl":"De dreta a esquerra (RTL)","langCode":"Codi d'idioma","longDescr":"Descripció llarga de la URL","cssClass":"Classes del full d'estil","advisoryTitle":"Títol consultiu","cssStyle":"Estil","ok":"D'acord","cancel":"Cancel·la","close":"Tanca","preview":"Previsualitza","resize":"Arrossegueu per redimensionar","generalTab":"General","advancedTab":"Avançat","validateNumberFailed":"Aquest valor no és un número.","confirmNewPage":"Els canvis en aquest contingut que no es desin es perdran. Esteu segur que voleu carregar una pàgina nova?","confirmCancel":"Algunes opcions s'han canviat. Esteu segur que voleu tancar el quadre de diàleg?","options":"Opcions","target":"Destí","targetNew":"Nova finestra (_blank)","targetTop":"Finestra superior (_top)","targetSelf":"Mateixa finestra (_self)","targetParent":"Finestra pare (_parent)","langDirLTR":"D'esquerra a dreta (LTR)","langDirRTL":"De dreta a esquerra (RTL)","styles":"Estil","cssClasses":"Classes del full d'estil","width":"Amplada","height":"Alçada","align":"Alineació","alignLeft":"Ajusta a l'esquerra","alignRight":"Ajusta a la dreta","alignCenter":"Centre","alignJustify":"Justificat","alignTop":"Superior","alignMiddle":"Centre","alignBottom":"Inferior","alignNone":"None","invalidValue":"Valor no vàlid.","invalidHeight":"L'alçada ha de ser un número.","invalidWidth":"L'amplada ha de ser un número.","invalidCssLength":"El valor especificat per als \"%1\" camps ha de ser un número positiu amb o sense unitat de mesura vàlida de CSS (px, %, in, cm, mm, em, ex, pt o pc).","invalidHtmlLength":"El valor especificat per als \"%1\" camps ha de ser un número positiu amb o sense unitat de mesura vàlida d'HTML (px o %).","invalidInlineStyle":"El valor especificat per l'estil en línia ha de constar d'una o més tuples amb el format \"name: value\", separats per punt i coma.","cssLengthTooltip":"Introduïu un número per un valor en píxels o un número amb una unitat vàlida de CSS (px, %, in, cm, mm, em, ex, pt o pc).","unavailable":"%1<span class=\"cke_accessibility\">, no disponible</span>"},"about":{"copy":"Copyright &copy; $1. Tots els drets reservats.","dlgTitle":"Quant al CKEditor","help":"Premi $1 per obtenir ajuda.","moreInfo":"Per informació sobre llicències visiteu el nostre lloc web:","title":"Quant al CKEditor","userGuide":"Manual d'usuari de CKEditor"},"basicstyles":{"bold":"Negreta","italic":"Cursiva","strike":"Ratllat","subscript":"Subíndex","superscript":"Superíndex","underline":"Subratllat"},"blockquote":{"toolbar":"Bloc de cita"},"clipboard":{"copy":"Copiar","copyError":"La configuració de seguretat del vostre navegador no permet executar automàticament les operacions de copiar. Si us plau, utilitzeu el teclat (Ctrl/Cmd+C).","cut":"Retallar","cutError":"La configuració de seguretat del vostre navegador no permet executar automàticament les operacions de retallar. Si us plau, utilitzeu el teclat (Ctrl/Cmd+X).","paste":"Enganxar","pasteArea":"Àrea d'enganxat","pasteMsg":"Si us plau, enganxi dins del següent camp utilitzant el teclat (<strong>Ctrl/Cmd+V</strong>) i premi OK.","securityMsg":"A causa de la configuració de seguretat del vostre navegador, l'editor no pot accedir a les dades del porta-retalls directament. Enganxeu-ho un altre cop en aquesta finestra.","title":"Enganxar"},"contextmenu":{"options":"Opcions del menú contextual"},"button":{"selectedLabel":"%1 (Seleccionat)"},"toolbar":{"toolbarCollapse":"Redueix la barra d'eines","toolbarExpand":"Amplia la barra d'eines","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor de barra d'eines"},"elementspath":{"eleLabel":"Ruta dels elements","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Adreça","tag_div":"Normal (DIV)","tag_h1":"Encapçalament 1","tag_h2":"Encapçalament 2","tag_h3":"Encapçalament 3","tag_h4":"Encapçalament 4","tag_h5":"Encapçalament 5","tag_h6":"Encapçalament 6","tag_p":"Normal","tag_pre":"Formatejat"},"horizontalrule":{"toolbar":"Insereix línia horitzontal"},"image":{"alertUrl":"Si us plau, escriviu la URL de la imatge","alt":"Text alternatiu","border":"Vora","btnUpload":"Envia-la al servidor","button2Img":"Voleu transformar el botó d'imatge seleccionat en una simple imatge?","hSpace":"Espaiat horit.","img2Button":"Voleu transformar la imatge seleccionada en un botó d'imatge?","infoTab":"Informació de la imatge","linkTab":"Enllaç","lockRatio":"Bloqueja les proporcions","menu":"Propietats de la imatge","resetSize":"Restaura la mida","title":"Propietats de la imatge","titleButton":"Propietats del botó d'imatge","upload":"Puja","urlMissing":"Falta la URL de la imatge.","vSpace":"Espaiat vert.","validateBorder":"La vora ha de ser un nombre enter.","validateHSpace":"HSpace ha de ser un nombre enter.","validateVSpace":"VSpace ha de ser un nombre enter."},"indent":{"indent":"Augmenta el sagnat","outdent":"Redueix el sagnat"},"fakeobjects":{"anchor":"Àncora","flash":"Animació Flash","hiddenfield":"Camp ocult","iframe":"IFrame","unknown":"Objecte desconegut"},"link":{"acccessKey":"Clau d'accés","advanced":"Avançat","advisoryContentType":"Tipus de contingut consultiu","advisoryTitle":"Títol consultiu","anchor":{"toolbar":"Insereix/Edita àncora","menu":"Propietats de l'àncora","title":"Propietats de l'àncora","name":"Nom de l'àncora","errorName":"Si us plau, escriviu el nom de l'ancora","remove":"Remove Anchor"},"anchorId":"Per Id d'element","anchorName":"Per nom d'àncora","charset":"Conjunt de caràcters font enllaçat","cssClasses":"Classes del full d'estil","emailAddress":"Adreça de correu electrònic","emailBody":"Cos del missatge","emailSubject":"Assumpte del missatge","id":"Id","info":"Informació de l'enllaç","langCode":"Direcció de l'idioma","langDir":"Direcció de l'idioma","langDirLTR":"D'esquerra a dreta (LTR)","langDirRTL":"De dreta a esquerra (RTL)","menu":"Edita l'enllaç","name":"Nom","noAnchors":"(No hi ha àncores disponibles en aquest document)","noEmail":"Si us plau, escrigui l'adreça correu electrònic","noUrl":"Si us plau, escrigui l'enllaç URL","other":"<altre>","popupDependent":"Depenent (Netscape)","popupFeatures":"Característiques finestra popup","popupFullScreen":"Pantalla completa (IE)","popupLeft":"Posició esquerra","popupLocationBar":"Barra d'adreça","popupMenuBar":"Barra de menú","popupResizable":"Redimensionable","popupScrollBars":"Barres d'scroll","popupStatusBar":"Barra d'estat","popupToolbar":"Barra d'eines","popupTop":"Posició dalt","rel":"Relació","selectAnchor":"Selecciona una àncora","styles":"Estil","tabIndex":"Index de Tab","target":"Destí","targetFrame":"<marc>","targetFrameName":"Nom del marc de destí","targetPopup":"<finestra emergent>","targetPopupName":"Nom finestra popup","title":"Enllaç","toAnchor":"Àncora en aquesta pàgina","toEmail":"Correu electrònic","toUrl":"URL","toolbar":"Insereix/Edita enllaç","type":"Tipus d'enllaç","unlink":"Elimina l'enllaç","upload":"Puja"},"list":{"bulletedlist":"Llista de pics","numberedlist":"Llista numerada"},"magicline":{"title":"Insereix el paràgraf aquí"},"maximize":{"maximize":"Maximitza","minimize":"Minimitza"},"pastetext":{"button":"Enganxa com a text no formatat","title":"Enganxa com a text no formatat"},"pastefromword":{"confirmCleanup":"El text que voleu enganxar sembla provenir de Word. Voleu netejar aquest text abans que sigui enganxat?","error":"No ha estat possible netejar les dades enganxades degut a un error intern","title":"Enganxa des del Word","toolbar":"Enganxa des del Word"},"removeformat":{"toolbar":"Elimina Format"},"sourcearea":{"toolbar":"Codi font"},"specialchar":{"options":"Opcions de caràcters especials","title":"Selecciona el caràcter especial","toolbar":"Insereix caràcter especial"},"scayt":{"btn_about":"Quant a l'SCAYT","btn_dictionaries":"Diccionaris","btn_disable":"Deshabilita SCAYT","btn_enable":"Habilitat l'SCAYT","btn_langs":"Idiomes","btn_options":"Opcions","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Estil","panelTitle":"Estils de format","panelTitle1":"Estils de bloc","panelTitle2":"Estils incrustats","panelTitle3":"Estils d'objecte"},"table":{"border":"Mida vora","caption":"Títol","cell":{"menu":"Cel·la","insertBefore":"Insereix abans","insertAfter":"Insereix després","deleteCell":"Suprimeix","merge":"Fusiona","mergeRight":"Fusiona a la dreta","mergeDown":"Fusiona avall","splitHorizontal":"Divideix horitzontalment","splitVertical":"Divideix verticalment","title":"Propietats de la cel·la","cellType":"Tipus de cel·la","rowSpan":"Expansió de files","colSpan":"Expansió de columnes","wordWrap":"Ajustar al contingut","hAlign":"Alineació Horizontal","vAlign":"Alineació Vertical","alignBaseline":"A la línia base","bgColor":"Color de fons","borderColor":"Color de la vora","data":"Dades","header":"Capçalera","yes":"Sí","no":"No","invalidWidth":"L'amplada de cel·la ha de ser un nombre.","invalidHeight":"L'alçada de cel·la ha de ser un nombre.","invalidRowSpan":"L'expansió de files ha de ser un nombre enter.","invalidColSpan":"L'expansió de columnes ha de ser un nombre enter.","chooseColor":"Trieu"},"cellPad":"Encoixinament de cel·les","cellSpace":"Espaiat de cel·les","column":{"menu":"Columna","insertBefore":"Insereix columna abans de","insertAfter":"Insereix columna darrera","deleteColumn":"Suprimeix una columna"},"columns":"Columnes","deleteTable":"Suprimeix la taula","headers":"Capçaleres","headersBoth":"Ambdues","headersColumn":"Primera columna","headersNone":"Cap","headersRow":"Primera fila","invalidBorder":"El gruix de la vora ha de ser un nombre.","invalidCellPadding":"L'encoixinament de cel·la ha de ser un nombre.","invalidCellSpacing":"L'espaiat de cel·la ha de ser un nombre.","invalidCols":"El nombre de columnes ha de ser un nombre major que 0.","invalidHeight":"L'alçada de la taula ha de ser un nombre.","invalidRows":"El nombre de files ha de ser un nombre major que 0.","invalidWidth":"L'amplada de la taula ha de ser un nombre.","menu":"Propietats de la taula","row":{"menu":"Fila","insertBefore":"Insereix fila abans de","insertAfter":"Insereix fila darrera","deleteRow":"Suprimeix una fila"},"rows":"Files","summary":"Resum","title":"Propietats de la taula","toolbar":"Taula","widthPc":"percentatge","widthPx":"píxels","widthUnit":"unitat d'amplada"},"undo":{"redo":"Refés","undo":"Desfés"},"wsc":{"btnIgnore":"Ignora","btnIgnoreAll":"Ignora-les totes","btnReplace":"Canvia","btnReplaceAll":"Canvia-les totes","btnUndo":"Desfés","changeTo":"Reemplaça amb","errorLoading":"Error carregant el servidor: %s.","ieSpellDownload":"Verificació ortogràfica no instal·lada. Voleu descarregar-ho ara?","manyChanges":"Verificació ortogràfica: s'han canviat %1 paraules","noChanges":"Verificació ortogràfica: no s'ha canviat cap paraula","noMispell":"Verificació ortogràfica acabada: no hi ha cap paraula mal escrita","noSuggestions":"Cap suggeriment","notAvailable":"El servei no es troba disponible ara.","notInDic":"No és al diccionari","oneChange":"Verificació ortogràfica: s'ha canviat una paraula","progress":"Verificació ortogràfica en curs...","title":"Comprova l'ortografia","toolbar":"Revisa l'ortografia"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/cs.js b/js/ckeditor/lang/cs.js
new file mode 100644
index 0000000..86e5f56
--- /dev/null
+++ b/js/ckeditor/lang/cs.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['cs']={"editor":"Textový editor","editorPanel":"Panel textového editoru","common":{"editorHelp":"StisknÄ›te ALT 0 pro nápovÄ›du","browseServer":"Vybrat na serveru","url":"URL","protocol":"Protokol","upload":"Odeslat","uploadSubmit":"Odeslat na server","image":"Obrázek","flash":"Flash","form":"Formulář","checkbox":"ZaÅ¡krtávací políÄko","radio":"PÅ™epínaÄ","textField":"Textové pole","textarea":"Textová oblast","hiddenField":"Skryté pole","button":"TlaÄítko","select":"Seznam","imageButton":"Obrázkové tlaÄítko","notSet":"<nenastaveno>","id":"Id","name":"Jméno","langDir":"SmÄ›r jazyka","langDirLtr":"Zleva doprava (LTR)","langDirRtl":"Zprava doleva (RTL)","langCode":"Kód jazyka","longDescr":"Dlouhý popis URL","cssClass":"Třída stylu","advisoryTitle":"Pomocný titulek","cssStyle":"Styl","ok":"OK","cancel":"ZruÅ¡it","close":"Zavřít","preview":"Náhled","resize":"Uchopit pro zmÄ›nu velikosti","generalTab":"Obecné","advancedTab":"Rozšířené","validateNumberFailed":"Zadaná hodnota není Äíselná.","confirmNewPage":"Jakékoliv neuložené zmÄ›ny obsahu budou ztraceny. SkuteÄnÄ› chcete otevřít novou stránku?","confirmCancel":"NÄ›která z nastavení byla zmÄ›nÄ›na. SkuteÄnÄ› chcete zavřít dialogové okno?","options":"Nastavení","target":"Cíl","targetNew":"Nové okno (_blank)","targetTop":"Okno nejvyšší úrovnÄ› (_top)","targetSelf":"Stejné okno (_self)","targetParent":"RodiÄovské okno (_parent)","langDirLTR":"Zleva doprava (LTR)","langDirRTL":"Zprava doleva (RTL)","styles":"Styly","cssClasses":"Třídy stylů","width":"Šířka","height":"Výška","align":"Zarovnání","alignLeft":"Vlevo","alignRight":"Vpravo","alignCenter":"Na stÅ™ed","alignJustify":"Zarovnat do bloku","alignTop":"Nahoru","alignMiddle":"Na stÅ™ed","alignBottom":"Dolů","alignNone":"Žádné","invalidValue":"Neplatná hodnota.","invalidHeight":"Zadaná výška musí být Äíslo.","invalidWidth":"Šířka musí být Äíslo.","invalidCssLength":"Hodnota urÄená pro pole \"%1\" musí být kladné Äíslo bez nebo s platnou jednotkou míry CSS (px, %, in, cm, mm, em, ex, pt, nebo pc).","invalidHtmlLength":"Hodnota urÄená pro pole \"%1\" musí být kladné Äíslo bez nebo s platnou jednotkou míry HTML (px nebo %).","invalidInlineStyle":"Hodnota urÄená pro řádkový styl se musí skládat z jedné nebo více n-tic ve formátu \"název : hodnota\", oddÄ›lené stÅ™edníky","cssLengthTooltip":"Zadejte Äíslo jako hodnotu v pixelech nebo Äíslo s platnou jednotkou CSS (px, %, v cm, mm, em, ex, pt, nebo pc).","unavailable":"%1<span class=\"cke_accessibility\">, nedostupné</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"O aplikaci CKEditor","help":"ProhlédnÄ›te si $1 pro nápovÄ›du.","moreInfo":"Pro informace o lincenci navÅ¡tivte naÅ¡i webovou stránku:","title":"O aplikaci CKEditor","userGuide":"Uživatelská příruÄka CKEditor"},"basicstyles":{"bold":"TuÄné","italic":"Kurzíva","strike":"PÅ™eÅ¡krtnuté","subscript":"Dolní index","superscript":"Horní index","underline":"Podtržené"},"blockquote":{"toolbar":"Citace"},"clipboard":{"copy":"Kopírovat","copyError":"BezpeÄnostní nastavení vaÅ¡eho prohlížeÄe nedovolují editoru spustit funkci pro kopírování zvoleného textu do schránky. Prosím zkopírujte zvolený text do schránky pomocí klávesnice (Ctrl/Cmd+C).","cut":"Vyjmout","cutError":"BezpeÄnostní nastavení vaÅ¡eho prohlížeÄe nedovolují editoru spustit funkci pro vyjmutí zvoleného textu do schránky. Prosím vyjmÄ›te zvolený text do schránky pomocí klávesnice (Ctrl/Cmd+X).","paste":"Vložit","pasteArea":"Oblast vkládání","pasteMsg":"Do následujícího pole vložte požadovaný obsah pomocí klávesnice (<STRONG>Ctrl/Cmd+V</STRONG>) a stisknÄ›te <STRONG>OK</STRONG>.","securityMsg":"Z důvodů nastavení bezpeÄnosti vaÅ¡eho prohlížeÄe nemůže editor pÅ™istupovat přímo do schránky. Obsah schránky prosím vložte znovu do tohoto okna.","title":"Vložit"},"contextmenu":{"options":"Nastavení kontextové nabídky"},"button":{"selectedLabel":"%1 (Vybráno)"},"toolbar":{"toolbarCollapse":"Skrýt panel nástrojů","toolbarExpand":"Zobrazit panel nástrojů","toolbarGroups":{"document":"Dokument","clipboard":"Schránka/ZpÄ›t","editing":"Úpravy","forms":"Formuláře","basicstyles":"Základní styly","paragraph":"Odstavec","links":"Odkazy","insert":"Vložit","styles":"Styly","colors":"Barvy","tools":"Nástroje"},"toolbars":"Panely nástrojů editoru"},"elementspath":{"eleLabel":"Cesta objektu","eleTitle":"%1 objekt"},"format":{"label":"Formát","panelTitle":"Formát","tag_address":"Adresa","tag_div":"Normální (DIV)","tag_h1":"Nadpis 1","tag_h2":"Nadpis 2","tag_h3":"Nadpis 3","tag_h4":"Nadpis 4","tag_h5":"Nadpis 5","tag_h6":"Nadpis 6","tag_p":"Normální","tag_pre":"Naformátováno"},"horizontalrule":{"toolbar":"Vložit vodorovnou linku"},"image":{"alertUrl":"Zadejte prosím URL obrázku","alt":"Alternativní text","border":"Okraje","btnUpload":"Odeslat na server","button2Img":"SkuteÄnÄ› chcete pÅ™evést zvolené obrázkové tlaÄítko na obyÄejný obrázek?","hSpace":"Horizontální mezera","img2Button":"SkuteÄnÄ› chcete pÅ™evést zvolený obrázek na obrázkové tlaÄítko?","infoTab":"Informace o obrázku","linkTab":"Odkaz","lockRatio":"Zámek","menu":"Vlastnosti obrázku","resetSize":"Původní velikost","title":"Vlastnosti obrázku","titleButton":"Vlastností obrázkového tlaÄítka","upload":"Odeslat","urlMissing":"Zadané URL zdroje obrázku nebylo nalezeno.","vSpace":"Vertikální mezera","validateBorder":"Okraj musí být nastaven v celých Äíslech.","validateHSpace":"Horizontální mezera musí být nastavena v celých Äíslech.","validateVSpace":"Vertikální mezera musí být nastavena v celých Äíslech."},"indent":{"indent":"ZvÄ›tÅ¡it odsazení","outdent":"ZmenÅ¡it odsazení"},"fakeobjects":{"anchor":"Záložka","flash":"Flash animace","hiddenfield":"Skryté pole","iframe":"IFrame","unknown":"Neznámý objekt"},"link":{"acccessKey":"Přístupový klíÄ","advanced":"Rozšířené","advisoryContentType":"Pomocný typ obsahu","advisoryTitle":"Pomocný titulek","anchor":{"toolbar":"Záložka","menu":"Vlastnosti záložky","title":"Vlastnosti záložky","name":"Název záložky","errorName":"Zadejte prosím název záložky","remove":"Odstranit záložku"},"anchorId":"Podle Id objektu","anchorName":"Podle jména kotvy","charset":"PÅ™iÅ™azená znaková sada","cssClasses":"Třída stylu","emailAddress":"E-mailová adresa","emailBody":"TÄ›lo zprávy","emailSubject":"PÅ™edmÄ›t zprávy","id":"Id","info":"Informace o odkazu","langCode":"Kód jazyka","langDir":"SmÄ›r jazyka","langDirLTR":"Zleva doprava (LTR)","langDirRTL":"Zprava doleva (RTL)","menu":"ZmÄ›nit odkaz","name":"Jméno","noAnchors":"(Ve stránce není definována žádná kotva!)","noEmail":"Zadejte prosím e-mailovou adresu","noUrl":"Zadejte prosím URL odkazu","other":"<jiný>","popupDependent":"Závislost (Netscape)","popupFeatures":"Vlastnosti vyskakovacího okna","popupFullScreen":"Celá obrazovka (IE)","popupLeft":"Levý okraj","popupLocationBar":"Panel umístÄ›ní","popupMenuBar":"Panel nabídky","popupResizable":"Umožňující mÄ›nit velikost","popupScrollBars":"Posuvníky","popupStatusBar":"Stavový řádek","popupToolbar":"Panel nástrojů","popupTop":"Horní okraj","rel":"Vztah","selectAnchor":"Vybrat kotvu","styles":"Styl","tabIndex":"PoÅ™adí prvku","target":"Cíl","targetFrame":"<rámec>","targetFrameName":"Název cílového rámu","targetPopup":"<vyskakovací okno>","targetPopupName":"Název vyskakovacího okna","title":"Odkaz","toAnchor":"Kotva v této stránce","toEmail":"E-mail","toUrl":"URL","toolbar":"Odkaz","type":"Typ odkazu","unlink":"Odstranit odkaz","upload":"Odeslat"},"list":{"bulletedlist":"Odrážky","numberedlist":"Číslování"},"magicline":{"title":"zde vložit odstavec"},"maximize":{"maximize":"Maximalizovat","minimize":"Minimalizovat"},"pastetext":{"button":"Vložit jako Äistý text","title":"Vložit jako Äistý text"},"pastefromword":{"confirmCleanup":"Jak je vidÄ›t, vkládaný text je kopírován z Wordu. Chcete jej pÅ™ed vložením vyÄistit?","error":"Z důvodu vnitÅ™ní chyby nebylo možné provést vyÄiÅ¡tÄ›ní vkládaného textu.","title":"Vložit z Wordu","toolbar":"Vložit z Wordu"},"removeformat":{"toolbar":"Odstranit formátování"},"sourcearea":{"toolbar":"Zdroj"},"specialchar":{"options":"Nastavení speciálních znaků","title":"VýbÄ›r speciálního znaku","toolbar":"Vložit speciální znaky"},"scayt":{"btn_about":"O aplikaci SCAYT","btn_dictionaries":"Slovníky","btn_disable":"Vypnout SCAYT","btn_enable":"Zapnout SCAYT","btn_langs":"Jazyky","btn_options":"Nastavení","text_title":"Kontrola pravopisu bÄ›hem psaní (SCAYT)"},"stylescombo":{"label":"Styl","panelTitle":"Formátovací styly","panelTitle1":"Blokové styly","panelTitle2":"Řádkové styly","panelTitle3":"Objektové styly"},"table":{"border":"OhraniÄení","caption":"Popis","cell":{"menu":"Buňka","insertBefore":"Vložit buňku pÅ™ed","insertAfter":"Vložit buňku za","deleteCell":"Smazat buňky","merge":"SlouÄit buňky","mergeRight":"SlouÄit doprava","mergeDown":"SlouÄit dolů","splitHorizontal":"RozdÄ›lit buňky vodorovnÄ›","splitVertical":"RozdÄ›lit buňky svisle","title":"Vlastnosti buňky","cellType":"Typ buňky","rowSpan":"Spojit řádky","colSpan":"Spojit sloupce","wordWrap":"Zalamování","hAlign":"Vodorovné zarovnání","vAlign":"Svislé zarovnání","alignBaseline":"Na úÄaří","bgColor":"Barva pozadí","borderColor":"Barva okraje","data":"Data","header":"HlaviÄka","yes":"Ano","no":"Ne","invalidWidth":"Šířka buňky musí být Äíslo.","invalidHeight":"Zadaná výška buňky musí být Äíslená.","invalidRowSpan":"Zadaný poÄet slouÄených řádků musí být celé Äíslo.","invalidColSpan":"Zadaný poÄet slouÄených sloupců musí být celé Äíslo.","chooseColor":"VýbÄ›r"},"cellPad":"Odsazení obsahu v buňce","cellSpace":"Vzdálenost bunÄ›k","column":{"menu":"Sloupec","insertBefore":"Vložit sloupec pÅ™ed","insertAfter":"Vložit sloupec za","deleteColumn":"Smazat sloupec"},"columns":"Sloupce","deleteTable":"Smazat tabulku","headers":"Záhlaví","headersBoth":"Obojí","headersColumn":"První sloupec","headersNone":"Žádné","headersRow":"První řádek","invalidBorder":"Zdaná velikost okraje musí být Äíselná.","invalidCellPadding":"Zadané odsazení obsahu v buňce musí být Äíselné.","invalidCellSpacing":"Zadaná vzdálenost bunÄ›k musí být Äíselná.","invalidCols":"PoÄet sloupců musí být Äíslo vÄ›tší než 0.","invalidHeight":"Zadaná výška tabulky musí být Äíselná.","invalidRows":"PoÄet řádků musí být Äíslo vÄ›tší než 0.","invalidWidth":"Šířka tabulky musí být Äíslo.","menu":"Vlastnosti tabulky","row":{"menu":"Řádek","insertBefore":"Vložit řádek pÅ™ed","insertAfter":"Vložit řádek za","deleteRow":"Smazat řádky"},"rows":"Řádky","summary":"Souhrn","title":"Vlastnosti tabulky","toolbar":"Tabulka","widthPc":"procent","widthPx":"bodů","widthUnit":"jednotka šířky"},"undo":{"redo":"Znovu","undo":"ZpÄ›t"},"wsc":{"btnIgnore":"PÅ™eskoÄit","btnIgnoreAll":"PÅ™eskakovat vÅ¡e","btnReplace":"ZamÄ›nit","btnReplaceAll":"Zaměňovat vÅ¡e","btnUndo":"ZpÄ›t","changeTo":"ZmÄ›nit na","errorLoading":"Chyba nahrávání služby aplikace z: %s.","ieSpellDownload":"Kontrola pravopisu není nainstalována. Chcete ji nyní stáhnout?","manyChanges":"Kontrola pravopisu dokonÄena: %1 slov zmÄ›nÄ›no","noChanges":"Kontrola pravopisu dokonÄena: Beze zmÄ›n","noMispell":"Kontrola pravopisu dokonÄena: Žádné pravopisné chyby nenalezeny","noSuggestions":"- žádné návrhy -","notAvailable":"Omlouváme se, ale služba nyní není dostupná.","notInDic":"Není ve slovníku","oneChange":"Kontrola pravopisu dokonÄena: Jedno slovo zmÄ›nÄ›no","progress":"Probíhá kontrola pravopisu...","title":"Kontrola pravopisu","toolbar":"Zkontrolovat pravopis"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/cy.js b/js/ckeditor/lang/cy.js
new file mode 100644
index 0000000..2a878ab
--- /dev/null
+++ b/js/ckeditor/lang/cy.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['cy']={"editor":"Golygydd Testun Cyfoethog","editorPanel":"Panel Golygydd Testun Cyfoethog","common":{"editorHelp":"Gwasgwch ALT 0 am gymorth","browseServer":"Pori'r Gweinydd","url":"URL","protocol":"Protocol","upload":"Lanlwytho","uploadSubmit":"Anfon i'r Gweinydd","image":"Delwedd","flash":"Flash","form":"Ffurflen","checkbox":"Blwch ticio","radio":"Botwm Radio","textField":"Maes Testun","textarea":"Ardal Testun","hiddenField":"Maes Cudd","button":"Botwm","select":"Maes Dewis","imageButton":"Botwm Delwedd","notSet":"<heb osod>","id":"Id","name":"Name","langDir":"Cyfeiriad Iaith","langDirLtr":"Chwith i'r Dde (LTR)","langDirRtl":"Dde i'r Chwith (RTL)","langCode":"Cod Iaith","longDescr":"URL Disgrifiad Hir","cssClass":"Dosbarthiadau Dalen Arddull","advisoryTitle":"Teitl Cynghorol","cssStyle":"Arddull","ok":"Iawn","cancel":"Diddymu","close":"Cau","preview":"Rhagolwg","resize":"Ailfeintio","generalTab":"Cyffredinol","advancedTab":"Uwch","validateNumberFailed":"'Dyw'r gwerth hwn ddim yn rhif.","confirmNewPage":"Byddwch chi'n colli unrhyw newidiadau i'r cynnwys sydd heb eu cadw. Ydych am barhau i lwytho tudalen newydd?","confirmCancel":"Cafodd rhai o'r opsiynau eu newid. Ydych chi wir am gau'r deialog?","options":"Opsiynau","target":"Targed","targetNew":"Ffenest Newydd (_blank)","targetTop":"Ffenest ar y Brig (_top)","targetSelf":"Yr un Ffenest (_self)","targetParent":"Ffenest y Rhiant (_parent)","langDirLTR":"Chwith i'r Dde (LTR)","langDirRTL":"Dde i'r Chwith (RTL)","styles":"Arddull","cssClasses":"Dosbarthiadau Dalen Arddull","width":"Lled","height":"Uchder","align":"Alinio","alignLeft":"Chwith","alignRight":"Dde","alignCenter":"Canol","alignJustify":"Unioni","alignTop":"Brig","alignMiddle":"Canol","alignBottom":"Gwaelod","alignNone":"None","invalidValue":"Gwerth annilys.","invalidHeight":"Mae'n rhaid i'r uchder fod yn rhif.","invalidWidth":"Mae'n rhaid i'r lled fod yn rhif.","invalidCssLength":"Mae'n rhaid i'r gwerth ar gyfer maes \"%1\" fod yn rhif positif gyda neu heb uned fesuriad CSS dilys (px, %, in, cm, mm, em, ex, pt, neu pc).","invalidHtmlLength":"Mae'n rhaid i'r gwerth ar gyfer maes \"%1\" fod yn rhif positif gyda neu heb uned fesuriad HTML dilys (px neu %).","invalidInlineStyle":"Mae'n rhaid i'r gwerth ar gyfer arddull mewn-llinell gynnwys un set neu fwy ar y fformat \"enw : gwerth\", wedi'u gwahanu gyda hanner colon.","cssLengthTooltip":"Rhowch rif am werth mewn picsel neu rhif gydag uned CSS dilys (px, %, in, cm, mm, em, pt neu pc).","unavailable":"%1<span class=\"cke_accessibility\">, ddim ar gael</span>"},"about":{"copy":"Hawlfraint &copy; $1. Cedwir pob hawl.","dlgTitle":"Ynghylch CKEditor","help":"Gwirio $1 am gymorth.","moreInfo":"Am wybodaeth ynghylch trwyddedau, ewch i'n gwefan:","title":"Ynghylch CKEditor","userGuide":"Canllawiau Defnyddiwr CKEditor"},"basicstyles":{"bold":"Bras","italic":"Italig","strike":"Llinell Trwyddo","subscript":"Is-sgript","superscript":"Uwchsgript","underline":"Tanlinellu"},"blockquote":{"toolbar":"Dyfyniad bloc"},"clipboard":{"copy":"Copïo","copyError":"'Dyw gosodiadau diogelwch eich porwr ddim yn caniatàu'r golygydd i gynnal 'gweithredoedd copïo' yn awtomatig. Defnyddiwch y bysellfwrdd (Ctrl/Cmd+C).","cut":"Torri","cutError":"Nid yw gosodiadau diogelwch eich porwr yn caniatàu'r golygydd i gynnal 'gweithredoedd torri' yn awtomatig. Defnyddiwch y bysellfwrdd (Ctrl/Cmd+X).","paste":"Gludo","pasteArea":"Ardal Gludo","pasteMsg":"Gludwch i mewn i'r blwch canlynol gan ddefnyddio'r bysellfwrdd (<strong>Ctrl/Cmd+V</strong>) a phwyso <strong>Iawn</strong>.","securityMsg":"Oherwydd gosodiadau diogelwch eich porwr, 'dyw'r porwr ddim yn gallu ennill mynediad i'r data ar y clipfwrdd yn uniongyrchol. Mae angen i chi ei ludo eto i'r ffenestr hon.","title":"Gludo"},"contextmenu":{"options":"Opsiynau Dewislen Cyd-destun"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Cyfangu'r Bar Offer","toolbarExpand":"Ehangu'r Bar Offer","toolbarGroups":{"document":"Dogfen","clipboard":"Clipfwrdd/Dadwneud","editing":"Golygu","forms":"Ffurflenni","basicstyles":"Arddulliau Sylfaenol","paragraph":"Paragraff","links":"Dolenni","insert":"Mewnosod","styles":"Arddulliau","colors":"Lliwiau","tools":"Offer"},"toolbars":"Bariau offer y golygydd"},"elementspath":{"eleLabel":"Llwybr elfennau","eleTitle":"Elfen %1"},"format":{"label":"Fformat","panelTitle":"Fformat Paragraff","tag_address":"Cyfeiriad","tag_div":"Normal (DIV)","tag_h1":"Pennawd 1","tag_h2":"Pennawd 2","tag_h3":"Pennawd 3","tag_h4":"Pennawd 4","tag_h5":"Pennawd 5","tag_h6":"Pennawd 6","tag_p":"Normal","tag_pre":"Wedi'i Fformatio"},"horizontalrule":{"toolbar":"Mewnosod Llinell Lorweddol"},"image":{"alertUrl":"Rhowch URL y ddelwedd","alt":"Testun Amgen","border":"Ymyl","btnUpload":"Anfon i'r Gweinydd","button2Img":"Ydych am drawsffurfio'r botwm ddelwedd hwn ar ddelwedd syml?","hSpace":"BwlchLl","img2Button":"Ydych am drawsffurfio'r ddelwedd hon ar fotwm delwedd?","infoTab":"Gwyb Delwedd","linkTab":"Dolen","lockRatio":"Cloi Cymhareb","menu":"Priodweddau Delwedd","resetSize":"Ailosod Maint","title":"Priodweddau Delwedd","titleButton":"Priodweddau Botwm Delwedd","upload":"Lanlwytho","urlMissing":"URL gwreiddiol y ddelwedd ar goll.","vSpace":"BwlchF","validateBorder":"Rhaid i'r ymyl fod yn gyfanrif.","validateHSpace":"Rhaid i'r HSpace fod yn gyfanrif.","validateVSpace":"Rhaid i'r VSpace fod yn gyfanrif."},"indent":{"indent":"Cynyddu'r Mewnoliad","outdent":"Lleihau'r Mewnoliad"},"fakeobjects":{"anchor":"Angor","flash":"Animeiddiant Flash","hiddenfield":"Maes Cudd","iframe":"IFrame","unknown":"Gwrthrych Anhysbys"},"link":{"acccessKey":"Allwedd Mynediad","advanced":"Uwch","advisoryContentType":"Math y Cynnwys Cynghorol","advisoryTitle":"Teitl Cynghorol","anchor":{"toolbar":"Angor","menu":"Golygu'r Angor","title":"Priodweddau'r Angor","name":"Enw'r Angor","errorName":"Teipiwch enw'r angor","remove":"Tynnwch yr Angor"},"anchorId":"Gan Id yr Elfen","anchorName":"Gan Enw'r Angor","charset":"Set Nodau'r Adnodd Cysylltiedig","cssClasses":"Dosbarthiadau Dalen Arddull","emailAddress":"Cyfeiriad E-Bost","emailBody":"Corff y Neges","emailSubject":"Testun y Neges","id":"Id","info":"Gwyb y Ddolen","langCode":"Cod Iaith","langDir":"Cyfeiriad Iaith","langDirLTR":"Chwith i'r Dde (LTR)","langDirRTL":"Dde i'r Chwith (RTL)","menu":"Golygu Dolen","name":"Enw","noAnchors":"(Dim angorau ar gael yn y ddogfen)","noEmail":"Teipiwch gyfeiriad yr e-bost","noUrl":"Teipiwch URL y ddolen","other":"<eraill>","popupDependent":"Dibynnol (Netscape)","popupFeatures":"Nodweddion Ffenestr Bop","popupFullScreen":"Sgrin Llawn (IE)","popupLeft":"Safle Chwith","popupLocationBar":"Bar Safle","popupMenuBar":"Dewislen","popupResizable":"Ailfeintiol","popupScrollBars":"Barrau Sgrolio","popupStatusBar":"Bar Statws","popupToolbar":"Bar Offer","popupTop":"Safle Top","rel":"Perthynas","selectAnchor":"Dewiswch Angor","styles":"Arddull","tabIndex":"Indecs Tab","target":"Targed","targetFrame":"<ffrâm>","targetFrameName":"Enw Ffrâm y Targed","targetPopup":"<ffenestr bop>","targetPopupName":"Enw Ffenestr Bop","title":"Dolen","toAnchor":"Dolen at angor yn y testun","toEmail":"E-bost","toUrl":"URL","toolbar":"Dolen","type":"Math y Ddolen","unlink":"Datgysylltu","upload":"Lanlwytho"},"list":{"bulletedlist":"Mewnosod/Tynnu Rhestr Bwled","numberedlist":"Mewnosod/Tynnu Rhestr Rhifol"},"magicline":{"title":"Mewnosod paragraff yma"},"maximize":{"maximize":"Mwyhau","minimize":"Lleihau"},"pastetext":{"button":"Gludo fel testun plaen","title":"Gludo fel Testun Plaen"},"pastefromword":{"confirmCleanup":"Mae'r testun rydych chi am ludo wedi'i gopïo o Word. Ydych chi am ei lanhau cyn ei ludo?","error":"Doedd dim modd glanhau y data a ludwyd oherwydd gwall mewnol","title":"Gludo o Word","toolbar":"Gludo o Word"},"removeformat":{"toolbar":"Tynnu Fformat"},"sourcearea":{"toolbar":"HTML"},"specialchar":{"options":"Opsiynau Nodau Arbennig","title":"Dewis Nod Arbennig","toolbar":"Mewnosod Nod Arbennig"},"scayt":{"btn_about":"Ynghylch SCAYT","btn_dictionaries":"Geiriaduron","btn_disable":"Analluogi SCAYT","btn_enable":"Galluogi SCAYT","btn_langs":"Ieithoedd","btn_options":"Opsiynau","text_title":"Gwirio'r Sillafu Wrth Deipio"},"stylescombo":{"label":"Arddulliau","panelTitle":"Arddulliau Fformatio","panelTitle1":"Arddulliau Bloc","panelTitle2":"Arddulliau Mewnol","panelTitle3":"Arddulliau Gwrthrych"},"table":{"border":"Maint yr Ymyl","caption":"Pennawd","cell":{"menu":"Cell","insertBefore":"Mewnosod Cell Cyn","insertAfter":"Mewnosod Cell Ar Ôl","deleteCell":"Dileu Celloedd","merge":"Cyfuno Celloedd","mergeRight":"Cyfuno i'r Dde","mergeDown":"Cyfuno i Lawr","splitHorizontal":"Hollti'r Gell yn Lorweddol","splitVertical":"Hollti'r Gell yn Fertigol","title":"Priodweddau'r Gell","cellType":"Math y Gell","rowSpan":"Rhychwant Rhesi","colSpan":"Rhychwant Colofnau","wordWrap":"Lapio Geiriau","hAlign":"Aliniad Llorweddol","vAlign":"Aliniad Fertigol","alignBaseline":"Baslinell","bgColor":"Lliw Cefndir","borderColor":"Lliw Ymyl","data":"Data","header":"Pennyn","yes":"Ie","no":"Na","invalidWidth":"Mae'n rhaid i led y gell fod yn rhif.","invalidHeight":"Mae'n rhaid i uchder y gell fod yn rhif.","invalidRowSpan":"Mae'n rhaid i rychwant y rhesi fod yn gyfanrif.","invalidColSpan":"Mae'n rhaid i rychwant y colofnau fod yn gyfanrif.","chooseColor":"Dewis"},"cellPad":"Padio'r gell","cellSpace":"Bylchiad y gell","column":{"menu":"Colofn","insertBefore":"Mewnosod Colofn Cyn","insertAfter":"Mewnosod Colofn Ar Ôl","deleteColumn":"Dileu Colofnau"},"columns":"Colofnau","deleteTable":"Dileu Tabl","headers":"Penynnau","headersBoth":"Y Ddau","headersColumn":"Colofn gyntaf","headersNone":"Dim","headersRow":"Rhes gyntaf","invalidBorder":"Mae'n rhaid i faint yr ymyl fod yn rhif.","invalidCellPadding":"Mae'n rhaid i badiad y gell fod yn rhif positif.","invalidCellSpacing":"Mae'n rhaid i fylchiad y gell fod yn rhif positif.","invalidCols":"Mae'n rhaid cael o leiaf un golofn.","invalidHeight":"Mae'n rhaid i uchder y tabl fod yn rhif.","invalidRows":"Mae'n rhaid cael o leiaf un rhes.","invalidWidth":"Mae'n rhaid i led y tabl fod yn rhif.","menu":"Priodweddau'r Tabl","row":{"menu":"Rhes","insertBefore":"Mewnosod Rhes Cyn","insertAfter":"Mewnosod Rhes Ar Ôl","deleteRow":"Dileu Rhesi"},"rows":"Rhesi","summary":"Crynodeb","title":"Priodweddau'r Tabl","toolbar":"Tabl","widthPc":"y cant","widthPx":"picsel","widthUnit":"uned lled"},"undo":{"redo":"Ailwneud","undo":"Dadwneud"},"wsc":{"btnIgnore":"Anwybyddu Un","btnIgnoreAll":"Anwybyddu Pob","btnReplace":"Amnewid Un","btnReplaceAll":"Amnewid Pob","btnUndo":"Dadwneud","changeTo":"Newid i","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Gwirydd sillafu heb ei arsefydlu. A ydych am ei lawrlwytho nawr?","manyChanges":"Gwirio sillafu wedi gorffen: Newidiwyd %1 gair","noChanges":"Gwirio sillafu wedi gorffen: Dim newidiadau","noMispell":"Gwirio sillafu wedi gorffen: Dim camsillaf.","noSuggestions":"- Dim awgrymiadau -","notAvailable":"Nid yw'r gwasanaeth hwn ar gael yn bresennol.","notInDic":"Nid i'w gael yn y geiriadur","oneChange":"Gwirio sillafu wedi gorffen: Newidiwyd 1 gair","progress":"Gwirio sillafu yn ar y gweill...","title":"Gwirio Sillafu","toolbar":"Gwirio Sillafu"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/da.js b/js/ckeditor/lang/da.js
new file mode 100644
index 0000000..ab40a44
--- /dev/null
+++ b/js/ckeditor/lang/da.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['da']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Tryk ALT 0 for hjælp","browseServer":"Gennemse...","url":"URL","protocol":"Protokol","upload":"Upload","uploadSubmit":"Upload","image":"Indsæt billede","flash":"Indsæt Flash","form":"Indsæt formular","checkbox":"Indsæt afkrydsningsfelt","radio":"Indsæt alternativknap","textField":"Indsæt tekstfelt","textarea":"Indsæt tekstboks","hiddenField":"Indsæt skjult felt","button":"Indsæt knap","select":"Indsæt liste","imageButton":"Indsæt billedknap","notSet":"<intet valgt>","id":"Id","name":"Navn","langDir":"Tekstretning","langDirLtr":"Fra venstre mod højre (LTR)","langDirRtl":"Fra højre mod venstre (RTL)","langCode":"Sprogkode","longDescr":"Udvidet beskrivelse","cssClass":"Typografiark (CSS)","advisoryTitle":"Titel","cssStyle":"Typografi (CSS)","ok":"OK","cancel":"Annullér","close":"Luk","preview":"Forhåndsvisning","resize":"Træk for at skalere","generalTab":"Generelt","advancedTab":"Avanceret","validateNumberFailed":"Værdien er ikke et tal.","confirmNewPage":"Alt indhold, der ikke er blevet gemt, vil gå tabt. Er du sikker på, at du vil indlæse en ny side?","confirmCancel":"Nogle af indstillingerne er blevet ændret. Er du sikker på, at du vil lukke vinduet?","options":"Vis muligheder","target":"Mål","targetNew":"Nyt vindue (_blank)","targetTop":"Øverste vindue (_top)","targetSelf":"Samme vindue (_self)","targetParent":"Samme vindue (_parent)","langDirLTR":"Venstre til højre (LTR)","langDirRTL":"Højre til venstre (RTL)","styles":"Style","cssClasses":"Stylesheetklasser","width":"Bredde","height":"Højde","align":"Justering","alignLeft":"Venstre","alignRight":"Højre","alignCenter":"Centreret","alignJustify":"Lige margener","alignTop":"Øverst","alignMiddle":"Centreret","alignBottom":"Nederst","alignNone":"Ingen","invalidValue":"Ugyldig værdi.","invalidHeight":"Højde skal være et tal.","invalidWidth":"Bredde skal være et tal.","invalidCssLength":"Værdien specificeret for \"%1\" feltet skal være et positivt nummer med eller uden en CSS måleenhed (px, %, in, cm, mm, em, ex, pt, eller pc).","invalidHtmlLength":"Værdien specificeret for \"%1\" feltet skal være et positivt nummer med eller uden en CSS måleenhed (px eller %).","invalidInlineStyle":"Værdien specificeret for inline style skal indeholde en eller flere elementer med et format som \"name:value\", separeret af semikoloner","cssLengthTooltip":"Indsæt en numerisk værdi i pixel eller nummer med en gyldig CSS værdi (px, %, in, cm, mm, em, ex, pt, eller pc).","unavailable":"%1<span class=\"cke_accessibility\">, ikke tilgængelig</span>"},"about":{"copy":"Copyright &copy; $1. Alle rettigheder forbeholdes.","dlgTitle":"Om CKEditor","help":"Se $1 for at få hjælp.","moreInfo":"For informationer omkring licens, se venligst vores hjemmeside (på engelsk):","title":"Om CKEditor","userGuide":"CKEditor-brugermanual"},"basicstyles":{"bold":"Fed","italic":"Kursiv","strike":"Gennemstreget","subscript":"Sænket skrift","superscript":"Hævet skrift","underline":"Understreget"},"blockquote":{"toolbar":"Blokcitat"},"clipboard":{"copy":"Kopiér","copyError":"Din browsers sikkerhedsindstillinger tillader ikke editoren at få automatisk adgang til udklipsholderen.<br><br>Brug i stedet tastaturet til at kopiere teksten (Ctrl/Cmd+C).","cut":"Klip","cutError":"Din browsers sikkerhedsindstillinger tillader ikke editoren at få automatisk adgang til udklipsholderen.<br><br>Brug i stedet tastaturet til at klippe teksten (Ctrl/Cmd+X).","paste":"Indsæt","pasteArea":"Indsæt område","pasteMsg":"Indsæt i feltet herunder (<STRONG>Ctrl/Cmd+V</STRONG>) og klik på <STRONG>OK</STRONG>.","securityMsg":"Din browsers sikkerhedsindstillinger tillader ikke editoren at få automatisk adgang til udklipsholderen.<br><br>Du skal indsætte udklipsholderens indhold i dette vindue igen.","title":"Indsæt"},"contextmenu":{"options":"Muligheder for hjælpemenu"},"button":{"selectedLabel":"%1 (Valgt)"},"toolbar":{"toolbarCollapse":"Sammenklap værktøjslinje","toolbarExpand":"Udvid værktøjslinje","toolbarGroups":{"document":"Dokument","clipboard":"Udklipsholder/Fortryd","editing":"Redigering","forms":"Formularer","basicstyles":"Basis styles","paragraph":"Paragraf","links":"Links","insert":"Indsæt","styles":"Typografier","colors":"Farver","tools":"Værktøjer"},"toolbars":"Editors værktøjslinjer"},"elementspath":{"eleLabel":"Sti på element","eleTitle":"%1 element"},"format":{"label":"Formatering","panelTitle":"Formatering","tag_address":"Adresse","tag_div":"Normal (DIV)","tag_h1":"Overskrift 1","tag_h2":"Overskrift 2","tag_h3":"Overskrift 3","tag_h4":"Overskrift 4","tag_h5":"Overskrift 5","tag_h6":"Overskrift 6","tag_p":"Normal","tag_pre":"Formateret"},"horizontalrule":{"toolbar":"Indsæt vandret streg"},"image":{"alertUrl":"Indtast stien til billedet","alt":"Alternativ tekst","border":"Ramme","btnUpload":"Upload fil til serveren","button2Img":"Vil du lave billedknappen om til et almindeligt billede?","hSpace":"Vandret margen","img2Button":"Vil du lave billedet om til en billedknap?","infoTab":"Generelt","linkTab":"Hyperlink","lockRatio":"Lås størrelsesforhold","menu":"Egenskaber for billede","resetSize":"Nulstil størrelse","title":"Egenskaber for billede","titleButton":"Egenskaber for billedknap","upload":"Upload","urlMissing":"Kilde på billed-URL mangler","vSpace":"Lodret margen","validateBorder":"Kant skal være et helt nummer.","validateHSpace":"HSpace skal være et helt nummer.","validateVSpace":"VSpace skal være et helt nummer."},"indent":{"indent":"Forøg indrykning","outdent":"Formindsk indrykning"},"fakeobjects":{"anchor":"Anker","flash":"Flashanimation","hiddenfield":"Skjult felt","iframe":"Iframe","unknown":"Ukendt objekt"},"link":{"acccessKey":"Genvejstast","advanced":"Avanceret","advisoryContentType":"Indholdstype","advisoryTitle":"Titel","anchor":{"toolbar":"Indsæt/redigér bogmærke","menu":"Egenskaber for bogmærke","title":"Egenskaber for bogmærke","name":"Bogmærkenavn","errorName":"Indtast bogmærkenavn","remove":"Fjern bogmærke"},"anchorId":"Efter element-Id","anchorName":"Efter ankernavn","charset":"Tegnsæt","cssClasses":"Typografiark","emailAddress":"E-mailadresse","emailBody":"Besked","emailSubject":"Emne","id":"Id","info":"Generelt","langCode":"Tekstretning","langDir":"Tekstretning","langDirLTR":"Fra venstre mod højre (LTR)","langDirRTL":"Fra højre mod venstre (RTL)","menu":"Redigér hyperlink","name":"Navn","noAnchors":"(Ingen bogmærker i dokumentet)","noEmail":"Indtast e-mailadresse!","noUrl":"Indtast hyperlink-URL!","other":"<anden>","popupDependent":"Koblet/dependent (Netscape)","popupFeatures":"Egenskaber for popup","popupFullScreen":"Fuld skærm (IE)","popupLeft":"Position fra venstre","popupLocationBar":"Adresselinje","popupMenuBar":"Menulinje","popupResizable":"Justérbar","popupScrollBars":"Scrollbar","popupStatusBar":"Statuslinje","popupToolbar":"Værktøjslinje","popupTop":"Position fra toppen","rel":"Relation","selectAnchor":"Vælg et anker","styles":"Typografi","tabIndex":"Tabulatorindeks","target":"Mål","targetFrame":"<ramme>","targetFrameName":"Destinationsvinduets navn","targetPopup":"<popup vindue>","targetPopupName":"Popupvinduets navn","title":"Egenskaber for hyperlink","toAnchor":"Bogmærke på denne side","toEmail":"E-mail","toUrl":"URL","toolbar":"Indsæt/redigér hyperlink","type":"Type","unlink":"Fjern hyperlink","upload":"Upload"},"list":{"bulletedlist":"Punktopstilling","numberedlist":"Talopstilling"},"magicline":{"title":"Indsæt afsnit"},"maximize":{"maximize":"Maksimér","minimize":"Minimér"},"pastetext":{"button":"Indsæt som ikke-formateret tekst","title":"Indsæt som ikke-formateret tekst"},"pastefromword":{"confirmCleanup":"Den tekst du forsøger at indsætte ser ud til at komme fra Word. Vil du rense teksten før den indsættes?","error":"Det var ikke muligt at fjerne formatteringen på den indsatte tekst grundet en intern fejl","title":"Indsæt fra Word","toolbar":"Indsæt fra Word"},"removeformat":{"toolbar":"Fjern formatering"},"sourcearea":{"toolbar":"Kilde"},"specialchar":{"options":"Muligheder for specialkarakterer","title":"Vælg symbol","toolbar":"Indsæt symbol"},"scayt":{"btn_about":"Om SCAYT","btn_dictionaries":"Ordbøger","btn_disable":"Deaktivér SCAYT","btn_enable":"Aktivér SCAYT","btn_langs":"Sprog","btn_options":"Indstillinger","text_title":"Stavekontrol mens du skriver"},"stylescombo":{"label":"Typografi","panelTitle":"Formattering på stylesheet","panelTitle1":"Block typografi","panelTitle2":"Inline typografi","panelTitle3":"Object typografi"},"table":{"border":"Rammebredde","caption":"Titel","cell":{"menu":"Celle","insertBefore":"Indsæt celle før","insertAfter":"Indsæt celle efter","deleteCell":"Slet celle","merge":"Flet celler","mergeRight":"Flet til højre","mergeDown":"Flet nedad","splitHorizontal":"Del celle vandret","splitVertical":"Del celle lodret","title":"Celleegenskaber","cellType":"Celletype","rowSpan":"Række span (rows span)","colSpan":"Kolonne span (columns span)","wordWrap":"Tekstombrydning","hAlign":"Vandret justering","vAlign":"Lodret justering","alignBaseline":"Grundlinje","bgColor":"Baggrundsfarve","borderColor":"Rammefarve","data":"Data","header":"Hoved","yes":"Ja","no":"Nej","invalidWidth":"Cellebredde skal være et tal.","invalidHeight":"Cellehøjde skal være et tal.","invalidRowSpan":"Række span skal være et heltal.","invalidColSpan":"Kolonne span skal være et heltal.","chooseColor":"Vælg"},"cellPad":"Cellemargen","cellSpace":"Celleafstand","column":{"menu":"Kolonne","insertBefore":"Indsæt kolonne før","insertAfter":"Indsæt kolonne efter","deleteColumn":"Slet kolonne"},"columns":"Kolonner","deleteTable":"Slet tabel","headers":"Hoved","headersBoth":"Begge","headersColumn":"Første kolonne","headersNone":"Ingen","headersRow":"Første række","invalidBorder":"Rammetykkelse skal være et tal.","invalidCellPadding":"Cellemargen skal være et tal.","invalidCellSpacing":"Celleafstand skal være et tal.","invalidCols":"Antallet af kolonner skal være større end 0.","invalidHeight":"Tabelhøjde skal være et tal.","invalidRows":"Antallet af rækker skal være større end 0.","invalidWidth":"Tabelbredde skal være et tal.","menu":"Egenskaber for tabel","row":{"menu":"Række","insertBefore":"Indsæt række før","insertAfter":"Indsæt række efter","deleteRow":"Slet række"},"rows":"Rækker","summary":"Resumé","title":"Egenskaber for tabel","toolbar":"Tabel","widthPc":"procent","widthPx":"pixels","widthUnit":"Bredde på enhed"},"undo":{"redo":"Annullér fortryd","undo":"Fortryd"},"wsc":{"btnIgnore":"Ignorér","btnIgnoreAll":"Ignorér alle","btnReplace":"Erstat","btnReplaceAll":"Erstat alle","btnUndo":"Tilbage","changeTo":"Forslag","errorLoading":"Fejl ved indlæsning af host: %s.","ieSpellDownload":"Stavekontrol ikke installeret. Vil du installere den nu?","manyChanges":"Stavekontrol færdig: %1 ord ændret","noChanges":"Stavekontrol færdig: Ingen ord ændret","noMispell":"Stavekontrol færdig: Ingen fejl fundet","noSuggestions":"(ingen forslag)","notAvailable":"Stavekontrol er desværre ikke tilgængelig.","notInDic":"Ikke i ordbogen","oneChange":"Stavekontrol færdig: Et ord ændret","progress":"Stavekontrollen arbejder...","title":"Stavekontrol","toolbar":"Stavekontrol"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/de.js b/js/ckeditor/lang/de.js
new file mode 100644
index 0000000..088c6e3
--- /dev/null
+++ b/js/ckeditor/lang/de.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['de']={"editor":"WYSIWYG-Editor","editorPanel":"WYSIWYG-Editor-Leiste","common":{"editorHelp":"Drücken Sie ALT 0 für Hilfe","browseServer":"Server durchsuchen","url":"URL","protocol":"Protokoll","upload":"Hochladen","uploadSubmit":"Zum Server senden","image":"Bild","flash":"Flash","form":"Formular","checkbox":"Checkbox","radio":"Radiobutton","textField":"Textfeld einzeilig","textarea":"Textfeld mehrzeilig","hiddenField":"Verstecktes Feld","button":"Klickbutton","select":"Auswahlfeld","imageButton":"Bildbutton","notSet":"<nichts>","id":"ID","name":"Name","langDir":"Schreibrichtung","langDirLtr":"Links nach Rechts (LTR)","langDirRtl":"Rechts nach Links (RTL)","langCode":"Sprachenkürzel","longDescr":"Langform URL","cssClass":"Stylesheet Klasse","advisoryTitle":"Titel Beschreibung","cssStyle":"Style","ok":"OK","cancel":"Abbrechen","close":"Schließen","preview":"Vorschau","resize":"Zum Vergrößern ziehen","generalTab":"Allgemein","advancedTab":"Erweitert","validateNumberFailed":"Dieser Wert ist keine Nummer.","confirmNewPage":"Alle nicht gespeicherten Änderungen gehen verlohren. Sind Sie sicher die neue Seite zu laden?","confirmCancel":"Einige Optionen wurden geändert. Wollen Sie den Dialog dennoch schließen?","options":"Optionen","target":"Zielseite","targetNew":"Neues Fenster (_blank)","targetTop":"Oberstes Fenster (_top)","targetSelf":"Gleiches Fenster (_self)","targetParent":"Oberes Fenster (_parent)","langDirLTR":"Links nach Rechts (LNR)","langDirRTL":"Rechts nach Links (RNL)","styles":"Style","cssClasses":"Stylesheet Klasse","width":"Breite","height":"Höhe","align":"Ausrichtung","alignLeft":"Links","alignRight":"Rechts","alignCenter":"Zentriert","alignJustify":"Blocksatz","alignTop":"Oben","alignMiddle":"Mitte","alignBottom":"Unten","alignNone":"Keine","invalidValue":"Ungültiger Wert.","invalidHeight":"Höhe muss eine Zahl sein.","invalidWidth":"Breite muss eine Zahl sein.","invalidCssLength":"Wert spezifiziert für \"%1\" Feld muss ein positiver numerischer Wert sein mit oder ohne korrekte CSS Messeinheit (px, %, in, cm, mm, em, ex, pt oder pc).","invalidHtmlLength":"Wert spezifiziert für \"%1\" Feld muss ein positiver numerischer Wert sein mit oder ohne korrekte HTML Messeinheit (px oder %).","invalidInlineStyle":"Wert spezifiziert für inline Stilart muss enthalten ein oder mehr Tupels mit dem Format \"Name : Wert\" getrennt mit Semikolons.","cssLengthTooltip":"Gebe eine Zahl ein für ein Wert in pixels oder eine Zahl mit einer korrekten CSS Messeinheit (px, %, in, cm, mm, em, ex, pt oder pc).","unavailable":"%1<span class=\"cke_accessibility\">, nicht verfügbar</span>"},"about":{"copy":"Copyright &copy; $1. Alle Rechte vorbehalten.","dlgTitle":"Über CKEditor","help":"Prüfe $1 für Hilfe.","moreInfo":"Für Informationen über unsere Lizenzbestimmungen besuchen sie bitte unsere Webseite:","title":"Über CKEditor","userGuide":"CKEditor Benutzerhandbuch"},"basicstyles":{"bold":"Fett","italic":"Kursiv","strike":"Durchgestrichen","subscript":"Tiefgestellt","superscript":"Hochgestellt","underline":"Unterstrichen"},"blockquote":{"toolbar":"Zitatblock"},"clipboard":{"copy":"Kopieren","copyError":"Die Sicherheitseinstellungen Ihres Browsers lassen es nicht zu, den Text automatisch kopieren. Bitte benutzen Sie die System-Zwischenablage über STRG-C (kopieren).","cut":"Ausschneiden","cutError":"Die Sicherheitseinstellungen Ihres Browsers lassen es nicht zu, den Text automatisch auszuschneiden. Bitte benutzen Sie die System-Zwischenablage über STRG-X (ausschneiden) und STRG-V (einfügen).","paste":"Einfügen","pasteArea":"Einfügebereich","pasteMsg":"Bitte fügen Sie den Text in der folgenden Box über die Tastatur (mit <STRONG>Strg+V</STRONG>) ein und bestätigen Sie mit <STRONG>OK</STRONG>.","securityMsg":"Aufgrund von Sicherheitsbeschränkungen Ihres Browsers kann der Editor nicht direkt auf die Zwischenablage zugreifen. Bitte fügen Sie den Inhalt erneut in diesem Fenster ein.","title":"Einfügen"},"contextmenu":{"options":"Kontextmenü Optionen"},"button":{"selectedLabel":"%1 (Ausgewählt)"},"toolbar":{"toolbarCollapse":"Symbolleiste einklappen","toolbarExpand":"Symbolleiste ausklappen","toolbarGroups":{"document":"Dokument","clipboard":"Zwischenablage/Rückgängig","editing":"Editieren","forms":"Formularen","basicstyles":"Grundstile","paragraph":"Absatz","links":"Links","insert":"Einfügen","styles":"Stile","colors":"Farben","tools":"Werkzeuge"},"toolbars":"Editor Symbolleisten"},"elementspath":{"eleLabel":"Elements Pfad","eleTitle":"%1 Element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Addresse","tag_div":"Normal (DIV)","tag_h1":"Überschrift 1","tag_h2":"Überschrift 2","tag_h3":"Überschrift 3","tag_h4":"Überschrift 4","tag_h5":"Überschrift 5","tag_h6":"Überschrift 6","tag_p":"Normal","tag_pre":"Formatiert"},"horizontalrule":{"toolbar":"Horizontale Linie einfügen"},"image":{"alertUrl":"Bitte geben Sie die Bild-URL an","alt":"Alternativer Text","border":"Rahmen","btnUpload":"Zum Server senden","button2Img":"Möchten Sie den gewählten Bild-Button in ein einfaches Bild umwandeln?","hSpace":"Horizontal-Abstand","img2Button":"Möchten Sie das gewählten Bild in einen Bild-Button umwandeln?","infoTab":"Bild-Info","linkTab":"Link","lockRatio":"Größenverhältnis beibehalten","menu":"Bild-Eigenschaften","resetSize":"Größe zurücksetzen","title":"Bild-Eigenschaften","titleButton":"Bildbutton-Eigenschaften","upload":"Hochladen","urlMissing":"Imagequelle URL fehlt.","vSpace":"Vertikal-Abstand","validateBorder":"Rahmen muß eine ganze Zahl sein.","validateHSpace":"Horizontal-Abstand muß eine ganze Zahl sein.","validateVSpace":"Vertikal-Abstand muß eine ganze Zahl sein."},"indent":{"indent":"Einzug erhöhen","outdent":"Einzug verringern"},"fakeobjects":{"anchor":"Anker","flash":"Flash Animation","hiddenfield":"Verstecktes Feld","iframe":"IFrame","unknown":"Unbekanntes Objekt"},"link":{"acccessKey":"Zugriffstaste","advanced":"Erweitert","advisoryContentType":"Inhaltstyp","advisoryTitle":"Titel Beschreibung","anchor":{"toolbar":"Anker einfügen/editieren","menu":"Anker-Eigenschaften","title":"Anker-Eigenschaften","name":"Anker Name","errorName":"Bitte geben Sie den Namen des Ankers ein","remove":"Anker entfernen"},"anchorId":"nach Element Id","anchorName":"nach Anker Name","charset":"Ziel-Zeichensatz","cssClasses":"Stylesheet Klasse","emailAddress":"E-Mail Adresse","emailBody":"Nachrichtentext","emailSubject":"Betreffzeile","id":"Id","info":"Link-Info","langCode":"Sprachenkürzel","langDir":"Schreibrichtung","langDirLTR":"Links nach Rechts (LTR)","langDirRTL":"Rechts nach Links (RTL)","menu":"Link editieren","name":"Name","noAnchors":"(keine Anker im Dokument vorhanden)","noEmail":"Bitte geben Sie e-Mail Adresse an","noUrl":"Bitte geben Sie die Link-URL an","other":"<andere>","popupDependent":"Abhängig (Netscape)","popupFeatures":"Pop-up Fenster-Eigenschaften","popupFullScreen":"Vollbild (IE)","popupLeft":"Linke Position","popupLocationBar":"Adress-Leiste","popupMenuBar":"Menü-Leiste","popupResizable":"Größe änderbar","popupScrollBars":"Rollbalken","popupStatusBar":"Statusleiste","popupToolbar":"Symbolleiste","popupTop":"Obere Position","rel":"Beziehung","selectAnchor":"Anker auswählen","styles":"Style","tabIndex":"Tab-Index","target":"Zielseite","targetFrame":"<Frame>","targetFrameName":"Ziel-Fenster-Name","targetPopup":"<Pop-up Fenster>","targetPopupName":"Pop-up Fenster-Name","title":"Link","toAnchor":"Anker in dieser Seite","toEmail":"E-Mail","toUrl":"URL","toolbar":"Link einfügen/editieren","type":"Link-Typ","unlink":"Link entfernen","upload":"Hochladen"},"list":{"bulletedlist":"Liste","numberedlist":"Nummerierte Liste"},"magicline":{"title":"Absatz hier einfügen"},"maximize":{"maximize":"Maximieren","minimize":"Minimieren"},"pastetext":{"button":"Als Text einfügen","title":"Als Text einfügen"},"pastefromword":{"confirmCleanup":"Der Text, den Sie einfügen möchten, scheint aus MS-Word kopiert zu sein. Möchten Sie ihn zuvor bereinigen lassen?","error":"Aufgrund eines internen Fehlers war es nicht möglich die eingefügten Daten zu bereinigen","title":"Aus MS-Word einfügen","toolbar":"Aus MS-Word einfügen"},"removeformat":{"toolbar":"Formatierungen entfernen"},"sourcearea":{"toolbar":"Quellcode"},"specialchar":{"options":"Sonderzeichen Optionen","title":"Sonderzeichen auswählen","toolbar":"Sonderzeichen einfügen/editieren"},"scayt":{"btn_about":"Über SCAYT","btn_dictionaries":"Wörterbücher","btn_disable":"SCAYT ausschalten","btn_enable":"SCAYT einschalten","btn_langs":"Sprachen","btn_options":"Optionen","text_title":"Rechtschreibprüfung während der Texteingabe (SCAYT)"},"stylescombo":{"label":"Stil","panelTitle":"Formatierungsstile","panelTitle1":"Block Stilart","panelTitle2":"Inline Stilart","panelTitle3":"Objekt Stilart"},"table":{"border":"Rahmen","caption":"Überschrift","cell":{"menu":"Zelle","insertBefore":"Zelle davor einfügen","insertAfter":"Zelle danach einfügen","deleteCell":"Zelle löschen","merge":"Zellen verbinden","mergeRight":"Nach rechts verbinden","mergeDown":"Nach unten verbinden","splitHorizontal":"Zelle horizontal teilen","splitVertical":"Zelle vertikal teilen","title":"Zellen-Eigenschaften","cellType":"Zellart","rowSpan":"Anzahl Zeilen verbinden","colSpan":"Anzahl Spalten verbinden","wordWrap":"Zeilenumbruch","hAlign":"Horizontale Ausrichtung","vAlign":"Vertikale Ausrichtung","alignBaseline":"Grundlinie","bgColor":"Hintergrundfarbe","borderColor":"Rahmenfarbe","data":"Daten","header":"Überschrift","yes":"Ja","no":"Nein","invalidWidth":"Zellenbreite muß eine Zahl sein.","invalidHeight":"Zellenhöhe muß eine Zahl sein.","invalidRowSpan":"\"Anzahl Zeilen verbinden\" muss eine Ganzzahl sein.","invalidColSpan":"\"Anzahl Spalten verbinden\" muss eine Ganzzahl sein.","chooseColor":"Wählen"},"cellPad":"Zellenabstand innen","cellSpace":"Zellenabstand außen","column":{"menu":"Spalte","insertBefore":"Spalte links davor einfügen","insertAfter":"Spalte rechts danach einfügen","deleteColumn":"Spalte löschen"},"columns":"Spalte","deleteTable":"Tabelle löschen","headers":"Kopfzeile","headersBoth":"Beide","headersColumn":"Erste Spalte","headersNone":"Keine","headersRow":"Erste Zeile","invalidBorder":"Die Rahmenbreite muß eine Zahl sein.","invalidCellPadding":"Der Zellenabstand innen muß eine positive Zahl sein.","invalidCellSpacing":"Der Zellenabstand außen muß eine positive Zahl sein.","invalidCols":"Die Anzahl der Spalten muß größer als 0 sein..","invalidHeight":"Die Tabellenbreite muß eine Zahl sein.","invalidRows":"Die Anzahl der Zeilen muß größer als 0 sein.","invalidWidth":"Die Tabellenbreite muss eine Zahl sein.","menu":"Tabellen-Eigenschaften","row":{"menu":"Zeile","insertBefore":"Zeile oberhalb einfügen","insertAfter":"Zeile unterhalb einfügen","deleteRow":"Zeile entfernen"},"rows":"Zeile","summary":"Inhaltsübersicht","title":"Tabellen-Eigenschaften","toolbar":"Tabelle","widthPc":"%","widthPx":"Pixel","widthUnit":"Breite Einheit"},"undo":{"redo":"Wiederherstellen","undo":"Rückgängig"},"wsc":{"btnIgnore":"Ignorieren","btnIgnoreAll":"Alle Ignorieren","btnReplace":"Ersetzen","btnReplaceAll":"Alle Ersetzen","btnUndo":"Rückgängig","changeTo":"Ändern in","errorLoading":"Fehler beim laden des Dienstanbieters: %s.","ieSpellDownload":"Rechtschreibprüfung nicht installiert. Möchten Sie sie jetzt herunterladen?","manyChanges":"Rechtschreibprüfung abgeschlossen - %1 Wörter geändert","noChanges":"Rechtschreibprüfung abgeschlossen - keine Worte geändert","noMispell":"Rechtschreibprüfung abgeschlossen - keine Fehler gefunden","noSuggestions":" - keine Vorschläge - ","notAvailable":"Entschuldigung, aber dieser Dienst steht im Moment nicht zur Verfügung.","notInDic":"Nicht im Wörterbuch","oneChange":"Rechtschreibprüfung abgeschlossen - ein Wort geändert","progress":"Rechtschreibprüfung läuft...","title":"Rechtschreibprüfung","toolbar":"Rechtschreibprüfung"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/el.js b/js/ckeditor/lang/el.js
new file mode 100644
index 0000000..c6f4510
--- /dev/null
+++ b/js/ckeditor/lang/el.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['el']={"editor":"ΕπεξεÏγαστής ΠλοÏσιου Κειμένου","editorPanel":"Πίνακας ΕπεξεÏγαστή ΠλοÏσιου Κειμένου","common":{"editorHelp":"Πατήστε το ALT 0 για βοήθεια","browseServer":"ΕξεÏεÏνηση Διακομιστή","url":"URL","protocol":"ΠÏωτόκολλο","upload":"Αποστολή","uploadSubmit":"Αποστολή στον Διακομιστή","image":"Εικόνα","flash":"Flash","form":"ΦόÏμα","checkbox":"Κουτί Επιλογής","radio":"Κουμπί Επιλογής","textField":"Πεδίο Κειμένου","textarea":"ΠεÏιοχή Κειμένου","hiddenField":"ΚÏυφό Πεδίο","button":"Κουμπί","select":"Πεδίο Επιλογής","imageButton":"Κουμπί Εικόνας","notSet":"<δεν έχει Ïυθμιστεί>","id":"Id","name":"Όνομα","langDir":"ΚατεÏθυνση Κειμένου","langDirLtr":"ΑÏιστεÏά Ï€Ïος Δεξιά (LTR)","langDirRtl":"Δεξιά Ï€Ïος ΑÏιστεÏά (RTL)","langCode":"Κωδικός Γλώσσας","longDescr":"Αναλυτική ΠεÏιγÏαφή URL","cssClass":"Κλάσεις ΦÏλλων Στυλ","advisoryTitle":"Ενδεικτικός Τίτλος","cssStyle":"ΜοÏφή Κειμένου","ok":"OK","cancel":"ΑκÏÏωση","close":"Κλείσιμο","preview":"ΠÏοεπισκόπηση","resize":"Αλλαγή Μεγέθους","generalTab":"Γενικά","advancedTab":"Για ΠÏοχωÏημένους","validateNumberFailed":"Αυτή η τιμή δεν είναι αÏιθμός.","confirmNewPage":"Οι όποιες αλλαγές στο πεÏιεχόμενο θα χαθοÏν. Είσαστε σίγουÏοι ότι θέλετε να φοÏτώσετε μια νέα σελίδα;","confirmCancel":"ΜεÏικές επιλογές έχουν αλλάξει. Είσαστε σίγουÏοι ότι θέλετε να κλείσετε το παÏάθυÏο διαλόγου;","options":"Επιλογές","target":"ΠÏοοÏισμός","targetNew":"Îέο ΠαÏάθυÏο (_blank)","targetTop":"ΑÏχική ΠεÏιοχή (_top)","targetSelf":"Ίδιο ΠαÏάθυÏο (_self)","targetParent":"Γονεϊκό ΠαÏάθυÏο (_parent)","langDirLTR":"ΑÏιστεÏά Ï€Ïος Δεξιά (LTR)","langDirRTL":"Δεξιά Ï€Ïος ΑÏιστεÏά (RTL)","styles":"ΜοÏφή","cssClasses":"Κλάσεις ΦÏλλων Στυλ","width":"Πλάτος","height":"Ύψος","align":"Στοίχιση","alignLeft":"ΑÏιστεÏά","alignRight":"Δεξιά","alignCenter":"ΚέντÏο","alignJustify":"ΠλήÏης Στοίχιση","alignTop":"Πάνω","alignMiddle":"Μέση","alignBottom":"Κάτω","alignNone":"ΧωÏίς","invalidValue":"Μη έγκυÏη τιμή.","invalidHeight":"Το Ïψος Ï€Ïέπει να είναι ένας αÏιθμός.","invalidWidth":"Το πλάτος Ï€Ïέπει να είναι ένας αÏιθμός.","invalidCssLength":"Η τιμή που οÏίζεται για το πεδίο \"%1\" Ï€Ïέπει να είναι ένας θετικός αÏιθμός με ή χωÏίς μια έγκυÏη μονάδα μέτÏησης CSS (px, %, in, cm, mm, em, ex, pt, ή pc).","invalidHtmlLength":"Η τιμή που οÏίζεται για το πεδίο \"%1\" Ï€Ïέπει να είναι ένας θετικός αÏιθμός με ή χωÏίς μια έγκυÏη μονάδα μέτÏησης HTML (px ή %).","invalidInlineStyle":"Η τιμή για το εν σειÏά στυλ Ï€Ïέπει να πεÏιέχει ένα ή πεÏισσότεÏα ζεÏγη με την μοÏφή \"όνομα: τιμή\" διαχωÏισμένα με Ελληνικό εÏωτηματικό.","cssLengthTooltip":"Εισάγεται μια τιμή σε pixel ή έναν αÏιθμό μαζί με μια έγκυÏη μονάδα μέτÏησης CSS (px, %, in, cm, mm, em, ex, pt, ή pc).","unavailable":"%1<span class=\"cke_accessibility\">, δεν είναι διαθέσιμο</span>"},"about":{"copy":"Πνευματικά δικαιώματα &copy; $1 Με επιφÏλαξη παντός δικαιώματος.","dlgTitle":"ΠεÏί του CKEditor","help":"Ελέγξτε τις $1 για βοήθεια.","moreInfo":"Για πληÏοφοÏίες σχετικές με την άδεια χÏήσης, παÏακαλοÏμε επισκεφθείτε την ιστοσελίδα μας:","title":"ΠεÏί του CKEditor","userGuide":"Οδηγίες ΧÏήστη CKEditor"},"basicstyles":{"bold":"Έντονη","italic":"Πλάγια","strike":"ΔιακÏιτή ΔιαγÏαφή","subscript":"Δείκτης","superscript":"Εκθέτης","underline":"ΥπογÏάμμιση"},"blockquote":{"toolbar":"ΠεÏιοχή ΠαÏάθεσης"},"clipboard":{"copy":"ΑντιγÏαφή","copyError":"Οι Ïυθμίσεις ασφαλείας του πεÏιηγητή σας δεν επιτÏέπουν την επιλεγμένη εÏγασία αντιγÏαφής. ΠαÏακαλώ χÏησιμοποιείστε το πληκτÏολόγιο (Ctrl/Cmd+C).","cut":"Αποκοπή","cutError":"Οι Ïυθμίσεις ασφαλείας του πεÏιηγητή σας δεν επιτÏέπουν την επιλεγμένη εÏγασία αποκοπής. ΠαÏακαλώ χÏησιμοποιείστε το πληκτÏολόγιο (Ctrl/Cmd+X).","paste":"Επικόλληση","pasteArea":"ΠεÏιοχή Επικόλλησης","pasteMsg":"ΠαÏακαλώ επικολλήστε στο ακόλουθο κουτί χÏησιμοποιώντας το πληκτÏολόγιο (<strong>Ctrl/Cmd+V</strong>) και πατήστε OK.","securityMsg":"Λόγων των Ïυθμίσεων ασφάλειας του πεÏιηγητή σας, ο επεξεÏγαστής δεν μποÏεί να έχει Ï€Ïόσβαση στην μνήμη επικόλλησης. ΧÏειάζεται να επικολλήσετε ξανά σε αυτό το παÏάθυÏο.","title":"Επικόλληση"},"contextmenu":{"options":"Επιλογές Αναδυόμενου ΜενοÏ"},"button":{"selectedLabel":"%1 (Επιλεγμένο)"},"toolbar":{"toolbarCollapse":"ΣÏμπτυξη ΕÏγαλειοθήκης","toolbarExpand":"Ανάπτυξη ΕÏγαλειοθήκης","toolbarGroups":{"document":"ΈγγÏαφο","clipboard":"ΠÏόχειÏο/ΑναίÏεση","editing":"ΕπεξεÏγασία","forms":"ΦόÏμες","basicstyles":"Βασικά Στυλ","paragraph":"ΠαÏάγÏαφος","links":"ΣÏνδεσμοι","insert":"Εισαγωγή","styles":"Στυλ","colors":"ΧÏώματα","tools":"ΕÏγαλεία"},"toolbars":"ΕÏγαλειοθήκες επεξεÏγαστή"},"elementspath":{"eleLabel":"ΔιαδÏομή Στοιχείων","eleTitle":"Στοιχείο %1"},"format":{"label":"ΜοÏφοποίηση","panelTitle":"ΜοÏφοποίηση ΠαÏαγÏάφου","tag_address":"ΔιεÏθυνση","tag_div":"Κανονική (DIV)","tag_h1":"Κεφαλίδα 1","tag_h2":"Κεφαλίδα 2","tag_h3":"Κεφαλίδα 3","tag_h4":"Κεφαλίδα 4","tag_h5":"Κεφαλίδα 5","tag_h6":"Κεφαλίδα 6","tag_p":"Κανονική","tag_pre":"ΠÏο-μοÏφοποιημένη"},"horizontalrule":{"toolbar":"Εισαγωγή ΟÏιζόντιας ΓÏαμμής"},"image":{"alertUrl":"Εισάγετε την τοποθεσία (URL) της εικόνας","alt":"Εναλλακτικό Κείμενο","border":"ΠεÏίγÏαμμα","btnUpload":"Αποστολή στον Διακομιστή","button2Img":"Θέλετε να μετατÏέψετε το επιλεγμένο κουμπί εικόνας σε απλή εικόνα;","hSpace":"HSpace","img2Button":"Θέλετε να μεταμοÏφώσετε την επιλεγμένη εικόνα που είναι πάνω σε ένα κουμπί;","infoTab":"ΠληÏοφοÏίες Εικόνας","linkTab":"ΣÏνδεσμος","lockRatio":"Κλείδωμα Αναλογίας","menu":"Ιδιότητες Εικόνας","resetSize":"ΕπαναφοÏά ΑÏÏ‡Î¹ÎºÎ¿Ï ÎœÎµÎ³Î­Î¸Î¿Ï…Ï‚","title":"Ιδιότητες Εικόνας","titleButton":"Ιδιότητες ÎšÎ¿Ï…Î¼Ï€Î¹Î¿Ï Î•Î¹ÎºÏŒÎ½Î±Ï‚","upload":"Αποστολή","urlMissing":"Το URL πηγής για την εικόνα λείπει.","vSpace":"VSpace","validateBorder":"Το πεÏίγÏαμμα Ï€Ïέπει να είναι ένας ακέÏαιος αÏιθμός.","validateHSpace":"Το HSpace Ï€Ïέπει να είναι ένας ακέÏαιος αÏιθμός.","validateVSpace":"Το VSpace Ï€Ïέπει να είναι ένας ακέÏαιος αÏιθμός."},"indent":{"indent":"ΑÏξηση Εσοχής","outdent":"Μείωση Εσοχής"},"fakeobjects":{"anchor":"ΆγκυÏα","flash":"Ταινία Flash","hiddenfield":"ΚÏυφό Πεδίο","iframe":"IFrame","unknown":"Άγνωστο Αντικείμενο"},"link":{"acccessKey":"Συντόμευση","advanced":"Για ΠÏοχωÏημένους","advisoryContentType":"Ενδεικτικός ΤÏπος ΠεÏιεχομένου","advisoryTitle":"Ενδεικτικός Τίτλος","anchor":{"toolbar":"Εισαγωγή/επεξεÏγασία ΆγκυÏας","menu":"Ιδιότητες άγκυÏας","title":"Ιδιότητες άγκυÏας","name":"Όνομα άγκυÏας","errorName":"ΠαÏακαλοÏμε εισάγετε όνομα άγκυÏας","remove":"ΑφαίÏεση ΆγκυÏας"},"anchorId":"Βάσει του Element Id","anchorName":"Βάσει του Ονόματος ΆγκυÏας","charset":"Κωδικοποίηση ΧαÏακτήÏων ΠÏοσαÏτημένης Πηγής","cssClasses":"Κλάσεις ΦÏλλων Στυλ","emailAddress":"ΔιεÏθυνση E-mail","emailBody":"Κείμενο ΜηνÏματος","emailSubject":"Θέμα ΜηνÏματος","id":"Id","info":"ΠληÏοφοÏίες Συνδέσμου","langCode":"ΚατεÏθυνση Κειμένου","langDir":"ΚατεÏθυνση Κειμένου","langDirLTR":"ΑÏιστεÏά Ï€Ïος Δεξιά (LTR)","langDirRTL":"Δεξιά Ï€Ïος ΑÏιστεÏά (RTL)","menu":"ΕπεξεÏγασία Συνδέσμου","name":"Όνομα","noAnchors":"(Δεν υπάÏχουν άγκυÏες στο κείμενο)","noEmail":"Εισάγετε τη διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου","noUrl":"Εισάγετε την τοποθεσία (URL) του συνδέσμου","other":"<άλλο>","popupDependent":"ΕξαÏτημένο (Netscape)","popupFeatures":"Επιλογές Αναδυόμενου ΠαÏαθÏÏου","popupFullScreen":"ΠλήÏης Οθόνη (IE)","popupLeft":"Θέση ΑÏιστεÏά","popupLocationBar":"ΓÏαμμή Τοποθεσίας","popupMenuBar":"ΓÏαμμή Επιλογών","popupResizable":"ΠÏοσαÏμοζόμενο Μέγεθος","popupScrollBars":"ΜπάÏες ΚÏλισης","popupStatusBar":"ΓÏαμμή Κατάστασης","popupToolbar":"ΕÏγαλειοθήκη","popupTop":"Θέση Πάνω","rel":"Σχέση","selectAnchor":"Επιλέξτε μια ΆγκυÏα","styles":"ΜοÏφή","tabIndex":"ΣειÏά Μεταπήδησης","target":"ΠαÏάθυÏο ΠÏοοÏισμοÏ","targetFrame":"<πλαίσιο>","targetFrameName":"Όνομα Πλαισίου ΠÏοοÏισμοÏ","targetPopup":"<αναδυόμενο παÏάθυÏο>","targetPopupName":"Όνομα Αναδυόμενου ΠαÏαθÏÏου","title":"ΣÏνδεσμος","toAnchor":"ΆγκυÏα σε αυτήν τη σελίδα","toEmail":"E-Mail","toUrl":"URL","toolbar":"ΣÏνδεσμος","type":"ΤÏπος Συνδέσμου","unlink":"ΑφαίÏεση Συνδέσμου","upload":"Αποστολή"},"list":{"bulletedlist":"Εισαγωγή/ΑπομάκÏυνση Λίστας Κουκκίδων","numberedlist":"Εισαγωγή/ΑπομάκÏυνση ΑÏιθμημένης Λίστας"},"magicline":{"title":"Εισάγετε παÏάγÏαφο εδώ"},"maximize":{"maximize":"Μεγιστοποίηση","minimize":"Ελαχιστοποίηση"},"pastetext":{"button":"Επικόλληση ως απλό κείμενο","title":"Επικόλληση ως απλό κείμενο"},"pastefromword":{"confirmCleanup":"Το κείμενο που επικολλάται φαίνεται να είναι αντιγÏαμμένο από το Word. Μήπως θα θέλατε να καθαÏιστεί Ï€ÏÎ¿Ï„Î¿Ï ÎµÏ€Î¹ÎºÎ¿Î»Î»Î·Î¸ÎµÎ¯;","error":"Δεν ήταν δυνατό να καθαÏιστοÏν τα δεδομένα λόγω ενός εσωτεÏÎ¹ÎºÎ¿Ï ÏƒÏ†Î¬Î»Î¼Î±Ï„Î¿Ï‚","title":"Επικόλληση από το Word","toolbar":"Επικόλληση από το Word"},"removeformat":{"toolbar":"ΕκκαθάÏιση ΜοÏφοποίησης"},"sourcearea":{"toolbar":"Κώδικας"},"specialchar":{"options":"Επιλογές Ειδικών ΧαÏακτήÏων","title":"Επιλέξτε Έναν Ειδικό ΧαÏακτήÏα","toolbar":"Εισαγωγή Î•Î¹Î´Î¹ÎºÎ¿Ï Î§Î±ÏακτήÏα"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Λεξικά","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Γλώσσες","btn_options":"Επιλογές","text_title":"Spell Check As You Type"},"stylescombo":{"label":"ΜοÏφές","panelTitle":"Στυλ ΜοÏφοποίησης","panelTitle1":"Στυλ Τμημάτων","panelTitle2":"Στυλ Εν ΣειÏά","panelTitle3":"Στυλ Αντικειμένων"},"table":{"border":"Πάχος ΠεÏιγÏάμματος","caption":"Λεζάντα","cell":{"menu":"Κελί","insertBefore":"Εισαγωγή ÎšÎµÎ»Î¹Î¿Ï Î Ïιν","insertAfter":"Εισαγωγή ÎšÎµÎ»Î¹Î¿Ï ÎœÎµÏ„Î¬","deleteCell":"ΔιαγÏαφή Κελιών","merge":"Ενοποίηση Κελιών","mergeRight":"Συγχώνευση Με Δεξιά","mergeDown":"Συγχώνευση Με Κάτω","splitHorizontal":"ΟÏιζόντια ΔιαίÏεση ΚελιοÏ","splitVertical":"ΚατακόÏυφη ΔιαίÏεση ΚελιοÏ","title":"Ιδιότητες ΚελιοÏ","cellType":"ΤÏπος ΚελιοÏ","rowSpan":"ΕÏÏος ΓÏαμμών","colSpan":"ΕÏÏος Στηλών","wordWrap":"Αναδίπλωση Λέξεων","hAlign":"ΟÏιζόντια Στοίχιση","vAlign":"Κάθετη Στοίχιση","alignBaseline":"Baseline","bgColor":"ΧÏώμα Φόντου","borderColor":"ΧÏώμα ΠεÏιγÏάμματος","data":"Δεδομένα","header":"Κεφαλίδα","yes":"Îαι","no":"Όχι","invalidWidth":"Το πλάτος του ÎºÎµÎ»Î¹Î¿Ï Ï€Ïέπει να είναι αÏιθμός.","invalidHeight":"Το Ïψος του ÎºÎµÎ»Î¹Î¿Ï Ï€Ïέπει να είναι αÏιθμός.","invalidRowSpan":"Το εÏÏος των γÏαμμών Ï€Ïέπει να είναι ακέÏαιος αÏιθμός.","invalidColSpan":"Το εÏÏος των στηλών Ï€Ïέπει να είναι ακέÏαιος αÏιθμός.","chooseColor":"Επιλέξτε"},"cellPad":"ΑναπλήÏωση κελιών","cellSpace":"Απόσταση κελιών","column":{"menu":"Στήλη","insertBefore":"Εισαγωγή Στήλης ΠÏιν","insertAfter":"Εισαγωγή Στήλης Μετά","deleteColumn":"ΔιαγÏαφή Στηλών"},"columns":"Στήλες","deleteTable":"ΔιαγÏαφή Πίνακα","headers":"Κεφαλίδες","headersBoth":"Και τα δÏο","headersColumn":"ΠÏώτη στήλη","headersNone":"Κανένα","headersRow":"ΠÏώτη ΓÏαμμή","invalidBorder":"Το πάχος του πεÏιγÏάμματος Ï€Ïέπει να είναι ένας αÏιθμός.","invalidCellPadding":"Η αναπλήÏωση των κελιών Ï€Ïέπει να είναι θετικός αÏιθμός.","invalidCellSpacing":"Η απόσταση Î¼ÎµÏ„Î±Î¾Ï Ï„Ï‰Î½ κελιών Ï€Ïέπει να είναι ένας θετικός αÏιθμός.","invalidCols":"Ο αÏιθμός των στηλών Ï€Ïέπει να είναι μεγαλÏτεÏος από 0.","invalidHeight":"Το Ïψος του πίνακα Ï€Ïέπει να είναι αÏιθμός.","invalidRows":"Ο αÏιθμός των σειÏών Ï€Ïέπει να είναι μεγαλÏτεÏος από 0.","invalidWidth":"Το πλάτος του πίνακα Ï€Ïέπει να είναι ένας αÏιθμός.","menu":"Ιδιότητες Πίνακα","row":{"menu":"ΓÏαμμή","insertBefore":"Εισαγωγή ΓÏαμμής ΠÏιν","insertAfter":"Εισαγωγή ΓÏαμμής Μετά","deleteRow":"ΔιαγÏαφή ΓÏαμμών"},"rows":"ΓÏαμμές","summary":"ΠεÏίληψη","title":"Ιδιότητες Πίνακα","toolbar":"Πίνακας","widthPc":"τοις εκατό","widthPx":"pixel","widthUnit":"μονάδα πλάτους"},"undo":{"redo":"Επανάληψη","undo":"ΑναίÏεση"},"wsc":{"btnIgnore":"Αγνόηση","btnIgnoreAll":"Αγνόηση όλων","btnReplace":"Αντικατάσταση","btnReplaceAll":"Αντικατάσταση όλων","btnUndo":"ΑναίÏεση","changeTo":"Αλλαγή σε","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Δεν υπάÏχει εγκατεστημένος οÏθογÏάφος. Θέλετε να τον κατεβάσετε Ï„ÏŽÏα;","manyChanges":"Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Άλλαξαν %1 λέξεις","noChanges":"Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Δεν άλλαξαν λέξεις","noMispell":"Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Δεν βÏέθηκαν λάθη","noSuggestions":"- Δεν υπάÏχουν Ï€Ïοτάσεις -","notAvailable":"Η υπηÏεσία δεν είναι διαθέσιμη αυτήν την στιγμή.","notInDic":"Δεν υπάÏχει στο λεξικό","oneChange":"Ο οÏθογÏαφικός έλεγχος ολοκληÏώθηκε: Άλλαξε μια λέξη","progress":"Γίνεται οÏθογÏαφικός έλεγχος...","title":"ΟÏθογÏαφικός Έλεγχος","toolbar":"ΟÏθογÏαφικός Έλεγχος"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/en-au.js b/js/ckeditor/lang/en-au.js
new file mode 100644
index 0000000..026761f
--- /dev/null
+++ b/js/ckeditor/lang/en-au.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['en-au']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<not set>","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Resize","generalTab":"General","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Align","alignLeft":"Left","alignRight":"Right","alignCenter":"Centre","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strike Through","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Paragraph Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"image":{"alertUrl":"Please type the image URL","alt":"Alternative Text","border":"Border","btnUpload":"Send it to the Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Image Info","linkTab":"Link","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Upload","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Language Code","langDir":"Language Direction","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","menu":"Edit Link","name":"Name","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Style","tabIndex":"Tab Index","target":"Target","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Link","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Link","type":"Link Type","unlink":"Unlink","upload":"Upload"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Paste as plain text","title":"Paste as Plain Text"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Special Character Options","title":"Select Special Character","toolbar":"Insert Special Character"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Border size","caption":"Caption","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Delete Cells","merge":"Merge Cells","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"Columns","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a number.","invalidCellSpacing":"Cell spacing must be a number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"Redo","undo":"Undo"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/en-ca.js b/js/ckeditor/lang/en-ca.js
new file mode 100644
index 0000000..174f7be
--- /dev/null
+++ b/js/ckeditor/lang/en-ca.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['en-ca']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<not set>","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Resize","generalTab":"General","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Align","alignLeft":"Left","alignRight":"Right","alignCenter":"Centre","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strike Through","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Paragraph Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"image":{"alertUrl":"Please type the image URL","alt":"Alternative Text","border":"Border","btnUpload":"Send it to the Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Image Info","linkTab":"Link","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Upload","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Language Code","langDir":"Language Direction","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","menu":"Edit Link","name":"Name","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Style","tabIndex":"Tab Index","target":"Target","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Link","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Link","type":"Link Type","unlink":"Unlink","upload":"Upload"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Paste as plain text","title":"Paste as Plain Text"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Special Character Options","title":"Select Special Character","toolbar":"Insert Special Character"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Border size","caption":"Caption","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Delete Cells","merge":"Merge Cells","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"Columns","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a number.","invalidCellSpacing":"Cell spacing must be a number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"Redo","undo":"Undo"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/en-gb.js b/js/ckeditor/lang/en-gb.js
new file mode 100644
index 0000000..b29e97c
--- /dev/null
+++ b/js/ckeditor/lang/en-gb.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['en-gb']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<not set>","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Drag to resize","generalTab":"General","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialogue window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Align","alignLeft":"Left","alignRight":"Right","alignCenter":"Centre","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strike Through","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Paragraph Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"image":{"alertUrl":"Please type the image URL","alt":"Alternative Text","border":"Border","btnUpload":"Send it to the Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Image Info","linkTab":"Link","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Upload","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Language Code","langDir":"Language Direction","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","menu":"Edit Link","name":"Name","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Style","tabIndex":"Tab Index","target":"Target","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Link","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Link","type":"Link Type","unlink":"Unlink","upload":"Upload"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximise","minimize":"Minimise"},"pastetext":{"button":"Paste as plain text","title":"Paste as Plain Text"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Special Character Options","title":"Select Special Character","toolbar":"Insert Special Character"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Border size","caption":"Caption","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Delete Cells","merge":"Merge Cells","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"Columns","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a number.","invalidCellSpacing":"Cell spacing must be a number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"Redo","undo":"Undo"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/en.js b/js/ckeditor/lang/en.js
new file mode 100644
index 0000000..6e4e1b5
--- /dev/null
+++ b/js/ckeditor/lang/en.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['en']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<not set>","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Resize","generalTab":"General","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Alignment","alignLeft":"Left","alignRight":"Right","alignCenter":"Center","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Paragraph Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"image":{"alertUrl":"Please type the image URL","alt":"Alternative Text","border":"Border","btnUpload":"Send it to the Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Image Info","linkTab":"Link","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Upload","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Language Code","langDir":"Language Direction","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","menu":"Edit Link","name":"Name","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Style","tabIndex":"Tab Index","target":"Target","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Link","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Link","type":"Link Type","unlink":"Unlink","upload":"Upload"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Paste as plain text","title":"Paste as Plain Text"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Special Character Options","title":"Select Special Character","toolbar":"Insert Special Character"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Border size","caption":"Caption","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Delete Cells","merge":"Merge Cells","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"Columns","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"Redo","undo":"Undo"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/eo.js b/js/ckeditor/lang/eo.js
new file mode 100644
index 0000000..3e35fcd
--- /dev/null
+++ b/js/ckeditor/lang/eo.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['eo']={"editor":"RiĉTeksta Redaktilo","editorPanel":"Panelo de la RiĉTeksta Redaktilo","common":{"editorHelp":"Premu ALT 0 por helpilo","browseServer":"Foliumi en la Servilo","url":"URL","protocol":"Protokolo","upload":"AlÅuti","uploadSubmit":"Sendu al Servilo","image":"Bildo","flash":"FlaÅo","form":"Formularo","checkbox":"Markobutono","radio":"Radiobutono","textField":"Teksta kampo","textarea":"Teksta Areo","hiddenField":"KaÅita Kampo","button":"Butono","select":"Elekta Kampo","imageButton":"Bildbutono","notSet":"<DefaÅ­lta>","id":"Id","name":"Nomo","langDir":"Skribdirekto","langDirLtr":"De maldekstro dekstren (LTR)","langDirRtl":"De dekstro maldekstren (RTL)","langCode":"Lingva Kodo","longDescr":"URL de Longa Priskribo","cssClass":"Klasoj de Stilfolioj","advisoryTitle":"Priskriba Titolo","cssStyle":"Stilo","ok":"Akcepti","cancel":"Rezigni","close":"Fermi","preview":"Vidigi Aspekton","resize":"Movigi por ÅanÄi la grandon","generalTab":"Äœenerala","advancedTab":"Speciala","validateNumberFailed":"Tiu valoro ne estas nombro.","confirmNewPage":"La neregistritaj ÅanÄoj estas perdotaj. Ĉu vi certas, ke vi volas Åargi novan paÄon?","confirmCancel":"Iuj opcioj esta ÅanÄitaj. Ĉu vi certas, ke vi volas fermi la dialogon?","options":"Opcioj","target":"Celo","targetNew":"Nova Fenestro (_blank)","targetTop":"Supra Fenestro (_top)","targetSelf":"Sama Fenestro (_self)","targetParent":"Patra Fenestro (_parent)","langDirLTR":"De maldekstro dekstren (LTR)","langDirRTL":"De dekstro maldekstren (RTL)","styles":"Stilo","cssClasses":"Stilfoliaj Klasoj","width":"LarÄo","height":"Alto","align":"Äœisrandigo","alignLeft":"Maldekstre","alignRight":"Dekstre","alignCenter":"Centre","alignJustify":"Äœisrandigi AmbaÅ­flanke","alignTop":"Supre","alignMiddle":"Centre","alignBottom":"Malsupre","alignNone":"None","invalidValue":"Nevalida Valoro","invalidHeight":"Alto devas esti nombro.","invalidWidth":"LarÄo devas esti nombro.","invalidCssLength":"La valoro indikita por la \"%1\" kampo devas esti pozitiva nombro kun aÅ­ sen valida CSSmezurunuo (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"La valoro indikita por la \"%1\" kampo devas esti pozitiva nombro kun aÅ­ sen valida HTMLmezurunuo (px or %).","invalidInlineStyle":"La valoro indikita por la enlinia stilo devas konsisti el unu aÅ­ pluraj elementoj kun la formato de \"nomo : valoro\", apartigitaj per punktokomoj.","cssLengthTooltip":"Entajpu nombron por rastrumera valoro aÅ­ nombron kun valida CSSunuo (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, nehavebla</span>"},"about":{"copy":"Copyright &copy; $1. Ĉiuj rajtoj rezervitaj.","dlgTitle":"Pri CKEditor","help":"Kontroli $1 por helpo.","moreInfo":"Por informoj pri licenco, bonvolu viziti nian retpaÄaron:","title":"Pri CKEditor","userGuide":"CKEditor Uzindikoj"},"basicstyles":{"bold":"Grasa","italic":"Kursiva","strike":"Trastreko","subscript":"Suba indico","superscript":"Supra indico","underline":"Substreko"},"blockquote":{"toolbar":"Citaĵo"},"clipboard":{"copy":"Kopii","copyError":"La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras kopiajn operaciojn. Bonvolu uzi la klavaron por tio (Ctrl/Cmd-C).","cut":"Eltondi","cutError":"La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras eltondajn operaciojn. Bonvolu uzi la klavaron por tio (Ctrl/Cmd-X).","paste":"Interglui","pasteArea":"Intergluoareo","pasteMsg":"Bonvolu glui la tekston en la jenan areon per uzado de la klavaro (<strong>Ctrl/Cmd+V</strong>) kaj premu OK","securityMsg":"Pro la sekurecagordo de via TTT-legilo, la redaktilo ne povas rekte atingi viajn datenojn en la poÅo. Bonvolu denove interglui la datenojn en tiun fenestron.","title":"Interglui"},"contextmenu":{"options":"Opcioj de Kunteksta Menuo"},"button":{"selectedLabel":"%1 (Selektita)"},"toolbar":{"toolbarCollapse":"Faldi la ilbreton","toolbarExpand":"Malfaldi la ilbreton","toolbarGroups":{"document":"Dokumento","clipboard":"PoÅo/Malfari","editing":"Redaktado","forms":"Formularoj","basicstyles":"Bazaj stiloj","paragraph":"Paragrafo","links":"Ligiloj","insert":"Enmeti","styles":"Stiloj","colors":"Koloroj","tools":"Iloj"},"toolbars":"Ilobretoj de la redaktilo"},"elementspath":{"eleLabel":"Vojo al Elementoj","eleTitle":"%1 elementoj"},"format":{"label":"Formato","panelTitle":"ParagrafFormato","tag_address":"Adreso","tag_div":"Normala (DIV)","tag_h1":"Titolo 1","tag_h2":"Titolo 2","tag_h3":"Titolo 3","tag_h4":"Titolo 4","tag_h5":"Titolo 5","tag_h6":"Titolo 6","tag_p":"Normala","tag_pre":"Formatita"},"horizontalrule":{"toolbar":"Enmeti Horizontalan Linion"},"image":{"alertUrl":"Bonvolu tajpi la retadreson de la bildo","alt":"AnstataÅ­iga Teksto","border":"Bordero","btnUpload":"Sendu al Servilo","button2Img":"Ĉu vi volas transformi la selektitan bildbutonon en simplan bildon?","hSpace":"Horizontala Spaco","img2Button":"Ĉu vi volas transformi la selektitan bildon en bildbutonon?","infoTab":"Informoj pri Bildo","linkTab":"Ligilo","lockRatio":"Konservi Proporcion","menu":"Atributoj de Bildo","resetSize":"Origina Grando","title":"Atributoj de Bildo","titleButton":"Bildbutonaj Atributoj","upload":"AlÅuti","urlMissing":"La fontretadreso de la bildo mankas.","vSpace":"Vertikala Spaco","validateBorder":"La bordero devas esti entjera nombro.","validateHSpace":"La horizontala spaco devas esti entjera nombro.","validateVSpace":"La vertikala spaco devas esti entjera nombro."},"indent":{"indent":"Pligrandigi KrommarÄenon","outdent":"Malpligrandigi KrommarÄenon"},"fakeobjects":{"anchor":"Ankro","flash":"FlaÅAnimacio","hiddenfield":"KaÅita kampo","iframe":"Enlinia Kadro (IFrame)","unknown":"Nekonata objekto"},"link":{"acccessKey":"Fulmoklavo","advanced":"Speciala","advisoryContentType":"Enhavotipo","advisoryTitle":"Priskriba Titolo","anchor":{"toolbar":"Ankro","menu":"Enmeti/ÅœanÄi Ankron","title":"Ankraj Atributoj","name":"Ankra Nomo","errorName":"Bv entajpi la ankran nomon","remove":"Forigi Ankron"},"anchorId":"Per Elementidentigilo","anchorName":"Per Ankronomo","charset":"Signaro de la Ligita Rimedo","cssClasses":"Klasoj de Stilfolioj","emailAddress":"RetpoÅto","emailBody":"MesaÄa korpo","emailSubject":"MesaÄa Temo","id":"Id","info":"Informoj pri la Ligilo","langCode":"Lingva Kodo","langDir":"Skribdirekto","langDirLTR":"De maldekstro dekstren (LTR)","langDirRTL":"De dekstro maldekstren (RTL)","menu":"ÅœanÄi Ligilon","name":"Nomo","noAnchors":"<Ne disponeblas ankroj en la dokumento>","noEmail":"Bonvolu entajpi la retpoÅtadreson","noUrl":"Bonvolu entajpi la URL-on","other":"<alia>","popupDependent":"Dependa (Netscape)","popupFeatures":"Atributoj de la Åœprucfenestro","popupFullScreen":"Tutekrane (IE)","popupLeft":"Maldekstra Pozicio","popupLocationBar":"Adresobreto","popupMenuBar":"Menubreto","popupResizable":"DimensiÅanÄebla","popupScrollBars":"Rulumskaloj","popupStatusBar":"Statobreto","popupToolbar":"Ilobreto","popupTop":"Supra Pozicio","rel":"Rilato","selectAnchor":"Elekti Ankron","styles":"Stilo","tabIndex":"Taba Indekso","target":"Celo","targetFrame":"<kadro>","targetFrameName":"Nomo de CelKadro","targetPopup":"<Åprucfenestro>","targetPopupName":"Nomo de Åœprucfenestro","title":"Ligilo","toAnchor":"Ankri en tiu ĉi paÄo","toEmail":"RetpoÅto","toUrl":"URL","toolbar":"Enmeti/ÅœanÄi Ligilon","type":"Tipo de Ligilo","unlink":"Forigi Ligilon","upload":"AlÅuti"},"list":{"bulletedlist":"Bula Listo","numberedlist":"Numera Listo"},"magicline":{"title":"Enmeti paragrafon ĉi-tien"},"maximize":{"maximize":"Pligrandigi","minimize":"Malgrandigi"},"pastetext":{"button":"Interglui kiel platan tekston","title":"Interglui kiel platan tekston"},"pastefromword":{"confirmCleanup":"La teksto, kiun vi volas interglui, Åajnas esti kopiita el Word. Ĉu vi deziras purigi Äin antaÅ­ intergluo?","error":"Ne eblis purigi la intergluitajn datenojn pro interna eraro","title":"Interglui el Word","toolbar":"Interglui el Word"},"removeformat":{"toolbar":"Forigi Formaton"},"sourcearea":{"toolbar":"Fonto"},"specialchar":{"options":"Opcioj pri Specialaj Signoj","title":"Selekti Specialan Signon","toolbar":"Enmeti Specialan Signon"},"scayt":{"btn_about":"Pri OKDVT","btn_dictionaries":"Vortaroj","btn_disable":"Malebligi OKDVT","btn_enable":"Ebligi OKDVT","btn_langs":"Lingvoj","btn_options":"Opcioj","text_title":"OrtografiKontrolado Dum Vi Tajpas (OKDVT)"},"stylescombo":{"label":"Stiloj","panelTitle":"Stiloj pri enpaÄigo","panelTitle1":"Stiloj de blokoj","panelTitle2":"Enliniaj Stiloj","panelTitle3":"Stiloj de objektoj"},"table":{"border":"Bordero","caption":"Tabeltitolo","cell":{"menu":"Ĉelo","insertBefore":"Enmeti Ĉelon AntaÅ­","insertAfter":"Enmeti Ĉelon Post","deleteCell":"Forigi la Ĉelojn","merge":"Kunfandi la Ĉelojn","mergeRight":"Kunfandi dekstren","mergeDown":"Kunfandi malsupren ","splitHorizontal":"Horizontale dividi","splitVertical":"Vertikale dividi","title":"Ĉelatributoj","cellType":"Ĉeltipo","rowSpan":"Kunfando de linioj","colSpan":"Kunfando de kolumnoj","wordWrap":"Cezuro","hAlign":"Horizontala Äisrandigo","vAlign":"Vertikala Äisrandigo","alignBaseline":"Malsupro de la teksto","bgColor":"Fonkoloro","borderColor":"Borderkoloro","data":"Datenoj","header":"Supra paÄotitolo","yes":"Jes","no":"No","invalidWidth":"ĈellarÄo devas esti nombro.","invalidHeight":"Ĉelalto devas esti nombro.","invalidRowSpan":"Kunfando de linioj devas esti entjera nombro.","invalidColSpan":"Kunfando de kolumnoj devas esti entjera nombro.","chooseColor":"Elektu"},"cellPad":"Interna MarÄeno de la ĉeloj","cellSpace":"Spaco inter la Ĉeloj","column":{"menu":"Kolumno","insertBefore":"Enmeti kolumnon antaÅ­","insertAfter":"Enmeti kolumnon post","deleteColumn":"Forigi Kolumnojn"},"columns":"Kolumnoj","deleteTable":"Forigi Tabelon","headers":"Supraj PaÄotitoloj","headersBoth":"AmbaÅ­","headersColumn":"Unua kolumno","headersNone":"Neniu","headersRow":"Unua linio","invalidBorder":"La bordergrando devas esti nombro.","invalidCellPadding":"La interna marÄeno en la ĉeloj devas esti pozitiva nombro.","invalidCellSpacing":"La spaco inter la ĉeloj devas esti pozitiva nombro.","invalidCols":"La nombro de la kolumnoj devas superi 0.","invalidHeight":"La tabelalto devas esti nombro.","invalidRows":"La nombro de la linioj devas superi 0.","invalidWidth":"La tabellarÄo devas esti nombro.","menu":"Atributoj de Tabelo","row":{"menu":"Linio","insertBefore":"Enmeti linion antaÅ­","insertAfter":"Enmeti linion post","deleteRow":"Forigi Liniojn"},"rows":"Linioj","summary":"Resumo","title":"Atributoj de Tabelo","toolbar":"Tabelo","widthPc":"elcentoj","widthPx":"Rastrumeroj","widthUnit":"unuo de larÄo"},"undo":{"redo":"Refari","undo":"Malfari"},"wsc":{"btnIgnore":"Ignori","btnIgnoreAll":"Ignori Ĉion","btnReplace":"AnstataÅ­igi","btnReplaceAll":"AnstataÅ­igi Ĉion","btnUndo":"Malfari","changeTo":"ÅœanÄi al","errorLoading":"Eraro en la servoelÅuto el la gastiga komputiko: %s.","ieSpellDownload":"Ortografikontrolilo ne instalita. Ĉu vi volas elÅuti Äin nun?","manyChanges":"Ortografikontrolado finita: %1 vortoj korektitaj","noChanges":"Ortografikontrolado finita: neniu vorto korektita","noMispell":"Ortografikontrolado finita: neniu eraro trovita","noSuggestions":"- Neniu propono -","notAvailable":"BedaÅ­rinde la servo ne funkcias nuntempe.","notInDic":"Ne trovita en la vortaro","oneChange":"Ortografikontrolado finita: unu vorto korektita","progress":"La ortografio estas kontrolata...","title":"Kontroli la ortografion","toolbar":"Kontroli la ortografion"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/es.js b/js/ckeditor/lang/es.js
new file mode 100644
index 0000000..0a86852
--- /dev/null
+++ b/js/ckeditor/lang/es.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['es']={"editor":"Editor de texto enriquecido","editorPanel":"Panel del Editor de Texto Enriquecido","common":{"editorHelp":"Pulse ALT 0 para ayuda","browseServer":"Ver Servidor","url":"URL","protocol":"Protocolo","upload":"Cargar","uploadSubmit":"Enviar al Servidor","image":"Imagen","flash":"Flash","form":"Formulario","checkbox":"Casilla de Verificación","radio":"Botones de Radio","textField":"Campo de Texto","textarea":"Area de Texto","hiddenField":"Campo Oculto","button":"Botón","select":"Campo de Selección","imageButton":"Botón Imagen","notSet":"<No definido>","id":"Id","name":"Nombre","langDir":"Orientación","langDirLtr":"Izquierda a Derecha (LTR)","langDirRtl":"Derecha a Izquierda (RTL)","langCode":"Cód. de idioma","longDescr":"Descripción larga URL","cssClass":"Clases de hojas de estilo","advisoryTitle":"Título","cssStyle":"Estilo","ok":"Aceptar","cancel":"Cancelar","close":"Cerrar","preview":"Previsualización","resize":"Arrastre para redimensionar","generalTab":"General","advancedTab":"Avanzado","validateNumberFailed":"El valor no es un número.","confirmNewPage":"Cualquier cambio que no se haya guardado se perderá.\r\n¿Está seguro de querer crear una nueva página?","confirmCancel":"Algunas de las opciones se han cambiado.\r\n¿Está seguro de querer cerrar el diálogo?","options":"Opciones","target":"Destino","targetNew":"Nueva ventana (_blank)","targetTop":"Ventana principal (_top)","targetSelf":"Misma ventana (_self)","targetParent":"Ventana padre (_parent)","langDirLTR":"Izquierda a derecha (LTR)","langDirRTL":"Derecha a izquierda (RTL)","styles":"Estilos","cssClasses":"Clase de la hoja de estilos","width":"Anchura","height":"Altura","align":"Alineación","alignLeft":"Izquierda","alignRight":"Derecha","alignCenter":"Centrado","alignJustify":"Justificado","alignTop":"Tope","alignMiddle":"Centro","alignBottom":"Pie","alignNone":"None","invalidValue":"Valor no válido","invalidHeight":"Altura debe ser un número.","invalidWidth":"Anchura debe ser un número.","invalidCssLength":"El valor especificado para el campo \"%1\" debe ser un número positivo, incluyendo optionalmente una unidad de medida CSS válida (px, %, in, cm, mm, em, ex, pt, o pc).","invalidHtmlLength":"El valor especificado para el campo \"%1\" debe ser un número positivo, incluyendo optionalmente una unidad de medida HTML válida (px o %).","invalidInlineStyle":"El valor especificado para el estilo debe consistir en uno o más pares con el formato \"nombre: valor\", separados por punto y coma.","cssLengthTooltip":"Introduca un número para el valor en pixels o un número con una unidad de medida CSS válida (px, %, in, cm, mm, em, ex, pt, o pc).","unavailable":"%1<span class=\"cke_accessibility\">, no disponible</span>"},"about":{"copy":"Copyright &copy; $1. Todos los derechos reservados.","dlgTitle":"Acerca de CKEditor","help":"Lea la $1 para resolver sus dudas.","moreInfo":"Para información de licencia, por favor visite nuestro sitio web:","title":"Acerca de CKEditor","userGuide":"Guía de usuario de CKEditor"},"basicstyles":{"bold":"Negrita","italic":"Cursiva","strike":"Tachado","subscript":"Subíndice","superscript":"Superíndice","underline":"Subrayado"},"blockquote":{"toolbar":"Cita"},"clipboard":{"copy":"Copiar","copyError":"La configuración de seguridad de este navegador no permite la ejecución automática de operaciones de copiado.\r\nPor favor use el teclado (Ctrl/Cmd+C).","cut":"Cortar","cutError":"La configuración de seguridad de este navegador no permite la ejecución automática de operaciones de cortado.\r\nPor favor use el teclado (Ctrl/Cmd+X).","paste":"Pegar","pasteArea":"Zona de pegado","pasteMsg":"Por favor pegue dentro del cuadro utilizando el teclado (<STRONG>Ctrl/Cmd+V</STRONG>);\r\nluego presione <STRONG>Aceptar</STRONG>.","securityMsg":"Debido a la configuración de seguridad de su navegador, el editor no tiene acceso al portapapeles.\r\nEs necesario que lo pegue de nuevo en esta ventana.","title":"Pegar"},"contextmenu":{"options":"Opciones del menú contextual"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Contraer barra de herramientas","toolbarExpand":"Expandir barra de herramientas","toolbarGroups":{"document":"Documento","clipboard":"Portapapeles/Deshacer","editing":"Edición","forms":"Formularios","basicstyles":"Estilos básicos","paragraph":"Párrafo","links":"Enlaces","insert":"Insertar","styles":"Estilos","colors":"Colores","tools":"Herramientas"},"toolbars":"Barras de herramientas del editor"},"elementspath":{"eleLabel":"Ruta de los elementos","eleTitle":"%1 elemento"},"format":{"label":"Formato","panelTitle":"Formato","tag_address":"Dirección","tag_div":"Normal (DIV)","tag_h1":"Encabezado 1","tag_h2":"Encabezado 2","tag_h3":"Encabezado 3","tag_h4":"Encabezado 4","tag_h5":"Encabezado 5","tag_h6":"Encabezado 6","tag_p":"Normal","tag_pre":"Con formato"},"horizontalrule":{"toolbar":"Insertar Línea Horizontal"},"image":{"alertUrl":"Por favor escriba la URL de la imagen","alt":"Texto Alternativo","border":"Borde","btnUpload":"Enviar al Servidor","button2Img":"¿Desea convertir el botón de imagen en una simple imagen?","hSpace":"Esp.Horiz","img2Button":"¿Desea convertir la imagen en un botón de imagen?","infoTab":"Información de Imagen","linkTab":"Vínculo","lockRatio":"Proporcional","menu":"Propiedades de Imagen","resetSize":"Tamaño Original","title":"Propiedades de Imagen","titleButton":"Propiedades de Botón de Imagen","upload":"Cargar","urlMissing":"Debe indicar la URL de la imagen.","vSpace":"Esp.Vert","validateBorder":"El borde debe ser un número.","validateHSpace":"El espaciado horizontal debe ser un número.","validateVSpace":"El espaciado vertical debe ser un número."},"indent":{"indent":"Aumentar Sangría","outdent":"Disminuir Sangría"},"fakeobjects":{"anchor":"Ancla","flash":"Animación flash","hiddenfield":"Campo oculto","iframe":"IFrame","unknown":"Objeto desconocido"},"link":{"acccessKey":"Tecla de Acceso","advanced":"Avanzado","advisoryContentType":"Tipo de Contenido","advisoryTitle":"Título","anchor":{"toolbar":"Referencia","menu":"Propiedades de Referencia","title":"Propiedades de Referencia","name":"Nombre de la Referencia","errorName":"Por favor, complete el nombre de la Referencia","remove":"Quitar Referencia"},"anchorId":"Por ID de elemento","anchorName":"Por Nombre de Referencia","charset":"Fuente de caracteres vinculado","cssClasses":"Clases de hojas de estilo","emailAddress":"Dirección de E-Mail","emailBody":"Cuerpo del Mensaje","emailSubject":"Título del Mensaje","id":"Id","info":"Información de Vínculo","langCode":"Código idioma","langDir":"Orientación","langDirLTR":"Izquierda a Derecha (LTR)","langDirRTL":"Derecha a Izquierda (RTL)","menu":"Editar Vínculo","name":"Nombre","noAnchors":"(No hay referencias disponibles en el documento)","noEmail":"Por favor escriba la dirección de e-mail","noUrl":"Por favor escriba el vínculo URL","other":"<otro>","popupDependent":"Dependiente (Netscape)","popupFeatures":"Características de Ventana Emergente","popupFullScreen":"Pantalla Completa (IE)","popupLeft":"Posición Izquierda","popupLocationBar":"Barra de ubicación","popupMenuBar":"Barra de Menú","popupResizable":"Redimensionable","popupScrollBars":"Barras de desplazamiento","popupStatusBar":"Barra de Estado","popupToolbar":"Barra de Herramientas","popupTop":"Posición Derecha","rel":"Relación","selectAnchor":"Seleccionar una referencia","styles":"Estilo","tabIndex":"Indice de tabulación","target":"Destino","targetFrame":"<marco>","targetFrameName":"Nombre del Marco Destino","targetPopup":"<ventana emergente>","targetPopupName":"Nombre de Ventana Emergente","title":"Vínculo","toAnchor":"Referencia en esta página","toEmail":"E-Mail","toUrl":"URL","toolbar":"Insertar/Editar Vínculo","type":"Tipo de vínculo","unlink":"Eliminar Vínculo","upload":"Cargar"},"list":{"bulletedlist":"Viñetas","numberedlist":"Numeración"},"magicline":{"title":"Insertar párrafo aquí"},"maximize":{"maximize":"Maximizar","minimize":"Minimizar"},"pastetext":{"button":"Pegar como Texto Plano","title":"Pegar como Texto Plano"},"pastefromword":{"confirmCleanup":"El texto que desea parece provenir de Word.\r\n¿Desea depurarlo antes de pegarlo?","error":"No ha sido posible limpiar los datos debido a un error interno","title":"Pegar desde Word","toolbar":"Pegar desde Word"},"removeformat":{"toolbar":"Eliminar Formato"},"sourcearea":{"toolbar":"Fuente HTML"},"specialchar":{"options":"Opciones de caracteres especiales","title":"Seleccione un caracter especial","toolbar":"Insertar Caracter Especial"},"scayt":{"btn_about":"Acerca de Corrector","btn_dictionaries":"Diccionarios","btn_disable":"Desactivar Corrector","btn_enable":"Activar Corrector","btn_langs":"Idiomas","btn_options":"Opciones","text_title":"Comprobar Ortografía Mientras Escribe"},"stylescombo":{"label":"Estilo","panelTitle":"Estilos para formatear","panelTitle1":"Estilos de párrafo","panelTitle2":"Estilos de carácter","panelTitle3":"Estilos de objeto"},"table":{"border":"Tamaño de Borde","caption":"Título","cell":{"menu":"Celda","insertBefore":"Insertar celda a la izquierda","insertAfter":"Insertar celda a la derecha","deleteCell":"Eliminar Celdas","merge":"Combinar Celdas","mergeRight":"Combinar a la derecha","mergeDown":"Combinar hacia abajo","splitHorizontal":"Dividir la celda horizontalmente","splitVertical":"Dividir la celda verticalmente","title":"Propiedades de celda","cellType":"Tipo de Celda","rowSpan":"Expandir filas","colSpan":"Expandir columnas","wordWrap":"Ajustar al contenido","hAlign":"Alineación Horizontal","vAlign":"Alineación Vertical","alignBaseline":"Linea de base","bgColor":"Color de fondo","borderColor":"Color de borde","data":"Datos","header":"Encabezado","yes":"Sí","no":"No","invalidWidth":"La anchura de celda debe ser un número.","invalidHeight":"La altura de celda debe ser un número.","invalidRowSpan":"La expansión de filas debe ser un número entero.","invalidColSpan":"La expansión de columnas debe ser un número entero.","chooseColor":"Elegir"},"cellPad":"Esp. interior","cellSpace":"Esp. e/celdas","column":{"menu":"Columna","insertBefore":"Insertar columna a la izquierda","insertAfter":"Insertar columna a la derecha","deleteColumn":"Eliminar Columnas"},"columns":"Columnas","deleteTable":"Eliminar Tabla","headers":"Encabezados","headersBoth":"Ambas","headersColumn":"Primera columna","headersNone":"Ninguno","headersRow":"Primera fila","invalidBorder":"El tamaño del borde debe ser un número.","invalidCellPadding":"El espaciado interior debe ser un número.","invalidCellSpacing":"El espaciado entre celdas debe ser un número.","invalidCols":"El número de columnas debe ser un número mayor que 0.","invalidHeight":"La altura de tabla debe ser un número.","invalidRows":"El número de filas debe ser un número mayor que 0.","invalidWidth":"La anchura de tabla debe ser un número.","menu":"Propiedades de Tabla","row":{"menu":"Fila","insertBefore":"Insertar fila en la parte superior","insertAfter":"Insertar fila en la parte inferior","deleteRow":"Eliminar Filas"},"rows":"Filas","summary":"Síntesis","title":"Propiedades de Tabla","toolbar":"Tabla","widthPc":"porcentaje","widthPx":"pixeles","widthUnit":"unidad de la anchura"},"undo":{"redo":"Rehacer","undo":"Deshacer"},"wsc":{"btnIgnore":"Ignorar","btnIgnoreAll":"Ignorar Todo","btnReplace":"Reemplazar","btnReplaceAll":"Reemplazar Todo","btnUndo":"Deshacer","changeTo":"Cambiar a","errorLoading":"Error cargando la aplicación del servidor: %s.","ieSpellDownload":"Módulo de Control de Ortografía no instalado.\r\n¿Desea descargarlo ahora?","manyChanges":"Control finalizado: se ha cambiado %1 palabras","noChanges":"Control finalizado: no se ha cambiado ninguna palabra","noMispell":"Control finalizado: no se encontraron errores","noSuggestions":"- No hay sugerencias -","notAvailable":"Lo sentimos pero el servicio no está disponible.","notInDic":"No se encuentra en el Diccionario","oneChange":"Control finalizado: se ha cambiado una palabra","progress":"Control de Ortografía en progreso...","title":"Comprobar ortografía","toolbar":"Ortografía"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/et.js b/js/ckeditor/lang/et.js
new file mode 100644
index 0000000..58297dd
--- /dev/null
+++ b/js/ckeditor/lang/et.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['et']={"editor":"Rikkalik tekstiredaktor","editorPanel":"Rikkaliku tekstiredaktori paneel","common":{"editorHelp":"Abi saamiseks vajuta ALT 0","browseServer":"Serveri sirvimine","url":"URL","protocol":"Protokoll","upload":"Laadi üles","uploadSubmit":"Saada serverisse","image":"Pilt","flash":"Flash","form":"Vorm","checkbox":"Märkeruut","radio":"Raadionupp","textField":"Tekstilahter","textarea":"Tekstiala","hiddenField":"Varjatud lahter","button":"Nupp","select":"Valiklahter","imageButton":"Piltnupp","notSet":"<määramata>","id":"ID","name":"Nimi","langDir":"Keele suund","langDirLtr":"Vasakult paremale (LTR)","langDirRtl":"Paremalt vasakule (RTL)","langCode":"Keele kood","longDescr":"Pikk kirjeldus URL","cssClass":"Stiilistiku klassid","advisoryTitle":"Soovituslik pealkiri","cssStyle":"Laad","ok":"Olgu","cancel":"Loobu","close":"Sulge","preview":"Eelvaade","resize":"Suuruse muutmiseks lohista","generalTab":"Üldine","advancedTab":"Täpsemalt","validateNumberFailed":"See väärtus pole number.","confirmNewPage":"Kõik salvestamata muudatused lähevad kaotsi. Kas oled kindel, et tahad laadida uue lehe?","confirmCancel":"Mõned valikud on muudetud. Kas oled kindel, et tahad dialoogi sulgeda?","options":"Valikud","target":"Sihtkoht","targetNew":"Uus aken (_blank)","targetTop":"Kõige ülemine aken (_top)","targetSelf":"Sama aken (_self)","targetParent":"Vanemaken (_parent)","langDirLTR":"Vasakult paremale (LTR)","langDirRTL":"Paremalt vasakule (RTL)","styles":"Stiili","cssClasses":"Stiililehe klassid","width":"Laius","height":"Kõrgus","align":"Joondus","alignLeft":"Vasak","alignRight":"Paremale","alignCenter":"Kesk","alignJustify":"Rööpjoondus","alignTop":"Üles","alignMiddle":"Keskele","alignBottom":"Alla","alignNone":"None","invalidValue":"Vigane väärtus.","invalidHeight":"Kõrgus peab olema number.","invalidWidth":"Laius peab olema number.","invalidCssLength":"\"%1\" välja jaoks määratud väärtus peab olema positiivne täisarv CSS ühikuga (px, %, in, cm, mm, em, ex, pt või pc) või ilma.","invalidHtmlLength":"\"%1\" välja jaoks määratud väärtus peab olema positiivne täisarv HTML ühikuga (px või %) või ilma.","invalidInlineStyle":"Reasisese stiili määrangud peavad koosnema paarisväärtustest (tuples), mis on semikoolonitega eraldatult järgnevas vormingus: \"nimi : väärtus\".","cssLengthTooltip":"Sisesta väärtus pikslites või number koos sobiva CSS-i ühikuga (px, %, in, cm, mm, em, ex, pt või pc).","unavailable":"%1<span class=\"cke_accessibility\">, pole saadaval</span>"},"about":{"copy":"Copyright &copy; $1. Kõik õigused kaitstud.","dlgTitle":"CKEditorist","help":"Abi jaoks vaata $1.","moreInfo":"Litsentsi andmed leiab meie veebilehelt:","title":"CKEditorist","userGuide":"CKEditori kasutusjuhendit"},"basicstyles":{"bold":"Paks","italic":"Kursiiv","strike":"Läbijoonitud","subscript":"Allindeks","superscript":"Ülaindeks","underline":"Allajoonitud"},"blockquote":{"toolbar":"Blokktsitaat"},"clipboard":{"copy":"Kopeeri","copyError":"Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt kopeerida. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl/Cmd+C).","cut":"Lõika","cutError":"Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt lõigata. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl/Cmd+X).","paste":"Aseta","pasteArea":"Asetamise ala","pasteMsg":"Palun aseta tekst järgnevasse kasti kasutades klaviatuuri klahvikombinatsiooni (<STRONG>Ctrl/Cmd+V</STRONG>) ja vajuta seejärel <STRONG>OK</STRONG>.","securityMsg":"Sinu veebisirvija turvaseadete tõttu ei oma redaktor otsest ligipääsu lõikelaua andmetele. Sa pead asetama need uuesti siia aknasse.","title":"Asetamine"},"contextmenu":{"options":"Kontekstimenüü valikud"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Tööriistariba peitmine","toolbarExpand":"Tööriistariba näitamine","toolbarGroups":{"document":"Dokument","clipboard":"Lõikelaud/tagasivõtmine","editing":"Muutmine","forms":"Vormid","basicstyles":"Põhistiilid","paragraph":"Lõik","links":"Lingid","insert":"Sisesta","styles":"Stiilid","colors":"Värvid","tools":"Tööriistad"},"toolbars":"Redaktori tööriistaribad"},"elementspath":{"eleLabel":"Elementide asukoht","eleTitle":"%1 element"},"format":{"label":"Vorming","panelTitle":"Vorming","tag_address":"Aadress","tag_div":"Tavaline (DIV)","tag_h1":"Pealkiri 1","tag_h2":"Pealkiri 2","tag_h3":"Pealkiri 3","tag_h4":"Pealkiri 4","tag_h5":"Pealkiri 5","tag_h6":"Pealkiri 6","tag_p":"Tavaline","tag_pre":"Vormindatud"},"horizontalrule":{"toolbar":"Horisontaaljoone sisestamine"},"image":{"alertUrl":"Palun kirjuta pildi URL","alt":"Alternatiivne tekst","border":"Joon","btnUpload":"Saada serverisse","button2Img":"Kas tahad teisendada valitud pildiga nupu tavaliseks pildiks?","hSpace":"H. vaheruum","img2Button":"Kas tahad teisendada valitud tavalise pildi pildiga nupuks?","infoTab":"Pildi info","linkTab":"Link","lockRatio":"Lukusta kuvasuhe","menu":"Pildi omadused","resetSize":"Lähtesta suurus","title":"Pildi omadused","titleButton":"Piltnupu omadused","upload":"Lae üles","urlMissing":"Pildi lähte-URL on puudu.","vSpace":"V. vaheruum","validateBorder":"Äärise laius peab olema täisarv.","validateHSpace":"Horisontaalne vaheruum peab olema täisarv.","validateVSpace":"Vertikaalne vaheruum peab olema täisarv."},"indent":{"indent":"Taande suurendamine","outdent":"Taande vähendamine"},"fakeobjects":{"anchor":"Ankur","flash":"Flashi animatsioon","hiddenfield":"Varjatud väli","iframe":"IFrame","unknown":"Tundmatu objekt"},"link":{"acccessKey":"Juurdepääsu võti","advanced":"Täpsemalt","advisoryContentType":"Juhendava sisu tüüp","advisoryTitle":"Juhendav tiitel","anchor":{"toolbar":"Ankru sisestamine/muutmine","menu":"Ankru omadused","title":"Ankru omadused","name":"Ankru nimi","errorName":"Palun sisesta ankru nimi","remove":"Eemalda ankur"},"anchorId":"Elemendi id järgi","anchorName":"Ankru nime järgi","charset":"Lingitud ressursi märgistik","cssClasses":"Stiilistiku klassid","emailAddress":"E-posti aadress","emailBody":"Sõnumi tekst","emailSubject":"Sõnumi teema","id":"ID","info":"Lingi info","langCode":"Keele suund","langDir":"Keele suund","langDirLTR":"Vasakult paremale (LTR)","langDirRTL":"Paremalt vasakule (RTL)","menu":"Muuda linki","name":"Nimi","noAnchors":"(Selles dokumendis pole ankruid)","noEmail":"Palun kirjuta e-posti aadress","noUrl":"Palun kirjuta lingi URL","other":"<muu>","popupDependent":"Sõltuv (Netscape)","popupFeatures":"Hüpikakna omadused","popupFullScreen":"Täisekraan (IE)","popupLeft":"Vasak asukoht","popupLocationBar":"Aadressiriba","popupMenuBar":"Menüüriba","popupResizable":"Suurust saab muuta","popupScrollBars":"Kerimisribad","popupStatusBar":"Olekuriba","popupToolbar":"Tööriistariba","popupTop":"Ülemine asukoht","rel":"Suhe","selectAnchor":"Vali ankur","styles":"Laad","tabIndex":"Tab indeks","target":"Sihtkoht","targetFrame":"<raam>","targetFrameName":"Sihtmärk raami nimi","targetPopup":"<hüpikaken>","targetPopupName":"Hüpikakna nimi","title":"Link","toAnchor":"Ankur sellel lehel","toEmail":"E-post","toUrl":"URL","toolbar":"Lingi lisamine/muutmine","type":"Lingi liik","unlink":"Lingi eemaldamine","upload":"Lae üles"},"list":{"bulletedlist":"Punktloend","numberedlist":"Numberloend"},"magicline":{"title":"Sisesta siia lõigu tekst"},"maximize":{"maximize":"Maksimeerimine","minimize":"Minimeerimine"},"pastetext":{"button":"Asetamine tavalise tekstina","title":"Asetamine tavalise tekstina"},"pastefromword":{"confirmCleanup":"Tekst, mida tahad asetada näib pärinevat Wordist. Kas tahad selle enne asetamist puhastada?","error":"Asetatud andmete puhastamine ei olnud sisemise vea tõttu võimalik","title":"Asetamine Wordist","toolbar":"Asetamine Wordist"},"removeformat":{"toolbar":"Vormingu eemaldamine"},"sourcearea":{"toolbar":"Lähtekood"},"specialchar":{"options":"Erimärkide valikud","title":"Erimärgi valimine","toolbar":"Erimärgi sisestamine"},"scayt":{"btn_about":"SCAYT-ist lähemalt","btn_dictionaries":"Sõnaraamatud","btn_disable":"SCAYT keelatud","btn_enable":"SCAYT lubatud","btn_langs":"Keeled","btn_options":"Valikud","text_title":"Õigekirjakontroll kirjutamise ajal"},"stylescombo":{"label":"Stiil","panelTitle":"Vormindusstiilid","panelTitle1":"Blokkstiilid","panelTitle2":"Reasisesed stiilid","panelTitle3":"Objektistiilid"},"table":{"border":"Joone suurus","caption":"Tabeli tiitel","cell":{"menu":"Lahter","insertBefore":"Sisesta lahter enne","insertAfter":"Sisesta lahter peale","deleteCell":"Eemalda lahtrid","merge":"Ühenda lahtrid","mergeRight":"Ühenda paremale","mergeDown":"Ühenda alla","splitHorizontal":"Poolita lahter horisontaalselt","splitVertical":"Poolita lahter vertikaalselt","title":"Lahtri omadused","cellType":"Lahtri liik","rowSpan":"Ridade vahe","colSpan":"Tulpade vahe","wordWrap":"Sõnade murdmine","hAlign":"Horisontaalne joondus","vAlign":"Vertikaalne joondus","alignBaseline":"Baasjoon","bgColor":"Tausta värv","borderColor":"Äärise värv","data":"Andmed","header":"Päis","yes":"Jah","no":"Ei","invalidWidth":"Lahtri laius peab olema number.","invalidHeight":"Lahtri kõrgus peab olema number.","invalidRowSpan":"Ridade vahe peab olema täisarv.","invalidColSpan":"Tulpade vahe peab olema täisarv.","chooseColor":"Vali"},"cellPad":"Lahtri täidis","cellSpace":"Lahtri vahe","column":{"menu":"Veerg","insertBefore":"Sisesta veerg enne","insertAfter":"Sisesta veerg peale","deleteColumn":"Eemalda veerud"},"columns":"Veerud","deleteTable":"Kustuta tabel","headers":"Päised","headersBoth":"Mõlemad","headersColumn":"Esimene tulp","headersNone":"Puudub","headersRow":"Esimene rida","invalidBorder":"Äärise suurus peab olema number.","invalidCellPadding":"Lahtrite polsterdus (padding) peab olema positiivne arv.","invalidCellSpacing":"Lahtrite vahe peab olema positiivne arv.","invalidCols":"Tulpade arv peab olema nullist suurem.","invalidHeight":"Tabeli kõrgus peab olema number.","invalidRows":"Ridade arv peab olema nullist suurem.","invalidWidth":"Tabeli laius peab olema number.","menu":"Tabeli omadused","row":{"menu":"Rida","insertBefore":"Sisesta rida enne","insertAfter":"Sisesta rida peale","deleteRow":"Eemalda read"},"rows":"Read","summary":"Kokkuvõte","title":"Tabeli omadused","toolbar":"Tabel","widthPc":"protsenti","widthPx":"pikslit","widthUnit":"laiuse ühik"},"undo":{"redo":"Toimingu kordamine","undo":"Tagasivõtmine"},"wsc":{"btnIgnore":"Ignoreeri","btnIgnoreAll":"Ignoreeri kõiki","btnReplace":"Asenda","btnReplaceAll":"Asenda kõik","btnUndo":"Võta tagasi","changeTo":"Muuda","errorLoading":"Viga rakenduse teenushosti laadimisel: %s.","ieSpellDownload":"Õigekirja kontrollija ei ole paigaldatud. Soovid sa selle alla laadida?","manyChanges":"Õigekirja kontroll sooritatud: %1 sõna muudetud","noChanges":"Õigekirja kontroll sooritatud: ühtegi sõna ei muudetud","noMispell":"Õigekirja kontroll sooritatud: õigekirjuvigu ei leitud","noSuggestions":"- Soovitused puuduvad -","notAvailable":"Kahjuks ei ole teenus praegu saadaval.","notInDic":"Puudub sõnastikust","oneChange":"Õigekirja kontroll sooritatud: üks sõna muudeti","progress":"Toimub õigekirja kontroll...","title":"Õigekirjakontroll","toolbar":"Õigekirjakontroll"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/eu.js b/js/ckeditor/lang/eu.js
new file mode 100644
index 0000000..f6d33ab
--- /dev/null
+++ b/js/ckeditor/lang/eu.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['eu']={"editor":"Testu Aberastuko Editorea","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"ALT 0 sakatu laguntza jasotzeko","browseServer":"Zerbitzaria arakatu","url":"URL","protocol":"Protokoloa","upload":"Gora kargatu","uploadSubmit":"Zerbitzarira bidali","image":"Irudia","flash":"Flasha","form":"Formularioa","checkbox":"Kontrol-laukia","radio":"Aukera-botoia","textField":"Testu Eremua","textarea":"Testu-area","hiddenField":"Ezkutuko Eremua","button":"Botoia","select":"Hautespen Eremua","imageButton":"Irudi Botoia","notSet":"<Ezarri gabe>","id":"Id","name":"Izena","langDir":"Hizkuntzaren Norabidea","langDirLtr":"Ezkerretik Eskumara(LTR)","langDirRtl":"Eskumatik Ezkerrera (RTL)","langCode":"Hizkuntza Kodea","longDescr":"URL Deskribapen Luzea","cssClass":"Estilo-orriko Klaseak","advisoryTitle":"Izenburua","cssStyle":"Estiloa","ok":"Ados","cancel":"Utzi","close":"Itxi","preview":"Aurrebista","resize":"Arrastatu tamaina aldatzeko","generalTab":"Orokorra","advancedTab":"Aurreratua","validateNumberFailed":"Balio hau ez da zenbaki bat.","confirmNewPage":"Eduki honetan gorde gabe dauden aldaketak galduko dira. Ziur zaude orri berri bat kargatu nahi duzula?","confirmCancel":"Aukera batzuk aldatu egin dira. Ziur zaude elkarrizketa-koadroa itxi nahi duzula?","options":"Aukerak","target":"Target (Helburua)","targetNew":"Leiho Berria (_blank)","targetTop":"Goieneko Leihoan (_top)","targetSelf":"Leiho Berdinean (_self)","targetParent":"Leiho Gurasoan (_parent)","langDirLTR":"Ezkerretik Eskumara(LTR)","langDirRTL":"Eskumatik Ezkerrera (RTL)","styles":"Estiloa","cssClasses":"Estilo-orriko Klaseak","width":"Zabalera","height":"Altuera","align":"Lerrokatu","alignLeft":"Ezkerrera","alignRight":"Eskuman","alignCenter":"Erdian","alignJustify":"Justifikatu","alignTop":"Goian","alignMiddle":"Erdian","alignBottom":"Behean","alignNone":"None","invalidValue":"Balio ezegokia.","invalidHeight":"Altuera zenbaki bat izan behar da.","invalidWidth":"Zabalera zenbaki bat izan behar da.","invalidCssLength":"\"%1\" eremurako zehaztutako balioa zenbaki positibo bat izan behar du, aukeran CSS neurri unitate batekin (px, %, in, cm, mm, em, ex, pt edo pc).","invalidHtmlLength":"\"%1\" eremurako zehaztutako balioa zenbaki positibo bat izan behar du, aukeran HTML neurri unitate batekin (px edo %).","invalidInlineStyle":"Lerroko estiloan zehazten dena tupla \"name : value\" formatuko eta puntu eta komaz bereiztutako tupla bat edo gehiago izan behar dira.","cssLengthTooltip":"Zenbakia bakarrik zehazten bada pixeletan egongo da. CSS neurri unitatea ere zehaztu ahal da (px, %, in, cm, mm, em, ex, pt, edo pc).","unavailable":"%1<span class=\"cke_accessibility\">, erabilezina</span>"},"about":{"copy":"Copyright &copy; $1. Eskubide guztiak erreserbaturik.","dlgTitle":"CKEditor(r)i buruz","help":"$1 aztertu laguntza jasotzeko.","moreInfo":"Lizentziari buruzko informazioa gure webgunean:","title":"CKEditor(r)i buruz","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Lodia","italic":"Etzana","strike":"Marratua","subscript":"Azpi-indize","superscript":"Goi-indize","underline":"Azpimarratu"},"blockquote":{"toolbar":"Aipamen blokea"},"clipboard":{"copy":"Kopiatu","copyError":"Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki kopiatzea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl/Cmd+C).","cut":"Ebaki","cutError":"Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki moztea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl/Cmd+X).","paste":"Itsatsi","pasteArea":"Itsasteko Area","pasteMsg":"Mesedez teklatua erabilita (<STRONG>Ctrl/Cmd+V</STRONG>) ondorego eremuan testua itsatsi eta <STRONG>OK</STRONG> sakatu.","securityMsg":"Nabigatzailearen segurtasun ezarpenak direla eta, editoreak ezin du arbela zuzenean erabili. Leiho honetan berriro itsatsi behar duzu.","title":"Itsatsi"},"contextmenu":{"options":"Testuingurko Menuaren Aukerak"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Tresna-barra Txikitu","toolbarExpand":"Tresna-barra Luzatu","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editorearen Tresna-barra"},"elementspath":{"eleLabel":"Elementu bidea","eleTitle":"%1 elementua"},"format":{"label":"Formatua","panelTitle":"Formatua","tag_address":"Helbidea","tag_div":"Paragrafoa (DIV)","tag_h1":"Izenburua 1","tag_h2":"Izenburua 2","tag_h3":"Izenburua 3","tag_h4":"Izenburua 4","tag_h5":"Izenburua 5","tag_h6":"Izenburua 6","tag_p":"Arrunta","tag_pre":"Formateatua"},"horizontalrule":{"toolbar":"Txertatu Marra Horizontala"},"image":{"alertUrl":"Mesedez Irudiaren URLa idatzi","alt":"Ordezko Testua","border":"Ertza","btnUpload":"Zerbitzarira bidalia","button2Img":"Aukeratutako irudi botoia, irudi normal batean eraldatu nahi duzu?","hSpace":"HSpace","img2Button":"Aukeratutako irudia, irudi botoi batean eraldatu nahi duzu?","infoTab":"Irudi informazioa","linkTab":"Esteka","lockRatio":"Erlazioa Blokeatu","menu":"Irudi Ezaugarriak","resetSize":"Tamaina Berrezarri","title":"Irudi Ezaugarriak","titleButton":"Irudi Botoiaren Ezaugarriak","upload":"Gora Kargatu","urlMissing":"Irudiaren iturburu URL-a falta da.","vSpace":"VSpace","validateBorder":"Ertza zenbaki oso bat izan behar da.","validateHSpace":"HSpace zenbaki oso bat izan behar da.","validateVSpace":"VSpace zenbaki oso bat izan behar da."},"indent":{"indent":"Handitu Koska","outdent":"Txikitu Koska"},"fakeobjects":{"anchor":"Aingura","flash":"Flash Animazioa","hiddenfield":"Ezkutuko Eremua","iframe":"IFrame","unknown":"Objektu ezezaguna"},"link":{"acccessKey":"Sarbide-gakoa","advanced":"Aurreratua","advisoryContentType":"Eduki Mota (Content Type)","advisoryTitle":"Izenburua","anchor":{"toolbar":"Aingura","menu":"Ainguraren Ezaugarriak","title":"Ainguraren Ezaugarriak","name":"Ainguraren Izena","errorName":"Idatzi ainguraren izena","remove":"Remove Anchor"},"anchorId":"Elementuaren ID-gatik","anchorName":"Aingura izenagatik","charset":"Estekatutako Karaktere Multzoa","cssClasses":"Estilo-orriko Klaseak","emailAddress":"ePosta Helbidea","emailBody":"Mezuaren Gorputza","emailSubject":"Mezuaren Gaia","id":"Id","info":"Estekaren Informazioa","langCode":"Hizkuntzaren Norabidea","langDir":"Hizkuntzaren Norabidea","langDirLTR":"Ezkerretik Eskumara(LTR)","langDirRTL":"Eskumatik Ezkerrera (RTL)","menu":"Aldatu Esteka","name":"Izena","noAnchors":"(Ez daude aingurak eskuragarri dokumentuan)","noEmail":"Mesedez ePosta helbidea idatzi","noUrl":"Mesedez URL esteka idatzi","other":"<bestelakoa>","popupDependent":"Menpekoa (Netscape)","popupFeatures":"Popup Leihoaren Ezaugarriak","popupFullScreen":"Pantaila Osoa (IE)","popupLeft":"Ezkerreko Posizioa","popupLocationBar":"Kokaleku Barra","popupMenuBar":"Menu Barra","popupResizable":"Tamaina Aldakorra","popupScrollBars":"Korritze Barrak","popupStatusBar":"Egoera Barra","popupToolbar":"Tresna Barra","popupTop":"Goiko Posizioa","rel":"Erlazioa","selectAnchor":"Aingura bat hautatu","styles":"Estiloa","tabIndex":"Tabulazio Indizea","target":"Target (Helburua)","targetFrame":"<marko>","targetFrameName":"Marko Helburuaren Izena","targetPopup":"<popup leihoa>","targetPopupName":"Popup Leihoaren Izena","title":"Esteka","toAnchor":"Aingura orrialde honetan","toEmail":"ePosta","toUrl":"URL","toolbar":"Txertatu/Editatu Esteka","type":"Esteka Mota","unlink":"Kendu Esteka","upload":"Gora kargatu"},"list":{"bulletedlist":"Buletdun Zerrenda","numberedlist":"Zenbakidun Zerrenda"},"magicline":{"title":"Txertatu paragrafoa hemen"},"maximize":{"maximize":"Maximizatu","minimize":"Minimizatu"},"pastetext":{"button":"Testu Arrunta bezala Itsatsi","title":"Testu Arrunta bezala Itsatsi"},"pastefromword":{"confirmCleanup":"Itsatsi nahi duzun testua Wordetik hartua dela dirudi. Itsatsi baino lehen garbitu nahi duzu?","error":"Barneko errore bat dela eta ezin izan da testua garbitu","title":"Itsatsi Word-etik","toolbar":"Itsatsi Word-etik"},"removeformat":{"toolbar":"Kendu Formatua"},"sourcearea":{"toolbar":"HTML Iturburua"},"specialchar":{"options":"Karaktere Berezien Aukerak","title":"Karaktere Berezia Aukeratu","toolbar":"Txertatu Karaktere Berezia"},"scayt":{"btn_about":"SCAYTi buruz","btn_dictionaries":"Hiztegiak","btn_disable":"Desgaitu SCAYT","btn_enable":"Gaitu SCAYT","btn_langs":"Hizkuntzak","btn_options":"Aukerak","text_title":"Ortografia Zuzenketa Idatzi Ahala (SCAYT)"},"stylescombo":{"label":"Estiloa","panelTitle":"Formatu Estiloak","panelTitle1":"Bloke Estiloak","panelTitle2":"Inline Estiloak","panelTitle3":"Objektu Estiloak"},"table":{"border":"Ertzaren Zabalera","caption":"Epigrafea","cell":{"menu":"Gelaxka","insertBefore":"Txertatu Gelaxka Aurretik","insertAfter":"Txertatu Gelaxka Ostean","deleteCell":"Kendu Gelaxkak","merge":"Batu Gelaxkak","mergeRight":"Elkartu Eskumara","mergeDown":"Elkartu Behera","splitHorizontal":"Banatu Gelaxkak Horizontalki","splitVertical":"Banatu Gelaxkak Bertikalki","title":"Gelaxken Ezaugarriak","cellType":"Gelaxka Mota","rowSpan":"Hedatutako Lerroak","colSpan":"Hedatutako Zutabeak","wordWrap":"Itzulbira","hAlign":"Lerrokatze Horizontala","vAlign":"Lerrokatze Bertikala","alignBaseline":"Oinarri-lerroan","bgColor":"Fondoaren Kolorea","borderColor":"Ertzaren Kolorea","data":"Data","header":"Goiburua","yes":"Bai","no":"Ez","invalidWidth":"Gelaxkaren zabalera zenbaki bat izan behar da.","invalidHeight":"Gelaxkaren altuera zenbaki bat izan behar da.","invalidRowSpan":"Lerroen hedapena zenbaki osoa izan behar da.","invalidColSpan":"Zutabeen hedapena zenbaki osoa izan behar da.","chooseColor":"Choose"},"cellPad":"Gelaxken betegarria","cellSpace":"Gelaxka arteko tartea","column":{"menu":"Zutabea","insertBefore":"Txertatu Zutabea Aurretik","insertAfter":"Txertatu Zutabea Ostean","deleteColumn":"Ezabatu Zutabeak"},"columns":"Zutabeak","deleteTable":"Ezabatu Taula","headers":"Goiburuak","headersBoth":"Biak","headersColumn":"Lehen zutabea","headersNone":"Bat ere ez","headersRow":"Lehen lerroa","invalidBorder":"Ertzaren tamaina zenbaki bat izan behar da.","invalidCellPadding":"Gelaxken betegarria zenbaki bat izan behar da.","invalidCellSpacing":"Gelaxka arteko tartea zenbaki bat izan behar da.","invalidCols":"Zutabe kopurua 0 baino handiagoa den zenbakia izan behar da.","invalidHeight":"Taularen altuera zenbaki bat izan behar da.","invalidRows":"Lerro kopurua 0 baino handiagoa den zenbakia izan behar da.","invalidWidth":"Taularen zabalera zenbaki bat izan behar da.","menu":"Taularen Ezaugarriak","row":{"menu":"Lerroa","insertBefore":"Txertatu Lerroa Aurretik","insertAfter":"Txertatu Lerroa Ostean","deleteRow":"Ezabatu Lerroak"},"rows":"Lerroak","summary":"Laburpena","title":"Taularen Ezaugarriak","toolbar":"Taula","widthPc":"ehuneko","widthPx":"pixel","widthUnit":"zabalera unitatea"},"undo":{"redo":"Berregin","undo":"Desegin"},"wsc":{"btnIgnore":"Ezikusi","btnIgnoreAll":"Denak Ezikusi","btnReplace":"Ordezkatu","btnReplaceAll":"Denak Ordezkatu","btnUndo":"Desegin","changeTo":"Honekin ordezkatu","errorLoading":"Errorea gertatu da aplikazioa zerbitzaritik kargatzean: %s.","ieSpellDownload":"Zuzentzaile ortografikoa ez dago instalatuta. Deskargatu nahi duzu?","manyChanges":"Zuzenketa ortografikoa bukatuta: %1 hitz aldatu dira","noChanges":"Zuzenketa ortografikoa bukatuta: Ez da ezer aldatu","noMispell":"Zuzenketa ortografikoa bukatuta: Akatsik ez","noSuggestions":"- Iradokizunik ez -","notAvailable":"Barkatu baina momentu honetan zerbitzua ez dago erabilgarri.","notInDic":"Ez dago hiztegian","oneChange":"Zuzenketa ortografikoa bukatuta: Hitz bat aldatu da","progress":"Zuzenketa ortografikoa martxan...","title":"Ortografia zuzenketa","toolbar":"Ortografia"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/fa.js b/js/ckeditor/lang/fa.js
new file mode 100644
index 0000000..acf140f
--- /dev/null
+++ b/js/ckeditor/lang/fa.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['fa']={"editor":"ویرایش‌گر متن غنی","editorPanel":"پنل ویرایشگر متن غنی","common":{"editorHelp":"کلید Alt+0 را برای راهنمایی بÙشارید","browseServer":"Ùهرست​نمایی سرور","url":"URL","protocol":"قرارداد","upload":"بالاگذاری","uploadSubmit":"به سرور بÙرست","image":"تصویر","flash":"Ùلش","form":"Ùرم","checkbox":"چک‌باکس","radio":"دکمه‌ی رادیویی","textField":"Ùیلد متنی","textarea":"ناحیهٴ متنی","hiddenField":"Ùیلد پنهان","button":"دکمه","select":"Ùیلد انتخاب چند گزینه​ای","imageButton":"دکمه‌ی تصویری","notSet":"<تعیین‌نشده>","id":"شناسه","name":"نام","langDir":"جهت زبان","langDirLtr":"Ú†Ù¾ به راست","langDirRtl":"راست به Ú†Ù¾","langCode":"کد زبان","longDescr":"URL توصی٠طولانی","cssClass":"کلاس​های شیوه​نامه (Stylesheet)","advisoryTitle":"عنوان Ú©Ù…Ú©ÛŒ","cssStyle":"سبک","ok":"پذیرش","cancel":"انصراÙ","close":"بستن","preview":"پیش‌نمایش","resize":"تغییر اندازه","generalTab":"عمومی","advancedTab":"پیش‌رÙته","validateNumberFailed":"این مقدار یک عدد نیست.","confirmNewPage":"هر تغییر ایجاد شده​ی ذخیره نشده از بین خواهد رÙت. آیا اطمینان دارید Ú©Ù‡ قصد بارگیری صÙحه جدیدی را دارید؟","confirmCancel":"برخی از گزینه‌ها تغییر کرده‌اند. آیا واقعا قصد بستن این پنجره را دارید؟","options":"گزینه​ها","target":"مقصد","targetNew":"پنجره جدید","targetTop":"بالاترین پنجره","targetSelf":"همان پنجره","targetParent":"پنجره والد","langDirLTR":"Ú†Ù¾ به راست","langDirRTL":"راست به Ú†Ù¾","styles":"سبک","cssClasses":"کلاس‌های سبک‌نامه","width":"عرض","height":"طول","align":"چینش","alignLeft":"Ú†Ù¾","alignRight":"راست","alignCenter":"وسط","alignJustify":"بلوک چین","alignTop":"بالا","alignMiddle":"میانه","alignBottom":"پائین","alignNone":"هیچ","invalidValue":"مقدار نامعتبر.","invalidHeight":"ارتÙاع باید یک عدد باشد.","invalidWidth":"عرض باید یک عدد باشد.","invalidCssLength":"عدد تعیین شده برای Ùیلد \"%1\" باید یک عدد مثبت با یا بدون یک واحد اندازه گیری CSS معتبر باشد (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"عدد تعیین شده برای Ùیلد \"%1\" باید یک عدد مثبت با یا بدون یک واحد اندازه گیری HTML معتبر باشد (px or %).","invalidInlineStyle":"عدد تعیین شده برای سبک درون​خطی -Inline Style- باید دارای یک یا چند چندتایی با Ø´Ú©Ù„ÛŒ شبیه \"name : value\" Ú©Ù‡ باید با یک \";\" از هم جدا شوند.","cssLengthTooltip":"یک عدد برای یک مقدار بر حسب پیکسل Ùˆ یا یک عدد با یک واحد CSS معتبر وارد کنید (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">ØŒ غیر قابل دسترس</span>"},"about":{"copy":"حق نشر &copy; $1. کلیه حقوق محÙوظ است.","dlgTitle":"درباره CKEditor","help":" برای راهنمایی $1 را ملاحظه کنید.","moreInfo":"برای کسب اطلاعات مجوز لطÙا به وب سایت ما مراجعه کنید:","title":"درباره CKEditor","userGuide":"راهنمای کاربران CKEditor"},"basicstyles":{"bold":"درشت","italic":"خمیده","strike":"خط‌خورده","subscript":"زیرنویس","superscript":"بالانویس","underline":"زیرخط‌دار"},"blockquote":{"toolbar":"بلوک نقل قول"},"clipboard":{"copy":"رونوشت","copyError":"تنظیمات امنیتی مرورگر شما اجازه نمیدهد Ú©Ù‡ ویرایشگر به طور خودکار عملکردهای Ú©Ù¾ÛŒ کردن را انجام دهد. لطÙا با دکمههای صÙحه کلید این کار را انجام دهید (Ctrl/Cmd+C).","cut":"برش","cutError":"تنظیمات امنیتی مرورگر شما اجازه نمیدهد Ú©Ù‡ ویرایشگر به طور خودکار عملکردهای برش را انجام دهد. لطÙا با دکمههای صÙحه کلید این کار را انجام دهید (Ctrl/Cmd+X).","paste":"چسباندن","pasteArea":"محل چسباندن","pasteMsg":"لطÙا متن را با کلیدهای (<STRONG>Ctrl/Cmd+V</STRONG>) در این جعبهٴ متنی بچسبانید Ùˆ <STRONG>پذیرش</STRONG> را بزنید.","securityMsg":"به خاطر تنظیمات امنیتی مرورگر شما، ویرایشگر نمیتواند دسترسی مستقیم به دادههای clipboard داشته باشد. شما باید دوباره آنرا در این پنجره بچسبانید.","title":"چسباندن"},"contextmenu":{"options":"گزینه​های منوی زمینه"},"button":{"selectedLabel":"%1 (انتخاب شده)"},"toolbar":{"toolbarCollapse":"بستن نوار ابزار","toolbarExpand":"بازکردن نوار ابزار","toolbarGroups":{"document":"سند","clipboard":"حاÙظه موقت/برگشت","editing":"در حال ویرایش","forms":"Ùرم​ها","basicstyles":"سبک‌های پایه","paragraph":"بند","links":"پیوندها","insert":"ورود","styles":"سبک‌ها","colors":"رنگ​ها","tools":"ابزارها"},"toolbars":"نوار ابزارهای ویرایش‌گر"},"elementspath":{"eleLabel":"مسیر عناصر","eleTitle":"%1 عنصر"},"format":{"label":"قالب","panelTitle":"قالب بند","tag_address":"نشانی","tag_div":"بند","tag_h1":"سرنویس Û±","tag_h2":"سرنویس Û²","tag_h3":"سرنویس Û³","tag_h4":"سرنویس Û´","tag_h5":"سرنویس Ûµ","tag_h6":"سرنویس Û¶","tag_p":"معمولی","tag_pre":"قالب‌دار"},"horizontalrule":{"toolbar":"گنجاندن خط اÙÙ‚ÛŒ"},"image":{"alertUrl":"لطÙا URL تصویر را بنویسید","alt":"متن جایگزین","border":"لبه","btnUpload":"به سرور بÙرست","button2Img":"آیا مایلید از یک تصویر ساده روی دکمه تصویری انتخاب شده استÙاده کنید؟","hSpace":"Ùاصلهٴ اÙÙ‚ÛŒ","img2Button":"آیا مایلید از یک دکمه تصویری روی تصویر انتخاب شده استÙاده کنید؟","infoTab":"اطلاعات تصویر","linkTab":"پیوند","lockRatio":"Ù‚ÙÙ„ کردن نسبت","menu":"ویژگی​های تصویر","resetSize":"بازنشانی اندازه","title":"ویژگی​های تصویر","titleButton":"ویژگی​های دکمهٴ تصویری","upload":"انتقال به سرور","urlMissing":"آدرس URL اصلی تصویر یاÙت نشد.","vSpace":"Ùاصلهٴ عمودی","validateBorder":"مقدار خطوط باید یک عدد باشد.","validateHSpace":"مقدار Ùاصله گذاری اÙÙ‚ÛŒ باید یک عدد باشد.","validateVSpace":"مقدار Ùاصله گذاری عمودی باید یک عدد باشد."},"indent":{"indent":"اÙزایش تورÙتگی","outdent":"کاهش تورÙتگی"},"fakeobjects":{"anchor":"لنگر","flash":"انیمشن Ùلش","hiddenfield":"Ùیلد پنهان","iframe":"IFrame","unknown":"شیء ناشناخته"},"link":{"acccessKey":"کلید دستیابی","advanced":"پیشرÙته","advisoryContentType":"نوع محتوای Ú©Ù…Ú©ÛŒ","advisoryTitle":"عنوان Ú©Ù…Ú©ÛŒ","anchor":{"toolbar":"گنجاندن/ویرایش لنگر","menu":"ویژگی​های لنگر","title":"ویژگی​های لنگر","name":"نام لنگر","errorName":"لطÙا نام لنگر را بنویسید","remove":"حذ٠لنگر"},"anchorId":"با شناسهٴ المان","anchorName":"با نام لنگر","charset":"نویسه​گان منبع پیوند شده","cssClasses":"کلاس​های شیوه​نامه(Stylesheet)","emailAddress":"نشانی پست الکترونیکی","emailBody":"متن پیام","emailSubject":"موضوع پیام","id":"شناسه","info":"اطلاعات پیوند","langCode":"جهت​نمای زبان","langDir":"جهت​نمای زبان","langDirLTR":"Ú†Ù¾ به راست (LTR)","langDirRTL":"راست به Ú†Ù¾ (RTL)","menu":"ویرایش پیوند","name":"نام","noAnchors":"(در این سند لنگری دردسترس نیست)","noEmail":"لطÙا نشانی پست الکترونیکی را بنویسید","noUrl":"لطÙا URL پیوند را بنویسید","other":"<سایر>","popupDependent":"وابسته (Netscape)","popupFeatures":"ویژگی​های پنجرهٴ پاپاپ","popupFullScreen":"تمام صÙحه (IE)","popupLeft":"موقعیت Ú†Ù¾","popupLocationBar":"نوار موقعیت","popupMenuBar":"نوار منو","popupResizable":"قابل تغییر اندازه","popupScrollBars":"میله​های پیمایش","popupStatusBar":"نوار وضعیت","popupToolbar":"نوار ابزار","popupTop":"موقعیت بالا","rel":"وابستگی","selectAnchor":"یک لنگر برگزینید","styles":"شیوه (style)","tabIndex":"نمایهٴ دسترسی با برگه","target":"مقصد","targetFrame":"<Ùریم>","targetFrameName":"نام Ùریم مقصد","targetPopup":"<پنجرهٴ پاپاپ>","targetPopupName":"نام پنجرهٴ پاپاپ","title":"پیوند","toAnchor":"لنگر در همین صÙحه","toEmail":"پست الکترونیکی","toUrl":"URL","toolbar":"گنجاندن/ویرایش پیوند","type":"نوع پیوند","unlink":"برداشتن پیوند","upload":"انتقال به سرور"},"list":{"bulletedlist":"Ùهرست نقطه​ای","numberedlist":"Ùهرست شماره​دار"},"magicline":{"title":"قرار دادن بند در اینجا"},"maximize":{"maximize":"بیشنه کردن","minimize":"کمینه کردن"},"pastetext":{"button":"چسباندن به عنوان متن ساده","title":"چسباندن به عنوان متن ساده"},"pastefromword":{"confirmCleanup":"متنی Ú©Ù‡ میخواهید بچسبانید به نظر میرسد Ú©Ù‡ از Word Ú©Ù¾ÛŒ شده است. آیا میخواهید قبل از چسباندن آن را پاکسازی کنید؟","error":"به دلیل بروز خطای داخلی امکان پاکسازی اطلاعات بازنشانی شده وجود ندارد.","title":"چسباندن از Word","toolbar":"چسباندن از Word"},"removeformat":{"toolbar":"برداشتن Ùرمت"},"sourcearea":{"toolbar":"منبع"},"specialchar":{"options":"گزینه‌های نویسه‌های ویژه","title":"گزینش نویسه‌ی ویژه","toolbar":"گنجاندن نویسه‌ی ویژه"},"scayt":{"btn_about":"درباره SCAYT","btn_dictionaries":"دیکشنریها","btn_disable":"غیرÙعالسازی SCAYT","btn_enable":"Ùعالسازی SCAYT","btn_langs":"زبانها","btn_options":"گزینهها","text_title":"بررسی املای تایپ شما"},"stylescombo":{"label":"سبک","panelTitle":"سبکهای قالببندی","panelTitle1":"سبکهای بلوک","panelTitle2":"سبکهای درونخطی","panelTitle3":"سبکهای شیء"},"table":{"border":"اندازهٴ لبه","caption":"عنوان","cell":{"menu":"سلول","insertBefore":"اÙزودن سلول قبل از","insertAfter":"اÙزودن سلول بعد از","deleteCell":"حذ٠سلولها","merge":"ادغام سلولها","mergeRight":"ادغام به راست","mergeDown":"ادغام به پایین","splitHorizontal":"جدا کردن اÙÙ‚ÛŒ سلول","splitVertical":"جدا کردن عمودی سلول","title":"ویژگیهای سلول","cellType":"نوع سلول","rowSpan":"محدوده ردیÙها","colSpan":"محدوده ستونها","wordWrap":"شکستن کلمه","hAlign":"چینش اÙÙ‚ÛŒ","vAlign":"چینش عمودی","alignBaseline":"خط مبنا","bgColor":"رنگ زمینه","borderColor":"رنگ خطوط","data":"اطلاعات","header":"سرنویس","yes":"بله","no":"خیر","invalidWidth":"عرض سلول باید یک عدد باشد.","invalidHeight":"ارتÙاع سلول باید عدد باشد.","invalidRowSpan":"مقدار محدوده ردیÙها باید یک عدد باشد.","invalidColSpan":"مقدار محدوده ستونها باید یک عدد باشد.","chooseColor":"انتخاب"},"cellPad":"Ùاصلهٴ پرشده در سلول","cellSpace":"Ùاصلهٴ میان سلولها","column":{"menu":"ستون","insertBefore":"اÙزودن ستون قبل از","insertAfter":"اÙزودن ستون بعد از","deleteColumn":"حذ٠ستونها"},"columns":"ستونها","deleteTable":"پاک کردن جدول","headers":"سرنویسها","headersBoth":"هردو","headersColumn":"اولین ستون","headersNone":"هیچ","headersRow":"اولین ردیÙ","invalidBorder":"مقدار اندازه خطوط باید یک عدد باشد.","invalidCellPadding":"بالشتک سلول باید یک عدد باشد.","invalidCellSpacing":"مقدار Ùاصلهگذاری سلول باید یک عدد باشد.","invalidCols":"تعداد ستونها باید یک عدد بزرگتر از 0 باشد.","invalidHeight":"مقدار ارتÙاع جدول باید یک عدد باشد.","invalidRows":"تعداد ردیÙها باید یک عدد بزرگتر از 0 باشد.","invalidWidth":"مقدار پهنای جدول باید یک عدد باشد.","menu":"ویژگیهای جدول","row":{"menu":"سطر","insertBefore":"اÙزودن سطر قبل از","insertAfter":"اÙزودن سطر بعد از","deleteRow":"حذ٠سطرها"},"rows":"سطرها","summary":"خلاصه","title":"ویژگیهای جدول","toolbar":"جدول","widthPc":"درصد","widthPx":"پیکسل","widthUnit":"واحد پهنا"},"undo":{"redo":"بازچیدن","undo":"واچیدن"},"wsc":{"btnIgnore":"چشمپوشی","btnIgnoreAll":"چشمپوشی همه","btnReplace":"جایگزینی","btnReplaceAll":"جایگزینی همه","btnUndo":"واچینش","changeTo":"تغییر به","errorLoading":"خطا در بارگیری برنامه خدمات میزبان: %s.","ieSpellDownload":"بررسی کنندهٴ املا نصب نشده است. آیا میخواهید آن را هماکنون دریاÙت کنید؟","manyChanges":"بررسی املا انجام شد. %1 واژه تغییر یاÙت","noChanges":"بررسی املا انجام شد. هیچ واژهای تغییر نیاÙت","noMispell":"بررسی املا انجام شد. هیچ غلط املائی یاÙت نشد","noSuggestions":"- پیشنهادی نیست -","notAvailable":"با عرض پوزش خدمات الان در دسترس نیستند.","notInDic":"در واژه~نامه یاÙت نشد","oneChange":"بررسی املا انجام شد. یک واژه تغییر یاÙت","progress":"بررسی املا در حال انجام...","title":"بررسی املا","toolbar":"بررسی املا"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/fi.js b/js/ckeditor/lang/fi.js
new file mode 100644
index 0000000..5967040
--- /dev/null
+++ b/js/ckeditor/lang/fi.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['fi']={"editor":"Rikastekstieditori","editorPanel":"Rikastekstieditoripaneeli","common":{"editorHelp":"Paina ALT 0 nähdäksesi ohjeen","browseServer":"Selaa palvelinta","url":"Osoite","protocol":"Protokolla","upload":"Lisää tiedosto","uploadSubmit":"Lähetä palvelimelle","image":"Kuva","flash":"Flash-animaatio","form":"Lomake","checkbox":"Valintaruutu","radio":"Radiopainike","textField":"Tekstikenttä","textarea":"Tekstilaatikko","hiddenField":"Piilokenttä","button":"Painike","select":"Valintakenttä","imageButton":"Kuvapainike","notSet":"<ei asetettu>","id":"Tunniste","name":"Nimi","langDir":"Kielen suunta","langDirLtr":"Vasemmalta oikealle (LTR)","langDirRtl":"Oikealta vasemmalle (RTL)","langCode":"Kielikoodi","longDescr":"Pitkän kuvauksen URL","cssClass":"Tyyliluokat","advisoryTitle":"Avustava otsikko","cssStyle":"Tyyli","ok":"OK","cancel":"Peruuta","close":"Sulje","preview":"Esikatselu","resize":"Raahaa muuttaaksesi kokoa","generalTab":"Yleinen","advancedTab":"Lisäominaisuudet","validateNumberFailed":"Arvon pitää olla numero.","confirmNewPage":"Kaikki tallentamattomat muutokset tähän sisältöön menetetään. Oletko varma, että haluat ladata uuden sivun?","confirmCancel":"Jotkut asetuksista on muuttuneet. Oletko varma, että haluat sulkea valintaikkunan?","options":"Asetukset","target":"Kohde","targetNew":"Uusi ikkuna (_blank)","targetTop":"Päällimmäinen ikkuna (_top)","targetSelf":"Sama ikkuna (_self)","targetParent":"Ylemmän tason ikkuna (_parent)","langDirLTR":"Vasemmalta oikealle (LTR)","langDirRTL":"Oikealta vasemmalle (RTL)","styles":"Tyyli","cssClasses":"Tyylitiedoston luokat","width":"Leveys","height":"Korkeus","align":"Kohdistus","alignLeft":"Vasemmalle","alignRight":"Oikealle","alignCenter":"Keskelle","alignJustify":"Tasaa molemmat reunat","alignTop":"Ylös","alignMiddle":"Keskelle","alignBottom":"Alas","alignNone":"Ei asetettu","invalidValue":"Virheellinen arvo.","invalidHeight":"Korkeuden täytyy olla numero.","invalidWidth":"Leveyden täytyy olla numero.","invalidCssLength":"Kentän \"%1\" arvon täytyy olla positiivinen luku CSS mittayksikön (px, %, in, cm, mm, em, ex, pt tai pc) kanssa tai ilman.","invalidHtmlLength":"Kentän \"%1\" arvon täytyy olla positiivinen luku HTML mittayksikön (px tai %) kanssa tai ilman.","invalidInlineStyle":"Tyylille annetun arvon täytyy koostua yhdestä tai useammasta \"nimi : arvo\" parista, jotka ovat eroteltuna toisistaan puolipisteillä.","cssLengthTooltip":"Anna numeroarvo pikseleinä tai numeroarvo CSS mittayksikön kanssa (px, %, in, cm, mm, em, ex, pt, tai pc).","unavailable":"%1<span class=\"cke_accessibility\">, ei saatavissa</span>"},"about":{"copy":"Copyright &copy; $1. Kaikki oikeuden pidätetään.","dlgTitle":"Tietoa CKEditorista","help":"Katso ohjeet: $1.","moreInfo":"Lisenssitiedot löytyvät kotisivuiltamme:","title":"Tietoa CKEditorista","userGuide":"CKEditorin käyttäjäopas"},"basicstyles":{"bold":"Lihavoitu","italic":"Kursivoitu","strike":"Yliviivattu","subscript":"Alaindeksi","superscript":"Yläindeksi","underline":"Alleviivattu"},"blockquote":{"toolbar":"Lainaus"},"clipboard":{"copy":"Kopioi","copyError":"Selaimesi turva-asetukset eivät salli editorin toteuttaa kopioimista. Käytä näppäimistöä kopioimiseen (Ctrl+C).","cut":"Leikkaa","cutError":"Selaimesi turva-asetukset eivät salli editorin toteuttaa leikkaamista. Käytä näppäimistöä leikkaamiseen (Ctrl+X).","paste":"Liitä","pasteArea":"Leikealue","pasteMsg":"Liitä painamalla (<STRONG>Ctrl+V</STRONG>) ja painamalla <STRONG>OK</STRONG>.","securityMsg":"Selaimesi turva-asetukset eivät salli editorin käyttää leikepöytää suoraan. Sinun pitää suorittaa liittäminen tässä ikkunassa.","title":"Liitä"},"contextmenu":{"options":"Pikavalikon ominaisuudet"},"button":{"selectedLabel":"%1 (Valittu)"},"toolbar":{"toolbarCollapse":"Kutista työkalupalkki","toolbarExpand":"Laajenna työkalupalkki","toolbarGroups":{"document":"Dokumentti","clipboard":"Leikepöytä/Kumoa","editing":"Muokkaus","forms":"Lomakkeet","basicstyles":"Perustyylit","paragraph":"Kappale","links":"Linkit","insert":"Lisää","styles":"Tyylit","colors":"Värit","tools":"Työkalut"},"toolbars":"Editorin työkalupalkit"},"elementspath":{"eleLabel":"Elementin polku","eleTitle":"%1 elementti"},"format":{"label":"Muotoilu","panelTitle":"Muotoilu","tag_address":"Osoite","tag_div":"Normaali (DIV)","tag_h1":"Otsikko 1","tag_h2":"Otsikko 2","tag_h3":"Otsikko 3","tag_h4":"Otsikko 4","tag_h5":"Otsikko 5","tag_h6":"Otsikko 6","tag_p":"Normaali","tag_pre":"Muotoiltu"},"horizontalrule":{"toolbar":"Lisää murtoviiva"},"image":{"alertUrl":"Kirjoita kuvan osoite (URL)","alt":"Vaihtoehtoinen teksti","border":"Kehys","btnUpload":"Lähetä palvelimelle","button2Img":"Haluatko muuntaa valitun kuvanäppäimen kuvaksi?","hSpace":"Vaakatila","img2Button":"Haluatko muuntaa valitun kuvan kuvanäppäimeksi?","infoTab":"Kuvan tiedot","linkTab":"Linkki","lockRatio":"Lukitse suhteet","menu":"Kuvan ominaisuudet","resetSize":"Alkuperäinen koko","title":"Kuvan ominaisuudet","titleButton":"Kuvapainikkeen ominaisuudet","upload":"Lisää kuva","urlMissing":"Kuvan lähdeosoite puuttuu.","vSpace":"Pystytila","validateBorder":"Kehyksen täytyy olla kokonaisluku.","validateHSpace":"HSpace-määrityksen täytyy olla kokonaisluku.","validateVSpace":"VSpace-määrityksen täytyy olla kokonaisluku."},"indent":{"indent":"Suurenna sisennystä","outdent":"Pienennä sisennystä"},"fakeobjects":{"anchor":"Ankkuri","flash":"Flash animaatio","hiddenfield":"Piilokenttä","iframe":"IFrame-kehys","unknown":"Tuntematon objekti"},"link":{"acccessKey":"Pikanäppäin","advanced":"Lisäominaisuudet","advisoryContentType":"Avustava sisällön tyyppi","advisoryTitle":"Avustava otsikko","anchor":{"toolbar":"Lisää ankkuri/muokkaa ankkuria","menu":"Ankkurin ominaisuudet","title":"Ankkurin ominaisuudet","name":"Nimi","errorName":"Ankkurille on kirjoitettava nimi","remove":"Poista ankkuri"},"anchorId":"Ankkurin ID:n mukaan","anchorName":"Ankkurin nimen mukaan","charset":"Linkitetty kirjaimisto","cssClasses":"Tyyliluokat","emailAddress":"Sähköpostiosoite","emailBody":"Viesti","emailSubject":"Aihe","id":"Tunniste","info":"Linkin tiedot","langCode":"Kielen suunta","langDir":"Kielen suunta","langDirLTR":"Vasemmalta oikealle (LTR)","langDirRTL":"Oikealta vasemmalle (RTL)","menu":"Muokkaa linkkiä","name":"Nimi","noAnchors":"(Ei ankkureita tässä dokumentissa)","noEmail":"Kirjoita sähköpostiosoite","noUrl":"Linkille on kirjoitettava URL","other":"<muu>","popupDependent":"Riippuva (Netscape)","popupFeatures":"Popup ikkunan ominaisuudet","popupFullScreen":"Täysi ikkuna (IE)","popupLeft":"Vasemmalta (px)","popupLocationBar":"Osoiterivi","popupMenuBar":"Valikkorivi","popupResizable":"Venytettävä","popupScrollBars":"Vierityspalkit","popupStatusBar":"Tilarivi","popupToolbar":"Vakiopainikkeet","popupTop":"Ylhäältä (px)","rel":"Suhde","selectAnchor":"Valitse ankkuri","styles":"Tyyli","tabIndex":"Tabulaattori indeksi","target":"Kohde","targetFrame":"<kehys>","targetFrameName":"Kohdekehyksen nimi","targetPopup":"<popup ikkuna>","targetPopupName":"Popup ikkunan nimi","title":"Linkki","toAnchor":"Ankkuri tässä sivussa","toEmail":"Sähköposti","toUrl":"Osoite","toolbar":"Lisää linkki/muokkaa linkkiä","type":"Linkkityyppi","unlink":"Poista linkki","upload":"Lisää tiedosto"},"list":{"bulletedlist":"Luettelomerkit","numberedlist":"Numerointi"},"magicline":{"title":"Lisää kappale tähän."},"maximize":{"maximize":"Suurenna","minimize":"Pienennä"},"pastetext":{"button":"Liitä tekstinä","title":"Liitä tekstinä"},"pastefromword":{"confirmCleanup":"Liittämäsi teksti näyttäisi olevan Word-dokumentista. Haluatko siivota sen ennen liittämistä? (Suositus: Kyllä)","error":"Liitetyn tiedon siivoaminen ei onnistunut sisäisen virheen takia","title":"Liitä Word-dokumentista","toolbar":"Liitä Word-dokumentista"},"removeformat":{"toolbar":"Poista muotoilu"},"sourcearea":{"toolbar":"Koodi"},"specialchar":{"options":"Erikoismerkin ominaisuudet","title":"Valitse erikoismerkki","toolbar":"Lisää erikoismerkki"},"scayt":{"btn_about":"Tietoja oikoluvusta kirjoitetaessa","btn_dictionaries":"Sanakirjat","btn_disable":"Poista käytöstä oikoluku kirjoitetaessa","btn_enable":"Ota käyttöön oikoluku kirjoitettaessa","btn_langs":"Kielet","btn_options":"Asetukset","text_title":"Oikolue kirjoitettaessa"},"stylescombo":{"label":"Tyyli","panelTitle":"Muotoilujen tyylit","panelTitle1":"Lohkojen tyylit","panelTitle2":"Rivinsisäiset tyylit","panelTitle3":"Objektien tyylit"},"table":{"border":"Rajan paksuus","caption":"Otsikko","cell":{"menu":"Solu","insertBefore":"Lisää solu eteen","insertAfter":"Lisää solu perään","deleteCell":"Poista solut","merge":"Yhdistä solut","mergeRight":"Yhdistä oikealla olevan kanssa","mergeDown":"Yhdistä alla olevan kanssa","splitHorizontal":"Jaa solu vaakasuunnassa","splitVertical":"Jaa solu pystysuunnassa","title":"Solun ominaisuudet","cellType":"Solun tyyppi","rowSpan":"Rivin jatkuvuus","colSpan":"Solun jatkuvuus","wordWrap":"Rivitys","hAlign":"Horisontaali kohdistus","vAlign":"Vertikaali kohdistus","alignBaseline":"Alas (teksti)","bgColor":"Taustan väri","borderColor":"Reunan väri","data":"Data","header":"Ylätunniste","yes":"Kyllä","no":"Ei","invalidWidth":"Solun leveyden täytyy olla numero.","invalidHeight":"Solun korkeuden täytyy olla numero.","invalidRowSpan":"Rivin jatkuvuuden täytyy olla kokonaisluku.","invalidColSpan":"Solun jatkuvuuden täytyy olla kokonaisluku.","chooseColor":"Valitse"},"cellPad":"Solujen sisennys","cellSpace":"Solujen väli","column":{"menu":"Sarake","insertBefore":"Lisää sarake vasemmalle","insertAfter":"Lisää sarake oikealle","deleteColumn":"Poista sarakkeet"},"columns":"Sarakkeet","deleteTable":"Poista taulu","headers":"Ylätunnisteet","headersBoth":"Molemmat","headersColumn":"Ensimmäinen sarake","headersNone":"Ei","headersRow":"Ensimmäinen rivi","invalidBorder":"Reunan koon täytyy olla numero.","invalidCellPadding":"Solujen sisennyksen täytyy olla numero.","invalidCellSpacing":"Solujen välin täytyy olla numero.","invalidCols":"Sarakkeiden määrän täytyy olla suurempi kuin 0.","invalidHeight":"Taulun korkeuden täytyy olla numero.","invalidRows":"Rivien määrän täytyy olla suurempi kuin 0.","invalidWidth":"Taulun leveyden täytyy olla numero.","menu":"Taulun ominaisuudet","row":{"menu":"Rivi","insertBefore":"Lisää rivi yläpuolelle","insertAfter":"Lisää rivi alapuolelle","deleteRow":"Poista rivit"},"rows":"Rivit","summary":"Yhteenveto","title":"Taulun ominaisuudet","toolbar":"Taulu","widthPc":"prosenttia","widthPx":"pikseliä","widthUnit":"leveysyksikkö"},"undo":{"redo":"Toista","undo":"Kumoa"},"wsc":{"btnIgnore":"Jätä huomioimatta","btnIgnoreAll":"Jätä kaikki huomioimatta","btnReplace":"Korvaa","btnReplaceAll":"Korvaa kaikki","btnUndo":"Kumoa","changeTo":"Vaihda","errorLoading":"Virhe ladattaessa oikolukupalvelua isännältä: %s.","ieSpellDownload":"Oikeinkirjoituksen tarkistusta ei ole asennettu. Haluatko ladata sen nyt?","manyChanges":"Tarkistus valmis: %1 sanaa muutettiin","noChanges":"Tarkistus valmis: Yhtään sanaa ei muutettu","noMispell":"Tarkistus valmis: Ei virheitä","noSuggestions":"Ei ehdotuksia","notAvailable":"Valitettavasti oikoluku ei ole käytössä tällä hetkellä.","notInDic":"Ei sanakirjassa","oneChange":"Tarkistus valmis: Yksi sana muutettiin","progress":"Tarkistus käynnissä...","title":"Oikoluku","toolbar":"Tarkista oikeinkirjoitus"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/fo.js b/js/ckeditor/lang/fo.js
new file mode 100644
index 0000000..a4fb781
--- /dev/null
+++ b/js/ckeditor/lang/fo.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['fo']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Trýst ALT og 0 fyri vegleiðing","browseServer":"Ambætarakagi","url":"URL","protocol":"Protokoll","upload":"Send til ambætaran","uploadSubmit":"Send til ambætaran","image":"Myndir","flash":"Flash","form":"Formur","checkbox":"Flugubein","radio":"Radioknøttur","textField":"Tekstteigur","textarea":"Tekstumráði","hiddenField":"Fjaldur teigur","button":"Knøttur","select":"Valskrá","imageButton":"Myndaknøttur","notSet":"<ikki sett>","id":"Id","name":"Navn","langDir":"Tekstkós","langDirLtr":"Frá vinstru til høgru (LTR)","langDirRtl":"Frá høgru til vinstru (RTL)","langCode":"Málkoda","longDescr":"Víðkað URL frágreiðing","cssClass":"Typografi klassar","advisoryTitle":"Vegleiðandi heiti","cssStyle":"Typografi","ok":"Góðkent","cancel":"Avlýst","close":"Lat aftur","preview":"Frumsýn","resize":"Drag fyri at broyta stødd","generalTab":"Generelt","advancedTab":"Fjølbroytt","validateNumberFailed":"Hetta er ikki eitt tal.","confirmNewPage":"Allar ikki goymdar broytingar í hesum innihaldið hvørva. Skal nýggj síða lesast kortini?","confirmCancel":"Nakrir valmøguleikar eru broyttir. Ert tú vísur í, at dialogurin skal latast aftur?","options":"Options","target":"Target","targetNew":"Nýtt vindeyga (_blank)","targetTop":"Vindeyga ovast (_top)","targetSelf":"Sama vindeyga (_self)","targetParent":"Upphavligt vindeyga (_parent)","langDirLTR":"Frá vinstru til høgru (LTR)","langDirRTL":"Frá høgru til vinstru (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Breidd","height":"Hædd","align":"Justering","alignLeft":"Vinstra","alignRight":"Høgra","alignCenter":"Miðsett","alignJustify":"Javnir tekstkantar","alignTop":"Ovast","alignMiddle":"Miðja","alignBottom":"Botnur","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Hædd má vera eitt tal.","invalidWidth":"Breidd má vera eitt tal.","invalidCssLength":"Virðið sett í \"%1\" feltið má vera eitt positivt tal, við ella uttan gyldugum CSS mátieind (px, %, in, cm, mm, em, ex, pt, ella pc).","invalidHtmlLength":"Virðið sett í \"%1\" feltiðield má vera eitt positivt tal, við ella uttan gyldugum CSS mátieind (px ella %).","invalidInlineStyle":"Virði specifiserað fyri inline style má hava eitt ella fleiri pør (tuples) skrivað sum \"name : value\", hvørt parið sundurskilt við semi-colon.","cssLengthTooltip":"Skriva eitt tal fyri eitt virði í pixels ella eitt tal við gyldigum CSS eind (px, %, in, cm, mm, em, ex, pt, ella pc).","unavailable":"%1<span class=\"cke_accessibility\">, ikki tøkt</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"Um CKEditor","help":"Kekka $1 fyri hjálp.","moreInfo":"Licens upplýsingar finnast á heimasíðu okkara:","title":"Um CKEditor","userGuide":"CKEditor Brúkaravegleiðing"},"basicstyles":{"bold":"Feit skrift","italic":"Skráskrift","strike":"Yvirstrikað","subscript":"Lækkað skrift","superscript":"Hækkað skrift","underline":"Undirstrikað"},"blockquote":{"toolbar":"Blockquote"},"clipboard":{"copy":"Avrita","copyError":"Trygdaruppseting alnótskagans forðar tekstviðgeranum í at avrita tekstin. Vinarliga nýt knappaborðið til at avrita tekstin (Ctrl/Cmd+C).","cut":"Kvett","cutError":"Trygdaruppseting alnótskagans forðar tekstviðgeranum í at kvetta tekstin. Vinarliga nýt knappaborðið til at kvetta tekstin (Ctrl/Cmd+X).","paste":"Innrita","pasteArea":"Avritingarumráði","pasteMsg":"Vinarliga koyr tekstin í hendan rútin við knappaborðinum (<strong>Ctrl/Cmd+V</strong>) og klikk á <strong>Góðtak</strong>.","securityMsg":"Trygdaruppseting alnótskagans forðar tekstviðgeranum í beinleiðis atgongd til avritingarminnið. Tygum mugu royna aftur í hesum rútinum.","title":"Innrita"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Lat Toolbar aftur","toolbarExpand":"Vís Toolbar","toolbarGroups":{"document":"Dokument","clipboard":"Clipboard/Undo","editing":"Editering","forms":"Formar","basicstyles":"Grundleggjandi Styles","paragraph":"Reglubrot","links":"Leinkjur","insert":"Set inn","styles":"Styles","colors":"Litir","tools":"Tól"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Slóð til elementir","eleTitle":"%1 element"},"format":{"label":"Skriftsnið","panelTitle":"Skriftsnið","tag_address":"Adressa","tag_div":"Vanligt (DIV)","tag_h1":"Yvirskrift 1","tag_h2":"Yvirskrift 2","tag_h3":"Yvirskrift 3","tag_h4":"Yvirskrift 4","tag_h5":"Yvirskrift 5","tag_h6":"Yvirskrift 6","tag_p":"Vanligt","tag_pre":"Sniðgivið"},"horizontalrule":{"toolbar":"Ger vatnrætta linju"},"image":{"alertUrl":"Rita slóðina til myndina","alt":"Alternativur tekstur","border":"Bordi","btnUpload":"Send til ambætaran","button2Img":"Skal valdi myndaknøttur gerast til vanliga mynd?","hSpace":"Høgri breddi","img2Button":"Skal valda mynd gerast til myndaknøtt?","infoTab":"Myndaupplýsingar","linkTab":"Tilknýti","lockRatio":"Læs lutfallið","menu":"Myndaeginleikar","resetSize":"Upprunastødd","title":"Myndaeginleikar","titleButton":"Eginleikar fyri myndaknøtt","upload":"Send","urlMissing":"URL til mynd manglar.","vSpace":"Vinstri breddi","validateBorder":"Bordi má vera eitt heiltal.","validateHSpace":"HSpace má vera eitt heiltal.","validateVSpace":"VSpace má vera eitt heiltal."},"indent":{"indent":"Økja reglubrotarinntriv","outdent":"Minka reglubrotarinntriv"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Fjaldur teigur","iframe":"IFrame","unknown":"Ókent Object"},"link":{"acccessKey":"Snarvegisknöttur","advanced":"Fjølbroytt","advisoryContentType":"Vegleiðandi innihaldsslag","advisoryTitle":"Vegleiðandi heiti","anchor":{"toolbar":"Ger/broyt marknastein","menu":"Eginleikar fyri marknastein","title":"Eginleikar fyri marknastein","name":"Heiti marknasteinsins","errorName":"Vinarliga rita marknasteinsins heiti","remove":"Strika marknastein"},"anchorId":"Eftir element Id","anchorName":"Eftir navni á marknasteini","charset":"Atknýtt teknsett","cssClasses":"Typografi klassar","emailAddress":"Teldupost-adressa","emailBody":"Breyðtekstur","emailSubject":"Evni","id":"Id","info":"Tilknýtis upplýsingar","langCode":"Tekstkós","langDir":"Tekstkós","langDirLTR":"Frá vinstru til høgru (LTR)","langDirRTL":"Frá høgru til vinstru (RTL)","menu":"Broyt tilknýti","name":"Navn","noAnchors":"(Eingir marknasteinar eru í hesum dokumentið)","noEmail":"Vinarliga skriva teldupost-adressu","noUrl":"Vinarliga skriva tilknýti (URL)","other":"<annað>","popupDependent":"Bundið (Netscape)","popupFeatures":"Popup vindeygans víðkaðu eginleikar","popupFullScreen":"Fullur skermur (IE)","popupLeft":"Frástøða frá vinstru","popupLocationBar":"Adressulinja","popupMenuBar":"Skrábjálki","popupResizable":"Stødd kann broytast","popupScrollBars":"Rullibjálki","popupStatusBar":"Støðufrágreiðingarbjálki","popupToolbar":"Amboðsbjálki","popupTop":"Frástøða frá íerva","rel":"Relatión","selectAnchor":"Vel ein marknastein","styles":"Typografi","tabIndex":"Tabulator indeks","target":"Target","targetFrame":"<ramma>","targetFrameName":"Vís navn vindeygans","targetPopup":"<popup vindeyga>","targetPopupName":"Popup vindeygans navn","title":"Tilknýti","toAnchor":"Tilknýti til marknastein í tekstinum","toEmail":"Teldupostur","toUrl":"URL","toolbar":"Ger/broyt tilknýti","type":"Tilknýtisslag","unlink":"Strika tilknýti","upload":"Send til ambætaran"},"list":{"bulletedlist":"Punktmerktur listi","numberedlist":"Talmerktur listi"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maksimera","minimize":"Minimera"},"pastetext":{"button":"Innrita som reinan tekst","title":"Innrita som reinan tekst"},"pastefromword":{"confirmCleanup":"Teksturin, tú roynir at seta inn, sýnist at stava frá Word. Skal teksturin reinsast fyrst?","error":"Tað eydnaðist ikki at reinsa tekstin vegna ein internan feil","title":"Innrita frá Word","toolbar":"Innrita frá Word"},"removeformat":{"toolbar":"Strika sniðgeving"},"sourcearea":{"toolbar":"Kelda"},"specialchar":{"options":"Møguleikar við serteknum","title":"Vel sertekn","toolbar":"Set inn sertekn"},"scayt":{"btn_about":"Um SCAYT","btn_dictionaries":"Orðabøkur","btn_disable":"Nokta SCAYT","btn_enable":"Loyv SCAYT","btn_langs":"Tungumál","btn_options":"Uppseting","text_title":"Kanna stavseting, meðan tú skrivar"},"stylescombo":{"label":"Typografi","panelTitle":"Formatterings stílir","panelTitle1":"Blokk stílir","panelTitle2":"Inline stílir","panelTitle3":"Object stílir"},"table":{"border":"Bordabreidd","caption":"Tabellfrágreiðing","cell":{"menu":"Meski","insertBefore":"Set meska inn áðrenn","insertAfter":"Set meska inn aftaná","deleteCell":"Strika meskar","merge":"Flætta meskar","mergeRight":"Flætta meskar til høgru","mergeDown":"Flætta saman","splitHorizontal":"Kloyv meska vatnrætt","splitVertical":"Kloyv meska loddrætt","title":"Mesku eginleikar","cellType":"Mesku slag","rowSpan":"Ræð spenni","colSpan":"Kolonnu spenni","wordWrap":"Orðkloyving","hAlign":"Horisontal plasering","vAlign":"Loddrøtt plasering","alignBaseline":"Basislinja","bgColor":"Bakgrundslitur","borderColor":"Bordalitur","data":"Data","header":"Header","yes":"Ja","no":"Nei","invalidWidth":"Meskubreidd má vera eitt tal.","invalidHeight":"Meskuhædd má vera eitt tal.","invalidRowSpan":"Raðspennið má vera eitt heiltal.","invalidColSpan":"Kolonnuspennið má vera eitt heiltal.","chooseColor":"Vel"},"cellPad":"Meskubreddi","cellSpace":"Fjarstøða millum meskar","column":{"menu":"Kolonna","insertBefore":"Set kolonnu inn áðrenn","insertAfter":"Set kolonnu inn aftaná","deleteColumn":"Strika kolonnur"},"columns":"Kolonnur","deleteTable":"Strika tabell","headers":"Yvirskriftir","headersBoth":"Báðir","headersColumn":"Fyrsta kolonna","headersNone":"Eingin","headersRow":"Fyrsta rað","invalidBorder":"Borda-stødd má vera eitt tal.","invalidCellPadding":"Cell padding má vera eitt tal.","invalidCellSpacing":"Cell spacing má vera eitt tal.","invalidCols":"Talið av kolonnum má vera eitt tal størri enn 0.","invalidHeight":"Tabell-hædd má vera eitt tal.","invalidRows":"Talið av røðum má vera eitt tal størri enn 0.","invalidWidth":"Tabell-breidd má vera eitt tal.","menu":"Eginleikar fyri tabell","row":{"menu":"Rað","insertBefore":"Set rað inn áðrenn","insertAfter":"Set rað inn aftaná","deleteRow":"Strika røðir"},"rows":"Røðir","summary":"Samandráttur","title":"Eginleikar fyri tabell","toolbar":"Tabell","widthPc":"prosent","widthPx":"pixels","widthUnit":"breiddar unit"},"undo":{"redo":"Vend aftur","undo":"Angra"},"wsc":{"btnIgnore":"Forfjóna","btnIgnoreAll":"Forfjóna alt","btnReplace":"Yvirskriva","btnReplaceAll":"Yvirskriva alt","btnUndo":"Angra","changeTo":"Broyt til","errorLoading":"Feilur við innlesing av application service host: %s.","ieSpellDownload":"Rættstavarin er ikki tøkur í tekstviðgeranum. Vilt tú heinta hann nú?","manyChanges":"Rættstavarin liðugur: %1 orð broytt","noChanges":"Rættstavarin liðugur: Einki orð varð broytt","noMispell":"Rættstavarin liðugur: Eingin feilur funnin","noSuggestions":"- Einki uppskot -","notAvailable":"Tíverri, ikki tøkt í løtuni.","notInDic":"Finst ikki í orðabókini","oneChange":"Rættstavarin liðugur: Eitt orð er broytt","progress":"Rættstavarin arbeiðir...","title":"Kanna stavseting","toolbar":"Kanna stavseting"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/fr-ca.js b/js/ckeditor/lang/fr-ca.js
new file mode 100644
index 0000000..0437bc2
--- /dev/null
+++ b/js/ckeditor/lang/fr-ca.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['fr-ca']={"editor":"Éditeur de texte enrichi","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Appuyez sur 0 pour de l'aide","browseServer":"Parcourir le serveur","url":"URL","protocol":"Protocole","upload":"Envoyer","uploadSubmit":"Envoyer au serveur","image":"Image","flash":"Animation Flash","form":"Formulaire","checkbox":"Case à cocher","radio":"Bouton radio","textField":"Champ texte","textarea":"Zone de texte","hiddenField":"Champ caché","button":"Bouton","select":"Liste déroulante","imageButton":"Bouton image","notSet":"<Par défaut>","id":"Id","name":"Nom","langDir":"Sens d'écriture","langDirLtr":"De gauche à droite (LTR)","langDirRtl":"De droite à gauche (RTL)","langCode":"Code langue","longDescr":"URL de description longue","cssClass":"Classes CSS","advisoryTitle":"Titre","cssStyle":"Style","ok":"OK","cancel":"Annuler","close":"Fermer","preview":"Aperçu","resize":"Redimensionner","generalTab":"Général","advancedTab":"Avancé","validateNumberFailed":"Cette valeur n'est pas un nombre.","confirmNewPage":"Les changements non sauvegardés seront perdus. Êtes-vous certain de vouloir charger une nouvelle page?","confirmCancel":"Certaines options ont été modifiées. Êtes-vous certain de vouloir fermer?","options":"Options","target":"Cible","targetNew":"Nouvelle fenêtre (_blank)","targetTop":"Fenêtre supérieur (_top)","targetSelf":"Cette fenêtre (_self)","targetParent":"Fenêtre parent (_parent)","langDirLTR":"De gauche à droite (LTR)","langDirRTL":"De droite à gauche (RTL)","styles":"Style","cssClasses":"Classe CSS","width":"Largeur","height":"Hauteur","align":"Alignement","alignLeft":"Gauche","alignRight":"Droite","alignCenter":"Centré","alignJustify":"Justifié","alignTop":"Haut","alignMiddle":"Milieu","alignBottom":"Bas","alignNone":"None","invalidValue":"Valeur invalide.","invalidHeight":"La hauteur doit être un nombre.","invalidWidth":"La largeur doit être un nombre.","invalidCssLength":"La valeur spécifiée pour le champ \"%1\" doit être un nombre positif avec ou sans unité de mesure CSS valide (px, %, in, cm, mm, em, ex, pt, ou pc).","invalidHtmlLength":"La valeur spécifiée pour le champ \"%1\" doit être un nombre positif avec ou sans unité de mesure HTML valide (px ou %).","invalidInlineStyle":"La valeur spécifiée pour le style intégré doit être composée d'un ou plusieurs couples de valeur au format \"nom : valeur\", separés par des points-virgules.","cssLengthTooltip":"Entrer un nombre pour la valeur en pixel ou un nombre avec une unité CSS valide (px, %, in, cm, mm, em, ex, pt, ou pc).","unavailable":"%1<span class=\"cke_accessibility\">, indisponible</span>"},"about":{"copy":"Copyright &copy; $1. Tous droits réservés.","dlgTitle":"À propos de CKEditor","help":"Consulter $1 pour l'aide.","moreInfo":"Pour les informations de licence, consulter notre site internet:","title":"À propos de CKEditor","userGuide":"Guide utilisateur de CKEditor"},"basicstyles":{"bold":"Gras","italic":"Italique","strike":"Barré","subscript":"Indice","superscript":"Exposant","underline":"Souligné"},"blockquote":{"toolbar":"Citation"},"clipboard":{"copy":"Copier","copyError":"Les paramètres de sécurité de votre navigateur empêchent l'éditeur de copier automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl/Cmd+C).","cut":"Couper","cutError":"Les paramètres de sécurité de votre navigateur empêchent l'éditeur de couper automatiquement vos données. Veuillez utiliser les équivalents claviers (Ctrl/Cmd+X).","paste":"Coller","pasteArea":"Coller la zone","pasteMsg":"Veuillez coller dans la zone ci-dessous en utilisant le clavier (<STRONG>Ctrl/Cmd+V</STRONG>) et appuyer sur <STRONG>OK</STRONG>.","securityMsg":"A cause des paramètres de sécurité de votre navigateur, l'éditeur ne peut accéder au presse-papier directement. Vous devez coller à nouveau le contenu dans cette fenêtre.","title":"Coller"},"contextmenu":{"options":"Options du menu contextuel"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Enrouler la barre d'outils","toolbarExpand":"Dérouler la barre d'outils","toolbarGroups":{"document":"Document","clipboard":"Presse papier/Annuler","editing":"Édition","forms":"Formulaires","basicstyles":"Styles de base","paragraph":"Paragraphe","links":"Liens","insert":"Insérer","styles":"Styles","colors":"Couleurs","tools":"Outils"},"toolbars":"Barre d'outils de l'éditeur"},"elementspath":{"eleLabel":"Chemin d'éléments","eleTitle":"element %1"},"format":{"label":"Format","panelTitle":"Format de paragraphe","tag_address":"Adresse","tag_div":"Normal (DIV)","tag_h1":"En-tête 1","tag_h2":"En-tête 2","tag_h3":"En-tête 3","tag_h4":"En-tête 4","tag_h5":"En-tête 5","tag_h6":"En-tête 6","tag_p":"Normal","tag_pre":"Formaté"},"horizontalrule":{"toolbar":"Insérer un séparateur horizontale"},"image":{"alertUrl":"Veuillez saisir l'URL de l'image","alt":"Texte alternatif","border":"Bordure","btnUpload":"Envoyer sur le serveur","button2Img":"Désirez-vous transformer l'image sélectionnée en image simple?","hSpace":"Espacement horizontal","img2Button":"Désirez-vous transformer l'image sélectionnée en bouton image?","infoTab":"Informations sur l'image","linkTab":"Lien","lockRatio":"Verrouiller les proportions","menu":"Propriétés de l'image","resetSize":"Taille originale","title":"Propriétés de l'image","titleButton":"Propriétés du bouton image","upload":"Téléverser","urlMissing":"L'URL de la source de l'image est manquant.","vSpace":"Espacement vertical","validateBorder":"La bordure doit être un entier.","validateHSpace":"L'espacement horizontal doit être un entier.","validateVSpace":"L'espacement vertical doit être un entier."},"indent":{"indent":"Augmenter le retrait","outdent":"Diminuer le retrait"},"fakeobjects":{"anchor":"Ancre","flash":"Animation Flash","hiddenfield":"Champ caché","iframe":"IFrame","unknown":"Objet inconnu"},"link":{"acccessKey":"Touche d'accessibilité","advanced":"Avancé","advisoryContentType":"Type de contenu","advisoryTitle":"Description","anchor":{"toolbar":"Ancre","menu":"Modifier l'ancre","title":"Propriétés de l'ancre","name":"Nom de l'ancre","errorName":"Veuillez saisir le nom de l'ancre","remove":"Supprimer l'ancre"},"anchorId":"Par ID","anchorName":"Par nom","charset":"Encodage de la cible","cssClasses":"Classes CSS","emailAddress":"Courriel","emailBody":"Corps du message","emailSubject":"Objet du message","id":"ID","info":"Informations sur le lien","langCode":"Code de langue","langDir":"Sens d'écriture","langDirLTR":"De gauche à droite (LTR)","langDirRTL":"De droite à gauche (RTL)","menu":"Modifier le lien","name":"Nom","noAnchors":"(Pas d'ancre disponible dans le document)","noEmail":"Veuillez saisir le courriel","noUrl":"Veuillez saisir l'URL","other":"<autre>","popupDependent":"Dépendante (Netscape)","popupFeatures":"Caractéristiques de la fenêtre popup","popupFullScreen":"Plein écran (IE)","popupLeft":"Position de la gauche","popupLocationBar":"Barre d'adresse","popupMenuBar":"Barre de menu","popupResizable":"Redimensionnable","popupScrollBars":"Barres de défilement","popupStatusBar":"Barre d'état","popupToolbar":"Barre d'outils","popupTop":"Position à partir du haut","rel":"Relation","selectAnchor":"Sélectionner une ancre","styles":"Style","tabIndex":"Ordre de tabulation","target":"Destination","targetFrame":"<Cadre>","targetFrameName":"Nom du cadre de destination","targetPopup":"<fenêtre popup>","targetPopupName":"Nom de la fenêtre popup","title":"Lien","toAnchor":"Ancre dans cette page","toEmail":"Courriel","toUrl":"URL","toolbar":"Lien","type":"Type de lien","unlink":"Supprimer le lien","upload":"Téléverser"},"list":{"bulletedlist":"Liste à puces","numberedlist":"Liste numérotée"},"magicline":{"title":"Insérer le paragraphe ici"},"maximize":{"maximize":"Maximizer","minimize":"Minimizer"},"pastetext":{"button":"Coller comme texte","title":"Coller comme texte"},"pastefromword":{"confirmCleanup":"Le texte que vous tentez de coller semble provenir de Word. Désirez vous le nettoyer avant de coller?","error":"Il n'a pas été possible de nettoyer les données collées du à une erreur interne","title":"Coller de Word","toolbar":"Coller de Word"},"removeformat":{"toolbar":"Supprimer le formatage"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Option des caractères spéciaux","title":"Sélectionner un caractère spécial","toolbar":"Insérer un caractère spécial"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Styles de formattage","panelTitle1":"Styles de block","panelTitle2":"Styles en ligne","panelTitle3":"Styles d'objet"},"table":{"border":"Taille de la bordure","caption":"Titre","cell":{"menu":"Cellule","insertBefore":"Insérer une cellule avant","insertAfter":"Insérer une cellule après","deleteCell":"Supprimer des cellules","merge":"Fusionner les cellules","mergeRight":"Fusionner à droite","mergeDown":"Fusionner en bas","splitHorizontal":"Scinder la cellule horizontalement","splitVertical":"Scinder la cellule verticalement","title":"Propriétés de la cellule","cellType":"Type de cellule","rowSpan":"Fusion de lignes","colSpan":"Fusion de colonnes","wordWrap":"Retour à la ligne","hAlign":"Alignement horizontal","vAlign":"Alignement vertical","alignBaseline":"Bas du texte","bgColor":"Couleur d'arrière plan","borderColor":"Couleur de bordure","data":"Données","header":"En-tête","yes":"Oui","no":"Non","invalidWidth":"La largeur de cellule doit être un nombre.","invalidHeight":"La hauteur de cellule doit être un nombre.","invalidRowSpan":"La fusion de lignes doit être un nombre entier.","invalidColSpan":"La fusion de colonnes doit être un nombre entier.","chooseColor":"Sélectionner"},"cellPad":"Marge interne des cellules","cellSpace":"Espacement des cellules","column":{"menu":"Colonne","insertBefore":"Insérer une colonne avant","insertAfter":"Insérer une colonne après","deleteColumn":"Supprimer des colonnes"},"columns":"Colonnes","deleteTable":"Supprimer le tableau","headers":"En-têtes","headersBoth":"Les deux.","headersColumn":"Première colonne","headersNone":"Aucun","headersRow":"Première ligne","invalidBorder":"La taille de bordure doit être un nombre.","invalidCellPadding":"La marge interne des cellules doit être un nombre positif.","invalidCellSpacing":"L'espacement des cellules doit être un nombre positif.","invalidCols":"Le nombre de colonnes doit être supérieur à 0.","invalidHeight":"La hauteur du tableau doit être un nombre.","invalidRows":"Le nombre de lignes doit être supérieur à 0.","invalidWidth":"La largeur du tableau doit être un nombre.","menu":"Propriétés du tableau","row":{"menu":"Ligne","insertBefore":"Insérer une ligne avant","insertAfter":"Insérer une ligne après","deleteRow":"Supprimer des lignes"},"rows":"Lignes","summary":"Résumé","title":"Propriétés du tableau","toolbar":"Tableau","widthPc":"pourcentage","widthPx":"pixels","widthUnit":"unité de largeur"},"undo":{"redo":"Refaire","undo":"Annuler"},"wsc":{"btnIgnore":"Ignorer","btnIgnoreAll":"Ignorer tout","btnReplace":"Remplacer","btnReplaceAll":"Remplacer tout","btnUndo":"Annuler","changeTo":"Changer en","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Le Correcteur d'orthographe n'est pas installé. Souhaitez-vous le télécharger maintenant?","manyChanges":"Vérification d'orthographe terminée: %1 mots modifiés","noChanges":"Vérification d'orthographe terminée: Pas de modifications","noMispell":"Vérification d'orthographe terminée: pas d'erreur trouvée","noSuggestions":"- Pas de suggestion -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Pas dans le dictionnaire","oneChange":"Vérification d'orthographe terminée: Un mot modifié","progress":"Vérification d'orthographe en cours...","title":"Spell Checker","toolbar":"Orthographe"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/fr.js b/js/ckeditor/lang/fr.js
new file mode 100644
index 0000000..32f7937
--- /dev/null
+++ b/js/ckeditor/lang/fr.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['fr']={"editor":"Éditeur de Texte Enrichi","editorPanel":"Tableau de bord de l'éditeur de texte enrichi","common":{"editorHelp":"Appuyez sur ALT-0 pour l'aide","browseServer":"Explorer le serveur","url":"URL","protocol":"Protocole","upload":"Envoyer","uploadSubmit":"Envoyer sur le serveur","image":"Image","flash":"Flash","form":"Formulaire","checkbox":"Case à cocher","radio":"Bouton Radio","textField":"Champ texte","textarea":"Zone de texte","hiddenField":"Champ caché","button":"Bouton","select":"Liste déroulante","imageButton":"Bouton image","notSet":"<non défini>","id":"Id","name":"Nom","langDir":"Sens d'écriture","langDirLtr":"Gauche à droite (LTR)","langDirRtl":"Droite à gauche (RTL)","langCode":"Code de langue","longDescr":"URL de description longue (longdesc => malvoyant)","cssClass":"Classe CSS","advisoryTitle":"Description (title)","cssStyle":"Style","ok":"OK","cancel":"Annuler","close":"Fermer","preview":"Aperçu","resize":"Déplacer pour modifier la taille","generalTab":"Général","advancedTab":"Avancé","validateNumberFailed":"Cette valeur n'est pas un nombre.","confirmNewPage":"Les changements non sauvegardés seront perdus. Êtes-vous sûr de vouloir charger une nouvelle page?","confirmCancel":"Certaines options ont été modifiées. Êtes-vous sûr de vouloir fermer?","options":"Options","target":"Cible (Target)","targetNew":"Nouvelle fenêtre (_blank)","targetTop":"Fenêtre supérieure (_top)","targetSelf":"Même fenêtre (_self)","targetParent":"Fenêtre parent (_parent)","langDirLTR":"Gauche à Droite (LTR)","langDirRTL":"Droite à Gauche (RTL)","styles":"Style","cssClasses":"Classes de style","width":"Largeur","height":"Hauteur","align":"Alignement","alignLeft":"Gauche","alignRight":"Droite","alignCenter":"Centré","alignJustify":"Justifier","alignTop":"Haut","alignMiddle":"Milieu","alignBottom":"Bas","alignNone":"Aucun","invalidValue":"Valeur incorrecte.","invalidHeight":"La hauteur doit être un nombre.","invalidWidth":"La largeur doit être un nombre.","invalidCssLength":"La valeur spécifiée pour le champ \"%1\" doit être un nombre positif avec ou sans unité de mesure CSS valide (px, %, in, cm, mm, em, ex, pt, ou pc).","invalidHtmlLength":"La valeur spécifiée pour le champ \"%1\" doit être un nombre positif avec ou sans unité de mesure HTML valide (px ou %).","invalidInlineStyle":"La valeur spécifiée pour le style inline doit être composée d'un ou plusieurs couples de valeur au format \"nom : valeur\", separés par des points-virgules.","cssLengthTooltip":"Entrer un nombre pour une valeur en pixels ou un nombre avec une unité de mesure CSS valide (px, %, in, cm, mm, em, ex, pt, ou pc).","unavailable":"%1<span class=\"cke_accessibility\">, Indisponible</span>"},"about":{"copy":"Copyright &copy; $1. Tous droits réservés.","dlgTitle":"À propos de CKEditor","help":"Consulter $1 pour l'aide.","moreInfo":"Pour les informations de licence, veuillez visiter notre site web:","title":"À propos de CKEditor","userGuide":"Guide de l'utilisateur CKEditor en anglais"},"basicstyles":{"bold":"Gras","italic":"Italique","strike":"Barré","subscript":"Indice","superscript":"Exposant","underline":"Souligné"},"blockquote":{"toolbar":"Citation"},"clipboard":{"copy":"Copier","copyError":"Les paramètres de sécurité de votre navigateur ne permettent pas à l'éditeur d'exécuter automatiquement des opérations de copie. Veuillez utiliser le raccourci clavier (Ctrl/Cmd+C).","cut":"Couper","cutError":"Les paramètres de sécurité de votre navigateur ne permettent pas à l'éditeur d'exécuter automatiquement l'opération \"couper\". Veuillez utiliser le raccourci clavier (Ctrl/Cmd+X).","paste":"Coller","pasteArea":"Coller la zone","pasteMsg":"Veuillez coller le texte dans la zone suivante en utilisant le raccourci clavier (<strong>Ctrl/Cmd+V</strong>) et cliquez sur OK.","securityMsg":"A cause des paramètres de sécurité de votre navigateur, l'éditeur n'est pas en mesure d'accéder directement à vos données contenues dans le presse-papier. Vous devriez réessayer de coller les données dans la fenêtre.","title":"Coller"},"contextmenu":{"options":"Options du menu contextuel"},"button":{"selectedLabel":"%1 (Sélectionné)"},"toolbar":{"toolbarCollapse":"Enrouler la barre d'outils","toolbarExpand":"Dérouler la barre d'outils","toolbarGroups":{"document":"Document","clipboard":"Presse-papier/Défaire","editing":"Editer","forms":"Formulaires","basicstyles":"Styles de base","paragraph":"Paragraphe","links":"Liens","insert":"Insérer","styles":"Styles","colors":"Couleurs","tools":"Outils"},"toolbars":"Barre d'outils de l'éditeur"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 éléments"},"format":{"label":"Format","panelTitle":"Format de paragraphe","tag_address":"Adresse","tag_div":"Normal (DIV)","tag_h1":"Titre 1","tag_h2":"Titre 2","tag_h3":"Titre 3","tag_h4":"Titre 4","tag_h5":"Titre 5","tag_h6":"Titre 6","tag_p":"Normal","tag_pre":"Formaté"},"horizontalrule":{"toolbar":"Ligne horizontale"},"image":{"alertUrl":"Veuillez entrer l'adresse de l'image","alt":"Texte de remplacement","border":"Bordure","btnUpload":"Envoyer sur le serveur","button2Img":"Voulez-vous transformer le bouton image sélectionné en simple image?","hSpace":"Espacement horizontal","img2Button":"Voulez-vous transformer l'image en bouton image?","infoTab":"Informations sur l'image","linkTab":"Lien","lockRatio":"Conserver les proportions","menu":"Propriétés de l'image","resetSize":"Taille d'origine","title":"Propriétés de l'image","titleButton":"Propriétés du bouton image","upload":"Envoyer","urlMissing":"L'adresse source de l'image est manquante.","vSpace":"Espacement vertical","validateBorder":"Bordure doit être un entier.","validateHSpace":"HSpace doit être un entier.","validateVSpace":"VSpace doit être un entier."},"indent":{"indent":"Augmenter le retrait (tabulation)","outdent":"Diminuer le retrait (tabulation)"},"fakeobjects":{"anchor":"Ancre","flash":"Animation Flash","hiddenfield":"Champ caché","iframe":"IFrame","unknown":"Objet inconnu"},"link":{"acccessKey":"Touche d'accessibilité","advanced":"Avancé","advisoryContentType":"Type de contenu (ex: text/html)","advisoryTitle":"Description (title)","anchor":{"toolbar":"Ancre","menu":"Editer l'ancre","title":"Propriétés de l'ancre","name":"Nom de l'ancre","errorName":"Veuillez entrer le nom de l'ancre.","remove":"Supprimer l'ancre"},"anchorId":"Par ID d'élément","anchorName":"Par nom d'ancre","charset":"Charset de la cible","cssClasses":"Classe CSS","emailAddress":"Adresse E-Mail","emailBody":"Corps du message","emailSubject":"Sujet du message","id":"Id","info":"Infos sur le lien","langCode":"Code de langue","langDir":"Sens d'écriture","langDirLTR":"Gauche à droite","langDirRTL":"Droite à gauche","menu":"Editer le lien","name":"Nom","noAnchors":"(Aucune ancre disponible dans ce document)","noEmail":"Veuillez entrer l'adresse e-mail","noUrl":"Veuillez entrer l'adresse du lien","other":"<autre>","popupDependent":"Dépendante (Netscape)","popupFeatures":"Options de la fenêtre popup","popupFullScreen":"Plein écran (IE)","popupLeft":"Position gauche","popupLocationBar":"Barre d'adresse","popupMenuBar":"Barre de menu","popupResizable":"Redimensionnable","popupScrollBars":"Barres de défilement","popupStatusBar":"Barre de status","popupToolbar":"Barre d'outils","popupTop":"Position haute","rel":"Relation","selectAnchor":"Sélectionner l'ancre","styles":"Style","tabIndex":"Index de tabulation","target":"Cible","targetFrame":"<cadre>","targetFrameName":"Nom du Cadre destination","targetPopup":"<fenêtre popup>","targetPopupName":"Nom de la fenêtre popup","title":"Lien","toAnchor":"Ancre","toEmail":"E-mail","toUrl":"URL","toolbar":"Lien","type":"Type de lien","unlink":"Supprimer le lien","upload":"Envoyer"},"list":{"bulletedlist":"Insérer/Supprimer la liste à puces","numberedlist":"Insérer/Supprimer la liste numérotée"},"magicline":{"title":"Insérez un paragraphe ici"},"maximize":{"maximize":"Agrandir","minimize":"Minimiser"},"pastetext":{"button":"Coller comme texte sans mise en forme","title":"Coller comme texte sans mise en forme"},"pastefromword":{"confirmCleanup":"Le texte à coller semble provenir de Word. Désirez-vous le nettoyer avant de coller?","error":"Il n'a pas été possible de nettoyer les données collées à la suite d'une erreur interne.","title":"Coller depuis Word","toolbar":"Coller depuis Word"},"removeformat":{"toolbar":"Supprimer la mise en forme"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Options des caractères spéciaux","title":"Sélectionnez un caractère","toolbar":"Insérer un caractère spécial"},"scayt":{"btn_about":"A propos de SCAYT","btn_dictionaries":"Dictionnaires","btn_disable":"Désactiver SCAYT","btn_enable":"Activer SCAYT","btn_langs":"Langues","btn_options":"Options","text_title":"Vérification de l'Orthographe en Cours de Frappe (SCAYT)"},"stylescombo":{"label":"Styles","panelTitle":"Styles de mise en page","panelTitle1":"Styles de blocs","panelTitle2":"Styles en ligne","panelTitle3":"Styles d'objet"},"table":{"border":"Taille de la bordure","caption":"Titre du tableau","cell":{"menu":"Cellule","insertBefore":"Insérer une cellule avant","insertAfter":"Insérer une cellule après","deleteCell":"Supprimer les cellules","merge":"Fusionner les cellules","mergeRight":"Fusionner à droite","mergeDown":"Fusionner en bas","splitHorizontal":"Fractionner horizontalement","splitVertical":"Fractionner verticalement","title":"Propriétés de la cellule","cellType":"Type de cellule","rowSpan":"Fusion de lignes","colSpan":"Fusion de colonnes","wordWrap":"Césure","hAlign":"Alignement Horizontal","vAlign":"Alignement Vertical","alignBaseline":"Bas du texte","bgColor":"Couleur d'arrière-plan","borderColor":"Couleur de Bordure","data":"Données","header":"Entête","yes":"Oui","no":"Non","invalidWidth":"La Largeur de Cellule doit être un nombre.","invalidHeight":"La Hauteur de Cellule doit être un nombre.","invalidRowSpan":"La fusion de lignes doit être un nombre entier.","invalidColSpan":"La fusion de colonnes doit être un nombre entier.","chooseColor":"Choisissez"},"cellPad":"Marge interne des cellules","cellSpace":"Espacement des cellules","column":{"menu":"Colonnes","insertBefore":"Insérer une colonne avant","insertAfter":"Insérer une colonne après","deleteColumn":"Supprimer les colonnes"},"columns":"Colonnes","deleteTable":"Supprimer le tableau","headers":"En-Têtes","headersBoth":"Les deux","headersColumn":"Première colonne","headersNone":"Aucunes","headersRow":"Première ligne","invalidBorder":"La taille de la bordure doit être un nombre.","invalidCellPadding":"La marge intérieure des cellules doit être un nombre positif.","invalidCellSpacing":"L'espacement des cellules doit être un nombre positif.","invalidCols":"Le nombre de colonnes doit être supérieur à 0.","invalidHeight":"La hauteur du tableau doit être un nombre.","invalidRows":"Le nombre de lignes doit être supérieur à 0.","invalidWidth":"La largeur du tableau doit être un nombre.","menu":"Propriétés du tableau","row":{"menu":"Ligne","insertBefore":"Insérer une ligne avant","insertAfter":"Insérer une ligne après","deleteRow":"Supprimer les lignes"},"rows":"Lignes","summary":"Résumé (description)","title":"Propriétés du tableau","toolbar":"Tableau","widthPc":"% pourcents","widthPx":"pixels","widthUnit":"unité de largeur"},"undo":{"redo":"Rétablir","undo":"Annuler"},"wsc":{"btnIgnore":"Ignorer","btnIgnoreAll":"Ignorer tout","btnReplace":"Remplacer","btnReplaceAll":"Remplacer tout","btnUndo":"Annuler","changeTo":"Modifier pour","errorLoading":"Erreur du chargement du service depuis l'hôte : %s.","ieSpellDownload":"La vérification d'orthographe n'est pas installée. Voulez-vous la télécharger maintenant?","manyChanges":"Vérification de l'orthographe terminée : %1 mots corrigés.","noChanges":"Vérification de l'orthographe terminée : Aucun mot corrigé.","noMispell":"Vérification de l'orthographe terminée : aucune erreur trouvée.","noSuggestions":"- Aucune suggestion -","notAvailable":"Désolé, le service est indisponible actuellement.","notInDic":"N'existe pas dans le dictionnaire.","oneChange":"Vérification de l'orthographe terminée : Un seul mot corrigé.","progress":"Vérification de l'orthographe en cours...","title":"Vérifier l'orthographe","toolbar":"Vérifier l'orthographe"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/gl.js b/js/ckeditor/lang/gl.js
new file mode 100644
index 0000000..00f3a5e
--- /dev/null
+++ b/js/ckeditor/lang/gl.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['gl']={"editor":"Editor de texto mellorado","editorPanel":"Panel do editor de texto mellorado","common":{"editorHelp":"Prema ALT 0 para obter axuda","browseServer":"Examinar o servidor","url":"URL","protocol":"Protocolo","upload":"Enviar","uploadSubmit":"Enviar ao servidor","image":"Imaxe","flash":"Flash","form":"Formulario","checkbox":"Caixa de selección","radio":"Botón de opción","textField":"Campo de texto","textarea":"Ãrea de texto","hiddenField":"Campo agochado","button":"Botón","select":"Campo de selección","imageButton":"Botón de imaxe","notSet":"<sen estabelecer>","id":"ID","name":"Nome","langDir":"Dirección de escritura do idioma","langDirLtr":"Esquerda a dereita (LTR)","langDirRtl":"Dereita a esquerda (RTL)","langCode":"Código do idioma","longDescr":"Descrición completa do URL","cssClass":"Clases da folla de estilos","advisoryTitle":"Título","cssStyle":"Estilo","ok":"Aceptar","cancel":"Cancelar","close":"Pechar","preview":"Vista previa","resize":"Redimensionar","generalTab":"Xeral","advancedTab":"Avanzado","validateNumberFailed":"Este valor non é un número.","confirmNewPage":"Calquera cambio que non gardara neste contido perderase.\r\nConfirma que quere cargar unha páxina nova?","confirmCancel":"Algunhas das opcións foron cambiadas.\r\nConfirma que quere pechar o diálogo?","options":"Opcións","target":"Destino","targetNew":"Nova xanela (_blank)","targetTop":"Xanela principal (_top)","targetSelf":"Mesma xanela (_self)","targetParent":"Xanela superior (_parent)","langDirLTR":"Esquerda a dereita (LTR)","langDirRTL":"Dereita a esquerda (RTL)","styles":"Estilo","cssClasses":"Clases da folla de estilos","width":"Largo","height":"Alto","align":"Aliñamento","alignLeft":"Esquerda","alignRight":"Dereita","alignCenter":"Centro","alignJustify":"Xustificado","alignTop":"Arriba","alignMiddle":"Centro","alignBottom":"Abaixo","alignNone":"None","invalidValue":"Valor incorrecto.","invalidHeight":"O alto debe ser un número.","invalidWidth":"O largo debe ser un número.","invalidCssLength":"O valor especificado para o campo «%1» debe ser un número positivo con ou sen unha unidade de medida CSS correcta (px, %, in, cm, mm, em, ex, pt, ou pc).","invalidHtmlLength":"O valor especificado para o campo «%1» debe ser un número positivo con ou sen unha unidade de medida HTML correcta (px ou %).","invalidInlineStyle":"O valor especificado no estilo en liña debe consistir nunha ou máis tuplas co formato «nome : valor», separadas por punto e coma.","cssLengthTooltip":"Escriba un número para o valor en píxeles ou un número cunha unidade CSS correcta (px, %, in, cm, mm, em, ex, pt, ou pc).","unavailable":"%1<span class=\"cke_accessibility\">, non dispoñíbel</span>"},"about":{"copy":"Copyright &copy; $1. Todos os dereitos reservados.","dlgTitle":"Sobre o CKEditor","help":"Consulte $1 para obter axuda.","moreInfo":"Para obter información sobre a licenza, visite o noso sitio web:","title":"Sobre o CKEditor","userGuide":"Guía do usuario do CKEditor"},"basicstyles":{"bold":"Negra","italic":"Cursiva","strike":"Riscado","subscript":"Subíndice","superscript":"Superíndice","underline":"Subliñado"},"blockquote":{"toolbar":"Cita"},"clipboard":{"copy":"Copiar","copyError":"Os axustes de seguranza do seu navegador non permiten que o editor realice automaticamente as tarefas de copia. Use o teclado para iso (Ctrl/Cmd+C).","cut":"Cortar","cutError":"Os axustes de seguranza do seu navegador non permiten que o editor realice automaticamente as tarefas de corte. Use o teclado para iso (Ctrl/Cmd+X).","paste":"Pegar","pasteArea":"Zona de pegado","pasteMsg":"Pegue dentro do seguinte cadro usando o teclado (<STRONG>Ctrl/Cmd+V</STRONG>) e prema en Aceptar","securityMsg":"Por mor da configuración de seguranza do seu navegador, o editor non ten acceso ao portapapeis. É necesario pegalo novamente nesta xanela.","title":"Pegar"},"contextmenu":{"options":"Opcións do menú contextual"},"button":{"selectedLabel":"%1 (seleccionado)"},"toolbar":{"toolbarCollapse":"Contraer a barra de ferramentas","toolbarExpand":"Expandir a barra de ferramentas","toolbarGroups":{"document":"Documento","clipboard":"Portapapeis/desfacer","editing":"Edición","forms":"Formularios","basicstyles":"Estilos básicos","paragraph":"Paragrafo","links":"Ligazóns","insert":"Inserir","styles":"Estilos","colors":"Cores","tools":"Ferramentas"},"toolbars":"Barras de ferramentas do editor"},"elementspath":{"eleLabel":"Ruta dos elementos","eleTitle":"Elemento %1"},"format":{"label":"Formato","panelTitle":"Formato do parágrafo","tag_address":"Enderezo","tag_div":"Normal (DIV)","tag_h1":"Enacabezado 1","tag_h2":"Encabezado 2","tag_h3":"Encabezado 3","tag_h4":"Encabezado 4","tag_h5":"Encabezado 5","tag_h6":"Encabezado 6","tag_p":"Normal","tag_pre":"Formatado"},"horizontalrule":{"toolbar":"Inserir unha liña horizontal"},"image":{"alertUrl":"Escriba o URL da imaxe","alt":"Texto alternativo","border":"Bordo","btnUpload":"Enviar ao servidor","button2Img":"Quere converter o botón da imaxe seleccionada nunha imaxe sinxela?","hSpace":"Esp.Horiz.","img2Button":"Quere converter a imaxe seleccionada nun botón de imaxe?","infoTab":"Información da imaxe","linkTab":"Ligazón","lockRatio":"Proporcional","menu":"Propiedades da imaxe","resetSize":"Tamaño orixinal","title":"Propiedades da imaxe","titleButton":"Propiedades do botón de imaxe","upload":"Cargar","urlMissing":"Non se atopa o URL da imaxe.","vSpace":"Esp.Vert.","validateBorder":"O bordo debe ser un número.","validateHSpace":"O espazado horizontal debe ser un número.","validateVSpace":"O espazado vertical debe ser un número."},"indent":{"indent":"Aumentar a sangría","outdent":"Reducir a sangría"},"fakeobjects":{"anchor":"Ancoraxe","flash":"Animación «Flash»","hiddenfield":"Campo agochado","iframe":"IFrame","unknown":"Obxecto descoñecido"},"link":{"acccessKey":"Chave de acceso","advanced":"Avanzado","advisoryContentType":"Tipo de contido informativo","advisoryTitle":"Título","anchor":{"toolbar":"Ancoraxe","menu":"Editar a ancoraxe","title":"Propiedades da ancoraxe","name":"Nome da ancoraxe","errorName":"Escriba o nome da ancoraxe","remove":"Retirar a ancoraxe"},"anchorId":"Polo ID do elemento","anchorName":"Polo nome da ancoraxe","charset":"Codificación do recurso ligado","cssClasses":"Clases da folla de estilos","emailAddress":"Enderezo de correo","emailBody":"Corpo da mensaxe","emailSubject":"Asunto da mensaxe","id":"ID","info":"Información da ligazón","langCode":"Código do idioma","langDir":"Dirección de escritura do idioma","langDirLTR":"Esquerda a dereita (LTR)","langDirRTL":"Dereita a esquerda (RTL)","menu":"Editar a ligazón","name":"Nome","noAnchors":"(Non hai ancoraxes dispoñíbeis no documento)","noEmail":"Escriba o enderezo de correo","noUrl":"Escriba a ligazón URL","other":"<outro>","popupDependent":"Dependente (Netscape)","popupFeatures":"Características da xanela emerxente","popupFullScreen":"Pantalla completa (IE)","popupLeft":"Posición esquerda","popupLocationBar":"Barra de localización","popupMenuBar":"Barra do menú","popupResizable":"Redimensionábel","popupScrollBars":"Barras de desprazamento","popupStatusBar":"Barra de estado","popupToolbar":"Barra de ferramentas","popupTop":"Posición superior","rel":"Relación","selectAnchor":"Seleccionar unha ancoraxe","styles":"Estilo","tabIndex":"Ãndice de tabulación","target":"Destino","targetFrame":"<marco>","targetFrameName":"Nome do marco de destino","targetPopup":"<xanela emerxente>","targetPopupName":"Nome da xanela emerxente","title":"Ligazón","toAnchor":"Ligar coa ancoraxe no testo","toEmail":"Correo","toUrl":"URL","toolbar":"Ligazón","type":"Tipo de ligazón","unlink":"Eliminar a ligazón","upload":"Enviar"},"list":{"bulletedlist":"Inserir/retirar lista viñeteada","numberedlist":"Inserir/retirar lista numerada"},"magicline":{"title":"Inserir aquí o parágrafo"},"maximize":{"maximize":"Maximizar","minimize":"Minimizar"},"pastetext":{"button":"Pegar como texto simple","title":"Pegar como texto simple"},"pastefromword":{"confirmCleanup":"O texto que quere pegar semella ser copiado desde o Word. Quere depuralo antes de pegalo?","error":"Non foi posíbel depurar os datos pegados por mor dun erro interno","title":"Pegar desde Word","toolbar":"Pegar desde Word"},"removeformat":{"toolbar":"Retirar o formato"},"sourcearea":{"toolbar":"Orixe"},"specialchar":{"options":"Opcións de caracteres especiais","title":"Seleccione un carácter especial","toolbar":"Inserir un carácter especial"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Estilos","panelTitle":"Estilos de formatando","panelTitle1":"Estilos de bloque","panelTitle2":"Estilos de carácter","panelTitle3":"Estilos de obxecto"},"table":{"border":"Tamaño do bordo","caption":"Título","cell":{"menu":"Cela","insertBefore":"Inserir a cela á esquerda","insertAfter":"Inserir a cela á dereita","deleteCell":"Eliminar celas","merge":"Combinar celas","mergeRight":"Combinar á dereita","mergeDown":"Combinar cara abaixo","splitHorizontal":"Dividir a cela en horizontal","splitVertical":"Dividir a cela en vertical","title":"Propiedades da cela","cellType":"Tipo de cela","rowSpan":"Expandir filas","colSpan":"Expandir columnas","wordWrap":"Axustar ao contido","hAlign":"Aliñación horizontal","vAlign":"Aliñación vertical","alignBaseline":"Liña de base","bgColor":"Cor do fondo","borderColor":"Cor do bordo","data":"Datos","header":"Cabeceira","yes":"Si","no":"Non","invalidWidth":"O largo da cela debe ser un número.","invalidHeight":"O alto da cela debe ser un número.","invalidRowSpan":"A expansión de filas debe ser un número enteiro.","invalidColSpan":"A expansión de columnas debe ser un número enteiro.","chooseColor":"Escoller"},"cellPad":"Marxe interior da cela","cellSpace":"Marxe entre celas","column":{"menu":"Columna","insertBefore":"Inserir a columna á esquerda","insertAfter":"Inserir a columna á dereita","deleteColumn":"Borrar Columnas"},"columns":"Columnas","deleteTable":"Borrar Táboa","headers":"Cabeceiras","headersBoth":"Ambas","headersColumn":"Primeira columna","headersNone":"Ningún","headersRow":"Primeira fila","invalidBorder":"O tamaño do bordo debe ser un número.","invalidCellPadding":"A marxe interior debe ser un número positivo.","invalidCellSpacing":"A marxe entre celas debe ser un número positivo.","invalidCols":"O número de columnas debe ser un número maior que 0.","invalidHeight":"O alto da táboa debe ser un número.","invalidRows":"O número de filas debe ser un número maior que 0","invalidWidth":"O largo da táboa debe ser un número.","menu":"Propiedades da táboa","row":{"menu":"Fila","insertBefore":"Inserir a fila por riba","insertAfter":"Inserir a fila por baixo","deleteRow":"Eliminar filas"},"rows":"Filas","summary":"Resumo","title":"Propiedades da táboa","toolbar":"Taboa","widthPc":"porcentaxe","widthPx":"píxeles","widthUnit":"unidade do largo"},"undo":{"redo":"Refacer","undo":"Desfacer"},"wsc":{"btnIgnore":"Ignorar","btnIgnoreAll":"Ignorar Todas","btnReplace":"Substituir","btnReplaceAll":"Substituir Todas","btnUndo":"Desfacer","changeTo":"Cambiar a","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"O corrector ortográfico non está instalado. ¿Quere descargalo agora?","manyChanges":"Corrección ortográfica rematada: %1 verbas substituidas","noChanges":"Corrección ortográfica rematada: Non se substituiu nengunha verba","noMispell":"Corrección ortográfica rematada: Non se atoparon erros","noSuggestions":"- Sen candidatos -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Non está no diccionario","oneChange":"Corrección ortográfica rematada: Unha verba substituida","progress":"Corrección ortográfica en progreso...","title":"Spell Checker","toolbar":"Corrección Ortográfica"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/gu.js b/js/ckeditor/lang/gu.js
new file mode 100644
index 0000000..0299ac0
--- /dev/null
+++ b/js/ckeditor/lang/gu.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['gu']={"editor":"રીચ ટેકà«àª·à«àª¤à« àªàª¡à«€àªŸàª°","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"પà«àª°à«‡àª¸ ALT 0 મદદ માટ","browseServer":"સરà«àªµàª° બà«àª°àª¾àª‰àª કરો","url":"URL","protocol":"પà«àª°à«‹àªŸà«‹àª•à«‰àª²","upload":"અપલોડ","uploadSubmit":"આ સરà«àªµàª°àª¨à«‡ મોકલવà«àª‚","image":"ચિતà«àª°","flash":"ફà«àª²à«…શ","form":"ફૉરà«àª®/પતà«àª°àª•","checkbox":"ચેક બોકà«àª¸","radio":"રેડિઓ બટન","textField":"ટેકà«àª¸à«àªŸ ફીલà«àª¡, શબà«àª¦ કà«àª·à«‡àª¤à«àª°","textarea":"ટેકà«àª¸à«àªŸ àªàª°àª¿àª†, શબà«àª¦ વિસà«àª¤àª¾àª°","hiddenField":"ગà«àªªà«àª¤ કà«àª·à«‡àª¤à«àª°","button":"બટન","select":"પસંદગી કà«àª·à«‡àª¤à«àª°","imageButton":"ચિતà«àª° બટન","notSet":"<સેટ નથી>","id":"Id","name":"નામ","langDir":"ભાષા લેખવાની પદà«àª§àª¤àª¿","langDirLtr":"ડાબે થી જમણે (LTR)","langDirRtl":"જમણે થી ડાબે (RTL)","langCode":"ભાષા કોડ","longDescr":"વધારે માહિતી માટે URL","cssClass":"સà«àªŸàª¾àª‡àª²-શીટ કà«àª²àª¾àª¸","advisoryTitle":"મà«àª–à«àª¯ મથાળà«àª‚","cssStyle":"સà«àªŸàª¾àª‡àª²","ok":"ઠીક છે","cancel":"રદ કરવà«àª‚","close":"બંધ કરવà«àª‚","preview":"જોવà«àª‚","resize":"ખેંચી ને યોગà«àª¯ કરવà«àª‚","generalTab":"જનરલ","advancedTab":"અડà«àªµàª¾àª¨à«àª¸àª¡","validateNumberFailed":"આ રકમ આકડો નથી.","confirmNewPage":"સવે કારà«àª¯ વગરનà«àª‚ ફકરો ખોવાઈ જશે. તમને ખાતરી છે કે તમને નવà«àª‚ પાનà«àª‚ ખોલવà«àª‚ છે?","confirmCancel":"ઘણા વિકલà«àªªà«‹ બદલાયા છે. તમારે આ બોકà«àª·à« બંધ કરવà«àª‚ છે?","options":"વિકલà«àªªà«‹","target":"લકà«àª·à«àª¯","targetNew":"નવી વિનà«àª¡à«‹ (_blank)","targetTop":"ઉપરની વિનà«àª¡à«‹ (_top)","targetSelf":"àªàªœ વિનà«àª¡à«‹ (_self)","targetParent":"પેરનટ વિનà«àª¡à«‹ (_parent)","langDirLTR":"ડાબે થી જમણે (LTR)","langDirRTL":"જમણે થી ડાબે (RTL)","styles":"શૈલી","cssClasses":"શૈલી કલાસીસ","width":"પહોળાઈ","height":"ઊંચાઈ","align":"લાઇનદોરીમાં ગોઠવવà«àª‚","alignLeft":"ડાબી બાજૠગોઠવવà«àª‚","alignRight":"જમણી","alignCenter":"મધà«àª¯ સેનà«àªŸàª°","alignJustify":"બà«àª²à«‰àª•, અંતરાય જસà«àªŸàª¿àª«àª¾àª‡","alignTop":"ઉપર","alignMiddle":"વચà«àªšà«‡","alignBottom":"નીચે","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"ઉંચાઈ àªàª• આંકડો હોવો જોઈàª.","invalidWidth":"પોહળ ઈ àªàª• આંકડો હોવો જોઈàª.","invalidCssLength":"\"%1\" ની વેલà«àª¯à« àªàª• પોસીટીવ આંકડો હોવો જોઈઠઅથવા CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc) વગર.","invalidHtmlLength":"\"%1\" ની વેલà«àª¯à« àªàª• પોસીટીવ આંકડો હોવો જોઈઠઅથવા HTML measurement unit (px or %) વગર.","invalidInlineStyle":"ઈનલાઈન સà«àªŸàª¾àªˆàª² ની વેલà«àª¯à« \"name : value\" ના ફોરà«àª®à«‡àªŸ માં હોવી જોઈàª, વચà«àªšà«‡ સેમી-કોલોન જોઈàª.","cssLengthTooltip":"પિકà«àª·à«àª²à« નો આંકડો CSS unit (px, %, in, cm, mm, em, ex, pt, or pc) માં નાખો.","unavailable":"%1<span class=\"cke_accessibility\">, નથી મળતà«àª‚</span>"},"about":{"copy":"કોપીરાઈટ &copy; $1. ઓલ રાઈટà«àª¸ ","dlgTitle":"CKEditor વિષે","help":"મદદ માટે $1 તપાસો","moreInfo":"લાયસનસની માહિતી માટે અમારી વેબ સાઈટ","title":"CKEditor વિષે","userGuide":"CKEditor યà«àªàª°à«àª¸ ગાઈડ"},"basicstyles":{"bold":"બોલà«àª¡/સà«àªªàª·à«àªŸ","italic":"ઇટેલિક, તà«àª°àª¾àª‚સા","strike":"છેકી નાખવà«àª‚","subscript":"àªàª• ચિહà«àª¨àª¨à«€ નીચે કરેલà«àª‚ બીજà«àª‚ ચિહà«àª¨","superscript":"àªàª• ચિહà«àª¨ ઉપર કરેલà«àª‚ બીજà«àª‚ ચિહà«àª¨.","underline":"અનà«àª¡àª°à«àª²àª¾àª‡àª¨, નીચે લીટી"},"blockquote":{"toolbar":"બà«àª²à«‰àª•-કોટ, અવતરણચિહà«àª¨à«‹"},"clipboard":{"copy":"નકલ","copyError":"તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસ કોપી કરવાની પરવાનગી નથી આપતી. (Ctrl/Cmd+C) का पà¥à¤°à¤¯à¥‹à¤— करें।","cut":"કાપવà«àª‚","cutError":"તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસ કટ કરવાની પરવાનગી નથી આપતી. (Ctrl/Cmd+X) નો ઉપયોગ કરો.","paste":"પેસà«àªŸ","pasteArea":"પેસà«àªŸ કરવાની જગà«àª¯àª¾","pasteMsg":"Ctrl/Cmd+V નો પà«àª°àª¯à«‹àª— કરી પેસà«àªŸ કરો","securityMsg":"તમારા બà«àª°àª¾àª‰àªàª° ની સà«àª°àª•à«àª·àª¿àª¤ સેટિંગસના કારણે,àªàª¡àª¿àªŸàª° તમારા કિલà«àªªàª¬à«‹àª°à«àª¡ ડેટા ને કોપી નથી કરી શકતો. તમારે આ વિનà«àª¡à«‹àª®àª¾àª‚ ફરીથી પેસà«àªŸ કરવà«àª‚ પડશે.","title":"પેસà«àªŸ"},"contextmenu":{"options":"કોનà«àª¤à«‡àª•à«àª·à«àª¤à« મેનà«àª¨àª¾ વિકલà«àªªà«‹"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"ટૂલબાર નાનà«àª‚ કરવà«àª‚","toolbarExpand":"ટૂલબાર મોટà«àª‚ કરવà«àª‚","toolbarGroups":{"document":"દસà«àª¤àª¾àªµà«‡àªœ","clipboard":"કà«àª²àª¿àªªàª¬à«‹àª°à«àª¡/અન","editing":"àªàª¡à«€àªŸ કરવà«àª‚","forms":"ફોરà«àª®","basicstyles":"બેસિકૠસà«àªŸàª¾àª‡àª²","paragraph":"ફકરો","links":"લીંક","insert":"ઉમેરવà«àª‚","styles":"સà«àªŸàª¾àª‡àª²","colors":"રંગ","tools":"ટૂલà«àª¸"},"toolbars":"àªàª¡à«€àªŸàª° ટૂલ બાર"},"elementspath":{"eleLabel":"àªàª²à«€àª®à«‡àª¨à«àªŸà«àª¸ નો ","eleTitle":"àªàª²à«€àª®à«‡àª¨à«àªŸ %1"},"format":{"label":"ફૉનà«àªŸ ફૉરà«àª®àªŸ, રચનાની શૈલી","panelTitle":"ફૉનà«àªŸ ફૉરà«àª®àªŸ, રચનાની શૈલી","tag_address":"સરનામà«àª‚","tag_div":"શીરà«àª·àª• (DIV)","tag_h1":"શીરà«àª·àª• 1","tag_h2":"શીરà«àª·àª• 2","tag_h3":"શીરà«àª·àª• 3","tag_h4":"શીરà«àª·àª• 4","tag_h5":"શીરà«àª·àª• 5","tag_h6":"શીરà«àª·àª• 6","tag_p":"સામાનà«àª¯","tag_pre":"ફૉરà«àª®àªŸà«‡àª¡"},"horizontalrule":{"toolbar":"સમસà«àª¤àª°à«€àª¯ રેખા ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી"},"image":{"alertUrl":"ચિતà«àª°àª¨à«€ URL ટાઇપ કરો","alt":"ઑલà«àªŸàª°à«àª¨àªŸ ટેકà«àª¸à«àªŸ","border":"બોરà«àª¡àª°","btnUpload":"આ સરà«àªµàª°àª¨à«‡ મોકલવà«àª‚","button2Img":"તમારે ઈમેજ બટનને સાદી ઈમેજમાં બદલવà«àª‚ છે.","hSpace":"સમસà«àª¤àª°à«€àª¯ જગà«àª¯àª¾","img2Button":"તમારે સાદી ઈમેજને ઈમેજ બટનમાં બદલવà«àª‚ છે.","infoTab":"ચિતà«àª° ની જાણકારી","linkTab":"લિંક","lockRatio":"લૉક ગà«àª£à«‹àª¤à«àª¤àª°","menu":"ચિતà«àª°àª¨àª¾ ગà«àª£","resetSize":"રીસેટ સાઇàª","title":"ચિતà«àª°àª¨àª¾ ગà«àª£","titleButton":"ચિતà«àª° બટનના ગà«àª£","upload":"અપલોડ","urlMissing":"ઈમેજની મૂળ URL છે નહી.","vSpace":"લંબરૂપ જગà«àª¯àª¾","validateBorder":"બોરà«àª¡à«‡àª° આંકડો હોવો જોઈàª.","validateHSpace":"HSpaceઆંકડો હોવો જોઈàª.","validateVSpace":"VSpace આંકડો હોવો જોઈàª. "},"indent":{"indent":"ઇનà«àª¡à«‡àª¨à«àªŸ, લીટીના આરંભમાં જગà«àª¯àª¾ વધારવી","outdent":"ઇનà«àª¡à«‡àª¨à«àªŸ લીટીના આરંભમાં જગà«àª¯àª¾ ઘટાડવી"},"fakeobjects":{"anchor":"અનકર","flash":"ફà«àª²à«‡àª¶ ","hiddenfield":"હિડન ","iframe":"IFrame","unknown":"અનનોન ઓબà«àªœà«‡àª•à«àªŸ"},"link":{"acccessKey":"àªàª•à«àª¸à«‡àª¸ કી","advanced":"અડà«àªµàª¾àª¨à«àª¸àª¡","advisoryContentType":"મà«àª–à«àª¯ કનà«àªŸà«‡àª¨à«àªŸ પà«àª°àª•àª¾àª°","advisoryTitle":"મà«àª–à«àª¯ મથાળà«àª‚","anchor":{"toolbar":"àªàª‚કર ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી","menu":"àªàª‚કરના ગà«àª£","title":"àªàª‚કરના ગà«àª£","name":"àªàª‚કરનà«àª‚ નામ","errorName":"àªàª‚કરનà«àª‚ નામ ટાઈપ કરો","remove":"સà«àª¥àª¿àª° નકરવà«àª‚"},"anchorId":"àªàª‚કર àªàª²àª¿àª®àª¨à«àªŸ Id થી પસંદ કરો","anchorName":"àªàª‚કર નામથી પસંદ કરો","charset":"લિંક રિસૉરà«àª¸ કૅરિકà«àªŸàª° સેટ","cssClasses":"સà«àªŸàª¾àª‡àª²-શીટ કà«àª²àª¾àª¸","emailAddress":"ઈ-મેલ સરનામà«àª‚","emailBody":"સંદેશ","emailSubject":"ઈ-મેલ વિષય","id":"Id","info":"લિંક ઇનà«àª«à«‰ ટૅબ","langCode":"ભાષા લેખવાની પદà«àª§àª¤àª¿","langDir":"ભાષા લેખવાની પદà«àª§àª¤àª¿","langDirLTR":"ડાબે થી જમણે (LTR)","langDirRTL":"જમણે થી ડાબે (RTL)","menu":" લિંક àªàª¡àª¿àªŸ/માં ફેરફાર કરવો","name":"નામ","noAnchors":"(ડૉકà«àª¯à«àª®àª¨à«àªŸàª®àª¾àª‚ àªàª‚કરની સંખà«àª¯àª¾)","noEmail":"ઈ-મેલ સરનામà«àª‚ ટાઇપ કરો","noUrl":"લિંક URL ટાઇપ કરો","other":"<other> <અનà«àª¯>","popupDependent":"ડિપેનà«àª¡àª¨à«àªŸ (Netscape)","popupFeatures":"પૉપ-અપ વિનà«àª¡à«‹ ફીચરસૅ","popupFullScreen":"ફà«àª² સà«àª•à«àª°à«€àª¨ (IE)","popupLeft":"ડાબી બાજà«","popupLocationBar":"લોકેશન બાર","popupMenuBar":"મેનà«àª¯à«‚ બાર","popupResizable":"રીસાઈàªàªàª¬àª²","popupScrollBars":"સà«àª•à«àª°à«‹àª² બાર","popupStatusBar":"સà«àªŸà«…ટસ બાર","popupToolbar":"ટૂલ બાર","popupTop":"જમણી બાજà«","rel":"સંબંધની સà«àª¥àª¿àª¤àª¿","selectAnchor":"àªàª‚કર પસંદ કરો","styles":"સà«àªŸàª¾àª‡àª²","tabIndex":"ટૅબ ઇનà«àª¡à«‡àª•à«àª¸","target":"ટારà«àª—ેટ/લકà«àª·à«àª¯","targetFrame":"<ફà«àª°à«‡àª®>","targetFrameName":"ટારà«àª—ેટ ફà«àª°à«‡àª® નà«àª‚ નામ","targetPopup":"<પૉપ-અપ વિનà«àª¡à«‹>","targetPopupName":"પૉપ-અપ વિનà«àª¡à«‹ નà«àª‚ નામ","title":"લિંક","toAnchor":"આ પેજનો àªàª‚કર","toEmail":"ઈ-મેલ","toUrl":"URL","toolbar":"લિંક ઇનà«àª¸àª°à«àªŸ/દાખલ કરવી","type":"લિંક પà«àª°àª•àª¾àª°","unlink":"લિંક કાઢવી","upload":"અપલોડ"},"list":{"bulletedlist":"બà«àª²à«‡àªŸ સૂચિ","numberedlist":"સંખà«àª¯àª¾àª‚કન સૂચિ"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"મોટà«àª‚ કરવà«àª‚","minimize":"નાનà«àª‚ કરવà«àª‚"},"pastetext":{"button":"પેસà«àªŸ (ટેકà«àª¸à«àªŸ)","title":"પેસà«àªŸ (ટેકà«àª¸à«àªŸ)"},"pastefromword":{"confirmCleanup":"તમે જે ટેકà«àª·à«àª¤à« કોપી કરી રહà«àª¯àª¾ છો ટે વરà«àª¡ ની છે. કોપી કરતા પેહલા સાફ કરવી છે?","error":"પેસà«àªŸ કરેલો ડેટા ઇનà«àªŸàª°àª¨àª² àªàª°àª° ના લીથે સાફ કરી શકાયો નથી.","title":"પેસà«àªŸ (વડૅ ટેકà«àª¸à«àªŸ)","toolbar":"પેસà«àªŸ (વડૅ ટેકà«àª¸à«àªŸ)"},"removeformat":{"toolbar":"ફૉરà«àª®àªŸ કાઢવà«àª‚"},"sourcearea":{"toolbar":"મૂળ કે પà«àª°àª¾àª¥àª®àª¿àª• દસà«àª¤àª¾àªµà«‡àªœ"},"specialchar":{"options":"સà«àªªà«‡àª¶àª¿àª…લ કરેકà«àªŸàª°àª¨àª¾ વિકલà«àªªà«‹","title":"સà«àªªà«‡àª¶àª¿àª…લ વિશિષà«àªŸ અકà«àª·àª° પસંદ કરો","toolbar":"વિશિષà«àªŸ અકà«àª·àª° ઇનà«àª¸àª°à«àªŸ/દાખલ કરવà«àª‚"},"scayt":{"btn_about":"SCAYT વિષે","btn_dictionaries":"શબà«àª¦àª•à«‹àª¶","btn_disable":"SCAYT ડિસેબલ કરવà«àª‚","btn_enable":"SCAYT àªàª¨à«‡àª¬àª² કરવà«àª‚","btn_langs":"ભાષાઓ","btn_options":"વિકલà«àªªà«‹","text_title":"ટાઈપ કરતા સà«àªªà«‡àª² તપાસો"},"stylescombo":{"label":"શૈલી/રીત","panelTitle":"ફોરà«àª®à«‡àªŸ ","panelTitle1":"બà«àª²à«‹àª• ","panelTitle2":"ઈનલાઈન ","panelTitle3":"ઓબà«àªœà«‡àª•à«àªŸ પદà«àª§àª¤àª¿"},"table":{"border":"કોઠાની બાજà«(બોરà«àª¡àª°) સાઇàª","caption":"મથાળà«àª‚/કૅપà«àª¶àª¨ ","cell":{"menu":"કોષના ખાના","insertBefore":"પહેલાં કોષ ઉમેરવો","insertAfter":"પછી કોષ ઉમેરવો","deleteCell":"કોષ ડિલીટ/કાઢી નાખવો","merge":"કોષ ભેગા કરવા","mergeRight":"જમણી બાજૠભેગા કરવા","mergeDown":"નીચે ભેગા કરવા","splitHorizontal":"કોષને સમસà«àª¤àª°à«€àª¯ વિભાજન કરવà«àª‚","splitVertical":"કોષને સીધà«àª‚ ને ઊભà«àª‚ વિભાજન કરવà«àª‚","title":"સેલના ગà«àª£","cellType":"સેલનો પà«àª°àª•àª¾àª°","rowSpan":"આડી કટારની જગà«àª¯àª¾","colSpan":"ઊભી કતારની જગà«àª¯àª¾","wordWrap":"વરà«àª¡ રેપ","hAlign":"સપાટ લાઈનદોરી","vAlign":"ઊભી લાઈનદોરી","alignBaseline":"બસે લાઈન","bgColor":"પાછાળનો રંગ","borderColor":"બોરà«àª¡à«‡àª° રંગ","data":"સà«àªµà«€àª•à«ƒàª¤ માહિતી","header":"મથાળà«àª‚","yes":"હા","no":"ના","invalidWidth":"સેલની પોહલાઈ આંકડો હોવો જોઈàª.","invalidHeight":"સેલની ઊંચાઈ આંકડો હોવો જોઈàª.","invalidRowSpan":"રો સà«àªªàª¾àª¨ આંકડો હોવો જોઈàª.","invalidColSpan":"કોલમ સà«àªªàª¾àª¨ આંકડો હોવો જોઈàª.","chooseColor":"પસંદ કરવà«àª‚"},"cellPad":"સેલ પૅડિંગ","cellSpace":"સેલ અંતર","column":{"menu":"કૉલમ/ઊભી કટાર","insertBefore":"પહેલાં કૉલમ/ઊભી કટાર ઉમેરવી","insertAfter":"પછી કૉલમ/ઊભી કટાર ઉમેરવી","deleteColumn":"કૉલમ/ઊભી કટાર ડિલીટ/કાઢી નાખવી"},"columns":"કૉલમ/ઊભી કટાર","deleteTable":"કોઠો ડિલીટ/કાઢી નાખવà«àª‚","headers":"મથાળા","headersBoth":"બેવà«àª‚","headersColumn":"પહેલી ઊભી કટાર","headersNone":"નથી ","headersRow":"પહેલી કટાર","invalidBorder":"બોરà«àª¡àª° àªàª• આંકડો હોવો જોઈàª","invalidCellPadding":"સેલની અંદરની જગà«àª¯àª¾ સà«àª¨à«àª¯ કરતા વધારે હોવી જોઈàª.","invalidCellSpacing":"સેલ વચà«àªšà«‡àª¨à«€ જગà«àª¯àª¾ સà«àª¨à«àª¯ કરતા વધારે હોવી જોઈàª.","invalidCols":"ઉભી કટાર, 0 કરતા વધારે હોવી જોઈàª.","invalidHeight":"ટેબલની ઊંચાઈ આંકડો હોવો જોઈàª.","invalidRows":"આડી કટાર, 0 કરતા વધારે હોવી જોઈàª.","invalidWidth":"ટેબલની પોહલાઈ આંકડો હોવો જોઈàª.","menu":"ટેબલ, કોઠાનà«àª‚ મથાળà«àª‚","row":{"menu":"પંકà«àª¤àª¿àª¨àª¾ ખાના","insertBefore":"પહેલાં પંકà«àª¤àª¿ ઉમેરવી","insertAfter":"પછી પંકà«àª¤àª¿ ઉમેરવી","deleteRow":"પંકà«àª¤àª¿àª“ ડિલીટ/કાઢી નાખવી"},"rows":"પંકà«àª¤àª¿àª¨àª¾ ખાના","summary":"ટૂંકો àªàª¹à«‡àªµàª¾àª²","title":"ટેબલ, કોઠાનà«àª‚ મથાળà«àª‚","toolbar":"ટેબલ, કોઠો","widthPc":"પà«àª°àª¤àª¿àª¶àª¤","widthPx":"પિકસલ","widthUnit":"પોહાલાઈ àªàª•àª®"},"undo":{"redo":"રિડૂ; પછી હતી àªàªµà«€ સà«àª¥àª¿àª¤àª¿ પાછી લાવવી","undo":"રદ કરવà«àª‚; પહેલાં હતી àªàªµà«€ સà«àª¥àª¿àª¤àª¿ પાછી લાવવી"},"wsc":{"btnIgnore":"ઇગà«àª¨à«‹àª°/અવગણના કરવી","btnIgnoreAll":"બધાની ઇગà«àª¨à«‹àª°/અવગણના કરવી","btnReplace":"બદલવà«àª‚","btnReplaceAll":"બધા બદલી કરો","btnUndo":"અનà«àª¡à«‚","changeTo":"આનાથી બદલવà«àª‚","errorLoading":"સરà«àªµàª¿àª¸ àªàªªà«àª²à«€àª•à«‡àª¶àª¨ લોડ નથી થ: %s.","ieSpellDownload":"સà«àªªà«‡àª²-ચેકર ઇનà«àª¸à«àªŸà«‹àª² નથી. શà«àª‚ તમે ડાઉનલોડ કરવા માંગો છો?","manyChanges":"શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: %1 શબà«àª¦ બદલયા છે","noChanges":"શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: àªàª•àªªàª£ શબà«àª¦ બદલયો નથી","noMispell":"શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: ખોટી જોડણી મળી નથી","noSuggestions":"- કઇ સજેશન નથી -","notAvailable":"માફ કરશો, આ સà«àªµàª¿àª§àª¾ ઉપલબà«àª§ નથી","notInDic":"શબà«àª¦àª•à«‹àª¶àª®àª¾àª‚ નથી","oneChange":"શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક પૂરà«àª£: àªàª• શબà«àª¦ બદલયો છે","progress":"શબà«àª¦àª¨à«€ જોડણી/સà«àªªà«‡àª² ચેક ચાલૠછે...","title":"સà«àªªà«‡àª² ","toolbar":"જોડણી (સà«àªªà«‡àª²àª¿àª‚ગ) તપાસવી"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/he.js b/js/ckeditor/lang/he.js
new file mode 100644
index 0000000..1384b8c
--- /dev/null
+++ b/js/ckeditor/lang/he.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['he']={"editor":"עורך טקסט עשיר","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"לחץ ×לט ALT + 0 לעזרה","browseServer":"סייר השרת","url":"כתובת (URL)","protocol":"פרוטוקול","upload":"העל××”","uploadSubmit":"שליחה לשרת","image":"תמונה","flash":"פל×ש","form":"טופס","checkbox":"תיבת סימון","radio":"לחצן ×פשרויות","textField":"שדה טקסט","textarea":"×יזור טקסט","hiddenField":"שדה חבוי","button":"כפתור","select":"שדה בחירה","imageButton":"כפתור תמונה","notSet":"<×œ× × ×§×‘×¢>","id":"זיהוי (ID)","name":"ש×","langDir":"כיוון שפה","langDirLtr":"שמ×ל לימין (LTR)","langDirRtl":"ימין לשמ×ל (RTL)","langCode":"קוד שפה","longDescr":"קישור לתי×ור מפורט","cssClass":"מחלקת עיצוב (CSS Class)","advisoryTitle":"כותרת מוצעת","cssStyle":"סגנון","ok":"×ישור","cancel":"ביטול","close":"סגירה","preview":"תצוגה מקדימה","resize":"יש לגרור בכדי לשנות ×ת הגודל","generalTab":"כללי","advancedTab":"×פשרויות מתקדמות","validateNumberFailed":"הערך חייב להיות מספרי.","confirmNewPage":"כל ×”×©×™× ×•×™×™× ×©×œ× × ×©×ž×¨×• ×™×בדו. ×”×× ×œ×”×¢×œ×•×ª דף חדש?","confirmCancel":"חלק מה×פשרויות שונו, ×”×× ×œ×¡×’×•×¨ ×ת הדי×לוג?","options":"×פשרויות","target":"מטרה","targetNew":"חלון חדש (_blank)","targetTop":"החלון העליון ביותר (_top)","targetSelf":"×ותו חלון (_self)","targetParent":"חלון ×”×ב (_parent)","langDirLTR":"שמ×ל לימין (LTR)","langDirRTL":"ימין לשמ×ל (RTL)","styles":"סגנון","cssClasses":"מחלקות גליונות סגנון","width":"רוחב","height":"גובה","align":"יישור","alignLeft":"לשמ×ל","alignRight":"לימין","alignCenter":"מרכז","alignJustify":"יישור לשוליי×","alignTop":"למעלה","alignMiddle":"ל×מצע","alignBottom":"לתחתית","alignNone":"None","invalidValue":"ערך ×œ× ×—×•×§×™.","invalidHeight":"הגובה חייב להיות מספר.","invalidWidth":"הרוחב חייב להיות מספר.","invalidCssLength":"הערך שצוין לשדה \"%1\" חייב להיות מספר חיובי ×¢× ×ו ×œ×œ× ×™×—×™×“×ª מידה חוקית של CSS (px, %, in, cm, mm, em, ex, pt, ×ו pc).","invalidHtmlLength":"הערך שצוין לשדה \"%1\" חייב להיות מספר חיובי ×¢× ×ו ×œ×œ× ×™×—×™×“×ª מידה חוקית של HTML (px ×ו %).","invalidInlineStyle":"הערך שצויין לשדה הסגנון חייב להכיל זוג ×¢×¨×›×™× ×חד ×ו יותר בפורמט \"×©× : ערך\", ×ž×•×¤×¨×“×™× ×¢×œ ידי נקודה-פסיק.","cssLengthTooltip":"יש להכניס מספר המייצג ×¤×™×§×¡×œ×™× ×ו מספר ×¢× ×™×—×™×“×ª גליונות סגנון תקינה (px, %, in, cm, mm, em, ex, pt, ×ו pc).","unavailable":"%1<span class=\"cke_accessibility\">, ×œ× ×–×ž×™×Ÿ</span>"},"about":{"copy":"Copyright &copy; $1. כל הזכויות שמורות.","dlgTitle":"×ודות CKEditor","help":"היכנסו ל$1 לעזרה.","moreInfo":"למידע נוסף בקרו ב×תרנו:","title":"×ודות CKEditor","userGuide":"מדריך המשתמש של CKEditor"},"basicstyles":{"bold":"מודגש","italic":"נטוי","strike":"כתיב מחוק","subscript":"כתיב תחתון","superscript":"כתיב עליון","underline":"קו תחתון"},"blockquote":{"toolbar":"בלוק ציטוט"},"clipboard":{"copy":"העתקה","copyError":"הגדרות ×”×בטחה בדפדפן שלך ×œ× ×ž×פשרות לעורך לבצע פעולות העתקה ×וטומטיות. יש להשתמש במקלדת ×œ×©× ×›×š (Ctrl/Cmd+C).","cut":"גזירה","cutError":"הגדרות ×”×בטחה בדפדפן שלך ×œ× ×ž×פשרות לעורך לבצע פעולות גזירה ×וטומטיות. יש להשתמש במקלדת ×œ×©× ×›×š (Ctrl/Cmd+X).","paste":"הדבקה","pasteArea":"×יזור הדבקה","pasteMsg":"× × ×œ×”×“×‘×™×§ בתוך הקופסה ב×מצעות (<b>Ctrl/Cmd+V</b>) וללחוץ על <b>×ישור</b>.","securityMsg":"עקב הגדרות ×בטחה בדפדפן, ×œ× × ×™×ª×Ÿ לגשת ×ל לוח ×”×’×–×™×¨×™× (Clipboard) בצורה ישירה. × × ×œ×”×“×‘×™×§ שוב בחלון ×–×”.","title":"הדבקה"},"contextmenu":{"options":"×פשרויות תפריט ההקשר"},"button":{"selectedLabel":"1% (סומן)"},"toolbar":{"toolbarCollapse":"מזעור סרגל כלי×","toolbarExpand":"הרחבת סרגל כלי×","toolbarGroups":{"document":"מסמך","clipboard":"לוח ×”×’×–×™×¨×™× (Clipboard)/צעד ×חרון","editing":"עריכה","forms":"טפסי×","basicstyles":"עיצוב בסיסי","paragraph":"פסקה","links":"קישורי×","insert":"הכנסה","styles":"עיצוב","colors":"צבעי×","tools":"כלי×"},"toolbars":"סרגלי ×›×œ×™× ×©×œ העורך"},"elementspath":{"eleLabel":"×¢×¥ ×”×למנטי×","eleTitle":"%1 ×למנט"},"format":{"label":"עיצוב","panelTitle":"עיצוב","tag_address":"כתובת","tag_div":"נורמלי (DIV)","tag_h1":"כותרת","tag_h2":"כותרת 2","tag_h3":"כותרת 3","tag_h4":"כותרת 4","tag_h5":"כותרת 5","tag_h6":"כותרת 6","tag_p":"נורמלי","tag_pre":"קוד"},"horizontalrule":{"toolbar":"הוספת קו ×ופקי"},"image":{"alertUrl":"יש להקליד ×ת כתובת התמונה","alt":"טקסט חלופי","border":"מסגרת","btnUpload":"שליחה לשרת","button2Img":"×”×× ×œ×”×¤×•×š ×ת תמונת הכפתור לתמונה פשוטה?","hSpace":"מרווח ×ופקי","img2Button":"×”×× ×œ×”×¤×•×š ×ת התמונה לכפתור תמונה?","infoTab":"מידע על התמונה","linkTab":"קישור","lockRatio":"נעילת היחס","menu":"תכונות התמונה","resetSize":"×יפוס הגודל","title":"מ×פייני התמונה","titleButton":"מ×פיני כפתור תמונה","upload":"העל××”","urlMissing":"כתובת התמונה חסרה.","vSpace":"מרווח ×× ×›×™","validateBorder":"שדה המסגרת חייב להיות מספר של×.","validateHSpace":"שדה המרווח ×”×ופקי חייב להיות מספר של×.","validateVSpace":"שדה המרווח ×”×× ×›×™ חייב להיות מספר של×."},"indent":{"indent":"הגדלת ×”×–×—×”","outdent":"הקטנת ×”×–×—×”"},"fakeobjects":{"anchor":"עוגן","flash":"סרטון פל×ש","hiddenfield":"שדה חבוי","iframe":"חלון פנימי (iframe)","unknown":"×ובייקט ×œ× ×™×“×•×¢"},"link":{"acccessKey":"מקש גישה","advanced":"×פשרויות מתקדמות","advisoryContentType":"Content Type מוצע","advisoryTitle":"כותרת מוצעת","anchor":{"toolbar":"הוספת/עריכת נקודת עיגון","menu":"מ×פייני נקודת עיגון","title":"מ×פייני נקודת עיגון","name":"×©× ×œ× ×§×•×“×ª עיגון","errorName":"יש להקליד ×©× ×œ× ×§×•×“×ª עיגון","remove":"מחיקת נקודת עיגון"},"anchorId":"עפ\"×™ זיהוי (ID) ×”×למנט","anchorName":"עפ\"×™ ×©× ×”×¢×•×’×Ÿ","charset":"קידוד המש×ב המקושר","cssClasses":"גיליונות עיצוב קבוצות","emailAddress":"כתובת הדו×\"ל","emailBody":"גוף ההודעה","emailSubject":"× ×•×©× ×”×”×•×“×¢×”","id":"זיהוי (ID)","info":"מידע על הקישור","langCode":"קוד שפה","langDir":"כיוון שפה","langDirLTR":"שמ×ל לימין (LTR)","langDirRTL":"ימין לשמ×ל (RTL)","menu":"מ×פייני קישור","name":"ש×","noAnchors":"(×ין ×¢×•×’× ×™× ×–×ž×™× ×™× ×‘×“×£)","noEmail":"יש להקליד ×ת כתובת הדו×\"ל","noUrl":"יש להקליד ×ת כתובת הקישור (URL)","other":"<×חר>","popupDependent":"תלוי (Netscape)","popupFeatures":"תכונות החלון הקופץ","popupFullScreen":"מסך ×ž×œ× (IE)","popupLeft":"×ž×™×§×•× ×¦×“ שמ×ל","popupLocationBar":"סרגל כתובת","popupMenuBar":"סרגל תפריט","popupResizable":"שינוי גודל","popupScrollBars":"ניתן לגלילה","popupStatusBar":"סרגל חיווי","popupToolbar":"סרגל הכלי×","popupTop":"×ž×™×§×•× ×¦×“ עליון","rel":"קשר גומלין","selectAnchor":"בחירת עוגן","styles":"סגנון","tabIndex":"מספר ט×ב","target":"מטרה","targetFrame":"<מסגרת>","targetFrameName":"×©× ×ž×¡×’×¨×ª היעד","targetPopup":"<חלון קופץ>","targetPopupName":"×©× ×”×—×œ×•×Ÿ הקופץ","title":"קישור","toAnchor":"עוגן בעמוד ×–×”","toEmail":"דו×\"ל","toUrl":"כתובת (URL)","toolbar":"הוספת/עריכת קישור","type":"סוג קישור","unlink":"הסרת הקישור","upload":"העל××”"},"list":{"bulletedlist":"רשימת נקודות","numberedlist":"רשימה ממוספרת"},"magicline":{"title":"הכנס פסקה ×›×ן"},"maximize":{"maximize":"הגדלה למקסימו×","minimize":"הקטנה למינימו×"},"pastetext":{"button":"הדבקה כטקסט פשוט","title":"הדבקה כטקסט פשוט"},"pastefromword":{"confirmCleanup":"נר××” הטקסט שבכוונתך להדביק מקורו בקובץ וורד. ×”×× ×‘×¨×¦×•× ×š לנקות ×ותו ×˜×¨× ×”×”×“×‘×§×”?","error":"×œ× × ×™×ª×Ÿ ×”×™×” לנקות ×ת המידע בשל תקלה פנימית.","title":"הדבקה מ-Word","toolbar":"הדבקה מ-Word"},"removeformat":{"toolbar":"הסרת העיצוב"},"sourcearea":{"toolbar":"מקור"},"specialchar":{"options":"×פשרויות ×ª×•×•×™× ×ž×™×•×—×“×™×","title":"בחירת תו מיוחד","toolbar":"הוספת תו מיוחד"},"scayt":{"btn_about":"×ודות SCAYT","btn_dictionaries":"מילון","btn_disable":"בטל SCAYT","btn_enable":"×פשר SCAYT","btn_langs":"שפות","btn_options":"×פשרויות","text_title":"בדיקת ×יות בזמן כתיבה (SCAYT)"},"stylescombo":{"label":"סגנון","panelTitle":"סגנונות פורמט","panelTitle1":"סגנונות בלוק","panelTitle2":"סגנונות רצף","panelTitle3":"סגנונות ×ובייקט"},"table":{"border":"גודל מסגרת","caption":"כיתוב","cell":{"menu":"מ×פייני ת×","insertBefore":"הוספת ×ª× ×œ×¤× ×™","insertAfter":"הוספת ×ª× ×חרי","deleteCell":"מחיקת ת××™×","merge":"מיזוג ת××™×","mergeRight":"מזג ימינה","mergeDown":"מזג למטה","splitHorizontal":"פיצול ×ª× ×ופקית","splitVertical":"פיצול ×ª× ×נכית","title":"תכונות הת×","cellType":"סוג הת×","rowSpan":"מתיחת השורות","colSpan":"מתיחת הת××™×","wordWrap":"מניעת גלישת שורות","hAlign":"יישור ×ופקי","vAlign":"יישור ×× ×›×™","alignBaseline":"שורת בסיס","bgColor":"צבע רקע","borderColor":"צבע מסגרת","data":"מידע","header":"כותרת","yes":"כן","no":"ל×","invalidWidth":"שדה רוחב ×”×ª× ×—×™×™×‘ להיות מספר.","invalidHeight":"שדה גובה ×”×ª× ×—×™×™×‘ להיות מספר.","invalidRowSpan":"שדה מתיחת השורות חייב להיות מספר של×.","invalidColSpan":"שדה מתיחת העמודות חייב להיות מספר של×.","chooseColor":"בחר"},"cellPad":"ריפוד ת×","cellSpace":"מרווח ת×","column":{"menu":"עמודה","insertBefore":"הוספת עמודה לפני","insertAfter":"הוספת עמודה ×חרי","deleteColumn":"מחיקת עמודות"},"columns":"עמודות","deleteTable":"מחק טבלה","headers":"כותרות","headersBoth":"שניה×","headersColumn":"עמודה ר×שונה","headersNone":"×ין","headersRow":"שורה ר×שונה","invalidBorder":"שדה גודל המסגרת חייב להיות מספר.","invalidCellPadding":"שדה ריפוד הת××™× ×—×™×™×‘ להיות מספר חיובי.","invalidCellSpacing":"שדה ריווח הת××™× ×—×™×™×‘ להיות מספר חיובי.","invalidCols":"שדה מספר העמודות חייב להיות מספר גדול מ 0.","invalidHeight":"שדה גובה הטבלה חייב להיות מספר.","invalidRows":"שדה מספר השורות חייב להיות מספר גדול מ 0.","invalidWidth":"שדה רוחב הטבלה חייב להיות מספר.","menu":"מ×פייני טבלה","row":{"menu":"שורה","insertBefore":"הוספת שורה לפני","insertAfter":"הוספת שורה ×חרי","deleteRow":"מחיקת שורות"},"rows":"שורות","summary":"תקציר","title":"מ×פייני טבלה","toolbar":"טבלה","widthPc":"×חוז","widthPx":"פיקסלי×","widthUnit":"יחידת רוחב"},"undo":{"redo":"חזרה על צעד ×חרון","undo":"ביטול צעד ×חרון"},"wsc":{"btnIgnore":"התעלמות","btnIgnoreAll":"התעלמות מהכל","btnReplace":"החלפה","btnReplaceAll":"החלפת הכל","btnUndo":"החזרה","changeTo":"שינוי ל","errorLoading":"שגי××” בהעל×ת השירות: %s.","ieSpellDownload":"בודק ×”×יות ×œ× ×ž×•×ª×§×Ÿ, ×”×× ×œ×”×•×¨×™×“×•?","manyChanges":"בדיקות ×יות הסתיימה: %1 ×ž×™×œ×™× ×©×•× ×•","noChanges":"בדיקות ×יות הסתיימה: ×œ× ×©×•× ×ª×” ××£ מילה","noMispell":"בדיקות ×יות הסתיימה: ×œ× × ×ž×¦×ו שגי×ות כתיב","noSuggestions":"- ×ין הצעות -","notAvailable":"×œ× × ×ž×¦× ×©×™×¨×•×ª זמין.","notInDic":"×œ× × ×ž×¦× ×‘×ž×™×œ×•×Ÿ","oneChange":"בדיקות ×יות הסתיימה: שונתה מילה ×חת","progress":"בודק ×”×יות בתהליך בדיקה....","title":"בדיקת ×יות","toolbar":"בדיקת ×יות"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/hi.js b/js/ckeditor/lang/hi.js
new file mode 100644
index 0000000..521181d
--- /dev/null
+++ b/js/ckeditor/lang/hi.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['hi']={"editor":"रिच टेकà¥à¤¸à¥à¤Ÿ à¤à¤¡à¤¿à¤Ÿà¤°","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"मदद के लिये ALT 0 दबाà¤","browseServer":"सरà¥à¤µà¤° बà¥à¤°à¤¾à¤‰à¥› करें","url":"URL","protocol":"पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤²","upload":"अपलोड","uploadSubmit":"इसे सरà¥à¤µà¤° को भेजें","image":"तसà¥à¤µà¥€à¤°","flash":"फ़à¥à¤²à¥ˆà¤¶","form":"फ़ॉरà¥à¤®","checkbox":"चॅक बॉकà¥à¤¸","radio":"रेडिओ बटन","textField":"टेकà¥à¤¸à¥à¤Ÿ फ़ीलà¥à¤¡","textarea":"टेकà¥à¤¸à¥à¤Ÿ à¤à¤°à¤¿à¤¯à¤¾","hiddenField":"गà¥à¤ªà¥à¤¤ फ़ीलà¥à¤¡","button":"बटन","select":"चà¥à¤¨à¤¾à¤µ फ़ीलà¥à¤¡","imageButton":"तसà¥à¤µà¥€à¤° बटन","notSet":"<सॅट नहीं>","id":"Id","name":"नाम","langDir":"भाषा लिखने की दिशा","langDirLtr":"बायें से दायें (LTR)","langDirRtl":"दायें से बायें (RTL)","langCode":"भाषा कोड","longDescr":"अधिक विवरण के लिठURL","cssClass":"सà¥à¤Ÿà¤¾à¤‡à¤²-शीट कà¥à¤²à¤¾à¤¸","advisoryTitle":"परामरà¥à¤¶ शीरà¥à¤¶à¤•","cssStyle":"सà¥à¤Ÿà¤¾à¤‡à¤²","ok":"ठीक है","cancel":"रदà¥à¤¦ करें","close":"Close","preview":"पà¥à¤°à¥€à¤µà¥à¤¯à¥‚","resize":"Resize","generalTab":"सामानà¥à¤¯","advancedTab":"à¤à¤¡à¥à¤µà¤¾à¤¨à¥à¤¸à¥à¤¡","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"टारà¥à¤—ेट","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"बायें से दायें (LTR)","langDirRTL":"दायें से बायें (RTL)","styles":"सà¥à¤Ÿà¤¾à¤‡à¤²","cssClasses":"सà¥à¤Ÿà¤¾à¤‡à¤²-शीट कà¥à¤²à¤¾à¤¸","width":"चौड़ाई","height":"ऊà¤à¤šà¤¾à¤ˆ","align":"à¤à¤²à¤¾à¤‡à¤¨","alignLeft":"दायें","alignRight":"दायें","alignCenter":"बीच में","alignJustify":"बà¥à¤²à¥‰à¤• जसà¥à¤Ÿà¥€à¥žà¤¾à¤ˆ","alignTop":"ऊपर","alignMiddle":"मधà¥à¤¯","alignBottom":"नीचे","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"बोलà¥à¤¡","italic":"इटैलिक","strike":"सà¥à¤Ÿà¥à¤°à¤¾à¤‡à¤• थà¥à¤°à¥‚","subscript":"अधोलेख","superscript":"अभिलेख","underline":"रेखांकण"},"blockquote":{"toolbar":"बà¥à¤²à¥‰à¤•-कोट"},"clipboard":{"copy":"कॉपी","copyError":"आपके बà¥à¤°à¤¾à¤†à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ सॅटिनà¥à¤—à¥à¤¸ ने कॉपी करने की अनà¥à¤®à¤¤à¤¿ नहीं पà¥à¤°à¤¦à¤¾à¤¨ की है। (Ctrl/Cmd+C) का पà¥à¤°à¤¯à¥‹à¤— करें।","cut":"कट","cutError":"आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ सॅटिनà¥à¤—à¥à¤¸ ने कट करने की अनà¥à¤®à¤¤à¤¿ नहीं पà¥à¤°à¤¦à¤¾à¤¨ की है। (Ctrl/Cmd+X) का पà¥à¤°à¤¯à¥‹à¤— करें।","paste":"पेसà¥à¤Ÿ","pasteArea":"Paste Area","pasteMsg":"Ctrl/Cmd+V का पà¥à¤°à¤¯à¥‹à¤— करके पेसà¥à¤Ÿ करें और ठीक है करें.","securityMsg":"आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°à¤•à¥à¤·à¤¾ आपके बà¥à¤°à¤¾à¤‰à¥›à¤° की सà¥à¤°Kश सैटिंग के कारण, à¤à¤¡à¤¿à¤Ÿà¤° आपके कà¥à¤²à¤¿à¤ªà¤¬à¥‹à¤°à¥à¤¡ डेटा को नहीं पा सकता है. आपको उसे इस विनà¥à¤¡à¥‹ में दोबारा पेसà¥à¤Ÿ करना होगा.","title":"पेसà¥à¤Ÿ"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"à¤à¤¡à¤¿à¤Ÿà¤° टूलबार"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"फ़ॉरà¥à¤®à¥ˆà¤Ÿ","panelTitle":"फ़ॉरà¥à¤®à¥ˆà¤Ÿ","tag_address":"पता","tag_div":"शीरà¥à¤·à¤• (DIV)","tag_h1":"शीरà¥à¤·à¤• 1","tag_h2":"शीरà¥à¤·à¤• 2","tag_h3":"शीरà¥à¤·à¤• 3","tag_h4":"शीरà¥à¤·à¤• 4","tag_h5":"शीरà¥à¤·à¤• 5","tag_h6":"शीरà¥à¤·à¤• 6","tag_p":"साधारण","tag_pre":"फ़ॉरà¥à¤®à¥ˆà¤Ÿà¥…ड"},"horizontalrule":{"toolbar":"हॉरिज़ॉनà¥à¤Ÿà¤² रेखा इनà¥à¤¸à¤°à¥à¤Ÿ करें"},"image":{"alertUrl":"तसà¥à¤µà¥€à¤° का URL टाइप करें ","alt":"वैकलà¥à¤ªà¤¿à¤• टेकà¥à¤¸à¥à¤Ÿ","border":"बॉरà¥à¤¡à¤°","btnUpload":"इसे सरà¥à¤µà¤° को भेजें","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"हॉरिज़ॉनà¥à¤Ÿà¤² सà¥à¤ªà¥‡à¤¸","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"तसà¥à¤µà¥€à¤° की जानकारी","linkTab":"लिंक","lockRatio":"लॉक अनà¥à¤ªà¤¾à¤¤","menu":"तसà¥à¤µà¥€à¤° पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","resetSize":"रीसॅट साइज़","title":"तसà¥à¤µà¥€à¤° पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","titleButton":"तसà¥à¤µà¥€à¤° बटन पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","upload":"अपलोड","urlMissing":"Image source URL is missing.","vSpace":"वरà¥à¤Ÿà¤¿à¤•à¤² सà¥à¤ªà¥‡à¤¸","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"इनà¥à¤¡à¥…नà¥à¤Ÿ बà¥à¤¾à¤¯à¥‡à¤‚","outdent":"इनà¥à¤¡à¥…नà¥à¤Ÿ कम करें"},"fakeobjects":{"anchor":"à¤à¤‚कर इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन","flash":"Flash Animation","hiddenfield":"गà¥à¤ªà¥à¤¤ फ़ीलà¥à¤¡","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"à¤à¤•à¥à¤¸à¥…स की","advanced":"à¤à¤¡à¥à¤µà¤¾à¤¨à¥à¤¸à¥à¤¡","advisoryContentType":"परामरà¥à¤¶ कनà¥à¤Ÿà¥…नà¥à¤Ÿ पà¥à¤°à¤•à¤¾à¤°","advisoryTitle":"परामरà¥à¤¶ शीरà¥à¤¶à¤•","anchor":{"toolbar":"à¤à¤‚कर इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन","menu":"à¤à¤‚कर पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","title":"à¤à¤‚कर पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","name":"à¤à¤‚कर का नाम","errorName":"à¤à¤‚कर का नाम टाइप करें","remove":"Remove Anchor"},"anchorId":"à¤à¤²à¥€à¤®à¥…नà¥à¤Ÿ Id से","anchorName":"à¤à¤‚कर नाम से","charset":"लिंक रिसोरà¥à¤¸ करॅकà¥à¤Ÿà¤° सॅट","cssClasses":"सà¥à¤Ÿà¤¾à¤‡à¤²-शीट कà¥à¤²à¤¾à¤¸","emailAddress":"ई-मेल पता","emailBody":"संदेश","emailSubject":"संदेश विषय","id":"Id","info":"लिंक ","langCode":"भाषा लिखने की दिशा","langDir":"भाषा लिखने की दिशा","langDirLTR":"बायें से दायें (LTR)","langDirRTL":"दायें से बायें (RTL)","menu":"लिंक संपादन","name":"नाम","noAnchors":"(डॉकà¥à¤¯à¥‚मॅनà¥à¤Ÿ में à¤à¤‚करà¥à¤¸ की संखà¥à¤¯à¤¾)","noEmail":"ई-मेल पता टाइप करें","noUrl":"लिंक URL टाइप करें","other":"<अनà¥à¤¯>","popupDependent":"डिपेनà¥à¤¡à¥…नà¥à¤Ÿ (Netscape)","popupFeatures":"पॉप-अप विनà¥à¤¡à¥‹ फ़ीचरà¥à¤¸","popupFullScreen":"फ़à¥à¤² सà¥à¤•à¥à¤°à¥€à¤¨ (IE)","popupLeft":"बायीं तरफ","popupLocationBar":"लोकेशन बार","popupMenuBar":"मॅनà¥à¤¯à¥‚ बार","popupResizable":"आकार बदलने लायक","popupScrollBars":"सà¥à¤•à¥à¤°à¥‰à¤² बार","popupStatusBar":"सà¥à¤Ÿà¥‡à¤Ÿà¤¸ बार","popupToolbar":"टूल बार","popupTop":"दायीं तरफ","rel":"संबंध","selectAnchor":"à¤à¤‚कर चà¥à¤¨à¥‡à¤‚","styles":"सà¥à¤Ÿà¤¾à¤‡à¤²","tabIndex":"टैब इनà¥à¤¡à¥…कà¥à¤¸","target":"टारà¥à¤—ेट","targetFrame":"<फ़à¥à¤°à¥‡à¤®>","targetFrameName":"टारà¥à¤—ेट फ़à¥à¤°à¥‡à¤® का नाम","targetPopup":"<पॉप-अप विनà¥à¤¡à¥‹>","targetPopupName":"पॉप-अप विनà¥à¤¡à¥‹ का नाम","title":"लिंक","toAnchor":"इस पेज का à¤à¤‚कर","toEmail":"ई-मेल","toUrl":"URL","toolbar":"लिंक इनà¥à¤¸à¤°à¥à¤Ÿ/संपादन","type":"लिंक पà¥à¤°à¤•à¤¾à¤°","unlink":"लिंक हटायें","upload":"अपलोड"},"list":{"bulletedlist":"बà¥à¤²à¥…ट सूची","numberedlist":"अंकीय सूची"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"मेकà¥à¤¸à¤¿à¤®à¤¾à¤ˆà¤œà¤¼","minimize":"मिनिमाईज़"},"pastetext":{"button":"पेसà¥à¤Ÿ (सादा टॅकà¥à¤¸à¥à¤Ÿ)","title":"पेसà¥à¤Ÿ (सादा टॅकà¥à¤¸à¥à¤Ÿ)"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"पेसà¥à¤Ÿ (वरà¥à¤¡ से)","toolbar":"पेसà¥à¤Ÿ (वरà¥à¤¡ से)"},"removeformat":{"toolbar":"फ़ॉरà¥à¤®à¥ˆà¤Ÿ हटायें"},"sourcearea":{"toolbar":"सोरà¥à¤¸"},"specialchar":{"options":"विशेष चरितà¥à¤° विकलà¥à¤ª","title":"विशेष करॅकà¥à¤Ÿà¤° चà¥à¤¨à¥‡à¤‚","toolbar":"विशेष करॅकà¥à¤Ÿà¤° इनà¥à¤¸à¤°à¥à¤Ÿ करें"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"सà¥à¤Ÿà¤¾à¤‡à¤²","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"बॉरà¥à¤¡à¤° साइज़","caption":"शीरà¥à¤·à¤•","cell":{"menu":"खाना","insertBefore":"पहले सैल डालें","insertAfter":"बाद में सैल डालें","deleteCell":"सैल डिलीट करें","merge":"सैल मिलायें","mergeRight":"बाà¤à¤¯à¤¾ विलय","mergeDown":"नीचे विलय करें","splitHorizontal":"सैल को कà¥à¤·à¥ˆà¤¤à¤¿à¤œ सà¥à¤¥à¤¿à¤¤à¤¿ में विभाजित करें","splitVertical":"सैल को लमà¥à¤¬à¤¾à¤•à¤¾à¤° में विभाजित करें","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"सैल पैडिंग","cellSpace":"सैल अंतर","column":{"menu":"कालम","insertBefore":"पहले कालम डालें","insertAfter":"बाद में कालम डालें","deleteColumn":"कालम डिलीट करें"},"columns":"कालम","deleteTable":"टेबल डिलीट करें","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"टेबल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","row":{"menu":"पंकà¥à¤¤à¤¿","insertBefore":"पहले पंकà¥à¤¤à¤¿ डालें","insertAfter":"बाद में पंकà¥à¤¤à¤¿ डालें","deleteRow":"पंकà¥à¤¤à¤¿à¤¯à¤¾à¤ डिलीट करें"},"rows":"पंकà¥à¤¤à¤¿à¤¯à¤¾à¤","summary":"सारांश","title":"टेबल पà¥à¤°à¥‰à¤ªà¤°à¥à¤Ÿà¥€à¥›","toolbar":"टेबल","widthPc":"पà¥à¤°à¤¤à¤¿à¤¶à¤¤","widthPx":"पिकà¥à¤¸à¥ˆà¤²","widthUnit":"width unit"},"undo":{"redo":"रीडू","undo":"अनà¥à¤¡à¥‚"},"wsc":{"btnIgnore":"इगà¥à¤¨à¥‹à¤°","btnIgnoreAll":"सभी इगà¥à¤¨à¥‹à¤° करें","btnReplace":"रिपà¥à¤²à¥‡à¤¸","btnReplaceAll":"सभी रिपà¥à¤²à¥‡à¤¸ करें","btnUndo":"अनà¥à¤¡à¥‚","changeTo":"इसमें बदलें","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"सà¥à¤ªà¥…ल-चॅकर इनà¥à¤¸à¥à¤Ÿà¤¾à¤² नहीं किया गया है। कà¥à¤¯à¤¾ आप इसे डाउनलोड करना चाहेंगे?","manyChanges":"वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : %1 शबà¥à¤¦ बदले गये","noChanges":"वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š :कोई शबà¥à¤¦ नहीं बदला गया","noMispell":"वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : कोई गलत वरà¥à¤¤à¤¨à¥€ (सà¥à¤ªà¥…लिंग) नहीं पाई गई","noSuggestions":"- कोई सà¥à¤à¤¾à¤µ नहीं -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"शबà¥à¤¦à¤•à¥‹à¤¶ में नहीं","oneChange":"वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š : à¤à¤• शबà¥à¤¦ बदला गया","progress":"वरà¥à¤¤à¤¨à¥€ की जाà¤à¤š (सà¥à¤ªà¥…ल-चॅक) जारी है...","title":"Spell Checker","toolbar":"वरà¥à¤¤à¤¨à¥€ (सà¥à¤ªà¥‡à¤²à¤¿à¤‚ग) जाà¤à¤š"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/hr.js b/js/ckeditor/lang/hr.js
new file mode 100644
index 0000000..b785cae
--- /dev/null
+++ b/js/ckeditor/lang/hr.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['hr']={"editor":"Bogati ureÄ‘ivaÄ teksta, %1","editorPanel":"PloÄa Bogatog UreÄ‘ivaÄa Teksta","common":{"editorHelp":"Pritisni ALT 0 za pomoć","browseServer":"Pretraži server","url":"URL","protocol":"Protokol","upload":"PoÅ¡alji","uploadSubmit":"PoÅ¡alji na server","image":"Slika","flash":"Flash","form":"Forma","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<nije postavljeno>","id":"Id","name":"Naziv","langDir":"Smjer jezika","langDirLtr":"S lijeva na desno (LTR)","langDirRtl":"S desna na lijevo (RTL)","langCode":"Kôd jezika","longDescr":"DugaÄki opis URL","cssClass":"Klase stilova","advisoryTitle":"Advisory naslov","cssStyle":"Stil","ok":"OK","cancel":"PoniÅ¡ti","close":"Zatvori","preview":"Pregledaj","resize":"Povuci za promjenu veliÄine","generalTab":"Općenito","advancedTab":"Napredno","validateNumberFailed":"Ova vrijednost nije broj.","confirmNewPage":"Sve napravljene promjene će biti izgubljene ukoliko ih niste snimili. Sigurno želite uÄitati novu stranicu?","confirmCancel":"Neke od opcija su promjenjene. Sigurno želite zatvoriti ovaj prozor?","options":"Opcije","target":"OdrediÅ¡te","targetNew":"Novi prozor (_blank)","targetTop":"VrÅ¡ni prozor (_top)","targetSelf":"Isti prozor (_self)","targetParent":"Roditeljski prozor (_parent)","langDirLTR":"S lijeva na desno (LTR)","langDirRTL":"S desna na lijevo (RTL)","styles":"Stil","cssClasses":"Klase stilova","width":"Å irina","height":"Visina","align":"Poravnanje","alignLeft":"Lijevo","alignRight":"Desno","alignCenter":"SrediÅ¡nje","alignJustify":"Blok poravnanje","alignTop":"Vrh","alignMiddle":"Sredina","alignBottom":"Dolje","alignNone":"None","invalidValue":"Neispravna vrijednost.","invalidHeight":"Visina mora biti broj.","invalidWidth":"Å irina mora biti broj.","invalidCssLength":"Vrijednost odreÄ‘ena za \"%1\" polje mora biti pozitivni broj sa ili bez važećih CSS mjernih jedinica (px, %, in, cm, mm, em, ex, pt ili pc).","invalidHtmlLength":"Vrijednost odreÄ‘ena za \"%1\" polje mora biti pozitivni broj sa ili bez važećih HTML mjernih jedinica (px ili %).","invalidInlineStyle":"Vrijednost za linijski stil mora sadržavati jednu ili viÅ¡e definicija s formatom \"naziv:vrijednost\", odvojenih toÄka-zarezom.","cssLengthTooltip":"Unesite broj za vrijednost u pikselima ili broj s važećim CSS mjernim jedinicama (px, %, in, cm, mm, em, ex, pt ili pc).","unavailable":"%1<span class=\"cke_accessibility\">, nedostupno</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"O CKEditoru","help":"Provjeri $1 za pomoć.","moreInfo":"Za informacije o licencama posjetite naÅ¡u web stranicu:","title":"O CKEditoru","userGuide":"VodiÄ za CKEditor korisnike"},"basicstyles":{"bold":"Podebljaj","italic":"Ukosi","strike":"Precrtano","subscript":"Subscript","superscript":"Superscript","underline":"Potcrtano"},"blockquote":{"toolbar":"Blockquote"},"clipboard":{"copy":"Kopiraj","copyError":"Sigurnosne postavke VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tipkovnici (Ctrl/Cmd+C).","cut":"Izreži","cutError":"Sigurnosne postavke VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog izrezivanja. Molimo koristite kraticu na tipkovnici (Ctrl/Cmd+X).","paste":"Zalijepi","pasteArea":"Prostor za ljepljenje","pasteMsg":"Molimo zaljepite unutar doljnjeg okvira koristeći tipkovnicu (<STRONG>Ctrl/Cmd+V</STRONG>) i kliknite <STRONG>OK</STRONG>.","securityMsg":"Zbog sigurnosnih postavki VaÅ¡eg pretraživaÄa, editor nema direktan pristup VaÅ¡em meÄ‘uspremniku. Potrebno je ponovno zalijepiti tekst u ovaj prozor.","title":"Zalijepi"},"contextmenu":{"options":"Opcije izbornika"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Smanji alatnu traku","toolbarExpand":"ProÅ¡iri alatnu traku","toolbarGroups":{"document":"Dokument","clipboard":"MeÄ‘uspremnik/PoniÅ¡ti","editing":"UreÄ‘ivanje","forms":"Forme","basicstyles":"Osnovni stilovi","paragraph":"Paragraf","links":"Veze","insert":"Umetni","styles":"Stilovi","colors":"Boje","tools":"Alatke"},"toolbars":"Alatne trake ureÄ‘ivaÄa teksta"},"elementspath":{"eleLabel":"Putanja elemenata","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatirano"},"horizontalrule":{"toolbar":"Ubaci vodoravnu liniju"},"image":{"alertUrl":"Unesite URL slike","alt":"Alternativni tekst","border":"Okvir","btnUpload":"PoÅ¡alji na server","button2Img":"Želite li promijeniti odabrani gumb u jednostavnu sliku?","hSpace":"HSpace","img2Button":"Želite li promijeniti odabranu sliku u gumb?","infoTab":"Info slike","linkTab":"Link","lockRatio":"ZakljuÄaj odnos","menu":"Svojstva slika","resetSize":"ObriÅ¡i veliÄinu","title":"Svojstva slika","titleButton":"Image Button svojstva","upload":"PoÅ¡alji","urlMissing":"Nedostaje URL slike.","vSpace":"VSpace","validateBorder":"Okvir mora biti cijeli broj.","validateHSpace":"HSpace mora biti cijeli broj","validateVSpace":"VSpace mora biti cijeli broj."},"indent":{"indent":"Pomakni udesno","outdent":"Pomakni ulijevo"},"fakeobjects":{"anchor":"Sidro","flash":"Flash animacija","hiddenfield":"Sakriveno polje","iframe":"IFrame","unknown":"Nepoznati objekt"},"link":{"acccessKey":"Pristupna tipka","advanced":"Napredno","advisoryContentType":"Advisory vrsta sadržaja","advisoryTitle":"Advisory naslov","anchor":{"toolbar":"Ubaci/promijeni sidro","menu":"Svojstva sidra","title":"Svojstva sidra","name":"Ime sidra","errorName":"Molimo unesite ime sidra","remove":"Ukloni sidro"},"anchorId":"Po Id elementa","anchorName":"Po nazivu sidra","charset":"Kodna stranica povezanih resursa","cssClasses":"Stylesheet klase","emailAddress":"E-Mail adresa","emailBody":"Sadržaj poruke","emailSubject":"Naslov","id":"Id","info":"Link Info","langCode":"Smjer jezika","langDir":"Smjer jezika","langDirLTR":"S lijeva na desno (LTR)","langDirRTL":"S desna na lijevo (RTL)","menu":"Promijeni link","name":"Naziv","noAnchors":"(Nema dostupnih sidra)","noEmail":"Molimo upiÅ¡ite e-mail adresu","noUrl":"Molimo upiÅ¡ite URL link","other":"<drugi>","popupDependent":"Ovisno (Netscape)","popupFeatures":"Mogućnosti popup prozora","popupFullScreen":"Cijeli ekran (IE)","popupLeft":"Lijeva pozicija","popupLocationBar":"Traka za lokaciju","popupMenuBar":"Izborna traka","popupResizable":"Promjenjiva veliÄina","popupScrollBars":"Scroll traka","popupStatusBar":"Statusna traka","popupToolbar":"Traka s alatima","popupTop":"Gornja pozicija","rel":"Veza","selectAnchor":"Odaberi sidro","styles":"Stil","tabIndex":"Tab Indeks","target":"Meta","targetFrame":"<okvir>","targetFrameName":"Ime ciljnog okvira","targetPopup":"<popup prozor>","targetPopupName":"Naziv popup prozora","title":"Link","toAnchor":"Sidro na ovoj stranici","toEmail":"E-Mail","toUrl":"URL","toolbar":"Ubaci/promijeni link","type":"Link vrsta","unlink":"Ukloni link","upload":"PoÅ¡alji"},"list":{"bulletedlist":"ObiÄna lista","numberedlist":"BrojÄana lista"},"magicline":{"title":"Ubaci paragraf ovdje"},"maximize":{"maximize":"Povećaj","minimize":"Smanji"},"pastetext":{"button":"Zalijepi kao Äisti tekst","title":"Zalijepi kao Äisti tekst"},"pastefromword":{"confirmCleanup":"Tekst koji želite zalijepiti Äini se da je kopiran iz Worda. Želite li prije oÄistiti tekst?","error":"Nije moguće oÄistiti podatke za ljepljenje zbog interne greÅ¡ke","title":"Zalijepi iz Worda","toolbar":"Zalijepi iz Worda"},"removeformat":{"toolbar":"Ukloni formatiranje"},"sourcearea":{"toolbar":"Kôd"},"specialchar":{"options":"Opcije specijalnih znakova","title":"Odaberite posebni karakter","toolbar":"Ubaci posebne znakove"},"scayt":{"btn_about":"O SCAYT","btn_dictionaries":"RjeÄnici","btn_disable":"Onemogući SCAYT","btn_enable":"Omogući SCAYT","btn_langs":"Jezici","btn_options":"Opcije","text_title":"Provjeri pravopis tijekom tipkanja (SCAYT)"},"stylescombo":{"label":"Stil","panelTitle":"Stilovi formatiranja","panelTitle1":"Block stilovi","panelTitle2":"Inline stilovi","panelTitle3":"Object stilovi"},"table":{"border":"VeliÄina okvira","caption":"Naslov","cell":{"menu":"Ćelija","insertBefore":"Ubaci ćeliju prije","insertAfter":"Ubaci ćeliju poslije","deleteCell":"IzbriÅ¡i ćelije","merge":"Spoji ćelije","mergeRight":"Spoji desno","mergeDown":"Spoji dolje","splitHorizontal":"Podijeli ćeliju vodoravno","splitVertical":"Podijeli ćeliju okomito","title":"Svojstva ćelije","cellType":"Vrsta ćelije","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Prelazak u novi red","hAlign":"Vodoravno poravnanje","vAlign":"Okomito poravnanje","alignBaseline":"Osnovna linija","bgColor":"Boja pozadine","borderColor":"Boja ruba","data":"Podatak","header":"Zaglavlje","yes":"Da","no":"ne","invalidWidth":"Å irina ćelije mora biti broj.","invalidHeight":"Visina ćelije mora biti broj.","invalidRowSpan":"Rows span mora biti cijeli broj.","invalidColSpan":"Columns span mora biti cijeli broj.","chooseColor":"Odaberi"},"cellPad":"Razmak ćelija","cellSpace":"Prostornost ćelija","column":{"menu":"Kolona","insertBefore":"Ubaci kolonu prije","insertAfter":"Ubaci kolonu poslije","deleteColumn":"IzbriÅ¡i kolone"},"columns":"Kolona","deleteTable":"IzbriÅ¡i tablicu","headers":"Zaglavlje","headersBoth":"Oba","headersColumn":"Prva kolona","headersNone":"NiÅ¡ta","headersRow":"Prvi red","invalidBorder":"Debljina ruba mora biti broj.","invalidCellPadding":"Razmak ćelija mora biti broj.","invalidCellSpacing":"Prostornost ćelija mora biti broj.","invalidCols":"Broj kolona mora biti broj veći od 0.","invalidHeight":"Visina tablice mora biti broj.","invalidRows":"Broj redova mora biti broj veći od 0.","invalidWidth":"Å irina tablice mora biti broj.","menu":"Svojstva tablice","row":{"menu":"Red","insertBefore":"Ubaci red prije","insertAfter":"Ubaci red poslije","deleteRow":"IzbriÅ¡i redove"},"rows":"Redova","summary":"Sažetak","title":"Svojstva tablice","toolbar":"Tablica","widthPc":"postotaka","widthPx":"piksela","widthUnit":"jedinica Å¡irine"},"undo":{"redo":"Ponovi","undo":"PoniÅ¡ti"},"wsc":{"btnIgnore":"Zanemari","btnIgnoreAll":"Zanemari sve","btnReplace":"Zamijeni","btnReplaceAll":"Zamijeni sve","btnUndo":"Vrati","changeTo":"Promijeni u","errorLoading":"GreÅ¡ka uÄitavanja aplikacije: %s.","ieSpellDownload":"Provjera pravopisa nije instalirana. Želite li skinuti provjeru pravopisa?","manyChanges":"Provjera zavrÅ¡ena: Promijenjeno %1 rijeÄi","noChanges":"Provjera zavrÅ¡ena: Nije napravljena promjena","noMispell":"Provjera zavrÅ¡ena: Nema greÅ¡aka","noSuggestions":"-Nema preporuke-","notAvailable":"Žao nam je, ali usluga trenutno nije dostupna.","notInDic":"Nije u rjeÄniku","oneChange":"Provjera zavrÅ¡ena: Jedna rijeÄ promjenjena","progress":"Provjera u tijeku...","title":"Provjera pravopisa","toolbar":"Provjeri pravopis"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/hu.js b/js/ckeditor/lang/hu.js
new file mode 100644
index 0000000..4650677
--- /dev/null
+++ b/js/ckeditor/lang/hu.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['hu']={"editor":"HTML szerkesztÅ‘","editorPanel":"Rich Text szerkesztÅ‘ panel","common":{"editorHelp":"Segítségért nyomjon ALT 0","browseServer":"Böngészés a szerveren","url":"Hivatkozás","protocol":"Protokoll","upload":"Feltöltés","uploadSubmit":"Küldés a szerverre","image":"Kép","flash":"Flash","form":"Å°rlap","checkbox":"JelölÅ‘négyzet","radio":"Választógomb","textField":"SzövegmezÅ‘","textarea":"Szövegterület","hiddenField":"RejtettmezÅ‘","button":"Gomb","select":"LegördülÅ‘ lista","imageButton":"Képgomb","notSet":"<nincs beállítva>","id":"Azonosító","name":"Név","langDir":"Ãrás iránya","langDirLtr":"Balról jobbra","langDirRtl":"Jobbról balra","langCode":"Nyelv kódja","longDescr":"Részletes leírás webcíme","cssClass":"Stíluskészlet","advisoryTitle":"Súgócimke","cssStyle":"Stílus","ok":"Rendben","cancel":"Mégsem","close":"Bezárás","preview":"ElÅ‘nézet","resize":"Húzza az átméretezéshez","generalTab":"Ãltalános","advancedTab":"További opciók","validateNumberFailed":"A mezÅ‘be csak számokat írhat.","confirmNewPage":"Minden nem mentett változás el fog veszni! Biztosan be szeretné tölteni az oldalt?","confirmCancel":"Az űrlap tartalma megváltozott, ám a változásokat nem rögzítette. Biztosan be szeretné zárni az űrlapot?","options":"Beállítások","target":"Cél","targetNew":"Új ablak (_blank)","targetTop":"LegfelsÅ‘ ablak (_top)","targetSelf":"Aktuális ablakban (_self)","targetParent":"SzülÅ‘ ablak (_parent)","langDirLTR":"Balról jobbra (LTR)","langDirRTL":"Jobbról balra (RTL)","styles":"Stílus","cssClasses":"Stíluslap osztály","width":"Szélesség","height":"Magasság","align":"Igazítás","alignLeft":"Bal","alignRight":"Jobbra","alignCenter":"Középre","alignJustify":"Sorkizárt","alignTop":"Tetejére","alignMiddle":"Középre","alignBottom":"Aljára","alignNone":"None","invalidValue":"Érvénytelen érték.","invalidHeight":"A magasság mezÅ‘be csak számokat írhat.","invalidWidth":"A szélesség mezÅ‘be csak számokat írhat.","invalidCssLength":"\"%1\"-hez megadott érték csakis egy pozitív szám lehet, esetleg egy érvényes CSS egységgel megjelölve(px, %, in, cm, mm, em, ex, pt vagy pc).","invalidHtmlLength":"\"%1\"-hez megadott érték csakis egy pozitív szám lehet, esetleg egy érvényes HTML egységgel megjelölve(px vagy %).","invalidInlineStyle":"Az inline stílusnak megadott értéknek tartalmaznia kell egy vagy több rekordot a \"name : value\" formátumban, pontosvesszÅ‘vel elválasztva.","cssLengthTooltip":"Adjon meg egy számot értéknek pixelekben vagy egy számot érvényes CSS mértékegységben (px, %, in, cm, mm, em, ex, pt, vagy pc).","unavailable":"%1<span class=\"cke_accessibility\">, nem elérhetÅ‘</span>"},"about":{"copy":"Copyright &copy; $1. Minden jog fenntartva.","dlgTitle":"CKEditor névjegy","help":"Itt találsz segítséget: $1","moreInfo":"Licenszelési információkért kérjük látogassa meg weboldalunkat:","title":"CKEditor névjegy","userGuide":"CKEditor Felhasználói útmutató"},"basicstyles":{"bold":"Félkövér","italic":"DÅ‘lt","strike":"Ãthúzott","subscript":"Alsó index","superscript":"FelsÅ‘ index","underline":"Aláhúzott"},"blockquote":{"toolbar":"Idézet blokk"},"clipboard":{"copy":"Másolás","copyError":"A böngészÅ‘ biztonsági beállításai nem engedélyezik a szerkesztÅ‘nek, hogy végrehajtsa a másolás műveletet. Használja az alábbi billentyűkombinációt (Ctrl/Cmd+X).","cut":"Kivágás","cutError":"A böngészÅ‘ biztonsági beállításai nem engedélyezik a szerkesztÅ‘nek, hogy végrehajtsa a kivágás műveletet. Használja az alábbi billentyűkombinációt (Ctrl/Cmd+X).","paste":"Beillesztés","pasteArea":"Beszúrás mezÅ‘","pasteMsg":"Másolja be az alábbi mezÅ‘be a <STRONG>Ctrl/Cmd+V</STRONG> billentyűk lenyomásával, majd nyomjon <STRONG>Rendben</STRONG>-t.","securityMsg":"A böngészÅ‘ biztonsági beállításai miatt a szerkesztÅ‘ nem képes hozzáférni a vágólap adataihoz. Illeszd be újra ebben az ablakban.","title":"Beillesztés"},"contextmenu":{"options":"Helyi menü opciók"},"button":{"selectedLabel":"%1 (Kiválasztva)"},"toolbar":{"toolbarCollapse":"Eszköztár összecsukása","toolbarExpand":"Eszköztár szétnyitása","toolbarGroups":{"document":"Dokumentum","clipboard":"Vágólap/Visszavonás","editing":"Szerkesztés","forms":"Å°rlapok","basicstyles":"Alapstílusok","paragraph":"Bekezdés","links":"Hivatkozások","insert":"Beszúrás","styles":"Stílusok","colors":"Színek","tools":"Eszközök"},"toolbars":"SzerkesztÅ‘ Eszköztár"},"elementspath":{"eleLabel":"Elem utak","eleTitle":"%1 elem"},"format":{"label":"Formátum","panelTitle":"Formátum","tag_address":"Címsor","tag_div":"Bekezdés (DIV)","tag_h1":"Fejléc 1","tag_h2":"Fejléc 2","tag_h3":"Fejléc 3","tag_h4":"Fejléc 4","tag_h5":"Fejléc 5","tag_h6":"Fejléc 6","tag_p":"Normál","tag_pre":"Formázott"},"horizontalrule":{"toolbar":"Elválasztóvonal beillesztése"},"image":{"alertUrl":"Töltse ki a kép webcímét","alt":"Buborék szöveg","border":"Keret","btnUpload":"Küldés a szerverre","button2Img":"A kiválasztott képgombból sima képet szeretne csinálni?","hSpace":"Vízsz. táv","img2Button":"A kiválasztott képbÅ‘l képgombot szeretne csinálni?","infoTab":"Alaptulajdonságok","linkTab":"Hivatkozás","lockRatio":"Arány megtartása","menu":"Kép tulajdonságai","resetSize":"Eredeti méret","title":"Kép tulajdonságai","titleButton":"Képgomb tulajdonságai","upload":"Feltöltés","urlMissing":"Hiányzik a kép URL-je","vSpace":"Függ. táv","validateBorder":"A keret méretének egész számot kell beírni!","validateHSpace":"Vízszintes távolságnak egész számot kell beírni!","validateVSpace":"FüggÅ‘leges távolságnak egész számot kell beírni!"},"indent":{"indent":"Behúzás növelése","outdent":"Behúzás csökkentése"},"fakeobjects":{"anchor":"Horgony","flash":"Flash animáció","hiddenfield":"Rejtett mezõ","iframe":"IFrame","unknown":"Ismeretlen objektum"},"link":{"acccessKey":"Billentyűkombináció","advanced":"További opciók","advisoryContentType":"Súgó tartalomtípusa","advisoryTitle":"Súgócimke","anchor":{"toolbar":"Horgony beillesztése/szerkesztése","menu":"Horgony tulajdonságai","title":"Horgony tulajdonságai","name":"Horgony neve","errorName":"Kérem adja meg a horgony nevét","remove":"Horgony eltávolítása"},"anchorId":"Azonosító szerint","anchorName":"Horgony név szerint","charset":"Hivatkozott tartalom kódlapja","cssClasses":"Stíluskészlet","emailAddress":"E-Mail cím","emailBody":"Ãœzenet","emailSubject":"Ãœzenet tárgya","id":"Id","info":"Alaptulajdonságok","langCode":"Ãrás iránya","langDir":"Ãrás iránya","langDirLTR":"Balról jobbra","langDirRTL":"Jobbról balra","menu":"Hivatkozás módosítása","name":"Név","noAnchors":"(Nincs horgony a dokumentumban)","noEmail":"Adja meg az E-Mail címet","noUrl":"Adja meg a hivatkozás webcímét","other":"<más>","popupDependent":"SzülÅ‘höz kapcsolt (csak Netscape)","popupFeatures":"Felugró ablak jellemzÅ‘i","popupFullScreen":"Teljes képernyÅ‘ (csak IE)","popupLeft":"Bal pozíció","popupLocationBar":"Címsor","popupMenuBar":"Menü sor","popupResizable":"Ãtméretezés","popupScrollBars":"GördítÅ‘sáv","popupStatusBar":"Ãllapotsor","popupToolbar":"Eszköztár","popupTop":"FelsÅ‘ pozíció","rel":"Kapcsolat típusa","selectAnchor":"Horgony választása","styles":"Stílus","tabIndex":"Tabulátor index","target":"Tartalom megjelenítése","targetFrame":"<keretben>","targetFrameName":"Keret neve","targetPopup":"<felugró ablakban>","targetPopupName":"Felugró ablak neve","title":"Hivatkozás tulajdonságai","toAnchor":"Horgony az oldalon","toEmail":"E-Mail","toUrl":"URL","toolbar":"Hivatkozás beillesztése/módosítása","type":"Hivatkozás típusa","unlink":"Hivatkozás törlése","upload":"Feltöltés"},"list":{"bulletedlist":"Felsorolás","numberedlist":"Számozás"},"magicline":{"title":"Szúrja be a bekezdést ide"},"maximize":{"maximize":"Teljes méret","minimize":"Kis méret"},"pastetext":{"button":"Beillesztés formázatlan szövegként","title":"Beillesztés formázatlan szövegként"},"pastefromword":{"confirmCleanup":"Úgy tűnik a beillesztett szöveget Word-bÅ‘l másolt át. Meg szeretné tisztítani a szöveget? (ajánlott)","error":"Egy belsÅ‘ hiba miatt nem sikerült megtisztítani a szöveget","title":"Beillesztés Word-bÅ‘l","toolbar":"Beillesztés Word-bÅ‘l"},"removeformat":{"toolbar":"Formázás eltávolítása"},"sourcearea":{"toolbar":"Forráskód"},"specialchar":{"options":"Speciális karakter opciók","title":"Speciális karakter választása","toolbar":"Speciális karakter beillesztése"},"scayt":{"btn_about":"SCAYT névjegy","btn_dictionaries":"Szótár","btn_disable":"SCAYT letiltása","btn_enable":"SCAYT engedélyezése","btn_langs":"Nyelvek","btn_options":"Beállítások","text_title":"Helyesírás ellenÅ‘rzés gépelés közben"},"stylescombo":{"label":"Stílus","panelTitle":"Formázási stílusok","panelTitle1":"Blokk stílusok","panelTitle2":"Inline stílusok","panelTitle3":"Objektum stílusok"},"table":{"border":"Szegélyméret","caption":"Felirat","cell":{"menu":"Cella","insertBefore":"Beszúrás balra","insertAfter":"Beszúrás jobbra","deleteCell":"Cellák törlése","merge":"Cellák egyesítése","mergeRight":"Cellák egyesítése jobbra","mergeDown":"Cellák egyesítése lefelé","splitHorizontal":"Cellák szétválasztása vízszintesen","splitVertical":"Cellák szétválasztása függÅ‘legesen","title":"Cella tulajdonságai","cellType":"Cella típusa","rowSpan":"FüggÅ‘leges egyesítés","colSpan":"Vízszintes egyesítés","wordWrap":"Hosszú sorok törése","hAlign":"Vízszintes igazítás","vAlign":"FüggÅ‘leges igazítás","alignBaseline":"Alapvonalra","bgColor":"Háttér színe","borderColor":"Keret színe","data":"Adat","header":"Fejléc","yes":"Igen","no":"Nem","invalidWidth":"A szélesség mezÅ‘be csak számokat írhat.","invalidHeight":"A magasság mezÅ‘be csak számokat írhat.","invalidRowSpan":"A függÅ‘leges egyesítés mezÅ‘be csak számokat írhat.","invalidColSpan":"A vízszintes egyesítés mezÅ‘be csak számokat írhat.","chooseColor":"Válasszon"},"cellPad":"Cella belsÅ‘ margó","cellSpace":"Cella térköz","column":{"menu":"Oszlop","insertBefore":"Beszúrás balra","insertAfter":"Beszúrás jobbra","deleteColumn":"Oszlopok törlése"},"columns":"Oszlopok","deleteTable":"Táblázat törlése","headers":"Fejlécek","headersBoth":"MindkettÅ‘","headersColumn":"ElsÅ‘ oszlop","headersNone":"Nincsenek","headersRow":"ElsÅ‘ sor","invalidBorder":"A szegélyméret mezÅ‘be csak számokat írhat.","invalidCellPadding":"A cella belsÅ‘ margó mezÅ‘be csak számokat írhat.","invalidCellSpacing":"A cella térköz mezÅ‘be csak számokat írhat.","invalidCols":"Az oszlopok számának nagyobbnak kell lenni mint 0.","invalidHeight":"A magasság mezÅ‘be csak számokat írhat.","invalidRows":"A sorok számának nagyobbnak kell lenni mint 0.","invalidWidth":"A szélesség mezÅ‘be csak számokat írhat.","menu":"Táblázat tulajdonságai","row":{"menu":"Sor","insertBefore":"Beszúrás fölé","insertAfter":"Beszúrás alá","deleteRow":"Sorok törlése"},"rows":"Sorok","summary":"Leírás","title":"Táblázat tulajdonságai","toolbar":"Táblázat","widthPc":"százalék","widthPx":"képpont","widthUnit":"Szélesség egység"},"undo":{"redo":"Ismétlés","undo":"Visszavonás"},"wsc":{"btnIgnore":"Kihagyja","btnIgnoreAll":"Mindet kihagyja","btnReplace":"Csere","btnReplaceAll":"Összes cseréje","btnUndo":"Visszavonás","changeTo":"Módosítás","errorLoading":"Hiba a szolgáltatás host betöltése közben: %s.","ieSpellDownload":"A helyesírás-ellenÅ‘rzÅ‘ nincs telepítve. Szeretné letölteni most?","manyChanges":"Helyesírás-ellenÅ‘rzés kész: %1 szó cserélve","noChanges":"Helyesírás-ellenÅ‘rzés kész: Nincs változtatott szó","noMispell":"Helyesírás-ellenÅ‘rzés kész: Nem találtam hibát","noSuggestions":"Nincs javaslat","notAvailable":"Sajnálom, de a szolgáltatás jelenleg nem elérhetÅ‘.","notInDic":"Nincs a szótárban","oneChange":"Helyesírás-ellenÅ‘rzés kész: Egy szó cserélve","progress":"Helyesírás-ellenÅ‘rzés folyamatban...","title":"Helyesírás ellenörzÅ‘","toolbar":"Helyesírás-ellenÅ‘rzés"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/id.js b/js/ckeditor/lang/id.js
new file mode 100644
index 0000000..3ec9e0d
--- /dev/null
+++ b/js/ckeditor/lang/id.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['id']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Tekan ALT 0 untuk bantuan.","browseServer":"Jelajah Server","url":"URL","protocol":"Protokol","upload":"Unggah","uploadSubmit":"Kirim ke Server","image":"Gambar","flash":"Flash","form":"Formulir","checkbox":"Kotak Cek","radio":"Tombol Radio","textField":"Kolom Teks","textarea":"Area Teks","hiddenField":"Kolom Tersembunyi","button":"Tombol","select":"Kolom Seleksi","imageButton":"Tombol Gambar","notSet":"<tidak diatur>","id":"Id","name":"Nama","langDir":"Arah Bahasa","langDirLtr":"Kiri ke Kanan (LTR)","langDirRtl":"Kanan ke Kiri","langCode":"Kode Bahasa","longDescr":"Deskripsi URL Panjang","cssClass":"Kelas Stylesheet","advisoryTitle":"Penasehat Judul","cssStyle":"Gaya","ok":"OK","cancel":"Batal","close":"Tutup","preview":"Pratinjau","resize":"Ubah ukuran","generalTab":"Umum","advancedTab":"Advanced","validateNumberFailed":"Nilai ini tidak sebuah angka","confirmNewPage":"Semua perubahan yang tidak disimpan di konten ini akan hilang. Apakah anda yakin ingin memuat halaman baru?","confirmCancel":"Beberapa opsi telah berubah. Apakah anda yakin ingin menutup dialog?","options":"Opsi","target":"Sasaran","targetNew":"Jendela Baru (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Jendela yang Sama (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Kiri ke Kanan (LTR)","langDirRTL":"Kanan ke Kiri (RTL)","styles":"Gaya","cssClasses":"Kelas Stylesheet","width":"Lebar","height":"Tinggi","align":"Penjajaran","alignLeft":"Kiri","alignRight":"Kanan","alignCenter":"Tengah","alignJustify":"Rata kiri-kanan","alignTop":"Atas","alignMiddle":"Tengah","alignBottom":"Bawah","alignNone":"None","invalidValue":"Nilai tidak sah.","invalidHeight":"Tinggi harus sebuah angka.","invalidWidth":"Lebar harus sebuah angka.","invalidCssLength":"Nilai untuk \"%1\" harus sebuah angkat positif dengan atau tanpa pengukuran unit CSS yang sah (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Nilai yang dispesifikasian untuk kolom \"%1\" harus sebuah angka positif dengan atau tanpa sebuah unit pengukuran HTML (px atau %) yang valid.","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Masukkan sebuah angka untuk sebuah nilai dalam pixel atau sebuah angka dengan unit CSS yang sah (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, tidak tersedia</span>"},"about":{"copy":"Hak cipta &copy; $1. All rights reserved.","dlgTitle":"Tentang CKEditor","help":"Cel $1 untuk bantuan.","moreInfo":"Untuk informasi lisensi silahkan kunjungi web site kami:","title":"Tentang CKEditor","userGuide":"Petunjuk Pengguna CKEditor"},"basicstyles":{"bold":"Huruf Tebal","italic":"Huruf Miring","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"Garis Bawah"},"blockquote":{"toolbar":"Kutipan Blok"},"clipboard":{"copy":"Salin","copyError":"Pengaturan keamanan peramban anda tidak mengizinkan editor untuk mengeksekusi operasi menyalin secara otomatis. Mohon gunakan papan tuts (Ctrl/Cmd+C)","cut":"Potong","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Tempel","pasteArea":"Area Tempel","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Tempel"},"contextmenu":{"options":"Opsi Konteks Pilihan"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Ciutkan Toolbar","toolbarExpand":"Bentangkan Toolbar","toolbarGroups":{"document":"Dokumen","clipboard":"Papan klip / Kembalikan perlakuan","editing":"Sunting","forms":"Formulir","basicstyles":"Gaya Dasar","paragraph":"Paragraf","links":"Tautan","insert":"Sisip","styles":"Gaya","colors":"Warna","tools":"Alat"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Bentuk","panelTitle":"Bentuk Paragraf","tag_address":"Alamat","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Membentuk"},"horizontalrule":{"toolbar":"Sisip Garis Horisontal"},"image":{"alertUrl":"Mohon tulis URL gambar","alt":"Teks alternatif","border":"Batas","btnUpload":"Kirim ke Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Info Gambar","linkTab":"Tautan","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Unggah","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Tingkatkan Lekuk","outdent":"Kurangi Lekuk"},"fakeobjects":{"anchor":"Anchor","flash":"Animasi Flash","hiddenfield":"Kolom Tersembunyi","iframe":"IFrame","unknown":"Obyek Tak Dikenal"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Penasehat Judul","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Kelas Stylesheet","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Kode Bahasa","langDir":"Arah Bahasa","langDirLTR":"Kiri ke Kanan (LTR)","langDirRTL":"Kanan ke Kiri (RTL)","menu":"Edit Link","name":"Nama","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Gaya","tabIndex":"Tab Index","target":"Sasaran","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Tautan","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Tautan","type":"Link Type","unlink":"Unlink","upload":"Unggah"},"list":{"bulletedlist":"Sisip/Hapus Daftar Bullet","numberedlist":"Sisip/Hapus Daftar Bernomor"},"magicline":{"title":"Masukkan paragraf disini"},"maximize":{"maximize":"Memperbesar","minimize":"Memperkecil"},"pastetext":{"button":"Tempel sebagai teks polos","title":"Tempel sebagai Teks Polos"},"pastefromword":{"confirmCleanup":"Teks yang ingin anda tempel sepertinya di salin dari Word. Apakah anda mau membersihkannya sebelum menempel?","error":"Tidak mungkin membersihkan data yang ditempel dikerenakan kesalahan internal","title":"Tempel dari Word","toolbar":"Tempel dari Word"},"removeformat":{"toolbar":"Hapus Format"},"sourcearea":{"toolbar":"Sumber"},"specialchar":{"options":"Opsi spesial karakter","title":"Pilih spesial karakter","toolbar":"Sisipkan spesial karakter"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Gaya","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Ukuran batas","caption":"Judul halaman","cell":{"menu":"Sel","insertBefore":"Sisip Sel Sebelum","insertAfter":"Sisip Sel Setelah","deleteCell":"Hapus Sel","merge":"Gabungkan Sel","mergeRight":"Gabungkan ke Kanan","mergeDown":"Gabungkan ke Bawah","splitHorizontal":"Pisahkan Sel Secara Horisontal","splitVertical":"Pisahkan Sel Secara Vertikal","title":"Properti Sel","cellType":"Tipe Sel","rowSpan":"Rentang antar baris","colSpan":"Rentang antar kolom","wordWrap":"Word Wrap","hAlign":"Jajaran Horisontal","vAlign":"Jajaran Vertikal","alignBaseline":"Dasar","bgColor":"Warna Latar Belakang","borderColor":"Warna Batasan","data":"Data","header":"Header","yes":"Ya","no":"Tidak","invalidWidth":"Lebar sel harus sebuah angka.","invalidHeight":"Tinggi sel harus sebuah angka","invalidRowSpan":"Rentang antar baris harus angka seluruhnya.","invalidColSpan":"Rentang antar kolom harus angka seluruhnya","chooseColor":"Pilih"},"cellPad":"Sel spasi dalam","cellSpace":"Spasi antar sel","column":{"menu":"Kolom","insertBefore":"Sisip Kolom Sebelum","insertAfter":"Sisip Kolom Sesudah","deleteColumn":"Hapus Kolom"},"columns":"Kolom","deleteTable":"Hapus Tabel","headers":"Headers","headersBoth":"Keduanya","headersColumn":"Kolom pertama","headersNone":"Tidak ada","headersRow":"Baris Pertama","invalidBorder":"Ukuran batasan harus sebuah angka","invalidCellPadding":"'Spasi dalam' sel harus angka positif.","invalidCellSpacing":"Spasi antar sel harus angka positif.","invalidCols":"Jumlah kolom harus sebuah angka lebih besar dari 0","invalidHeight":"Tinggi tabel harus sebuah angka.","invalidRows":"Jumlah barus harus sebuah angka dan lebih besar dari 0.","invalidWidth":"Lebar tabel harus sebuah angka.","menu":"Properti Tabel","row":{"menu":"Baris","insertBefore":"Sisip Baris Sebelum","insertAfter":"Sisip Baris Sesudah","deleteRow":"Hapus Baris"},"rows":"Baris","summary":"Intisari","title":"Properti Tabel","toolbar":"Tabe","widthPc":"persen","widthPx":"piksel","widthUnit":"lebar satuan"},"undo":{"redo":"Kembali lakukan","undo":"Batalkan perlakuan"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/is.js b/js/ckeditor/lang/is.js
new file mode 100644
index 0000000..09e10ce
--- /dev/null
+++ b/js/ckeditor/lang/is.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['is']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Fletta í skjalasafni","url":"Vefslóð","protocol":"Samskiptastaðall","upload":"Senda upp","uploadSubmit":"Hlaða upp","image":"Setja inn mynd","flash":"Flash","form":"Setja inn innsláttarform","checkbox":"Setja inn hökunarreit","radio":"Setja inn valhnapp","textField":"Setja inn textareit","textarea":"Setja inn textasvæði","hiddenField":"Setja inn falið svæði","button":"Setja inn hnapp","select":"Setja inn lista","imageButton":"Setja inn myndahnapp","notSet":"<ekkert valið>","id":"Auðkenni","name":"Nafn","langDir":"Lesstefna","langDirLtr":"Frá vinstri til hægri (LTR)","langDirRtl":"Frá hægri til vinstri (RTL)","langCode":"Tungumálakóði","longDescr":"Nánari lýsing","cssClass":"Stílsniðsflokkur","advisoryTitle":"Titill","cssStyle":"Stíll","ok":"à lagi","cancel":"Hætta við","close":"Close","preview":"Forskoða","resize":"Resize","generalTab":"Almennt","advancedTab":"Tæknilegt","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Mark","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Frá vinstri til hægri (LTR)","langDirRTL":"Frá hægri til vinstri (RTL)","styles":"Stíll","cssClasses":"Stílsniðsflokkur","width":"Breidd","height":"Hæð","align":"Jöfnun","alignLeft":"Vinstri","alignRight":"Hægri","alignCenter":"Miðjað","alignJustify":"Jafna báðum megin","alignTop":"Efst","alignMiddle":"Miðjuð","alignBottom":"Neðst","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Feitletrað","italic":"Skáletrað","strike":"Yfirstrikað","subscript":"Niðurskrifað","superscript":"Uppskrifað","underline":"Undirstrikað"},"blockquote":{"toolbar":"Inndráttur"},"clipboard":{"copy":"Afrita","copyError":"Öryggisstillingar vafrans þíns leyfa ekki afritun texta með músaraðgerð. Notaðu lyklaborðið í afrita (Ctrl/Cmd+C).","cut":"Klippa","cutError":"Öryggisstillingar vafrans þíns leyfa ekki klippingu texta með músaraðgerð. Notaðu lyklaborðið í klippa (Ctrl/Cmd+X).","paste":"Líma","pasteArea":"Paste Area","pasteMsg":"Límdu í svæðið hér að neðan og (<STRONG>Ctrl/Cmd+V</STRONG>) og smelltu á <STRONG>OK</STRONG>.","securityMsg":"Vegna öryggisstillinga í vafranum þínum fær ritillinn ekki beinan aðgang að klippuborðinu. Þú verður að líma innihaldið aftur inn í þennan glugga.","title":"Líma"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Stílsnið","panelTitle":"Stílsnið","tag_address":"Vistfang","tag_div":"Venjulegt (DIV)","tag_h1":"Fyrirsögn 1","tag_h2":"Fyrirsögn 2","tag_h3":"Fyrirsögn 3","tag_h4":"Fyrirsögn 4","tag_h5":"Fyrirsögn 5","tag_h6":"Fyrirsögn 6","tag_p":"Venjulegt letur","tag_pre":"Forsniðið"},"horizontalrule":{"toolbar":"Lóðrétt lína"},"image":{"alertUrl":"Sláðu inn slóðina að myndinni","alt":"Baklægur texti","border":"Rammi","btnUpload":"Hlaða upp","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"Vinstri bil","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Almennt","linkTab":"Stikla","lockRatio":"Festa stærðarhlutfall","menu":"Eigindi myndar","resetSize":"Reikna stærð","title":"Eigindi myndar","titleButton":"Eigindi myndahnapps","upload":"Hlaða upp","urlMissing":"Image source URL is missing.","vSpace":"Hægri bil","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Minnka inndrátt","outdent":"Auka inndrátt"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Skammvalshnappur","advanced":"Tæknilegt","advisoryContentType":"Tegund innihalds","advisoryTitle":"Titill","anchor":{"toolbar":"Stofna/breyta kaflamerki","menu":"Eigindi kaflamerkis","title":"Eigindi kaflamerkis","name":"Nafn bókamerkis","errorName":"Sláðu inn nafn bókamerkis!","remove":"Remove Anchor"},"anchorId":"Eftir auðkenni einingar","anchorName":"Eftir akkerisnafni","charset":"Táknróf","cssClasses":"Stílsniðsflokkur","emailAddress":"Netfang","emailBody":"Meginmál","emailSubject":"Efni","id":"Auðkenni","info":"Almennt","langCode":"Lesstefna","langDir":"Lesstefna","langDirLTR":"Frá vinstri til hægri (LTR)","langDirRTL":"Frá hægri til vinstri (RTL)","menu":"Breyta stiklu","name":"Nafn","noAnchors":"<Engin bókamerki á skrá>","noEmail":"Sláðu inn netfang!","noUrl":"Sláðu inn veffang stiklunnar!","other":"<annar>","popupDependent":"Háð venslum (Netscape)","popupFeatures":"Eigindi sprettiglugga","popupFullScreen":"Heilskjár (IE)","popupLeft":"Fjarlægð frá vinstri","popupLocationBar":"Fanglína","popupMenuBar":"Vallína","popupResizable":"Resizable","popupScrollBars":"Skrunstikur","popupStatusBar":"Stöðustika","popupToolbar":"Verkfærastika","popupTop":"Fjarlægð frá efri brún","rel":"Relationship","selectAnchor":"Veldu akkeri","styles":"Stíll","tabIndex":"Raðnúmer innsláttarreits","target":"Mark","targetFrame":"<rammi>","targetFrameName":"Nafn markglugga","targetPopup":"<sprettigluggi>","targetPopupName":"Nafn sprettiglugga","title":"Stikla","toAnchor":"Bókamerki á þessari síðu","toEmail":"Netfang","toUrl":"Vefslóð","toolbar":"Stofna/breyta stiklu","type":"Stikluflokkur","unlink":"Fjarlægja stiklu","upload":"Senda upp"},"list":{"bulletedlist":"Punktalisti","numberedlist":"Númeraður listi"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Líma sem ósniðinn texta","title":"Líma sem ósniðinn texta"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Líma úr Word","toolbar":"Líma úr Word"},"removeformat":{"toolbar":"Fjarlægja snið"},"sourcearea":{"toolbar":"Kóði"},"specialchar":{"options":"Special Character Options","title":"Velja tákn","toolbar":"Setja inn merki"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stílflokkur","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Breidd ramma","caption":"Titill","cell":{"menu":"Reitur","insertBefore":"Skjóta inn reiti fyrir aftan","insertAfter":"Skjóta inn reiti fyrir framan","deleteCell":"Fella reit","merge":"Sameina reiti","mergeRight":"Sameina til hægri","mergeDown":"Sameina niður á við","splitHorizontal":"Kljúfa reit lárétt","splitVertical":"Kljúfa reit lóðrétt","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Reitaspássía","cellSpace":"Bil milli reita","column":{"menu":"Dálkur","insertBefore":"Skjóta inn dálki vinstra megin","insertAfter":"Skjóta inn dálki hægra megin","deleteColumn":"Fella dálk"},"columns":"Dálkar","deleteTable":"Fella töflu","headers":"Fyrirsagnir","headersBoth":"Hvort tveggja","headersColumn":"Fyrsti dálkur","headersNone":"Engar","headersRow":"Fyrsta röð","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Eigindi töflu","row":{"menu":"Röð","insertBefore":"Skjóta inn röð fyrir ofan","insertAfter":"Skjóta inn röð fyrir neðan","deleteRow":"Eyða röð"},"rows":"Raðir","summary":"Ãfram","title":"Eigindi töflu","toolbar":"Tafla","widthPc":"prósent","widthPx":"myndeindir","widthUnit":"width unit"},"undo":{"redo":"Hætta við afturköllun","undo":"Afturkalla"},"wsc":{"btnIgnore":"Hunsa","btnIgnoreAll":"Hunsa allt","btnReplace":"Skipta","btnReplaceAll":"Skipta öllu","btnUndo":"Til baka","changeTo":"Tillaga","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Villuleit ekki sett upp.<br>Viltu setja hana upp?","manyChanges":"Villuleit lokið: %1 orðum breytt","noChanges":"Villuleit lokið: Engu orði breytt","noMispell":"Villuleit lokið: Engin villa fannst","noSuggestions":"- engar tillögur -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Ekki í orðabókinni","oneChange":"Villuleit lokið: Einu orði breytt","progress":"Villuleit í gangi...","title":"Spell Checker","toolbar":"Villuleit"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/it.js b/js/ckeditor/lang/it.js
new file mode 100644
index 0000000..f5220ff
--- /dev/null
+++ b/js/ckeditor/lang/it.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['it']={"editor":"Rich Text Editor","editorPanel":"Pannello Rich Text Editor","common":{"editorHelp":"Premi ALT 0 per aiuto","browseServer":"Cerca sul server","url":"URL","protocol":"Protocollo","upload":"Carica","uploadSubmit":"Invia al server","image":"Immagine","flash":"Oggetto Flash","form":"Modulo","checkbox":"Checkbox","radio":"Radio Button","textField":"Campo di testo","textarea":"Area di testo","hiddenField":"Campo nascosto","button":"Bottone","select":"Menu di selezione","imageButton":"Bottone immagine","notSet":"<non impostato>","id":"Id","name":"Nome","langDir":"Direzione scrittura","langDirLtr":"Da Sinistra a Destra (LTR)","langDirRtl":"Da Destra a Sinistra (RTL)","langCode":"Codice Lingua","longDescr":"URL descrizione estesa","cssClass":"Nome classe CSS","advisoryTitle":"Titolo","cssStyle":"Stile","ok":"OK","cancel":"Annulla","close":"Chiudi","preview":"Anteprima","resize":"Trascina per ridimensionare","generalTab":"Generale","advancedTab":"Avanzate","validateNumberFailed":"Il valore inserito non è un numero.","confirmNewPage":"Ogni modifica non salvata sarà persa. Sei sicuro di voler caricare una nuova pagina?","confirmCancel":"Alcune delle opzioni sono state cambiate. Sei sicuro di voler chiudere la finestra di dialogo?","options":"Opzioni","target":"Destinazione","targetNew":"Nuova finestra (_blank)","targetTop":"Finestra in primo piano (_top)","targetSelf":"Stessa finestra (_self)","targetParent":"Finestra Padre (_parent)","langDirLTR":"Da sinistra a destra (LTR)","langDirRTL":"Da destra a sinistra (RTL)","styles":"Stile","cssClasses":"Classi di stile","width":"Larghezza","height":"Altezza","align":"Allineamento","alignLeft":"Sinistra","alignRight":"Destra","alignCenter":"Centrato","alignJustify":"Giustifica","alignTop":"In Alto","alignMiddle":"Centrato","alignBottom":"In Basso","alignNone":"Nessuno","invalidValue":"Valore non valido.","invalidHeight":"L'altezza dev'essere un numero","invalidWidth":"La Larghezza dev'essere un numero","invalidCssLength":"Il valore indicato per il campo \"%1\" deve essere un numero positivo con o senza indicazione di una valida unità di misura per le classi CSS (px, %, in, cm, mm, em, ex, pt, o pc).","invalidHtmlLength":"Il valore indicato per il campo \"%1\" deve essere un numero positivo con o senza indicazione di una valida unità di misura per le pagine HTML (px o %).","invalidInlineStyle":"Il valore specificato per lo stile inline deve consistere in una o più tuple con il formato di \"name : value\", separati da semicolonne.","cssLengthTooltip":"Inserisci un numero per il valore in pixel oppure un numero con una valida unità CSS (px, %, in, cm, mm, ex, pt, o pc).","unavailable":"%1<span class=\"cke_accessibility\">, non disponibile</span>"},"about":{"copy":"Copyright &copy; $1. Tutti i diritti riservati.","dlgTitle":"Riguardo CKEditor","help":"Vedi $1 per l'aiuto.","moreInfo":"Per le informazioni sulla licenza si prega di visitare il nostro sito:","title":"Riguardo CKEditor","userGuide":"Guida Utente CKEditor"},"basicstyles":{"bold":"Grassetto","italic":"Corsivo","strike":"Barrato","subscript":"Pedice","superscript":"Apice","underline":"Sottolineato"},"blockquote":{"toolbar":"Citazione"},"clipboard":{"copy":"Copia","copyError":"Le impostazioni di sicurezza del browser non permettono di copiare automaticamente il testo. Usa la tastiera (Ctrl/Cmd+C).","cut":"Taglia","cutError":"Le impostazioni di sicurezza del browser non permettono di tagliare automaticamente il testo. Usa la tastiera (Ctrl/Cmd+X).","paste":"Incolla","pasteArea":"Incolla","pasteMsg":"Incolla il testo all'interno dell'area sottostante usando la scorciatoia di tastiere (<STRONG>Ctrl/Cmd+V</STRONG>) e premi <STRONG>OK</STRONG>.","securityMsg":"A causa delle impostazioni di sicurezza del browser,l'editor non è in grado di accedere direttamente agli appunti. E' pertanto necessario incollarli di nuovo in questa finestra.","title":"Incolla"},"contextmenu":{"options":"Opzioni del menù contestuale"},"button":{"selectedLabel":"%1 (selezionato)"},"toolbar":{"toolbarCollapse":"Minimizza Toolbar","toolbarExpand":"Espandi Toolbar","toolbarGroups":{"document":"Documento","clipboard":"Copia negli appunti/Annulla","editing":"Modifica","forms":"Form","basicstyles":"Stili di base","paragraph":"Paragrafo","links":"Link","insert":"Inserisci","styles":"Stili","colors":"Colori","tools":"Strumenti"},"toolbars":"Editor toolbar"},"elementspath":{"eleLabel":"Percorso degli elementi","eleTitle":"%1 elemento"},"format":{"label":"Formato","panelTitle":"Formato","tag_address":"Indirizzo","tag_div":"Paragrafo (DIV)","tag_h1":"Titolo 1","tag_h2":"Titolo 2","tag_h3":"Titolo 3","tag_h4":"Titolo 4","tag_h5":"Titolo 5","tag_h6":"Titolo 6","tag_p":"Normale","tag_pre":"Formattato"},"horizontalrule":{"toolbar":"Inserisci riga orizzontale"},"image":{"alertUrl":"Devi inserire l'URL per l'immagine","alt":"Testo alternativo","border":"Bordo","btnUpload":"Invia al server","button2Img":"Vuoi trasformare il bottone immagine selezionato in un'immagine semplice?","hSpace":"HSpace","img2Button":"Vuoi trasferomare l'immagine selezionata in un bottone immagine?","infoTab":"Informazioni immagine","linkTab":"Collegamento","lockRatio":"Blocca rapporto","menu":"Proprietà immagine","resetSize":"Reimposta dimensione","title":"Proprietà immagine","titleButton":"Proprietà bottone immagine","upload":"Carica","urlMissing":"Manca l'URL dell'immagine.","vSpace":"VSpace","validateBorder":"Il campo Bordo deve essere un numero intero.","validateHSpace":"Il campo HSpace deve essere un numero intero.","validateVSpace":"Il campo VSpace deve essere un numero intero."},"indent":{"indent":"Aumenta rientro","outdent":"Riduci rientro"},"fakeobjects":{"anchor":"Ancora","flash":"Animazione Flash","hiddenfield":"Campo Nascosto","iframe":"IFrame","unknown":"Oggetto sconosciuto"},"link":{"acccessKey":"Scorciatoia da tastiera","advanced":"Avanzate","advisoryContentType":"Tipo della risorsa collegata","advisoryTitle":"Titolo","anchor":{"toolbar":"Inserisci/Modifica Ancora","menu":"Proprietà ancora","title":"Proprietà ancora","name":"Nome ancora","errorName":"Inserici il nome dell'ancora","remove":"Rimuovi l'ancora"},"anchorId":"Per id elemento","anchorName":"Per Nome","charset":"Set di caretteri della risorsa collegata","cssClasses":"Nome classe CSS","emailAddress":"Indirizzo E-Mail","emailBody":"Corpo del messaggio","emailSubject":"Oggetto del messaggio","id":"Id","info":"Informazioni collegamento","langCode":"Direzione scrittura","langDir":"Direzione scrittura","langDirLTR":"Da Sinistra a Destra (LTR)","langDirRTL":"Da Destra a Sinistra (RTL)","menu":"Modifica collegamento","name":"Nome","noAnchors":"(Nessuna ancora disponibile nel documento)","noEmail":"Devi inserire un'indirizzo e-mail","noUrl":"Devi inserire l'URL del collegamento","other":"<altro>","popupDependent":"Dipendente (Netscape)","popupFeatures":"Caratteristiche finestra popup","popupFullScreen":"A tutto schermo (IE)","popupLeft":"Posizione da sinistra","popupLocationBar":"Barra degli indirizzi","popupMenuBar":"Barra del menu","popupResizable":"Ridimensionabile","popupScrollBars":"Barre di scorrimento","popupStatusBar":"Barra di stato","popupToolbar":"Barra degli strumenti","popupTop":"Posizione dall'alto","rel":"Relazioni","selectAnchor":"Scegli Ancora","styles":"Stile","tabIndex":"Ordine di tabulazione","target":"Destinazione","targetFrame":"<riquadro>","targetFrameName":"Nome del riquadro di destinazione","targetPopup":"<finestra popup>","targetPopupName":"Nome finestra popup","title":"Collegamento","toAnchor":"Ancora nel testo","toEmail":"E-Mail","toUrl":"URL","toolbar":"Collegamento","type":"Tipo di Collegamento","unlink":"Elimina collegamento","upload":"Carica"},"list":{"bulletedlist":"Inserisci/Rimuovi Elenco Puntato","numberedlist":"Inserisci/Rimuovi Elenco Numerato"},"magicline":{"title":"Inserisci paragrafo qui"},"maximize":{"maximize":"Massimizza","minimize":"Minimizza"},"pastetext":{"button":"Incolla come testo semplice","title":"Incolla come testo semplice"},"pastefromword":{"confirmCleanup":"Il testo da incollare sembra provenire da Word. Desideri pulirlo prima di incollare?","error":"Non è stato possibile eliminare il testo incollato a causa di un errore interno.","title":"Incolla da Word","toolbar":"Incolla da Word"},"removeformat":{"toolbar":"Elimina formattazione"},"sourcearea":{"toolbar":"Sorgente"},"specialchar":{"options":"Opzioni carattere speciale","title":"Seleziona carattere speciale","toolbar":"Inserisci carattere speciale"},"scayt":{"btn_about":"About COMS","btn_dictionaries":"Dizionari","btn_disable":"Disabilita COMS","btn_enable":"Abilita COMS","btn_langs":"Lingue","btn_options":"Opzioni","text_title":"Controllo Ortografico Mentre Scrivi"},"stylescombo":{"label":"Stili","panelTitle":"Stili di formattazione","panelTitle1":"Stili per blocchi","panelTitle2":"Stili in linea","panelTitle3":"Stili per oggetti"},"table":{"border":"Dimensione bordo","caption":"Intestazione","cell":{"menu":"Cella","insertBefore":"Inserisci Cella Prima","insertAfter":"Inserisci Cella Dopo","deleteCell":"Elimina celle","merge":"Unisce celle","mergeRight":"Unisci a Destra","mergeDown":"Unisci in Basso","splitHorizontal":"Dividi Cella Orizzontalmente","splitVertical":"Dividi Cella Verticalmente","title":"Proprietà della cella","cellType":"Tipo di cella","rowSpan":"Su più righe","colSpan":"Su più colonne","wordWrap":"Ritorno a capo","hAlign":"Allineamento orizzontale","vAlign":"Allineamento verticale","alignBaseline":"Linea Base","bgColor":"Colore di Sfondo","borderColor":"Colore del Bordo","data":"Dati","header":"Intestazione","yes":"Si","no":"No","invalidWidth":"La larghezza della cella dev'essere un numero.","invalidHeight":"L'altezza della cella dev'essere un numero.","invalidRowSpan":"Il numero di righe dev'essere un numero intero.","invalidColSpan":"Il numero di colonne dev'essere un numero intero.","chooseColor":"Scegli"},"cellPad":"Padding celle","cellSpace":"Spaziatura celle","column":{"menu":"Colonna","insertBefore":"Inserisci Colonna Prima","insertAfter":"Inserisci Colonna Dopo","deleteColumn":"Elimina colonne"},"columns":"Colonne","deleteTable":"Cancella Tabella","headers":"Intestazione","headersBoth":"Entrambe","headersColumn":"Prima Colonna","headersNone":"Nessuna","headersRow":"Prima Riga","invalidBorder":"La dimensione del bordo dev'essere un numero.","invalidCellPadding":"Il paging delle celle dev'essere un numero","invalidCellSpacing":"La spaziatura tra le celle dev'essere un numero.","invalidCols":"Il numero di colonne dev'essere un numero maggiore di 0.","invalidHeight":"L'altezza della tabella dev'essere un numero.","invalidRows":"Il numero di righe dev'essere un numero maggiore di 0.","invalidWidth":"La larghezza della tabella dev'essere un numero.","menu":"Proprietà tabella","row":{"menu":"Riga","insertBefore":"Inserisci Riga Prima","insertAfter":"Inserisci Riga Dopo","deleteRow":"Elimina righe"},"rows":"Righe","summary":"Indice","title":"Proprietà tabella","toolbar":"Tabella","widthPc":"percento","widthPx":"pixel","widthUnit":"unità larghezza"},"undo":{"redo":"Ripristina","undo":"Annulla"},"wsc":{"btnIgnore":"Ignora","btnIgnoreAll":"Ignora tutto","btnReplace":"Cambia","btnReplaceAll":"Cambia tutto","btnUndo":"Annulla","changeTo":"Cambia in","errorLoading":"Errore nel caricamento dell'host col servizio applicativo: %s.","ieSpellDownload":"Contollo ortografico non installato. Lo vuoi scaricare ora?","manyChanges":"Controllo ortografico completato: %1 parole cambiate","noChanges":"Controllo ortografico completato: nessuna parola cambiata","noMispell":"Controllo ortografico completato: nessun errore trovato","noSuggestions":"- Nessun suggerimento -","notAvailable":"Il servizio non è momentaneamente disponibile.","notInDic":"Non nel dizionario","oneChange":"Controllo ortografico completato: 1 parola cambiata","progress":"Controllo ortografico in corso","title":"Controllo ortografico","toolbar":"Correttore ortografico"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ja.js b/js/ckeditor/lang/ja.js
new file mode 100644
index 0000000..eaaab15
--- /dev/null
+++ b/js/ckeditor/lang/ja.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ja']={"editor":"リッãƒãƒ†ã‚­ã‚¹ãƒˆã‚¨ãƒ‡ã‚£ã‚¿","editorPanel":"リッãƒãƒ†ã‚­ã‚¹ãƒˆã‚¨ãƒ‡ã‚£ã‚¿ãƒ‘ãƒãƒ«","common":{"editorHelp":"ヘルプ㯠ALT 0 を押ã—ã¦ãã ã•ã„","browseServer":"サーãƒãƒ–ラウザ","url":"URL","protocol":"プロトコル","upload":"アップロード","uploadSubmit":"サーãƒãƒ¼ã«é€ä¿¡","image":"イメージ","flash":"Flash","form":"フォーム","checkbox":"ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹","radio":"ラジオボタン","textField":"1行テキスト","textarea":"テキストエリア","hiddenField":"ä¸å¯è¦–フィールド","button":"ボタン","select":"é¸æŠžãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰","imageButton":"ç”»åƒãƒœã‚¿ãƒ³","notSet":"<ãªã—>","id":"Id","name":"Name属性","langDir":"文字表記ã®æ–¹å‘","langDirLtr":"å·¦ã‹ã‚‰å³ (LTR)","langDirRtl":"å³ã‹ã‚‰å·¦ (RTL)","langCode":"言語コード","longDescr":"longdesc属性(長文説明)","cssClass":"スタイルシートクラス","advisoryTitle":"Title属性","cssStyle":"スタイルシート","ok":"OK","cancel":"キャンセル","close":"é–‰ã˜ã‚‹","preview":"プレビュー","resize":"ドラッグã—ã¦ãƒªã‚µã‚¤ã‚º","generalTab":"全般","advancedTab":"高度ãªè¨­å®š","validateNumberFailed":"値ãŒæ•°ã§ã¯ã‚ã‚Šã¾ã›ã‚“","confirmNewPage":"変更内容をä¿å­˜ã›ãšã€ æ–°ã—ã„ページを開ã„ã¦ã‚‚よã‚ã—ã„ã§ã—ょã†ã‹ï¼Ÿ","confirmCancel":"オプション設定を変更ã—ã¾ã—ãŸã€‚ダイアログを閉ã˜ã¦ã‚‚よã‚ã—ã„ã§ã—ょã†ã‹ï¼Ÿ","options":"オプション","target":"ターゲット","targetNew":"æ–°ã—ã„ウインドウ (_blank)","targetTop":"最上部ウィンドウ (_top)","targetSelf":"åŒã˜ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ (_self)","targetParent":"親ウィンドウ (_parent)","langDirLTR":"å·¦ã‹ã‚‰å³ (LTR)","langDirRTL":"å³ã‹ã‚‰å·¦ (RTL)","styles":"スタイル","cssClasses":"スタイルシートクラス","width":"å¹…","height":"高ã•","align":"è¡Œæƒãˆ","alignLeft":"å·¦","alignRight":"å³","alignCenter":"中央","alignJustify":"両端æƒãˆ","alignTop":"上","alignMiddle":"中央","alignBottom":"下","alignNone":"ãªã—","invalidValue":"ä¸æ­£ãªå€¤ã§ã™ã€‚","invalidHeight":"高ã•ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidWidth":"å¹…ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidCssLength":"入力ã•ã‚ŒãŸ \"%1\" é …ç›®ã®å€¤ã¯ã€CSSã®å¤§ãã•(px, %, in, cm, mm, em, ex, pt, ã¾ãŸã¯ pc)ãŒæ­£ã—ã„ã‚‚ã®ã§ã‚ã‚‹/ãªã„ã«é–¢ã‚らãšã€æ­£ã®å€¤ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚","invalidHtmlLength":"入力ã•ã‚ŒãŸ \"%1\" é …ç›®ã®å€¤ã¯ã€HTMLã®å¤§ãã•(px ã¾ãŸã¯ %)ãŒæ­£ã—ã„ã‚‚ã®ã§ã‚ã‚‹/ãªã„ã«é–¢ã‚らãšã€æ­£ã®å€¤ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚","invalidInlineStyle":"入力ã•ã‚ŒãŸã‚¤ãƒ³ãƒ©ã‚¤ãƒ³ã‚¹ã‚¿ã‚¤ãƒ«ã®å€¤ã¯ã€\"åå‰ : 値\" ã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã®ã‚»ãƒƒãƒˆã§ã€è¤‡æ•°ã®å ´åˆã¯ã‚»ãƒŸã‚³ãƒ­ãƒ³ã§åŒºåˆ‡ã‚‰ã‚Œã¦ã„ã‚‹å½¢å¼ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚","cssLengthTooltip":"ピクセル数もã—ãã¯CSSã«ã‚»ãƒƒãƒˆã§ãる数値を入力ã—ã¦ãã ã•ã„。(px,%,in,cm,mm,em,ex,pt,or pc)","unavailable":"%1<span class=\"cke_accessibility\">, 利用ä¸å¯èƒ½</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"CKEditorã«ã¤ã„ã¦","help":"$1 ã®ãƒ˜ãƒ«ãƒ—を見ã¦ãã ã•ã„。","moreInfo":"ライセンス情報ã®è©³ç´°ã¯ã‚¦ã‚§ãƒ–サイトã«ã¦ç¢ºèªã—ã¦ãã ã•ã„:","title":"CKEditorã«ã¤ã„ã¦","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"太字","italic":"斜体","strike":"打ã¡æ¶ˆã—ç·š","subscript":"下付ã","superscript":"上付ã","underline":"下線"},"blockquote":{"toolbar":"ブロック引用文"},"clipboard":{"copy":"コピー","copyError":"ブラウザーã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã‚¨ãƒ‡ã‚£ã‚¿ã®ã‚³ãƒ”ーæ“作を自動ã§å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。実行ã™ã‚‹ã«ã¯æ‰‹å‹•ã§ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã®(Ctrl/Cmd+C)を使用ã—ã¦ãã ã•ã„。","cut":"切りå–ã‚Š","cutError":"ブラウザーã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã‚¨ãƒ‡ã‚£ã‚¿ã®åˆ‡ã‚Šå–ã‚Šæ“作を自動ã§å®Ÿè¡Œã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。実行ã™ã‚‹ã«ã¯æ‰‹å‹•ã§ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã®(Ctrl/Cmd+X)を使用ã—ã¦ãã ã•ã„。","paste":"貼り付ã‘","pasteArea":"貼り付ã‘場所","pasteMsg":"キーボード(<STRONG>Ctrl/Cmd+V</STRONG>)を使用ã—ã¦ã€æ¬¡ã®å…¥åŠ›ã‚¨ãƒªã‚¢å†…ã§è²¼ã‚Šä»˜ã‘ã¦ã€<STRONG>OK</STRONG>を押ã—ã¦ãã ã•ã„。","securityMsg":"ブラウザã®ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£è¨­å®šã«ã‚ˆã‚Šã€ã‚¨ãƒ‡ã‚£ã‚¿ã¯ã‚¯ãƒªãƒƒãƒ—ボードデータã«ç›´æŽ¥ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。ã“ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã¯è²¼ã‚Šä»˜ã‘æ“作を行ã†åº¦ã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚","title":"貼り付ã‘"},"contextmenu":{"options":"コンテキストメニューオプション"},"button":{"selectedLabel":"%1 (é¸æŠžä¸­)"},"toolbar":{"toolbarCollapse":"ツールãƒãƒ¼ã‚’é–‰ã˜ã‚‹","toolbarExpand":"ツールãƒãƒ¼ã‚’é–‹ã","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"編集ツールãƒãƒ¼"},"elementspath":{"eleLabel":"è¦ç´ ãƒ‘ス","eleTitle":"%1 è¦ç´ "},"format":{"label":"書å¼","panelTitle":"段è½ã®æ›¸å¼","tag_address":"アドレス","tag_div":"標準 (DIV)","tag_h1":"見出㗠1","tag_h2":"見出㗠2","tag_h3":"見出㗠3","tag_h4":"見出㗠4","tag_h5":"見出㗠5","tag_h6":"見出㗠6","tag_p":"標準","tag_pre":"書å¼ä»˜ã"},"horizontalrule":{"toolbar":"水平線"},"image":{"alertUrl":"ç”»åƒã®URLを入力ã—ã¦ãã ã•ã„","alt":"代替テキスト","border":"æž ç·šã®å¹…","btnUpload":"サーãƒãƒ¼ã«é€ä¿¡","button2Img":"é¸æŠžã—ãŸç”»åƒãƒœã‚¿ãƒ³ã‚’ç”»åƒã«å¤‰æ›ã—ã¾ã™ã‹ï¼Ÿ","hSpace":"水平間隔","img2Button":"é¸æŠžã—ãŸç”»åƒã‚’ç”»åƒãƒœã‚¿ãƒ³ã«å¤‰æ›ã—ã¾ã™ã‹ï¼Ÿ","infoTab":"ç”»åƒæƒ…å ±","linkTab":"リンク","lockRatio":"比率を固定","menu":"ç”»åƒã®ãƒ—ロパティ","resetSize":"サイズをリセット","title":"ç”»åƒã®ãƒ—ロパティ","titleButton":"ç”»åƒãƒœã‚¿ãƒ³ã®ãƒ—ロパティ","upload":"アップロード","urlMissing":"ç”»åƒã®URLを入力ã—ã¦ãã ã•ã„。","vSpace":"åž‚ç›´é–“éš”","validateBorder":"æž ç·šã®å¹…ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","validateHSpace":"水平間隔ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","validateVSpace":"åž‚ç›´é–“éš”ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。"},"indent":{"indent":"インデント","outdent":"インデント解除"},"fakeobjects":{"anchor":"アンカー","flash":"Flash Animation","hiddenfield":"ä¸å¯è¦–フィールド","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"アクセスキー","advanced":"高度ãªè¨­å®š","advisoryContentType":"Content Type属性","advisoryTitle":"Title属性","anchor":{"toolbar":"アンカー挿入/編集","menu":"アンカーã®ç·¨é›†","title":"アンカーã®ãƒ—ロパティ","name":"アンカーå","errorName":"アンカーåを入力ã—ã¦ãã ã•ã„。","remove":"アンカーを削除"},"anchorId":"エレメントID","anchorName":"アンカーå","charset":"リンク先ã®charset","cssClasses":"スタイルシートクラス","emailAddress":"E-Mail アドレス","emailBody":"本文","emailSubject":"件å","id":"Id","info":"ãƒã‚¤ãƒ‘ーリンク情報","langCode":"言語コード","langDir":"文字表記ã®æ–¹å‘","langDirLTR":"å·¦ã‹ã‚‰å³ (LTR)","langDirRTL":"å³ã‹ã‚‰å·¦ (RTL)","menu":"リンクを編集","name":"Name属性","noAnchors":"(ã“ã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆå†…ã«ã‚¢ãƒ³ã‚«ãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“)","noEmail":"メールアドレスを入力ã—ã¦ãã ã•ã„。","noUrl":"リンクURLを入力ã—ã¦ãã ã•ã„。","other":"<ãã®ä»–ã®>","popupDependent":"é–‹ã„ãŸã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã«é€£å‹•ã—ã¦é–‰ã˜ã‚‹ (Netscape)","popupFeatures":"ãƒãƒƒãƒ—アップウィンドウ特徴","popupFullScreen":"全画é¢ãƒ¢ãƒ¼ãƒ‰(IE)","popupLeft":"左端ã‹ã‚‰ã®åº§æ¨™ã§æŒ‡å®š","popupLocationBar":"ロケーションãƒãƒ¼","popupMenuBar":"メニューãƒãƒ¼","popupResizable":"サイズå¯å¤‰","popupScrollBars":"スクロールãƒãƒ¼","popupStatusBar":"ステータスãƒãƒ¼","popupToolbar":"ツールãƒãƒ¼","popupTop":"上端ã‹ã‚‰ã®åº§æ¨™ã§æŒ‡å®š","rel":"関連リンク","selectAnchor":"アンカーをé¸æŠž","styles":"スタイルシート","tabIndex":"タブインデックス","target":"ターゲット","targetFrame":"<フレーム>","targetFrameName":"ターゲットã®ãƒ•ãƒ¬ãƒ¼ãƒ å","targetPopup":"<ãƒãƒƒãƒ—アップウィンドウ>","targetPopupName":"ãƒãƒƒãƒ—アップウィンドウå","title":"ãƒã‚¤ãƒ‘ーリンク","toAnchor":"ページ内ã®ã‚¢ãƒ³ã‚«ãƒ¼","toEmail":"E-Mail","toUrl":"URL","toolbar":"リンク挿入/編集","type":"リンクタイプ","unlink":"リンクを削除","upload":"アップロード"},"list":{"bulletedlist":"番å·ç„¡ã—リスト","numberedlist":"番å·ä»˜ãリスト"},"magicline":{"title":"ã“ã“ã«æ®µè½ã‚’挿入"},"maximize":{"maximize":"最大化","minimize":"最å°åŒ–"},"pastetext":{"button":"プレーンテキストã¨ã—ã¦è²¼ã‚Šä»˜ã‘","title":"プレーンテキストã¨ã—ã¦è²¼ã‚Šä»˜ã‘"},"pastefromword":{"confirmCleanup":"貼り付ã‘ã‚’è¡Œã†ãƒ†ã‚­ã‚¹ãƒˆã¯ãƒ¯ãƒ¼ãƒ‰æ–‡ç« ã‹ã‚‰ã‚³ãƒ”ーã•ã‚Œã‚ˆã†ã¨ã—ã¦ã„ã¾ã™ã€‚貼り付ã‘ã‚‹å‰ã«ã‚¯ãƒªãƒ¼ãƒ‹ãƒ³ã‚°ã‚’è¡Œã„ã¾ã™ã‹ï¼Ÿ","error":"内部エラーã«ã‚ˆã‚Šè²¼ã‚Šä»˜ã‘ãŸãƒ‡ãƒ¼ã‚¿ã‚’クリアã§ãã¾ã›ã‚“ã§ã—ãŸ","title":"ワード文章ã‹ã‚‰è²¼ã‚Šä»˜ã‘","toolbar":"ワード文章ã‹ã‚‰è²¼ã‚Šä»˜ã‘"},"removeformat":{"toolbar":"書å¼ã‚’解除"},"sourcearea":{"toolbar":"ソース"},"specialchar":{"options":"特殊文字オプション","title":"特殊文字ã®é¸æŠž","toolbar":"特殊文字を挿入"},"scayt":{"btn_about":"SCAYTバージョï¾","btn_dictionaries":"辞書","btn_disable":"SCAYT無効","btn_enable":"SCAYT有効","btn_langs":"言語","btn_options":"オプション","text_title":"スペルãƒã‚§ãƒƒã‚¯è¨­å®š(SCAYT)"},"stylescombo":{"label":"スタイル","panelTitle":"スタイル","panelTitle1":"ブロックスタイル","panelTitle2":"インラインスタイル","panelTitle3":"オブジェクトスタイル"},"table":{"border":"æž ç·šã®å¹…","caption":"キャプション","cell":{"menu":"セル","insertBefore":"セルをå‰ã«æŒ¿å…¥","insertAfter":"セルを後ã«æŒ¿å…¥","deleteCell":"セルを削除","merge":"セルをçµåˆ","mergeRight":"å³ã«çµåˆ","mergeDown":"下ã«çµåˆ","splitHorizontal":"セルを水平方å‘ã«åˆ†å‰²","splitVertical":"セルを垂直方å‘ã«åˆ†å‰²","title":"セルã®ãƒ—ロパティ","cellType":"セルã®ç¨®é¡ž","rowSpan":"è¡Œã®çµåˆæ•°","colSpan":"列ã®çµåˆæ•°","wordWrap":"å˜èªžã®æŠ˜ã‚Šè¿”ã—","hAlign":"水平方å‘ã®é…ç½®","vAlign":"åž‚ç›´æ–¹å‘ã®é…ç½®","alignBaseline":"ベースライン","bgColor":"背景色","borderColor":"ボーダーカラー","data":"テーブルデータ (td)","header":"ヘッダ","yes":"ã¯ã„","no":"ã„ã„ãˆ","invalidWidth":"セル幅ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidHeight":"セル高ã•ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidRowSpan":"縦幅(行数)ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidColSpan":"横幅(列数)ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","chooseColor":"色ã®é¸æŠž"},"cellPad":"セル内間隔","cellSpace":"セル内余白","column":{"menu":"列","insertBefore":"列を左ã«æŒ¿å…¥","insertAfter":"列をå³ã«æŒ¿å…¥","deleteColumn":"列を削除"},"columns":"列数","deleteTable":"表を削除","headers":"ヘッダ (th)","headersBoth":"両方","headersColumn":"最åˆã®åˆ—ã®ã¿","headersNone":"ãªã—","headersRow":"最åˆã®è¡Œã®ã¿","invalidBorder":"æž ç·šã®å¹…ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidCellPadding":"セル内余白ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidCellSpacing":"セル間余白ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidCols":"列数ã¯0より大ããªæ•°å€¤ã‚’入力ã—ã¦ãã ã•ã„。","invalidHeight":"高ã•ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","invalidRows":"行数ã¯0より大ããªæ•°å€¤ã‚’入力ã—ã¦ãã ã•ã„。","invalidWidth":"å¹…ã¯æ•°å€¤ã§å…¥åŠ›ã—ã¦ãã ã•ã„。","menu":"表ã®ãƒ—ロパティ","row":{"menu":"è¡Œ","insertBefore":"行を上ã«æŒ¿å…¥","insertAfter":"行を下ã«æŒ¿å…¥","deleteRow":"行を削除"},"rows":"行数","summary":"表ã®æ¦‚è¦","title":"表ã®ãƒ—ロパティ","toolbar":"表","widthPc":"パーセント","widthPx":"ピクセル","widthUnit":"å¹…ã®å˜ä½"},"undo":{"redo":"ã‚„ã‚Šç›´ã™","undo":"å…ƒã«æˆ»ã™"},"wsc":{"btnIgnore":"無視","btnIgnoreAll":"ã™ã¹ã¦ç„¡è¦–","btnReplace":"ç½®æ›","btnReplaceAll":"ã™ã¹ã¦ç½®æ›","btnUndo":"ã‚„ã‚Šç›´ã—","changeTo":"変更","errorLoading":"アプリケーションサービスホスト読込ã¿ã‚¨ãƒ©ãƒ¼: %s.","ieSpellDownload":"スペルãƒã‚§ãƒƒã‚«ãƒ¼ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã¾ã›ã‚“。今ã™ãダウンロードã—ã¾ã™ã‹?","manyChanges":"スペルãƒã‚§ãƒƒã‚¯å®Œäº†: %1 語å¥å¤‰æ›´ã•ã‚Œã¾ã—ãŸ","noChanges":"スペルãƒã‚§ãƒƒã‚¯å®Œäº†: 語å¥ã¯å¤‰æ›´ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ","noMispell":"スペルãƒã‚§ãƒƒã‚¯å®Œäº†: スペルã®èª¤ã‚Šã¯ã‚ã‚Šã¾ã›ã‚“ã§ã—ãŸ","noSuggestions":"- 該当ãªã— -","notAvailable":"申ã—訳ã‚ã‚Šã¾ã›ã‚“ã€ç¾åœ¨ã‚µãƒ¼ãƒ“スを利用ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“","notInDic":"辞書ã«ã‚ã‚Šã¾ã›ã‚“","oneChange":"スペルãƒã‚§ãƒƒã‚¯å®Œäº†: 1語å¥å¤‰æ›´ã•ã‚Œã¾ã—ãŸ","progress":"スペルãƒã‚§ãƒƒã‚¯å‡¦ç†ä¸­...","title":"スペルãƒã‚§ãƒƒã‚¯","toolbar":"スペルãƒã‚§ãƒƒã‚¯"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ka.js b/js/ckeditor/lang/ka.js
new file mode 100644
index 0000000..52216b6
--- /dev/null
+++ b/js/ckeditor/lang/ka.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ka']={"editor":"ტექსტის რედáƒáƒ¥áƒ¢áƒáƒ áƒ˜","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"დáƒáƒáƒ­áƒ˜áƒ áƒ”თ ALT 0-ს დáƒáƒ®áƒ›áƒáƒ áƒ”ბის მისáƒáƒ¦áƒ”ბáƒáƒ“","browseServer":"სერვერზე დáƒáƒ—ვáƒáƒšáƒ˜áƒ”რებáƒ","url":"URL","protocol":"პრáƒáƒ¢áƒáƒ™áƒáƒšáƒ˜","upload":"áƒáƒ¢áƒ•áƒ˜áƒ áƒ—ვáƒ","uploadSubmit":"სერვერზე გáƒáƒ’ზáƒáƒ•áƒœáƒ","image":"სურáƒáƒ—ი","flash":"Flash","form":"ფáƒáƒ áƒ›áƒ","checkbox":"მáƒáƒœáƒ˜áƒ¨áƒ•áƒœáƒ˜áƒ¡ ღილáƒáƒ™áƒ˜","radio":"áƒáƒ›áƒáƒ áƒ©áƒ”ვის ღილáƒáƒ™áƒ˜","textField":"ტექსტური ველი","textarea":"ტექსტური áƒáƒ áƒ”","hiddenField":"მáƒáƒšáƒ£áƒšáƒ˜ ველი","button":"ღილáƒáƒ™áƒ˜","select":"áƒáƒ áƒ©áƒ”ვის ველი","imageButton":"სურáƒáƒ—იáƒáƒœáƒ˜ ღილáƒáƒ™áƒ˜","notSet":"<áƒáƒ áƒáƒ¤áƒ”რი>","id":"Id","name":"სáƒáƒ®áƒ”ლი","langDir":"ენის მიმáƒáƒ áƒ—ულებáƒ","langDirLtr":"მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ• (LTR)","langDirRtl":"მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• (RTL)","langCode":"ენის კáƒáƒ“ი","longDescr":"დიდი áƒáƒ¦áƒ¬áƒ”რის URL","cssClass":"CSS კლáƒáƒ¡áƒ˜","advisoryTitle":"სáƒáƒ—áƒáƒ£áƒ áƒ˜","cssStyle":"CSS სტილი","ok":"დიáƒáƒ®","cancel":"გáƒáƒ£áƒ¥áƒ›áƒ”ბáƒ","close":"დáƒáƒ®áƒ£áƒ áƒ•áƒ","preview":"გáƒáƒ“áƒáƒ®áƒ”დვáƒ","resize":"გáƒáƒ¬áƒ˜áƒ” ზáƒáƒ›áƒ˜áƒ¡ შესáƒáƒªáƒ•áƒšáƒ”ლáƒáƒ“","generalTab":"ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ","advancedTab":"გáƒáƒ¤áƒáƒ áƒ—áƒáƒ”ბული","validateNumberFailed":"ეს მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ რიცხვი.","confirmNewPage":"áƒáƒ› დáƒáƒ™áƒ£áƒ›áƒ”ნტში ყველრჩáƒáƒ£áƒ¬áƒ”რელი ცვლილებრდáƒáƒ˜áƒ™áƒáƒ áƒ’ებáƒ. დáƒáƒ áƒ¬áƒ›áƒ£áƒœáƒ”ბული ხáƒáƒ áƒ— რáƒáƒ› áƒáƒ®áƒáƒšáƒ˜ გვერდის ჩáƒáƒ¢áƒ•áƒ˜áƒ áƒ—ვრგინდáƒáƒ—?","confirmCancel":"ზáƒáƒ’იერთი პáƒáƒ áƒáƒ›áƒ”ტრი შეცვლილიáƒ, დáƒáƒ áƒ¬áƒ›áƒ£áƒœáƒ”ბულილ ხáƒáƒ áƒ— რáƒáƒ› ფáƒáƒœáƒ¯áƒ áƒ˜áƒ¡ დáƒáƒ®áƒ£áƒ áƒ•áƒ გსურთ?","options":"პáƒáƒ áƒáƒ›áƒ”ტრები","target":"გáƒáƒ®áƒ¡áƒœáƒ˜áƒ¡ áƒáƒ“გილი","targetNew":"áƒáƒ®áƒáƒšáƒ˜ ფáƒáƒœáƒ¯áƒáƒ áƒ (_blank)","targetTop":"ზედრფáƒáƒœáƒ¯áƒáƒ áƒ (_top)","targetSelf":"იგივე ფáƒáƒœáƒ¯áƒáƒ áƒ (_self)","targetParent":"მშáƒáƒ‘ელი ფáƒáƒœáƒ¯áƒáƒ áƒ (_parent)","langDirLTR":"მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ• (LTR)","langDirRTL":"მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• (RTL)","styles":"სტილი","cssClasses":"CSS კლáƒáƒ¡áƒ˜","width":"სიგáƒáƒœáƒ”","height":"სიმáƒáƒ¦áƒšáƒ”","align":"სწáƒáƒ áƒ”ბáƒ","alignLeft":"მáƒáƒ áƒªáƒ®áƒ”ნáƒ","alignRight":"მáƒáƒ áƒ¯áƒ•áƒ”ნáƒ","alignCenter":"შუáƒ","alignJustify":"両端æƒãˆ","alignTop":"ზემáƒáƒ—áƒ","alignMiddle":"შუáƒ","alignBottom":"ქვემáƒáƒ—áƒ","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"სიმáƒáƒ¦áƒšáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidWidth":"სიგáƒáƒœáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, მიუწვდáƒáƒ›áƒ”ლიáƒ</span>"},"about":{"copy":"Copyright &copy; $1. ყველრუფლებრდáƒáƒªáƒ£áƒšáƒ˜áƒ.","dlgTitle":"CKEditor-ის შესáƒáƒ®áƒ”ბ","help":"დáƒáƒ®áƒ›áƒáƒ áƒ”ბისთვის იხილეთ $1.","moreInfo":"ლიცენზიის ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ˜áƒ¡áƒ—ვის ეწვიეთ ჩვენს სáƒáƒ˜áƒ¢áƒ¡:","title":"CKEditor-ის შესáƒáƒ®áƒ”ბ","userGuide":"CKEditor-ის მáƒáƒ›áƒ®áƒ›áƒáƒ áƒ”ბლის სáƒáƒ®áƒ”ლმძღვáƒáƒœáƒ”ლáƒ"},"basicstyles":{"bold":"მსხვილი","italic":"დáƒáƒ®áƒ áƒ˜áƒšáƒ˜","strike":"გáƒáƒ“áƒáƒ®áƒáƒ–ული","subscript":"ინდექსი","superscript":"ხáƒáƒ áƒ˜áƒ¡áƒ®áƒ˜","underline":"გáƒáƒ®áƒáƒ–ული"},"blockquote":{"toolbar":"ციტáƒáƒ¢áƒ"},"clipboard":{"copy":"áƒáƒ¡áƒšáƒ˜","copyError":"თქვენი ბრáƒáƒ£áƒ–ერის უსáƒáƒ¤áƒ áƒ—ხáƒáƒ”ბის პáƒáƒ áƒáƒ›áƒ”ტრები áƒáƒ  იძლევრáƒáƒ¡áƒšáƒ˜áƒ¡ áƒáƒžáƒ”რáƒáƒªáƒ˜áƒ˜áƒ¡ áƒáƒ•áƒ¢áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ გáƒáƒœáƒ®áƒáƒ áƒªáƒ˜áƒ”ლების სáƒáƒ¨áƒ£áƒáƒšáƒ”ბáƒáƒ¡. გáƒáƒ›áƒáƒ˜áƒ§áƒ”ნეთ კლáƒáƒ•áƒ˜áƒáƒ¢áƒ£áƒ áƒ áƒáƒ›áƒ˜áƒ¡áƒ—ვის (Ctrl/Cmd+C).","cut":"áƒáƒ›áƒáƒ­áƒ áƒ","cutError":"თქვენი ბრáƒáƒ£áƒ–ერის უსáƒáƒ¤áƒ áƒ—ხáƒáƒ”ბის პáƒáƒ áƒáƒ›áƒ”ტრები áƒáƒ  იძლევრáƒáƒ›áƒáƒ­áƒ áƒ˜áƒ¡ áƒáƒžáƒ”რáƒáƒªáƒ˜áƒ˜áƒ¡ áƒáƒ•áƒ¢áƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ გáƒáƒœáƒ®áƒáƒ áƒªáƒ˜áƒ”ლების სáƒáƒ¨áƒ£áƒáƒšáƒ”ბáƒáƒ¡. გáƒáƒ›áƒáƒ˜áƒ§áƒ”ნეთ კლáƒáƒ•áƒ˜áƒáƒ¢áƒ£áƒ áƒ áƒáƒ›áƒ˜áƒ¡áƒ—ვის (Ctrl/Cmd+X).","paste":"ჩáƒáƒ¡áƒ›áƒ","pasteArea":"ჩáƒáƒ¡áƒ›áƒ˜áƒ¡ áƒáƒ áƒ”","pasteMsg":"ჩáƒáƒ¡áƒ•áƒ˜áƒ— áƒáƒ› áƒáƒ áƒ˜áƒ¡ შიგნით კლáƒáƒ•áƒ˜áƒáƒ¢áƒ£áƒ áƒ˜áƒ¡ გáƒáƒ›áƒáƒ§áƒ”ნებით (<strong>Ctrl/Cmd+V</strong>) დრდáƒáƒáƒ­áƒ˜áƒ áƒ”თ OK-ს","securityMsg":"თქვენი ბრáƒáƒ£áƒ–ერის უსáƒáƒ¤áƒ áƒ—ხáƒáƒ”ბის პáƒáƒ áƒáƒ›áƒ”ტრები áƒáƒ  იძლევრclipboard-ის მáƒáƒœáƒáƒªáƒ”მების წვდáƒáƒ›áƒ˜áƒ¡ უფლებáƒáƒ¡. კიდევ უნდრჩáƒáƒ¡áƒ•áƒáƒ— ტექსტი áƒáƒ› ფáƒáƒœáƒ¯áƒáƒ áƒáƒ¨áƒ˜.","title":"ჩáƒáƒ¡áƒ›áƒ"},"contextmenu":{"options":"კáƒáƒœáƒ¢áƒ”ქსტური მენიუს პáƒáƒ áƒáƒ›áƒ”ტრები"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"ხელსáƒáƒ¬áƒ§áƒáƒ—რზáƒáƒšáƒ˜áƒ¡ შეწევáƒ","toolbarExpand":"ხელსáƒáƒ¬áƒ§áƒáƒ—რზáƒáƒšáƒ˜áƒ¡ გáƒáƒ›áƒáƒ¬áƒ”ვáƒ","toolbarGroups":{"document":"დáƒáƒ™áƒ£áƒ›áƒ”ნტი","clipboard":"Clipboard/გáƒáƒ£áƒ¥áƒ›áƒ”ბáƒ","editing":"რედáƒáƒ¥áƒ¢áƒ˜áƒ áƒ”ბáƒ","forms":"ფáƒáƒ áƒ›áƒ”ბი","basicstyles":"ძირითáƒáƒ“ი სტილები","paragraph":"áƒáƒ‘ზáƒáƒªáƒ˜","links":"ბმულები","insert":"ჩáƒáƒ¡áƒ›áƒ","styles":"სტილები","colors":"ფერები","tools":"ხელსáƒáƒ¬áƒ§áƒáƒ”ბი"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"ელემეტის გზáƒ","eleTitle":"%1 ელემენტი"},"format":{"label":"ფიáƒáƒ áƒ›áƒáƒ¢áƒ˜áƒ áƒ”ბáƒ","panelTitle":"ფáƒáƒ áƒ›áƒáƒ¢áƒ˜áƒ áƒ”ბáƒ","tag_address":"მისáƒáƒ›áƒáƒ áƒ—ი","tag_div":"ჩვეულებრივი (DIV)","tag_h1":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 1","tag_h2":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 2","tag_h3":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 3","tag_h4":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 4","tag_h5":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 5","tag_h6":"სáƒáƒ—áƒáƒ£áƒ áƒ˜ 6","tag_p":"ჩვეულებრივი","tag_pre":"ფáƒáƒ áƒ›áƒáƒ¢áƒ˜áƒ áƒ”ბული"},"horizontalrule":{"toolbar":"ჰáƒáƒ áƒ˜áƒ–áƒáƒœáƒ¢áƒáƒšáƒ£áƒ áƒ˜ ხáƒáƒ–ის ჩáƒáƒ¡áƒ›áƒ"},"image":{"alertUrl":"áƒáƒ™áƒ áƒ˜áƒ¤áƒ”თ სურáƒáƒ—ის URL","alt":"სáƒáƒœáƒáƒªáƒ•áƒšáƒ ტექსტი","border":"ჩáƒáƒ áƒ©áƒ","btnUpload":"სერვერისთვის გáƒáƒ’ზáƒáƒ•áƒœáƒ","button2Img":"გსურთ áƒáƒ áƒ©áƒ”ული სურáƒáƒ—იáƒáƒœáƒ˜ ღილáƒáƒ™áƒ˜áƒ¡ გáƒáƒ“áƒáƒ¥áƒªáƒ”ვრჩვეულებრივ ღილáƒáƒ™áƒáƒ“?","hSpace":"ჰáƒáƒ áƒ˜áƒ–áƒáƒœáƒ¢áƒáƒšáƒ£áƒ áƒ˜ სივრცე","img2Button":"გსურთ áƒáƒ áƒ©áƒ”ული ჩვეულებრივი ღილáƒáƒ™áƒ˜áƒ¡ გáƒáƒ“áƒáƒ¥áƒªáƒ”ვრსურáƒáƒ—იáƒáƒœ ღილáƒáƒ™áƒáƒ“?","infoTab":"სურáƒáƒ—ის ინფáƒáƒ áƒ›áƒªáƒ˜áƒ","linkTab":"ბმული","lockRatio":"პრáƒáƒžáƒáƒ áƒªáƒ˜áƒ˜áƒ¡ შენáƒáƒ áƒ©áƒ£áƒœáƒ”ბáƒ","menu":"სურáƒáƒ—ის პáƒáƒ áƒáƒ›áƒ”ტრები","resetSize":"ზáƒáƒ›áƒ˜áƒ¡ დáƒáƒ‘რუნებáƒ","title":"სურáƒáƒ—ის პáƒáƒ áƒáƒ›áƒ”ტრები","titleButton":"სურáƒáƒ—იáƒáƒœáƒ˜ ღილáƒáƒ™áƒ˜áƒ¡ პáƒáƒ áƒáƒ›áƒ”ტრები","upload":"áƒáƒ¢áƒ•áƒ˜áƒ áƒ—ვáƒ","urlMissing":"სურáƒáƒ—ის URL áƒáƒ áƒáƒ შევსებული.","vSpace":"ვერტიკáƒáƒšáƒ£áƒ áƒ˜ სივრცე","validateBorder":"ჩáƒáƒ áƒ©áƒ მთელი რიცხვი უნდრიყáƒáƒ¡.","validateHSpace":"ჰáƒáƒ áƒ˜áƒ–áƒáƒœáƒ¢áƒáƒšáƒ£áƒ áƒ˜ სივრცე მთელი რიცხვი უნდრიყáƒáƒ¡.","validateVSpace":"ვერტიკáƒáƒšáƒ£áƒ áƒ˜ სივრცე მთელი რიცხვი უნდრიყáƒáƒ¡."},"indent":{"indent":"მეტáƒáƒ“ შეწევáƒ","outdent":"ნáƒáƒ™áƒšáƒ”ბáƒáƒ“ შეწევáƒ"},"fakeobjects":{"anchor":"ღუზáƒ","flash":"Flash áƒáƒœáƒ˜áƒ›áƒáƒªáƒ˜áƒ","hiddenfield":"მáƒáƒšáƒ£áƒšáƒ˜ ველი","iframe":"IFrame","unknown":"უცნáƒáƒ‘ი áƒáƒ‘იექტი"},"link":{"acccessKey":"წვდáƒáƒ›áƒ˜áƒ¡ ღილáƒáƒ™áƒ˜","advanced":"დáƒáƒ¬áƒ•áƒ áƒ˜áƒšáƒ”ბით","advisoryContentType":"შიგთáƒáƒ•áƒ¡áƒ˜áƒ¡ ტიპი","advisoryTitle":"სáƒáƒ—áƒáƒ£áƒ áƒ˜","anchor":{"toolbar":"ღუზáƒ","menu":"ღუზის რედáƒáƒ¥áƒ¢áƒ˜áƒ áƒ”ბáƒ","title":"ღუზის პáƒáƒ áƒáƒ›áƒ”ტრები","name":"ღუზუს სáƒáƒ®áƒ”ლი","errorName":"áƒáƒ™áƒ áƒ˜áƒ¤áƒ”თ ღუზის სáƒáƒ®áƒ”ლი","remove":"Remove Anchor"},"anchorId":"ელემენტის Id-თ","anchorName":"ღუზის სáƒáƒ®áƒ”ლით","charset":"კáƒáƒ“ირებáƒ","cssClasses":"CSS კლáƒáƒ¡áƒ˜","emailAddress":"ელფáƒáƒ¡áƒ¢áƒ˜áƒ¡ მისáƒáƒ›áƒáƒ áƒ—ები","emailBody":"წერილის ტექსტი","emailSubject":"წერილის სáƒáƒ—áƒáƒ£áƒ áƒ˜","id":"Id","info":"ბმულის ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ","langCode":"ენის კáƒáƒ“ი","langDir":"ენის მიმáƒáƒ áƒ—ულებáƒ","langDirLTR":"მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ• (LTR)","langDirRTL":"მáƒáƒ áƒ¯áƒ•áƒœáƒ˜áƒ“áƒáƒœ მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• (RTL)","menu":"ბმულის რედáƒáƒ¥áƒ¢áƒ˜áƒ áƒ”ბáƒ","name":"სáƒáƒ®áƒ”ლი","noAnchors":"(áƒáƒ› დáƒáƒ™áƒ£áƒ›áƒ”ნტში ღუზრáƒáƒ áƒáƒ)","noEmail":"áƒáƒ™áƒ áƒ˜áƒ¤áƒ”თ ელფáƒáƒ¡áƒ¢áƒ˜áƒ¡ მისáƒáƒ›áƒáƒ áƒ—ი","noUrl":"áƒáƒ™áƒ áƒ˜áƒ¤áƒ”თ ბმულის URL","other":"<სხვáƒ>","popupDependent":"დáƒáƒ›áƒáƒ™áƒ˜áƒ“ებული (Netscape)","popupFeatures":"Popup ფáƒáƒœáƒ¯áƒ áƒ˜áƒ¡ პáƒáƒ áƒáƒ›áƒ”ტრები","popupFullScreen":"მთელი ეკრáƒáƒœáƒ˜ (IE)","popupLeft":"მáƒáƒ áƒªáƒ®áƒ”ნრპáƒáƒ–იციáƒ","popupLocationBar":"ნáƒáƒ•áƒ˜áƒ’áƒáƒªáƒ˜áƒ˜áƒ¡ ზáƒáƒšáƒ˜","popupMenuBar":"მენიუს ზáƒáƒšáƒ˜","popupResizable":"ცვáƒáƒšáƒ”ბáƒáƒ“ი ზáƒáƒ›áƒ˜áƒ—","popupScrollBars":"გáƒáƒ“áƒáƒ®áƒ•áƒ”ვის ზáƒáƒšáƒ”ბი","popupStatusBar":"სტáƒáƒ¢áƒ£áƒ¡áƒ˜áƒ¡ ზáƒáƒšáƒ˜","popupToolbar":"ხელსáƒáƒ¬áƒ§áƒáƒ—რზáƒáƒšáƒ˜","popupTop":"ზედრპáƒáƒ–იციáƒ","rel":"კáƒáƒ•áƒ¨áƒ˜áƒ áƒ˜","selectAnchor":"áƒáƒ˜áƒ áƒ©áƒ˜áƒ”თ ღუზáƒ","styles":"CSS სტილი","tabIndex":"Tab-ის ინდექსი","target":"გáƒáƒ®áƒ¡áƒœáƒ˜áƒ¡ áƒáƒ“გილი","targetFrame":"<frame>","targetFrameName":"Frame-ის სáƒáƒ®áƒ”ლი","targetPopup":"<popup ფáƒáƒœáƒ¯áƒáƒ áƒ>","targetPopupName":"Popup ფáƒáƒœáƒ¯áƒ áƒ˜áƒ¡ სáƒáƒ®áƒ”ლი","title":"ბმული","toAnchor":"ბმული ტექსტში ღუზáƒáƒ–ე","toEmail":"ელფáƒáƒ¡áƒ¢áƒ","toUrl":"URL","toolbar":"ბმული","type":"ბმულის ტიპი","unlink":"ბმულის მáƒáƒ®áƒ¡áƒœáƒ","upload":"áƒáƒ¥áƒáƒ©áƒ•áƒ"},"list":{"bulletedlist":"ღილიáƒáƒœáƒ˜ სიáƒ","numberedlist":"გáƒáƒ“áƒáƒœáƒáƒ›áƒ áƒ˜áƒšáƒ˜ სიáƒ"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"გáƒáƒ“იდებáƒ","minimize":"დáƒáƒžáƒáƒ¢áƒáƒ áƒáƒ•áƒ”ბáƒ"},"pastetext":{"button":"მხáƒáƒšáƒáƒ“ ტექსტის ჩáƒáƒ¡áƒ›áƒ","title":"მხáƒáƒšáƒáƒ“ ტექსტის ჩáƒáƒ¡áƒ›áƒ"},"pastefromword":{"confirmCleanup":"ჩáƒáƒ¡áƒáƒ¡áƒ›áƒ”ლი ტექსტი ვáƒáƒ áƒ“იდáƒáƒœ გáƒáƒ“მáƒáƒ¢áƒáƒœáƒ˜áƒšáƒ¡ გáƒáƒ•áƒ¡ - გინდáƒáƒ— მისი წინáƒáƒ¡áƒ¬áƒáƒ  გáƒáƒ¬áƒ›áƒ”ნდáƒ?","error":"შიდრშეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ვერ მáƒáƒ®áƒ”რხდრტექსტის გáƒáƒ¬áƒ›áƒ”ნდáƒ","title":"ვáƒáƒ áƒ“იდáƒáƒœ ჩáƒáƒ¡áƒ›áƒ","toolbar":"ვáƒáƒ áƒ“იდáƒáƒœ ჩáƒáƒ¡áƒ›áƒ"},"removeformat":{"toolbar":"ფáƒáƒ áƒ›áƒáƒ¢áƒ˜áƒ áƒ”ბის მáƒáƒ®áƒ¡áƒœáƒ"},"sourcearea":{"toolbar":"კáƒáƒ“ები"},"specialchar":{"options":"სპეციáƒáƒšáƒ£áƒ áƒ˜ სიმბáƒáƒšáƒáƒ¡ პáƒáƒ áƒáƒ›áƒ”ტრები","title":"სპეციáƒáƒšáƒ£áƒ áƒ˜ სიმბáƒáƒšáƒáƒ¡ áƒáƒ áƒ©áƒ”ვáƒ","toolbar":"სპეციáƒáƒšáƒ£áƒ áƒ˜ სიმბáƒáƒšáƒáƒ¡ ჩáƒáƒ¡áƒ›áƒ"},"scayt":{"btn_about":"SCAYT-ის შესáƒáƒ®áƒ”ბ","btn_dictionaries":"ლექსიკáƒáƒœáƒ”ბი","btn_disable":"SCAYT-ის გáƒáƒ›áƒáƒ áƒ—ვáƒ","btn_enable":"SCAYT-ის ჩáƒáƒ áƒ—ვáƒ","btn_langs":"ენები","btn_options":"პáƒáƒ áƒáƒ›áƒ”ტრები","text_title":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბრკრეფისáƒáƒ¡"},"stylescombo":{"label":"სტილები","panelTitle":"ფáƒáƒ áƒ›áƒáƒ¢áƒ˜áƒ áƒ”ბის სტილები","panelTitle1":"áƒáƒ áƒ˜áƒ¡ სტილები","panelTitle2":"თáƒáƒœáƒ“áƒáƒ áƒ—ული სტილები","panelTitle3":"áƒáƒ‘იექტის სტილები"},"table":{"border":"ჩáƒáƒ áƒ©áƒáƒ¡ ზáƒáƒ›áƒ","caption":"სáƒáƒ—áƒáƒ£áƒ áƒ˜","cell":{"menu":"უჯრáƒ","insertBefore":"უჯრის ჩáƒáƒ¡áƒ›áƒ მáƒáƒœáƒáƒ›áƒ“ე","insertAfter":"უჯრის ჩáƒáƒ¡áƒ›áƒ მერე","deleteCell":"უჯრების წáƒáƒ¨áƒšáƒ","merge":"უჯრების შეერთებáƒ","mergeRight":"შეერთებრმáƒáƒ áƒ¯áƒ•áƒ”ნáƒáƒ¡áƒ—áƒáƒœ","mergeDown":"შეერთებრქვემáƒáƒ—áƒáƒ¡áƒ—áƒáƒœ","splitHorizontal":"გáƒáƒ§áƒáƒ¤áƒ ჰáƒáƒ áƒ˜áƒ–áƒáƒœáƒ¢áƒáƒšáƒ£áƒ áƒáƒ“","splitVertical":"გáƒáƒ§áƒáƒ¤áƒ ვერტიკáƒáƒšáƒ£áƒ áƒáƒ“","title":"უჯრის პáƒáƒ áƒáƒ›áƒ”ტრები","cellType":"უჯრის ტიპი","rowSpan":"სტრიქáƒáƒœáƒ”ბის áƒáƒ“ენáƒáƒ‘áƒ","colSpan":"სვეტების áƒáƒ“ენáƒáƒ‘áƒ","wordWrap":"სტრიქáƒáƒœáƒ˜áƒ¡ გáƒáƒ“áƒáƒ¢áƒáƒœáƒ (Word Wrap)","hAlign":"ჰáƒáƒ áƒ˜áƒ–áƒáƒœáƒ¢áƒáƒšáƒ£áƒ áƒ˜ სწáƒáƒ áƒ”ბáƒ","vAlign":"ვერტიკáƒáƒšáƒ£áƒ áƒ˜ სწáƒáƒ áƒ”ბáƒ","alignBaseline":"ძირითáƒáƒ“ი ხáƒáƒ–ის გáƒáƒ¡áƒ¬áƒ•áƒ áƒ˜áƒ•","bgColor":"ფáƒáƒœáƒ˜áƒ¡ ფერი","borderColor":"ჩáƒáƒ áƒ©áƒáƒ¡ ფერი","data":"მáƒáƒœáƒáƒªáƒ”მები","header":"სáƒáƒ—áƒáƒ£áƒ áƒ˜","yes":"დიáƒáƒ®","no":"áƒáƒ áƒ","invalidWidth":"უჯრის სიგáƒáƒœáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidHeight":"უჯრის სიმáƒáƒ¦áƒšáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidRowSpan":"სტრიქáƒáƒœáƒ”ბის რáƒáƒáƒ“ენáƒáƒ‘რმთელი რიცხვი უნდრიყáƒáƒ¡.","invalidColSpan":"სვეტების რáƒáƒáƒ“ენáƒáƒ‘რმთელი რიცხვი უნდრიყáƒáƒ¡.","chooseColor":"áƒáƒ áƒ©áƒ”ვáƒ"},"cellPad":"უჯრის კიდე (padding)","cellSpace":"უჯრის სივრცე (spacing)","column":{"menu":"სვეტი","insertBefore":"სვეტის ჩáƒáƒ›áƒáƒ¢áƒ”ბრწინ","insertAfter":"სვეტის ჩáƒáƒ›áƒáƒ¢áƒ”ბრმერე","deleteColumn":"სვეტების წáƒáƒ¨áƒšáƒ"},"columns":"სვეტი","deleteTable":"ცხრილის წáƒáƒ¨áƒšáƒ","headers":"სáƒáƒ—áƒáƒ£áƒ áƒ”ბი","headersBoth":"áƒáƒ áƒ˜áƒ•áƒ”","headersColumn":"პირველი სვეტი","headersNone":"áƒáƒ áƒáƒ¤áƒ”რი","headersRow":"პირველი სტრიქáƒáƒœáƒ˜","invalidBorder":"ჩáƒáƒ áƒ©áƒáƒ¡ ზáƒáƒ›áƒ რიცხვით უდნრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidCellPadding":"უჯრის კიდე (padding) რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidCellSpacing":"უჯრის სივრცე (spacing) რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidCols":"სვეტების რáƒáƒáƒ“ენáƒáƒ‘რდáƒáƒ“ებითი რიცხვი უნდრიყáƒáƒ¡.","invalidHeight":"ცხრილის სიმáƒáƒ¦áƒšáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","invalidRows":"სტრიქáƒáƒœáƒ”ბის რáƒáƒáƒ“ენáƒáƒ‘რდáƒáƒ“ებითი რიცხვი უნდრიყáƒáƒ¡.","invalidWidth":"ცხრილის სიგáƒáƒœáƒ” რიცხვით უნდრიყáƒáƒ¡ წáƒáƒ áƒ›áƒáƒ“გენილი.","menu":"ცხრილის პáƒáƒ áƒáƒ›áƒ”ტრები","row":{"menu":"სტრიქáƒáƒœáƒ˜","insertBefore":"სტრიქáƒáƒœáƒ˜áƒ¡ ჩáƒáƒ›áƒáƒ¢áƒ”ბრწინ","insertAfter":"სტრიქáƒáƒœáƒ˜áƒ¡ ჩáƒáƒ›áƒáƒ¢áƒ”ბრმერე","deleteRow":"სტრიქáƒáƒœáƒ”ბის წáƒáƒ¨áƒšáƒ"},"rows":"სტრიქáƒáƒœáƒ˜","summary":"შეჯáƒáƒ›áƒ”ბáƒ","title":"ცხრილის პáƒáƒ áƒáƒ›áƒ”ტრები","toolbar":"ცხრილი","widthPc":"პრáƒáƒªáƒ”ნტი","widthPx":"წერტილი","widthUnit":"სáƒáƒ–áƒáƒ›áƒ˜ ერთეული"},"undo":{"redo":"გáƒáƒ›áƒ”áƒáƒ áƒ”ბáƒ","undo":"გáƒáƒ£áƒ¥áƒ›áƒ”ბáƒ"},"wsc":{"btnIgnore":"უგულებელყáƒáƒ¤áƒ","btnIgnoreAll":"ყველáƒáƒ¡ უგულებელყáƒáƒ¤áƒ","btnReplace":"შეცვლáƒ","btnReplaceAll":"ყველáƒáƒ¡ შეცვლáƒ","btnUndo":"გáƒáƒ£áƒ¥áƒ›áƒ”ბáƒ","changeTo":"შეცვლელი","errorLoading":"სერვისის გáƒáƒ›áƒáƒ«áƒáƒ®áƒ”ბის შეცდáƒáƒ›áƒ: %s.","ieSpellDownload":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბრáƒáƒ áƒáƒ დáƒáƒ˜áƒœáƒ¡áƒ¢áƒáƒšáƒ˜áƒ áƒ”ბული. ჩáƒáƒ›áƒáƒ•áƒ¥áƒáƒ©áƒáƒ— ინტერნეტიდáƒáƒœ?","manyChanges":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბáƒ: %1 სიტყვრშეიცვáƒáƒšáƒ","noChanges":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბáƒ: áƒáƒ áƒáƒ¤áƒ”რი შეცვლილáƒ","noMispell":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბáƒ: შეცდáƒáƒ›áƒ áƒáƒ  მáƒáƒ˜áƒ«áƒ”ბნáƒ","noSuggestions":"- áƒáƒ áƒáƒ შემáƒáƒ—áƒáƒ•áƒáƒ–ებრ-","notAvailable":"უკáƒáƒªáƒ áƒáƒ•áƒáƒ“, ეს სერვისი áƒáƒ›áƒŸáƒáƒ›áƒáƒ“ მიუწვდáƒáƒ›áƒ”ლიáƒ.","notInDic":"áƒáƒ áƒáƒ ლექსიკáƒáƒœáƒ¨áƒ˜","oneChange":"მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბáƒ: ერთი სიტყვრშეიცვáƒáƒšáƒ","progress":"მიმდინáƒáƒ áƒ”áƒáƒ‘ს მáƒáƒ áƒ—ლწერის შემáƒáƒ¬áƒ›áƒ”ბáƒ...","title":"მáƒáƒ áƒ—ლწერáƒ","toolbar":"მáƒáƒ áƒ—ლწერáƒ"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/km.js b/js/ckeditor/lang/km.js
new file mode 100644
index 0000000..0bdffb5
--- /dev/null
+++ b/js/ckeditor/lang/km.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['km']={"editor":"ឧបករណáŸâ€‹ážŸážšážŸáŸážšâ€‹áž¢ážáŸ’ážáž”ទ​សម្បូរ​បែប","editorPanel":"ផ្ទាំង​ឧបករណáŸâ€‹ážŸážšážŸáŸážšâ€‹áž¢ážáŸ’ážáž”ទ​សម្បូរ​បែប","common":{"editorHelp":"ចុច ALT 0 សម្រាប់​ជំនួយ","browseServer":"រក​មើល​ក្នុង​ម៉ាស៊ីន​បម្រើ","url":"URL","protocol":"ពិធីការ","upload":"ផ្ទុក​ឡើង","uploadSubmit":"បញ្ជូនទៅកាន់ម៉ាស៊ីន​បម្រើ","image":"រូបភាព","flash":"Flash","form":"បែបបទ","checkbox":"ប្រអប់​ធីក","radio":"ប៊ូážáž»áž„​មូល","textField":"វាល​អážáŸ’ážáž”áž‘","textarea":"Textarea","hiddenField":"វាល​កំបាំង","button":"ប៊ូážáž»áž„","select":"វាល​ជម្រើស","imageButton":"ប៊ូážáž»áž„​រូបភាព","notSet":"<មិនកំណážáŸ‹>","id":"Id","name":"ឈ្មោះ","langDir":"ទិសដៅភាសា","langDirLtr":"ពីឆ្វáŸáž„ទៅស្ážáž¶áŸ† (LTR)","langDirRtl":"ពីស្ážáž¶áŸ†áž‘ៅឆ្វáŸáž„ (RTL)","langCode":"áž›áŸážâ€‹áž€áž¼ážŠâ€‹áž—ាសា","longDescr":"URL អធិប្បាយ​វែង","cssClass":"Stylesheet Classes","advisoryTitle":"ចំណង​ជើង​ណែនាំ","cssStyle":"រចនាបáž","ok":"ព្រម","cancel":"បោះបង់","close":"បិទ","preview":"មើល​ជា​មុន","resize":"ប្ដូរ​ទំហំ","generalTab":"ទូទៅ","advancedTab":"កម្រិážâ€‹ážáŸ’ពស់","validateNumberFailed":"ážáž˜áŸ’លៃ​នáŸáŸ‡â€‹áž–ុំ​មែន​ជា​លáŸážâ€‹áž‘áŸáŸ”","confirmNewPage":"រាល់​បន្លាស់​ប្ដូរ​នានា​ដែល​មិន​ទាន់​រក្សា​ទុក​ក្នុង​មាážáž·áž€áž¶â€‹áž“áŸáŸ‡ នឹង​ážáŸ’រូវ​បាážáŸ‹â€‹áž”ង់។ ážáž¾â€‹áž¢áŸ’នក​ពិážâ€‹áž‡áž¶â€‹áž…ង់​ផ្ទុក​ទំពáŸážšâ€‹ážáŸ’មី​មែនទáŸ?","confirmCancel":"ការ​កំណážáŸ‹â€‹áž˜áž½áž™â€‹áž…ំនួន​ážáŸ’រូ​វ​បាន​ផ្លាស់​ប្ដូរ។ ážáž¾â€‹áž¢áŸ’នក​ពិážâ€‹áž‡áž¶â€‹áž…ង់​បិទ​ប្រអប់​នáŸáŸ‡â€‹áž˜áŸ‚áž“áž‘áŸ?","options":"ការ​កំណážáŸ‹","target":"គោលដៅ","targetNew":"វីនដូ​ážáŸ’មី (_blank)","targetTop":"វីនដូ​លើ​គ០(_top)","targetSelf":"វីនដូ​ដូច​គ្នា (_self)","targetParent":"វីនដូ​ម០(_parent)","langDirLTR":"ពីឆ្វáŸáž„ទៅស្ážáž¶áŸ†(LTR)","langDirRTL":"ពីស្ážáž¶áŸ†áž‘ៅឆ្វáŸáž„(RTL)","styles":"រចនាបáž","cssClasses":"Stylesheet Classes","width":"ទទឹង","height":"កំពស់","align":"កំណážáŸ‹â€‹áž‘ីážáž¶áŸ†áž„","alignLeft":"ážáž¶áž„ឆ្វង","alignRight":"ážáž¶áž„ស្ážáž¶áŸ†","alignCenter":"កណ្ážáž¶áž›","alignJustify":"ážáŸ†ážšáž¹áž˜ážŸáž„ážáž¶áž„","alignTop":"ážáž¶áž„លើ","alignMiddle":"កណ្ážáž¶áž›","alignBottom":"ážáž¶áž„ក្រោម","alignNone":"គ្មាន","invalidValue":"ážáž˜áŸ’លៃ​មិន​ážáŸ’រឹម​ážáŸ’រូវ។","invalidHeight":"ážáž˜áŸ’លៃ​កំពស់​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","invalidWidth":"ážáž˜áŸ’លៃ​ទទឹង​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","invalidCssLength":"ážáž˜áŸ’លៃ​កំណážáŸ‹â€‹ážŸáž˜áŸ’រាប់​វាល \"%1\" ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹ážœáž·áž‡áŸ’ជមាន​ ដោយ​ភ្ជាប់ឬ​មិន​ភ្ជាប់​ជាមួយ​នឹង​ឯកážáž¶â€‹ážšáž„្វាស់​របស់ CSS (px, %, in, cm, mm, em, ex, pt ឬ pc) ។","invalidHtmlLength":"ážáž˜áŸ’លៃ​កំណážáŸ‹â€‹ážŸáž˜áŸ’រាប់​វាល \"%1\" ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹ážœáž·áž‡áŸ’ជមាន ដោយ​ភ្ជាប់​ឬ​មិន​ភ្ជាប់​ជាមួយ​នឹង​ឯកážáž¶â€‹ážšáž„្វាស់​របស់ HTML (px ឬ %) ។","invalidInlineStyle":"ážáž˜áŸ’លៃ​កំណážáŸ‹â€‹ážŸáž˜áŸ’រាប់​រចនាបážâ€‹áž€áŸ’នុង​ážáž½ ážáŸ’រូវ​ážáŸ‚​មាន​មួយ​ឬ​ធាážáž»â€‹áž…្រើន​ដោយ​មាន​ទ្រង់ទ្រាយ​ជា \"ឈ្មោះ : ážáž˜áŸ’លៃ\" ហើយ​ញែក​ចáŸáž‰â€‹áž–ី​គ្នា​ដោយ​ចុច​ក្បៀស។","cssLengthTooltip":"បញ្ចូល​លáŸážâ€‹ážŸáž˜áŸ’រាប់​ážáž˜áŸ’លៃ​ជា​ភិចសែល ឬ​លáŸážâ€‹ážŠáŸ‚ល​មាន​ឯកážáž¶â€‹ážáŸ’រឹមážáŸ’រូវ​របស់ CSS (px, %, in, cm, mm, em, ex, pt ឬ pc) ។","unavailable":"%1<span class=\"cke_accessibility\">, មិន​មាន</span>"},"about":{"copy":"រក្សាសិទ្ធិ &copy; $1។ រក្សា​សិទ្ធិ​គ្រប់​បែប​យ៉ាង។","dlgTitle":"អំពី CKEditor","help":"áž–áž·áž“áž·ážáŸ’áž™ $1 សម្រាប់​ជំនួយ។","moreInfo":"សម្រាប់​ពáŸážáŸŒáž˜áž¶áž“​អំពី​អាជ្ញាបណញណ សូម​មើល​ក្នុង​គáŸáž áž‘ំពáŸážšâ€‹ážšáž”ស់​យើង៖","title":"អំពី CKEditor","userGuide":"វិធី​ប្រើ​ប្រាស់ CKEditor"},"basicstyles":{"bold":"ដិáž","italic":"ទ្រáŸáž","strike":"គូស​បន្ទាážáŸ‹â€‹áž…ំ​កណ្ដាល","subscript":"អក្សរážáž¼áž…ក្រោម","superscript":"អក្សរážáž¼áž…លើ","underline":"គូស​បន្ទាážáŸ‹â€‹áž€áŸ’រោម"},"blockquote":{"toolbar":"ប្លក់​ពាក្យ​សម្រង់"},"clipboard":{"copy":"ចម្លង","copyError":"ការកំណážáŸ‹ážŸáž»ážœážáŸ’ážáž—ាពរបស់កម្មវិធីរុករករបស់លោកអ្នក áž“áŸáŸ‡â€‹áž˜áž·áž“អាចធ្វើកម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘ ចំលងអážáŸ’ážáž”ទយកដោយស្វáŸáž™áž”្រវážáŸ’ážáž”ានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនáŸáŸ‡ (Ctrl/Cmd+C)។","cut":"កាážáŸ‹áž™áž€","cutError":"ការកំណážáŸ‹ážŸáž»ážœážáŸ’ážáž—ាពរបស់កម្មវិធីរុករករបស់លោកអ្នក áž“áŸáŸ‡â€‹áž˜áž·áž“អាចធ្វើកម្មវិធីážáž¶áž€áŸ‹ážáŸ‚ងអážáŸ’ážáž”áž‘ កាážáŸ‹áž¢ážáŸ’ážáž”ទយកដោយស្វáŸáž™áž”្រវážáŸ’ážáž”ានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនáŸáŸ‡ (Ctrl/Cmd+X) ។","paste":"បិទ​ភ្ជាប់","pasteArea":"ážáŸ†áž”ន់​បិទ​ភ្ជាប់","pasteMsg":"សូមចំលងអážáŸ’ážáž”ទទៅដាក់ក្នុងប្រអប់ដូចážáž¶áž„ក្រោមដោយប្រើប្រាស់ ឃី ​(<STRONG>Ctrl/Cmd+V</STRONG>) ហើយចុច <STRONG>OK</STRONG> ។","securityMsg":"ព្រោះážáŸ‚​ការកំណážáŸ‹â€‹ážŸáž»ážœážáŸ’ážáž·áž—ាព ប្រអប់សរសáŸážšâ€‹áž˜áž·áž“​អាចចាប់​យកទិន្ននáŸáž™áž–ីក្ážáž¶ážšážáž˜áŸ’បៀážážáŸ’ទាស់​អ្នក​​ដោយផ្ទាល់​បានទáŸáŸ” អ្នក​ážáŸ’រូវចំលង​ដាក់វាម្ážáž„​ទៀហក្នុងផ្ទាំងនáŸáŸ‡áŸ”","title":"បិទ​ភ្ជាប់"},"contextmenu":{"options":"ជម្រើស​ម៉ឺនុយ​បរិបទ"},"button":{"selectedLabel":"%1 (បាន​ជ្រើស​រើស)"},"toolbar":{"toolbarCollapse":"បង្រួម​របារ​ឧបករណáŸ","toolbarExpand":"ពង្រីក​របារ​ឧបករណáŸ","toolbarGroups":{"document":"ឯកសារ","clipboard":"Clipboard/មិន​ធ្វើ​វិញ","editing":"ការ​កែ​សម្រួល","forms":"បែបបទ","basicstyles":"រចនាបážâ€‹áž˜áž¼áž›ážŠáŸ’ឋាន","paragraph":"កážáž¶ážážŽáŸ’ឌ","links":"ážáŸ†ážŽ","insert":"បញ្ចូល","styles":"រចនាបáž","colors":"ពណ៌","tools":"ឧបករណáŸ"},"toolbars":"របារ​ឧបករណáŸâ€‹áž€áŸ‚​សម្រួល"},"elementspath":{"eleLabel":"ទីážáž¶áŸ†áž„​ធាážáž»","eleTitle":"ធាážáž» %1"},"format":{"label":"ទម្រង់","panelTitle":"ទម្រង់​កážáž¶ážážŽáŸ’ឌ","tag_address":"អាសយដ្ឋាន","tag_div":"ធម្មážáž¶ (DIV)","tag_h1":"ចំណង​ជើង 1","tag_h2":"ចំណង​ជើង 2","tag_h3":"ចំណង​ជើង 3","tag_h4":"ចំណង​ជើង 4","tag_h5":"ចំណង​ជើង 5","tag_h6":"ចំណង​ជើង 6","tag_p":"ធម្មážáž¶","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"បន្ážáŸ‚មបន្ទាážáŸ‹áž•áŸ’ážáŸáž€"},"image":{"alertUrl":"សូម​បញ្ចូល URL រូបភាព","alt":"អážáŸ’ážáž”ទជំនួស","border":"ស៊ុម","btnUpload":"ផ្ញើ​ទៅ​ម៉ាស៊ីន​បម្រើ","button2Img":"ážáž¾â€‹áž¢áŸ’នក​ចង់​ផ្លាស់​ប្ដូរ​ប៊ូážáž»áž„​រូបភាព​ដែល​បាន​ជ្រើស នៅ​លើ​រូបភាព​ធម្មážáž¶â€‹áž˜áž½áž™â€‹áž˜áŸ‚áž“áž‘áŸ?","hSpace":"គម្លាážâ€‹áž•áŸ’ដáŸáž€","img2Button":"ážáž¾â€‹áž¢áŸ’នក​ចង់​ផ្លាស់​ប្ដូរ​រូបភាព​ដែល​បាន​ជ្រើស នៅ​លើ​ប៊ូážáž»áž„​រូបភាព​មែនទáŸ?","infoTab":"áž–ážáŸŒáž˜áž¶áž“អំពីរូបភាព","linkTab":"ážáŸ†ážŽ","lockRatio":"ចាក់​សោ​ផល​ធៀប","menu":"លក្ážážŽáŸˆâ€‹ážšáž¼áž”ភាព","resetSize":"កំណážáŸ‹áž‘ំហំឡើងវិញ","title":"លក្ážážŽáŸˆâ€‹ážšáž¼áž”ភាព","titleButton":"លក្ážážŽáŸˆâ€‹áž”៊ូážáž»áž„​រូបភាព","upload":"ផ្ទុកឡើង","urlMissing":"ážáŸ’វះ URL ប្រភព​រូប​ភាព។","vSpace":"គម្លាážâ€‹áž”ញ្ឈរ","validateBorder":"ស៊ុម​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","validateHSpace":"គម្លាážâ€‹áž•áŸ’ដáŸáž€â€‹ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","validateVSpace":"គម្លាážâ€‹áž”ញ្ឈរ​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”"},"indent":{"indent":"បន្ážáŸ‚មការចូលបន្ទាážáŸ‹","outdent":"បន្ážáž™áž€áž¶ážšáž…ូលបន្ទាážáŸ‹"},"fakeobjects":{"anchor":"យុážáŸ’កា","flash":"Flash មាន​ចលនា","hiddenfield":"វាល​កំបាំង","iframe":"IFrame","unknown":"ážœážáŸ’ážáž»â€‹áž˜áž·áž“​ស្គាល់"},"link":{"acccessKey":"សោរ​ចូល","advanced":"កម្រិážâ€‹ážáŸ’ពស់","advisoryContentType":"ប្រភáŸáž‘អážáŸ’ážáž”ទ​ប្រឹក្សា","advisoryTitle":"ចំណងជើង​ប្រឹក្សា","anchor":{"toolbar":"យុážáŸ’កា","menu":"កែ​យុážáŸ’កា","title":"លក្ážážŽáŸˆâ€‹áž™áž»ážáŸ’កា","name":"ឈ្មោះ​យុážáŸ’កា","errorName":"សូម​បញ្ចូល​ឈ្មោះ​យុážáŸ’កា","remove":"ដក​យុážáŸ’កា​ចáŸáž‰"},"anchorId":"ážáž¶áž˜ ID ធាážáž»","anchorName":"ážáž¶áž˜â€‹ážˆáŸ’មោះ​យុážáŸ’កា","charset":"áž›áŸážáž€áž¼ážáž¢áž€áŸ’សររបស់ឈ្នាប់","cssClasses":"Stylesheet Classes","emailAddress":"អាសយដ្ឋាន​អ៊ីមែល","emailBody":"ážáž½â€‹áž¢ážáŸ’ážáž”áž‘","emailSubject":"ប្រធានបទ​សារ","id":"Id","info":"áž–áŸážáŸŒáž˜áž¶áž“​ពី​ážáŸ†ážŽ","langCode":"កូដ​ភាសា","langDir":"ទិសដៅភាសា","langDirLTR":"ពីឆ្វáŸáž„ទៅស្ážáž¶áŸ†(LTR)","langDirRTL":"ពីស្ážáž¶áŸ†áž‘ៅឆ្វáŸáž„(RTL)","menu":"កែ​ážáŸ†ážŽ","name":"ឈ្មោះ","noAnchors":"(មិន​មាន​យុážáŸ’កា​នៅ​ក្នុង​ឯកសារ​អážáŸ’ážážáž”ទ​ទáŸ)","noEmail":"សូម​បញ្ចូល​អាសយដ្ឋាន​អ៊ីមែល","noUrl":"សូម​បញ្ចូល​ážáŸ†ážŽ URL","other":"<ផ្សáŸáž„​ទៀáž>","popupDependent":"Dependent (Netscape)","popupFeatures":"មុážâ€‹áž„ារ​ផុស​ផ្ទាំង​វីនដូ​ឡើង","popupFullScreen":"áž–áŸáž‰â€‹áž¢áŸáž€áŸ’រង់ (IE)","popupLeft":"ទីážáž¶áŸ†áž„ážáž¶áž„ឆ្វáŸáž„","popupLocationBar":"របារ​ទីážáž¶áŸ†áž„","popupMenuBar":"របារ​ម៉ឺនុយ","popupResizable":"អាច​ប្ដូរ​ទំហំ","popupScrollBars":"របារ​រំកិល","popupStatusBar":"របារ​ស្ážáž¶áž“ភាព","popupToolbar":"របារ​ឧបករណáŸ","popupTop":"ទីážáž¶áŸ†áž„​កំពូល","rel":"សម្ពន្ធ​ភាព","selectAnchor":"រើស​យក​យុážáŸ’កា​មួយ","styles":"ស្ទីល","tabIndex":"áž›áŸáž Tab","target":"គោលដៅ","targetFrame":"<ស៊ុម>","targetFrameName":"ឈ្មោះ​ស៊ុម​ជា​គោល​ដៅ","targetPopup":"<វីនដូ​ផុស​ឡើង>","targetPopupName":"ឈ្មោះ​វីនដូážâ€‹áž•áž»ážŸâ€‹áž¡áž¾áž„","title":"ážáŸ†ážŽ","toAnchor":"ážâ€‹áž—្ជាប់​ទៅ​យុážáŸ’កា​ក្នុង​អážáŸ’ážáž”áž‘","toEmail":"អ៊ីមែល","toUrl":"URL","toolbar":"ážáŸ†ážŽ","type":"ប្រភáŸáž‘​ážáŸ†ážŽ","unlink":"ផ្ដាច់​ážáŸ†ážŽ","upload":"ផ្ទុក​ឡើង"},"list":{"bulletedlist":"បញ្ចូល / លុប​បញ្ជី​ជា​ចំណុច​មូល","numberedlist":"បញ្ចូល / លុប​បញ្ជី​ជា​លáŸáž"},"magicline":{"title":"បញ្ចូល​កážáž¶ážážŽáŸ’ឌ​នៅ​ទីនáŸáŸ‡"},"maximize":{"maximize":"ពង្រីក​អážáž·áž”រមា","minimize":"បង្រួម​អប្បបរមា"},"pastetext":{"button":"បិទ​ភ្ជាប់​ជា​អážáŸ’ážáž”ទ​ធម្មážáž¶","title":"បិទ​ភ្ជាប់​ជា​អážáŸ’ážáž”ទ​ធម្មážáž¶"},"pastefromword":{"confirmCleanup":"អážáŸ’ážáž”ទ​ដែល​អ្នក​ចង់​បិទ​ភ្ជាប់​នáŸáŸ‡ ទំនង​ដូច​ជា​ចម្លង​មក​ពី Word។ ážáž¾â€‹áž¢áŸ’នក​ចង់​សម្អាážâ€‹ážœáž¶â€‹áž˜áž»áž“​បិទ​ភ្ជាប់​ទáŸ?","error":"ដោយ​សារ​មាន​បញ្ហា​ផ្នែក​ក្នុង​ធ្វើ​ឲ្យ​មិន​អាច​សម្អាážâ€‹áž‘ិន្ននáŸáž™â€‹ážŠáŸ‚ល​បាន​បិទ​ភ្ជាប់","title":"បិទ​ភ្ជាប់​ពី Word","toolbar":"បិទ​ភ្ជាប់​ពី Word"},"removeformat":{"toolbar":"ជម្រះ​ទ្រង់​ទ្រាយ"},"sourcearea":{"toolbar":"អក្សរ​កូដ"},"specialchar":{"options":"ជម្រើស​ážáž½â€‹áž¢áž€áŸ’សរ​ពិសáŸážŸ","title":"រើស​ážáž½áž¢áž€áŸ’សរ​ពិសáŸážŸ","toolbar":"បន្ážáŸ‚មអក្សរពិសáŸážŸ"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"រចនាបáž","panelTitle":"ទ្រង់ទ្រាយ​រចនាបáž","panelTitle1":"រចនាបážâ€‹áž”្លក់","panelTitle2":"រចនាបážâ€‹áž€áŸ’នុង​ជួរ","panelTitle3":"រចនាបážâ€‹ážœážáŸ’ážáž»"},"table":{"border":"ទំហំ​បន្ទាážáŸ‹â€‹ážŸáŸŠáž»áž˜","caption":"ចំណងជើង","cell":{"menu":"ក្រឡា","insertBefore":"បញ្ចូល​ក្រឡា​ពីមុáž","insertAfter":"បញ្ចូល​ក្រឡា​ពី​ក្រោយ","deleteCell":"លុប​ក្រឡា","merge":"បញ្ចូល​ក្រឡា​ចូល​គ្នា","mergeRight":"បញ្ចូល​គ្នា​ážáž¶áž„​ស្ដាំ","mergeDown":"បញ្ចូល​គ្នា​ចុះ​ក្រោម","splitHorizontal":"ពុះ​ក្រឡា​ផ្ដáŸáž€","splitVertical":"ពុះ​ក្រឡា​បញ្ឈរ","title":"លក្ážážŽáŸˆâ€‹áž€áŸ’រឡា","cellType":"ប្រភáŸáž‘​ក្រឡា","rowSpan":"ចំនួន​ជួរ​ដáŸáž€â€‹áž›áž¶áž™â€‹áž…ូល​គ្នា","colSpan":"ចំនួន​ជួរ​ឈរ​លាយ​ចូល​គ្នា","wordWrap":"រុំ​ពាក្យ","hAlign":"ការ​ážáž˜áŸ’រឹម​ផ្ដáŸáž€","vAlign":"ការ​ážáž˜áŸ’រឹម​បញ្ឈរ","alignBaseline":"ážáŸ’សែ​បន្ទាážáŸ‹â€‹áž‚ោល","bgColor":"ពណ៌​ផ្ទៃ​ក្រោយ","borderColor":"ពណ៌​បន្ទាážáŸ‹â€‹ážŸáŸŠáž»áž˜","data":"ទិន្ននáŸáž™","header":"ក្បាល","yes":"ព្រម","no":"áž‘áŸ","invalidWidth":"ទទឹង​ក្រឡា​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","invalidHeight":"កម្ពស់​ក្រឡា​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","invalidRowSpan":"ចំនួន​ជួរ​ដáŸáž€â€‹áž›áž¶áž™â€‹áž…ូល​គ្នា​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹áž‘ាំង​អស់។","invalidColSpan":"ចំនួន​ជួរ​ឈរ​លាយ​ចូល​គ្នា​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹áž‘ាំង​អស់។","chooseColor":"រើស"},"cellPad":"ចន្លោះ​ក្រឡា","cellSpace":"គម្លាážâ€‹áž€áŸ’រឡា","column":{"menu":"ជួរ​ឈរ","insertBefore":"បញ្ចូល​ជួរ​ឈរ​ពីមុáž","insertAfter":"បញ្ចូល​ជួរ​ឈរ​ពី​ក្រោយ","deleteColumn":"លុប​ជួរ​ឈរ"},"columns":"ជួរឈរ","deleteTable":"លុប​ážáž¶ážšáž¶áž„","headers":"ក្បាល","headersBoth":"ទាំង​ពីរ","headersColumn":"ជួរ​ឈរ​ដំបូង","headersNone":"មិន​មាន","headersRow":"ជួរ​ដáŸáž€â€‹ážŠáŸ†áž”ូង","invalidBorder":"ទំហំ​បន្ទាážáŸ‹â€‹ážŸáŸŠáž»áž˜â€‹ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","invalidCellPadding":"ចន្លោះ​ក្រឡា​ážáŸ’រូវ​ážáŸ‚ជា​លáŸážâ€‹ážœáž·áž‡áŸ’ជមាន។","invalidCellSpacing":"គម្លាážâ€‹áž€áŸ’រឡា​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹ážœáž·áž‡áŸ’ជមាន។","invalidCols":"ចំនួន​ជួរ​ឈរ​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹áž’ំ​ជាង 0។","invalidHeight":"កម្ពស់​ážáž¶ážšáž¶áž„​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸáž","invalidRows":"ចំនួន​ជួរ​ដáŸáž€â€‹ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážâ€‹áž’ំ​ជាង 0។","invalidWidth":"ទទឹង​ážáž¶ážšáž¶áž„​ážáŸ’រូវ​ážáŸ‚​ជា​លáŸážáŸ”","menu":"លក្ážážŽáŸˆâ€‹ážáž¶ážšáž¶áž„","row":{"menu":"ជួរ​ដáŸáž€","insertBefore":"បញ្ចូល​ជួរ​ដáŸáž€â€‹áž–ីមុáž","insertAfter":"បញ្ចូល​ជួរ​ដáŸáž€â€‹áž–ី​ក្រោយ","deleteRow":"លុប​ជួរ​ដáŸáž€"},"rows":"ជួរ​ដáŸáž€","summary":"សáŸáž…ក្ážáž¸â€‹ážŸáž„្ážáŸáž”","title":"លក្ážážŽáŸˆâ€‹ážáž¶ážšáž¶áž„","toolbar":"ážáž¶ážšáž¶áž„","widthPc":"ភាគរយ","widthPx":"ភីកសែល","widthUnit":"ឯកážáž¶â€‹áž‘ទឹង"},"undo":{"redo":"ធ្វើ​ឡើង​វិញ","undo":"មិន​ធ្វើ​វិញ"},"wsc":{"btnIgnore":"មិនផ្លាស់ប្ážáž¼ážš","btnIgnoreAll":"មិនផ្លាស់ប្ážáž¼ážš ទាំងអស់","btnReplace":"ជំនួស","btnReplaceAll":"ជំនួសទាំងអស់","btnUndo":"សារឡើងវិញ","changeTo":"ផ្លាស់ប្ážáž¼ážšáž‘ៅ","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"ពុំមានកម្មវិធីពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ ។ ážáž¾áž…ង់ទាញយកពីណា?","manyChanges":"ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: %1 ពាក្យបានផ្លាស់ប្ážáž¼ážš","noChanges":"ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: ពុំមានផ្លាស់ប្ážáž¼ážš","noMispell":"ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: គ្មានកំហុស","noSuggestions":"- គ្មានសំណើរ -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"គ្មានក្នុងវចនានុក្រម","oneChange":"ការពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធបានចប់: ពាក្យមួយážáŸ’រូចបានផ្លាស់ប្ážáž¼ážš","progress":"កំពុងពិនិážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ...","title":"Spell Checker","toolbar":"áž–áž·áž“áž·ážáŸ’យអក្ážážšáž¶ážœáž·ážšáž»áž‘្ធ"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ko.js b/js/ckeditor/lang/ko.js
new file mode 100644
index 0000000..509d9bf
--- /dev/null
+++ b/js/ckeditor/lang/ko.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ko']={"editor":"리치 í…스트 편집기","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"ë„ì›€ì´ í•„ìš”í•˜ì‹œë©´ ALT 0 ì„ ëˆ„ë¥´ì„¸ìš”","browseServer":"서버 보기","url":"URL","protocol":"프로토콜","upload":"업로드","uploadSubmit":"서버로 전송","image":"ì´ë¯¸ì§€","flash":"플래쉬","form":"í¼","checkbox":"ì²´í¬ë°•ìŠ¤","radio":"ë¼ë””오버튼","textField":"입력필드","textarea":"ìž…ë ¥ì˜ì—­","hiddenField":"숨김필드","button":"버튼","select":"펼침목ë¡","imageButton":"ì´ë¯¸ì§€ë²„튼","notSet":"<설정ë˜ì§€ ì•ŠìŒ>","id":"ID","name":"Name","langDir":"쓰기 ë°©í–¥","langDirLtr":"왼쪽ì—ì„œ 오른쪽 (LTR)","langDirRtl":"오른쪽ì—ì„œ 왼쪽 (RTL)","langCode":"언어 코드","longDescr":"URL 설명","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"예","cancel":"아니오","close":"닫기","preview":"미리보기","resize":"í¬ê¸° ì¡°ì ˆ","generalTab":"ì¼ë°˜","advancedTab":"ìžì„¸ížˆ","validateNumberFailed":"ì´ ê°’ì€ ìˆ«ìžê°€ 아닙니다.","confirmNewPage":"저장하지 ì•Šì€ ëª¨ë“  ë³€ê²½ì‚¬í•­ì€ ìœ ì‹¤ë©ë‹ˆë‹¤. ì •ë§ë¡œ 새로운 페ì´ì§€ë¥¼ 부르겠습니까?","confirmCancel":"ëª‡ëª‡ê°œì˜ ì˜µì…˜ì´ ë°”ê¼ˆìŠµë‹ˆë‹¤. ì •ë§ë¡œ ì°½ì„ ë‹«ìœ¼ì‹œê² ìŠµë‹ˆê¹Œ?","options":"옵션","target":"타겟","targetNew":"새로운 ì°½ (_blank)","targetTop":"최ìƒìœ„ ì°½ (_top)","targetSelf":"ê°™ì€ ì°½ (_self)","targetParent":"부모 ì°½ (_parent)","langDirLTR":"왼쪽ì—ì„œ 오른쪽 (LTR)","langDirRTL":"오른쪽ì—ì„œ 왼쪽 (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"너비","height":"높ì´","align":"ì •ë ¬","alignLeft":"왼쪽","alignRight":"오른쪽","alignCenter":"가운ë°","alignJustify":"両端æƒãˆ","alignTop":"위","alignMiddle":"중간","alignBottom":"아래","alignNone":"None","invalidValue":"ìž˜ëª»ëœ ê°’.","invalidHeight":"높ì´ëŠ” 숫ìžì—¬ì•¼ 합니다.","invalidWidth":"ë„“ì´ëŠ” 숫ìžì—¬ì•¼ 합니다.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, 사용할 수 ì—†ìŒ</span>"},"about":{"copy":"저작권 &copy; $1 . íŒê¶Œ 소유.","dlgTitle":"CKEditor ì— ëŒ€í•˜ì—¬","help":"ë„ì›€ì´ í•„ìš”í•˜ì‹œë©´ $1 를 확ì¸í•˜ì„¸ìš”","moreInfo":"ë¼ì´ì„¼ìŠ¤ì— 대한 정보를 보고싶다면 ìš°ë¦¬ì˜ ì›¹ 사ì´íŠ¸ë¥¼ 방문하세요:","title":"CKEditorì— ëŒ€í•˜ì—¬","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"진하게","italic":"ì´í…”릭","strike":"취소선","subscript":"아래 첨ìž","superscript":"위 첨ìž","underline":"밑줄"},"blockquote":{"toolbar":"ì¸ìš© 블ë¡"},"clipboard":{"copy":"복사하기","copyError":"브ë¼ìš°ì €ì˜ ë³´ì•ˆì„¤ì •ë•Œë¬¸ì— ë³µì‚¬í•˜ê¸° ê¸°ëŠ¥ì„ ì‹¤í–‰í•  수 없습니다. 키보드 ëª…ë ¹ì„ ì‚¬ìš©í•˜ì‹­ì‹œìš”. (Ctrl/Cmd+C).","cut":"잘ë¼ë‚´ê¸°","cutError":"브ë¼ìš°ì €ì˜ ë³´ì•ˆì„¤ì •ë•Œë¬¸ì— ìž˜ë¼ë‚´ê¸° ê¸°ëŠ¥ì„ ì‹¤í–‰í•  수 없습니다. 키보드 ëª…ë ¹ì„ ì‚¬ìš©í•˜ì‹­ì‹œìš”. (Ctrl/Cmd+X).","paste":"붙여넣기","pasteArea":"범위 붙여넣기","pasteMsg":"í‚¤ë³´ë“œì˜ (<STRONG>Ctrl/Cmd+V</STRONG>) 를 ì´ìš©í•´ì„œ ìƒìžì•ˆì— 붙여넣고 <STRONG>OK</STRONG> 를 누르세요.","securityMsg":"브러우저 보안 설정으로 ì¸í•´, í´ë¦½ë³´ë“œì˜ ìžë£Œë¥¼ ì§ì ‘ 접근할 수 없습니다. ì´ ì°½ì— ë‹¤ì‹œ 붙여넣기 하십시오.","title":"붙여넣기"},"contextmenu":{"options":"컨í…스트 메뉴 옵션"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"툴바 ì‚­ì œ","toolbarExpand":"확장 툴바","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"편집ìžìš© 툴바들"},"elementspath":{"eleLabel":"요소 위치","eleTitle":"%1 요소"},"format":{"label":"í¬ë§·","panelTitle":"í¬ë§·","tag_address":"Address","tag_div":"기본 (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"수í‰ì„  삽입"},"image":{"alertUrl":"ì´ë¯¸ì§€ URLì„ ìž…ë ¥í•˜ì‹­ì‹œìš”","alt":"ì´ë¯¸ì§€ 설명","border":"í…Œë‘리","btnUpload":"서버로 전송","button2Img":"단순 ì´ë¯¸ì§€ì—ì„œ ì„ íƒí•œ ì´ë¯¸ì§€ ë²„íŠ¼ì„ ë³€í™˜í•˜ì‹œê² ìŠµë‹ˆê¹Œ?","hSpace":"수í‰ì—¬ë°±","img2Button":"ì´ë¯¸ì§€ ë²„íŠ¼ì— ì„ íƒí•œ ì´ë¯¸ì§€ë¥¼ 변환하시겠습니까?","infoTab":"ì´ë¯¸ì§€ ì •ë³´","linkTab":"ë§í¬","lockRatio":"비율 유지","menu":"ì´ë¯¸ì§€ 설정","resetSize":"ì›ëž˜ í¬ê¸°ë¡œ","title":"ì´ë¯¸ì§€ 설정","titleButton":"ì´ë¯¸ì§€ë²„튼 ì†ì„±","upload":"업로드","urlMissing":"ì´ë¯¸ì§€ 소스 URLì´ ì—†ìŠµë‹ˆë‹¤.","vSpace":"수ì§ì—¬ë°±","validateBorder":"í…Œë‘리는 정수여야 합니다.","validateHSpace":"가로 길ì´ëŠ” 정수여야 합니다.","validateVSpace":"세로 길ì´ëŠ” 정수여야 합니다."},"indent":{"indent":"들여쓰기","outdent":"내어쓰기"},"fakeobjects":{"anchor":"책갈피 삽입/변경","flash":"Flash Animation","hiddenfield":"숨김필드","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"엑세스 키","advanced":"ìžì„¸ížˆ","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"책갈피 삽입/변경","menu":"책갈피 ì†ì„±","title":"책갈피 ì†ì„±","name":"책갈피 ì´ë¦„","errorName":"책갈피 ì´ë¦„ì„ ìž…ë ¥í•˜ì‹­ì‹œìš”.","remove":"Remove Anchor"},"anchorId":"책갈피 ID","anchorName":"책갈피 ì´ë¦„","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"ì´ë©”ì¼ ì£¼ì†Œ","emailBody":"ë‚´ìš©","emailSubject":"제목","id":"ID","info":"ë§í¬ ì •ë³´","langCode":"쓰기 ë°©í–¥","langDir":"쓰기 ë°©í–¥","langDirLTR":"왼쪽ì—ì„œ 오른쪽 (LTR)","langDirRTL":"오른쪽ì—ì„œ 왼쪽 (RTL)","menu":"ë§í¬ 수정","name":"Name","noAnchors":"(ë¬¸ì„œì— ì±…ê°ˆí”¼ê°€ 없습니다.)","noEmail":"ì´ë©”ì¼ì£¼ì†Œë¥¼ 입력하십시요.","noUrl":"ë§í¬ URLì„ ìž…ë ¥í•˜ì‹­ì‹œìš”.","other":"<기타>","popupDependent":"Dependent (Netscape)","popupFeatures":"íŒì—…ì°½ 설정","popupFullScreen":"전체화면 (IE)","popupLeft":"왼쪽 위치","popupLocationBar":"주소표시줄","popupMenuBar":"메뉴바","popupResizable":"í¬ê¸° ì¡°ì ˆ 가능","popupScrollBars":"스í¬ë¡¤ë°”","popupStatusBar":"ìƒíƒœë°”","popupToolbar":"툴바","popupTop":"윗쪽 위치","rel":"관계","selectAnchor":"책갈피 ì„ íƒ","styles":"Style","tabIndex":"탭 순서","target":"타겟","targetFrame":"<프레임>","targetFrameName":"타겟 프레임 ì´ë¦„","targetPopup":"<íŒì—…ì°½>","targetPopupName":"íŒì—…ì°½ ì´ë¦„","title":"ë§í¬","toAnchor":"책갈피","toEmail":"ì´ë©”ì¼","toUrl":"URL","toolbar":"ë§í¬ 삽입/변경","type":"ë§í¬ 종류","unlink":"ë§í¬ ì‚­ì œ","upload":"업로드"},"list":{"bulletedlist":"순서없는 목ë¡","numberedlist":"순서있는 목ë¡"},"magicline":{"title":"ì—¬ê¸°ì— ê·¸ëž˜í”„ 삽입"},"maximize":{"maximize":"최대","minimize":"최소"},"pastetext":{"button":"í…스트로 붙여넣기","title":"í…스트로 붙여넣기"},"pastefromword":{"confirmCleanup":"붙여 넣기 í•  í…스트는 MS Wordì—ì„œ 복사 í•œ 것입니다. 붙여 넣기 ì „ì— MS Word í¬ë©§ì„ ì‚­ì œ 하시겠습니까?","error":"내부 오류로 붙여 ë„£ì€ ë°ì´í„°ë¥¼ 정리 í•  수 없습니다.","title":"MS Word 형ì‹ì—ì„œ 붙여넣기","toolbar":"MS Word 형ì‹ì—ì„œ 붙여넣기"},"removeformat":{"toolbar":"í¬ë§· 지우기"},"sourcearea":{"toolbar":"소스"},"specialchar":{"options":"íŠ¹ìˆ˜ë¬¸ìž ì˜µì…˜","title":"íŠ¹ìˆ˜ë¬¸ìž ì„ íƒ","toolbar":"íŠ¹ìˆ˜ë¬¸ìž ì‚½ìž…"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"스타ì¼","panelTitle":"ì „ì²´ 구성 스타ì¼","panelTitle1":"ë¸”ë¡ ìŠ¤íƒ€ì¼","panelTitle2":"ì¸ë¼ì¸ 스타ì¼","panelTitle3":"오브ì íŠ¸ 스타ì¼"},"table":{"border":"í…Œë‘리 í¬ê¸°","caption":"캡션","cell":{"menu":"ì…€/칸(Cell)","insertBefore":"ì•žì— ì…€/칸 삽입","insertAfter":"ë’¤ì— ì…€/칸 삽입","deleteCell":"ì…€ ì‚­ì œ","merge":"ì…€ 합치기","mergeRight":"오른쪽 뭉치기","mergeDown":"왼쪽 뭉치기","splitHorizontal":"ìˆ˜í‰ ë‚˜ëˆ„ê¸°","splitVertical":"ìˆ˜ì§ ë‚˜ëˆ„ê¸°","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"ì…€ 여백","cellSpace":"ì…€ 간격","column":{"menu":"ì—´(Column)","insertBefore":"ì•žì— ì—´ 삽입","insertAfter":"ë’¤ì— ì—´ 삽입","deleteColumn":"세로줄 ì‚­ì œ"},"columns":"세로줄","deleteTable":"í‘œ ì‚­ì œ","headers":"í•´ë”","headersBoth":"모ë‘","headersColumn":"첫 ì—´","headersNone":"None","headersRow":"첫 í–‰","invalidBorder":"í…Œë‘리 í¬ê¸°ëŠ” 숫ìžì—¬ì•¼ 합니다.","invalidCellPadding":"ì…€ ì•ˆìª½ì˜ ì—¬ë°±ì€ 0 ì´ìƒì´ì–´ì•¼ 합니다.","invalidCellSpacing":"ì…€ ê°„ê²©ì€ 0 ì´ìƒì´ì–´ì•¼ 합니다.","invalidCols":"í–‰ 번호는 0보다 í° ìˆ«ìžì—¬ì•¼ 합니다.","invalidHeight":"í‘œ 높ì´ëŠ” 숫ìžì—¬ì•¼ 합니다.","invalidRows":"í–‰ 번호는 0보다 í° ìˆ«ìžì—¬ì•¼ 합니다.","invalidWidth":"í‘œì˜ í­ì€ 숫ìžì—¬ì•¼ 합니다.","menu":"í‘œ 설정","row":{"menu":"í–‰(Row)","insertBefore":"ì•žì— í–‰ 삽입","insertAfter":"ë’¤ì— í–‰ 삽입","deleteRow":"가로줄 ì‚­ì œ"},"rows":"가로줄","summary":"요약","title":"í‘œ 설정","toolbar":"í‘œ","widthPc":"í¼ì„¼íŠ¸","widthPx":"픽셀","widthUnit":"í­ ë‹¨ìœ„"},"undo":{"redo":"재실행","undo":"취소"},"wsc":{"btnIgnore":"건너뜀","btnIgnoreAll":"ëª¨ë‘ ê±´ë„ˆëœ€","btnReplace":"변경","btnReplaceAll":"ëª¨ë‘ ë³€ê²½","btnUndo":"취소","changeTo":"변경할 단어","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"ì² ìž ê²€ì‚¬ê¸°ê°€ 철치ë˜ì§€ 않았습니다. 지금 다운로드하시겠습니까?","manyChanges":"ì² ìžê²€ì‚¬ 완료: %1 단어가 변경ë˜ì—ˆìŠµë‹ˆë‹¤.","noChanges":"ì² ìžê²€ì‚¬ 완료: ë³€ê²½ëœ ë‹¨ì–´ê°€ 없습니다.","noMispell":"ì² ìžê²€ì‚¬ 완료: ìž˜ëª»ëœ ì² ìžê°€ 없습니다.","noSuggestions":"- 추천단어 ì—†ìŒ -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"ì‚¬ì „ì— ì—†ëŠ” 단어","oneChange":"ì² ìžê²€ì‚¬ 완료: 단어가 변경ë˜ì—ˆìŠµë‹ˆë‹¤.","progress":"ì² ìžê²€ì‚¬ë¥¼ 진행중입니다...","title":"Spell Check","toolbar":"ì² ìžê²€ì‚¬"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ku.js b/js/ckeditor/lang/ku.js
new file mode 100644
index 0000000..ec90e9f
--- /dev/null
+++ b/js/ckeditor/lang/ku.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ku']={"editor":"سەرنووسەی دەقی تەواو","editorPanel":"بڕگەی سەرنووسەی دەقی تەواو","common":{"editorHelp":"کلیکی ALT Ù„Û•Ú¯Û•Úµ 0 بکه‌ بۆ یارمەتی","browseServer":"هێنانی ڕاژە","url":"ناونیشانی بەستەر","protocol":"پڕۆتۆکۆڵ","upload":"بارکردن","uploadSubmit":"ناردنی بۆ ڕاژە","image":"ÙˆÛŽÙ†Û•","flash":"Ùلاش","form":"داڕشتە","checkbox":"خانەی نیشانکردن","radio":"جێگرەوەی دوگمە","textField":"خانەی دەق","textarea":"ڕووبەری دەق","hiddenField":"شاردنەوی خانە","button":"دوگمە","select":"هەڵبژاردەی خانە","imageButton":"دوگمەی ÙˆÛŽÙ†Û•","notSet":"<هیچ دانەدراوە>","id":"ناسنامە","name":"ناو","langDir":"ئاراستەی زمان","langDirLtr":"Ú†Û•Ù¾ بۆ ڕاست (LTR)","langDirRtl":"ڕاست بۆ Ú†Û•Ù¾ (RTL)","langCode":"هێمای زمان","longDescr":"پێناسەی درێژی بەستەر","cssClass":"شێوازی چینی په‌ڕە","advisoryTitle":"ڕاوێژکاری سەردێڕ","cssStyle":"شێواز","ok":"باشە","cancel":"پاشگەزبوونەوە","close":"داخستن","preview":"پێشبینین","resize":"گۆڕینی ئەندازە","generalTab":"گشتی","advancedTab":"پەرەسەندوو","validateNumberFailed":"ئەم نرخە ژمارە نیە، تکایە نرخێکی ژمارە بنووسە.","confirmNewPage":"سەرجەم گۆڕانکاریەکان Ùˆ پێکهاتەکانی ناووەوە لەدەست دەدەی گەر بێتوو پاشکەوتی Ù†Û•Ú©Û•ÛŒ یەکەم جار، تۆ هەر دڵنیایی لەکردنەوەی پەنجەرەکی نوێ؟","confirmCancel":"هەندێك هەڵبژاردە گۆڕدراوە. تۆ دڵنیایی Ù„Û• داخستنی ئەم دیالۆگە؟","options":"هەڵبژاردەکان","target":"ئامانج","targetNew":"پەنجەرەیەکی نوێ (_blank)","targetTop":"لووتکەی پەنجەرە (_top)","targetSelf":"لەهەمان پەنجەرە (_self)","targetParent":"پەنجەرەی باوان (_parent)","langDirLTR":"Ú†Û•Ù¾ بۆ ڕاست (LTR)","langDirRTL":"ڕاست بۆ Ú†Û•Ù¾ (RTL)","styles":"شێواز","cssClasses":"شێوازی چینی Ù¾Û•Ú•Û•","width":"پانی","height":"درێژی","align":"ڕێککەرەوە","alignLeft":"Ú†Û•Ù¾","alignRight":"ڕاست","alignCenter":"ناوەڕاست","alignJustify":"هاوستوونی","alignTop":"سەرەوە","alignMiddle":"ناوەند","alignBottom":"ژێرەوە","alignNone":"هیچ","invalidValue":"نرخێکی نادرووست.","invalidHeight":"درێژی دەبێت ژمارە بێت.","invalidWidth":"پانی دەبێت ژمارە بێت.","invalidCssLength":"ئەم نرخەی دراوە بۆ خانەی \"%1\" دەبێت ژمارەکی درووست بێت یان بێ ناونیشانی ئامرازی (px, %, in, cm, mm, em, ex, pt, یان pc).","invalidHtmlLength":"ئەم نرخەی دراوە بۆ خانەی \"%1\" دەبێت ژمارەکی درووست بێت یان بێ ناونیشانی ئامرازی HTML (px یان %).","invalidInlineStyle":"دانەی نرخی شێوازی ناوهێڵ دەبێت پێکهاتبێت لەیەك یان زیاتری داڕشتە \"ناو : نرخ\", جیاکردنەوەی بە Ùاریزە Ùˆ خاڵ","cssLengthTooltip":"ژمارەیەك بنووسه‌ بۆ نرخی piksel یان ئامرازێکی درووستی CSS (px, %, in, cm, mm, em, ex, pt, یان pc).","unavailable":"%1<span class=\"cke_accessibility\">, ئامادە نیە</span>"},"about":{"copy":"ماÙÛŒ لەبەرگەرتنەوەی &copy; $1. گشتی پارێزراوه. ورگێڕانی بۆ کوردی لەلایەن Ù‡Û†Ú˜Û• کۆیی.","dlgTitle":"دەربارەی CKEditor","help":"سەیری $1 بکه بۆ یارمەتی.","moreInfo":"بۆ زانیاری زیاتر دەربارەی مۆڵەتی بەکارهێنان، تکایه سەردانی ماڵپەڕەکەمان بکه:","title":"دەربارەی CKEditor","userGuide":"ڕێپیشاندەری CKEditors"},"basicstyles":{"bold":"Ù‚Û•ÚµÛ•Ùˆ","italic":"لار","strike":"لێدان","subscript":"ژێرنووس","superscript":"سەرنووس","underline":"ژێرهێڵ"},"blockquote":{"toolbar":"بەربەستکردنی ووتەی وەرگیراو"},"clipboard":{"copy":"لەبەرگرتنەوە","copyError":"پارێزی وێبگەڕەکەت ڕێگەنادات بەسەرنووسەکە Ù„Û• لکاندنی دەقی خۆکارارنە. تکایە لەبری ئەمە ئەم Ùەرمانە بەکاربهێنە بەداگرتنی کلیلی (Ctrl/Cmd+C).","cut":"بڕین","cutError":"پارێزی وێبگەڕەکەت ڕێگەنادات بە سەرنووسەکە لەبڕینی خۆکارانە. تکایە لەبری ئەمە ئەم Ùەرمانە بەکاربهێنە بەداگرتنی کلیلی (Ctrl/Cmd+X).","paste":"لکاندن","pasteArea":"ناوچەی لکاندن","pasteMsg":"تکایە بیلکێنە لەناوەوەی ئەم سنوقە Ù„Û•Ú•ÛŽÛŒ تەختەکلیلەکەت بە بەکارهێنانی کلیلی (<STRONG>Ctrl/Cmd+V</STRONG>) دووای کلیکی باشە بکە.","securityMsg":"بەهۆی شێوەپێدانی پارێزی وێبگەڕەکەت، سەرنووسەکه ناتوانێت دەستبگەیەنێت بەهەڵگیراوەکە ڕاستەوخۆ. بۆیه پێویسته دووباره بیلکێنیت Ù„Û•Ù… پەنجەرەیه.","title":"لکاندن"},"contextmenu":{"options":"هەڵبژاردەی لیستەی کلیکی دەستی ڕاست"},"button":{"selectedLabel":"%1 (هەڵبژێردراو)"},"toolbar":{"toolbarCollapse":"شاردنەوی Ù‡ÛŽÚµÛŒ تووڵامراز","toolbarExpand":"نیشاندانی Ù‡ÛŽÚµÛŒ تووڵامراز","toolbarGroups":{"document":"Ù¾Û•Ú•Ù‡","clipboard":"بڕین/پووچکردنەوە","editing":"چاکسازی","forms":"داڕشتە","basicstyles":"شێوازی بنچینەیی","paragraph":"بڕگە","links":"بەستەر","insert":"خستنە ناو","styles":"شێواز","colors":"ڕەنگەکان","tools":"ئامرازەکان"},"toolbars":"تووڵامرازی دەسکاریکەر"},"elementspath":{"eleLabel":"Ú•ÛŽÚ•Û•ÙˆÛŒ توخمەکان","eleTitle":"%1 توخم"},"format":{"label":"ڕازاندنەوە","panelTitle":"بەشی ڕازاندنەوه","tag_address":"ناونیشان","tag_div":"(DIV)-ÛŒ ئاسایی","tag_h1":"سەرنووسەی Ù¡","tag_h2":"سەرنووسەی Ù¢","tag_h3":"سەرنووسەی Ù£","tag_h4":"سەرنووسەی Ù¤","tag_h5":"سەرنووسەی Ù¥","tag_h6":"سەرنووسەی Ù¦","tag_p":"ئاسایی","tag_pre":"شێوازکراو"},"horizontalrule":{"toolbar":"دانانی Ù‡ÛŽÙ„ÛŒ ئاسۆیی"},"image":{"alertUrl":"تکایه ناونیشانی بەستەری وێنه بنووسه","alt":"جێگرەوەی دەق","border":"پەراوێز","btnUpload":"ناردنی بۆ ڕاژه","button2Img":"تۆ دەتەوێت دوگمەی ÙˆÛŽÙ†Û•ÛŒ دیاریکراو بگۆڕیت بۆ وێنەیەکی ئاسایی؟","hSpace":"بۆشایی ئاسۆیی","img2Button":"تۆ دەتەوێت ÙˆÛŽÙ†Û•ÛŒ دیاریکراو بگۆڕیت بۆ دوگمەی وێنه؟","infoTab":"زانیاری وێنه","linkTab":"بەستەر","lockRatio":"داخستنی Ú•ÛŽÚ˜Ù‡","menu":"خاسیەتی وێنه","resetSize":"ڕێکخستنەوەی قەباره","title":"خاسیەتی وێنه","titleButton":"خاسیەتی دوگمەی وێنه","upload":"بارکردن","urlMissing":"سەرچاوەی بەستەری وێنه بزره","vSpace":"بۆشایی ئەستونی","validateBorder":"پەراوێز دەبێت بەتەواوی تەنها ژماره بێت.","validateHSpace":"بۆشایی ئاسۆیی دەبێت بەتەواوی تەنها ژمارە بێت.","validateVSpace":"بۆشایی ئەستونی دەبێت بەتەواوی تەنها ژماره بێت."},"indent":{"indent":"زیادکردنی بۆشایی","outdent":"کەمکردنەوەی بۆشایی"},"fakeobjects":{"anchor":"لەنگەر","flash":"Ùلاش","hiddenfield":"شاردنەوەی خانه","iframe":"لەچوارچێوە","unknown":"بەرکارێکی نەناسراو"},"link":{"acccessKey":"کلیلی دەستپێگەیشتن","advanced":"پێشکەوتوو","advisoryContentType":"جۆری ناوەڕۆکی ڕاویژکار","advisoryTitle":"ڕاوێژکاری سەردێڕ","anchor":{"toolbar":"دانان/چاکسازی لەنگەر","menu":"چاکسازی لەنگەر","title":"خاسیەتی لەنگەر","name":"ناوی لەنگەر","errorName":"تکایه ناوی لەنگەر بنووسه","remove":"لابردنی لەنگەر"},"anchorId":"بەپێی ناسنامەی توخم","anchorName":"بەپێی ناوی لەنگەر","charset":"بەستەری سەرچاوەی نووسە","cssClasses":"شێوازی چینی Ù¾Û•Ú•Ù‡","emailAddress":"ناونیشانی ئیمەیل","emailBody":"ناوەڕۆکی نامە","emailSubject":"بابەتی نامە","id":"ناسنامە","info":"زانیاری بەستەر","langCode":"هێمای زمان","langDir":"ئاراستەی زمان","langDirLTR":"Ú†Û•Ù¾ بۆ ڕاست (LTR)","langDirRTL":"ڕاست بۆ Ú†Û•Ù¾ (RTL)","menu":"چاکسازی بەستەر","name":"ناو","noAnchors":"(هیچ جۆرێکی لەنگەر ئامادە نیە Ù„Û•Ù… پەڕەیه)","noEmail":"تکایە ناونیشانی ئیمەیل بنووسە","noUrl":"تکایە ناونیشانی بەستەر بنووسە","other":"<هیتر>","popupDependent":"پێوەبەستراو (Netscape)","popupFeatures":"خاسیەتی پەنجەرەی سەرهەڵدەر","popupFullScreen":"Ù¾Ú• بەپڕی شاشە (IE)","popupLeft":"جێگای Ú†Û•Ù¾","popupLocationBar":"Ù‡ÛŽÚµÛŒ ناونیشانی بەستەر","popupMenuBar":"Ù‡ÛŽÚµÛŒ لیسته","popupResizable":"توانای گۆڕینی قەباره","popupScrollBars":"Ù‡ÛŽÚµÛŒ هاتووچۆپێکردن","popupStatusBar":"Ù‡ÛŽÚµÛŒ دۆخ","popupToolbar":"Ù‡ÛŽÚµÛŒ تووڵامراز","popupTop":"جێگای سەرەوە","rel":"پەیوەندی","selectAnchor":"هەڵبژاردنی لەنگەرێك","styles":"شێواز","tabIndex":"بازدەری تابی ئیندێکس","target":"ئامانج","targetFrame":"<چووارچێوە>","targetFrameName":"ناوی ئامانجی چووارچێوە","targetPopup":"<پەنجەرەی سەرهەڵدەر>","targetPopupName":"ناوی پەنجەرەی سەرهەڵدەر","title":"بەستەر","toAnchor":"بەستەر بۆ لەنگەر له دەق","toEmail":"ئیمەیل","toUrl":"ناونیشانی بەستەر","toolbar":"دانان/ڕێکخستنی بەستەر","type":"جۆری بەستەر","unlink":"لابردنی بەستەر","upload":"بارکردن"},"list":{"bulletedlist":"دانان/لابردنی خاڵی لیست","numberedlist":"دانان/لابردنی ژمارەی لیست"},"magicline":{"title":"بڕگە لێرە دابنێ"},"maximize":{"maximize":"ئەوپەڕی گەورەیی","minimize":"ئەوپەڕی بچووکی"},"pastetext":{"button":"لکاندنی ÙˆÛ•Ùƒ دەقی ڕوون","title":"لکاندنی ÙˆÛ•Ùƒ دەقی ڕوون"},"pastefromword":{"confirmCleanup":"ئەم دەقەی بەتەمای بیلکێنی پێدەچێت له word هێنرابێت. دەتەوێت پاکی بکەیوه Ù¾ÛŽØ´ ئەوەی بیلکێنی؟","error":"هیچ ڕێگەیەك نەبوو لەلکاندنی دەقەکه بەهۆی هەڵەیەکی ناوەخۆیی","title":"لکاندنی لەلایەن Word","toolbar":"لکاندنی Ù„Û•Ú•ÛŽÛŒ Word"},"removeformat":{"toolbar":"لابردنی داڕشتەکە"},"sourcearea":{"toolbar":"سەرچاوە"},"specialchar":{"options":"هەڵبژاردەی نووسەی تایبەتی","title":"هەڵبژاردنی نووسەی تایبەتی","toolbar":"دانانی نووسەی تایبەتی"},"scayt":{"btn_about":"دهربارهی SCAYT","btn_dictionaries":"Ùهرههنگهکان","btn_disable":"ناچالاککردنی SCAYT","btn_enable":"چالاککردنی SCAYT","btn_langs":"زمانهکان","btn_options":"ههڵبژارده","text_title":"پشکنینی نووسه لهکاتی نووسین"},"stylescombo":{"label":"شێواز","panelTitle":"شێوازی ڕازاندنەوە","panelTitle1":"شێوازی خشت","panelTitle2":"شێوازی ناوهێڵ","panelTitle3":"شێوازی بەرکار"},"table":{"border":"گەورەیی پەراوێز","caption":"سەردێڕ","cell":{"menu":"خانه","insertBefore":"دانانی خانه Ù„Û•Ù¾ÛŽØ´","insertAfter":"دانانی خانه لەپاش","deleteCell":"سڕینەوەی خانه","merge":"تێکەڵکردنی خانە","mergeRight":"تێکەڵکردنی Ù„Û•Ú¯Û•Úµ ڕاست","mergeDown":"تێکەڵکردنی Ù„Û•Ú¯Û•Úµ خوارەوە","splitHorizontal":"دابەشکردنی خانەی ئاسۆیی","splitVertical":"دابەشکردنی خانەی ئەستونی","title":"خاسیەتی خانه","cellType":"جۆری خانه","rowSpan":"ماوەی نێوان ڕیز","colSpan":"بستی ئەستونی","wordWrap":"پێچانەوەی وشە","hAlign":"ڕیزکردنی ئاسۆیی","vAlign":"ڕیزکردنی ئەستونی","alignBaseline":"هێڵەبنەڕەت","bgColor":"Ú•Û•Ù†Ú¯ÛŒ پاشبنەما","borderColor":"Ú•Û•Ù†Ú¯ÛŒ پەراوێز","data":"داتا","header":"سەرپەڕه","yes":"بەڵێ","no":"نەخێر","invalidWidth":"پانی خانه دەبێت بەتەواوی ژماره بێت.","invalidHeight":"درێژی خانه بەتەواوی دەبێت ژمارە بێت.","invalidRowSpan":"ماوەی نێوان ڕیز بەتەواوی دەبێت ژمارە بێت.","invalidColSpan":"ماوەی نێوان ئەستونی بەتەواوی دەبێت ژمارە بێت.","chooseColor":"هەڵبژێرە"},"cellPad":"بۆشایی ناوپۆش","cellSpace":"بۆشایی خانه","column":{"menu":"ئەستون","insertBefore":"دانانی ئەستون Ù„Û•Ù¾ÛŽØ´","insertAfter":"دانانی ئەستوون لەپاش","deleteColumn":"سڕینەوەی ئەستوون"},"columns":"ستوونەکان","deleteTable":"سڕینەوەی خشتە","headers":"سەرپەڕه","headersBoth":"هەردووك","headersColumn":"یەکەم ئەستوون","headersNone":"هیچ","headersRow":"یەکەم ڕیز","invalidBorder":"ژمارەی پەراوێز دەبێت تەنها ژماره بێت.","invalidCellPadding":"ناوپۆشی خانه دەبێت ژمارەکی درووست بێت.","invalidCellSpacing":"بۆشایی خانه دەبێت ژمارەکی درووست بێت.","invalidCols":"ژمارەی ئەستوونی دەبێت گەورەتر بێت لەژمارەی 0.","invalidHeight":"درێژی خشته دهبێت تهنها ژماره بێت.","invalidRows":"ژمارەی ڕیز دەبێت گەورەتر بێت لەژمارەی 0.","invalidWidth":"پانی خشته دەبێت تەنها ژماره بێت.","menu":"خاسیەتی خشتە","row":{"menu":"ڕیز","insertBefore":"دانانی ڕیز Ù„Û•Ù¾ÛŽØ´","insertAfter":"دانانی ڕیز لەپاش","deleteRow":"سڕینەوەی ڕیز"},"rows":"ڕیز","summary":"کورتە","title":"خاسیەتی خشتە","toolbar":"خشتە","widthPc":"لەسەدا","widthPx":"وێنەخاڵ - پیکسل","widthUnit":"پانی یەکە"},"undo":{"redo":"هەڵگەڕاندنەوە","undo":"پووچکردنەوە"},"wsc":{"btnIgnore":"پشتگوێ کردن","btnIgnoreAll":"پشتگوێکردنی ههمووی","btnReplace":"لهبریدانن","btnReplaceAll":"لهبریدانانی ههمووی","btnUndo":"پووچکردنهوه","changeTo":"گۆڕینی بۆ","errorLoading":"ههڵه لههێنانی داخوازینامهی خانهخۆێی ڕاژه: %s.","ieSpellDownload":"پشکنینی ڕێنووس دانهمزراوه. دهتهوێت ئێستا دایبگریت?","manyChanges":"پشکنینی ڕێنووس کۆتای هات: لهسهدا %1 ÛŒ وشهکان گۆڕدرا","noChanges":"پشکنینی ڕێنووس کۆتای هات: هیچ وشهیهك نۆگۆڕدرا","noMispell":"پشکنینی ڕێنووس کۆتای هات: هیچ ههڵهیهکی ڕێنووس نهدۆزراوه","noSuggestions":"- هیچ پێشنیارێك -","notAvailable":"ببووره، لهمکاتهدا ڕاژهکه لهبهردهستا نیه.","notInDic":"لهÙهرههنگ دانیه","oneChange":"پشکنینی ڕێنووس کۆتای هات: یهك وشه گۆڕدرا","progress":"پشکنینی ڕێنووس لهبهردهوامبوون دایه...","title":"پشکنینی ڕێنووس","toolbar":"پشکنینی ڕێنووس"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/lt.js b/js/ckeditor/lang/lt.js
new file mode 100644
index 0000000..7eccad4
--- /dev/null
+++ b/js/ckeditor/lang/lt.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['lt']={"editor":"Pilnas redaktorius","editorPanel":"Pilno redagtoriaus skydelis","common":{"editorHelp":"Spauskite ALT 0 dÄ—l pagalbos","browseServer":"NarÅ¡yti po serverį","url":"URL","protocol":"Protokolas","upload":"Siųsti","uploadSubmit":"Siųsti į serverį","image":"Vaizdas","flash":"Flash","form":"Forma","checkbox":"Žymimasis langelis","radio":"Žymimoji akutÄ—","textField":"Teksto laukas","textarea":"Teksto sritis","hiddenField":"Nerodomas laukas","button":"Mygtukas","select":"Atrankos laukas","imageButton":"Vaizdinis mygtukas","notSet":"<nÄ—ra nustatyta>","id":"Id","name":"Vardas","langDir":"Teksto kryptis","langDirLtr":"IÅ¡ kairÄ—s į deÅ¡inÄ™ (LTR)","langDirRtl":"IÅ¡ deÅ¡inÄ—s į kairÄ™ (RTL)","langCode":"Kalbos kodas","longDescr":"Ilgas apraÅ¡ymas URL","cssClass":"Stilių lentelÄ—s klasÄ—s","advisoryTitle":"KonsultacinÄ— antraÅ¡tÄ—","cssStyle":"Stilius","ok":"OK","cancel":"Nutraukti","close":"Uždaryti","preview":"PeržiÅ«rÄ—ti","resize":"Pavilkite, kad pakeistumÄ—te dydį","generalTab":"Bendros savybÄ—s","advancedTab":"Papildomas","validateNumberFailed":"Å i reikÅ¡mÄ— nÄ—ra skaiÄius.","confirmNewPage":"Visas neiÅ¡saugotas turinys bus prarastas. Ar tikrai norite įkrauti naujÄ… puslapį?","confirmCancel":"Kai kurie parametrai pasikeitÄ—. Ar tikrai norite užverti langÄ…?","options":"Parametrai","target":"TikslinÄ— nuoroda","targetNew":"Naujas langas (_blank)","targetTop":"VirÅ¡utinis langas (_top)","targetSelf":"Esamas langas (_self)","targetParent":"Paskutinis langas (_parent)","langDirLTR":"IÅ¡ kairÄ—s į deÅ¡inÄ™ (LTR)","langDirRTL":"IÅ¡ deÅ¡inÄ—s į kairÄ™ (RTL)","styles":"Stilius","cssClasses":"Stilių klasÄ—s","width":"Plotis","height":"AukÅ¡tis","align":"Lygiuoti","alignLeft":"KairÄ™","alignRight":"DeÅ¡inÄ™","alignCenter":"CentrÄ…","alignJustify":"Lygiuoti abi puses","alignTop":"VirÅ¡Å«nÄ™","alignMiddle":"Vidurį","alignBottom":"ApaÄiÄ…","alignNone":"Niekas","invalidValue":"Neteisinga reikÅ¡mÄ—.","invalidHeight":"AukÅ¡tis turi bÅ«ti nurodytas skaiÄiais.","invalidWidth":"Plotis turi bÅ«ti nurodytas skaiÄiais.","invalidCssLength":"ReikÅ¡mÄ— nurodyta \"%1\" laukui, turi bÅ«ti teigiamas skaiÄius su arba be tinkamo CSS matavimo vieneto (px, %, in, cm, mm, em, ex, pt arba pc).","invalidHtmlLength":"ReikÅ¡mÄ— nurodyta \"%1\" laukui, turi bÅ«ti teigiamas skaiÄius su arba be tinkamo HTML matavimo vieneto (px arba %).","invalidInlineStyle":"ReikÅ¡mÄ— nurodyta vidiniame stiliuje turi bÅ«ti sudaryta iÅ¡ vieno Å¡ių reikÅ¡mių \"vardas : reikÅ¡mÄ—\", atskirta kabliataÅ¡kiais.","cssLengthTooltip":"Ä®veskite reikÅ¡mÄ™ pikseliais arba skaiÄiais su tinkamu CSS vienetu (px, %, in, cm, mm, em, ex, pt arba pc).","unavailable":"%1<span class=\"cke_accessibility\">, netinkamas</span>"},"about":{"copy":"Copyright &copy; $1. Visos teiss saugomos.","dlgTitle":"Apie CKEditor","help":"Patikrinkite $1 dÄ—l pagalbos.","moreInfo":"DÄ—l licencijavimo apsilankykite mÅ«sų svetainÄ—je:","title":"Apie CKEditor","userGuide":"CKEditor Vartotojo Gidas"},"basicstyles":{"bold":"Pusjuodis","italic":"Kursyvas","strike":"Perbrauktas","subscript":"Apatinis indeksas","superscript":"VirÅ¡utinis indeksas","underline":"Pabrauktas"},"blockquote":{"toolbar":"Citata"},"clipboard":{"copy":"Kopijuoti","copyError":"JÅ«sų narÅ¡yklÄ—s saugumo nustatymai neleidžia redaktoriui automatiÅ¡kai įvykdyti kopijavimo operacijų. Tam praÅ¡ome naudoti klaviatÅ«rÄ… (Ctrl/Cmd+C).","cut":"IÅ¡kirpti","cutError":"JÅ«sų narÅ¡yklÄ—s saugumo nustatymai neleidžia redaktoriui automatiÅ¡kai įvykdyti iÅ¡kirpimo operacijų. Tam praÅ¡ome naudoti klaviatÅ«rÄ… (Ctrl/Cmd+X).","paste":"Ä®dÄ—ti","pasteArea":"Ä®kelti dalį","pasteMsg":"Žemiau esanÄiame įvedimo lauke įdÄ—kite tekstÄ…, naudodami klaviatÅ«rÄ… (<STRONG>Ctrl/Cmd+V</STRONG>) ir paspauskite mygtukÄ… <STRONG>OK</STRONG>.","securityMsg":"DÄ—l jÅ«sų narÅ¡yklÄ—s saugumo nustatymų, redaktorius negali tiesiogiai pasiekti laikinosios atminties. Jums reikia nukopijuoti dar kartÄ… į šį langÄ….","title":"Ä®dÄ—ti"},"contextmenu":{"options":"Kontekstinio meniu parametrai"},"button":{"selectedLabel":"%1 (Pasirinkta)"},"toolbar":{"toolbarCollapse":"Apjungti įrankių juostÄ…","toolbarExpand":"IÅ¡plÄ—sti įrankių juostÄ…","toolbarGroups":{"document":"Dokumentas","clipboard":"AtmintinÄ—/Atgal","editing":"Redagavimas","forms":"Formos","basicstyles":"Pagrindiniai stiliai","paragraph":"Paragrafas","links":"Nuorodos","insert":"Ä®terpti","styles":"Stiliai","colors":"Spalvos","tools":"Ä®rankiai"},"toolbars":"Redaktoriaus įrankiai"},"elementspath":{"eleLabel":"Elemento kelias","eleTitle":"%1 elementas"},"format":{"label":"Å rifto formatas","panelTitle":"Å rifto formatas","tag_address":"Kreipinio","tag_div":"Normalus (DIV)","tag_h1":"AntraÅ¡tinis 1","tag_h2":"AntraÅ¡tinis 2","tag_h3":"AntraÅ¡tinis 3","tag_h4":"AntraÅ¡tinis 4","tag_h5":"AntraÅ¡tinis 5","tag_h6":"AntraÅ¡tinis 6","tag_p":"Normalus","tag_pre":"Formuotas"},"horizontalrule":{"toolbar":"Ä®terpti horizontaliÄ… linijÄ…"},"image":{"alertUrl":"PraÅ¡ome įvesti vaizdo URL","alt":"Alternatyvus Tekstas","border":"RÄ—melis","btnUpload":"Siųsti į serverį","button2Img":"Ar norite mygtukÄ… paversti paprastu paveiksliuku?","hSpace":"Hor.ErdvÄ—","img2Button":"Ar norite paveiksliukÄ… paversti mygtuku?","infoTab":"Vaizdo informacija","linkTab":"Nuoroda","lockRatio":"IÅ¡laikyti proporcijÄ…","menu":"Vaizdo savybÄ—s","resetSize":"Atstatyti dydį","title":"Vaizdo savybÄ—s","titleButton":"Vaizdinio mygtuko savybÄ—s","upload":"Nusiųsti","urlMissing":"Paveiksliuko nuorodos nÄ—ra.","vSpace":"Vert.ErdvÄ—","validateBorder":"ReikÅ¡mÄ— turi bÅ«ti sveikas skaiÄius.","validateHSpace":"ReikÅ¡mÄ— turi bÅ«ti sveikas skaiÄius.","validateVSpace":"ReikÅ¡mÄ— turi bÅ«ti sveikas skaiÄius."},"indent":{"indent":"Padidinti įtraukÄ…","outdent":"Sumažinti įtraukÄ…"},"fakeobjects":{"anchor":"ŽymÄ—","flash":"Flash animacija","hiddenfield":"PaslÄ—ptas laukas","iframe":"IFrame","unknown":"Nežinomas objektas"},"link":{"acccessKey":"Prieigos raktas","advanced":"Papildomas","advisoryContentType":"Konsultacinio turinio tipas","advisoryTitle":"KonsultacinÄ— antraÅ¡tÄ—","anchor":{"toolbar":"Ä®terpti/modifikuoti žymÄ™","menu":"ŽymÄ—s savybÄ—s","title":"ŽymÄ—s savybÄ—s","name":"ŽymÄ—s vardas","errorName":"PraÅ¡ome įvesti žymÄ—s vardÄ…","remove":"PaÅ¡alinti žymÄ™"},"anchorId":"Pagal žymÄ—s Id","anchorName":"Pagal žymÄ—s vardÄ…","charset":"Susietų iÅ¡teklių simbolių lentelÄ—","cssClasses":"Stilių lentelÄ—s klasÄ—s","emailAddress":"El.paÅ¡to adresas","emailBody":"ŽinutÄ—s turinys","emailSubject":"ŽinutÄ—s tema","id":"Id","info":"Nuorodos informacija","langCode":"Teksto kryptis","langDir":"Teksto kryptis","langDirLTR":"IÅ¡ kairÄ—s į deÅ¡inÄ™ (LTR)","langDirRTL":"IÅ¡ deÅ¡inÄ—s į kairÄ™ (RTL)","menu":"Taisyti nuorodÄ…","name":"Vardas","noAnchors":"(Å iame dokumente žymių nÄ—ra)","noEmail":"PraÅ¡ome įvesti el.paÅ¡to adresÄ…","noUrl":"PraÅ¡ome įvesti nuorodos URL","other":"<kitas>","popupDependent":"Priklausomas (Netscape)","popupFeatures":"IÅ¡skleidžiamo lango savybÄ—s","popupFullScreen":"Visas ekranas (IE)","popupLeft":"KairÄ— pozicija","popupLocationBar":"Adreso juosta","popupMenuBar":"Meniu juosta","popupResizable":"Kintamas dydis","popupScrollBars":"Slinkties juostos","popupStatusBar":"BÅ«senos juosta","popupToolbar":"Mygtukų juosta","popupTop":"VirÅ¡utinÄ— pozicija","rel":"SÄ…sajos","selectAnchor":"Pasirinkite žymÄ™","styles":"Stilius","tabIndex":"Tabuliavimo indeksas","target":"Paskirties vieta","targetFrame":"<kadras>","targetFrameName":"Paskirties kadro vardas","targetPopup":"<iÅ¡skleidžiamas langas>","targetPopupName":"Paskirties lango vardas","title":"Nuoroda","toAnchor":"ŽymÄ— Å¡iame puslapyje","toEmail":"El.paÅ¡tas","toUrl":"Nuoroda","toolbar":"Ä®terpti/taisyti nuorodÄ…","type":"Nuorodos tipas","unlink":"Panaikinti nuorodÄ…","upload":"Siųsti"},"list":{"bulletedlist":"Suženklintas sÄ…raÅ¡as","numberedlist":"Numeruotas sÄ…raÅ¡as"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"IÅ¡didinti","minimize":"Sumažinti"},"pastetext":{"button":"Ä®dÄ—ti kaip grynÄ… tekstÄ…","title":"Ä®dÄ—ti kaip grynÄ… tekstÄ…"},"pastefromword":{"confirmCleanup":"Tekstas, kurį įkeliate yra kopijuojamas iÅ¡ Word. Ar norite jį iÅ¡valyti prieÅ¡ įkeliant?","error":"DÄ—l vidinių sutrikimų, nepavyko iÅ¡valyti įkeliamo teksto","title":"Ä®dÄ—ti iÅ¡ Word","toolbar":"Ä®dÄ—ti iÅ¡ Word"},"removeformat":{"toolbar":"Panaikinti formatÄ…"},"sourcearea":{"toolbar":"Å altinis"},"specialchar":{"options":"Specialaus simbolio nustatymai","title":"Pasirinkite specialų simbolį","toolbar":"Ä®terpti specialų simbolį"},"scayt":{"btn_about":"Apie SCAYT","btn_dictionaries":"Žodynai","btn_disable":"IÅ¡jungti SCAYT","btn_enable":"Ä®jungti SCAYT","btn_langs":"Kalbos","btn_options":"Parametrai","text_title":"Tikrinti klaidas kai raÅ¡oma"},"stylescombo":{"label":"Stilius","panelTitle":"Stilių formatavimas","panelTitle1":"Blokų stiliai","panelTitle2":"Vidiniai stiliai","panelTitle3":"Objektų stiliai"},"table":{"border":"RÄ—melio dydis","caption":"AntraÅ¡tÄ—","cell":{"menu":"Langelis","insertBefore":"Ä®terpti langelį prieÅ¡","insertAfter":"Ä®terpti langelį po","deleteCell":"Å alinti langelius","merge":"Sujungti langelius","mergeRight":"Sujungti su deÅ¡ine","mergeDown":"Sujungti su apaÄia","splitHorizontal":"Skaidyti langelį horizontaliai","splitVertical":"Skaidyti langelį vertikaliai","title":"Cell nustatymai","cellType":"Cell rÅ«Å¡is","rowSpan":"EiluÄių Span","colSpan":"Stulpelių Span","wordWrap":"Sutraukti raides","hAlign":"Horizontalus lygiavimas","vAlign":"Vertikalus lygiavimas","alignBaseline":"ApatinÄ— linija","bgColor":"Fono spalva","borderColor":"RÄ—melio spalva","data":"Data","header":"AntraÅ¡tÄ—","yes":"Taip","no":"Ne","invalidWidth":"ReikÅ¡mÄ— turi bÅ«ti skaiÄius.","invalidHeight":"ReikÅ¡mÄ— turi bÅ«ti skaiÄius.","invalidRowSpan":"ReikÅ¡mÄ— turi bÅ«ti skaiÄius.","invalidColSpan":"ReikÅ¡mÄ— turi bÅ«ti skaiÄius.","chooseColor":"Pasirinkite"},"cellPad":"Tarpas nuo langelio rÄ—mo iki teksto","cellSpace":"Tarpas tarp langelių","column":{"menu":"Stulpelis","insertBefore":"Ä®terpti stulpelį prieÅ¡","insertAfter":"Ä®terpti stulpelį po","deleteColumn":"Å alinti stulpelius"},"columns":"Stulpeliai","deleteTable":"Å alinti lentelÄ™","headers":"AntraÅ¡tÄ—s","headersBoth":"Abu","headersColumn":"Pirmas stulpelis","headersNone":"NÄ—ra","headersRow":"Pirma eilutÄ—","invalidBorder":"ReikÅ¡mÄ— turi bÅ«ti nurodyta skaiÄiumi.","invalidCellPadding":"ReikÅ¡mÄ— turi bÅ«ti nurodyta skaiÄiumi.","invalidCellSpacing":"ReikÅ¡mÄ— turi bÅ«ti nurodyta skaiÄiumi.","invalidCols":"SkaiÄius turi bÅ«ti didesnis nei 0.","invalidHeight":"ReikÅ¡mÄ— turi bÅ«ti nurodyta skaiÄiumi.","invalidRows":"SkaiÄius turi bÅ«ti didesnis nei 0.","invalidWidth":"ReikÅ¡mÄ— turi bÅ«ti nurodyta skaiÄiumi.","menu":"LentelÄ—s savybÄ—s","row":{"menu":"EilutÄ—","insertBefore":"Ä®terpti eilutÄ™ prieÅ¡","insertAfter":"Ä®terpti eilutÄ™ po","deleteRow":"Å alinti eilutes"},"rows":"EilutÄ—s","summary":"Santrauka","title":"LentelÄ—s savybÄ—s","toolbar":"LentelÄ—","widthPc":"procentais","widthPx":"taÅ¡kais","widthUnit":"ploÄio vienetas"},"undo":{"redo":"Atstatyti","undo":"AtÅ¡aukti"},"wsc":{"btnIgnore":"Ignoruoti","btnIgnoreAll":"Ignoruoti visus","btnReplace":"Pakeisti","btnReplaceAll":"Pakeisti visus","btnUndo":"AtÅ¡aukti","changeTo":"Pakeisti į","errorLoading":"Klaida įkraunant servisÄ…: %s.","ieSpellDownload":"RaÅ¡ybos tikrinimas neinstaliuotas. Ar JÅ«s norite jį dabar atsisiųsti?","manyChanges":"RaÅ¡ybos tikrinimas baigtas: Pakeista %1 žodžių","noChanges":"RaÅ¡ybos tikrinimas baigtas: NÄ—ra pakeistų žodžių","noMispell":"RaÅ¡ybos tikrinimas baigtas: Nerasta raÅ¡ybos klaidų","noSuggestions":"- NÄ—ra pasiÅ«lymų -","notAvailable":"Atleiskite, Å¡iuo metu servisas neprieinamas.","notInDic":"Žodyne nerastas","oneChange":"RaÅ¡ybos tikrinimas baigtas: Vienas žodis pakeistas","progress":"Vyksta raÅ¡ybos tikrinimas...","title":"Tikrinti klaidas","toolbar":"RaÅ¡ybos tikrinimas"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/lv.js b/js/ckeditor/lang/lv.js
new file mode 100644
index 0000000..3df4d63
--- /dev/null
+++ b/js/ckeditor/lang/lv.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['lv']={"editor":"BagÄtinÄtÄ teksta redaktors","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"PalÄ«dzÄ«bai, nospiediet ALT 0 ","browseServer":"SkatÄ«t servera saturu","url":"URL","protocol":"Protokols","upload":"AugÅ¡upielÄdÄ“t","uploadSubmit":"NosÅ«tÄ«t serverim","image":"AttÄ“ls","flash":"Flash","form":"Forma","checkbox":"AtzÄ«mÄ“Å¡anas kastÄ«te","radio":"IzvÄ“les poga","textField":"Teksta rinda","textarea":"Teksta laukums","hiddenField":"PaslÄ“pta teksta rinda","button":"Poga","select":"IezÄ«mÄ“Å¡anas lauks","imageButton":"AttÄ“lpoga","notSet":"<nav iestatÄ«ts>","id":"Id","name":"Nosaukums","langDir":"Valodas lasÄ«Å¡anas virziens","langDirLtr":"No kreisÄs uz labo (LTR)","langDirRtl":"No labÄs uz kreiso (RTL)","langCode":"Valodas kods","longDescr":"Gara apraksta Hipersaite","cssClass":"Stilu saraksta klases","advisoryTitle":"KonsultatÄ«vs virsraksts","cssStyle":"Stils","ok":"DarÄ«ts!","cancel":"Atcelt","close":"AizvÄ“rt","preview":"PriekÅ¡skatÄ«jums","resize":"MÄ“rogot","generalTab":"VispÄrÄ«gi","advancedTab":"IzvÄ“rstais","validateNumberFailed":"Å Ä« vÄ“rtÄ«ba nav skaitlis","confirmNewPage":"Jebkuras nesaglabÄtÄs izmaiņas tiks zaudÄ“tas. Vai tieÅ¡Äm vÄ“laties atvÄ“rt jaunu lapu?","confirmCancel":"Daži no uzstÄdÄ«jumiem ir mainÄ«ti. Vai tieÅ¡Äm vÄ“laties aizvÄ“rt Å¡o dialogu?","options":"UzstÄdÄ«jumi","target":"MÄ“rÄ·is","targetNew":"Jauns logs (_blank)","targetTop":"VirsÄ“jais logs (_top)","targetSelf":"Tas pats logs (_self)","targetParent":"Avota logs (_parent)","langDirLTR":"Kreisais uz Labo (LTR)","langDirRTL":"Labais uz Kreiso (RTL)","styles":"Stils","cssClasses":"Stilu klases","width":"Platums","height":"Augstums","align":"NolÄ«dzinÄt","alignLeft":"Pa kreisi","alignRight":"Pa labi","alignCenter":"CentrÄ“ti","alignJustify":"IzlÄ«dzinÄt malas","alignTop":"AugÅ¡Ä","alignMiddle":"VertikÄli centrÄ“ts","alignBottom":"ApakÅ¡Ä","alignNone":"None","invalidValue":"Nekorekta vÄ“rtÄ«ba","invalidHeight":"Augstumam jÄbÅ«t skaitlim.","invalidWidth":"Platumam jÄbÅ«t skaitlim","invalidCssLength":"Laukam \"%1\" norÄdÄ«tajai vÄ“rtÄ«bai jÄbÅ«t pozitÄ«vam skaitlim ar vai bez korektÄm CSS mÄ“rvienÄ«bÄm (px, %, in, cm, mm, em, ex, pt, vai pc).","invalidHtmlLength":"Laukam \"%1\" norÄdÄ«tajai vÄ“rtÄ«bai jÄbÅ«t pozitÄ«vam skaitlim ar vai bez korektÄm HTML mÄ“rvienÄ«bÄm (px vai %).","invalidInlineStyle":"IekļautajÄ stilÄ norÄdÄ«tajai vÄ“rtÄ«bai jÄsastÄv no viena vai vairÄkiem pÄriem pÄ“c forma'ta \"nosaukums: vÄ“rtÄ«ba\", atdalÄ«tiem ar semikolu.","cssLengthTooltip":"Ievadiet vÄ“rtÄ«bu pikseļos vai skaitli ar derÄ«gu CSS mÄ“rvienÄ«bu (px, %, in, cm, mm, em, ex, pt, vai pc).","unavailable":"%1<span class=\"cke_accessibility\">, nav pieejams</span>"},"about":{"copy":"KopÄ“Å¡anas tiesÄ«bas &copy; $1. Visas tiesÄ«bas rezervÄ“tas.","dlgTitle":"Par CKEditor","help":"PÄrbaudiet $1 palÄ«dzÄ«bai.","moreInfo":"InformÄcijai par licenzÄ“Å¡anu apmeklÄ“jiet mÅ«su mÄjas lapu:","title":"Par CKEditor","userGuide":"CKEditor LietotÄja pamÄcÄ«ba"},"basicstyles":{"bold":"TrekninÄts","italic":"KursÄ«vs","strike":"PÄrsvÄ«trots","subscript":"ApakÅ¡rakstÄ","superscript":"AugÅ¡rakstÄ","underline":"PasvÄ«trots"},"blockquote":{"toolbar":"Bloka citÄts"},"clipboard":{"copy":"KopÄ“t","copyError":"JÅ«su pÄrlÅ«kprogrammas droÅ¡Ä«bas iestatÄ«jumi nepieļauj redaktoram automÄtiski veikt kopÄ“Å¡anas darbÄ«bu. LÅ«dzu, izmantojiet (Ctrl/Cmd+C), lai veiktu Å¡o darbÄ«bu.","cut":"Izgriezt","cutError":"JÅ«su pÄrlÅ«kprogrammas droÅ¡Ä«bas iestatÄ«jumi nepieļauj redaktoram automÄtiski veikt izgriezÅ¡anas darbÄ«bu. LÅ«dzu, izmantojiet (Ctrl/Cmd+X), lai veiktu Å¡o darbÄ«bu.","paste":"IelÄ«mÄ“t","pasteArea":"IelÄ«mÄ“Å¡anas zona","pasteMsg":"LÅ«dzu, ievietojiet tekstu Å¡ajÄ laukumÄ, izmantojot klaviatÅ«ru (<STRONG>Ctrl/Cmd+V</STRONG>) un apstipriniet ar <STRONG>DarÄ«ts!</STRONG>.","securityMsg":"JÅ«su pÄrlÅ«ka droÅ¡Ä«bas uzstÄdÄ«jumu dēļ, nav iespÄ“jams tieÅ¡i piekļūt jÅ«su starpliktuvei. Jums jÄielÄ«mÄ“ atkÄrtoti Å¡ajÄ logÄ.","title":"Ievietot"},"contextmenu":{"options":"UznirstoÅ¡Äs izvÄ“lnes uzstÄdÄ«jumi"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"AizvÄ“rt rÄ«kjoslu","toolbarExpand":"AtvÄ“rt rÄ«kjoslu","toolbarGroups":{"document":"Dokuments","clipboard":"Starpliktuve/Atcelt","editing":"LaboÅ¡ana","forms":"Formas","basicstyles":"Pamata stili","paragraph":"ParagrÄfs","links":"Saites","insert":"Ievietot","styles":"Stili","colors":"KrÄsas","tools":"RÄ«ki"},"toolbars":"Redaktora rÄ«kjoslas"},"elementspath":{"eleLabel":"Elementa ceļš","eleTitle":"%1 elements"},"format":{"label":"FormÄts","panelTitle":"FormÄts","tag_address":"Adrese","tag_div":"Rindkopa (DIV)","tag_h1":"Virsraksts 1","tag_h2":"Virsraksts 2","tag_h3":"Virsraksts 3","tag_h4":"Virsraksts 4","tag_h5":"Virsraksts 5","tag_h6":"Virsraksts 6","tag_p":"NormÄls teksts","tag_pre":"FormatÄ“ts teksts"},"horizontalrule":{"toolbar":"Ievietot horizontÄlu AtdalÄ«tÄjsvÄ«tru"},"image":{"alertUrl":"LÅ«dzu norÄdÄ«t attÄ“la hipersaiti","alt":"AlternatÄ«vais teksts","border":"RÄmis","btnUpload":"NosÅ«tÄ«t serverim","button2Img":"Vai vÄ“laties pÄrveidot izvÄ“lÄ“to attÄ“la pogu uz attÄ“la?","hSpace":"HorizontÄlÄ telpa","img2Button":"Vai vÄ“laties pÄrveidot izvÄ“lÄ“to attÄ“lu uz attÄ“la pogas?","infoTab":"InformÄcija par attÄ“lu","linkTab":"Hipersaite","lockRatio":"NemainÄ«ga Augstuma/Platuma attiecÄ«ba","menu":"AttÄ“la Ä«paÅ¡Ä«bas","resetSize":"Atjaunot sÄkotnÄ“jo izmÄ“ru","title":"AttÄ“la Ä«paÅ¡Ä«bas","titleButton":"AttÄ“lpogas Ä«paÅ¡Ä«bas","upload":"AugÅ¡upielÄdÄ“t","urlMissing":"TrÅ«kst attÄ“la atraÅ¡anÄs adrese.","vSpace":"VertikÄlÄ telpa","validateBorder":"Apmalei jÄbÅ«t veselam skaitlim","validateHSpace":"HSpace jÄbÅ«t veselam skaitlim","validateVSpace":"VSpace jÄbÅ«t veselam skaitlim"},"indent":{"indent":"PalielinÄt atkÄpi","outdent":"SamazinÄt atkÄpi"},"fakeobjects":{"anchor":"IezÄ«me","flash":"Flash animÄcija","hiddenfield":"SlÄ“pts lauks","iframe":"Iframe","unknown":"NezinÄms objekts"},"link":{"acccessKey":"Pieejas taustiņš","advanced":"IzvÄ“rstais","advisoryContentType":"KonsultatÄ«vs satura tips","advisoryTitle":"KonsultatÄ«vs virsraksts","anchor":{"toolbar":"Ievietot/Labot iezÄ«mi","menu":"Labot iezÄ«mi","title":"IezÄ«mes uzstÄdÄ«jumi","name":"IezÄ«mes nosaukums","errorName":"LÅ«dzu norÄdiet iezÄ«mes nosaukumu","remove":"Noņemt iezÄ«mi"},"anchorId":"PÄ“c elementa ID","anchorName":"PÄ“c iezÄ«mes nosaukuma","charset":"PievienotÄ resursa kodÄ“jums","cssClasses":"Stilu saraksta klases","emailAddress":"E-pasta adrese","emailBody":"Ziņas saturs","emailSubject":"Ziņas tÄ“ma","id":"ID","info":"Hipersaites informÄcija","langCode":"Valodas kods","langDir":"Valodas lasÄ«Å¡anas virziens","langDirLTR":"No kreisÄs uz labo (LTR)","langDirRTL":"No labÄs uz kreiso (RTL)","menu":"Labot hipersaiti","name":"Nosaukums","noAnchors":"(Å ajÄ dokumentÄ nav iezÄ«mju)","noEmail":"LÅ«dzu norÄdi e-pasta adresi","noUrl":"LÅ«dzu norÄdi hipersaiti","other":"<cits>","popupDependent":"AtkarÄ«gs (Netscape)","popupFeatures":"UznirstoÅ¡Ä loga nosaukums Ä«paÅ¡Ä«bas","popupFullScreen":"PilnÄ ekrÄnÄ (IE)","popupLeft":"KreisÄ koordinÄte","popupLocationBar":"AtraÅ¡anÄs vietas josla","popupMenuBar":"IzvÄ“lnes josla","popupResizable":"MÄ“rogojams","popupScrollBars":"Ritjoslas","popupStatusBar":"Statusa josla","popupToolbar":"RÄ«ku josla","popupTop":"AugÅ¡Ä“jÄ koordinÄte","rel":"RelÄcija","selectAnchor":"IzvÄ“lÄ“ties iezÄ«mi","styles":"Stils","tabIndex":"Ciļņu indekss","target":"MÄ“rÄ·is","targetFrame":"<ietvars>","targetFrameName":"MÄ“rÄ·a ietvara nosaukums","targetPopup":"<uznirstoÅ¡Ä logÄ>","targetPopupName":"UznirstoÅ¡Ä loga nosaukums","title":"Hipersaite","toAnchor":"IezÄ«me Å¡ajÄ lapÄ","toEmail":"E-pasts","toUrl":"Adrese","toolbar":"Ievietot/Labot hipersaiti","type":"Hipersaites tips","unlink":"Noņemt hipersaiti","upload":"AugÅ¡upielÄdÄ“t"},"list":{"bulletedlist":"Pievienot/Noņemt vienkÄrÅ¡u sarakstu","numberedlist":"NumurÄ“ts saraksts"},"magicline":{"title":"Ievietot Å¡eit rindkopu"},"maximize":{"maximize":"MaksimizÄ“t","minimize":"MinimizÄ“t"},"pastetext":{"button":"Ievietot kÄ vienkÄrÅ¡u tekstu","title":"Ievietot kÄ vienkÄrÅ¡u tekstu"},"pastefromword":{"confirmCleanup":"Teksts, kuru vÄ“laties ielÄ«mÄ“t, izskatÄs ir nokopÄ“ts no Word. Vai vÄ“laties to iztÄ«rÄ«t pirms ielÄ«mÄ“Å¡anas?","error":"IekÅ¡Ä“jas kļūdas dēļ, neizdevÄs iztÄ«rÄ«t ielÄ«mÄ“tos datus.","title":"Ievietot no Worda","toolbar":"Ievietot no Worda"},"removeformat":{"toolbar":"Noņemt stilus"},"sourcearea":{"toolbar":"HTML kods"},"specialchar":{"options":"SpeciÄlo simbolu uzstÄdÄ«jumi","title":"Ievietot Ä«paÅ¡u simbolu","toolbar":"Ievietot speciÄlo simbolu"},"scayt":{"btn_about":"Par SCAYT","btn_dictionaries":"VÄrdnÄ«cas","btn_disable":"AtslÄ“gt SCAYT","btn_enable":"IeslÄ“gt SCAYT","btn_langs":"Valodas","btn_options":"UzstÄdÄ«jumi","text_title":"PÄrbaudÄ«t gramatiku rakstot"},"stylescombo":{"label":"Stils","panelTitle":"FormatÄ“Å¡anas stili","panelTitle1":"Bloka stili","panelTitle2":"iekļautie stili","panelTitle3":"Objekta stili"},"table":{"border":"RÄmja izmÄ“rs","caption":"LeÄ£enda","cell":{"menu":"Å Å«na","insertBefore":"Pievienot Å¡Å«nu pirms","insertAfter":"Pievienot Å¡Å«nu pÄ“c","deleteCell":"DzÄ“st rÅ«tiņas","merge":"Apvienot rÅ«tiņas","mergeRight":"Apvieno pa labi","mergeDown":"Apvienot uz leju","splitHorizontal":"SadalÄ«t Å¡Å«nu horizontÄli","splitVertical":"SadalÄ«t Å¡Å«nu vertikÄli","title":"Å Å«nas uzstÄdÄ«jumi","cellType":"Å Å«nas tips","rowSpan":"Apvienotas rindas","colSpan":"Apvienotas kolonas","wordWrap":"VÄrdu pÄrnese","hAlign":"HorizontÄlais novietojums","vAlign":"VertikÄlais novietojums","alignBaseline":"Pamatrinda","bgColor":"Fona krÄsa","borderColor":"RÄmja krÄsa","data":"Dati","header":"Virsraksts","yes":"JÄ","no":"NÄ“","invalidWidth":"Å Å«nas platumam jÄbÅ«t skaitlim","invalidHeight":"Å Å«nas augstumam jÄbÅ«t skaitlim","invalidRowSpan":"Apvienojamo rindu skaitam jÄbÅ«t veselam skaitlim","invalidColSpan":"Apvienojamo kolonu skaitam jÄbÅ«t veselam skaitlim","chooseColor":"IzvÄ“lÄ“ties"},"cellPad":"RÅ«tiņu nobÄ«de","cellSpace":"RÅ«tiņu atstatums","column":{"menu":"Kolonna","insertBefore":"Ievietot kolonu pirms","insertAfter":"Ievieto kolonu pÄ“c","deleteColumn":"DzÄ“st kolonnas"},"columns":"Kolonnas","deleteTable":"DzÄ“st tabulu","headers":"Virsraksti","headersBoth":"Abi","headersColumn":"PirmÄ kolona","headersNone":"Nekas","headersRow":"PirmÄ rinda","invalidBorder":"RÄmju izmÄ“ram jÄbÅ«t skaitlim","invalidCellPadding":"Å Å«nu atkÄpÄ“m jÄbÅ«t pozitÄ«vam skaitlim","invalidCellSpacing":"Å Å«nu atstarpÄ“m jÄbÅ«t pozitÄ«vam skaitlim","invalidCols":"Kolonu skaitam jÄbÅ«t lielÄkam par 0","invalidHeight":"Tabulas augstumam jÄbÅ«t skaitlim","invalidRows":"Rindu skaitam jÄbÅ«t lielÄkam par 0","invalidWidth":"Tabulas platumam jÄbÅ«t skaitlim","menu":"Tabulas Ä«paÅ¡Ä«bas","row":{"menu":"Rinda","insertBefore":"Ievietot rindu pirms","insertAfter":"Ievietot rindu pÄ“c","deleteRow":"DzÄ“st rindas"},"rows":"Rindas","summary":"AnotÄcija","title":"Tabulas Ä«paÅ¡Ä«bas","toolbar":"Tabula","widthPc":"procentuÄli","widthPx":"pikseļos","widthUnit":"platuma mÄ“rvienÄ«ba"},"undo":{"redo":"AtkÄrtot","undo":"Atcelt"},"wsc":{"btnIgnore":"IgnorÄ“t","btnIgnoreAll":"IgnorÄ“t visu","btnReplace":"Aizvietot","btnReplaceAll":"Aizvietot visu","btnUndo":"Atcelt","changeTo":"NomainÄ«t uz","errorLoading":"Kļūda ielÄdÄ“jot aplikÄcijas servisa adresi: %s.","ieSpellDownload":"PareizrakstÄ«bas pÄrbaudÄ«tÄjs nav pievienots. Vai vÄ“laties to lejupielÄdÄ“t tagad?","manyChanges":"PareizrakstÄ«bas pÄrbaude pabeigta: %1 vÄrdi tika mainÄ«ti","noChanges":"PareizrakstÄ«bas pÄrbaude pabeigta: nekas netika labots","noMispell":"PareizrakstÄ«bas pÄrbaude pabeigta: kļūdas netika atrastas","noSuggestions":"- Nav ieteikumu -","notAvailable":"Atvainojiet, bet serviss Å¡obrÄ«d nav pieejams.","notInDic":"Netika atrasts vÄrdnÄ«cÄ","oneChange":"PareizrakstÄ«bas pÄrbaude pabeigta: 1 vÄrds izmainÄ«ts","progress":"Notiek pareizrakstÄ«bas pÄrbaude...","title":"PÄrbaudÄ«t gramatiku","toolbar":"PareizrakstÄ«bas pÄrbaude"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/mk.js b/js/ckeditor/lang/mk.js
new file mode 100644
index 0000000..dedab5d
--- /dev/null
+++ b/js/ckeditor/lang/mk.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['mk']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Send it to the Server","image":"Image","flash":"Flash","form":"Form","checkbox":"Checkbox","radio":"Radio Button","textField":"Text Field","textarea":"Textarea","hiddenField":"Hidden Field","button":"Button","select":"Selection Field","imageButton":"Image Button","notSet":"<not set>","id":"Id","name":"Name","langDir":"Language Direction","langDirLtr":"Left to Right (LTR)","langDirRtl":"Right to Left (RTL)","langCode":"Language Code","longDescr":"Long Description URL","cssClass":"Stylesheet Classes","advisoryTitle":"Advisory Title","cssStyle":"Style","ok":"OK","cancel":"Cancel","close":"Close","preview":"Preview","resize":"Resize","generalTab":"Општо","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Target","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","styles":"Style","cssClasses":"Stylesheet Classes","width":"Width","height":"Height","align":"Alignment","alignLeft":"Left","alignRight":"Right","alignCenter":"Center","alignJustify":"Justify","alignTop":"Top","alignMiddle":"Middle","alignBottom":"Bottom","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Copy","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"Cut","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"Paste","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Paste"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Paragraph Format","tag_address":"Address","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Insert Horizontal Line"},"image":{"alertUrl":"Please type the image URL","alt":"Alternative Text","border":"Border","btnUpload":"Send it to the Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Image Info","linkTab":"Link","lockRatio":"Lock Ratio","menu":"Image Properties","resetSize":"Reset Size","title":"Image Properties","titleButton":"Image Button Properties","upload":"Upload","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Increase Indent","outdent":"Decrease Indent"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Access Key","advanced":"Advanced","advisoryContentType":"Advisory Content Type","advisoryTitle":"Advisory Title","anchor":{"toolbar":"Anchor","menu":"Edit Anchor","title":"Anchor Properties","name":"Anchor Name","errorName":"Please type the anchor name","remove":"Remove Anchor"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"Stylesheet Classes","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"Id","info":"Link Info","langCode":"Language Code","langDir":"Language Direction","langDirLTR":"Left to Right (LTR)","langDirRTL":"Right to Left (RTL)","menu":"Edit Link","name":"Name","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"Style","tabIndex":"Tab Index","target":"Target","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"Link","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"Link","type":"Link Type","unlink":"Unlink","upload":"Upload"},"list":{"bulletedlist":"Insert/Remove Bulleted List","numberedlist":"Insert/Remove Numbered List"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Paste as plain text","title":"Paste as Plain Text"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Paste from Word","toolbar":"Paste from Word"},"removeformat":{"toolbar":"Remove Format"},"sourcearea":{"toolbar":"Source"},"specialchar":{"options":"Special Character Options","title":"Select Special Character","toolbar":"Insert Special Character"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Styles","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Border size","caption":"Caption","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Delete Cells","merge":"Merge Cells","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"Columns","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"Redo","undo":"Undo"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/mn.js b/js/ckeditor/lang/mn.js
new file mode 100644
index 0000000..e8b30c0
--- /dev/null
+++ b/js/ckeditor/lang/mn.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['mn']={"editor":"Ð¥ÑлбÑрт бичвÑÑ€ боловÑруулагч","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"ҮйлчлÑгч тооцоолуур (ÑервÑÑ€)-ийг үзÑÑ…","url":"цахим хуудаÑны хаÑг (URL)","protocol":"Протокол","upload":"ИлгÑÑж ачаалах","uploadSubmit":"Үүнийг үйлчлÑгч тооцоолуур (Ñервер) лүү илгÑÑÑ…","image":"Зураг","flash":"Флаш хөдөлгөөнтÑй зураг","form":"МаÑгт","checkbox":"ТÑмдÑглÑÑний нүд","radio":"Радио товчлуур","textField":"БичвÑрийн талбар","textarea":"БичвÑрийн зай","hiddenField":"Далд талбар","button":"Товчлуур","select":"Сонголтын талбар","imageButton":"Зургий товчуур","notSet":"<тохируулаагүй>","id":"Id (техникийн нÑÑ€)","name":"ÐÑÑ€","langDir":"Ð¥Ñлний чиглÑл","langDirLtr":"ЗүүнÑÑÑ Ð±Ð°Ñ€ÑƒÑƒÐ½ (LTR)","langDirRtl":"Ð‘Ð°Ñ€ÑƒÑƒÐ½Ð°Ð°Ñ Ð·Ò¯Ò¯Ð½ (RTL)","langCode":"Ð¥Ñлний код","longDescr":"Урт тайлбарын вÑб хаÑг","cssClass":"Ð¥ÑлбÑрийн хуудаÑны ангиуд","advisoryTitle":"Зөвлөх гарчиг","cssStyle":"Загвар","ok":"За","cancel":"Болих","close":"Хаах","preview":"Урьдчилан харах","resize":"Resize","generalTab":"Ерөнхий","advancedTab":"Гүнзгий","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Сонголт","target":"Бай","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Зүүн Ñ‚Ð°Ð»Ð°Ð°Ñ Ð±Ð°Ñ€ÑƒÑƒÐ½ тийшÑÑ (LTR)","langDirRTL":"Баруун Ñ‚Ð°Ð»Ð°Ð°Ñ Ð·Ò¯Ò¯Ð½ тийшÑÑ (RTL)","styles":"Загвар","cssClasses":"Ð¥ÑлбÑрийн хуудаÑны ангиуд","width":"Өргөн","height":"Өндөр","align":"ЭгнÑÑ","alignLeft":"Зүүн","alignRight":"Баруун","alignCenter":"Төвд","alignJustify":"ТÑгшлÑÑ…","alignTop":"ДÑÑд талд","alignMiddle":"Дунд","alignBottom":"Доод талд","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Өндөр нь тоо байх Ñ‘Ñтой.","invalidWidth":"Өргөн нь тоо байх Ñ‘Ñтой.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Тод бүдүүн","italic":"Ðалуу","strike":"Дундуур нь зурааÑтай болгох","subscript":"Суурь болгох","superscript":"ЗÑÑ€Ñг болгох","underline":"Доогуур нь зурааÑтай болгох"},"blockquote":{"toolbar":"ИшлÑл Ñ…ÑÑÑг"},"clipboard":{"copy":"Хуулах","copyError":"Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хуулах үйлдÑлийг зөвшөөрөхгүй байна. (Ctrl/Cmd+C) товчны хоÑлолыг ашиглана уу.","cut":"Хайчлах","cutError":"Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хайчлах үйлдÑлийг зөвшөөрөхгүй байна. (Ctrl/Cmd+X) товчны хоÑлолыг ашиглана уу.","paste":"Буулгах","pasteArea":"Paste Area","pasteMsg":"(<strong>Ctrl/Cmd+V</strong>) товчийг ашиглан paste Ñ…Ð¸Ð¹Ð½Ñ Ò¯Ò¯. Мөн <strong>OK</strong> дар.","securityMsg":"Таны үзүүлÑгч/browser/-н хамгаалалтын Ñ‚Ð¾Ñ…Ð¸Ñ€Ð³Ð¾Ð¾Ð½Ð¾Ð¾Ñ Ð±Ð¾Ð»Ð¾Ð¾Ð´ editor clipboard өгөгдөлрүү шууд хандах боломжгүй. Ð­Ð½Ñ Ñ†Ð¾Ð½Ñ…Ð¾Ð´ дахин paste хийхийг оролд.","title":"Буулгах"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"ХолбооÑууд","insert":"Оруулах","styles":"Загварууд","colors":"Онгөнүүд","tools":"Ð¥ÑÑ€ÑгÑлүүд"},"toolbars":"БолоÑруулагчийн Ñ…ÑÑ€ÑгÑлийн Ñамбар"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Параргафын загвар","panelTitle":"Параргафын загвар","tag_address":"ХаÑг","tag_div":"Paragraph (DIV)","tag_h1":"Гарчиг 1","tag_h2":"Гарчиг 2","tag_h3":"Гарчиг 3","tag_h4":"Гарчиг 4","tag_h5":"Гарчиг 5","tag_h6":"Гарчиг 6","tag_p":"Ð¥Ñвийн","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"Хөндлөн Ð·ÑƒÑ€Ð°Ð°Ñ Ð¾Ñ€ÑƒÑƒÐ»Ð°Ñ…"},"image":{"alertUrl":"Зурагны URL-ын төрлийн Ñонгоно уу","alt":"Зургийг орлох бичвÑÑ€","border":"ХүрÑÑ","btnUpload":"Үүнийг ÑервÑррүү илгÑÑ","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"Хөндлөн зай","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Зурагны мÑдÑÑлÑл","linkTab":"ХолбооÑ","lockRatio":"Радио түгжих","menu":"Зураг","resetSize":"Ñ…ÑмжÑÑ Ð´Ð°Ñ…Ð¸Ð½ оноох","title":"Зураг","titleButton":"Зурган товчны шинж чанар","upload":"Хуулах","urlMissing":"Зургийн ÑÑ… Ñурвалжийн хаÑг (URL) байхгүй байна.","vSpace":"БоÑоо зай","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Догол мөр хаÑах","outdent":"Догол мөр нÑмÑÑ…"},"fakeobjects":{"anchor":"Зангуу","flash":"Flash Animation","hiddenfield":"Ðууц талбар","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Холбох түлхүүр","advanced":"ÐÑмÑлт","advisoryContentType":"Зөвлөлдөх төрлийн агуулга","advisoryTitle":"Зөвлөлдөх гарчиг","anchor":{"toolbar":"Зангуу","menu":"Зангууг болоÑруулах","title":"Зангуугийн шинж чанар","name":"Зангуугийн нÑÑ€","errorName":"Зангуугийн нÑрийг оруулна уу","remove":"Зангууг уÑтгах"},"anchorId":"ЭлемÑнтйн Id нÑÑ€ÑÑÑ€","anchorName":"Зангуугийн нÑÑ€ÑÑÑ€","charset":"ТÑмдÑгт оноох нөөцөд холбогдÑон","cssClasses":"Stylesheet клаÑÑууд","emailAddress":"Э-шуудангийн хаÑг","emailBody":"ЗурваÑны их бие","emailSubject":"ЗурваÑны гарчиг","id":"Id","info":"ХолбооÑын тухай мÑдÑÑлÑл","langCode":"Ð¥Ñлний код","langDir":"Ð¥Ñлний чиглÑл","langDirLTR":"ЗүүнÑÑÑ Ð±Ð°Ñ€ÑƒÑƒÐ½ (LTR)","langDirRTL":"Ð‘Ð°Ñ€ÑƒÑƒÐ½Ð°Ð°Ñ Ð·Ò¯Ò¯Ð½ (RTL)","menu":"Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð·Ð°Ñварлах","name":"ÐÑÑ€","noAnchors":"(Баримт бичиг зангуугүй байна)","noEmail":"Э-шуудангий хаÑгаа ÑˆÐ¸Ð²Ð½Ñ Ò¯Ò¯","noUrl":"ХолбооÑны URL хаÑгийг ÑˆÐ¸Ð²Ð½Ñ Ò¯Ò¯","other":"<other>","popupDependent":"Хамаатай (Netscape)","popupFeatures":"Popup цонхны онцлог","popupFullScreen":"Цонх дүүргÑÑ… (Internet Explorer)","popupLeft":"Зүүн байрлал","popupLocationBar":"Location Ñ…ÑÑÑг","popupMenuBar":"ЦÑÑний Ñамбар","popupResizable":"Resizable","popupScrollBars":"Скрол Ñ…ÑÑÑгүүд","popupStatusBar":"Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ…ÑÑÑг","popupToolbar":"Багажны Ñамбар","popupTop":"ДÑÑд байрлал","rel":"Relationship","selectAnchor":"ÐÑг зангууг Ñонгоно уу","styles":"Загвар","tabIndex":"Tab индекÑ","target":"Байрлал","targetFrame":"<Ðгуулах хүрÑÑ>","targetFrameName":"Очих фремын нÑÑ€","targetPopup":"<popup цонх>","targetPopupName":"Popup цонхны нÑÑ€","title":"ХолбооÑ","toAnchor":"Ð­Ð½Ñ Ð±Ð¸Ñ‡Ð²ÑÑ€ дÑÑ… зангуу руу очих холбооÑ","toEmail":"Э-захиа","toUrl":"цахим хуудаÑны хаÑг (URL)","toolbar":"ХолбооÑ","type":"Линкийн төрөл","unlink":"Ð¥Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð°Ð²Ñ‡ хаÑÑ…","upload":"Хуулах"},"list":{"bulletedlist":"ЦÑгтÑй жагÑаалт","numberedlist":"ДугаарлагдÑан жагÑаалт"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"ДÑлгÑц дүүргÑÑ…","minimize":"Цонхыг багÑгаж харуулах"},"pastetext":{"button":"Энгийн бичвÑÑ€ÑÑÑ€ буулгах","title":"Энгийн бичвÑÑ€ÑÑÑ€ буулгах"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Word-Ð¾Ð¾Ñ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…","toolbar":"Word-Ð¾Ð¾Ñ Ð±ÑƒÑƒÐ»Ð³Ð°Ñ…"},"removeformat":{"toolbar":"Параргафын загварыг авч хаÑÑ…"},"sourcearea":{"toolbar":"Код"},"specialchar":{"options":"Special Character Options","title":"Онцгой Ñ‚ÑмдÑгт Ñонгох","toolbar":"Онцгой Ñ‚ÑмдÑгт оруулах"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Толь бичгүүд","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Ð¥Ñлүүд","btn_options":"Сонголт","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Загвар","panelTitle":"Загвар Ñ…ÑлбÑржүүлÑÑ…","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"ХүрÑÑний Ñ…ÑмжÑÑ","caption":"Тайлбар","cell":{"menu":"Ðүх/зай","insertBefore":"Ðүх/зай өмнө нь оруулах","insertAfter":"Ðүх/зай дараа нь оруулах","deleteCell":"Ðүх уÑтгах","merge":"Ðүх нÑгтÑÑ…","mergeRight":"Баруун тийш нÑгтгÑÑ…","mergeDown":"Доош нÑгтгÑÑ…","splitHorizontal":"Ðүх/зайг боÑоогоор нь туÑгаарлах","splitVertical":"Ðүх/зайг хөндлөнгөөр нь туÑгаарлах","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Ð¥ÑвтÑÑд Ñ‚ÑгшлÑÑ… арга","vAlign":"БоÑоод Ñ‚ÑгшлÑÑ… арга","alignBaseline":"Baseline","bgColor":"ДÑвÑгÑÑ€ өнгө","borderColor":"ХүрÑÑний өнгө","data":"Data","header":"Header","yes":"Тийм","no":"Үгүй","invalidWidth":"Ðүдний өргөн нь тоо байх Ñ‘Ñтой.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Сонгох"},"cellPad":"Ðүх доторлох(padding)","cellSpace":"Ðүх хоорондын зай (spacing)","column":{"menu":"Багана","insertBefore":"Багана өмнө нь оруулах","insertAfter":"Багана дараа нь оруулах","deleteColumn":"Багана уÑтгах"},"columns":"Багана","deleteTable":"Ð¥Ò¯ÑнÑгт уÑтгах","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Ð¥Ò¯ÑнÑгтийн өргөн нь тоо байх Ñ‘Ñтой.","menu":"Ð¥Ò¯ÑнÑгт","row":{"menu":"Мөр","insertBefore":"Мөр өмнө нь оруулах","insertAfter":"Мөр дараа нь оруулах","deleteRow":"Мөр уÑтгах"},"rows":"Мөр","summary":"Тайлбар","title":"Ð¥Ò¯ÑнÑгт","toolbar":"Ð¥Ò¯ÑнÑгт","widthPc":"хувь","widthPx":"цÑг","widthUnit":"өргөний нÑгж"},"undo":{"redo":"Өмнөх үйлдлÑÑ ÑÑргÑÑÑ…","undo":"Хүчингүй болгох"},"wsc":{"btnIgnore":"Зөвшөөрөх","btnIgnoreAll":"Бүгдийг зөвшөөрөх","btnReplace":"Солих","btnReplaceAll":"Бүгдийг Дарж бичих","btnUndo":"Буцаах","changeTo":"Өөрчлөх","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"ДүрÑм шалгагч Ñуугаагүй байна. Татаж авахыг Ñ…Ò¯Ñч байна уу?","manyChanges":"ДүрÑм шалгаад дууÑÑан: %1 үг өөрчлөгдÑөн","noChanges":"ДүрÑм шалгаад дууÑÑан: үг өөрчлөгдөөгүй","noMispell":"ДүрÑм шалгаад дууÑÑан: Ðлдаа олдÑонгүй","noSuggestions":"- Тайлбаргүй -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Толь бичиггүй","oneChange":"ДүрÑм шалгаад дууÑÑан: 1 үг өөрчлөгдÑөн","progress":"ДүрÑм шалгаж байгаа үйл Ñвц...","title":"Spell Checker","toolbar":"Үгийн дүрÑÑ… шалгах"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ms.js b/js/ckeditor/lang/ms.js
new file mode 100644
index 0000000..e5e083f
--- /dev/null
+++ b/js/ckeditor/lang/ms.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ms']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Browse Server","url":"URL","protocol":"Protokol","upload":"Muat Naik","uploadSubmit":"Hantar ke Server","image":"Gambar","flash":"Flash","form":"Borang","checkbox":"Checkbox","radio":"Butang Radio","textField":"Text Field","textarea":"Textarea","hiddenField":"Field Tersembunyi","button":"Butang","select":"Field Pilihan","imageButton":"Butang Bergambar","notSet":"<tidak di set>","id":"Id","name":"Nama","langDir":"Arah Tulisan","langDirLtr":"Kiri ke Kanan (LTR)","langDirRtl":"Kanan ke Kiri (RTL)","langCode":"Kod Bahasa","longDescr":"Butiran Panjang URL","cssClass":"Kelas-kelas Stylesheet","advisoryTitle":"Tajuk Makluman","cssStyle":"Stail","ok":"OK","cancel":"Batal","close":"Tutup","preview":"Prebiu","resize":"Resize","generalTab":"Umum","advancedTab":"Advanced","validateNumberFailed":"This value is not a number.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Options","target":"Sasaran","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Kiri ke Kanan (LTR)","langDirRTL":"Kanan ke Kiri (RTL)","styles":"Stail","cssClasses":"Kelas-kelas Stylesheet","width":"Lebar","height":"Tinggi","align":"Jajaran","alignLeft":"Kiri","alignRight":"Kanan","alignCenter":"Tengah","alignJustify":"Jajaran Blok","alignTop":"Atas","alignMiddle":"Pertengahan","alignBottom":"Bawah","alignNone":"None","invalidValue":"Nilai tidak sah.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Bold","italic":"Italic","strike":"Strike Through","subscript":"Subscript","superscript":"Superscript","underline":"Underline"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Salin","copyError":"Keselamatan perisian browser anda tidak membenarkan operasi salinan text/imej. Sila gunakan papan kekunci (Ctrl/Cmd+C).","cut":"Potong","cutError":"Keselamatan perisian browser anda tidak membenarkan operasi suntingan text/imej. Sila gunakan papan kekunci (Ctrl/Cmd+X).","paste":"Tampal","pasteArea":"Paste Area","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Tampal"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Alamat","tag_div":"Perenggan (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Telah Diformat"},"horizontalrule":{"toolbar":"Masukkan Garisan Membujur"},"image":{"alertUrl":"Sila taip URL untuk fail gambar","alt":"Text Alternatif","border":"Border","btnUpload":"Hantar ke Server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"Ruang Melintang","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Info Imej","linkTab":"Sambungan","lockRatio":"Tetapkan Nisbah","menu":"Ciri-ciri Imej","resetSize":"Saiz Set Semula","title":"Ciri-ciri Imej","titleButton":"Ciri-ciri Butang Bergambar","upload":"Muat Naik","urlMissing":"Image source URL is missing.","vSpace":"Ruang Menegak","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Tambahkan Inden","outdent":"Kurangkan Inden"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Kunci Akses","advanced":"Advanced","advisoryContentType":"Jenis Kandungan Makluman","advisoryTitle":"Tajuk Makluman","anchor":{"toolbar":"Masukkan/Sunting Pautan","menu":"Ciri-ciri Pautan","title":"Ciri-ciri Pautan","name":"Nama Pautan","errorName":"Sila taip nama pautan","remove":"Remove Anchor"},"anchorId":"dengan menggunakan ID elemen","anchorName":"dengan menggunakan nama pautan","charset":"Linked Resource Charset","cssClasses":"Kelas-kelas Stylesheet","emailAddress":"Alamat E-Mail","emailBody":"Isi Kandungan Mesej","emailSubject":"Subjek Mesej","id":"Id","info":"Butiran Sambungan","langCode":"Arah Tulisan","langDir":"Arah Tulisan","langDirLTR":"Kiri ke Kanan (LTR)","langDirRTL":"Kanan ke Kiri (RTL)","menu":"Sunting Sambungan","name":"Nama","noAnchors":"(Tiada pautan terdapat dalam dokumen ini)","noEmail":"Sila taip alamat e-mail","noUrl":"Sila taip sambungan URL","other":"<lain>","popupDependent":"Bergantungan (Netscape)","popupFeatures":"Ciri Tetingkap Popup","popupFullScreen":"Skrin Penuh (IE)","popupLeft":"Posisi Kiri","popupLocationBar":"Bar Lokasi","popupMenuBar":"Bar Menu","popupResizable":"Resizable","popupScrollBars":"Bar-bar skrol","popupStatusBar":"Bar Status","popupToolbar":"Toolbar","popupTop":"Posisi Atas","rel":"Relationship","selectAnchor":"Sila pilih pautan","styles":"Stail","tabIndex":"Indeks Tab ","target":"Sasaran","targetFrame":"<bingkai>","targetFrameName":"Nama Bingkai Sasaran","targetPopup":"<tetingkap popup>","targetPopupName":"Nama Tetingkap Popup","title":"Sambungan","toAnchor":"Pautan dalam muka surat ini","toEmail":"E-Mail","toUrl":"URL","toolbar":"Masukkan/Sunting Sambungan","type":"Jenis Sambungan","unlink":"Buang Sambungan","upload":"Muat Naik"},"list":{"bulletedlist":"Senarai tidak bernombor","numberedlist":"Senarai bernombor"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Tampal sebagai text biasa","title":"Tampal sebagai text biasa"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Tampal dari Word","toolbar":"Tampal dari Word"},"removeformat":{"toolbar":"Buang Format"},"sourcearea":{"toolbar":"Sumber"},"specialchar":{"options":"Special Character Options","title":"Sila pilih huruf istimewa","toolbar":"Masukkan Huruf Istimewa"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stail","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Saiz Border","caption":"Keterangan","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Buangkan Sel-sel","merge":"Cantumkan Sel-sel","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Tambahan Ruang Sel","cellSpace":"Ruangan Antara Sel","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Buangkan Lajur"},"columns":"Jaluran","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"None","headersRow":"First Row","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Ciri-ciri Jadual","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Buangkan Baris"},"rows":"Barisan","summary":"Summary","title":"Ciri-ciri Jadual","toolbar":"Jadual","widthPc":"peratus","widthPx":"piksel-piksel","widthUnit":"width unit"},"undo":{"redo":"Ulangkan","undo":"Batalkan"},"wsc":{"btnIgnore":"Biar","btnIgnoreAll":"Biarkan semua","btnReplace":"Ganti","btnReplaceAll":"Gantikan Semua","btnUndo":"Batalkan","changeTo":"Tukarkan kepada","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Pemeriksa ejaan tidak dipasang. Adakah anda mahu muat turun sekarang?","manyChanges":"Pemeriksaan ejaan siap: %1 perkataan diubah","noChanges":"Pemeriksaan ejaan siap: Tiada perkataan diubah","noMispell":"Pemeriksaan ejaan siap: Tiada salah ejaan","noSuggestions":"- Tiada cadangan -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Tidak terdapat didalam kamus","oneChange":"Pemeriksaan ejaan siap: Satu perkataan telah diubah","progress":"Pemeriksaan ejaan sedang diproses...","title":"Spell Checker","toolbar":"Semak Ejaan"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/nb.js b/js/ckeditor/lang/nb.js
new file mode 100644
index 0000000..778e06e
--- /dev/null
+++ b/js/ckeditor/lang/nb.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['nb']={"editor":"Rikteksteditor","editorPanel":"Panel for rikteksteditor","common":{"editorHelp":"Trykk ALT 0 for hjelp","browseServer":"Bla gjennom tjener","url":"URL","protocol":"Protokoll","upload":"Last opp","uploadSubmit":"Send det til serveren","image":"Bilde","flash":"Flash","form":"Skjema","checkbox":"Avmerkingsboks","radio":"Alternativknapp","textField":"Tekstboks","textarea":"Tekstområde","hiddenField":"Skjult felt","button":"Knapp","select":"Rullegardinliste","imageButton":"Bildeknapp","notSet":"<ikke satt>","id":"Id","name":"Navn","langDir":"Språkretning","langDirLtr":"Venstre til høyre (VTH)","langDirRtl":"Høyre til venstre (HTV)","langCode":"Språkkode","longDescr":"Utvidet beskrivelse","cssClass":"Stilarkklasser","advisoryTitle":"Tittel","cssStyle":"Stil","ok":"OK","cancel":"Avbryt","close":"Lukk","preview":"Forhåndsvis","resize":"Dra for å skalere","generalTab":"Generelt","advancedTab":"Avansert","validateNumberFailed":"Denne verdien er ikke et tall.","confirmNewPage":"Alle ulagrede endringer som er gjort i dette innholdet vil gå tapt. Er du sikker på at du vil laste en ny side?","confirmCancel":"Du har endret noen alternativer. Er du sikker på at du vil lukke dialogvinduet?","options":"Valg","target":"Mål","targetNew":"Nytt vindu (_blank)","targetTop":"Hele vindu (_top)","targetSelf":"Samme vindu (_self)","targetParent":"Foreldrevindu (_parent)","langDirLTR":"Venstre til høyre (VTH)","langDirRTL":"Høyre til venstre (HTV)","styles":"Stil","cssClasses":"Stilarkklasser","width":"Bredde","height":"Høyde","align":"Juster","alignLeft":"Venstre","alignRight":"Høyre","alignCenter":"Midtjuster","alignJustify":"Blokkjuster","alignTop":"Topp","alignMiddle":"Midten","alignBottom":"Bunn","alignNone":"Ingen","invalidValue":"Ugyldig verdi.","invalidHeight":"Høyde må være et tall.","invalidWidth":"Bredde må være et tall.","invalidCssLength":"Den angitte verdien for feltet \"%1\" må være et positivt tall med eller uten en gyldig CSS-målingsenhet (px, %, in, cm, mm, em, ex, pt, eller pc).","invalidHtmlLength":"Den angitte verdien for feltet \"%1\" må være et positivt tall med eller uten en gyldig HTML-målingsenhet (px eller %).","invalidInlineStyle":"Verdi angitt for inline stil må bestå av en eller flere sett med formatet \"navn : verdi\", separert med semikolon","cssLengthTooltip":"Skriv inn et tall for en piksel-verdi eller et tall med en gyldig CSS-enhet (px, %, in, cm, mm, em, ex, pt, eller pc).","unavailable":"%1<span class=\"cke_accessibility\">, utilgjenglig</span>"},"about":{"copy":"Copyright &copy; $1. Alle rettigheter reservert.","dlgTitle":"Om CKEditor","help":"Se $1 for hjelp.","moreInfo":"For lisensieringsinformasjon, vennligst besøk vårt nettsted:","title":"Om CKEditor","userGuide":"CKEditors brukerveiledning"},"basicstyles":{"bold":"Fet","italic":"Kursiv","strike":"Gjennomstreking","subscript":"Senket skrift","superscript":"Hevet skrift","underline":"Understreking"},"blockquote":{"toolbar":"Blokksitat"},"clipboard":{"copy":"Kopier","copyError":"Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst bruk tastatursnarveien (Ctrl/Cmd+C).","cut":"Klipp ut","cutError":"Din nettlesers sikkerhetsinstillinger tillater ikke automatisk utklipping av tekst. Vennligst bruk tastatursnarveien (Ctrl/Cmd+X).","paste":"Lim inn","pasteArea":"Innlimingsområde","pasteMsg":"Vennligst lim inn i følgende boks med tastaturet (<strong>Ctrl/Cmd+V</strong>) og trykk <strong>OK</strong>.","securityMsg":"Din nettlesers sikkerhetsinstillinger gir ikke redigeringsverktøyet direkte tilgang til utklippstavlen. Du må derfor lime det inn på nytt i dette vinduet.","title":"Lim inn"},"contextmenu":{"options":"Alternativer for høyreklikkmeny"},"button":{"selectedLabel":"%1 (Valgt)"},"toolbar":{"toolbarCollapse":"Skjul verktøylinje","toolbarExpand":"Vis verktøylinje","toolbarGroups":{"document":"Dokument","clipboard":"Utklippstavle/Angre","editing":"Redigering","forms":"Skjema","basicstyles":"Basisstiler","paragraph":"Avsnitt","links":"Lenker","insert":"Innsetting","styles":"Stiler","colors":"Farger","tools":"Verktøy"},"toolbars":"Verktøylinjer for editor"},"elementspath":{"eleLabel":"Element-sti","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Avsnittsformat","tag_address":"Adresse","tag_div":"Normal (DIV)","tag_h1":"Overskrift 1","tag_h2":"Overskrift 2","tag_h3":"Overskrift 3","tag_h4":"Overskrift 4","tag_h5":"Overskrift 5","tag_h6":"Overskrift 6","tag_p":"Normal","tag_pre":"Formatert"},"horizontalrule":{"toolbar":"Sett inn horisontal linje"},"image":{"alertUrl":"Vennligst skriv bilde-urlen","alt":"Alternativ tekst","border":"Ramme","btnUpload":"Send det til serveren","button2Img":"Vil du endre den valgte bildeknappen til et vanlig bilde?","hSpace":"HMarg","img2Button":"Vil du endre det valgte bildet til en bildeknapp?","infoTab":"Bildeinformasjon","linkTab":"Lenke","lockRatio":"Lås forhold","menu":"Bildeegenskaper","resetSize":"Tilbakestill størrelse","title":"Bildeegenskaper","titleButton":"Egenskaper for bildeknapp","upload":"Last opp","urlMissing":"Bildets adresse mangler.","vSpace":"VMarg","validateBorder":"Ramme må være et heltall.","validateHSpace":"HMarg må være et heltall.","validateVSpace":"VMarg må være et heltall."},"indent":{"indent":"Øk innrykk","outdent":"Reduser innrykk"},"fakeobjects":{"anchor":"Anker","flash":"Flash-animasjon","hiddenfield":"Skjult felt","iframe":"IFrame","unknown":"Ukjent objekt"},"link":{"acccessKey":"Aksessknapp","advanced":"Avansert","advisoryContentType":"Type","advisoryTitle":"Tittel","anchor":{"toolbar":"Sett inn/Rediger anker","menu":"Egenskaper for anker","title":"Egenskaper for anker","name":"Ankernavn","errorName":"Vennligst skriv inn ankernavnet","remove":"Fjern anker"},"anchorId":"Element etter ID","anchorName":"Anker etter navn","charset":"Lenket tegnsett","cssClasses":"Stilarkklasser","emailAddress":"E-postadresse","emailBody":"Melding","emailSubject":"Meldingsemne","id":"Id","info":"Lenkeinfo","langCode":"Språkkode","langDir":"Språkretning","langDirLTR":"Venstre til høyre (VTH)","langDirRTL":"Høyre til venstre (HTV)","menu":"Rediger lenke","name":"Navn","noAnchors":"(Ingen anker i dokumentet)","noEmail":"Vennligst skriv inn e-postadressen","noUrl":"Vennligst skriv inn lenkens URL","other":"<annen>","popupDependent":"Avhenging (Netscape)","popupFeatures":"Egenskaper for popup-vindu","popupFullScreen":"Fullskjerm (IE)","popupLeft":"Venstre posisjon","popupLocationBar":"Adresselinje","popupMenuBar":"Menylinje","popupResizable":"Skalerbar","popupScrollBars":"Scrollbar","popupStatusBar":"Statuslinje","popupToolbar":"Verktøylinje","popupTop":"Topp-posisjon","rel":"Relasjon (rel)","selectAnchor":"Velg et anker","styles":"Stil","tabIndex":"Tabindeks","target":"Mål","targetFrame":"<ramme>","targetFrameName":"Målramme","targetPopup":"<popup-vindu>","targetPopupName":"Navn på popup-vindu","title":"Lenke","toAnchor":"Lenke til anker i teksten","toEmail":"E-post","toUrl":"URL","toolbar":"Sett inn/Rediger lenke","type":"Lenketype","unlink":"Fjern lenke","upload":"Last opp"},"list":{"bulletedlist":"Legg til/Fjern punktmerket liste","numberedlist":"Legg til/Fjern nummerert liste"},"magicline":{"title":"Sett inn nytt avsnitt her"},"maximize":{"maximize":"Maksimer","minimize":"Minimer"},"pastetext":{"button":"Lim inn som ren tekst","title":"Lim inn som ren tekst"},"pastefromword":{"confirmCleanup":"Teksten du limer inn ser ut til å være kopiert fra Word. Vil du renske den før du limer den inn?","error":"Det var ikke mulig å renske den innlimte teksten på grunn av en intern feil","title":"Lim inn fra Word","toolbar":"Lim inn fra Word"},"removeformat":{"toolbar":"Fjern formatering"},"sourcearea":{"toolbar":"Kilde"},"specialchar":{"options":"Alternativer for spesialtegn","title":"Velg spesialtegn","toolbar":"Sett inn spesialtegn"},"scayt":{"btn_about":"Om SCAYT","btn_dictionaries":"Ordbøker","btn_disable":"Slå av SCAYT","btn_enable":"Slå på SCAYT","btn_langs":"Språk","btn_options":"Valg","text_title":"Stavekontroll mens du skriver"},"stylescombo":{"label":"Stil","panelTitle":"Stilformater","panelTitle1":"Blokkstiler","panelTitle2":"Inlinestiler","panelTitle3":"Objektstiler"},"table":{"border":"Rammestørrelse","caption":"Tittel","cell":{"menu":"Celle","insertBefore":"Sett inn celle før","insertAfter":"Sett inn celle etter","deleteCell":"Slett celler","merge":"Slå sammen celler","mergeRight":"Slå sammen høyre","mergeDown":"Slå sammen ned","splitHorizontal":"Del celle horisontalt","splitVertical":"Del celle vertikalt","title":"Celleegenskaper","cellType":"Celletype","rowSpan":"Radspenn","colSpan":"Kolonnespenn","wordWrap":"Tekstbrytning","hAlign":"Horisontal justering","vAlign":"Vertikal justering","alignBaseline":"Grunnlinje","bgColor":"Bakgrunnsfarge","borderColor":"Rammefarge","data":"Data","header":"Overskrift","yes":"Ja","no":"Nei","invalidWidth":"Cellebredde må være et tall.","invalidHeight":"Cellehøyde må være et tall.","invalidRowSpan":"Radspenn må være et heltall.","invalidColSpan":"Kolonnespenn må være et heltall.","chooseColor":"Velg"},"cellPad":"Cellepolstring","cellSpace":"Cellemarg","column":{"menu":"Kolonne","insertBefore":"Sett inn kolonne før","insertAfter":"Sett inn kolonne etter","deleteColumn":"Slett kolonner"},"columns":"Kolonner","deleteTable":"Slett tabell","headers":"Overskrifter","headersBoth":"Begge","headersColumn":"Første kolonne","headersNone":"Ingen","headersRow":"Første rad","invalidBorder":"Rammestørrelse må være et tall.","invalidCellPadding":"Cellepolstring må være et positivt tall.","invalidCellSpacing":"Cellemarg må være et positivt tall.","invalidCols":"Antall kolonner må være et tall større enn 0.","invalidHeight":"Tabellhøyde må være et tall.","invalidRows":"Antall rader må være et tall større enn 0.","invalidWidth":"Tabellbredde må være et tall.","menu":"Egenskaper for tabell","row":{"menu":"Rader","insertBefore":"Sett inn rad før","insertAfter":"Sett inn rad etter","deleteRow":"Slett rader"},"rows":"Rader","summary":"Sammendrag","title":"Egenskaper for tabell","toolbar":"Tabell","widthPc":"prosent","widthPx":"piksler","widthUnit":"Bredde-enhet"},"undo":{"redo":"Gjør om","undo":"Angre"},"wsc":{"btnIgnore":"Ignorer","btnIgnoreAll":"Ignorer alle","btnReplace":"Erstatt","btnReplaceAll":"Erstatt alle","btnUndo":"Angre","changeTo":"Endre til","errorLoading":"Feil under lasting av applikasjonstjenestetjener: %s.","ieSpellDownload":"Stavekontroll er ikke installert. Vil du laste den ned nå?","manyChanges":"Stavekontroll fullført: %1 ord endret","noChanges":"Stavekontroll fullført: ingen ord endret","noMispell":"Stavekontroll fullført: ingen feilstavinger funnet","noSuggestions":"- Ingen forslag -","notAvailable":"Beklager, tjenesten er utilgjenglig nå.","notInDic":"Ikke i ordboken","oneChange":"Stavekontroll fullført: Ett ord endret","progress":"Stavekontroll pågår...","title":"Stavekontroll","toolbar":"Stavekontroll"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/nl.js b/js/ckeditor/lang/nl.js
new file mode 100644
index 0000000..f991c9f
--- /dev/null
+++ b/js/ckeditor/lang/nl.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['nl']={"editor":"Tekstverwerker","editorPanel":"Tekstverwerker beheerpaneel","common":{"editorHelp":"Druk ALT 0 voor hulp","browseServer":"Bladeren op server","url":"URL","protocol":"Protocol","upload":"Upload","uploadSubmit":"Naar server verzenden","image":"Afbeelding","flash":"Flash","form":"Formulier","checkbox":"Selectievinkje","radio":"Keuzerondje","textField":"Tekstveld","textarea":"Tekstvak","hiddenField":"Verborgen veld","button":"Knop","select":"Selectieveld","imageButton":"Afbeeldingsknop","notSet":"<niet ingevuld>","id":"Id","name":"Naam","langDir":"Schrijfrichting","langDirLtr":"Links naar rechts (LTR)","langDirRtl":"Rechts naar links (RTL)","langCode":"Taalcode","longDescr":"Lange URL-omschrijving","cssClass":"Stylesheet-klassen","advisoryTitle":"Adviserende titel","cssStyle":"Stijl","ok":"OK","cancel":"Annuleren","close":"Sluiten","preview":"Voorbeeld","resize":"Sleep om te herschalen","generalTab":"Algemeen","advancedTab":"Geavanceerd","validateNumberFailed":"Deze waarde is geen geldig getal.","confirmNewPage":"Alle aangebrachte wijzigingen gaan verloren. Weet u zeker dat u een nieuwe pagina wilt openen?","confirmCancel":"Enkele opties zijn gewijzigd. Weet u zeker dat u dit dialoogvenster wilt sluiten?","options":"Opties","target":"Doelvenster","targetNew":"Nieuw venster (_blank)","targetTop":"Hele venster (_top)","targetSelf":"Zelfde venster (_self)","targetParent":"Origineel venster (_parent)","langDirLTR":"Links naar rechts (LTR)","langDirRTL":"Rechts naar links (RTL)","styles":"Stijl","cssClasses":"Stylesheet-klassen","width":"Breedte","height":"Hoogte","align":"Uitlijning","alignLeft":"Links","alignRight":"Rechts","alignCenter":"Centreren","alignJustify":"Uitvullen","alignTop":"Boven","alignMiddle":"Midden","alignBottom":"Onder","alignNone":"Geen","invalidValue":"Ongeldige waarde.","invalidHeight":"De hoogte moet een getal zijn.","invalidWidth":"De breedte moet een getal zijn.","invalidCssLength":"Waarde in veld \"%1\" moet een positief nummer zijn, met of zonder een geldige CSS meeteenheid (px, %, in, cm, mm, em, ex, pt of pc).","invalidHtmlLength":"Waarde in veld \"%1\" moet een positief nummer zijn, met of zonder een geldige HTML meeteenheid (px of %).","invalidInlineStyle":"Waarde voor de online stijl moet bestaan uit een of meerdere tupels met het formaat \"naam : waarde\", gescheiden door puntkomma's.","cssLengthTooltip":"Geef een nummer in voor een waarde in pixels of geef een nummer in met een geldige CSS eenheid (px, %, in, cm, mm, em, ex, pt, of pc).","unavailable":"%1<span class=\"cke_accessibility\">, niet beschikbaar</span>"},"about":{"copy":"Copyright &copy; $1. Alle rechten voorbehouden.","dlgTitle":"Over CKEditor","help":"Bekijk de $1 voor hulp.","moreInfo":"Bezoek onze website voor licentieinformatie:","title":"Over CKEditor","userGuide":"CKEditor gebruiksaanwijzing"},"basicstyles":{"bold":"Vet","italic":"Cursief","strike":"Doorhalen","subscript":"Subscript","superscript":"Superscript","underline":"Onderstrepen"},"blockquote":{"toolbar":"Citaatblok"},"clipboard":{"copy":"Kopiëren","copyError":"De beveiligingsinstelling van de browser verhinderen het automatisch kopiëren. Gebruik de sneltoets Ctrl/Cmd+C van het toetsenbord.","cut":"Knippen","cutError":"De beveiligingsinstelling van de browser verhinderen het automatisch knippen. Gebruik de sneltoets Ctrl/Cmd+X van het toetsenbord.","paste":"Plakken","pasteArea":"Plakgebied","pasteMsg":"Plak de tekst in het volgende vak gebruikmakend van uw toetsenbord (<strong>Ctrl/Cmd+V</strong>) en klik op OK.","securityMsg":"Door de beveiligingsinstellingen van uw browser is het niet mogelijk om direct vanuit het klembord in de editor te plakken. Middels opnieuw plakken in dit venster kunt u de tekst alsnog plakken in de editor.","title":"Plakken"},"contextmenu":{"options":"Contextmenu opties"},"button":{"selectedLabel":"%1 (Geselecteerd)"},"toolbar":{"toolbarCollapse":"Werkbalk inklappen","toolbarExpand":"Werkbalk uitklappen","toolbarGroups":{"document":"Document","clipboard":"Klembord/Ongedaan maken","editing":"Bewerken","forms":"Formulieren","basicstyles":"Basisstijlen","paragraph":"Paragraaf","links":"Links","insert":"Invoegen","styles":"Stijlen","colors":"Kleuren","tools":"Toepassingen"},"toolbars":"Werkbalken"},"elementspath":{"eleLabel":"Elementenpad","eleTitle":"%1 element"},"format":{"label":"Opmaak","panelTitle":"Opmaak","tag_address":"Adres","tag_div":"Normaal (DIV)","tag_h1":"Kop 1","tag_h2":"Kop 2","tag_h3":"Kop 3","tag_h4":"Kop 4","tag_h5":"Kop 5","tag_h6":"Kop 6","tag_p":"Normaal","tag_pre":"Met opmaak"},"horizontalrule":{"toolbar":"Horizontale lijn invoegen"},"image":{"alertUrl":"Geef de URL van de afbeelding","alt":"Alternatieve tekst","border":"Rand","btnUpload":"Naar server verzenden","button2Img":"Wilt u de geselecteerde afbeeldingsknop vervangen door een eenvoudige afbeelding?","hSpace":"HSpace","img2Button":"Wilt u de geselecteerde afbeelding vervangen door een afbeeldingsknop?","infoTab":"Informatie afbeelding","linkTab":"Link","lockRatio":"Afmetingen vergrendelen","menu":"Eigenschappen afbeelding","resetSize":"Afmetingen resetten","title":"Eigenschappen afbeelding","titleButton":"Eigenschappen afbeeldingsknop","upload":"Upload","urlMissing":"De URL naar de afbeelding ontbreekt.","vSpace":"VSpace","validateBorder":"Rand moet een heel nummer zijn.","validateHSpace":"HSpace moet een heel nummer zijn.","validateVSpace":"VSpace moet een heel nummer zijn."},"indent":{"indent":"Inspringing vergroten","outdent":"Inspringing verkleinen"},"fakeobjects":{"anchor":"Interne link","flash":"Flash animatie","hiddenfield":"Verborgen veld","iframe":"IFrame","unknown":"Onbekend object"},"link":{"acccessKey":"Toegangstoets","advanced":"Geavanceerd","advisoryContentType":"Aanbevolen content-type","advisoryTitle":"Adviserende titel","anchor":{"toolbar":"Interne link","menu":"Eigenschappen interne link","title":"Eigenschappen interne link","name":"Naam interne link","errorName":"Geef de naam van de interne link op","remove":"Interne link verwijderen"},"anchorId":"Op kenmerk interne link","anchorName":"Op naam interne link","charset":"Karakterset van gelinkte bron","cssClasses":"Stylesheet-klassen","emailAddress":"E-mailadres","emailBody":"Inhoud bericht","emailSubject":"Onderwerp bericht","id":"Id","info":"Linkomschrijving","langCode":"Taalcode","langDir":"Schrijfrichting","langDirLTR":"Links naar rechts (LTR)","langDirRTL":"Rechts naar links (RTL)","menu":"Link wijzigen","name":"Naam","noAnchors":"(Geen interne links in document gevonden)","noEmail":"Geef een e-mailadres","noUrl":"Geef de link van de URL","other":"<ander>","popupDependent":"Afhankelijk (Netscape)","popupFeatures":"Instellingen popupvenster","popupFullScreen":"Volledig scherm (IE)","popupLeft":"Positie links","popupLocationBar":"Locatiemenu","popupMenuBar":"Menubalk","popupResizable":"Herschaalbaar","popupScrollBars":"Schuifbalken","popupStatusBar":"Statusbalk","popupToolbar":"Werkbalk","popupTop":"Positie boven","rel":"Relatie","selectAnchor":"Kies een interne link","styles":"Stijl","tabIndex":"Tabvolgorde","target":"Doelvenster","targetFrame":"<frame>","targetFrameName":"Naam doelframe","targetPopup":"<popupvenster>","targetPopupName":"Naam popupvenster","title":"Link","toAnchor":"Interne link in pagina","toEmail":"E-mail","toUrl":"URL","toolbar":"Link invoegen/wijzigen","type":"Linktype","unlink":"Link verwijderen","upload":"Upload"},"list":{"bulletedlist":"Opsomming invoegen","numberedlist":"Genummerde lijst invoegen"},"magicline":{"title":"Hier paragraaf invoeren"},"maximize":{"maximize":"Maximaliseren","minimize":"Minimaliseren"},"pastetext":{"button":"Plakken als platte tekst","title":"Plakken als platte tekst"},"pastefromword":{"confirmCleanup":"De tekst die u wilt plakken lijkt gekopieerd te zijn vanuit Word. Wilt u de tekst opschonen voordat deze geplakt wordt?","error":"Het was niet mogelijk om de geplakte tekst op te schonen door een interne fout","title":"Plakken vanuit Word","toolbar":"Plakken vanuit Word"},"removeformat":{"toolbar":"Opmaak verwijderen"},"sourcearea":{"toolbar":"Broncode"},"specialchar":{"options":"Speciale tekens opties","title":"Selecteer speciaal teken","toolbar":"Speciaal teken invoegen"},"scayt":{"btn_about":"Over SCAYT","btn_dictionaries":"Woordenboeken","btn_disable":"SCAYT uitschakelen","btn_enable":"SCAYT inschakelen","btn_langs":"Talen","btn_options":"Opties","text_title":"Controleer de spelling tijdens het typen"},"stylescombo":{"label":"Stijl","panelTitle":"Opmaakstijlen","panelTitle1":"Blok stijlen","panelTitle2":"Inline stijlen","panelTitle3":"Object stijlen"},"table":{"border":"Randdikte","caption":"Onderschrift","cell":{"menu":"Cel","insertBefore":"Voeg cel in voor","insertAfter":"Voeg cel in na","deleteCell":"Cellen verwijderen","merge":"Cellen samenvoegen","mergeRight":"Voeg samen naar rechts","mergeDown":"Voeg samen naar beneden","splitHorizontal":"Splits cel horizontaal","splitVertical":"Splits cel vertikaal","title":"Celeigenschappen","cellType":"Celtype","rowSpan":"Rijen samenvoegen","colSpan":"Kolommen samenvoegen","wordWrap":"Automatische terugloop","hAlign":"Horizontale uitlijning","vAlign":"Verticale uitlijning","alignBaseline":"Tekstregel","bgColor":"Achtergrondkleur","borderColor":"Randkleur","data":"Gegevens","header":"Kop","yes":"Ja","no":"Nee","invalidWidth":"De celbreedte moet een getal zijn.","invalidHeight":"De celhoogte moet een getal zijn.","invalidRowSpan":"Rijen samenvoegen moet een heel getal zijn.","invalidColSpan":"Kolommen samenvoegen moet een heel getal zijn.","chooseColor":"Kies"},"cellPad":"Celopvulling","cellSpace":"Celafstand","column":{"menu":"Kolom","insertBefore":"Voeg kolom in voor","insertAfter":"Voeg kolom in na","deleteColumn":"Kolommen verwijderen"},"columns":"Kolommen","deleteTable":"Tabel verwijderen","headers":"Koppen","headersBoth":"Beide","headersColumn":"Eerste kolom","headersNone":"Geen","headersRow":"Eerste rij","invalidBorder":"De randdikte moet een getal zijn.","invalidCellPadding":"Celopvulling moet een getal zijn.","invalidCellSpacing":"Celafstand moet een getal zijn.","invalidCols":"Het aantal kolommen moet een getal zijn groter dan 0.","invalidHeight":"De tabelhoogte moet een getal zijn.","invalidRows":"Het aantal rijen moet een getal zijn groter dan 0.","invalidWidth":"De tabelbreedte moet een getal zijn.","menu":"Tabeleigenschappen","row":{"menu":"Rij","insertBefore":"Voeg rij in voor","insertAfter":"Voeg rij in na","deleteRow":"Rijen verwijderen"},"rows":"Rijen","summary":"Samenvatting","title":"Tabeleigenschappen","toolbar":"Tabel","widthPc":"procent","widthPx":"pixels","widthUnit":"eenheid breedte"},"undo":{"redo":"Opnieuw uitvoeren","undo":"Ongedaan maken"},"wsc":{"btnIgnore":"Negeren","btnIgnoreAll":"Alles negeren","btnReplace":"Vervangen","btnReplaceAll":"Alles vervangen","btnUndo":"Ongedaan maken","changeTo":"Wijzig in","errorLoading":"Er is een fout opgetreden bij het laden van de dienst: %s.","ieSpellDownload":"De spellingscontrole is niet geïnstalleerd. Wilt u deze nu downloaden?","manyChanges":"Klaar met spellingscontrole: %1 woorden aangepast","noChanges":"Klaar met spellingscontrole: geen woorden aangepast","noMispell":"Klaar met spellingscontrole: geen fouten gevonden","noSuggestions":"- Geen suggesties -","notAvailable":"Excuses, deze dienst is momenteel niet beschikbaar.","notInDic":"Niet in het woordenboek","oneChange":"Klaar met spellingscontrole: één woord aangepast","progress":"Bezig met spellingscontrole...","title":"Spellingscontrole","toolbar":"Spellingscontrole"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/no.js b/js/ckeditor/lang/no.js
new file mode 100644
index 0000000..3bbb660
--- /dev/null
+++ b/js/ckeditor/lang/no.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['no']={"editor":"Rikteksteditor","editorPanel":"Panel for rikteksteditor","common":{"editorHelp":"Trykk ALT 0 for hjelp","browseServer":"Bla igjennom server","url":"URL","protocol":"Protokoll","upload":"Last opp","uploadSubmit":"Send det til serveren","image":"Bilde","flash":"Flash","form":"Skjema","checkbox":"Avmerkingsboks","radio":"Alternativknapp","textField":"Tekstboks","textarea":"Tekstområde","hiddenField":"Skjult felt","button":"Knapp","select":"Rullegardinliste","imageButton":"Bildeknapp","notSet":"<ikke satt>","id":"Id","name":"Navn","langDir":"Språkretning","langDirLtr":"Venstre til høyre (VTH)","langDirRtl":"Høyre til venstre (HTV)","langCode":"Språkkode","longDescr":"Utvidet beskrivelse","cssClass":"Stilarkklasser","advisoryTitle":"Tittel","cssStyle":"Stil","ok":"OK","cancel":"Avbryt","close":"Lukk","preview":"Forhåndsvis","resize":"Dra for å skalere","generalTab":"Generelt","advancedTab":"Avansert","validateNumberFailed":"Denne verdien er ikke et tall.","confirmNewPage":"Alle ulagrede endringer som er gjort i dette innholdet vil bli tapt. Er du sikker på at du vil laste en ny side?","confirmCancel":"Noen av valgene har blitt endret. Er du sikker på at du vil lukke dialogen?","options":"Valg","target":"Mål","targetNew":"Nytt vindu (_blank)","targetTop":"Hele vindu (_top)","targetSelf":"Samme vindu (_self)","targetParent":"Foreldrevindu (_parent)","langDirLTR":"Venstre til høyre (VTH)","langDirRTL":"Høyre til venstre (HTV)","styles":"Stil","cssClasses":"Stilarkklasser","width":"Bredde","height":"Høyde","align":"Juster","alignLeft":"Venstre","alignRight":"Høyre","alignCenter":"Midtjuster","alignJustify":"Blokkjuster","alignTop":"Topp","alignMiddle":"Midten","alignBottom":"Bunn","alignNone":"Ingen","invalidValue":"Ugyldig verdi.","invalidHeight":"Høyde må være et tall.","invalidWidth":"Bredde må være et tall.","invalidCssLength":"Den angitte verdien for feltet \"%1\" må være et positivt tall med eller uten en gyldig CSS-målingsenhet (px, %, in, cm, mm, em, ex, pt, eller pc).","invalidHtmlLength":"Den angitte verdien for feltet \"%1\" må være et positivt tall med eller uten en gyldig HTML-målingsenhet (px eller %).","invalidInlineStyle":"Verdi angitt for inline stil må bestå av en eller flere sett med formatet \"navn : verdi\", separert med semikolon","cssLengthTooltip":"Skriv inn et tall for en piksel-verdi eller et tall med en gyldig CSS-enhet (px, %, in, cm, mm, em, ex, pt, eller pc).","unavailable":"%1<span class=\"cke_accessibility\">, utilgjenglig</span>"},"about":{"copy":"Copyright &copy; $1. Alle rettigheter reservert.","dlgTitle":"Om CKEditor","help":"Se $1 for hjelp.","moreInfo":"For lisensieringsinformasjon, vennligst besøk vårt nettsted:","title":"Om CKEditor","userGuide":"CKEditors brukerveiledning"},"basicstyles":{"bold":"Fet","italic":"Kursiv","strike":"Gjennomstreking","subscript":"Senket skrift","superscript":"Hevet skrift","underline":"Understreking"},"blockquote":{"toolbar":"Blokksitat"},"clipboard":{"copy":"Kopier","copyError":"Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst bruk snarveien (Ctrl/Cmd+C).","cut":"Klipp ut","cutError":"Din nettlesers sikkerhetsinstillinger tillater ikke automatisk utklipping av tekst. Vennligst bruk snarveien (Ctrl/Cmd+X).","paste":"Lim inn","pasteArea":"Innlimingsområde","pasteMsg":"Vennligst lim inn i følgende boks med tastaturet (<STRONG>Ctrl/Cmd+V</STRONG>) og trykk <STRONG>OK</STRONG>.","securityMsg":"Din nettlesers sikkerhetsinstillinger gir ikke redigeringsverktøyet direkte tilgang til utklippstavlen. Du må derfor lime det inn på nytt i dette vinduet.","title":"Lim inn"},"contextmenu":{"options":"Alternativer for høyreklikkmeny"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Skjul verktøylinje","toolbarExpand":"Vis verktøylinje","toolbarGroups":{"document":"Dokument","clipboard":"Utklippstavle/Angre","editing":"Redigering","forms":"Skjema","basicstyles":"Basisstiler","paragraph":"Avsnitt","links":"Lenker","insert":"Innsetting","styles":"Stiler","colors":"Farger","tools":"Verktøy"},"toolbars":"Verktøylinjer for editor"},"elementspath":{"eleLabel":"Element-sti","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Avsnittsformat","tag_address":"Adresse","tag_div":"Normal (DIV)","tag_h1":"Overskrift 1","tag_h2":"Overskrift 2","tag_h3":"Overskrift 3","tag_h4":"Overskrift 4","tag_h5":"Overskrift 5","tag_h6":"Overskrift 6","tag_p":"Normal","tag_pre":"Formatert"},"horizontalrule":{"toolbar":"Sett inn horisontal linje"},"image":{"alertUrl":"Vennligst skriv bilde-urlen","alt":"Alternativ tekst","border":"Ramme","btnUpload":"Send det til serveren","button2Img":"Vil du endre den valgte bildeknappen til et vanlig bilde?","hSpace":"HMarg","img2Button":"Vil du endre det valgte bildet til en bildeknapp?","infoTab":"Bildeinformasjon","linkTab":"Lenke","lockRatio":"Lås forhold","menu":"Bildeegenskaper","resetSize":"Tilbakestill størrelse","title":"Bildeegenskaper","titleButton":"Egenskaper for bildeknapp","upload":"Last opp","urlMissing":"Bildets adresse mangler.","vSpace":"VMarg","validateBorder":"Ramme må være et heltall.","validateHSpace":"HMarg må være et heltall.","validateVSpace":"VMarg må være et heltall."},"indent":{"indent":"Øk innrykk","outdent":"Reduser innrykk"},"fakeobjects":{"anchor":"Anker","flash":"Flash-animasjon","hiddenfield":"Skjult felt","iframe":"IFrame","unknown":"Ukjent objekt"},"link":{"acccessKey":"Aksessknapp","advanced":"Avansert","advisoryContentType":"Type","advisoryTitle":"Tittel","anchor":{"toolbar":"Sett inn/Rediger anker","menu":"Egenskaper for anker","title":"Egenskaper for anker","name":"Ankernavn","errorName":"Vennligst skriv inn ankernavnet","remove":"Fjern anker"},"anchorId":"Element etter ID","anchorName":"Anker etter navn","charset":"Lenket tegnsett","cssClasses":"Stilarkklasser","emailAddress":"E-postadresse","emailBody":"Melding","emailSubject":"Meldingsemne","id":"Id","info":"Lenkeinfo","langCode":"Språkkode","langDir":"Språkretning","langDirLTR":"Venstre til høyre (VTH)","langDirRTL":"Høyre til venstre (HTV)","menu":"Rediger lenke","name":"Navn","noAnchors":"(Ingen anker i dokumentet)","noEmail":"Vennligst skriv inn e-postadressen","noUrl":"Vennligst skriv inn lenkens URL","other":"<annen>","popupDependent":"Avhenging (Netscape)","popupFeatures":"Egenskaper for popup-vindu","popupFullScreen":"Fullskjerm (IE)","popupLeft":"Venstre posisjon","popupLocationBar":"Adresselinje","popupMenuBar":"Menylinje","popupResizable":"Skalerbar","popupScrollBars":"Scrollbar","popupStatusBar":"Statuslinje","popupToolbar":"Verktøylinje","popupTop":"Topp-posisjon","rel":"Relasjon (rel)","selectAnchor":"Velg et anker","styles":"Stil","tabIndex":"Tabindeks","target":"Mål","targetFrame":"<ramme>","targetFrameName":"Målramme","targetPopup":"<popup-vindu>","targetPopupName":"Navn på popup-vindu","title":"Lenke","toAnchor":"Lenke til anker i teksten","toEmail":"E-post","toUrl":"URL","toolbar":"Sett inn/Rediger lenke","type":"Lenketype","unlink":"Fjern lenke","upload":"Last opp"},"list":{"bulletedlist":"Legg til/Fjern punktmerket liste","numberedlist":"Legg til/Fjern nummerert liste"},"magicline":{"title":"Sett inn nytt avsnitt her"},"maximize":{"maximize":"Maksimer","minimize":"Minimer"},"pastetext":{"button":"Lim inn som ren tekst","title":"Lim inn som ren tekst"},"pastefromword":{"confirmCleanup":"Teksten du limer inn ser ut til å være kopiert fra Word. Vil du renske den før du limer den inn?","error":"Det var ikke mulig å renske den innlimte teksten på grunn av en intern feil","title":"Lim inn fra Word","toolbar":"Lim inn fra Word"},"removeformat":{"toolbar":"Fjern formatering"},"sourcearea":{"toolbar":"Kilde"},"specialchar":{"options":"Alternativer for spesialtegn","title":"Velg spesialtegn","toolbar":"Sett inn spesialtegn"},"scayt":{"btn_about":"Om SCAYT","btn_dictionaries":"Ordbøker","btn_disable":"Slå av SCAYT","btn_enable":"Slå på SCAYT","btn_langs":"Språk","btn_options":"Valg","text_title":"Stavekontroll mens du skriver"},"stylescombo":{"label":"Stil","panelTitle":"Stilformater","panelTitle1":"Blokkstiler","panelTitle2":"Inlinestiler","panelTitle3":"Objektstiler"},"table":{"border":"Rammestørrelse","caption":"Tittel","cell":{"menu":"Celle","insertBefore":"Sett inn celle før","insertAfter":"Sett inn celle etter","deleteCell":"Slett celler","merge":"Slå sammen celler","mergeRight":"Slå sammen høyre","mergeDown":"Slå sammen ned","splitHorizontal":"Del celle horisontalt","splitVertical":"Del celle vertikalt","title":"Celleegenskaper","cellType":"Celletype","rowSpan":"Radspenn","colSpan":"Kolonnespenn","wordWrap":"Tekstbrytning","hAlign":"Horisontal justering","vAlign":"Vertikal justering","alignBaseline":"Grunnlinje","bgColor":"Bakgrunnsfarge","borderColor":"Rammefarge","data":"Data","header":"Overskrift","yes":"Ja","no":"Nei","invalidWidth":"Cellebredde må være et tall.","invalidHeight":"Cellehøyde må være et tall.","invalidRowSpan":"Radspenn må være et heltall.","invalidColSpan":"Kolonnespenn må være et heltall.","chooseColor":"Velg"},"cellPad":"Cellepolstring","cellSpace":"Cellemarg","column":{"menu":"Kolonne","insertBefore":"Sett inn kolonne før","insertAfter":"Sett inn kolonne etter","deleteColumn":"Slett kolonner"},"columns":"Kolonner","deleteTable":"Slett tabell","headers":"Overskrifter","headersBoth":"Begge","headersColumn":"Første kolonne","headersNone":"Ingen","headersRow":"Første rad","invalidBorder":"Rammestørrelse må være et tall.","invalidCellPadding":"Cellepolstring må være et positivt tall.","invalidCellSpacing":"Cellemarg må være et positivt tall.","invalidCols":"Antall kolonner må være et tall større enn 0.","invalidHeight":"Tabellhøyde må være et tall.","invalidRows":"Antall rader må være et tall større enn 0.","invalidWidth":"Tabellbredde må være et tall.","menu":"Egenskaper for tabell","row":{"menu":"Rader","insertBefore":"Sett inn rad før","insertAfter":"Sett inn rad etter","deleteRow":"Slett rader"},"rows":"Rader","summary":"Sammendrag","title":"Egenskaper for tabell","toolbar":"Tabell","widthPc":"prosent","widthPx":"piksler","widthUnit":"Bredde-enhet"},"undo":{"redo":"Gjør om","undo":"Angre"},"wsc":{"btnIgnore":"Ignorer","btnIgnoreAll":"Ignorer alle","btnReplace":"Erstatt","btnReplaceAll":"Erstatt alle","btnUndo":"Angre","changeTo":"Endre til","errorLoading":"Feil under lasting av applikasjonstjenestetjener: %s.","ieSpellDownload":"Stavekontroll er ikke installert. Vil du laste den ned nå?","manyChanges":"Stavekontroll fullført: %1 ord endret","noChanges":"Stavekontroll fullført: ingen ord endret","noMispell":"Stavekontroll fullført: ingen feilstavinger funnet","noSuggestions":"- Ingen forslag -","notAvailable":"Beklager, tjenesten er utilgjenglig nå.","notInDic":"Ikke i ordboken","oneChange":"Stavekontroll fullført: Ett ord endret","progress":"Stavekontroll pågår...","title":"Stavekontroll","toolbar":"Stavekontroll"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/pl.js b/js/ckeditor/lang/pl.js
new file mode 100644
index 0000000..1276d6a
--- /dev/null
+++ b/js/ckeditor/lang/pl.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['pl']={"editor":"Edytor tekstu sformatowanego","editorPanel":"Panel edytora tekstu sformatowanego","common":{"editorHelp":"W celu uzyskania pomocy naciśnij ALT 0","browseServer":"Przeglądaj","url":"Adres URL","protocol":"Protokół","upload":"Wyślij","uploadSubmit":"Wyślij","image":"Obrazek","flash":"Flash","form":"Formularz","checkbox":"Pole wyboru (checkbox)","radio":"Przycisk opcji (radio)","textField":"Pole tekstowe","textarea":"Obszar tekstowy","hiddenField":"Pole ukryte","button":"Przycisk","select":"Lista wyboru","imageButton":"Przycisk graficzny","notSet":"<nie ustawiono>","id":"Id","name":"Nazwa","langDir":"Kierunek tekstu","langDirLtr":"Od lewej do prawej (LTR)","langDirRtl":"Od prawej do lewej (RTL)","langCode":"Kod języka","longDescr":"Adres URL długiego opisu","cssClass":"Nazwa klasy CSS","advisoryTitle":"Opis obiektu docelowego","cssStyle":"Styl","ok":"OK","cancel":"Anuluj","close":"Zamknij","preview":"Podgląd","resize":"Przeciągnij, aby zmienić rozmiar","generalTab":"Ogólne","advancedTab":"Zaawansowane","validateNumberFailed":"Ta wartość nie jest liczbą.","confirmNewPage":"Wszystkie niezapisane zmiany zostaną utracone. Czy na pewno wczytać nową stronę?","confirmCancel":"Pewne opcje zostały zmienione. Czy na pewno zamknąć okno dialogowe?","options":"Opcje","target":"Obiekt docelowy","targetNew":"Nowe okno (_blank)","targetTop":"Okno najwyżej w hierarchii (_top)","targetSelf":"To samo okno (_self)","targetParent":"Okno nadrzędne (_parent)","langDirLTR":"Od lewej do prawej (LTR)","langDirRTL":"Od prawej do lewej (RTL)","styles":"Style","cssClasses":"Klasy arkusza stylów","width":"Szerokość","height":"Wysokość","align":"Wyrównaj","alignLeft":"Do lewej","alignRight":"Do prawej","alignCenter":"Do środka","alignJustify":"Wyjustuj","alignTop":"Do góry","alignMiddle":"Do środka","alignBottom":"Do dołu","alignNone":"Brak","invalidValue":"Nieprawidłowa wartość.","invalidHeight":"Wysokość musi być liczbą.","invalidWidth":"Szerokość musi być liczbą.","invalidCssLength":"Wartość podana dla pola \"%1\" musi być liczbą dodatnią bez jednostki lub z poprawną jednostką długości zgodną z CSS (px, %, in, cm, mm, em, ex, pt lub pc).","invalidHtmlLength":"Wartość podana dla pola \"%1\" musi być liczbą dodatnią bez jednostki lub z poprawną jednostką długości zgodną z HTML (px lub %).","invalidInlineStyle":"Wartość podana dla stylu musi składać się z jednej lub większej liczby krotek w formacie \"nazwa : wartość\", rozdzielonych średnikami.","cssLengthTooltip":"Wpisz liczbę dla wartości w pikselach lub liczbę wraz z jednostką długości zgodną z CSS (px, %, in, cm, mm, em, ex, pt lub pc).","unavailable":"%1<span class=\"cke_accessibility\">, niedostępne</span>"},"about":{"copy":"Copyright &copy; $1. Wszelkie prawa zastrzeżone.","dlgTitle":"Informacje o programie CKEditor","help":"Pomoc znajdziesz w $1.","moreInfo":"Informacje na temat licencji można znaleźć na naszej stronie:","title":"Informacje o programie CKEditor","userGuide":"podręczniku użytkownika programu CKEditor"},"basicstyles":{"bold":"Pogrubienie","italic":"Kursywa","strike":"Przekreślenie","subscript":"Indeks dolny","superscript":"Indeks górny","underline":"Podkreślenie"},"blockquote":{"toolbar":"Cytat"},"clipboard":{"copy":"Kopiuj","copyError":"Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne kopiowanie tekstu. Użyj skrótu klawiszowego Ctrl/Cmd+C.","cut":"Wytnij","cutError":"Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne wycinanie tekstu. Użyj skrótu klawiszowego Ctrl/Cmd+X.","paste":"Wklej","pasteArea":"Obszar wklejania","pasteMsg":"Wklej tekst w poniższym polu, używając skrótu klawiaturowego (<STRONG>Ctrl/Cmd+V</STRONG>), i kliknij <STRONG>OK</STRONG>.","securityMsg":"Zabezpieczenia przeglądarki uniemożliwiają wklejenie danych bezpośrednio do edytora. Proszę ponownie wkleić dane w tym oknie.","title":"Wklej"},"contextmenu":{"options":"Opcje menu kontekstowego"},"button":{"selectedLabel":"%1 (Wybrany)"},"toolbar":{"toolbarCollapse":"Zwiń pasek narzędzi","toolbarExpand":"Rozwiń pasek narzędzi","toolbarGroups":{"document":"Dokument","clipboard":"Schowek/Wstecz","editing":"Edycja","forms":"Formularze","basicstyles":"Style podstawowe","paragraph":"Akapit","links":"Hiperłącza","insert":"Wstawianie","styles":"Style","colors":"Kolory","tools":"Narzędzia"},"toolbars":"Paski narzędzi edytora"},"elementspath":{"eleLabel":"Ścieżka elementów","eleTitle":"element %1"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Adres","tag_div":"Normalny (DIV)","tag_h1":"Nagłówek 1","tag_h2":"Nagłówek 2","tag_h3":"Nagłówek 3","tag_h4":"Nagłówek 4","tag_h5":"Nagłówek 5","tag_h6":"Nagłówek 6","tag_p":"Normalny","tag_pre":"Tekst sformatowany"},"horizontalrule":{"toolbar":"Wstaw poziomą linię"},"image":{"alertUrl":"Podaj adres obrazka.","alt":"Tekst zastępczy","border":"Obramowanie","btnUpload":"Wyślij","button2Img":"Czy chcesz przekonwertować zaznaczony przycisk graficzny do zwykłego obrazka?","hSpace":"Odstęp poziomy","img2Button":"Czy chcesz przekonwertować zaznaczony obrazek do przycisku graficznego?","infoTab":"Informacje o obrazku","linkTab":"Hiperłącze","lockRatio":"Zablokuj proporcje","menu":"Właściwości obrazka","resetSize":"Przywróć rozmiar","title":"Właściwości obrazka","titleButton":"Właściwości przycisku graficznego","upload":"Wyślij","urlMissing":"Podaj adres URL obrazka.","vSpace":"Odstęp pionowy","validateBorder":"Wartość obramowania musi być liczbą całkowitą.","validateHSpace":"Wartość odstępu poziomego musi być liczbą całkowitą.","validateVSpace":"Wartość odstępu pionowego musi być liczbą całkowitą."},"indent":{"indent":"Zwiększ wcięcie","outdent":"Zmniejsz wcięcie"},"fakeobjects":{"anchor":"Kotwica","flash":"Animacja Flash","hiddenfield":"Pole ukryte","iframe":"IFrame","unknown":"Nieznany obiekt"},"link":{"acccessKey":"Klawisz dostępu","advanced":"Zaawansowane","advisoryContentType":"Typ MIME obiektu docelowego","advisoryTitle":"Opis obiektu docelowego","anchor":{"toolbar":"Wstaw/edytuj kotwicę","menu":"Właściwości kotwicy","title":"Właściwości kotwicy","name":"Nazwa kotwicy","errorName":"Wpisz nazwę kotwicy","remove":"Usuń kotwicę"},"anchorId":"Wg identyfikatora","anchorName":"Wg nazwy","charset":"Kodowanie znaków obiektu docelowego","cssClasses":"Nazwa klasy CSS","emailAddress":"Adres e-mail","emailBody":"Treść","emailSubject":"Temat","id":"Id","info":"Informacje ","langCode":"Kod języka","langDir":"Kierunek tekstu","langDirLTR":"Od lewej do prawej (LTR)","langDirRTL":"Od prawej do lewej (RTL)","menu":"Edytuj odnośnik","name":"Nazwa","noAnchors":"(W dokumencie nie zdefiniowano żadnych kotwic)","noEmail":"Podaj adres e-mail","noUrl":"Podaj adres URL","other":"<inny>","popupDependent":"Okno zależne (Netscape)","popupFeatures":"Właściwości wyskakującego okna","popupFullScreen":"Pełny ekran (IE)","popupLeft":"Pozycja w poziomie","popupLocationBar":"Pasek adresu","popupMenuBar":"Pasek menu","popupResizable":"Skalowalny","popupScrollBars":"Paski przewijania","popupStatusBar":"Pasek statusu","popupToolbar":"Pasek narzędzi","popupTop":"Pozycja w pionie","rel":"Relacja","selectAnchor":"Wybierz kotwicę","styles":"Styl","tabIndex":"Indeks kolejności","target":"Obiekt docelowy","targetFrame":"<ramka>","targetFrameName":"Nazwa ramki docelowej","targetPopup":"<wyskakujące okno>","targetPopupName":"Nazwa wyskakującego okna","title":"Odnośnik","toAnchor":"Odnośnik wewnątrz strony (kotwica)","toEmail":"Adres e-mail","toUrl":"Adres URL","toolbar":"Wstaw/edytuj odnośnik","type":"Typ odnośnika","unlink":"Usuń odnośnik","upload":"Wyślij"},"list":{"bulletedlist":"Lista wypunktowana","numberedlist":"Lista numerowana"},"magicline":{"title":"Wstaw nowy akapit"},"maximize":{"maximize":"Maksymalizuj","minimize":"Minimalizuj"},"pastetext":{"button":"Wklej jako czysty tekst","title":"Wklej jako czysty tekst"},"pastefromword":{"confirmCleanup":"Tekst, który chcesz wkleić, prawdopodobnie pochodzi z programu Microsoft Word. Czy chcesz go wyczyścić przed wklejeniem?","error":"Wyczyszczenie wklejonych danych nie było możliwe z powodu wystąpienia błędu.","title":"Wklej z programu MS Word","toolbar":"Wklej z programu MS Word"},"removeformat":{"toolbar":"Usuń formatowanie"},"sourcearea":{"toolbar":"Źródło dokumentu"},"specialchar":{"options":"Opcje znaków specjalnych","title":"Wybierz znak specjalny","toolbar":"Wstaw znak specjalny"},"scayt":{"btn_about":"Informacje o SCAYT","btn_dictionaries":"Słowniki","btn_disable":"Wyłącz SCAYT","btn_enable":"Włącz SCAYT","btn_langs":"Języki","btn_options":"Opcje","text_title":"Sprawdź pisownię podczas pisania (SCAYT)"},"stylescombo":{"label":"Styl","panelTitle":"Style formatujące","panelTitle1":"Style blokowe","panelTitle2":"Style liniowe","panelTitle3":"Style obiektowe"},"table":{"border":"Grubość obramowania","caption":"Tytuł","cell":{"menu":"Komórka","insertBefore":"Wstaw komórkę z lewej","insertAfter":"Wstaw komórkę z prawej","deleteCell":"Usuń komórki","merge":"Połącz komórki","mergeRight":"Połącz z komórką z prawej","mergeDown":"Połącz z komórką poniżej","splitHorizontal":"Podziel komórkę poziomo","splitVertical":"Podziel komórkę pionowo","title":"Właściwości komórki","cellType":"Typ komórki","rowSpan":"Scalenie wierszy","colSpan":"Scalenie komórek","wordWrap":"Zawijanie słów","hAlign":"Wyrównanie poziome","vAlign":"Wyrównanie pionowe","alignBaseline":"Linia bazowa","bgColor":"Kolor tła","borderColor":"Kolor obramowania","data":"Dane","header":"Nagłówek","yes":"Tak","no":"Nie","invalidWidth":"Szerokość komórki musi być liczbą.","invalidHeight":"Wysokość komórki musi być liczbą.","invalidRowSpan":"Scalenie wierszy musi być liczbą całkowitą.","invalidColSpan":"Scalenie komórek musi być liczbą całkowitą.","chooseColor":"Wybierz"},"cellPad":"Dopełnienie komórek","cellSpace":"Odstęp pomiędzy komórkami","column":{"menu":"Kolumna","insertBefore":"Wstaw kolumnę z lewej","insertAfter":"Wstaw kolumnę z prawej","deleteColumn":"Usuń kolumny"},"columns":"Liczba kolumn","deleteTable":"Usuń tabelę","headers":"Nagłówki","headersBoth":"Oba","headersColumn":"Pierwsza kolumna","headersNone":"Brak","headersRow":"Pierwszy wiersz","invalidBorder":"Wartość obramowania musi być liczbą.","invalidCellPadding":"Dopełnienie komórek musi być liczbą dodatnią.","invalidCellSpacing":"Odstęp pomiędzy komórkami musi być liczbą dodatnią.","invalidCols":"Liczba kolumn musi być większa niż 0.","invalidHeight":"Wysokość tabeli musi być liczbą.","invalidRows":"Liczba wierszy musi być większa niż 0.","invalidWidth":"Szerokość tabeli musi być liczbą.","menu":"Właściwości tabeli","row":{"menu":"Wiersz","insertBefore":"Wstaw wiersz powyżej","insertAfter":"Wstaw wiersz poniżej","deleteRow":"Usuń wiersze"},"rows":"Liczba wierszy","summary":"Podsumowanie","title":"Właściwości tabeli","toolbar":"Tabela","widthPc":"%","widthPx":"piksele","widthUnit":"jednostka szerokości"},"undo":{"redo":"Ponów","undo":"Cofnij"},"wsc":{"btnIgnore":"Ignoruj","btnIgnoreAll":"Ignoruj wszystkie","btnReplace":"Zmień","btnReplaceAll":"Zmień wszystkie","btnUndo":"Cofnij","changeTo":"Zmień na","errorLoading":"Błąd wczytywania hosta aplikacji usługi: %s.","ieSpellDownload":"Słownik nie jest zainstalowany. Czy chcesz go pobrać?","manyChanges":"Sprawdzanie zakończone: zmieniono %l słów","noChanges":"Sprawdzanie zakończone: nie zmieniono żadnego słowa","noMispell":"Sprawdzanie zakończone: nie znaleziono błędów","noSuggestions":"- Brak sugestii -","notAvailable":"Przepraszamy, ale usługa jest obecnie niedostępna.","notInDic":"Słowa nie ma w słowniku","oneChange":"Sprawdzanie zakończone: zmieniono jedno słowo","progress":"Trwa sprawdzanie...","title":"Sprawdź pisownię","toolbar":"Sprawdź pisownię"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/pt-br.js b/js/ckeditor/lang/pt-br.js
new file mode 100644
index 0000000..0683523
--- /dev/null
+++ b/js/ckeditor/lang/pt-br.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['pt-br']={"editor":"Editor de Rich Text","editorPanel":"Painel do editor de Rich Text","common":{"editorHelp":"Pressione ALT+0 para ajuda","browseServer":"Localizar no Servidor","url":"URL","protocol":"Protocolo","upload":"Enviar ao Servidor","uploadSubmit":"Enviar para o Servidor","image":"Imagem","flash":"Flash","form":"Formulário","checkbox":"Caixa de Seleção","radio":"Botão de Opção","textField":"Caixa de Texto","textarea":"Ãrea de Texto","hiddenField":"Campo Oculto","button":"Botão","select":"Caixa de Listagem","imageButton":"Botão de Imagem","notSet":"<não ajustado>","id":"Id","name":"Nome","langDir":"Direção do idioma","langDirLtr":"Esquerda para Direita (LTR)","langDirRtl":"Direita para Esquerda (RTL)","langCode":"Idioma","longDescr":"Descrição da URL","cssClass":"Classe de CSS","advisoryTitle":"Título","cssStyle":"Estilos","ok":"OK","cancel":"Cancelar","close":"Fechar","preview":"Visualizar","resize":"Arraste para redimensionar","generalTab":"Geral","advancedTab":"Avançado","validateNumberFailed":"Este valor não é um número.","confirmNewPage":"Todas as mudanças não salvas serão perdidas. Tem certeza de que quer abrir uma nova página?","confirmCancel":"Algumas opções foram alteradas. Tem certeza de que quer fechar a caixa de diálogo?","options":"Opções","target":"Destino","targetNew":"Nova Janela (_blank)","targetTop":"Janela de Cima (_top)","targetSelf":"Mesma Janela (_self)","targetParent":"Janela Pai (_parent)","langDirLTR":"Esquerda para Direita (LTR)","langDirRTL":"Direita para Esquerda (RTL)","styles":"Estilo","cssClasses":"Classes","width":"Largura","height":"Altura","align":"Alinhamento","alignLeft":"Esquerda","alignRight":"Direita","alignCenter":"Centralizado","alignJustify":"Justificar","alignTop":"Superior","alignMiddle":"Centralizado","alignBottom":"Inferior","alignNone":"Nenhum","invalidValue":"Valor inválido.","invalidHeight":"A altura tem que ser um número","invalidWidth":"A largura tem que ser um número.","invalidCssLength":"O valor do campo \"%1\" deve ser um número positivo opcionalmente seguido por uma válida unidade de medida de CSS (px, %, in, cm, mm, em, ex, pt ou pc).","invalidHtmlLength":"O valor do campo \"%1\" deve ser um número positivo opcionalmente seguido por uma válida unidade de medida de HTML (px ou %).","invalidInlineStyle":"O valor válido para estilo deve conter uma ou mais tuplas no formato \"nome : valor\", separados por ponto e vírgula.","cssLengthTooltip":"Insira um número para valor em pixels ou um número seguido de uma válida unidade de medida de CSS (px, %, in, cm, mm, em, ex, pt ou pc).","unavailable":"%1<span class=\"cke_accessibility\">, indisponível</span>"},"about":{"copy":"Copyright &copy; $1. Todos os direitos reservados.","dlgTitle":"Sobre o CKEditor","help":"Verifique o $1 para obter ajuda.","moreInfo":"Para informações sobre a licença por favor visite o nosso site:","title":"Sobre o CKEditor","userGuide":"Guia do Usuário do CKEditor"},"basicstyles":{"bold":"Negrito","italic":"Itálico","strike":"Tachado","subscript":"Subscrito","superscript":"Sobrescrito","underline":"Sublinhado"},"blockquote":{"toolbar":"Citação"},"clipboard":{"copy":"Copiar","copyError":"As configurações de segurança do seu navegador não permitem que o editor execute operações de copiar automaticamente. Por favor, utilize o teclado para copiar (Ctrl/Cmd+C).","cut":"Recortar","cutError":"As configurações de segurança do seu navegador não permitem que o editor execute operações de recortar automaticamente. Por favor, utilize o teclado para recortar (Ctrl/Cmd+X).","paste":"Colar","pasteArea":"Ãrea para Colar","pasteMsg":"Transfira o link usado na caixa usando o teclado com (<STRONG>Ctrl/Cmd+V</STRONG>) e <STRONG>OK</STRONG>.","securityMsg":"As configurações de segurança do seu navegador não permitem que o editor acesse os dados da área de transferência diretamente. Por favor cole o conteúdo manualmente nesta janela.","title":"Colar"},"contextmenu":{"options":"Opções Menu de Contexto"},"button":{"selectedLabel":"%1 (Selecionado)"},"toolbar":{"toolbarCollapse":"Diminuir Barra de Ferramentas","toolbarExpand":"Aumentar Barra de Ferramentas","toolbarGroups":{"document":"Documento","clipboard":"Clipboard/Desfazer","editing":"Edição","forms":"Formulários","basicstyles":"Estilos Básicos","paragraph":"Paragrafo","links":"Links","insert":"Inserir","styles":"Estilos","colors":"Cores","tools":"Ferramentas"},"toolbars":"Barra de Ferramentas do Editor"},"elementspath":{"eleLabel":"Caminho dos Elementos","eleTitle":"Elemento %1"},"format":{"label":"Formatação","panelTitle":"Formatação","tag_address":"Endereço","tag_div":"Normal (DIV)","tag_h1":"Título 1","tag_h2":"Título 2","tag_h3":"Título 3","tag_h4":"Título 4","tag_h5":"Título 5","tag_h6":"Título 6","tag_p":"Normal","tag_pre":"Formatado"},"horizontalrule":{"toolbar":"Inserir Linha Horizontal"},"image":{"alertUrl":"Por favor, digite a URL da imagem.","alt":"Texto Alternativo","border":"Borda","btnUpload":"Enviar para o Servidor","button2Img":"Deseja transformar o botão de imagem em uma imagem comum?","hSpace":"HSpace","img2Button":"Deseja transformar a imagem em um botão de imagem?","infoTab":"Informações da Imagem","linkTab":"Link","lockRatio":"Travar Proporções","menu":"Formatar Imagem","resetSize":"Redefinir para o Tamanho Original","title":"Formatar Imagem","titleButton":"Formatar Botão de Imagem","upload":"Enviar","urlMissing":"URL da imagem está faltando.","vSpace":"VSpace","validateBorder":"A borda deve ser um número inteiro.","validateHSpace":"O HSpace deve ser um número inteiro.","validateVSpace":"O VSpace deve ser um número inteiro."},"indent":{"indent":"Aumentar Recuo","outdent":"Diminuir Recuo"},"fakeobjects":{"anchor":"Âncora","flash":"Animação em Flash","hiddenfield":"Campo Oculto","iframe":"IFrame","unknown":"Objeto desconhecido"},"link":{"acccessKey":"Chave de Acesso","advanced":"Avançado","advisoryContentType":"Tipo de Conteúdo","advisoryTitle":"Título","anchor":{"toolbar":"Inserir/Editar Âncora","menu":"Formatar Âncora","title":"Formatar Âncora","name":"Nome da Âncora","errorName":"Por favor, digite o nome da âncora","remove":"Remover Âncora"},"anchorId":"Id da âncora","anchorName":"Nome da âncora","charset":"Charset do Link","cssClasses":"Classe de CSS","emailAddress":"Endereço E-Mail","emailBody":"Corpo da Mensagem","emailSubject":"Assunto da Mensagem","id":"Id","info":"Informações","langCode":"Direção do idioma","langDir":"Direção do idioma","langDirLTR":"Esquerda para Direita (LTR)","langDirRTL":"Direita para Esquerda (RTL)","menu":"Editar Link","name":"Nome","noAnchors":"(Não há âncoras no documento)","noEmail":"Por favor, digite o endereço de e-mail","noUrl":"Por favor, digite o endereço do Link","other":"<outro>","popupDependent":"Dependente (Netscape)","popupFeatures":"Propriedades da Janela Pop-up","popupFullScreen":"Modo Tela Cheia (IE)","popupLeft":"Esquerda","popupLocationBar":"Barra de Endereços","popupMenuBar":"Barra de Menus","popupResizable":"Redimensionável","popupScrollBars":"Barras de Rolagem","popupStatusBar":"Barra de Status","popupToolbar":"Barra de Ferramentas","popupTop":"Topo","rel":"Tipo de Relação","selectAnchor":"Selecione uma âncora","styles":"Estilos","tabIndex":"Ãndice de Tabulação","target":"Destino","targetFrame":"<frame>","targetFrameName":"Nome do Frame de Destino","targetPopup":"<janela popup>","targetPopupName":"Nome da Janela Pop-up","title":"Editar Link","toAnchor":"Âncora nesta página","toEmail":"E-Mail","toUrl":"URL","toolbar":"Inserir/Editar Link","type":"Tipo de hiperlink","unlink":"Remover Link","upload":"Enviar ao Servidor"},"list":{"bulletedlist":"Lista sem números","numberedlist":"Lista numerada"},"magicline":{"title":"Insera um parágrafo aqui"},"maximize":{"maximize":"Maximizar","minimize":"Minimize"},"pastetext":{"button":"Colar como Texto sem Formatação","title":"Colar como Texto sem Formatação"},"pastefromword":{"confirmCleanup":"O texto que você deseja colar parece ter sido copiado do Word. Você gostaria de remover a formatação antes de colar?","error":"Não foi possível limpar os dados colados devido a um erro interno","title":"Colar do Word","toolbar":"Colar do Word"},"removeformat":{"toolbar":"Remover Formatação"},"sourcearea":{"toolbar":"Código-Fonte"},"specialchar":{"options":"Opções de Caractere Especial","title":"Selecione um Caractere Especial","toolbar":"Inserir Caractere Especial"},"scayt":{"btn_about":"Sobre a correção ortográfica durante a digitação","btn_dictionaries":"Dicionários","btn_disable":"Desabilitar correção ortográfica durante a digitação","btn_enable":"Habilitar correção ortográfica durante a digitação","btn_langs":"Idiomas","btn_options":"Opções","text_title":"Correção ortográfica durante a digitação"},"stylescombo":{"label":"Estilo","panelTitle":"Estilos de Formatação","panelTitle1":"Estilos de bloco","panelTitle2":"Estilos de texto corrido","panelTitle3":"Estilos de objeto"},"table":{"border":"Borda","caption":"Legenda","cell":{"menu":"Célula","insertBefore":"Inserir célula a esquerda","insertAfter":"Inserir célula a direita","deleteCell":"Remover Células","merge":"Mesclar Células","mergeRight":"Mesclar com célula a direita","mergeDown":"Mesclar com célula abaixo","splitHorizontal":"Dividir célula horizontalmente","splitVertical":"Dividir célula verticalmente","title":"Propriedades da célula","cellType":"Tipo de célula","rowSpan":"Linhas cobertas","colSpan":"Colunas cobertas","wordWrap":"Quebra de palavra","hAlign":"Alinhamento horizontal","vAlign":"Alinhamento vertical","alignBaseline":"Patamar de alinhamento","bgColor":"Cor de fundo","borderColor":"Cor das bordas","data":"Dados","header":"Cabeçalho","yes":"Sim","no":"Não","invalidWidth":"A largura da célula tem que ser um número.","invalidHeight":"A altura da célula tem que ser um número.","invalidRowSpan":"Linhas cobertas tem que ser um número inteiro.","invalidColSpan":"Colunas cobertas tem que ser um número inteiro.","chooseColor":"Escolher"},"cellPad":"Margem interna","cellSpace":"Espaçamento","column":{"menu":"Coluna","insertBefore":"Inserir coluna a esquerda","insertAfter":"Inserir coluna a direita","deleteColumn":"Remover Colunas"},"columns":"Colunas","deleteTable":"Apagar Tabela","headers":"Cabeçalho","headersBoth":"Ambos","headersColumn":"Primeira coluna","headersNone":"Nenhum","headersRow":"Primeira linha","invalidBorder":"O tamanho da borda tem que ser um número.","invalidCellPadding":"A margem interna das células tem que ser um número.","invalidCellSpacing":"O espaçamento das células tem que ser um número.","invalidCols":"O número de colunas tem que ser um número maior que 0.","invalidHeight":"A altura da tabela tem que ser um número.","invalidRows":"O número de linhas tem que ser um número maior que 0.","invalidWidth":"A largura da tabela tem que ser um número.","menu":"Formatar Tabela","row":{"menu":"Linha","insertBefore":"Inserir linha acima","insertAfter":"Inserir linha abaixo","deleteRow":"Remover Linhas"},"rows":"Linhas","summary":"Resumo","title":"Formatar Tabela","toolbar":"Tabela","widthPc":"%","widthPx":"pixels","widthUnit":"unidade largura"},"undo":{"redo":"Refazer","undo":"Desfazer"},"wsc":{"btnIgnore":"Ignorar uma vez","btnIgnoreAll":"Ignorar Todas","btnReplace":"Alterar","btnReplaceAll":"Alterar Todas","btnUndo":"Desfazer","changeTo":"Alterar para","errorLoading":"Erro carregando servidor de aplicação: %s.","ieSpellDownload":"A verificação ortográfica não foi instalada. Você gostaria de realizar o download agora?","manyChanges":"Verificação ortográfica encerrada: %1 palavras foram alteradas","noChanges":"Verificação ortográfica encerrada: Não houve alterações","noMispell":"Verificação encerrada: Não foram encontrados erros de ortografia","noSuggestions":"-sem sugestões de ortografia-","notAvailable":"Desculpe, o serviço não está disponível no momento.","notInDic":"Não encontrada","oneChange":"Verificação ortográfica encerrada: Uma palavra foi alterada","progress":"Verificação ortográfica em andamento...","title":"Corretor Ortográfico","toolbar":"Verificar Ortografia"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/pt.js b/js/ckeditor/lang/pt.js
new file mode 100644
index 0000000..8169151
--- /dev/null
+++ b/js/ckeditor/lang/pt.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['pt']={"editor":"Editor de texto enriquecido","editorPanel":"Painel do editor de texto enriquecido","common":{"editorHelp":"Pressione ALT+0 para ajuda","browseServer":"Navegar no servidor","url":"URL","protocol":"Protocolo","upload":"Enviar","uploadSubmit":"Enviar para o servidor","image":"Imagem","flash":"Flash","form":"Formulário","checkbox":"Caixa de Seleção","radio":"Botão","textField":"Campo do Texto","textarea":"Ãrea do Texto","hiddenField":"Campo oculto","button":"Botão","select":"Campo da Seleção","imageButton":"Botão da Imagem","notSet":"<Não definido>","id":"Id.","name":"Nome","langDir":"Direção do Idioma","langDirLtr":"Esquerda para a Direita (EPD)","langDirRtl":"Direita para a Esquerda (DPE)","langCode":"Código do Idioma","longDescr":"Descrição Completa do URL","cssClass":"Classes de Estilo das Folhas","advisoryTitle":"Título Consultivo","cssStyle":"Estilo","ok":"CONFIRMAR","cancel":"Cancelar","close":"Fechar","preview":"Pré-visualização","resize":"Redimensionar","generalTab":"Geral","advancedTab":"Avançado","validateNumberFailed":"Este valor não é um numero.","confirmNewPage":"Irão ser perdidas quaisquer alterações não guardadas. Tem a certeza que deseja carregar a nova página?","confirmCancel":"Foram alteradas algumas das opções. Tem a certeza que deseja fechar a janela?","options":"Opções","target":"Destino","targetNew":"Nova Janela (_blank)","targetTop":"Janela Superior (_top)","targetSelf":"Mesma Janela (_self)","targetParent":"Janela Parente (_parent)","langDirLTR":"Esquerda para a Direita (EPD)","langDirRTL":"Direita para a Esquerda (DPE)","styles":"Estilo","cssClasses":"Classes de folhas de estilo","width":"Largura","height":"Altura","align":"Alinhamento","alignLeft":"Esquerda","alignRight":"Direita","alignCenter":"Centrado","alignJustify":"Justificado","alignTop":"Topo","alignMiddle":"Centro","alignBottom":"Base","alignNone":"Nenhum","invalidValue":"Valor inválido.","invalidHeight":"A altura deve ser um número.","invalidWidth":"A largura deve ser um número. ","invalidCssLength":"O valor especificado para o campo \"1%\" deve ser um número positivo, com ou sem uma unidade de medida CSS válida (px, %, in, cm, mm, em, ex, pt, ou pc).","invalidHtmlLength":"O valor especificado para o campo \"1%\" deve ser um número positivo, com ou sem uma unidade de medida HTML válida (px ou %).","invalidInlineStyle":"O valor especificado para o estilo em linha deve constituir um ou mais conjuntos de valores com o formato de \"nome : valor\", separados por ponto e vírgula.","cssLengthTooltip":"Insira um número para um valor em pontos ou um número com uma unidade CSS válida (px, %, in, cm, mm, em, ex, pt, ou pc).","unavailable":"%1<span class=\"cke_accessibility\">, indisponível</span>"},"about":{"copy":"Direitos de Autor &copy; $1. Todos os direitos reservados.","dlgTitle":"Sobre o CKEditor","help":"Doar $1 para ajudar.","moreInfo":"Para informação sobre licenciamento visite o nosso sítio web:","title":"Sobre o CKEditor","userGuide":"CKEditor - Guia do Utilizador"},"basicstyles":{"bold":"Negrito","italic":"Itálico","strike":"Rasurado","subscript":"Superior à linha","superscript":"Inferior à Linha","underline":"Sublinhado"},"blockquote":{"toolbar":"Bloco de citação"},"clipboard":{"copy":"Copiar","copyError":"A configuração de segurança do navegador não permite a execução automática de operações de copiar. Por favor use o teclado (Ctrl/Cmd+C).","cut":"Cortar","cutError":"A configuração de segurança do navegador não permite a execução automática de operações de cortar. Por favor use o teclado (Ctrl/Cmd+X).","paste":"Colar","pasteArea":"Colar área","pasteMsg":"Por favor, cole dentro da seguinte caixa usando o teclado (<STRONG>Ctrl/Cmd+V</STRONG>) e prima <STRONG>OK</STRONG>.","securityMsg":"Devido ás definições de segurança do teu browser, o editor não pode aceder ao clipboard diretamente. É necessário que voltes a colar as informações nesta janela.","title":"Colar"},"contextmenu":{"options":"Menu de opções de contexto"},"button":{"selectedLabel":"%1 (Selecionado)"},"toolbar":{"toolbarCollapse":"Ocultar barra de ferramentas","toolbarExpand":"Expandir barra de ferramentas","toolbarGroups":{"document":"Documento","clipboard":"Ãrea de transferência/Anular","editing":"Edição","forms":"Formulários","basicstyles":"Estilos Básicos","paragraph":"Parágrafo","links":"Hiperligações","insert":"Inserir","styles":"Estilos","colors":"Cores","tools":"Ferramentas"},"toolbars":"Editor de Barras de Ferramentas"},"elementspath":{"eleLabel":"Caminho dos elementos","eleTitle":"Elemento %1"},"format":{"label":"Formatar","panelTitle":"Formatar Parágrafo","tag_address":"Endereço","tag_div":"Normal (DIV)","tag_h1":"Título 1","tag_h2":"Título 2","tag_h3":"Título 3","tag_h4":"Título 4","tag_h5":"Título 5","tag_h6":"Título 6","tag_p":"Normal","tag_pre":"Formatado"},"horizontalrule":{"toolbar":"Inserir Linha Horizontal"},"image":{"alertUrl":"Por favor introduza o URL da imagem","alt":"Texto Alternativo","border":"Limite","btnUpload":"Enviar para o servidor","button2Img":"Deseja transformar o botão com imagem selecionado em uma imagem?","hSpace":"Esp.Horiz","img2Button":"Deseja transformar a imagem selecionada em um botão com imagem?","infoTab":"Informação da Imagem","linkTab":"Hiperligação","lockRatio":"Proporcional","menu":"Propriedades da Imagem","resetSize":"Tamanho Original","title":"Propriedades da Imagem","titleButton":"Propriedades do Botão de imagens","upload":"Carregar","urlMissing":"O URL da fonte da imagem está em falta.","vSpace":"Esp.Vert","validateBorder":"A borda tem de ser um numero.","validateHSpace":"HSpace tem de ser um numero.","validateVSpace":"VSpace tem de ser um numero."},"indent":{"indent":"Aumentar Avanço","outdent":"Diminuir Avanço"},"fakeobjects":{"anchor":" Inserir/Editar Âncora","flash":"Animação Flash","hiddenfield":"Campo oculto","iframe":"IFrame","unknown":"Objeto Desconhecido"},"link":{"acccessKey":"Chave de Acesso","advanced":"Avançado","advisoryContentType":"Tipo de Conteúdo","advisoryTitle":"Título","anchor":{"toolbar":" Inserir/Editar Âncora","menu":"Propriedades da Âncora","title":"Propriedades da Âncora","name":"Nome da Âncora","errorName":"Por favor, introduza o nome da âncora","remove":"Remove Anchor"},"anchorId":"Por ID de elemento","anchorName":"Por Nome de Referência","charset":"Fonte de caracteres vinculado","cssClasses":"Classes de Estilo de Folhas Classes","emailAddress":"Endereço de E-Mail","emailBody":"Corpo da Mensagem","emailSubject":"Título de Mensagem","id":"ID","info":"Informação de Hiperligação","langCode":"Orientação de idioma","langDir":"Orientação de idioma","langDirLTR":"Esquerda à Direita (LTR)","langDirRTL":"Direita a Esquerda (RTL)","menu":"Editar Hiperligação","name":"Nome","noAnchors":"(Não há referências disponíveis no documento)","noEmail":"Por favor introduza o endereço de e-mail","noUrl":"Por favor introduza a hiperligação URL","other":"<outro>","popupDependent":"Dependente (Netscape)","popupFeatures":"Características de Janela de Popup","popupFullScreen":"Janela Completa (IE)","popupLeft":"Posição Esquerda","popupLocationBar":"Barra de localização","popupMenuBar":"Barra de Menu","popupResizable":"Redimensionável","popupScrollBars":"Barras de deslocamento","popupStatusBar":"Barra de Estado","popupToolbar":"Barra de ferramentas","popupTop":"Posição Direita","rel":"Relação","selectAnchor":"Seleccionar una referência","styles":"Estilo","tabIndex":"Ãndice de tabulação","target":"Alvo","targetFrame":"<frame>","targetFrameName":"Nome do Frame Destino","targetPopup":"<janela de popup>","targetPopupName":"Nome da Janela de Popup","title":"Hiperligação","toAnchor":"Referência a esta página","toEmail":"Email","toUrl":"URL","toolbar":"Inserir/Editar Hiperligação","type":"Tipo de Hiperligação","unlink":"Eliminar Hiperligação","upload":"Carregar"},"list":{"bulletedlist":"Marcas","numberedlist":"Numeração"},"magicline":{"title":"Insira aqui o parágrafo"},"maximize":{"maximize":"Maximizar","minimize":"Minimizar"},"pastetext":{"button":"Colar como Texto Simples","title":"Colar como Texto Simples"},"pastefromword":{"confirmCleanup":"O texto que pretende colar parece ter sido copiado do Word. Deseja limpá-lo antes de colar?","error":"Não foi possivel limpar a informação colada decido a um erro interno.","title":"Colar do Word","toolbar":"Colar do Word"},"removeformat":{"toolbar":"Eliminar Formato"},"sourcearea":{"toolbar":"Fonte"},"specialchar":{"options":"Opções de caracteres especiais","title":"Selecione um caracter especial","toolbar":"Inserir carácter especial"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Estilos","panelTitle":"Estilos de Formatação","panelTitle1":"Estilos de bloco","panelTitle2":"Estilos de Linha","panelTitle3":"Estilos de Objeto"},"table":{"border":"Tamanho do contorno","caption":"Legenda","cell":{"menu":"Célula","insertBefore":"Inserir célula antes","insertAfter":"Inserir célula depois","deleteCell":"Apagar Células","merge":"Unir Células","mergeRight":"Unir à Direita","mergeDown":"Fundir abaixo","splitHorizontal":"Dividir célula horizontalmente","splitVertical":"Dividir célula verticalmente","title":"Propriedades da célula","cellType":"Tipo de célula","rowSpan":"Filas na Célula","colSpan":"Colunas na Célula","wordWrap":"Moldar texto","hAlign":"Alinhamento Horizontal","vAlign":"Alinhamento Vertical","alignBaseline":"Base","bgColor":"Cor de Fundo","borderColor":"Cor da Margem","data":"Dados","header":"Cabeçalho","yes":"Sim","no":"Não","invalidWidth":"A largura da célula deve ser um número.","invalidHeight":"A altura da célula deve ser um número.","invalidRowSpan":"As filas da célula deve ter um número inteiro.","invalidColSpan":"As colunas da célula deve ter um número inteiro.","chooseColor":"Escolher"},"cellPad":"Espaço interior","cellSpace":"Espaçamento de célula","column":{"menu":"Coluna","insertBefore":"Inserir Coluna Antes","insertAfter":"Inserir coluna depois","deleteColumn":"Apagar colunas"},"columns":"Colunas","deleteTable":"Apagar tabela","headers":"Cabeçalhos","headersBoth":"Ambos","headersColumn":"Primeira coluna","headersNone":"Nenhum","headersRow":"Primeira linha","invalidBorder":"O tamanho da margem tem de ser um número.","invalidCellPadding":"A criação do espaço na célula deve ser um número positivo.","invalidCellSpacing":"O espaçamento da célula deve ser um número positivo.","invalidCols":"O número de colunas tem de ser um número maior que 0.","invalidHeight":"A altura da tabela tem de ser um número.","invalidRows":"O número de linhas tem de ser maior que 0.","invalidWidth":"A largura da tabela tem de ser um número.","menu":"Propriedades da Tabela","row":{"menu":"Linha","insertBefore":"Inserir linha antes","insertAfter":"Inserir linha depois","deleteRow":"Apagar linhas"},"rows":"Linhas","summary":"Sumário","title":"Propriedades da Tabela","toolbar":"Tabela","widthPc":"percentagem","widthPx":"pontos","widthUnit":"unidade da largura"},"undo":{"redo":"Refazer","undo":"Anular"},"wsc":{"btnIgnore":"Ignorar","btnIgnoreAll":"Ignorar Tudo","btnReplace":"Substituir","btnReplaceAll":"Substituir Tudo","btnUndo":"Anular","changeTo":"Mudar para","errorLoading":"Error loading application service host: %s.","ieSpellDownload":" Verificação ortográfica não instalada. Quer descarregar agora?","manyChanges":"Verificação ortográfica completa: %1 palavras alteradas","noChanges":"Verificação ortográfica completa: não houve alteração de palavras","noMispell":"Verificação ortográfica completa: não foram encontrados erros","noSuggestions":"- Sem sugestões -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Não está num directório","oneChange":"Verificação ortográfica completa: uma palavra alterada","progress":"Verificação ortográfica em progresso…","title":"Spell Checker","toolbar":"Verificação Ortográfica"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ro.js b/js/ckeditor/lang/ro.js
new file mode 100644
index 0000000..4ec041b
--- /dev/null
+++ b/js/ckeditor/lang/ro.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ro']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Apasă ALT 0 pentru ajutor","browseServer":"Răsfoieşte server","url":"URL","protocol":"Protocol","upload":"Încarcă","uploadSubmit":"Trimite la server","image":"Imagine","flash":"Flash","form":"Formular (Form)","checkbox":"Bifă (Checkbox)","radio":"Buton radio (RadioButton)","textField":"Câmp text (TextField)","textarea":"Suprafaţă text (Textarea)","hiddenField":"Câmp ascuns (HiddenField)","button":"Buton","select":"Câmp selecţie (SelectionField)","imageButton":"Buton imagine (ImageButton)","notSet":"<nesetat>","id":"Id","name":"Nume","langDir":"Direcţia cuvintelor","langDirLtr":"stânga-dreapta (LTR)","langDirRtl":"dreapta-stânga (RTL)","langCode":"Codul limbii","longDescr":"Descrierea lungă URL","cssClass":"Clasele cu stilul paginii (CSS)","advisoryTitle":"Titlul consultativ","cssStyle":"Stil","ok":"OK","cancel":"Anulare","close":"Închide","preview":"Previzualizare","resize":"Trage pentru a redimensiona","generalTab":"General","advancedTab":"Avansat","validateNumberFailed":"Această valoare nu este un număr.","confirmNewPage":"Orice modificări nesalvate ale acestui conținut, vor fi pierdute. Sigur doriți încărcarea unei noi pagini?","confirmCancel":"Câteva opțiuni au fost schimbate. Sigur doriți să închideți dialogul?","options":"Opțiuni","target":"Țintă","targetNew":"Fereastră nouă (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"În aceeași fereastră (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"Stânga spre Dreapta (LTR)","langDirRTL":"Dreapta spre Stânga (RTL)","styles":"Stil","cssClasses":"Stylesheet Classes","width":"Lăţime","height":"Înălţime","align":"Aliniere","alignLeft":"Mărește Bara","alignRight":"Dreapta","alignCenter":"Centru","alignJustify":"Aliniere în bloc (Block Justify)","alignTop":"Sus","alignMiddle":"Mijloc","alignBottom":"Jos","alignNone":"None","invalidValue":"Varloare invalida","invalidHeight":"Înălțimea trebuie să fie un număr.","invalidWidth":"Lățimea trebuie să fie un număr.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, nu este disponibil</span>"},"about":{"copy":"Copyright &copy; $1. Toate drepturile rezervate.","dlgTitle":"Despre CKEeditor","help":"Citește $1 pentru ajutor.","moreInfo":"Pentru informații despre licență, vă rugăm vizitați web site-ul nostru:","title":"Despre CKEditor","userGuide":"CKEditor Ghid Utilizator"},"basicstyles":{"bold":"Îngroşat (bold)","italic":"Înclinat (italic)","strike":"Tăiat (strike through)","subscript":"Indice (subscript)","superscript":"Putere (superscript)","underline":"Subliniat (underline)"},"blockquote":{"toolbar":"Citat"},"clipboard":{"copy":"Copiază","copyError":"Setările de securitate ale navigatorului (browser) pe care îl folosiţi nu permit editorului să execute automat operaţiunea de copiere. Vă rugăm folosiţi tastatura (Ctrl/Cmd+C).","cut":"Taie","cutError":"Setările de securitate ale navigatorului (browser) pe care îl folosiţi nu permit editorului să execute automat operaţiunea de tăiere. Vă rugăm folosiţi tastatura (Ctrl/Cmd+X).","paste":"Adaugă","pasteArea":"Suprafața de adăugare","pasteMsg":"Vă rugăm adăugaţi în căsuţa următoare folosind tastatura (<strong>Ctrl/Cmd+V</strong>) şi apăsaţi OK","securityMsg":"Din cauza setărilor de securitate ale programului dvs. cu care navigaţi pe internet (browser), editorul nu poate accesa direct datele din clipboard. Va trebui să adăugaţi din nou datele în această fereastră.","title":"Adaugă"},"contextmenu":{"options":"Opțiuni Meniu Contextual"},"button":{"selectedLabel":"%1 (Selectat)"},"toolbar":{"toolbarCollapse":"Micșorează Bara","toolbarExpand":"Mărește Bara","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Editează bara de unelte"},"elementspath":{"eleLabel":"Calea elementelor","eleTitle":"%1 element"},"format":{"label":"Formatare","panelTitle":"Formatare","tag_address":"Adresă","tag_div":"Normal (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatat"},"horizontalrule":{"toolbar":"Inserează linie orizontală"},"image":{"alertUrl":"Vă rugăm să scrieţi URL-ul imaginii","alt":"Text alternativ","border":"Margine","btnUpload":"Trimite la server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Informaţii despre imagine","linkTab":"Link (Legătură web)","lockRatio":"Păstrează proporţiile","menu":"Proprietăţile imaginii","resetSize":"Resetează mărimea","title":"Proprietăţile imaginii","titleButton":"Proprietăţi buton imagine (Image Button)","upload":"Încarcă","urlMissing":"Sursa URL a imaginii lipsește.","vSpace":"VSpace","validateBorder":"Bordura trebuie să fie un număr întreg.","validateHSpace":"Hspace trebuie să fie un număr întreg.","validateVSpace":"Vspace trebuie să fie un număr întreg."},"indent":{"indent":"Creşte indentarea","outdent":"Scade indentarea"},"fakeobjects":{"anchor":"Inserează/Editează ancoră","flash":"Flash Animation","hiddenfield":"Câmp ascuns (HiddenField)","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Tasta de acces","advanced":"Avansat","advisoryContentType":"Tipul consultativ al titlului","advisoryTitle":"Titlul consultativ","anchor":{"toolbar":"Inserează/Editează ancoră","menu":"Proprietăţi ancoră","title":"Proprietăţi ancoră","name":"Numele ancorei","errorName":"Vă rugăm scrieţi numele ancorei","remove":"Elimină ancora"},"anchorId":"după Id-ul elementului","anchorName":"după numele ancorei","charset":"Setul de caractere al resursei legate","cssClasses":"Clasele cu stilul paginii (CSS)","emailAddress":"Adresă de e-mail","emailBody":"Opțiuni Meniu Contextual","emailSubject":"Subiectul mesajului","id":"Id","info":"Informaţii despre link (Legătură web)","langCode":"Direcţia cuvintelor","langDir":"Direcţia cuvintelor","langDirLTR":"stânga-dreapta (LTR)","langDirRTL":"dreapta-stânga (RTL)","menu":"Editează Link","name":"Nume","noAnchors":"(Nicio ancoră disponibilă în document)","noEmail":"Vă rugăm să scrieţi adresa de e-mail","noUrl":"Vă rugăm să scrieţi URL-ul","other":"<alt>","popupDependent":"Dependent (Netscape)","popupFeatures":"Proprietăţile ferestrei popup","popupFullScreen":"Tot ecranul (Full Screen)(IE)","popupLeft":"Poziţia la stânga","popupLocationBar":"Bara de locaţie","popupMenuBar":"Bara de meniu","popupResizable":"Redimensionabil","popupScrollBars":"Bare de derulare","popupStatusBar":"Bara de status","popupToolbar":"Bara de opţiuni","popupTop":"Poziţia la dreapta","rel":"Relație","selectAnchor":"Selectaţi o ancoră","styles":"Stil","tabIndex":"Indexul tabului","target":"Ţintă (Target)","targetFrame":"<frame>","targetFrameName":"Numele frameului ţintă","targetPopup":"<fereastra popup>","targetPopupName":"Numele ferestrei popup","title":"Link (Legătură web)","toAnchor":"Ancoră în această pagină","toEmail":"E-Mail","toUrl":"URL","toolbar":"Inserează/Editează link (legătură web)","type":"Tipul link-ului (al legăturii web)","unlink":"Înlătură link (legătură web)","upload":"Încarcă"},"list":{"bulletedlist":"Inserează / Elimină Listă cu puncte","numberedlist":"Inserează / Elimină Listă numerotată"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Mărește","minimize":"Micșorează"},"pastetext":{"button":"Adaugă ca text simplu (Plain Text)","title":"Adaugă ca text simplu (Plain Text)"},"pastefromword":{"confirmCleanup":"Textul pe care doriți să-l lipiți este din Word. Doriți curățarea textului înante de a-l adăuga?","error":"Nu a fost posibilă curățarea datelor adăugate datorită unei erori interne","title":"Adaugă din Word","toolbar":"Adaugă din Word"},"removeformat":{"toolbar":"Înlătură formatarea"},"sourcearea":{"toolbar":"Sursa"},"specialchar":{"options":"Opțiuni caractere speciale","title":"Selectează caracter special","toolbar":"Inserează caracter special"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stil","panelTitle":"Formatarea stilurilor","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Mărimea marginii","caption":"Titlu (Caption)","cell":{"menu":"Celulă","insertBefore":"Inserează celulă înainte","insertAfter":"Inserează celulă după","deleteCell":"Şterge celule","merge":"Uneşte celule","mergeRight":"Uneşte la dreapta","mergeDown":"Uneşte jos","splitHorizontal":"Împarte celula pe orizontală","splitVertical":"Împarte celula pe verticală","title":"Proprietăți celulă","cellType":"Tipul celulei","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Aliniament orizontal","vAlign":"Aliniament vertical","alignBaseline":"Baseline","bgColor":"Culoare fundal","borderColor":"Culoare bordură","data":"Data","header":"Antet","yes":"Da","no":"Nu","invalidWidth":"Lățimea celulei trebuie să fie un număr.","invalidHeight":"Înălțimea celulei trebuie să fie un număr.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Alege"},"cellPad":"Spaţiu în cadrul celulei","cellSpace":"Spaţiu între celule","column":{"menu":"Coloană","insertBefore":"Inserează coloană înainte","insertAfter":"Inserează coloană după","deleteColumn":"Şterge celule"},"columns":"Coloane","deleteTable":"Şterge tabel","headers":"Antente","headersBoth":"Ambele","headersColumn":"Prima coloană","headersNone":"Nimic","headersRow":"Primul rând","invalidBorder":"Dimensiunea bordurii trebuie să aibe un număr.","invalidCellPadding":"Spațierea celulei trebuie sa fie un număr pozitiv","invalidCellSpacing":"Spațierea celului trebuie să fie un număr pozitiv.","invalidCols":"Numărul coloanelor trebuie să fie mai mare decât 0.","invalidHeight":"Inaltimea celulei trebuie sa fie un numar.","invalidRows":"Numărul rândurilor trebuie să fie mai mare decât 0.","invalidWidth":"Lățimea tabelului trebuie să fie un număr.","menu":"Proprietăţile tabelului","row":{"menu":"Rând","insertBefore":"Inserează rând înainte","insertAfter":"Inserează rând după","deleteRow":"Şterge rânduri"},"rows":"Rânduri","summary":"Rezumat","title":"Proprietăţile tabelului","toolbar":"Tabel","widthPc":"procente","widthPx":"pixeli","widthUnit":"unitate lățime"},"undo":{"redo":"Starea ulterioară (redo)","undo":"Starea anterioară (undo)"},"wsc":{"btnIgnore":"Ignoră","btnIgnoreAll":"Ignoră toate","btnReplace":"Înlocuieşte","btnReplaceAll":"Înlocuieşte tot","btnUndo":"Starea anterioară (undo)","changeTo":"Schimbă în","errorLoading":"Eroare în lansarea aplicației service host %s.","ieSpellDownload":"Unealta pentru verificat textul (Spell checker) neinstalată. Doriţi să o descărcaţi acum?","manyChanges":"Verificarea textului terminată: 1% cuvinte modificate","noChanges":"Verificarea textului terminată: Niciun cuvânt modificat","noMispell":"Verificarea textului terminată: Nicio greşeală găsită","noSuggestions":"- Fără sugestii -","notAvailable":"Scuzați, dar serviciul nu este disponibil momentan.","notInDic":"Nu e în dicţionar","oneChange":"Verificarea textului terminată: Un cuvânt modificat","progress":"Verificarea textului în desfăşurare...","title":"Spell Checker","toolbar":"Verifică scrierea textului"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ru.js b/js/ckeditor/lang/ru.js
new file mode 100644
index 0000000..80a19bc
--- /dev/null
+++ b/js/ckeditor/lang/ru.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ru']={"editor":"Визуальный текÑтовый редактор","editorPanel":"Визуальный редактор текÑта","common":{"editorHelp":"Ðажмите ALT-0 Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñправки","browseServer":"Выбор на Ñервере","url":"СÑылка","protocol":"Протокол","upload":"Загрузка файла","uploadSubmit":"Загрузить на Ñервер","image":"Изображение","flash":"Flash","form":"Форма","checkbox":"ЧекбокÑ","radio":"Радиокнопка","textField":"ТекÑтовое поле","textarea":"МногоÑтрочное текÑтовое поле","hiddenField":"Скрытое поле","button":"Кнопка","select":"Выпадающий ÑпиÑок","imageButton":"Кнопка-изображение","notSet":"<не указано>","id":"Идентификатор","name":"ИмÑ","langDir":"Ðаправление текÑта","langDirLtr":"Слева направо (LTR)","langDirRtl":"Справа налево (RTL)","langCode":"Код Ñзыка","longDescr":"Длинное опиÑание ÑÑылки","cssClass":"КлаÑÑ CSS","advisoryTitle":"Заголовок","cssStyle":"Стиль","ok":"ОК","cancel":"Отмена","close":"Закрыть","preview":"ПредпроÑмотр","resize":"Перетащите Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð°","generalTab":"ОÑновное","advancedTab":"Дополнительно","validateNumberFailed":"Это значение не ÑвлÑетÑÑ Ñ‡Ð¸Ñлом.","confirmNewPage":"ÐеÑохранённые Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ потерÑны! Ð’Ñ‹ дейÑтвительно желаете перейти на другую Ñтраницу?","confirmCancel":"Ðекоторые параметры были изменены. Ð’Ñ‹ уверены, что желаете закрыть без ÑохранениÑ?","options":"Параметры","target":"Цель","targetNew":"Ðовое окно (_blank)","targetTop":"Главное окно (_top)","targetSelf":"Текущее окно (_self)","targetParent":"РодительÑкое окно (_parent)","langDirLTR":"Слева направо (LTR)","langDirRTL":"Справа налево (RTL)","styles":"Стиль","cssClasses":"CSS клаÑÑÑ‹","width":"Ширина","height":"Ð’Ñ‹Ñота","align":"Выравнивание","alignLeft":"По левому краю","alignRight":"По правому краю","alignCenter":"По центру","alignJustify":"По ширине","alignTop":"Поверху","alignMiddle":"ПоÑередине","alignBottom":"Понизу","alignNone":"Ðет","invalidValue":"ÐедопуÑтимое значение.","invalidHeight":"Ð’Ñ‹Ñота задаетÑÑ Ñ‡Ð¸Ñлом.","invalidWidth":"Ширина задаетÑÑ Ñ‡Ð¸Ñлом.","invalidCssLength":"Значение, указанное в поле \"%1\", должно быть положительным целым чиÑлом. ДопуÑкаетÑÑ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ðµ единиц меры CSS (px, %, in, cm, mm, em, ex, pt или pc).","invalidHtmlLength":"Значение, указанное в поле \"%1\", должно быть положительным целым чиÑлом. ДопуÑкаетÑÑ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ðµ единиц меры HTML (px или %).","invalidInlineStyle":"Значение, указанное Ð´Ð»Ñ ÑÑ‚Ð¸Ð»Ñ Ñлемента, должно ÑоÑтоÑÑ‚ÑŒ из одной или неÑкольких пар данных в формате \"параметр : значение\", разделённых точкой Ñ Ð·Ð°Ð¿Ñтой.","cssLengthTooltip":"Введите значение в пикÑелÑÑ…, либо чиÑло Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ единицей меры CSS (px, %, in, cm, mm, em, ex, pt или pc).","unavailable":"%1<span class=\"cke_accessibility\">, недоÑтупно</span>"},"about":{"copy":"Copyright &copy; $1. Ð’Ñе права защищены.","dlgTitle":"О CKEditor","help":"$1 Ñодержит подробную Ñправку по иÑпользованию.","moreInfo":"Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ о лицензии, пожалуйÑта, перейдите на наш Ñайт:","title":"О CKEditor","userGuide":"РуководÑтво Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ CKEditor"},"basicstyles":{"bold":"Полужирный","italic":"КурÑив","strike":"Зачеркнутый","subscript":"ПодÑтрочный индекÑ","superscript":"ÐадÑтрочный индекÑ","underline":"Подчеркнутый"},"blockquote":{"toolbar":"Цитата"},"clipboard":{"copy":"Копировать","copyError":"ÐаÑтройки безопаÑноÑти вашего браузера не разрешают редактору выполнÑÑ‚ÑŒ операции по копированию текÑта. ПожалуйÑта, иÑпользуйте Ð´Ð»Ñ Ñтого клавиатуру (Ctrl/Cmd+C).","cut":"Вырезать","cutError":"ÐаÑтройки безопаÑноÑти вашего браузера не разрешают редактору выполнÑÑ‚ÑŒ операции по вырезке текÑта. ПожалуйÑта, иÑпользуйте Ð´Ð»Ñ Ñтого клавиатуру (Ctrl/Cmd+X).","paste":"Ð’Ñтавить","pasteArea":"Зона Ð´Ð»Ñ Ð²Ñтавки","pasteMsg":"ПожалуйÑта, вÑтавьте текÑÑ‚ в зону ниже, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñƒ (<strong>Ctrl/Cmd+V</strong>) и нажмите кнопку \"OK\".","securityMsg":"ÐаÑтройки безопаÑноÑти вашего браузера не разрешают редактору напрÑмую обращатьÑÑ Ðº буферу обмена. Ð’Ñ‹ должны вÑтавить текÑÑ‚ Ñнова в Ñто окно.","title":"Ð’Ñтавить"},"contextmenu":{"options":"Параметры контекÑтного меню"},"button":{"selectedLabel":"%1 (Выбрано)"},"toolbar":{"toolbarCollapse":"Свернуть панель инÑтрументов","toolbarExpand":"Развернуть панель инÑтрументов","toolbarGroups":{"document":"Документ","clipboard":"Буфер обмена / Отмена дейÑтвий","editing":"Корректировка","forms":"Формы","basicstyles":"ПроÑтые Ñтили","paragraph":"Ðбзац","links":"СÑылки","insert":"Ð’Ñтавка","styles":"Стили","colors":"Цвета","tools":"ИнÑтрументы"},"toolbars":"Панели инÑтрументов редактора"},"elementspath":{"eleLabel":"Путь Ñлементов","eleTitle":"Элемент %1"},"format":{"label":"Форматирование","panelTitle":"Форматирование","tag_address":"ÐдреÑ","tag_div":"Обычное (div)","tag_h1":"Заголовок 1","tag_h2":"Заголовок 2","tag_h3":"Заголовок 3","tag_h4":"Заголовок 4","tag_h5":"Заголовок 5","tag_h6":"Заголовок 6","tag_p":"Обычное","tag_pre":"Моноширинное"},"horizontalrule":{"toolbar":"Ð’Ñтавить горизонтальную линию"},"image":{"alertUrl":"ПожалуйÑта, введите ÑÑылку на изображение","alt":"Ðльтернативный текÑÑ‚","border":"Граница","btnUpload":"Загрузить на Ñервер","button2Img":"Ð’Ñ‹ желаете преобразовать Ñто изображение-кнопку в обычное изображение?","hSpace":"Гориз. отÑтуп","img2Button":"Ð’Ñ‹ желаете преобразовать Ñто обычное изображение в изображение-кнопку?","infoTab":"Данные об изображении","linkTab":"СÑылка","lockRatio":"СохранÑÑ‚ÑŒ пропорции","menu":"СвойÑтва изображениÑ","resetSize":"Вернуть обычные размеры","title":"СвойÑтва изображениÑ","titleButton":"СвойÑтва изображениÑ-кнопки","upload":"Загрузить","urlMissing":"Ðе указана ÑÑылка на изображение.","vSpace":"Вертик. отÑтуп","validateBorder":"Размер границ должен быть задан чиÑлом.","validateHSpace":"Горизонтальный отÑтуп должен быть задан чиÑлом.","validateVSpace":"Вертикальный отÑтуп должен быть задан чиÑлом."},"indent":{"indent":"Увеличить отÑтуп","outdent":"Уменьшить отÑтуп"},"fakeobjects":{"anchor":"Якорь","flash":"Flash анимациÑ","hiddenfield":"Скрытое поле","iframe":"iFrame","unknown":"ÐеизвеÑтный объект"},"link":{"acccessKey":"Клавиша доÑтупа","advanced":"Дополнительно","advisoryContentType":"Тип Ñодержимого","advisoryTitle":"Заголовок","anchor":{"toolbar":"Ð’Ñтавить / редактировать Ñкорь","menu":"Изменить Ñкорь","title":"СвойÑтва ÑкорÑ","name":"Ð˜Ð¼Ñ ÑкорÑ","errorName":"ПожалуйÑта, введите Ð¸Ð¼Ñ ÑкорÑ","remove":"Удалить Ñкорь"},"anchorId":"По идентификатору","anchorName":"По имени","charset":"Кодировка реÑурÑа","cssClasses":"КлаÑÑÑ‹ CSS","emailAddress":"Email адреÑ","emailBody":"ТекÑÑ‚ ÑообщениÑ","emailSubject":"Тема ÑообщениÑ","id":"Идентификатор","info":"Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑÑылке","langCode":"Код Ñзыка","langDir":"Ðаправление текÑта","langDirLTR":"Слева направо (LTR)","langDirRTL":"Справа налево (RTL)","menu":"Редактировать ÑÑылку","name":"ИмÑ","noAnchors":"(Ð’ документе нет ни одного ÑкорÑ)","noEmail":"ПожалуйÑта, введите email адреÑ","noUrl":"ПожалуйÑта, введите ÑÑылку","other":"<другой>","popupDependent":"ЗавиÑимое (Netscape)","popupFeatures":"Параметры вÑплывающего окна","popupFullScreen":"ПолноÑкранное (IE)","popupLeft":"ОтÑтуп Ñлева","popupLocationBar":"Панель адреÑа","popupMenuBar":"Панель меню","popupResizable":"ИзменÑемый размер","popupScrollBars":"ПолоÑÑ‹ прокрутки","popupStatusBar":"Строка ÑоÑтоÑниÑ","popupToolbar":"Панель инÑтрументов","popupTop":"ОтÑтуп Ñверху","rel":"Отношение","selectAnchor":"Выберите Ñкорь","styles":"Стиль","tabIndex":"ПоÑледовательноÑÑ‚ÑŒ перехода","target":"Цель","targetFrame":"<фрейм>","targetFrameName":"Ð˜Ð¼Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð³Ð¾ фрейма","targetPopup":"<вÑплывающее окно>","targetPopupName":"Ð˜Ð¼Ñ Ð²Ñплывающего окна","title":"СÑылка","toAnchor":"СÑылка на Ñкорь в текÑте","toEmail":"Email","toUrl":"СÑылка","toolbar":"Ð’Ñтавить/Редактировать ÑÑылку","type":"Тип ÑÑылки","unlink":"Убрать ÑÑылку","upload":"Загрузка"},"list":{"bulletedlist":"Ð’Ñтавить / удалить маркированный ÑпиÑок","numberedlist":"Ð’Ñтавить / удалить нумерованный ÑпиÑок"},"magicline":{"title":"Ð’Ñтавить здеÑÑŒ параграф"},"maximize":{"maximize":"Развернуть","minimize":"Свернуть"},"pastetext":{"button":"Ð’Ñтавить только текÑÑ‚","title":"Ð’Ñтавить только текÑÑ‚"},"pastefromword":{"confirmCleanup":"ТекÑÑ‚, который вы желаете вÑтавить, по вÑей видимоÑти, был Ñкопирован из Word. Следует ли очиÑтить его перед вÑтавкой?","error":"Ðевозможно очиÑтить вÑтавленные данные из-за внутренней ошибки","title":"Ð’Ñтавить из Word","toolbar":"Ð’Ñтавить из Word"},"removeformat":{"toolbar":"Убрать форматирование"},"sourcearea":{"toolbar":"ИÑточник"},"specialchar":{"options":"Выбор Ñпециального Ñимвола","title":"Выберите Ñпециальный Ñимвол","toolbar":"Ð’Ñтавить Ñпециальный Ñимвол"},"scayt":{"btn_about":"О SCAYT","btn_dictionaries":"Словари","btn_disable":"Отключить SCAYT","btn_enable":"Включить SCAYT","btn_langs":"Языки","btn_options":"ÐаÑтройки","text_title":"Проверка орфографии по мере ввода (SCAYT)"},"stylescombo":{"label":"Стили","panelTitle":"Стили форматированиÑ","panelTitle1":"Стили блока","panelTitle2":"Стили Ñлемента","panelTitle3":"Стили объекта"},"table":{"border":"Размер границ","caption":"Заголовок","cell":{"menu":"Ячейка","insertBefore":"Ð’Ñтавить Ñчейку Ñлева","insertAfter":"Ð’Ñтавить Ñчейку Ñправа","deleteCell":"Удалить Ñчейки","merge":"Объединить Ñчейки","mergeRight":"Объединить Ñ Ð¿Ñ€Ð°Ð²Ð¾Ð¹","mergeDown":"Объединить Ñ Ð½Ð¸Ð¶Ð½ÐµÐ¹","splitHorizontal":"Разделить Ñчейку по горизонтали","splitVertical":"Разделить Ñчейку по вертикали","title":"СвойÑтва Ñчейки","cellType":"Тип Ñчейки","rowSpan":"ОбъединÑет Ñтрок","colSpan":"ОбъединÑет колонок","wordWrap":"ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾ Ñловам","hAlign":"Горизонтальное выравнивание","vAlign":"Вертикальное выравнивание","alignBaseline":"По базовой линии","bgColor":"Цвет фона","borderColor":"Цвет границ","data":"Данные","header":"Заголовок","yes":"Да","no":"Ðет","invalidWidth":"Ширина Ñчейки должна быть чиÑлом.","invalidHeight":"Ð’Ñ‹Ñота Ñчейки должна быть чиÑлом.","invalidRowSpan":"КоличеÑтво объединÑемых Ñтрок должно быть задано чиÑлом.","invalidColSpan":"КоличеÑтво объединÑемых колонок должно быть задано чиÑлом.","chooseColor":"Выберите"},"cellPad":"Внутренний отÑтуп Ñчеек","cellSpace":"Внешний отÑтуп Ñчеек","column":{"menu":"Колонка","insertBefore":"Ð’Ñтавить колонку Ñлева","insertAfter":"Ð’Ñтавить колонку Ñправа","deleteColumn":"Удалить колонки"},"columns":"Колонки","deleteTable":"Удалить таблицу","headers":"Заголовки","headersBoth":"Сверху и Ñлева","headersColumn":"Ð›ÐµÐ²Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½ÐºÐ°","headersNone":"Без заголовков","headersRow":"ВерхнÑÑ Ñтрока","invalidBorder":"Размер границ должен быть чиÑлом.","invalidCellPadding":"Внутренний отÑтуп Ñчеек (cellpadding) должен быть чиÑлом.","invalidCellSpacing":"Внешний отÑтуп Ñчеек (cellspacing) должен быть чиÑлом.","invalidCols":"КоличеÑтво Ñтолбцов должно быть больше 0.","invalidHeight":"Ð’Ñ‹Ñота таблицы должна быть чиÑлом.","invalidRows":"КоличеÑтво Ñтрок должно быть больше 0.","invalidWidth":"Ширина таблицы должна быть чиÑлом.","menu":"СвойÑтва таблицы","row":{"menu":"Строка","insertBefore":"Ð’Ñтавить Ñтроку Ñверху","insertAfter":"Ð’Ñтавить Ñтроку Ñнизу","deleteRow":"Удалить Ñтроки"},"rows":"Строки","summary":"Итоги","title":"СвойÑтва таблицы","toolbar":"Таблица","widthPc":"процентов","widthPx":"пикÑелей","widthUnit":"единица измерениÑ"},"undo":{"redo":"Повторить","undo":"Отменить"},"wsc":{"btnIgnore":"ПропуÑтить","btnIgnoreAll":"ПропуÑтить вÑÑ‘","btnReplace":"Заменить","btnReplaceAll":"Заменить вÑÑ‘","btnUndo":"Отменить","changeTo":"Изменить на","errorLoading":"Произошла ошибка при подключении к Ñерверу проверки орфографии: %s.","ieSpellDownload":"Модуль проверки орфографии не уÑтановлен. Хотите Ñкачать его?","manyChanges":"Проверка орфографии завершена. Изменено Ñлов: %1","noChanges":"Проверка орфографии завершена. Ðе изменено ни одного Ñлова","noMispell":"Проверка орфографии завершена. Ошибок не найдено","noSuggestions":"- Варианты отÑутÑтвуют -","notAvailable":"Извините, но в данный момент ÑÐµÑ€Ð²Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупен.","notInDic":"ОтÑутÑтвует в Ñловаре","oneChange":"Проверка орфографии завершена. Изменено одно Ñлово","progress":"ÐžÑ€Ñ„Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÑетÑÑ...","title":"Проверка орфографии","toolbar":"Проверить орфографию"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/si.js b/js/ckeditor/lang/si.js
new file mode 100644
index 0000000..3412490
--- /dev/null
+++ b/js/ckeditor/lang/si.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['si']={"editor":"පොහොසත් වචන සංස්කරණ","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"උදව් ලබ෠ගà·à¶±à·“මට ALT බොත්තම ඔබන්න","browseServer":"සෙවුම් සේවà·à¶¯à·à¶ºà¶šà¶º","url":"URL","protocol":"මුලà·à¶´à¶­à·Šâ€à¶»à¶º","upload":"උඩුගතකිරීම","uploadSubmit":"සේවà·à¶¯à·à¶ºà¶šà¶º වෙත යොමුකිරිම","image":"රුපය","flash":"දීප්තිය","form":"පà·à¶»à¶¸à¶º","checkbox":"ලකුණුකිරීමේ කොටුව","radio":"තේරීම් ","textField":"ලියන ප්â€à¶»à¶¯à·šà·à¶º","textarea":"අකුරු ","hiddenField":"à·ƒà·à¶Ÿà·€à·”ණු ප්â€à¶»à¶¯à·šà·à¶º","button":"බොත්තම","select":"තà·à¶»à¶±à·Šà¶± ","imageButton":"රුප ","notSet":"<යොද෠>","id":"අංකය","name":"නම","langDir":"භà·à·‚෠දිà·à·à·€","langDirLtr":"වමේසිට දකුණුට","langDirRtl":"දකුණේ සිට වමට","langCode":"භà·à·‚෠කේතය","longDescr":"සම්පුර්න පà·à·„à·à¶¯à·’ලි කිරීම","cssClass":"විලà·à· පත්â€à¶» පන්තිය","advisoryTitle":"උපදෙස් ","cssStyle":"විලà·à·ƒà¶º","ok":"නිරදි","cancel":"අවලංගු කිරීම","close":"à·€à·à·ƒà·“ම","preview":"නà·à·€à¶­ ","resize":"විà·à·à¶½à¶­à·Šà·€à¶º නà·à·€à¶­ වෙනස් කිරීම","generalTab":"පොදු කරුණු.","advancedTab":"දීය","validateNumberFailed":"මෙම වටිනà·à¶šà¶¸ අංකයක් නොවේ","confirmNewPage":"ආරක්ෂ෠නොකළ සියලුම දත්තයන් මà·à¶šà·’යනුලà·à¶¶à·š. ඔබට නව පිටුවක් ලබ෠ගà·à¶±à·“මට අවà·à·Šâ€à¶ºà¶¯?","confirmCancel":"ඇතම් විකල්පයන් වෙනස් කර ඇත. ඔබට මින් නික්මීමට අවà·à·Šâ€à¶ºà¶¯?","options":" විකල්ප","target":"අරමුණ","targetNew":"නව කව්ළුව","targetTop":"à·€à·à¶¯à¶œà¶­à·Š කව්ළුව","targetSelf":"එම කව්ළුව(_තම\\\\)","targetParent":"මව් කව්ළුව(_)","langDirLTR":"වමේසිට දකුණුට","langDirRTL":"දකුණේ සිට වමට","styles":"විලà·à·ƒà¶º","cssClasses":"විලà·à·ƒà¶´à¶­à·Šâ€à¶» පන්තිය","width":"පළල","height":"උස","align":"ගà·à¶½à¶´à·”ම","alignLeft":"වම","alignRight":"දකුණ","alignCenter":"මධ්â€à¶º","alignJustify":"Justify","alignTop":"ඉ","alignMiddle":"මà·à¶¯","alignBottom":"පහල","alignNone":"None","invalidValue":"à·€à·à¶»à¶¯à·“ වටිනà·à¶šà¶¸à¶šà·’","invalidHeight":"උස අංකයක් විය යුතුය","invalidWidth":"පළල අංකයක් විය යුතුය","invalidCssLength":"වටිනà·à¶šà¶¸à¶šà·Š නිරූපණය කිරීම \"%1\" ප්â€à¶»à¶¯à·šà·à¶º ධන සංක්â€à¶ºà·à¶­à·Šà¶¸à¶š වටිනà·à¶šà¶¸à¶šà·Š හ෠නිවරදි නොවන CSS මිනුම් එකක(px, %, in, cm, mm, em, ex, pt, pc)","invalidHtmlLength":"වටිනà·à¶šà¶¸à¶šà·Š නිරූපණය කිරීම \"%1\" ප්â€à¶»à¶¯à·šà·à¶º ධන සංක්â€à¶ºà·à¶­à·Šà¶¸à¶š වටිනà·à¶šà¶¸à¶šà·Š හ෠නිවරදි නොවන HTML මිනුම් එකක (px à·„à· %).","invalidInlineStyle":"වටිනà·à¶šà¶¸à¶šà·Š නිරූපණය කිරීම පේළි විලà·à·ƒà¶ºà¶ºà¶§ ආකෘතිය අනතර්ග විය යුතය \"නම : වටිනà·à¶šà¶¸\", තිත් කොමà·à·€à¶šà·’න් වෙන් වෙන ලද.","cssLengthTooltip":"සංක්â€à¶ºà· ඇතුලත් කිරීමේදී වටිනà·à¶šà¶¸ තිත් ප්â€à¶»à¶¸à·à¶«à¶º නිවරදි CSS ඒකක(තිත්, %, අඟල්,සෙමි, mm, em, ex, pt, pc)","unavailable":"%1<span පන්තිය=\"ළඟ෠වියහà·à¶šà·’ ද බලන්න\">, නොමà·à¶­à·’නම්</span>"},"about":{"copy":"පිටපත් අයිතිය සහ පිටපත් කිරීම;$1 .සියලුම හිමිකම් ඇවිරිණි.","dlgTitle":"CKEditor ගà·à¶± විස්තර","help":"උදව් සඳහ෠$1 ","moreInfo":"බලපත්â€à¶» තොරතුරු සදහ෠කරුණà·à¶šà¶» අපගේ විද්â€à¶ºà·”ත් ලිපිනයට පිවිසෙන්න:","title":"CKEditor ගà·à¶± විස්තර","userGuide":"CKEditor භà·à·€à·’ත෠කිරීම පිළිබඳ "},"basicstyles":{"bold":"තද අකුරින් ලියනලද","italic":"බà·à¶°à·“අකුරින් ලියන ලද","strike":"Strikethrough","subscript":"Subscript","superscript":"Superscript","underline":"යටින් ඉරි අදින ලද"},"blockquote":{"toolbar":"උද්ධෘත කොටස"},"clipboard":{"copy":"පිටපත් කරන්න","copyError":"Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl/Cmd+C).","cut":"කපà·à¶œà¶±à·Šà¶±","cutError":"Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl/Cmd+X).","paste":"අලවන්න","pasteArea":"අලවන ප්â€à¶»à¶¯à·šà·","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"අලවන්න"},"contextmenu":{"options":"අනතර්ග ලේඛණ විකල්ප"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"මෙවලම් තීරුව à·„à·à¶šà·”ලුම.","toolbarExpand":"මෙවලම් තීරුව දීගහà·à¶»à·”ම","toolbarGroups":{"document":"ලිපිය","clipboard":"ඇමිණුම වෙනස් කිරීම","editing":"සංස්කරණය","forms":"පà·à¶»à¶¸à¶º","basicstyles":"මුලික විලà·à·ƒà¶º","paragraph":"චේදය","links":"සබà·à¶³à·’ය","insert":"ඇතුලත් කිරීම","styles":"විලà·à·ƒà¶º","colors":"වර්ණය","tools":"මෙවලම්"},"toolbars":"සංස්කරණ මෙවලම් තීරුව"},"elementspath":{"eleLabel":"මුලද්â€à¶»à·€à·Šâ€à¶º මà·à¶»à·Šà¶œà¶º","eleTitle":"%1 මුල"},"format":{"label":"ආකෘතිය","panelTitle":"චේදයේ ","tag_address":"ලිපිනය","tag_div":"à·ƒà·à¶¸à·à¶±à·Šâ€à¶º(DIV)","tag_h1":"à·à·“ර්ෂය 1","tag_h2":"à·à·“ර්ෂය 2","tag_h3":"à·à·“ර්ෂය 3","tag_h4":"à·à·“ර්ෂය 4","tag_h5":"à·à·“ර්ෂය 5","tag_h6":"à·à·“ර්ෂය 6","tag_p":"à·ƒà·à¶¸à·à¶±à·Šâ€à¶º","tag_pre":"ආකෘතියන්"},"horizontalrule":{"toolbar":"තිරස් රේඛà·à·€à¶šà·Š ඇතුලත් කරන්න"},"image":{"alertUrl":"කරුණà·à¶šà¶» රුපයේ URL ලියන්න","alt":"විකල්ප ","border":"සීමà·à·€à·€à¶½ ","btnUpload":"සේවà·à¶¯à·à¶ºà¶šà¶º වෙත යොමුකිරිම","button2Img":"ඔබට තà·à¶»à¶± ලද රුපය පරිවර්තනය කිරීමට අවà·à·Šâ€à¶ºà¶¯?","hSpace":"HSpace","img2Button":"ඔබට තà·à¶»à¶± ලද රුපය පරිවර්තනය කිරීමට අවà·à·Šâ€à¶ºà¶¯?","infoTab":"රුපයේ තොරතුරු","linkTab":"සබà·à¶³à·’ය","lockRatio":"නවතන අනුපà·à¶­à¶º ","menu":"රුපයේ ගුණ","resetSize":"නà·à·€à¶­à¶­à·Š විà·à·à¶½à¶­à·Šà·€à¶º වෙනස් කිරීම","title":"රුපයේ ","titleButton":"රුප බොත්තමේ ගුණ","upload":"උඩුගතකිරීම","urlMissing":"රුප මුලà·à·à·Šâ€à¶» URL නà·à¶­.","vSpace":"VSpace","validateBorder":"මà·à¶‰à¶¸à·Š සම්පුර්ණ සංක්â€à¶ºà·à·€à¶šà·Š විය යුතුය.","validateHSpace":"HSpace සම්පුර්ණ සංක්â€à¶ºà·à·€à¶šà·Š විය යුතුය","validateVSpace":"VSpace සම්පුර්ණ සංක්â€à¶ºà·à·€à¶šà·Š විය යුතුය."},"indent":{"indent":"අතර පරතරය à·€à·à¶©à·’කරන්න","outdent":"අතර පරතරය අඩුකරන්න"},"fakeobjects":{"anchor":"ආධà·à¶»à¶º","flash":"Flash Animation","hiddenfield":"à·ƒà·à¶Ÿà·€à·”ණු ප්â€à¶»à¶¯à·šà·à¶º","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"ප්â€à¶»à·€à·šà· යතුර","advanced":"දීය","advisoryContentType":"උපදේà·à·à¶­à·Šà¶¸à¶š අන්තර්ගත ආකà·à¶»à¶º","advisoryTitle":"උපදේà·à·à¶­à·Šà¶¸à¶š නà·à¶¸à¶º","anchor":{"toolbar":"ආධà·à¶»à¶º","menu":"ආධà·à¶»à¶º වෙනස් කිරීම","title":"ආධà·à¶»à¶š ","name":"ආධà·à¶»à¶šà¶ºà·š නà·à¶¸à¶º","errorName":"කරුණà·à¶šà¶» ආධà·à¶»à¶šà¶ºà·š නà·à¶¸à¶º ඇතුල් කරන්න","remove":"ආධà·à¶»à¶šà¶º ඉවත් කිරීම"},"anchorId":"By Element Id","anchorName":"By Anchor Name","charset":"Linked Resource Charset","cssClasses":"විලà·à·ƒà¶´à¶­à·Šâ€à¶» පන්තිය","emailAddress":"E-Mail Address","emailBody":"Message Body","emailSubject":"Message Subject","id":"අංකය","info":"Link Info","langCode":"භà·à·‚෠කේතය","langDir":"භà·à·‚෠දිà·à·à·€","langDirLTR":"වමේසිට දකුණුට","langDirRTL":"දකුණේ සිට වමට","menu":"Edit Link","name":"නම","noAnchors":"(No anchors available in the document)","noEmail":"Please type the e-mail address","noUrl":"Please type the link URL","other":"<other>","popupDependent":"Dependent (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Full Screen (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Relationship","selectAnchor":"Select an Anchor","styles":"විලà·à·ƒà¶º","tabIndex":"Tab Index","target":"අරමුණ","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Popup Window Name","title":"සබà·à¶³à·’ය","toAnchor":"Link to anchor in the text","toEmail":"E-mail","toUrl":"URL","toolbar":"සබà·à¶³à·’ය","type":"Link Type","unlink":"Unlink","upload":"උඩුගතකිරීම"},"list":{"bulletedlist":"ඇතුලත් / ඉවත් කිරීම ලඉස්තුව","numberedlist":"ඇතුලත් / ඉවත් කිරීම අන්න්කිත ලඉස්තුව"},"magicline":{"title":"චේදය ඇතුලත් කරන්න"},"maximize":{"maximize":"විà·à·à¶½ කිරීම","minimize":"කුඩ෠කිරීම"},"pastetext":{"button":"à·ƒà·à¶¸à·à¶±à·Šâ€à¶º අක්ෂර ලෙස අලවන්න","title":"à·ƒà·à¶¸à·à¶±à·Šâ€à¶º අක්ෂර ලෙස අලවන්න"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"වචන වලින් අලවන්න","toolbar":"වචන වලින් අලවන්න"},"removeformat":{"toolbar":"à·ƒà·à¶šà·ƒà·“ම වෙනස් කරන්න"},"sourcearea":{"toolbar":"මුලà·à·à·Šâ€à¶»à¶º"},"specialchar":{"options":"විà·à·šà·‚ ගුණà·à¶‚ග වීකල්ප","title":"විà·à·šà·‚ ගුණà·à¶‚ග ","toolbar":"විà·à·šà·‚ ගුණà·à¶‚ග ඇතුලත් "},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"විලà·à·ƒà¶º","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"සීමà·à·€à·€à¶½ විà·à·à¶½à¶­à·Šà·€à¶º","caption":"Caption","cell":{"menu":"කොටුව","insertBefore":"පෙර කොටුවක් ඇතුල්කිරිම","insertAfter":"පසුව කොටුවක් ඇතුලත් ","deleteCell":"කොටුව මà·à¶šà·“ම","merge":"කොටු එකට යà·à¶šà·’රිම","mergeRight":"දකුණට ","mergeDown":"පහලට ","splitHorizontal":"තිරස්ව කොටු පà·à¶­à·’රීම","splitVertical":"සිරස්ව කොටු පà·à¶­à·’රීම","title":"කොටු ","cellType":"කොටු වර්ගය","rowSpan":"පේළි පළල","colSpan":"සිරස් පළල","wordWrap":"වචන ගà·à¶½à¶´à·”ම","hAlign":"තිරස්ව ","vAlign":"සිරස්ව ","alignBaseline":"පà·à¶¯ රේඛà·à·€","bgColor":"පසුබිම් වර්ණය","borderColor":"මà·à¶ºà·’ම් ","data":"Data","header":"à·à·“ර්ෂක","yes":"ඔව්","no":"නà·à¶­","invalidWidth":"කොටු පළල සංඛ්â€à¶ºà·Šà¶­à·Šà¶¸à¶š වටිනà·à¶šà¶¸à¶šà·Š විය යුතුය","invalidHeight":"කොටු උස සංඛ්â€à¶ºà·Šà¶­à·Šà¶¸à¶š වටිනà·à¶šà¶¸à¶šà·Š විය යුතුය","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"තà·à¶»à¶±à·Šà¶±"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"සිරස් ","deleteTable":"වගුව මකන්න","headers":"à·à·“ර්ෂක","headersBoth":"දෙකම","headersColumn":"පළමූ සිරස් තීරුව","headersNone":"කිසිවක්ම නොවේ","headersRow":"පළමූ පේළිය","invalidBorder":"Border size must be a number.","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Table height must be a number.","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Table width must be a number.","menu":"Table Properties","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"Rows","summary":"Summary","title":"Table Properties","toolbar":"Table","widthPc":"percent","widthPx":"pixels","widthUnit":"width unit"},"undo":{"redo":"නà·à·€à¶­ කිරීම","undo":"වෙනස් කිරීම"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sk.js b/js/ckeditor/lang/sk.js
new file mode 100644
index 0000000..15fb908
--- /dev/null
+++ b/js/ckeditor/lang/sk.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sk']={"editor":"Editor formátovaného textu","editorPanel":"Panel editora formátovaného textu","common":{"editorHelp":"StlaÄte ALT 0 pre nápovedu","browseServer":"PrechádzaÅ¥ server","url":"URL","protocol":"Protokol","upload":"OdoslaÅ¥","uploadSubmit":"OdoslaÅ¥ na server","image":"Obrázok","flash":"Flash","form":"Formulár","checkbox":"ZaÅ¡krtávacie políÄko","radio":"PrepínaÄ","textField":"Textové pole","textarea":"Textová oblasÅ¥","hiddenField":"Skryté pole","button":"TlaÄidlo","select":"Rozbaľovací zoznam","imageButton":"Obrázkové tlaÄidlo","notSet":"<nenastavené>","id":"Id","name":"Meno","langDir":"Orientácia jazyka","langDirLtr":"Zľava doprava (LTR)","langDirRtl":"Sprava doľava (RTL)","langCode":"Kód jazyka","longDescr":"Dlhý popis URL","cssClass":"Trieda Å¡týlu","advisoryTitle":"Pomocný titulok","cssStyle":"Å týl","ok":"OK","cancel":"ZruÅ¡iÅ¥","close":"Zatvorit","preview":"Náhľad","resize":"ZmeniÅ¥ veľkosÅ¥","generalTab":"Hlavné","advancedTab":"Rozšírené","validateNumberFailed":"Hodnota nieje Äíslo.","confirmNewPage":"Prajete si naÄítat novú stránku? VÅ¡etky neuložené zmeny budú stratené. ","confirmCancel":"Niektore možnosti boli zmenené. Naozaj chcete zavrieÅ¥ okno?","options":"Možnosti","target":"Cieľ","targetNew":"Nové okno (_blank)","targetTop":"NajvrchnejÅ¡ie okno (_top)","targetSelf":"To isté okno (_self)","targetParent":"RodiÄovské okno (_parent)","langDirLTR":"Zľava doprava (LTR)","langDirRTL":"Sprava doľava (RTL)","styles":"Å týl","cssClasses":"Triedy Å¡týlu","width":"Šírka","height":"Výška","align":"Zarovnanie","alignLeft":"Vľavo","alignRight":"Vpravo","alignCenter":"Na stred","alignJustify":"ZarovnaÅ¥ do bloku","alignTop":"Nahor","alignMiddle":"Na stred","alignBottom":"Dole","alignNone":"Žiadne","invalidValue":"Neplatná hodnota.","invalidHeight":"Výška musí byÅ¥ Äíslo.","invalidWidth":"Šírka musí byÅ¥ Äíslo.","invalidCssLength":"Å pecifikovaná hodnota pre pole \"%1\" musí byÅ¥ kladné Äíslo s alebo bez platnej CSS mernej jednotky (px, %, in, cm, mm, em, ex, pt alebo pc).","invalidHtmlLength":"Å pecifikovaná hodnota pre pole \"%1\" musí byÅ¥ kladné Äíslo s alebo bez platnej HTML mernej jednotky (px alebo %).","invalidInlineStyle":"Zadaná hodnota pre inline Å¡týl musí pozostávaÅ¥ s jedného, alebo viac dvojíc formátu \"názov: hodnota\", oddelených bodkoÄiarkou.","cssLengthTooltip":"Vložte Äíslo pre hodnotu v pixeloch alebo Äíslo so správnou CSS jednotou (px, %, in, cm, mm, em, ex, pt alebo pc).","unavailable":"%1<span class=\"cke_accessibility\">, nedostupný</span>"},"about":{"copy":"Copyright &copy; $1. VÅ¡etky práva vyhradené.","dlgTitle":"O CKEditor-e","help":"ZaÅ¡krtnite $1 pre pomoc.","moreInfo":"Pre informácie o licenciách, prosíme, navÅ¡tívte naÅ¡u web stránku:","title":"O CKEditor-e","userGuide":"Používateľská príruÄka KCEditor-a"},"basicstyles":{"bold":"TuÄné","italic":"Kurzíva","strike":"PreÄiarknuté","subscript":"Dolný index","superscript":"Horný index","underline":"PodÄiarknuté"},"blockquote":{"toolbar":"Citácia"},"clipboard":{"copy":"KopírovaÅ¥","copyError":"BezpeÄnostné nastavenia Vášho prehliadaÄa nedovoľujú editoru automaticky spustiÅ¥ operáciu kopírovania. Prosím, použite na to klávesnicu (Ctrl/Cmd+C).","cut":"Vystrihnúť","cutError":"BezpeÄnostné nastavenia Vášho prehliadaÄa nedovoľujú editoru automaticky spustiÅ¥ operáciu vystrihnutia. Prosím, použite na to klávesnicu (Ctrl/Cmd+X).","paste":"VložiÅ¥","pasteArea":"Miesto pre vloženie","pasteMsg":"Prosím, vložte nasledovný rámÄek použitím klávesnice (<STRONG>Ctrl/Cmd+V</STRONG>) a stlaÄte OK.","securityMsg":"Kvôli vaÅ¡im bezpeÄnostným nastaveniam prehliadaÄa editor nie je schopný pristupovaÅ¥ k vaÅ¡ej schránke na kopírovanie priamo. Vložte to preto do tohto okna.","title":"VložiÅ¥"},"contextmenu":{"options":"Možnosti kontextového menu"},"button":{"selectedLabel":"%1 (Vybrané)"},"toolbar":{"toolbarCollapse":"ZbaliÅ¥ liÅ¡tu nástrojov","toolbarExpand":"RozbaliÅ¥ liÅ¡tu nástrojov","toolbarGroups":{"document":"Dokument","clipboard":"Schránka pre kopírovanie/Späť","editing":"Upravovanie","forms":"Formuláre","basicstyles":"Základné Å¡týly","paragraph":"Odstavec","links":"Odkazy","insert":"VložiÅ¥","styles":"Å týly","colors":"Farby","tools":"Nástroje"},"toolbars":"LiÅ¡ty nástrojov editora"},"elementspath":{"eleLabel":"Cesta prvkov","eleTitle":"%1 prvok"},"format":{"label":"Formát","panelTitle":"Formát","tag_address":"Adresa","tag_div":"Normálny (DIV)","tag_h1":"Nadpis 1","tag_h2":"Nadpis 2","tag_h3":"Nadpis 3","tag_h4":"Nadpis 4","tag_h5":"Nadpis 5","tag_h6":"Nadpis 6","tag_p":"Normálny","tag_pre":"Formátovaný"},"horizontalrule":{"toolbar":"VložiÅ¥ vodorovnú Äiaru"},"image":{"alertUrl":"Zadajte prosím URL obrázka","alt":"Alternatívny text","border":"Rám (border)","btnUpload":"OdoslaÅ¥ to na server","button2Img":"Chcete zmeniÅ¥ vybrané obrázkové tlaÄidlo na jednoduchý obrázok?","hSpace":"H-medzera","img2Button":"Chcete zmeniÅ¥ vybraný obrázok na obrázkové tlaÄidlo?","infoTab":"Informácie o obrázku","linkTab":"Odkaz","lockRatio":"Pomer zámky","menu":"Vlastnosti obrázka","resetSize":"Pôvodná veľkosÅ¥","title":"Vlastnosti obrázka","titleButton":"Vlastnosti obrázkového tlaÄidla","upload":"NahraÅ¥","urlMissing":"Chýba URL zdroja obrázka.","vSpace":"V-medzera","validateBorder":"Rám (border) musí byÅ¥ celé Äíslo.","validateHSpace":"H-medzera musí byÅ¥ celé Äíslo.","validateVSpace":"V-medzera musí byÅ¥ celé Äíslo."},"indent":{"indent":"ZväÄÅ¡iÅ¥ odsadenie","outdent":"ZmenÅ¡iÅ¥ odsadenie"},"fakeobjects":{"anchor":"Kotva","flash":"Flash animácia","hiddenfield":"Skryté pole","iframe":"IFrame","unknown":"Neznámy objekt"},"link":{"acccessKey":"Prístupový kľúÄ","advanced":"Rozšírené","advisoryContentType":"Pomocný typ obsahu","advisoryTitle":"Pomocný titulok","anchor":{"toolbar":"Kotva","menu":"UpraviÅ¥ kotvu","title":"Vlastnosti kotvy","name":"Názov kotvy","errorName":"Zadajte prosím názov kotvy","remove":"OdstrániÅ¥ kotvu"},"anchorId":"Podľa Id objektu","anchorName":"Podľa mena kotvy","charset":"Priradená znaková sada","cssClasses":"Triedy Å¡týlu","emailAddress":"E-Mailová adresa","emailBody":"Telo správy","emailSubject":"Predmet správy","id":"Id","info":"Informácie o odkaze","langCode":"Orientácia jazyka","langDir":"Orientácia jazyka","langDirLTR":"Zľava doprava (LTR)","langDirRTL":"Sprava doľava (RTL)","menu":"UpraviÅ¥ odkaz","name":"Názov","noAnchors":"(V dokumente nie sú dostupné žiadne kotvy)","noEmail":"Zadajte prosím e-mailovú adresu","noUrl":"Zadajte prosím URL odkazu","other":"<iný>","popupDependent":"ZávislosÅ¥ (Netscape)","popupFeatures":"Vlastnosti vyskakovacieho okna","popupFullScreen":"Celá obrazovka (IE)","popupLeft":"Ľavý okraj","popupLocationBar":"Panel umiestnenia (location bar)","popupMenuBar":"Panel ponuky (menu bar)","popupResizable":"Meniteľná veľkosÅ¥ (resizable)","popupScrollBars":"Posuvníky (scroll bars)","popupStatusBar":"Stavový riadok (status bar)","popupToolbar":"Panel nástrojov (toolbar)","popupTop":"Horný okraj","rel":"VzÅ¥ah (rel)","selectAnchor":"VybraÅ¥ kotvu","styles":"Å týl","tabIndex":"Poradie prvku (tab index)","target":"Cieľ","targetFrame":"<rámec>","targetFrameName":"Názov rámu cieľa","targetPopup":"<vyskakovacie okno>","targetPopupName":"Názov vyskakovacieho okna","title":"Odkaz","toAnchor":"Odkaz na kotvu v texte","toEmail":"E-mail","toUrl":"URL","toolbar":"Odkaz","type":"Typ odkazu","unlink":"OdstrániÅ¥ odkaz","upload":"NahraÅ¥"},"list":{"bulletedlist":"VložiÅ¥/OdstrániÅ¥ zoznam s odrážkami","numberedlist":"VložiÅ¥/OdstrániÅ¥ Äíslovaný zoznam"},"magicline":{"title":"Sem vložte paragraf"},"maximize":{"maximize":"MaximalizovaÅ¥","minimize":"MinimalizovaÅ¥"},"pastetext":{"button":"VložiÅ¥ ako Äistý text","title":"VložiÅ¥ ako Äistý text"},"pastefromword":{"confirmCleanup":"Vkladaný text vyzerá byÅ¥ skopírovaný z Wordu. Chcete ho automaticky vyÄistiÅ¥ pred vkladaním?","error":"Nebolo možné vyÄistiÅ¥ vložené dáta kvôli internej chybe","title":"VložiÅ¥ z Wordu","toolbar":"VložiÅ¥ z Wordu"},"removeformat":{"toolbar":"OdstrániÅ¥ formátovanie"},"sourcearea":{"toolbar":"Zdroj"},"specialchar":{"options":"Možnosti Å¡peciálneho znaku","title":"Výber Å¡peciálneho znaku","toolbar":"VložiÅ¥ Å¡peciálny znak"},"scayt":{"btn_about":"O KPPP (Kontrola pravopisu poÄas písania)","btn_dictionaries":"Slovníky","btn_disable":"ZakázaÅ¥ KPPP (Kontrola pravopisu poÄas písania)","btn_enable":"PovoliÅ¥ KPPP (Kontrola pravopisu poÄas písania)","btn_langs":"Jazyky","btn_options":"Možnosti","text_title":"Kontrola pravopisu poÄas písania"},"stylescombo":{"label":"Å týly","panelTitle":"Formátovanie Å¡týlov","panelTitle1":"Å týly bloku","panelTitle2":"Vnútroriadkové (inline) Å¡týly","panelTitle3":"Å týly objeku"},"table":{"border":"Šírka rámu (border)","caption":"Popis","cell":{"menu":"Bunka","insertBefore":"VložiÅ¥ bunku pred","insertAfter":"VložiÅ¥ bunku za","deleteCell":"VymazaÅ¥ bunky","merge":"ZlúÄiÅ¥ bunky","mergeRight":"ZlúÄiÅ¥ doprava","mergeDown":"ZlúÄiÅ¥ dole","splitHorizontal":"RozdeliÅ¥ bunky horizontálne","splitVertical":"RozdeliÅ¥ bunky vertikálne","title":"Vlastnosti bunky","cellType":"Typ bunky","rowSpan":"Rozsah riadkov","colSpan":"Rozsah stĺpcov","wordWrap":"Zalomovanie riadkov","hAlign":"Horizontálne zarovnanie","vAlign":"Vertikálne zarovnanie","alignBaseline":"Základná Äiara (baseline)","bgColor":"Farba pozadia","borderColor":"Farba rámu","data":"Dáta","header":"HlaviÄka","yes":"Ãno","no":"Nie","invalidWidth":"Šírka bunky musí byÅ¥ Äíslo.","invalidHeight":"Výška bunky musí byÅ¥ Äíslo.","invalidRowSpan":"Rozsah riadkov musí byÅ¥ celé Äíslo.","invalidColSpan":"Rozsah stĺpcov musí byÅ¥ celé Äíslo.","chooseColor":"VybraÅ¥"},"cellPad":"Odsadenie obsahu (cell padding)","cellSpace":"VzdialenosÅ¥ buniek (cell spacing)","column":{"menu":"Stĺpec","insertBefore":"VložiÅ¥ stĺpec pred","insertAfter":"VložiÅ¥ stĺpec po","deleteColumn":"ZmazaÅ¥ stĺpce"},"columns":"Stĺpce","deleteTable":"VymazaÅ¥ tabuľku","headers":"HlaviÄka","headersBoth":"Obe","headersColumn":"Prvý stĺpec","headersNone":"Žiadne","headersRow":"Prvý riadok","invalidBorder":"Å irka rámu musí byÅ¥ Äíslo.","invalidCellPadding":"Odsadenie v bunkách (cell padding) musí byÅ¥ kladné Äíslo.","invalidCellSpacing":"Medzera mädzi bunkami (cell spacing) musí byÅ¥ kladné Äíslo.","invalidCols":"PoÄet stĺpcov musí byÅ¥ Äíslo väÄÅ¡ie ako 0.","invalidHeight":"Výška tabuľky musí byÅ¥ Äíslo.","invalidRows":"PoÄet riadkov musí byÅ¥ Äíslo väÄÅ¡ie ako 0.","invalidWidth":"Å irka tabuľky musí byÅ¥ Äíslo.","menu":"Vlastnosti tabuľky","row":{"menu":"Riadok","insertBefore":"VložiÅ¥ riadok pred","insertAfter":"VložiÅ¥ riadok po","deleteRow":"VymazaÅ¥ riadky"},"rows":"Riadky","summary":"Prehľad","title":"Vlastnosti tabuľky","toolbar":"Tabuľka","widthPc":"percent","widthPx":"pixelov","widthUnit":"jednotka šírky"},"undo":{"redo":"Znovu","undo":"Späť"},"wsc":{"btnIgnore":"IgnorovaÅ¥","btnIgnoreAll":"IgnorovaÅ¥ vÅ¡etko","btnReplace":"Prepísat","btnReplaceAll":"Prepísat vÅ¡etko","btnUndo":"Späť","changeTo":"ZmeniÅ¥ na","errorLoading":"Chyba pri naÄítaní slovníka z adresy: %s.","ieSpellDownload":"Kontrola pravopisu nie je naiÅ¡talovaná. Chcete ju teraz stiahnuÅ¥?","manyChanges":"Kontrola pravopisu dokonÄená: Bolo zmenených %1 slov","noChanges":"Kontrola pravopisu dokonÄená: Neboli zmenené žiadne slová","noMispell":"Kontrola pravopisu dokonÄená: Neboli nájdené žiadne chyby pravopisu","noSuggestions":"- Žiadny návrh -","notAvailable":"PrepáÄte, ale služba je momentálne nedostupná.","notInDic":"Nie je v slovníku","oneChange":"Kontrola pravopisu dokonÄená: Bolo zmenené jedno slovo","progress":"Prebieha kontrola pravopisu...","title":"SkontrolovaÅ¥ pravopis","toolbar":"Kontrola pravopisu"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sl.js b/js/ckeditor/lang/sl.js
new file mode 100644
index 0000000..1145b77
--- /dev/null
+++ b/js/ckeditor/lang/sl.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sl']={"editor":"Bogat Urejevalnik Besedila","editorPanel":"Rich Text Editor ploÅ¡Äa","common":{"editorHelp":"Pritisnite ALT 0 za pomoÄ","browseServer":"Prebrskaj na strežniku","url":"URL","protocol":"Protokol","upload":"Naloži","uploadSubmit":"PoÅ¡lji na strežnik","image":"Slika","flash":"Flash","form":"Obrazec","checkbox":"Potrditveno polje","radio":"Izbirno polje","textField":"Vnosno polje","textarea":"Vnosno obmoÄje","hiddenField":"Skrito polje","button":"Gumb","select":"Spustno Polje","imageButton":"Slikovni Gumb","notSet":"<ni doloÄen>","id":"Id","name":"Ime","langDir":"Smer jezika","langDirLtr":"Od leve proti desni (LTR)","langDirRtl":"Od desne proti levi (RTL)","langCode":"Koda Jezika","longDescr":"Dolg opis URL-ja","cssClass":"Razred stilne predloge","advisoryTitle":"Predlagani naslov","cssStyle":"Slog","ok":"V redu","cancel":"PrekliÄi","close":"Zapri","preview":"Predogled","resize":"Potegni za spremembo velikosti","generalTab":"SploÅ¡no","advancedTab":"Napredno","validateNumberFailed":"Ta vrednost ni Å¡tevilo.","confirmNewPage":"Vse neshranjene spremembe te vsebine bodo izgubljene. Ali res želite naložiti novo stran?","confirmCancel":"Nekaj možnosti je bilo spremenjenih. Ali res želite zapreti okno?","options":"Možnosti","target":"Cilj","targetNew":"Novo Okno (_blank)","targetTop":"Vrhovno Okno (_top)","targetSelf":"Enako Okno (_self)","targetParent":"MatiÄno Okno (_parent)","langDirLTR":"Od leve proti desni (LTR)","langDirRTL":"Od desne proti levi (RTL)","styles":"Slog","cssClasses":"Razred stilne predloge","width":"Å irina","height":"ViÅ¡ina","align":"Poravnava","alignLeft":"Levo","alignRight":"Desno","alignCenter":"Sredinsko","alignJustify":"Obojestranska poravnava","alignTop":"Na vrh","alignMiddle":"V sredino","alignBottom":"Na dno","alignNone":"Brez poravnave","invalidValue":"Neveljavna vrednost.","invalidHeight":"ViÅ¡ina mora biti Å¡tevilo.","invalidWidth":"Å irina mora biti Å¡tevilo.","invalidCssLength":"Vrednost doloÄena za \"%1\" polje mora biti pozitivna Å¡tevilka z ali brez veljavne CSS enote za merjenje (px, %, in, cm, mm, em, ex, pt, ali pc).","invalidHtmlLength":"Vrednost doloÄena za \"%1\" polje mora biti pozitivna Å¡tevilka z ali brez veljavne HTML enote za merjenje (px ali %).","invalidInlineStyle":"Vrednost doloÄena za inline slog mora biti sestavljena iz ene ali veÄ tork (tuples) z obliko \"ime : vrednost\", loÄenih z podpiÄji.","cssLengthTooltip":"Vnesite Å¡tevilko za vrednost v slikovnih pikah (pixels) ali Å¡tevilko z veljavno CSS enoto (px, %, in, cm, mm, em, ex, pt, ali pc).","unavailable":"%1<span class=\"cke_accessibility\">, nedosegljiv</span>"},"about":{"copy":"Copyright &copy; $1. Vse pravice pridržane.","dlgTitle":"O programu CKEditor","help":"Preverite $1 za pomoÄ.","moreInfo":"Za informacijo o licenci prosim obiÅ¡Äite naÅ¡o spletno stran:","title":"O programu CKEditor","userGuide":"CKEditor Navodila za Uporabo"},"basicstyles":{"bold":"Krepko","italic":"LežeÄe","strike":"PreÄrtano","subscript":"Podpisano","superscript":"Nadpisano","underline":"PodÄrtano"},"blockquote":{"toolbar":"Citat"},"clipboard":{"copy":"Kopiraj","copyError":"Varnostne nastavitve brskalnika ne dopuÅ¡Äajo samodejnega kopiranja. Uporabite kombinacijo tipk na tipkovnici (Ctrl/Cmd+C).","cut":"Izreži","cutError":"Varnostne nastavitve brskalnika ne dopuÅ¡Äajo samodejnega izrezovanja. Uporabite kombinacijo tipk na tipkovnici (Ctrl/Cmd+X).","paste":"Prilepi","pasteArea":"Prilepi Prostor","pasteMsg":"Prosim prilepite v sleÄi okvir s pomoÄjo tipkovnice (<STRONG>Ctrl/Cmd+V</STRONG>) in pritisnite <STRONG>V redu</STRONG>.","securityMsg":"Zaradi varnostnih nastavitev vaÅ¡ega brskalnika urejevalnik ne more neposredno dostopati do odložiÅ¡Äa. Vsebino odložiÅ¡Äa ponovno prilepite v to okno.","title":"Prilepi"},"contextmenu":{"options":"Možnosti Kontekstnega Menija"},"button":{"selectedLabel":"%1 (Izbrano)"},"toolbar":{"toolbarCollapse":"SkrÄi Orodno Vrstico","toolbarExpand":"RazÅ¡iri Orodno Vrstico","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Urejevalnik orodne vrstice"},"elementspath":{"eleLabel":"Pot elementov","eleTitle":"%1 element"},"format":{"label":"Oblika","panelTitle":"Oblika","tag_address":"Napis","tag_div":"Navaden (DIV)","tag_h1":"Naslov 1","tag_h2":"Naslov 2","tag_h3":"Naslov 3","tag_h4":"Naslov 4","tag_h5":"Naslov 5","tag_h6":"Naslov 6","tag_p":"Navaden","tag_pre":"Oblikovan"},"horizontalrule":{"toolbar":"Vstavi vodoravno Ärto"},"image":{"alertUrl":"Vnesite URL slike","alt":"Nadomestno besedilo","border":"Obroba","btnUpload":"PoÅ¡lji na strežnik","button2Img":"ŽeliÅ¡ pretvoriti izbrani gumb s sliko v preprosto sliko?","hSpace":"Vodoravni razmik","img2Button":"ŽeliÅ¡ pretvoriti izbrano sliko v gumb s sliko?","infoTab":"Podatki o sliki","linkTab":"Povezava","lockRatio":"Zakleni razmerje","menu":"Lastnosti slike","resetSize":"Ponastavi velikost","title":"Lastnosti slike","titleButton":"Lastnosti gumba s sliko","upload":"PoÅ¡lji","urlMissing":"Manjka vir (URL) slike.","vSpace":"NavpiÄni razmik","validateBorder":"Meja mora biti celo Å¡tevilo.","validateHSpace":"HSpace mora biti celo Å¡tevilo.","validateVSpace":"VSpace mora biti celo Å¡tevilo."},"indent":{"indent":"PoveÄaj zamik","outdent":"ZmanjÅ¡aj zamik"},"fakeobjects":{"anchor":"Sidro","flash":"Flash animacija","hiddenfield":"Skrito polje","iframe":"IFrame","unknown":"Neznan objekt"},"link":{"acccessKey":"Dostopno Geslo","advanced":"Napredno","advisoryContentType":"Predlagani tip vsebine (content-type)","advisoryTitle":"Predlagani naslov","anchor":{"toolbar":"Vstavi/uredi zaznamek","menu":"Lastnosti zaznamka","title":"Lastnosti zaznamka","name":"Ime zaznamka","errorName":"Prosim vnesite ime zaznamka","remove":"Remove Anchor"},"anchorId":"Po ID-ju elementa","anchorName":"Po imenu zaznamka","charset":"Kodna tabela povezanega vira","cssClasses":"Razred stilne predloge","emailAddress":"Elektronski naslov","emailBody":"Vsebina sporoÄila","emailSubject":"Predmet sporoÄila","id":"Id","info":"Podatki o povezavi","langCode":"Smer jezika","langDir":"Smer jezika","langDirLTR":"Od leve proti desni (LTR)","langDirRTL":"Od desne proti levi (RTL)","menu":"Uredi povezavo","name":"Ime","noAnchors":"(V tem dokumentu ni zaznamkov)","noEmail":"Vnesite elektronski naslov","noUrl":"Vnesite URL povezave","other":"<drug>","popupDependent":"Podokno (Netscape)","popupFeatures":"ZnaÄilnosti pojavnega okna","popupFullScreen":"Celozaslonska slika (IE)","popupLeft":"Lega levo","popupLocationBar":"Naslovna vrstica","popupMenuBar":"Menijska vrstica","popupResizable":"Spremenljive velikosti","popupScrollBars":"Drsniki","popupStatusBar":"Vrstica stanja","popupToolbar":"Orodna vrstica","popupTop":"Lega na vrhu","rel":"Odnos","selectAnchor":"Izberi zaznamek","styles":"Slog","tabIndex":"Å tevilka tabulatorja","target":"Cilj","targetFrame":"<okvir>","targetFrameName":"Ime ciljnega okvirja","targetPopup":"<pojavno okno>","targetPopupName":"Ime pojavnega okna","title":"Povezava","toAnchor":"Zaznamek na tej strani","toEmail":"Elektronski naslov","toUrl":"URL","toolbar":"Vstavi/uredi povezavo","type":"Vrsta povezave","unlink":"Odstrani povezavo","upload":"Prenesi"},"list":{"bulletedlist":"OznaÄen seznam","numberedlist":"OÅ¡tevilÄen seznam"},"magicline":{"title":"Vstavite odstavek tukaj"},"maximize":{"maximize":"Maksimiraj","minimize":"Minimiraj"},"pastetext":{"button":"Prilepi kot golo besedilo","title":"Prilepi kot golo besedilo"},"pastefromword":{"confirmCleanup":"Besedilo, ki ga želite prilepiti je kopirano iz Word-a. Ali ga želite oÄistiti, preden ga prilepite?","error":"Ni bilo mogoÄe oÄistiti prilepljenih podatkov zaradi notranje napake","title":"Prilepi iz Worda","toolbar":"Prilepi iz Worda"},"removeformat":{"toolbar":"Odstrani oblikovanje"},"sourcearea":{"toolbar":"Izvorna koda"},"specialchar":{"options":"Možnosti Posebnega Znaka","title":"Izberi Posebni Znak","toolbar":"Vstavi posebni znak"},"scayt":{"btn_about":"O storitvi SCAYT","btn_dictionaries":"Slovarji","btn_disable":"OnemogoÄi SCAYT","btn_enable":"OmogoÄi SCAYT","btn_langs":"Jeziki","btn_options":"Možnosti","text_title":"ÄŒrkovanje med tipkanjem"},"stylescombo":{"label":"Slog","panelTitle":"Oblikovalni Stili","panelTitle1":"Slogi odstavkov","panelTitle2":"Slogi besedila","panelTitle3":"Slogi objektov"},"table":{"border":"Velikost obrobe","caption":"Naslov","cell":{"menu":"Celica","insertBefore":"Vstavi celico pred","insertAfter":"Vstavi celico za","deleteCell":"IzbriÅ¡i celice","merge":"Združi celice","mergeRight":"Združi desno","mergeDown":"Druži navzdol","splitHorizontal":"Razdeli celico vodoravno","splitVertical":"Razdeli celico navpiÄno","title":"Lastnosti celice","cellType":"Vrsta celice","rowSpan":"Razpon vrstic","colSpan":"Razpon stolpcev","wordWrap":"Prelom besedila","hAlign":"Vodoravna poravnava","vAlign":"NavpiÄna poravnava","alignBaseline":"Osnovnica","bgColor":"Barva ozadja","borderColor":"Barva obrobe","data":"Podatki","header":"Glava","yes":"Da","no":"Ne","invalidWidth":"Å irina celice mora biti Å¡tevilo.","invalidHeight":"ViÅ¡ina celice mora biti Å¡tevilo.","invalidRowSpan":"Razpon vrstic mora biti celo Å¡tevilo.","invalidColSpan":"Razpon stolpcev mora biti celo Å¡tevilo.","chooseColor":"Izberi"},"cellPad":"Polnilo med celicami","cellSpace":"Razmik med celicami","column":{"menu":"Stolpec","insertBefore":"Vstavi stolpec pred","insertAfter":"Vstavi stolpec za","deleteColumn":"IzbriÅ¡i stolpce"},"columns":"Stolpci","deleteTable":"IzbriÅ¡i tabelo","headers":"Glave","headersBoth":"Oboje","headersColumn":"Prvi stolpec","headersNone":"Brez","headersRow":"Prva vrstica","invalidBorder":"Å irina obrobe mora biti Å¡tevilo.","invalidCellPadding":"Zamik celic mora biti Å¡tevilo","invalidCellSpacing":"Razmik med celicami mora biti Å¡tevilo.","invalidCols":"Å tevilo stolpcev mora biti veÄje od 0.","invalidHeight":"ViÅ¡ina tabele mora biti Å¡tevilo.","invalidRows":"Å tevilo vrstic mora biti veÄje od 0.","invalidWidth":"Å irina tabele mora biti Å¡tevilo.","menu":"Lastnosti tabele","row":{"menu":"Vrstica","insertBefore":"Vstavi vrstico pred","insertAfter":"Vstavi vrstico za","deleteRow":"IzbriÅ¡i vrstice"},"rows":"Vrstice","summary":"Povzetek","title":"Lastnosti tabele","toolbar":"Tabela","widthPc":"procentov","widthPx":"pik","widthUnit":"enota Å¡irine"},"undo":{"redo":"Ponovi","undo":"Razveljavi"},"wsc":{"btnIgnore":"Prezri","btnIgnoreAll":"Prezri vse","btnReplace":"Zamenjaj","btnReplaceAll":"Zamenjaj vse","btnUndo":"Razveljavi","changeTo":"Spremeni v","errorLoading":"Napaka pri nalaganju storitve programa na naslovu %s.","ieSpellDownload":"ÄŒrkovalnik ni nameÅ¡Äen. Ali ga želite prenesti sedaj?","manyChanges":"ÄŒrkovanje je konÄano: Spremenjenih je bilo %1 besed","noChanges":"ÄŒrkovanje je konÄano: Nobena beseda ni bila spremenjena","noMispell":"ÄŒrkovanje je konÄano: Brez napak","noSuggestions":"- Ni predlogov -","notAvailable":"Oprostite, storitev trenutno ni dosegljiva.","notInDic":"Ni v slovarju","oneChange":"ÄŒrkovanje je konÄano: Spremenjena je bila ena beseda","progress":"Preverjanje Ärkovanja se izvaja...","title":"ÄŒrkovalnik","toolbar":"Preveri Ärkovanje"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sq.js b/js/ckeditor/lang/sq.js
new file mode 100644
index 0000000..36c15bf
--- /dev/null
+++ b/js/ckeditor/lang/sq.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sq']={"editor":"Redaktues i Pasur Teksti","editorPanel":"Paneli i redaktuesit të tekstit të plotë","common":{"editorHelp":"Shtyp ALT 0 për ndihmë","browseServer":"Shfleto në Server","url":"URL","protocol":"Protokolli","upload":"Ngarko","uploadSubmit":"Dërgo në server","image":"Imazh","flash":"Objekt flash","form":"Formular","checkbox":"Checkbox","radio":"Buton radio","textField":"Fushë tekst","textarea":"Hapësirë tekst","hiddenField":"Fushë e fshehur","button":"Buton","select":"Menu zgjedhjeje","imageButton":"Buton imazhi","notSet":"<e pazgjedhur>","id":"Id","name":"Emër","langDir":"Kod gjuhe","langDirLtr":"Nga e majta në të djathtë (LTR)","langDirRtl":"Nga e djathta në të majtë (RTL)","langCode":"Kod gjuhe","longDescr":"Përshkrim i hollësishëm","cssClass":"Klasa stili CSS","advisoryTitle":"Titull","cssStyle":"Stil","ok":"OK","cancel":"Anulo","close":"Mbyll","preview":"Parashiko","resize":"Ripërmaso","generalTab":"Të përgjithshme","advancedTab":"Të përparuara","validateNumberFailed":"Vlera e futur nuk është një numër","confirmNewPage":"Çdo ndryshim që nuk është ruajtur do humbasë. Je i sigurtë që dëshiron të krijosh një faqe të re?","confirmCancel":"Disa opsione kanë ndryshuar. Je i sigurtë që dëshiron ta mbyllësh dritaren?","options":"Opsione","target":"Objektivi","targetNew":"Dritare e re (_blank)","targetTop":"Dritare në plan të parë (_top)","targetSelf":"E njëjta dritare (_self)","targetParent":"Dritarja prind (_parent)","langDirLTR":"Nga e majta në të djathë (LTR)","langDirRTL":"Nga e djathta në të majtë (RTL)","styles":"Stil","cssClasses":"Klasa Stili CSS","width":"Gjerësi","height":"Lartësi","align":"Rreshtim","alignLeft":"Majtas","alignRight":"Djathtas","alignCenter":"Qendër","alignJustify":"Zgjero","alignTop":"Lart","alignMiddle":"Në mes","alignBottom":"Poshtë","alignNone":"Asnjë","invalidValue":"Vlerë e pavlefshme","invalidHeight":"Lartësia duhet të jetë një numër","invalidWidth":"Gjerësia duhet të jetë një numër","invalidCssLength":"Vlera e fushës \"%1\" duhet të jetë një numër pozitiv me apo pa njësi matëse të vlefshme CSS (px, %, in, cm, mm, em, ex, pt ose pc).","invalidHtmlLength":"Vlera e fushës \"%1\" duhet të jetë një numër pozitiv me apo pa njësi matëse të vlefshme HTML (px ose %)","invalidInlineStyle":"Stili inline duhet të jetë një apo disa vlera të formatit \"emër: vlerë\", ndarë nga pikëpresje.","cssLengthTooltip":"Fut një numër për vlerën në pixel apo një numër me një njësi të vlefshme CSS (px, %, in, cm, mm, ex, pt, ose pc).","unavailable":"%1<span class=\"cke_accessibility\">, i padisponueshëm</span>"},"about":{"copy":"Të drejtat e kopjimit &copy; $1. Të gjitha të drejtat e rezervuara.","dlgTitle":"Rreth CKEditor","help":"Kontrollo $1 për ndihmë.","moreInfo":"Për informacione rreth licencave shih faqen tonë:","title":"Rreth CKEditor","userGuide":"Udhëzuesi i Shfrytëzuesit të CKEditor"},"basicstyles":{"bold":"Trash","italic":"Pjerrët","strike":"Nëpërmes","subscript":"Nën-skriptë","superscript":"Super-skriptë","underline":"Nënvijëzuar"},"blockquote":{"toolbar":"Citatet"},"clipboard":{"copy":"Kopjo","copyError":"Të dhënat e sigurisë së shfletuesit tuaj nuk lejojnë që redaktuesi automatikisht të kryej veprimin e kopjimit. Ju lutemi shfrytëzoni tastierën për këtë veprim (Ctrl/Cmd+C).","cut":"Preje","cutError":"Të dhënat e sigurisë së shfletuesit tuaj nuk lejojnë që redaktuesi automatikisht të kryej veprimin e prerjes. Ju lutemi shfrytëzoni tastierën për këtë veprim (Ctrl/Cmd+X).","paste":"Hidhe","pasteArea":"Hapësira Hedhëse","pasteMsg":"Ju lutemi hidhni brenda kutizës në vijim duke shfrytëzuar tastierën (<strong>Ctrl/Cmd+V</strong>) dhe shtypni Mirë.","securityMsg":"Për shkak të dhënave të sigurisë së shfletuesit tuaj, redaktuesi nuk është në gjendje të i qaset drejtpërdrejtë të dhanve të tabelës suaj të punës. Ju duhet të hidhni atë përsëri në këtë dritare.","title":"Hidhe"},"contextmenu":{"options":"Mundësitë e Menysë së Kontekstit"},"button":{"selectedLabel":"%1 (Përzgjedhur)"},"toolbar":{"toolbarCollapse":"Zvogëlo Shiritin","toolbarExpand":"Zgjero Shiritin","toolbarGroups":{"document":"Dokument","clipboard":"Tabela Punës/Ribëje","editing":"Duke Redaktuar","forms":"Formular","basicstyles":"Stili Bazë","paragraph":"Paragraf","links":"Nyjet","insert":"Shto","styles":"Stil","colors":"Ngjyrat","tools":"Mjetet"},"toolbars":"Shiritet e Redaktuesit"},"elementspath":{"eleLabel":"Rruga e elementeve","eleTitle":"%1 element"},"format":{"label":"Formati","panelTitle":"Formati i Paragrafit","tag_address":"Adresa","tag_div":"Normal (DIV)","tag_h1":"Titulli 1","tag_h2":"Titulli 2","tag_h3":"Titulli 3","tag_h4":"Titulli 4","tag_h5":"Titulli 5","tag_h6":"Titulli 6","tag_p":"Normal","tag_pre":"Formatuar"},"horizontalrule":{"toolbar":"Vendos Vijë Horizontale"},"image":{"alertUrl":"Ju lutemi shkruani URL-në e fotos","alt":"Tekst Alternativ","border":"Korniza","btnUpload":"Dërgo në server","button2Img":"Dëshironi të e ndërroni pullën e fotos së selektuar në një foto të thjeshtë?","hSpace":"HSpace","img2Button":"Dëshironi të ndryshoni foton e përzgjedhur në pullë?","infoTab":"Informacione mbi Fotografinë","linkTab":"Nyja","lockRatio":"Mbyll Racionin","menu":"Karakteristikat e Fotografisë","resetSize":"Rikthe Madhësinë","title":"Karakteristikat e Fotografisë","titleButton":"Karakteristikat e Pullës së Fotografisë","upload":"Ngarko","urlMissing":"Mungon URL e burimit të fotografisë.","vSpace":"Hapësira Vertikale","validateBorder":"Korniza duhet të jetë numër i plotë.","validateHSpace":"Hapësira horizontale duhet të jetë numër i plotë.","validateVSpace":"Hapësira vertikale duhet të jetë numër i plotë."},"indent":{"indent":"Rrite Identin","outdent":"Zvogëlo Identin"},"fakeobjects":{"anchor":"Spirancë","flash":"Objekt flash","hiddenfield":"Fushë e fshehur","iframe":"IFrame","unknown":"Objekt i Panjohur"},"link":{"acccessKey":"Sipas ID-së së Elementit","advanced":"Të përparuara","advisoryContentType":"Lloji i Përmbajtjes Këshillimore","advisoryTitle":"Titull","anchor":{"toolbar":"Spirancë","menu":"Redakto Spirancën","title":"Anchor Properties","name":"Emri i Spirancës","errorName":"Ju lutemi shkruani emrin e spirancës","remove":"Largo Spirancën"},"anchorId":"Sipas ID-së së Elementit","anchorName":"Sipas Emrit të Spirancës","charset":"Seti i Karaktereve të Burimeve të Nëdlidhura","cssClasses":"Klasa stili CSS","emailAddress":"Posta Elektronike","emailBody":"Trupi i Porosisë","emailSubject":"Titulli i Porosisë","id":"Id","info":"Informacione të Nyjes","langCode":"Kod gjuhe","langDir":"Drejtim teksti","langDirLTR":"Nga e majta në të djathë (LTR)","langDirRTL":"Nga e djathta në të majtë (RTL)","menu":"Redakto Nyjen","name":"Emër","noAnchors":"(Nuk ka asnjë spirancë në dokument)","noEmail":"Ju lutemi shkruani postën elektronike","noUrl":"Ju lutemi shkruani URL-në e nyjes","other":"<tjetër>","popupDependent":"E Varur (Netscape)","popupFeatures":"Karakteristikat e Dritares së Dialogut","popupFullScreen":"Ekran i Plotë (IE)","popupLeft":"Pozita Majtas","popupLocationBar":"Shiriti i Lokacionit","popupMenuBar":"Shiriti i Menysë","popupResizable":"I ndryshueshëm","popupScrollBars":"Shiritat zvarritës","popupStatusBar":"Shiriti i Statutit","popupToolbar":"Shiriti i Mejteve","popupTop":"Top Pozita","rel":"Marrëdhëniet","selectAnchor":"Përzgjidh një Spirancë","styles":"Stil","tabIndex":"Indeksi i fletave","target":"Objektivi","targetFrame":"<frame>","targetFrameName":"Emri i Kornizës së Synuar","targetPopup":"<popup window>","targetPopupName":"Emri i Dritares së Dialogut","title":"Nyja","toAnchor":"Lidhu me spirancën në tekst","toEmail":"Posta Elektronike","toUrl":"URL","toolbar":"Nyja","type":"Lloji i Nyjes","unlink":"Largo Nyjen","upload":"Ngarko"},"list":{"bulletedlist":"Vendos/Largo Listën me Pika","numberedlist":"Vendos/Largo Listën me Numra"},"magicline":{"title":"Vendos paragraf këtu"},"maximize":{"maximize":"Zmadho","minimize":"Zvogëlo"},"pastetext":{"button":"Hidhe si tekst të thjeshtë","title":"Hidhe si Tekst të Thjeshtë"},"pastefromword":{"confirmCleanup":"Teksti që dëshironi të e hidhni siç duket është kopjuar nga Word-i. Dëshironi të e pastroni para se të e hidhni?","error":"Nuk ishte e mundur të fshiheshin të dhënat e hedhura për shkak të një gabimi të brendshëm","title":"Hidhe nga Word-i","toolbar":"Hidhe nga Word-i"},"removeformat":{"toolbar":"Largo Formatin"},"sourcearea":{"toolbar":"Burimi"},"specialchar":{"options":"Mundësitë për Karaktere Speciale","title":"Përzgjidh Karakter Special","toolbar":"Vendos Karakter Special"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stil","panelTitle":"Stilet e Formatimit","panelTitle1":"Stilet e Bllokut","panelTitle2":"Stili i Brendshëm","panelTitle3":"Stilet e Objektit"},"table":{"border":"Madhësia e kornizave","caption":"Titull","cell":{"menu":"Qeli","insertBefore":"Shto Qeli Para","insertAfter":"Shto Qeli Prapa","deleteCell":"Gris Qelitë","merge":"Bashko Qelitë","mergeRight":"Bashko Djathtas","mergeDown":"Bashko Poshtë","splitHorizontal":"Ndaj Qelinë Horizontalisht","splitVertical":"Ndaj Qelinë Vertikalisht","title":"Rekuizitat e Qelisë","cellType":"Lloji i Qelisë","rowSpan":"Bashko Rreshtat","colSpan":"Bashko Kolonat","wordWrap":"Fund i Fjalës","hAlign":"Bashkimi Horizontal","vAlign":"Bashkimi Vertikal","alignBaseline":"Baza","bgColor":"Ngjyra e Prapavijës","borderColor":"Ngjyra e Kornizave","data":"Të dhënat","header":"Koka","yes":"Po","no":"Jo","invalidWidth":"Gjerësia e qelisë duhet të jetë numër.","invalidHeight":"Lartësia e qelisë duhet të jetë numër.","invalidRowSpan":"Hapësira e rreshtave duhet të jetë numër i plotë.","invalidColSpan":"Hapësira e kolonave duhet të jetë numër i plotë.","chooseColor":"Përzgjidh"},"cellPad":"Mbushja e qelisë","cellSpace":"Hapësira e qelisë","column":{"menu":"Kolona","insertBefore":"Vendos Kolonë Para","insertAfter":"Vendos Kolonë Pas","deleteColumn":"Gris Kolonat"},"columns":"Kolonat","deleteTable":"Gris Tabelën","headers":"Kokat","headersBoth":"Së bashku","headersColumn":"Kolona e parë","headersNone":"Asnjë","headersRow":"Rreshti i Parë","invalidBorder":"Madhësia e kufinjve duhet të jetë numër.","invalidCellPadding":"Mbushja e qelisë duhet të jetë numër pozitiv.","invalidCellSpacing":"Hapësira e qelisë duhet të jetë numër pozitiv.","invalidCols":"Numri i kolonave duhet të jetë numër më i madh se 0.","invalidHeight":"Lartësia e tabelës duhet të jetë numër.","invalidRows":"Numri i rreshtave duhet të jetë numër më i madh se 0.","invalidWidth":"Gjerësia e tabelës duhet të jetë numër.","menu":"Karakteristikat e Tabelës","row":{"menu":"Rreshti","insertBefore":"Shto Rresht Para","insertAfter":"Shto Rresht Prapa","deleteRow":"Gris Rreshtat"},"rows":"Rreshtat","summary":"Përmbledhje","title":"Karakteristikat e Tabelës","toolbar":"Tabela","widthPc":"përqind","widthPx":"piksell","widthUnit":"njësia e gjerësisë"},"undo":{"redo":"Ribëje","undo":"Rizhbëje"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sr-latn.js b/js/ckeditor/lang/sr-latn.js
new file mode 100644
index 0000000..0e51ad5
--- /dev/null
+++ b/js/ckeditor/lang/sr-latn.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sr-latn']={"editor":"Bogati ureÄ‘ivaÄ teksta","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Pretraži server","url":"URL","protocol":"Protokol","upload":"PoÅ¡alji","uploadSubmit":"PoÅ¡alji na server","image":"Slika","flash":"FleÅ¡","form":"Forma","checkbox":"Polje za potvrdu","radio":"Radio-dugme","textField":"Tekstualno polje","textarea":"Zona teksta","hiddenField":"Skriveno polje","button":"Dugme","select":"Izborno polje","imageButton":"Dugme sa slikom","notSet":"<nije postavljeno>","id":"Id","name":"Naziv","langDir":"Smer jezika","langDirLtr":"S leva na desno (LTR)","langDirRtl":"S desna na levo (RTL)","langCode":"Kôd jezika","longDescr":"Pun opis URL","cssClass":"Stylesheet klase","advisoryTitle":"Advisory naslov","cssStyle":"Stil","ok":"OK","cancel":"Otkaži","close":"Zatvori","preview":"Izgled stranice","resize":"Resize","generalTab":"OpÅ¡te","advancedTab":"Napredni tagovi","validateNumberFailed":"Ova vrednost nije broj.","confirmNewPage":"NesaÄuvane promene ovog sadržaja će biti izgubljene. Jeste li sigurni da želita da uÄitate novu stranu?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Opcije","target":"Meta","targetNew":"Novi prozor (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Isti prozor (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"S leva na desno (LTR)","langDirRTL":"S desna na levo (RTL)","styles":"Stil","cssClasses":"Stylesheet klase","width":"Å irina","height":"Visina","align":"Ravnanje","alignLeft":"Levo","alignRight":"Desno","alignCenter":"Sredina","alignJustify":"Obostrano ravnanje","alignTop":"Vrh","alignMiddle":"Sredina","alignBottom":"Dole","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Visina mora biti broj.","invalidWidth":"Å irina mora biti broj.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Podebljano","italic":"Kurziv","strike":"Precrtano","subscript":"Indeks","superscript":"Stepen","underline":"PodvuÄeno"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Kopiraj","copyError":"Sigurnosna podeÅ¡avanja VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog kopiranja teksta. Molimo Vas da koristite preÄicu sa tastature (Ctrl/Cmd+C).","cut":"Iseci","cutError":"Sigurnosna podeÅ¡avanja VaÅ¡eg pretraživaÄa ne dozvoljavaju operacije automatskog isecanja teksta. Molimo Vas da koristite preÄicu sa tastature (Ctrl/Cmd+X).","paste":"Zalepi","pasteArea":"Prostor za lepljenje","pasteMsg":"Molimo Vas da zalepite unutar donje povrine koristeći tastaturnu preÄicu (<STRONG>Ctrl/Cmd+V</STRONG>) i da pritisnete <STRONG>OK</STRONG>.","securityMsg":"Zbog sigurnosnih postavki vaÅ¡eg pregledaÄa, editor nije u mogućnosti da direktno pristupi podacima u klipbordu. Potrebno je da zalepite joÅ¡ jednom u ovom prozoru.","title":"Zalepi"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Suzi alatnu traku","toolbarExpand":"ProÅ¡iri alatnu traku","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Alatne trake"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Format","panelTitle":"Format","tag_address":"Adresa","tag_div":"Normalno (DIV)","tag_h1":"Naslov 1","tag_h2":"Naslov 2","tag_h3":"Naslov 3","tag_h4":"Naslov 4","tag_h5":"Naslov 5","tag_h6":"Naslov 6","tag_p":"Normal","tag_pre":"Formatirano"},"horizontalrule":{"toolbar":"Unesi horizontalnu liniju"},"image":{"alertUrl":"Unesite URL slike","alt":"Alternativni tekst","border":"Okvir","btnUpload":"PoÅ¡alji na server","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"Info slike","linkTab":"Link","lockRatio":"ZakljuÄaj odnos","menu":"Osobine slika","resetSize":"Resetuj veliÄinu","title":"Osobine slika","titleButton":"Osobine dugmeta sa slikom","upload":"PoÅ¡alji","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"Uvećaj levu marginu","outdent":"Smanji levu marginu"},"fakeobjects":{"anchor":"Unesi/izmeni sidro","flash":"Flash Animation","hiddenfield":"Skriveno polje","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"Pristupni taster","advanced":"Napredni tagovi","advisoryContentType":"Advisory vrsta sadržaja","advisoryTitle":"Advisory naslov","anchor":{"toolbar":"Unesi/izmeni sidro","menu":"Osobine sidra","title":"Osobine sidra","name":"Naziv sidra","errorName":"Unesite naziv sidra","remove":"Ukloni sidro"},"anchorId":"Po Id-u elementa","anchorName":"Po nazivu sidra","charset":"Linked Resource Charset","cssClasses":"Stylesheet klase","emailAddress":"E-Mail adresa","emailBody":"Sadržaj poruke","emailSubject":"Naslov","id":"Id","info":"Link Info","langCode":"Smer jezika","langDir":"Smer jezika","langDirLTR":"S leva na desno (LTR)","langDirRTL":"S desna na levo (RTL)","menu":"Izmeni link","name":"Naziv","noAnchors":"(Nema dostupnih sidra)","noEmail":"Otkucajte adresu elektronske pote","noUrl":"Unesite URL linka","other":"<оÑтало>","popupDependent":"Zavisno (Netscape)","popupFeatures":"Mogućnosti popup prozora","popupFullScreen":"Prikaz preko celog ekrana (IE)","popupLeft":"Od leve ivice ekrana (px)","popupLocationBar":"Lokacija","popupMenuBar":"Kontekstni meni","popupResizable":"Promenljive veliÄine","popupScrollBars":"Scroll bar","popupStatusBar":"Statusna linija","popupToolbar":"Toolbar","popupTop":"Od vrha ekrana (px)","rel":"Odnos","selectAnchor":"Odaberi sidro","styles":"Stil","tabIndex":"Tab indeks","target":"Meta","targetFrame":"<okvir>","targetFrameName":"Naziv odrediÅ¡nog frejma","targetPopup":"<popup prozor>","targetPopupName":"Naziv popup prozora","title":"Link","toAnchor":"Sidro na ovoj stranici","toEmail":"E-Mail","toUrl":"URL","toolbar":"Unesi/izmeni link","type":"Vrsta linka","unlink":"Ukloni link","upload":"PoÅ¡alji"},"list":{"bulletedlist":"Nenabrojiva lista","numberedlist":"Nabrojiva lista"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Zalepi kao Äist tekst","title":"Zalepi kao Äist tekst"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Zalepi iz Worda","toolbar":"Zalepi iz Worda"},"removeformat":{"toolbar":"Ukloni formatiranje"},"sourcearea":{"toolbar":"Kôd"},"specialchar":{"options":"Special Character Options","title":"Odaberite specijalni karakter","toolbar":"Unesi specijalni karakter"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Stil","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"VeliÄina okvira","caption":"Naslov tabele","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"ObriÅ¡i ćelije","merge":"Spoj celije","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Razmak ćelija","cellSpace":"Ćelijski prostor","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"ObriÅ¡i kolone"},"columns":"Kolona","deleteTable":"IzbriÅ¡i tabelu","headers":"Zaglavlja","headersBoth":"Oba","headersColumn":"Prva kolona","headersNone":"None","headersRow":"Prvi red","invalidBorder":"VeliÄina okvira mora biti broj.","invalidCellPadding":"Padding polja mora biti pozitivan broj.","invalidCellSpacing":"Razmak izmeÄ‘u ćelija mora biti pozitivan broj.","invalidCols":"Broj kolona mora biti broj veći od 0.","invalidHeight":"Visina tabele mora biti broj.","invalidRows":"Broj redova mora biti veći od 0.","invalidWidth":"Å irina tabele mora biti broj.","menu":"Osobine tabele","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"ObriÅ¡i redove"},"rows":"Redova","summary":"Sažetak","title":"Osobine tabele","toolbar":"Tabela","widthPc":"procenata","widthPx":"piksela","widthUnit":"jedinica za Å¡irinu"},"undo":{"redo":"Ponovi akciju","undo":"Poni�ti akciju"},"wsc":{"btnIgnore":"IgnoriÅ¡i","btnIgnoreAll":"IgnoriÅ¡i sve","btnReplace":"Zameni","btnReplaceAll":"Zameni sve","btnUndo":"Vrati akciju","changeTo":"Izmeni","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Provera spelovanja nije instalirana. Da li želite da je skinete sa Interneta?","manyChanges":"Provera spelovanja zavrÅ¡ena: %1 reÄ(i) je izmenjeno","noChanges":"Provera spelovanja zavrÅ¡ena: Nije izmenjena nijedna rec","noMispell":"Provera spelovanja zavrÅ¡ena: greÅ¡ke nisu pronadene","noSuggestions":"- Bez sugestija -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Nije u reÄniku","oneChange":"Provera spelovanja zavrÅ¡ena: Izmenjena je jedna reÄ","progress":"Provera spelovanja u toku...","title":"Spell Checker","toolbar":"Proveri spelovanje"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sr.js b/js/ckeditor/lang/sr.js
new file mode 100644
index 0000000..1b4b43e
--- /dev/null
+++ b/js/ckeditor/lang/sr.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sr']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Press ALT 0 for help","browseServer":"Претражи Ñервер","url":"УРЛ","protocol":"Протокол","upload":"Пошаљи","uploadSubmit":"Пошаљи на Ñервер","image":"Слика","flash":"Флеш елемент","form":"Форма","checkbox":"Поље за потврду","radio":"Радио-дугме","textField":"ТекÑтуално поље","textarea":"Зона текÑта","hiddenField":"Скривено поље","button":"Дугме","select":"Изборно поље","imageButton":"Дугме Ñа Ñликом","notSet":"<није поÑтављено>","id":"Ид","name":"Ðазив","langDir":"Смер језика","langDirLtr":"С лева на деÑно (LTR)","langDirRtl":"С деÑна на лево (RTL)","langCode":"Kôд језика","longDescr":"Пун Ð¾Ð¿Ð¸Ñ Ð£Ð Ð›","cssClass":"Stylesheet клаÑе","advisoryTitle":"Advisory наÑлов","cssStyle":"Стил","ok":"OK","cancel":"Oткажи","close":"Затвори","preview":"Изглед Ñтранице","resize":"Resize","generalTab":"Опште","advancedTab":"Ðапредни тагови","validateNumberFailed":"Ова вредноÑÑ‚ није цигра.","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Опције","target":"MeÑ‚a","targetNew":"New Window (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"Same Window (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"С лева на деÑно (LTR)","langDirRTL":"С деÑна на лево (RTL)","styles":"Стил","cssClasses":"Stylesheet клаÑе","width":"Ширина","height":"ВиÑина","align":"Равнање","alignLeft":"Лево","alignRight":"ДеÑно","alignCenter":"Средина","alignJustify":"ОбоÑтрано равнање","alignTop":"Врх","alignMiddle":"Средина","alignBottom":"Доле","alignNone":"None","invalidValue":"Invalid value.","invalidHeight":"Height must be a number.","invalidWidth":"Width must be a number.","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Подебљано","italic":"Курзив","strike":"Прецртано","subscript":"ИндекÑ","superscript":"Степен","underline":"Подвучено"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"Копирај","copyError":"СигурноÑна подешавања Вашег претраживача не дозвољавају операције аутоматÑког копирања текÑта. Молимо Ð’Ð°Ñ Ð´Ð° кориÑтите пречицу Ñа таÑтатуре (Ctrl/Cmd+C).","cut":"ИÑеци","cutError":"СигурноÑна подешавања Вашег претраживача не дозвољавају операције аутоматÑког иÑецања текÑта. Молимо Ð’Ð°Ñ Ð´Ð° кориÑтите пречицу Ñа таÑтатуре (Ctrl/Cmd+X).","paste":"Залепи","pasteArea":"Залепи зону","pasteMsg":"Молимо Ð’Ð°Ñ Ð´Ð° залепите унутар доње површине кориÑтећи таÑтатурну пречицу (<STRONG>Ctrl/Cmd+V</STRONG>) и да притиÑнете <STRONG>OK</STRONG>.","securityMsg":"Због ÑигурноÑних подешавања претраживача, едитор не може да приÑтупи оÑтаву. Требате да га поново залепите у овом прозору.","title":"Залепи"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"Склопи алатну траку","toolbarExpand":"Прошири алатну траку","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Едитор алатне траке"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"Формат","panelTitle":"Формат","tag_address":"Adresa","tag_div":"Ðормално (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatirano"},"horizontalrule":{"toolbar":"УнеÑи хоризонталну линију"},"image":{"alertUrl":"УнеÑите УРЛ Ñлике","alt":"Ðлтернативни текÑÑ‚","border":"Оквир","btnUpload":"Пошаљи на Ñервер","button2Img":"Да ли желите да промените одабрану Ñлику дугмета као једноÑтавну Ñлику?","hSpace":"HSpace","img2Button":"Да ли желите да промените одабрану Ñлику у Ñлику дугмета?","infoTab":"Инфо Ñлике","linkTab":"Линк","lockRatio":"Закључај одноÑ","menu":"ОÑобине Ñлика","resetSize":"РеÑетуј величину","title":"ОÑобине Ñлика","titleButton":"ОÑобине дугмета Ñа Ñликом","upload":"Пошаљи","urlMissing":"ÐедоÑтаје УРЛ Ñлике.","vSpace":"VSpace","validateBorder":"Ивица треба да буде цифра.","validateHSpace":"HSpace треба да буде цифра.","validateVSpace":"VSpace треба да буде цифра."},"indent":{"indent":"Увећај леву маргину","outdent":"Смањи леву маргину"},"fakeobjects":{"anchor":"Anchor","flash":"Flash Animation","hiddenfield":"Hidden Field","iframe":"IFrame","unknown":"Unknown Object"},"link":{"acccessKey":"ПриÑтупни таÑтер","advanced":"Ðапредни тагови","advisoryContentType":"Advisory врÑта Ñадржаја","advisoryTitle":"Advisory наÑлов","anchor":{"toolbar":"УнеÑи/измени Ñидро","menu":"ОÑобине Ñидра","title":"ОÑобине Ñидра","name":"Име Ñидра","errorName":"Молимо Ð’Ð°Ñ Ð´Ð° унеÑете име Ñидра","remove":"Remove Anchor"},"anchorId":"Пo Ид-jу елемента","anchorName":"По називу Ñидра","charset":"Linked Resource Charset","cssClasses":"Stylesheet клаÑе","emailAddress":"ÐдреÑа електронÑке поште","emailBody":"Садржај поруке","emailSubject":"ÐаÑлов","id":"Ид","info":"Линк инфо","langCode":"Смер језика","langDir":"Смер језика","langDirLTR":"С лева на деÑно (LTR)","langDirRTL":"С деÑна на лево (RTL)","menu":"Промени линк","name":"Ðазив","noAnchors":"(Ðема доÑтупних Ñидра)","noEmail":"Откуцајте адреÑу електронÑке поште","noUrl":"УнеÑите УРЛ линка","other":"<друго>","popupDependent":"ЗавиÑно (Netscape)","popupFeatures":"МогућноÑти иÑкачућег прозора","popupFullScreen":"Приказ преко целог екрана (ИE)","popupLeft":"Од леве ивице екрана (пикÑела)","popupLocationBar":"Локација","popupMenuBar":"КонтекÑтни мени","popupResizable":"Величина Ñе мења","popupScrollBars":"Скрол бар","popupStatusBar":"СтатуÑна линија","popupToolbar":"Toolbar","popupTop":"Од врха екрана (пикÑела)","rel":"ОдноÑ","selectAnchor":"Одабери Ñидро","styles":"Стил","tabIndex":"Таб индекÑ","target":"MeÑ‚a","targetFrame":"<оквир>","targetFrameName":"Ðазив одредишног фрејма","targetPopup":"<иÑкачући прозор>","targetPopupName":"Ðазив иÑкачућег прозора","title":"Линк","toAnchor":"Сидро на овој Ñтраници","toEmail":"EлектронÑка пошта","toUrl":"УРЛ","toolbar":"УнеÑи/измени линк","type":"Ð’Ñ€Ñта линка","unlink":"Уклони линк","upload":"Пошаљи"},"list":{"bulletedlist":"Ðенабројива лиÑта","numberedlist":"Ðабројиву лиÑту"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"Maximize","minimize":"Minimize"},"pastetext":{"button":"Залепи као чиÑÑ‚ текÑÑ‚","title":"Залепи као чиÑÑ‚ текÑÑ‚"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Залепи из Worda","toolbar":"Залепи из Worda"},"removeformat":{"toolbar":"Уклони форматирање"},"sourcearea":{"toolbar":"Kôд"},"specialchar":{"options":"Опције Ñпецијалног карактера","title":"Одаберите Ñпецијални карактер","toolbar":"УнеÑи Ñпецијални карактер"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Стил","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"Величина оквира","caption":"ÐаÑлов табеле","cell":{"menu":"Cell","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"Обриши ћелије","merge":"Спој ћелије","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"Размак ћелија","cellSpace":"ЋелијÑки проÑтор","column":{"menu":"Column","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Обриши колоне"},"columns":"Kолона","deleteTable":"Обриши таблу","headers":"Поглавља","headersBoth":"Оба","headersColumn":"Прва колона","headersNone":"None","headersRow":"Први ред","invalidBorder":"Величина ивице треба да буде цифра.","invalidCellPadding":"Пуњење ћелије треба да буде позитивна цифра.","invalidCellSpacing":"Размак ћелије треба да буде позитивна цифра.","invalidCols":"Број колона треба да буде цифра већа од 0.","invalidHeight":"ВиÑина табеле треба да буде цифра.","invalidRows":"Број реда треба да буде цифра већа од 0.","invalidWidth":"Ширина табеле треба да буде цифра.","menu":"ОÑобине табеле","row":{"menu":"Row","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Обриши редове"},"rows":"Редова","summary":"Резиме","title":"ОÑобине табеле","toolbar":"Табела","widthPc":"процената","widthPx":"пикÑела","widthUnit":"јединица ширине"},"undo":{"redo":"Понови акцију","undo":"Поништи акцију"},"wsc":{"btnIgnore":"Игнориши","btnIgnoreAll":"Игнориши Ñве","btnReplace":"Замени","btnReplaceAll":"Замени Ñве","btnUndo":"Врати акцију","changeTo":"Измени","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Провера Ñпеловања није инÑталирана. Да ли желите да је Ñкинете Ñа Интернета?","manyChanges":"Провера Ñпеловања завршена: %1 реч(и) је измењено","noChanges":"Провера Ñпеловања завршена: Ðије измењена ниједна реч","noMispell":"Провера Ñпеловања завршена: грешке ниÑу пронађене","noSuggestions":"- Без ÑугеÑтија -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Ðије у речнику","oneChange":"Провера Ñпеловања завршена: Измењена је једна реч","progress":"Провера Ñпеловања у току...","title":"Spell Checker","toolbar":"Провери Ñпеловање"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/sv.js b/js/ckeditor/lang/sv.js
new file mode 100644
index 0000000..8c3c831
--- /dev/null
+++ b/js/ckeditor/lang/sv.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['sv']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Tryck ALT 0 för hjälp","browseServer":"Bläddra på server","url":"URL","protocol":"Protokoll","upload":"Ladda upp","uploadSubmit":"Skicka till server","image":"Bild","flash":"Flash","form":"Formulär","checkbox":"Kryssruta","radio":"Alternativknapp","textField":"Textfält","textarea":"Textruta","hiddenField":"Dolt fält","button":"Knapp","select":"Flervalslista","imageButton":"Bildknapp","notSet":"<ej angivet>","id":"Id","name":"Namn","langDir":"Språkriktning","langDirLtr":"Vänster till Höger (VTH)","langDirRtl":"Höger till Vänster (HTV)","langCode":"Språkkod","longDescr":"URL-beskrivning","cssClass":"Stilmall","advisoryTitle":"Titel","cssStyle":"Stilmall","ok":"OK","cancel":"Avbryt","close":"Stäng","preview":"Förhandsgranska","resize":"Dra för att ändra storlek","generalTab":"Allmänt","advancedTab":"Avancerad","validateNumberFailed":"Värdet är inte ett nummer.","confirmNewPage":"Alla ändringar i innehållet kommer att förloras. Är du säker på att du vill ladda en ny sida?","confirmCancel":"Några av alternativen har ändrats. Är du säker på att du vill stänga dialogrutan?","options":"Alternativ","target":"Mål","targetNew":"Nytt fönster (_blank)","targetTop":"Översta fönstret (_top)","targetSelf":"Samma fönster (_self)","targetParent":"Föregående fönster (_parent)","langDirLTR":"Vänster till höger (LTR)","langDirRTL":"Höger till vänster (RTL)","styles":"Stil","cssClasses":"Stilmallar","width":"Bredd","height":"Höjd","align":"Justering","alignLeft":"Vänster","alignRight":"Höger","alignCenter":"Centrerad","alignJustify":"Justera till marginaler","alignTop":"Överkant","alignMiddle":"Mitten","alignBottom":"Nederkant","alignNone":"Ingen","invalidValue":"Felaktigt värde.","invalidHeight":"Höjd måste vara ett nummer.","invalidWidth":"Bredd måste vara ett nummer.","invalidCssLength":"Värdet för fältet \"%1\" måste vara ett positivt nummer med eller utan CSS-mätenheter (px, %, in, cm, mm, em, ex, pt, eller pc).","invalidHtmlLength":"Värdet för fältet \"%1\" måste vara ett positivt nummer med eller utan godkända HTML-mätenheter (px eller %).","invalidInlineStyle":"Det angivna värdet för style måste innehålla en eller flera tupler separerade med semikolon i följande format: \"name : value\"","cssLengthTooltip":"Ange ett nummer i pixlar eller ett nummer men godkänd CSS-mätenhet (px, %, in, cm, mm, em, ex, pt, eller pc).","unavailable":"%1<span class=\"cke_accessibility\">, Ej tillgänglig</span>"},"about":{"copy":"Copyright &copy; $1. Alla rättigheter reserverade.","dlgTitle":"Om CKEditor","help":"Se $1 för hjälp.","moreInfo":"För information av licensiering besök vår hemsida:","title":"Om CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"Fet","italic":"Kursiv","strike":"Genomstruken","subscript":"Nedsänkta tecken","superscript":"Upphöjda tecken","underline":"Understruken"},"blockquote":{"toolbar":"Blockcitat"},"clipboard":{"copy":"Kopiera","copyError":"Säkerhetsinställningar i Er webbläsare tillåter inte åtgärden kopiera. Använd (Ctrl/Cmd+C) istället.","cut":"Klipp ut","cutError":"Säkerhetsinställningar i Er webbläsare tillåter inte åtgärden klipp ut. Använd (Ctrl/Cmd+X) istället.","paste":"Klistra in","pasteArea":"Paste Area","pasteMsg":"Var god och klistra in Er text i rutan nedan genom att använda (<strong>Ctrl/Cmd+V</strong>) klicka sen på OK.","securityMsg":"På grund av din webbläsares säkerhetsinställningar kan verktyget inte få åtkomst till urklippsdatan. Var god och använd detta fönster istället.","title":"Klistra in"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Vald)"},"toolbar":{"toolbarCollapse":"Dölj verktygsfält","toolbarExpand":"Visa verktygsfält","toolbarGroups":{"document":"Dokument","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"Redigera verktygsfält"},"elementspath":{"eleLabel":"Elementets sökväg","eleTitle":"%1 element"},"format":{"label":"Teckenformat","panelTitle":"Teckenformat","tag_address":"Adress","tag_div":"Normal (DIV)","tag_h1":"Rubrik 1","tag_h2":"Rubrik 2","tag_h3":"Rubrik 3","tag_h4":"Rubrik 4","tag_h5":"Rubrik 5","tag_h6":"Rubrik 6","tag_p":"Normal","tag_pre":"Formaterad"},"horizontalrule":{"toolbar":"Infoga horisontal linje"},"image":{"alertUrl":"Var god och ange bildens URL","alt":"Alternativ text","border":"Kant","btnUpload":"Skicka till server","button2Img":"Vill du omvandla den valda bildknappen på en enkel bild?","hSpace":"Horis. marginal","img2Button":"Vill du omvandla den valda bildknappen på en enkel bild?","infoTab":"Bildinformation","linkTab":"Länk","lockRatio":"Lås höjd/bredd förhållanden","menu":"Bildegenskaper","resetSize":"Återställ storlek","title":"Bildegenskaper","titleButton":"Egenskaper för bildknapp","upload":"Ladda upp","urlMissing":"Bildkällans URL saknas.","vSpace":"Vert. marginal","validateBorder":"Kantlinje måste vara ett heltal.","validateHSpace":"HSpace måste vara ett heltal.","validateVSpace":"VSpace måste vara ett heltal."},"indent":{"indent":"Öka indrag","outdent":"Minska indrag"},"fakeobjects":{"anchor":"Ankare","flash":"Flashanimation","hiddenfield":"Gömt fält","iframe":"iFrame","unknown":"Okänt objekt"},"link":{"acccessKey":"Behörighetsnyckel","advanced":"Avancerad","advisoryContentType":"Innehållstyp","advisoryTitle":"Titel","anchor":{"toolbar":"Infoga/Redigera ankarlänk","menu":"Egenskaper för ankarlänk","title":"Egenskaper för ankarlänk","name":"Ankarnamn","errorName":"Var god ange ett ankarnamn","remove":"Radera ankare"},"anchorId":"Efter element-id","anchorName":"Efter ankarnamn","charset":"Teckenuppställning","cssClasses":"Stilmall","emailAddress":"E-postadress","emailBody":"Innehåll","emailSubject":"Ämne","id":"Id","info":"Länkinformation","langCode":"Språkkod","langDir":"Språkriktning","langDirLTR":"Vänster till höger (VTH)","langDirRTL":"Höger till vänster (HTV)","menu":"Redigera länk","name":"Namn","noAnchors":"(Inga ankare kunde hittas)","noEmail":"Var god ange e-postadress","noUrl":"Var god ange länkens URL","other":"<annan>","popupDependent":"Beroende (endast Netscape)","popupFeatures":"Popup-fönstrets egenskaper","popupFullScreen":"Helskärm (endast IE)","popupLeft":"Position från vänster","popupLocationBar":"Adressfält","popupMenuBar":"Menyfält","popupResizable":"Resizable","popupScrollBars":"Scrolllista","popupStatusBar":"Statusfält","popupToolbar":"Verktygsfält","popupTop":"Position från sidans topp","rel":"Förhållande","selectAnchor":"Välj ett ankare","styles":"Stilmall","tabIndex":"Tabindex","target":"Mål","targetFrame":"<ram>","targetFrameName":"Målets ramnamn","targetPopup":"<popup-fönster>","targetPopupName":"Popup-fönstrets namn","title":"Länk","toAnchor":"Länk till ankare i texten","toEmail":"E-post","toUrl":"URL","toolbar":"Infoga/Redigera länk","type":"Länktyp","unlink":"Radera länk","upload":"Ladda upp"},"list":{"bulletedlist":"Punktlista","numberedlist":"Numrerad lista"},"magicline":{"title":"Infoga paragraf här"},"maximize":{"maximize":"Maximera","minimize":"Minimera"},"pastetext":{"button":"Klistra in som vanlig text","title":"Klistra in som vanlig text"},"pastefromword":{"confirmCleanup":"Texten du vill klistra in verkar vara kopierad från Word. Vill du rensa den innan du klistrar in den?","error":"Det var inte möjligt att städa upp den inklistrade data på grund av ett internt fel","title":"Klistra in från Word","toolbar":"Klistra in från Word"},"removeformat":{"toolbar":"Radera formatering"},"sourcearea":{"toolbar":"Källa"},"specialchar":{"options":"Alternativ för utökade tecken","title":"Välj utökat tecken","toolbar":"Klistra in utökat tecken"},"scayt":{"btn_about":"Om SCAYT","btn_dictionaries":"Ordlistor","btn_disable":"Inaktivera SCAYT","btn_enable":"Aktivera SCAYT","btn_langs":"Språk","btn_options":"Inställningar","text_title":"Stavningskontroll medan du skriver"},"stylescombo":{"label":"Anpassad stil","panelTitle":"Formatmallar","panelTitle1":"Blockstil","panelTitle2":"Inbäddad stil","panelTitle3":"Objektets stil"},"table":{"border":"Kantstorlek","caption":"Rubrik","cell":{"menu":"Cell","insertBefore":"Lägg till cell före","insertAfter":"Lägg till cell efter","deleteCell":"Radera celler","merge":"Sammanfoga celler","mergeRight":"Sammanfoga höger","mergeDown":"Sammanfoga ner","splitHorizontal":"Dela cell horisontellt","splitVertical":"Dela cell vertikalt","title":"Egenskaper för cell","cellType":"Celltyp","rowSpan":"Rad spann","colSpan":"Kolumnen spann","wordWrap":"Radbrytning","hAlign":"Horisontell justering","vAlign":"Vertikal justering","alignBaseline":"Baslinje","bgColor":"Bakgrundsfärg","borderColor":"Ramfärg","data":"Data","header":"Rubrik","yes":"Ja","no":"Nej","invalidWidth":"Cellens bredd måste vara ett nummer.","invalidHeight":"Cellens höjd måste vara ett nummer.","invalidRowSpan":"Radutvidgning måste vara ett heltal.","invalidColSpan":"Kolumn måste vara ett heltal.","chooseColor":"Välj"},"cellPad":"Cellutfyllnad","cellSpace":"Cellavstånd","column":{"menu":"Kolumn","insertBefore":"Lägg till kolumn före","insertAfter":"Lägg till kolumn efter","deleteColumn":"Radera kolumn"},"columns":"Kolumner","deleteTable":"Radera tabell","headers":"Rubriker","headersBoth":"Båda","headersColumn":"Första kolumnen","headersNone":"Ingen","headersRow":"Första raden","invalidBorder":"Ram måste vara ett nummer.","invalidCellPadding":"Luft i cell måste vara ett nummer.","invalidCellSpacing":"Luft i cell måste vara ett nummer.","invalidCols":"Antal kolumner måste vara ett nummer större än 0.","invalidHeight":"Tabellens höjd måste vara ett nummer.","invalidRows":"Antal rader måste vara större än 0.","invalidWidth":"Tabell måste vara ett nummer.","menu":"Tabellegenskaper","row":{"menu":"Rad","insertBefore":"Lägg till rad före","insertAfter":"Lägg till rad efter","deleteRow":"Radera rad"},"rows":"Rader","summary":"Sammanfattning","title":"Tabellegenskaper","toolbar":"Tabell","widthPc":"procent","widthPx":"pixlar","widthUnit":"enhet bredd"},"undo":{"redo":"Gör om","undo":"Ångra"},"wsc":{"btnIgnore":"Ignorera","btnIgnoreAll":"Ignorera alla","btnReplace":"Ersätt","btnReplaceAll":"Ersätt alla","btnUndo":"Ångra","changeTo":"Ändra till","errorLoading":"Tjänsten är ej tillgänglig: %s.","ieSpellDownload":"Stavningskontrollen är ej installerad. Vill du göra det nu?","manyChanges":"Stavningskontroll slutförd: %1 ord rättades.","noChanges":"Stavningskontroll slutförd: Inga ord rättades.","noMispell":"Stavningskontroll slutförd: Inga stavfel påträffades.","noSuggestions":"- Förslag saknas -","notAvailable":"Tyvärr är tjänsten ej tillgänglig nu","notInDic":"Saknas i ordlistan","oneChange":"Stavningskontroll slutförd: Ett ord rättades.","progress":"Stavningskontroll pågår...","title":"Kontrollera stavning","toolbar":"Stavningskontroll"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/th.js b/js/ckeditor/lang/th.js
new file mode 100644
index 0000000..09e7e8d
--- /dev/null
+++ b/js/ckeditor/lang/th.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['th']={"editor":"Rich Text Editor","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"à¸à¸” ALT 0 หาà¸à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸„วามช่วยเหลือ","browseServer":"เปิดหน้าต่างจัดà¸à¸²à¸£à¹„ฟล์อัพโหลด","url":"ที่อยู่อ้างอิง URL","protocol":"โปรโตคอล","upload":"อัพโหลดไฟล์","uploadSubmit":"อัพโหลดไฟล์ไปเà¸à¹‡à¸šà¹„ว้ที่เครื่องà¹à¸¡à¹ˆà¸‚่าย (เซิร์ฟเวอร์)","image":"รูปภาพ","flash":"ไฟล์ Flash","form":"à¹à¸šà¸šà¸Ÿà¸­à¸£à¹Œà¸¡","checkbox":"เช็คบ๊อà¸","radio":"เรดิโอบัตตอน","textField":"เท็à¸à¸‹à¹Œà¸Ÿà¸´à¸¥à¸”์","textarea":"เท็à¸à¸‹à¹Œà¹à¸­à¹€à¸£à¸µà¸¢","hiddenField":"ฮิดเดนฟิลด์","button":"ปุ่ม","select":"à¹à¸–บตัวเลือà¸","imageButton":"ปุ่มà¹à¸šà¸šà¸£à¸¹à¸›à¸ à¸²à¸ž","notSet":"<ไม่ระบุ>","id":"ไอดี","name":"ชื่อ","langDir":"à¸à¸²à¸£à¹€à¸‚ียน-อ่านภาษา","langDirLtr":"จาà¸à¸‹à¹‰à¸²à¸¢à¹„ปขวา (LTR)","langDirRtl":"จาà¸à¸‚วามาซ้าย (RTL)","langCode":"รหัสภาษา","longDescr":"คำอธิบายประà¸à¸­à¸š URL","cssClass":"คลาสของไฟล์à¸à¸³à¸«à¸™à¸”ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","advisoryTitle":"คำเà¸à¸£à¸´à¹ˆà¸™à¸™à¸³","cssStyle":"ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","ok":"ตà¸à¸¥à¸‡","cancel":"ยà¸à¹€à¸¥à¸´à¸","close":"ปิด","preview":"ดูหน้าเอà¸à¸ªà¸²à¸£à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡","resize":"ปรับขนาด","generalTab":"ทั่วไป","advancedTab":"ขั้นสูง","validateNumberFailed":"ค่านี้ไม่ใช่ตัวเลข","confirmNewPage":"à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¹ƒà¸”ๆ ในเนื้อหานี้ ที่ไม่ได้ถูà¸à¸šà¸±à¸™à¸—ึà¸à¹„ว้ จะสูà¸à¸«à¸²à¸¢à¸—ั้งหมด คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¸ˆà¸°à¹€à¸£à¸µà¸¢à¸à¸«à¸™à¹‰à¸²à¹ƒà¸«à¸¡à¹ˆ?","confirmCancel":"ตัวเลือà¸à¸šà¸²à¸‡à¸•à¸±à¸§à¸¡à¸µà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¸ˆà¸°à¸›à¸´à¸”à¸à¸¥à¹ˆà¸­à¸‡à¹‚ต้ตอบนี้?","options":"ตัวเลือà¸","target":"à¸à¸²à¸£à¹€à¸›à¸´à¸”หน้าลิงค์","targetNew":"หน้าต่างใหม่ (_blank)","targetTop":"Topmost Window (_top)","targetSelf":"หน้าต่างเดียวà¸à¸±à¸™ (_self)","targetParent":"Parent Window (_parent)","langDirLTR":"จาà¸à¸‹à¹‰à¸²à¸¢à¹„ปขวา (LTR)","langDirRTL":"จาà¸à¸‚วามาซ้าย (RTL)","styles":"ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","cssClasses":"คลาสของไฟล์à¸à¸³à¸«à¸™à¸”ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","width":"ความà¸à¸§à¹‰à¸²à¸‡","height":"ความสูง","align":"à¸à¸²à¸£à¸ˆà¸±à¸”วาง","alignLeft":"ชิดซ้าย","alignRight":"ชิดขวา","alignCenter":"à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡","alignJustify":"நியாயபà¯à®ªà®Ÿà¯à®¤à¯à®¤à®µà¯à®®à¯","alignTop":"บนสุด","alignMiddle":"à¸à¸¶à¹ˆà¸‡à¸à¸¥à¸²à¸‡à¹à¸™à¸§à¸•à¸±à¹‰à¸‡","alignBottom":"ชิดด้านล่าง","alignNone":"None","invalidValue":"ค่าไม่ถูà¸à¸•à¹‰à¸­à¸‡","invalidHeight":"ความสูงต้องเป็นตัวเลข","invalidWidth":"ความà¸à¸§à¹‰à¸²à¸‡à¸•à¹‰à¸­à¸‡à¹€à¸›à¹‡à¸™à¸•à¸±à¸§à¹€à¸¥à¸‚","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"About CKEditor","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"About CKEditor","userGuide":"CKEditor User's Guide"},"basicstyles":{"bold":"ตัวหนา","italic":"ตัวเอียง","strike":"ตัวขีดเส้นทับ","subscript":"ตัวห้อย","superscript":"ตัวยà¸","underline":"ตัวขีดเส้นใต้"},"blockquote":{"toolbar":"Block Quote"},"clipboard":{"copy":"สำเนา","copyError":"ไม่สามารถสำเนาข้อความที่เลือà¸à¹„ว้ได้เนื่องจาà¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าระดับความปลอดภัย. à¸à¸£à¸¸à¸“าใช้ปุ่มลัดเพื่อวางข้อความà¹à¸—น (à¸à¸”ปุ่ม Ctrl/Cmd à¹à¸¥à¸°à¸•à¸±à¸§ C พร้อมà¸à¸±à¸™).","cut":"ตัด","cutError":"ไม่สามารถตัดข้อความที่เลือà¸à¹„ว้ได้เนื่องจาà¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าระดับความปลอดภัย. à¸à¸£à¸¸à¸“าใช้ปุ่มลัดเพื่อวางข้อความà¹à¸—น (à¸à¸”ปุ่ม Ctrl/Cmd à¹à¸¥à¸°à¸•à¸±à¸§ X พร้อมà¸à¸±à¸™).","paste":"วาง","pasteArea":"Paste Area","pasteMsg":"à¸à¸£à¸¸à¸“าใช้คีย์บอร์ดเท่านั้น โดยà¸à¸”ปุ๋ม (<strong>Ctrl/Cmd à¹à¸¥à¸° V</strong>)พร้อมๆà¸à¸±à¸™ à¹à¸¥à¸°à¸à¸” <strong>OK</strong>.","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"วาง"},"contextmenu":{"options":"Context Menu Options"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"ซ่อนà¹à¸–บเครื่องมือ","toolbarExpand":"เปิดà¹à¸–บเครื่องมือ","toolbarGroups":{"document":"Document","clipboard":"Clipboard/Undo","editing":"Editing","forms":"Forms","basicstyles":"Basic Styles","paragraph":"Paragraph","links":"Links","insert":"Insert","styles":"Styles","colors":"Colors","tools":"Tools"},"toolbars":"à¹à¸–บเครื่องมือช่วยพิมพ์ข้อความ"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 element"},"format":{"label":"รูปà¹à¸šà¸š","panelTitle":"รูปà¹à¸šà¸š","tag_address":"Address","tag_div":"Paragraph (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Normal","tag_pre":"Formatted"},"horizontalrule":{"toolbar":"à¹à¸—รà¸à¹€à¸ªà¹‰à¸™à¸„ั่นบรรทัด"},"image":{"alertUrl":"à¸à¸£à¸¸à¸“าระบุที่อยู่อ้างอิงออนไลน์ของไฟล์รูปภาพ (URL)","alt":"คำประà¸à¸­à¸šà¸£à¸¹à¸›à¸ à¸²à¸ž","border":"ขนาดขอบรูป","btnUpload":"อัพโหลดไฟล์ไปเà¸à¹‡à¸šà¹„ว้ที่เครื่องà¹à¸¡à¹ˆà¸‚่าย (เซิร์ฟเวอร์)","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"ระยะà¹à¸™à¸§à¸™à¸­à¸™","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"ข้อมูลของรูปภาพ","linkTab":"ลิ้งค์","lockRatio":"à¸à¸³à¸«à¸™à¸”อัตราส่วน à¸à¸§à¹‰à¸²à¸‡-สูง à¹à¸šà¸šà¸„งที่","menu":"คุณสมบัติของ รูปภาพ","resetSize":"à¸à¸³à¸«à¸™à¸”รูปเท่าขนาดจริง","title":"คุณสมบัติของ รูปภาพ","titleButton":"คุณสมบัติของ ปุ่มà¹à¸šà¸šà¸£à¸¹à¸›à¸ à¸²à¸ž","upload":"อัพโหลดไฟล์","urlMissing":"Image source URL is missing.","vSpace":"ระยะà¹à¸™à¸§à¸•à¸±à¹‰à¸‡","validateBorder":"Border must be a whole number.","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"เพิ่มระยะย่อหน้า","outdent":"ลดระยะย่อหน้า"},"fakeobjects":{"anchor":"à¹à¸—รà¸/à¹à¸à¹‰à¹„ข Anchor","flash":"ภาพอนิเมชั่นà¹à¸Ÿà¸¥à¸Š","hiddenfield":"ฮิดเดนฟิลด์","iframe":"IFrame","unknown":"วัตถุไม่ทราบชนิด"},"link":{"acccessKey":"à¹à¸­à¸„เซส คีย์","advanced":"ขั้นสูง","advisoryContentType":"ชนิดของคำเà¸à¸£à¸´à¹ˆà¸™à¸™à¸³","advisoryTitle":"คำเà¸à¸£à¸´à¹ˆà¸™à¸™à¸³","anchor":{"toolbar":"à¹à¸—รà¸/à¹à¸à¹‰à¹„ข Anchor","menu":"รายละเอียด Anchor","title":"รายละเอียด Anchor","name":"ชื่อ Anchor","errorName":"à¸à¸£à¸¸à¸“าระบุชื่อของ Anchor","remove":"Remove Anchor"},"anchorId":"ไอดี","anchorName":"ชื่อ","charset":"ลิงค์เชื่อมโยงไปยังชุดตัวอัà¸à¸©à¸£","cssClasses":"คลาสของไฟล์à¸à¸³à¸«à¸™à¸”ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","emailAddress":"อีเมล์ (E-Mail)","emailBody":"ข้อความ","emailSubject":"หัวเรื่อง","id":"ไอดี","info":"รายละเอียด","langCode":"à¸à¸²à¸£à¹€à¸‚ียน-อ่านภาษา","langDir":"à¸à¸²à¸£à¹€à¸‚ียน-อ่านภาษา","langDirLTR":"จาà¸à¸‹à¹‰à¸²à¸¢à¹„ปขวา (LTR)","langDirRTL":"จาà¸à¸‚วามาซ้าย (RTL)","menu":"à¹à¸à¹‰à¹„ข ลิงค์","name":"ชื่อ","noAnchors":"(ยังไม่มีจุดเชื่อมโยงภายในหน้าเอà¸à¸ªà¸²à¸£à¸™à¸µà¹‰)","noEmail":"à¸à¸£à¸¸à¸“าระบุอีเมล์ (E-mail)","noUrl":"à¸à¸£à¸¸à¸“าระบุที่อยู่อ้างอิงออนไลน์ (URL)","other":"<อื่น ๆ>","popupDependent":"à¹à¸ªà¸”งเต็มหน้าจอ (Netscape)","popupFeatures":"คุณสมบัติของหน้าจอเล็ภ(Pop-up)","popupFullScreen":"à¹à¸ªà¸”งเต็มหน้าจอ (IE5.5++ เท่านั้น)","popupLeft":"พิà¸à¸±à¸”ซ้าย (Left Position)","popupLocationBar":"à¹à¸ªà¸”งที่อยู่ของไฟล์","popupMenuBar":"à¹à¸ªà¸”งà¹à¸–บเมนู","popupResizable":"สามารถปรับขนาดได้","popupScrollBars":"à¹à¸ªà¸”งà¹à¸–บเลื่อน","popupStatusBar":"à¹à¸ªà¸”งà¹à¸–บสถานะ","popupToolbar":"à¹à¸ªà¸”งà¹à¸–บเครื่องมือ","popupTop":"พิà¸à¸±à¸”บน (Top Position)","rel":"ความสัมพันธ์","selectAnchor":"ระบุข้อมูลของจุดเชื่อมโยง (Anchor)","styles":"ลัà¸à¸©à¸“ะà¸à¸²à¸£à¹à¸ªà¸”งผล","tabIndex":"ลำดับของ à¹à¸—็บ","target":"à¸à¸²à¸£à¹€à¸›à¸´à¸”หน้าลิงค์","targetFrame":"<เปิดในเฟรม>","targetFrameName":"ชื่อทาร์เà¸à¹‡à¸•à¹€à¸Ÿà¸£à¸¡","targetPopup":"<เปิดหน้าจอเล็ภ(Pop-up)>","targetPopupName":"ระบุชื่อหน้าจอเล็ภ(Pop-up)","title":"ลิงค์เชื่อมโยงเว็บ อีเมล์ รูปภาพ หรือไฟล์อื่นๆ","toAnchor":"จุดเชื่อมโยง (Anchor)","toEmail":"ส่งอีเมล์ (E-Mail)","toUrl":"ที่อยู่อ้างอิง URL","toolbar":"à¹à¸—รà¸/à¹à¸à¹‰à¹„ข ลิงค์","type":"ประเภทของลิงค์","unlink":"ลบ ลิงค์","upload":"อัพโหลดไฟล์"},"list":{"bulletedlist":"ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸ªà¸±à¸à¸¥à¸±à¸à¸©à¸“์","numberedlist":"ลำดับรายà¸à¸²à¸£à¹à¸šà¸šà¸•à¸±à¸§à¹€à¸¥à¸‚"},"magicline":{"title":"Insert paragraph here"},"maximize":{"maximize":"ขยายใหà¸à¹ˆ","minimize":"ย่อขนาด"},"pastetext":{"button":"วางà¹à¸šà¸šà¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸˜à¸£à¸£à¸¡à¸”า","title":"วางà¹à¸šà¸šà¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸˜à¸£à¸£à¸¡à¸”า"},"pastefromword":{"confirmCleanup":"ข้อความที่คุณต้องà¸à¸²à¸£à¸§à¸²à¸‡à¸¥à¸‡à¹„ปเป็นข้อความที่คัดลอà¸à¸¡à¸²à¸ˆà¸²à¸à¹‚ปรà¹à¸à¸£à¸¡à¹„มโครซอฟท์เวิร์ด คุณต้องà¸à¸²à¸£à¸¥à¹‰à¸²à¸‡à¸„่าข้อความดังà¸à¸¥à¹ˆà¸²à¸§à¸à¹ˆà¸­à¸™à¸§à¸²à¸‡à¸¥à¸‡à¹„ปหรือไม่?","error":"ไม่สามารถล้างข้อมูลที่ต้องà¸à¸²à¸£à¸§à¸²à¸‡à¹„ด้เนื่องจาà¸à¹€à¸à¸´à¸”ข้อผิดพลาดภายในระบบ","title":"วางสำเนาจาà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¹€à¸§à¸´à¸£à¹Œà¸”","toolbar":"วางสำเนาจาà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¹€à¸§à¸´à¸£à¹Œà¸”"},"removeformat":{"toolbar":"ล้างรูปà¹à¸šà¸š"},"sourcearea":{"toolbar":"ดูรหัส HTML"},"specialchar":{"options":"Special Character Options","title":"à¹à¸—รà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸žà¸´à¹€à¸¨à¸©","toolbar":"à¹à¸—รà¸à¸•à¸±à¸§à¸­à¸±à¸à¸©à¸£à¸žà¸´à¹€à¸¨à¸©"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"ลัà¸à¸©à¸“ะ","panelTitle":"Formatting Styles","panelTitle1":"Block Styles","panelTitle2":"Inline Styles","panelTitle3":"Object Styles"},"table":{"border":"ขนาดเส้นขอบ","caption":"หัวเรื่องของตาราง","cell":{"menu":"ช่องตาราง","insertBefore":"Insert Cell Before","insertAfter":"Insert Cell After","deleteCell":"ลบช่อง","merge":"ผสานช่อง","mergeRight":"Merge Right","mergeDown":"Merge Down","splitHorizontal":"Split Cell Horizontally","splitVertical":"Split Cell Vertically","title":"Cell Properties","cellType":"Cell Type","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"Word Wrap","hAlign":"Horizontal Alignment","vAlign":"Vertical Alignment","alignBaseline":"Baseline","bgColor":"Background Color","borderColor":"Border Color","data":"Data","header":"Header","yes":"Yes","no":"No","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Choose"},"cellPad":"ระยะà¹à¸™à¸§à¸•à¸±à¹‰à¸‡","cellSpace":"ระยะà¹à¸™à¸§à¸™à¸­à¸™à¸™","column":{"menu":"คอลัมน์","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"ลบสดมน์"},"columns":"สดมน์","deleteTable":"ลบตาราง","headers":"ส่วนหัว","headersBoth":"ทั้งสองอย่าง","headersColumn":"คอลัมน์à¹à¸£à¸","headersNone":"None","headersRow":"à¹à¸–วà¹à¸£à¸","invalidBorder":"ขนาดเส้นà¸à¸£à¸­à¸šà¸•à¹‰à¸­à¸‡à¹€à¸›à¹‡à¸™à¸ˆà¸³à¸™à¸§à¸™à¸•à¸±à¸§à¹€à¸¥à¸‚","invalidCellPadding":"ช่องว่างภายในเซลล์ต้องเลขจำนวนบวà¸","invalidCellSpacing":"ช่องว่างภายในเซลล์ต้องเป็นเลขจำนวนบวà¸","invalidCols":"จำนวนคอลัมน์ต้องเป็นจำนวนมาà¸à¸à¸§à¹ˆà¸² 0","invalidHeight":"ส่วนสูงของตารางต้องเป็นตัวเลข","invalidRows":"จำนวนของà¹à¸–วต้องเป็นจำนวนมาà¸à¸à¸§à¹ˆà¸² 0","invalidWidth":"ความà¸à¸§à¹‰à¸²à¸‡à¸•à¸²à¸£à¸²à¸‡à¸•à¹‰à¸­à¸‡à¹€à¸›à¹‡à¸™à¸•à¸±à¸§à¹€à¸¥à¸‚","menu":"คุณสมบัติของ ตาราง","row":{"menu":"à¹à¸–ว","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"ลบà¹à¸–ว"},"rows":"à¹à¸–ว","summary":"สรุปความ","title":"คุณสมบัติของ ตาราง","toolbar":"ตาราง","widthPc":"เปอร์เซ็น","widthPx":"จุดสี","widthUnit":"หน่วยความà¸à¸§à¹‰à¸²à¸‡"},"undo":{"redo":"ทำซ้ำคำสั่ง","undo":"ยà¸à¹€à¸¥à¸´à¸à¸„ำสั่ง"},"wsc":{"btnIgnore":"ยà¸à¹€à¸§à¹‰à¸™","btnIgnoreAll":"ยà¸à¹€à¸§à¹‰à¸™à¸—ั้งหมด","btnReplace":"à¹à¸—นที่","btnReplaceAll":"à¹à¸—นที่ทั้งหมด","btnUndo":"ยà¸à¹€à¸¥à¸´à¸","changeTo":"à¹à¸à¹‰à¹„ขเป็น","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"ไม่ได้ติดตั้งระบบตรวจสอบคำสะà¸à¸”. ต้องà¸à¸²à¸£à¸•à¸´à¸”ตั้งไหมครับ?","manyChanges":"ตรวจสอบคำสะà¸à¸”เสร็จสิ้น:: à¹à¸à¹‰à¹„ข %1 คำ","noChanges":"ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: ไม่มีà¸à¸²à¸£à¹à¸à¹‰à¸„ำใดๆ","noMispell":"ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: ไม่พบคำสะà¸à¸”ผิด","noSuggestions":"- ไม่มีคำà¹à¸™à¸°à¸™à¸³à¹ƒà¸”ๆ -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"ไม่พบในดิà¸à¸Šà¸±à¸™à¸™à¸²à¸£à¸µ","oneChange":"ตรวจสอบคำสะà¸à¸”เสร็จสิ้น: à¹à¸à¹‰à¹„ข1คำ","progress":"à¸à¸³à¸¥à¸±à¸‡à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸„ำสะà¸à¸”...","title":"Spell Checker","toolbar":"ตรวจà¸à¸²à¸£à¸ªà¸°à¸à¸”คำ"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/tr.js b/js/ckeditor/lang/tr.js
new file mode 100644
index 0000000..43f0562
--- /dev/null
+++ b/js/ckeditor/lang/tr.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['tr']={"editor":"Zengin Metin Editörü","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Yardım için ALT 0 tuşlarına basın","browseServer":"Sunucuya Gözat","url":"URL","protocol":"Protokol","upload":"Karşıya Yükle","uploadSubmit":"Sunucuya Gönder","image":"Resim","flash":"Flash","form":"Form","checkbox":"Onay Kutusu","radio":"Seçenek Düğmesi","textField":"Metin Kutusu","textarea":"Metin Alanı","hiddenField":"Gizli Alan","button":"Düğme","select":"Seçme Alanı","imageButton":"Resim Düğmesi","notSet":"<tanımlanmamış>","id":"Kimlik","name":"İsim","langDir":"Dil Yönü","langDirLtr":"Soldan Sağa (LTR)","langDirRtl":"Sağdan Sola (RTL)","langCode":"Dil Kodlaması","longDescr":"Uzun Tanımlı URL","cssClass":"Biçem Sayfası Sınıfları","advisoryTitle":"Öneri Başlığı","cssStyle":"Biçem","ok":"Tamam","cancel":"İptal","close":"Kapat","preview":"Önizleme","resize":"Yeniden Boyutlandır","generalTab":"Genel","advancedTab":"Gelişmiş","validateNumberFailed":"Bu değer bir sayı değildir.","confirmNewPage":"Bu içerikle ilgili kaydedilmemiş tüm bilgiler kaybolacaktır. Yeni bir sayfa yüklemek istediğinizden emin misiniz?","confirmCancel":"Bazı seçenekleri değiştirdiniz. İletişim penceresini kapatmak istediğinizden emin misiniz?","options":"Seçenekler","target":"Hedef","targetNew":"Yeni Pencere (_blank)","targetTop":"En Üstteki Pencere (_top)","targetSelf":"Aynı Pencere (_self)","targetParent":"Üst Pencere (_parent)","langDirLTR":"Soldan Sağa (LTR)","langDirRTL":"Sağdan Sola (RTL)","styles":"Biçem","cssClasses":"Biçem Sayfası Sınıfları","width":"Genişlik","height":"Yükseklik","align":"Hizalama","alignLeft":"Sol","alignRight":"Sağ","alignCenter":"Ortala","alignJustify":"İki Kenara Yaslanmış","alignTop":"Üst","alignMiddle":"Orta","alignBottom":"Alt","alignNone":"Hiçbiri","invalidValue":"Geçersiz değer.","invalidHeight":"Yükseklik değeri bir sayı olmalıdır.","invalidWidth":"Genişlik değeri bir sayı olmalıdır.","invalidCssLength":"\"%1\" alanı için verilen değer, geçerli bir CSS ölçü birimi (px, %, in, cm, mm, em, ex, pt, veya pc) içeren veya içermeyen pozitif bir sayı olmalıdır.","invalidHtmlLength":"Belirttiğiniz sayı \"%1\" alanı için pozitif bir sayı HTML birim değeri olmalıdır (px veya %).","invalidInlineStyle":"Satıriçi biçem için verilen değer, \"isim : değer\" biçiminde birbirinden noktalı virgüllerle ayrılan bir veya daha fazla değişkenler grubundan oluşmalıdır.","cssLengthTooltip":"Piksel türünde bir sayı veya geçerli bir CSS ölçü birimi (px, %, in, cm, mm, em, ex, pt veya pc) içeren bir sayı girin.","unavailable":"%1<span class=\"cke_accessibility\">, kullanılamaz</span>"},"about":{"copy":"Copyright &copy; $1. Tüm hakları saklıdır.","dlgTitle":"CKEditor Hakkında","help":"Yardım için $1 kontrol edin.","moreInfo":"Lisanslama hakkında daha fazla bilgi almak için lütfen sitemizi ziyaret edin:","title":"CKEditor Hakkında","userGuide":"CKEditor Kullanıcı Kılavuzu"},"basicstyles":{"bold":"Kalın","italic":"İtalik","strike":"Üstü Çizgili","subscript":"Alt Simge","superscript":"Üst Simge","underline":"Altı Çizgili"},"blockquote":{"toolbar":"Blok Oluştur"},"clipboard":{"copy":"Kopyala","copyError":"Gezgin yazılımınızın güvenlik ayarları düzenleyicinin otomatik kopyalama işlemine izin vermiyor. İşlem için (Ctrl/Cmd+C) tuşlarını kullanın.","cut":"Kes","cutError":"Gezgin yazılımınızın güvenlik ayarları düzenleyicinin otomatik kesme işlemine izin vermiyor. İşlem için (Ctrl/Cmd+X) tuşlarını kullanın.","paste":"Yapıştır","pasteArea":"Yapıştırma Alanı","pasteMsg":"Lütfen aşağıdaki kutunun içine yapıştırın. (<STRONG>Ctrl/Cmd+V</STRONG>) ve <STRONG>Tamam</STRONG> butonunu tıklayın.","securityMsg":"Gezgin yazılımınızın güvenlik ayarları düzenleyicinin direkt olarak panoya erişimine izin vermiyor. Bu pencere içine tekrar yapıştırmalısınız..","title":"Yapıştır"},"contextmenu":{"options":"İçerik Menüsü Seçenekleri"},"button":{"selectedLabel":"%1 (Seçilmiş)"},"toolbar":{"toolbarCollapse":"Araç çubuklarını topla","toolbarExpand":"Araç çubuklarını aç","toolbarGroups":{"document":"Belge","clipboard":"Pano/Geri al","editing":"Düzenleme","forms":"Formlar","basicstyles":"Temel Stiller","paragraph":"Paragraf","links":"Bağlantılar","insert":"Ekle","styles":"Stiller","colors":"Renkler","tools":"Araçlar"},"toolbars":"Araç çubukları Editörü"},"elementspath":{"eleLabel":"Elementlerin yolu","eleTitle":"%1 elementi"},"format":{"label":"Biçim","panelTitle":"Biçim","tag_address":"Adres","tag_div":"Paragraf (DIV)","tag_h1":"Başlık 1","tag_h2":"Başlık 2","tag_h3":"Başlık 3","tag_h4":"Başlık 4","tag_h5":"Başlık 5","tag_h6":"Başlık 6","tag_p":"Normal","tag_pre":"Biçimli"},"horizontalrule":{"toolbar":"Yatay Satır Ekle"},"image":{"alertUrl":"Lütfen resmin URL'sini yazınız","alt":"Alternatif Yazı","border":"Kenar","btnUpload":"Sunucuya Yolla","button2Img":"Seçili resim butonunu basit resime çevirmek istermisiniz?","hSpace":"Yatay Boşluk","img2Button":"Seçili olan resimi, resimli butona çevirmek istermisiniz?","infoTab":"Resim Bilgisi","linkTab":"Köprü","lockRatio":"Oranı Kilitle","menu":"Resim Özellikleri","resetSize":"Boyutu Başa Döndür","title":"Resim Özellikleri","titleButton":"Resimli Düğme Özellikleri","upload":"Karşıya Yükle","urlMissing":"Resmin URL kaynağı eksiktir.","vSpace":"Dikey Boşluk","validateBorder":"Çerçeve tam sayı olmalıdır.","validateHSpace":"HSpace tam sayı olmalıdır.","validateVSpace":"VSpace tam sayı olmalıdır."},"indent":{"indent":"Sekme Arttır","outdent":"Sekme Azalt"},"fakeobjects":{"anchor":"Bağlantı","flash":"Flash Animasyonu","hiddenfield":"Gizli Alan","iframe":"IFrame","unknown":"Bilinmeyen Nesne"},"link":{"acccessKey":"Erişim Tuşu","advanced":"Gelişmiş","advisoryContentType":"Danışma İçerik Türü","advisoryTitle":"Danışma Başlığı","anchor":{"toolbar":"Bağlantı Ekle/Düzenle","menu":"Bağlantı Özellikleri","title":"Bağlantı Özellikleri","name":"Bağlantı Adı","errorName":"Lütfen bağlantı için ad giriniz","remove":"Bağlantıyı Kaldır"},"anchorId":"Eleman Kimlik Numarası ile","anchorName":"Bağlantı Adı ile","charset":"Bağlı Kaynak Karakter Gurubu","cssClasses":"Biçem Sayfası Sınıfları","emailAddress":"E-Posta Adresi","emailBody":"İleti Gövdesi","emailSubject":"İleti Konusu","id":"Id","info":"Link Bilgisi","langCode":"Dil Yönü","langDir":"Dil Yönü","langDirLTR":"Soldan Sağa (LTR)","langDirRTL":"Sağdan Sola (RTL)","menu":"Link Düzenle","name":"Ad","noAnchors":"(Bu belgede hiç çapa yok)","noEmail":"Lütfen E-posta adresini yazın","noUrl":"Lütfen Link URL'sini yazın","other":"<diğer>","popupDependent":"Bağımlı (Netscape)","popupFeatures":"Yeni Açılan Pencere Özellikleri","popupFullScreen":"Tam Ekran (IE)","popupLeft":"Sola Göre Konum","popupLocationBar":"Yer Çubuğu","popupMenuBar":"Menü Çubuğu","popupResizable":"Resizable","popupScrollBars":"Kaydırma Çubukları","popupStatusBar":"Durum Çubuğu","popupToolbar":"Araç Çubuğu","popupTop":"Yukarıya Göre Konum","rel":"İlişki","selectAnchor":"Bağlantı Seç","styles":"Biçem","tabIndex":"Sekme İndeksi","target":"Hedef","targetFrame":"<çerçeve>","targetFrameName":"Hedef Çerçeve Adı","targetPopup":"<yeni açılan pencere>","targetPopupName":"Yeni Açılan Pencere Adı","title":"Link","toAnchor":"Bu sayfada çapa","toEmail":"E-Posta","toUrl":"URL","toolbar":"Link Ekle/Düzenle","type":"Link Türü","unlink":"Köprü Kaldır","upload":"Karşıya Yükle"},"list":{"bulletedlist":"Simgeli Liste","numberedlist":"Numaralı Liste"},"magicline":{"title":"Parağrafı buraya ekle"},"maximize":{"maximize":"Büyült","minimize":"Küçült"},"pastetext":{"button":"Düz Metin Olarak Yapıştır","title":"Düz Metin Olarak Yapıştır"},"pastefromword":{"confirmCleanup":"Yapıştırmaya çalıştığınız metin Word'den kopyalanmıştır. Yapıştırmadan önce silmek istermisiniz?","error":"Yapıştırmadaki veri bilgisi hata düzelene kadar silinmeyecektir","title":"Word'den Yapıştır","toolbar":"Word'den Yapıştır"},"removeformat":{"toolbar":"Biçimi Kaldır"},"sourcearea":{"toolbar":"Kaynak"},"specialchar":{"options":"Özel Karakter Seçenekleri","title":"Özel Karakter Seç","toolbar":"Özel Karakter Ekle"},"scayt":{"btn_about":"SCAYT'ı hakkında","btn_dictionaries":"Sözlükler","btn_disable":"SCAYT'ı pasifleştir","btn_enable":"SCAYT'ı etkinleştir","btn_langs":"Diller","btn_options":"Seçenekler","text_title":"Girmiş olduğunuz kelime denetimi"},"stylescombo":{"label":"Biçem","panelTitle":"Stilleri Düzenliyor","panelTitle1":"Blok Stilleri","panelTitle2":"Inline Stilleri","panelTitle3":"Nesne Stilleri"},"table":{"border":"Kenar Kalınlığı","caption":"Başlık","cell":{"menu":"Hücre","insertBefore":"Hücre Ekle - Önce","insertAfter":"Hücre Ekle - Sonra","deleteCell":"Hücre Sil","merge":"Hücreleri Birleştir","mergeRight":"Birleştir - Sağdaki İle ","mergeDown":"Birleştir - Aşağıdaki İle ","splitHorizontal":"Hücreyi Yatay Böl","splitVertical":"Hücreyi Dikey Böl","title":"Hücre Özellikleri","cellType":"Hücre Tipi","rowSpan":"Satırlar Mesafesi (Span)","colSpan":"Sütünlar Mesafesi (Span)","wordWrap":"Kelime Kaydırma","hAlign":"Düşey Hizalama","vAlign":"Yataş Hizalama","alignBaseline":"Tabana","bgColor":"Arkaplan Rengi","borderColor":"Çerçeve Rengi","data":"Veri","header":"Başlık","yes":"Evet","no":"Hayır","invalidWidth":"Hücre genişliği sayı olmalıdır.","invalidHeight":"Hücre yüksekliği sayı olmalıdır.","invalidRowSpan":"Satırların mesafesi tam sayı olmalıdır.","invalidColSpan":"Sütünların mesafesi tam sayı olmalıdır.","chooseColor":"Seçiniz"},"cellPad":"Izgara yazı arası","cellSpace":"Izgara kalınlığı","column":{"menu":"Sütun","insertBefore":"Kolon Ekle - Önce","insertAfter":"Kolon Ekle - Sonra","deleteColumn":"Sütun Sil"},"columns":"Sütunlar","deleteTable":"Tabloyu Sil","headers":"Başlıklar","headersBoth":"Her İkisi","headersColumn":"İlk Sütun","headersNone":"Yok","headersRow":"İlk Satır","invalidBorder":"Çerceve büyüklüklüğü sayı olmalıdır.","invalidCellPadding":"Hücre aralığı (padding) sayı olmalıdır.","invalidCellSpacing":"Hücre boşluğu (spacing) sayı olmalıdır.","invalidCols":"Sütün sayısı 0 sayısından büyük olmalıdır.","invalidHeight":"Tablo yüksekliği sayı olmalıdır.","invalidRows":"Satır sayısı 0 sayısından büyük olmalıdır.","invalidWidth":"Tablo genişliği sayı olmalıdır.","menu":"Tablo Özellikleri","row":{"menu":"Satır","insertBefore":"Satır Ekle - Önce","insertAfter":"Satır Ekle - Sonra","deleteRow":"Satır Sil"},"rows":"Satırlar","summary":"Özet","title":"Tablo Özellikleri","toolbar":"Tablo","widthPc":"yüzde","widthPx":"piksel","widthUnit":"genişlik birimi"},"undo":{"redo":"Tekrarla","undo":"Geri Al"},"wsc":{"btnIgnore":"Yoksay","btnIgnoreAll":"Tümünü Yoksay","btnReplace":"Değiştir","btnReplaceAll":"Tümünü Değiştir","btnUndo":"Geri Al","changeTo":"Şuna değiştir:","errorLoading":"Uygulamada yüklerken hata oluştu: %s.","ieSpellDownload":"Yazım denetimi yüklenmemiş. Şimdi yüklemek ister misiniz?","manyChanges":"Yazım denetimi tamamlandı: %1 kelime değiştirildi","noChanges":"Yazım denetimi tamamlandı: Hiçbir kelime değiştirilmedi","noMispell":"Yazım denetimi tamamlandı: Yanlış yazıma rastlanmadı","noSuggestions":"- Öneri Yok -","notAvailable":"Üzügünüz, bu servis şuanda hizmet dışıdır.","notInDic":"Sözlükte Yok","oneChange":"Yazım denetimi tamamlandı: Bir kelime değiştirildi","progress":"Yazım denetimi işlemde...","title":"Yazımı Denetle","toolbar":"Yazım Denetimi"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/tt.js b/js/ckeditor/lang/tt.js
new file mode 100644
index 0000000..45bb79c
--- /dev/null
+++ b/js/ckeditor/lang/tt.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['tt']={"editor":"Форматлаулы текÑÑ‚ өлкәÑе","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"Ярдәм өчен ALT 0 баÑыгыз","browseServer":"Сервер карап чыгу","url":"Сылталама","protocol":"Протокол","upload":"Йөкләү","uploadSubmit":"Серверга җибәрү","image":"РәÑем","flash":"Флеш","form":"Форма","checkbox":"ЧекбокÑ","radio":"Радио төймә","textField":"ТекÑÑ‚ кыры","textarea":"ТекÑÑ‚ мәйданы","hiddenField":"Яшерен кыр","button":"Төймə","select":"Сайлау кыры","imageButton":"РәÑемле төймə","notSet":"<билгеләнмәгән>","id":"Id","name":"ИÑем","langDir":"Язылыш юнəлеше","langDirLtr":"Сулдан уңга Ñзылыш (LTR)","langDirRtl":"Уңнан Ñулга Ñзылыш (RTL)","langCode":"Тел коды","longDescr":"Җентекле таÑвирламага Ñылталама","cssClass":"Стильләр клаÑÑлары","advisoryTitle":"Киңәш иÑем","cssStyle":"Стиль","ok":"Тәмам","cancel":"Баш тарту","close":"Чыгу","preview":"Карап алу","resize":"Зурлыкны үзгәртү","generalTab":"Төп","advancedTab":"Киңәйтелгән көйләүләр","validateNumberFailed":"Әлеге кыйммәт Ñан түгел","confirmNewPage":"Any unsaved changes to this content will be lost. Are you sure you want to load new page?","confirmCancel":"You have changed some options. Are you sure you want to close the dialog window?","options":"Үзлекләр","target":"МакÑат","targetNew":"Яңа тәрәзә (_blank)","targetTop":"Ó¨Ñке тәрәзә (_top)","targetSelf":"Шул үк тәрәзә (_self)","targetParent":"Ðна тәрәзә (_parent)","langDirLTR":"Сулдан уңга Ñзылыш (LTR)","langDirRTL":"Уңнан Ñулга Ñзылыш (RTL)","styles":"Стиль","cssClasses":"Стильләр клаÑÑлары","width":"Киңлек","height":"Биеклек","align":"Тигезләү","alignLeft":"Сул Ñкка","alignRight":"Уң Ñкка","alignCenter":"Үзәккә","alignJustify":"Киңлеккә карап тигезләү","alignTop":"Ó¨Ñкә","alignMiddle":"Уртага","alignBottom":"ÐÑка","alignNone":"Һичбер","invalidValue":"Ð”Ó©Ñ€ÐµÑ Ð±ÑƒÐ»Ð¼Ð°Ð³Ð°Ð½ кыйммәт","invalidHeight":"Биеклек Ñан булырга тиеш","invalidWidth":"Киңлек Ñан булырга тиеш","invalidCssLength":"Value specified for the \"%1\" field must be a positive number with or without a valid CSS measurement unit (px, %, in, cm, mm, em, ex, pt, or pc).","invalidHtmlLength":"Value specified for the \"%1\" field must be a positive number with or without a valid HTML measurement unit (px or %).","invalidInlineStyle":"Value specified for the inline style must consist of one or more tuples with the format of \"name : value\", separated by semi-colons.","cssLengthTooltip":"Enter a number for a value in pixels or a number with a valid CSS unit (px, %, in, cm, mm, em, ex, pt, or pc).","unavailable":"%1<span class=\"cke_accessibility\">, unavailable</span>"},"about":{"copy":"Copyright &copy; $1. Бар хокуклар Ñакланган","dlgTitle":"CKEditor турында","help":"Check $1 for help.","moreInfo":"For licensing information please visit our web site:","title":"CKEditor турында","userGuide":"CKEditor кулланмаÑÑ‹"},"basicstyles":{"bold":"Калын","italic":"КурÑив","strike":"Сызылган","subscript":"ÐÑкы индекÑ","superscript":"Ó¨Ñке индекÑ","underline":"ÐÑтына Ñызылган"},"blockquote":{"toolbar":"Өземтә блогы"},"clipboard":{"copy":"Күчермәләү","copyError":"Браузерыгызның иминлек үзлекләре автоматик рәвештә күчермәләү үтәүне Ñ‚Ñ‹Ñ. Тиз төймәләрне (Ctrl/Cmd+C) кулланыгыз.","cut":"КиÑеп алу","cutError":"Браузерыгызның иминлек үзлекләре автоматик рәвештә күчермәләү үтәүне Ñ‚Ñ‹Ñ. Тиз төймәләрне (Ctrl/Cmd+C) кулланыгыз.","paste":"Ó¨ÑÑ‚Ó™Ò¯","pasteArea":"Ó¨ÑÑ‚Ó™Ò¯ мәйданы","pasteMsg":"Please paste inside the following box using the keyboard (<strong>Ctrl/Cmd+V</strong>) and hit OK","securityMsg":"Because of your browser security settings, the editor is not able to access your clipboard data directly. You are required to paste it again in this window.","title":"Ó¨ÑÑ‚Ó™Ò¯"},"contextmenu":{"options":"КонтекÑÑ‚ меню үзлекләре"},"button":{"selectedLabel":"%1 (Сайланган)"},"toolbar":{"toolbarCollapse":"Collapse Toolbar","toolbarExpand":"Expand Toolbar","toolbarGroups":{"document":"Документ","clipboard":"Ðлмашу буферы/Кайтару","editing":"Төзәтү","forms":"Формалар","basicstyles":"Төп Ñтильләр","paragraph":"Параграф","links":"Сылталамалар","insert":"Ó¨ÑÑ‚Ó™Ò¯","styles":"Стильләр","colors":"ТөÑләр","tools":"Кораллар"},"toolbars":"Editor toolbars"},"elementspath":{"eleLabel":"Elements path","eleTitle":"%1 Ñлемент"},"format":{"label":"Форматлау","panelTitle":"Параграф форматлавы","tag_address":"ÐдреÑ","tag_div":"Гади (DIV)","tag_h1":"Башлам 1","tag_h2":"Башлам 2","tag_h3":"Башлам 3","tag_h4":"Башлам 4","tag_h5":"Башлам 5","tag_h6":"Башлам 6","tag_p":"Гади","tag_pre":"Форматлаулы"},"horizontalrule":{"toolbar":"Ятма Ñызык Ó©ÑÑ‚Ó™Ò¯"},"image":{"alertUrl":"РәÑемгә Ñылталама Ñзыгыз","alt":"Ðльтернатив текÑÑ‚","border":"Чик","btnUpload":"Серверга җибәрү","button2Img":"Do you want to transform the selected image button on a simple image?","hSpace":"HSpace","img2Button":"Do you want to transform the selected image on a image button?","infoTab":"РәÑем таÑвирламаÑÑ‹","linkTab":"Сылталама","lockRatio":"Lock Ratio","menu":"РәÑем үзлекләре","resetSize":"Баштагы зурлык","title":"РәÑем үзлекләре","titleButton":"РәÑемле төймə үзлекләре","upload":"Йөкләү","urlMissing":"Image source URL is missing.","vSpace":"VSpace","validateBorder":"Чик киңлеге Ñан булырга тиеш","validateHSpace":"HSpace must be a whole number.","validateVSpace":"VSpace must be a whole number."},"indent":{"indent":"ОтÑтупны арттыру","outdent":"ОтÑтупны кечерәйтү"},"fakeobjects":{"anchor":"Якорь","flash":"Флеш анимациÑÑÑ‹","hiddenfield":"Яшерен кыр","iframe":"IFrame","unknown":"Танылмаган объект"},"link":{"acccessKey":"Access Key","advanced":"Киңәйтелгән көйләүләр","advisoryContentType":"Advisory Content Type","advisoryTitle":"Киңәш иÑем","anchor":{"toolbar":"Якорь","menu":"Якорьне үзгәртү","title":"Якорь үзлекләре","name":"Якорь иÑеме","errorName":"Якорьнең иÑемен Ñзыгыз","remove":"Якорьне бетерү"},"anchorId":"Элемент идентификаторы буенча","anchorName":"Якорь иÑеме буенча","charset":"Linked Resource Charset","cssClasses":"Стильләр клаÑÑлары","emailAddress":"Электрон почта адреÑÑ‹","emailBody":"Хат Ñчтәлеге","emailSubject":"Хат темаÑÑ‹","id":"Идентификатор","info":"Сылталама таÑвирламаÑÑ‹","langCode":"Тел коды","langDir":"Язылыш юнəлеше","langDirLTR":"Сулдан уңга Ñзылыш (LTR)","langDirRTL":"Уңнан Ñулга Ñзылыш (RTL)","menu":"Сылталамаyны үзгәртү","name":"ИÑем","noAnchors":"(Әлеге документта Ñкорьләр табылмады)","noEmail":"Электрон почта адреÑын Ñзыгыз","noUrl":"Сылталаманы Ñзыгыз","other":"<бүтән>","popupDependent":"Бәйле (Netscape)","popupFeatures":"Popup Window Features","popupFullScreen":"Тулы Ñкран (IE)","popupLeft":"Left Position","popupLocationBar":"Location Bar","popupMenuBar":"Menu Bar","popupResizable":"Resizable","popupScrollBars":"Scroll Bars","popupStatusBar":"Status Bar","popupToolbar":"Toolbar","popupTop":"Top Position","rel":"Бәйләнеш","selectAnchor":"Якорьне Ñайлау","styles":"Стиль","tabIndex":"Tab Index","target":"МакÑат","targetFrame":"<frame>","targetFrameName":"Target Frame Name","targetPopup":"<popup window>","targetPopupName":"Попап тәрәзәÑе иÑеме","title":"Сылталама","toAnchor":"Якорьне текÑÑ‚ белән бәйләү","toEmail":"Электрон почта","toUrl":"Сылталама","toolbar":"Сылталама","type":"Сылталама төре","unlink":"Сылталаманы бетерү","upload":"Йөкләү"},"list":{"bulletedlist":"Маркерлы тезмә Ó©ÑÑ‚Ó™Ò¯/бетерү","numberedlist":" Ðомерланган тезмә Ó©ÑÑ‚Ó™Ò¯/бетерү"},"magicline":{"title":"Бирегә параграф Ó©ÑÑ‚Ó™Ò¯"},"maximize":{"maximize":"Зурайту","minimize":"Кечерәйтү"},"pastetext":{"button":"ФорматлауÑыз текÑÑ‚ Ó©ÑÑ‚Ó™Ò¯","title":"ФорматлауÑыз текÑÑ‚ Ó©ÑÑ‚Ó™Ò¯"},"pastefromword":{"confirmCleanup":"The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?","error":"It was not possible to clean up the pasted data due to an internal error","title":"Word'тан Ó©ÑÑ‚Ó™Ò¯","toolbar":"Word'тан Ó©ÑÑ‚Ó™Ò¯"},"removeformat":{"toolbar":"Форматлауны бетерү"},"sourcearea":{"toolbar":"Чыганак"},"specialchar":{"options":"МахÑÑƒÑ Ñимвол үзлекләре","title":"МахÑÑƒÑ Ñимвол Ñайлау","toolbar":"МахÑÑƒÑ Ñимвол Ó©ÑÑ‚Ó™Ò¯"},"scayt":{"btn_about":"About SCAYT","btn_dictionaries":"Dictionaries","btn_disable":"Disable SCAYT","btn_enable":"Enable SCAYT","btn_langs":"Languages","btn_options":"Options","text_title":"Spell Check As You Type"},"stylescombo":{"label":"Стильләр","panelTitle":"Форматлау Ñтильләре","panelTitle1":"Блоклар Ñтильләре","panelTitle2":"Эчке Ñтильләр","panelTitle3":"Объектлар Ñтильләре"},"table":{"border":"Чик калынлыгы","caption":"ИÑем","cell":{"menu":"Күзәнәк","insertBefore":"Ðлдына күзәнәк Ó©ÑÑ‚Ó™Ò¯","insertAfter":"Ðртына күзәнәк Ó©ÑÑ‚Ó™Ò¯","deleteCell":"Күзәнәкләрне бетерү","merge":"Күзәнәкләрне берләштерү","mergeRight":"Уң Ñктагы белән берләштерү","mergeDown":"ÐÑтагы белән берләштерү","splitHorizontal":"Күзәнәкне юлларга бүлү","splitVertical":"Күзәнәкне баганаларга бүлү","title":"Күзәнәк үзлекләре","cellType":"Күзәнәк төре","rowSpan":"Юлларны берләштерү","colSpan":"Баганаларны берләштерү","wordWrap":"ТекÑтны күчерү","hAlign":"Ятма тигезләү","vAlign":"ÐÑма тигезләү","alignBaseline":"ТаÑныч Ñызыгы","bgColor":"Фон Ñ‚Ó©Ñе","borderColor":"Чик Ñ‚Ó©Ñе","data":"Мәгълүмат","header":"Башлык","yes":"Әйе","no":"Юк","invalidWidth":"Cell width must be a number.","invalidHeight":"Cell height must be a number.","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"Сайлау"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"Багана","insertBefore":"Сулдан баганалар Ó©ÑÑ‚Ó™Ò¯","insertAfter":"Уңнан баганалар Ó©ÑÑ‚Ó™Ò¯","deleteColumn":"Баганаларны бетерү"},"columns":"Баганалар","deleteTable":"Таблицаны бетерү","headers":"Башлыклар","headersBoth":"ИкеÑе дә","headersColumn":"Беренче багана","headersNone":"Һичбер","headersRow":"Беренче юл","invalidBorder":"Чик киңлеге Ñан булырга тиеш","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"Number of columns must be a number greater than 0.","invalidHeight":"Таблица биеклеге Ñан булырга тиеш","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"Таблица киңлеге Ñан булырга тиеш","menu":"Таблица үзлекләре","row":{"menu":"Юл","insertBefore":"Ó¨Ñтән юллар Ó©ÑÑ‚Ó™Ò¯","insertAfter":"ÐÑтан юллар Ó©ÑÑ‚Ó™Ò¯","deleteRow":"Юлларны бетерү"},"rows":"Юллар","summary":"Йомгаклау","title":"Таблица үзлекләре","toolbar":"Таблица","widthPc":"процент","widthPx":"Ðокталар","widthUnit":"киңлек берәмлеге"},"undo":{"redo":"Кабатлау","undo":"Кайтару"},"wsc":{"btnIgnore":"Ignore","btnIgnoreAll":"Ignore All","btnReplace":"Replace","btnReplaceAll":"Replace All","btnUndo":"Undo","changeTo":"Change to","errorLoading":"Error loading application service host: %s.","ieSpellDownload":"Spell checker not installed. Do you want to download it now?","manyChanges":"Spell check complete: %1 words changed","noChanges":"Spell check complete: No words changed","noMispell":"Spell check complete: No misspellings found","noSuggestions":"- No suggestions -","notAvailable":"Sorry, but service is unavailable now.","notInDic":"Not in dictionary","oneChange":"Spell check complete: One word changed","progress":"Spell check in progress...","title":"Spell Checker","toolbar":"Check Spelling"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/ug.js b/js/ckeditor/lang/ug.js
new file mode 100644
index 0000000..3d5785c
--- /dev/null
+++ b/js/ckeditor/lang/ug.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['ug']={"editor":"تەھرىرلىگۈچ","editorPanel":"Rich Text Editor panel","common":{"editorHelp":"ALT+0 نى بÛسىپ ياردەمنى كۆرۈڭ","browseServer":"كۆرسىتىش مۇلازىمÛتىر","url":"ئەسلى ھۆججەت","protocol":"ÙƒÛلىشىم","upload":"يۈكلە","uploadSubmit":"مۇلازىمÛتىرغا يۈكلە","image":"سۈرەت","flash":"Flash","form":"جەدۋەل","checkbox":"ÙƒÛ†Ù¾ تاللاش رامكىسى","radio":"يەككە تاللاش توپچىسى","textField":"يەككە قۇر تÛكىست","textarea":"ÙƒÛ†Ù¾ قۇر تÛكىست","hiddenField":"يوشۇرۇن دائىرە","button":"توپچا","select":"تىزىم/تىزىملىك","imageButton":"سۈرەت دائىرە","notSet":"‹تەڭشەلمىگەن›","id":"ID","name":"ئات","langDir":"تىل يۆنىلىشى","langDirLtr":"سولدىن ئوڭغا (LTR)","langDirRtl":"ئوڭدىن سولغا (RTL)","langCode":"تىل كودى","longDescr":"تەپسىلىي چۈشەندۈرۈش ئادرÛسى","cssClass":"ئۇسلۇب خىلىنىڭ ئاتى","advisoryTitle":"ماۋزۇ","cssStyle":"قۇر ئىچىدىكى ئۇسلۇبى","ok":"جەزملە","cancel":"ۋاز ÙƒÛ•Ú†","close":"تاقا","preview":"ئالدىن كۆزەت","resize":"چوڭلۇقىنى ئۆزگەرت","generalTab":"ئادەتتىكى","advancedTab":"ئالىي","validateNumberFailed":"سان پىچىمىدا كىرگۈزۈش زۆرۈر","confirmNewPage":"نۆۋەتتىكى پۈتۈك مەزمۇنى ساقلانمىدى، ÙŠÛÚ­Ù‰ پۈتۈك قۇرامسىز؟","confirmCancel":"قىسمەن ئۆزگەرتىش ساقلانمىدى، بۇ سۆزلەشكۈنى تاقامسىز؟","options":"تاللانما","target":"نىشان كۆزنەك","targetNew":"ÙŠÛÚ­Ù‰ كۆزنەك (_blank)","targetTop":"پۈتۈن بەت (_top)","targetSelf":"مەزكۇر كۆزنەك (_self)","targetParent":"ئاتا كۆزنەك (_parent)","langDirLTR":"سولدىن ئوڭغا (LTR)","langDirRTL":"ئوڭدىن سولغا (RTL)","styles":"ئۇسلۇبلار","cssClasses":"ئۇسلۇب خىللىرى","width":"كەڭلىك","height":"ئÛگىزلىك","align":"توغرىلىنىشى","alignLeft":"سول","alignRight":"ئوڭ","alignCenter":"ئوتتۇرا","alignJustify":"ئىككى تەرەپتىن توغرىلا","alignTop":"ئۈستى","alignMiddle":"ئوتتۇرا","alignBottom":"ئاستى","alignNone":"None","invalidValue":"ئىناۋەتسىز قىممەت.","invalidHeight":"ئÛگىزلىك چوقۇم رەقەم پىچىمىدا بولۇشى زۆرۈر","invalidWidth":"كەڭلىك چوقۇم رەقەم پىچىمىدا بولۇشى زۆرۈر","invalidCssLength":"بۇ سۆز بۆلىكى چوقۇم مۇۋاپىق بولغان CSS ئۇزۇنلۇق قىممىتى بولۇشى زۆرۈر، بىرلىكى (px, %, in, cm, mm, em, ex, pt ياكى pc)","invalidHtmlLength":"بۇ سۆز بۆلىكى چوقۇم بىرىكمە HTML ئۇزۇنلۇق قىممىتى بولۇشى ÙƒÛرەك. ئۆز ئىچىگە ئالىدىغان بىرلىك (px ياكى %)","invalidInlineStyle":"ئىچكى باغلانما ئۇسلۇبى چوقۇم Ú†Ûكىتلىك Ù¾Û•Ø´ بىلەن ئايرىلغان بىر ياكى ÙƒÛ†Ù¾ «خاسلىق ئاتى:خاسلىق قىممىتى» پىچىمىدا بولۇشى لازىم","cssLengthTooltip":"بۇ سۆز بۆلىكى بىرىكمە CSS ئۇزۇنلۇق قىممىتى بولۇشى ÙƒÛرەك. ئۆز ئىچىگە ئالىدىغان بىرلىك (px, %, in, cm, mm, em, ex, pt ياكى pc)","unavailable":"%1<span class=\\\\\"cke_accessibility\\\\\">ØŒ ئىشلەتكىلى بولمايدۇ</span>"},"about":{"copy":"Copyright &copy; $1. نەشر ھوقۇقىغا ئىگە","dlgTitle":"CKEditor ھەققىدە","help":"$1 نى زىيارەت قىلىپ ياردەمگە ئÛرىشىڭ","moreInfo":"تور تۇرايىمىزنى زىيارەت قىلىپ ÙƒÛلىشىمگە ئائىت تÛخىمۇ ÙƒÛ†Ù¾ ئۇچۇرغا ئÛرىشىڭ","title":"CKEditor ھەققىدە","userGuide":"CKEditor ئىشلەتكۈچى قوللانمىسى"},"basicstyles":{"bold":"توم","italic":"يانتۇ","strike":"ئۆچۈرۈش سىزىقى","subscript":"تۆۋەن ئىندÛكس","superscript":"يۇقىرى ئىندÛكس","underline":"ئاستى سىزىق"},"blockquote":{"toolbar":"بۆلەك نەقىل"},"clipboard":{"copy":"نەشر ھوقۇقىغا ئىگە بەلگىسى","copyError":"تور كۆرگۈڭىزنىڭ بىخەتەرلىك تەڭشىكى تەھرىرلىگۈچنىڭ كۆچۈر مەشغۇلاتىنى ئۆزلۈكىدىن ئىجرا قىلىشىغا يول قويمايدۇ، ھەرپتاختا تÛز كۇنۇپكا (Ctrl/Cmd+C) ئارقىلىق تاماملاڭ","cut":"كەس","cutError":"تور كۆرگۈڭىزنىڭ بىخەتەرلىك تەڭشىكى تەھرىرلىگۈچنىڭ كەس مەشغۇلاتىنى ئۆزلۈكىدىن ئىجرا قىلىشىغا يول قويمايدۇ، ھەرپتاختا تÛز كۇنۇپكا (Ctrl/Cmd+X) ئارقىلىق تاماملاڭ","paste":"چاپلا","pasteArea":"چاپلاش دائىرىسى","pasteMsg":"ھەرپتاختا تÛز كۇنۇپكا (<STRONG>Ctrl/Cmd+V</STRONG>) نى ئىشلىتىپ مەزمۇننى تۆۋەندىكى رامكىغا كۆچۈرۈڭ، ئاندىن <STRONG>جەزملە</STRONG>نى بÛسىڭ","securityMsg":"توركۆرگۈڭىزنىڭ بىخەتەرلىك تەڭشىكى سەۋەبىدىن بۇ تەھرىرلىگۈچ چاپلاش تاختىسىدىكى مەزمۇننى بىۋاستە زىيارەت قىلالمايدۇ، بۇ كۆزنەكتە قايتا بىر Ù‚Ûتىم چاپلىشىڭىز ÙƒÛرەك.","title":"چاپلا"},"contextmenu":{"options":"قىسقا يول تىزىملىك تاللانمىسى"},"button":{"selectedLabel":"%1 (Selected)"},"toolbar":{"toolbarCollapse":"قورال بالداقنى قاتلا","toolbarExpand":"قورال بالداقنى ياي","toolbarGroups":{"document":"پۈتۈك","clipboard":"چاپلاش تاختىسى/ÙŠÛنىۋال","editing":"تەھرىر","forms":"جەدۋەل","basicstyles":"ئاساسىي ئۇسلۇب","paragraph":"ئابزاس","links":"ئۇلانما","insert":"قىستۇر","styles":"ئۇسلۇب","colors":"رەڭ","tools":"قورال"},"toolbars":"قورال بالداق"},"elementspath":{"eleLabel":"ئÛÙ„ÛÙ…Ûنت يولى","eleTitle":"%1 ئÛÙ„ÛÙ…Ûنت"},"format":{"label":"پىچىم","panelTitle":"پىچىم","tag_address":"ئادرÛس","tag_div":"ئابزاس (DIV)","tag_h1":"ماۋزۇ 1","tag_h2":"ماۋزۇ 2","tag_h3":"ماۋزۇ 3","tag_h4":"ماۋزۇ 4","tag_h5":"ماۋزۇ 5","tag_h6":"ماۋزۇ 6","tag_p":"ئادەتتىكى","tag_pre":"تىزىلغان پىچىم"},"horizontalrule":{"toolbar":"توغرا سىزىق قىستۇر"},"image":{"alertUrl":"سۈرەت ئادرÛسىنى كىرگۈزۈڭ","alt":"تÛكىست ئالماشتۇر","border":"گىرۋەك چوڭلۇقى","btnUpload":"مۇلازىمÛتىرغا يۈكلە","button2Img":"نۆۋەتتىكى توپچىنى سۈرەتكە ئۆزگەرتەمسىز؟","hSpace":"توغرىسىغا ئارىلىقى","img2Button":"نۆۋەتتىكى سۈرەتنى توپچىغا ئۆزگەرتەمسىز؟","infoTab":"سۈرەت","linkTab":"ئۇلانما","lockRatio":"نىسبەتنى قۇلۇپلا","menu":"سۈرەت خاسلىقى","resetSize":"ئەسلى Ú†ÙˆÚ­Ù„Û‡Ù‚","title":"سۈرەت خاسلىقى","titleButton":"سۈرەت دائىرە خاسلىقى","upload":"يۈكلە","urlMissing":"سۈرەتنىڭ ئەسلى ھۆججەت ئادرÛسى ÙƒÛ•Ù…","vSpace":"بويىغا ئارىلىقى","validateBorder":"گىرۋەك چوڭلۇقى چوقۇم سان بولىدۇ","validateHSpace":"توغرىسىغا ئارىلىق چوقۇم پۈتۈن سان بولىدۇ","validateVSpace":"بويىغا ئارىلىق چوقۇم پۈتۈن سان بولىدۇ"},"indent":{"indent":"تارايت","outdent":"كەڭەيت"},"fakeobjects":{"anchor":"لەڭگەرلىك نۇقتا","flash":"Flash جانلاندۇرۇم","hiddenfield":"يوشۇرۇن دائىرە","iframe":"IFrame","unknown":"يوچۇن Ù†Û•Ú­"},"link":{"acccessKey":"زىيارەت كۇنۇپكا","advanced":"ئالىي","advisoryContentType":"مەزمۇن تىپى","advisoryTitle":"ماۋزۇ","anchor":{"toolbar":"لەڭگەرلىك نۇقتا ئۇلانمىسى قىستۇر/تەھرىرلە","menu":"لەڭگەرلىك نۇقتا ئۇلانما خاسلىقى","title":"لەڭگەرلىك نۇقتا ئۇلانما خاسلىقى","name":"لەڭگەرلىك نۇقتا ئاتى","errorName":"لەڭگەرلىك نۇقتا ئاتىنى كىرگۈزۈڭ","remove":"لەڭگەرلىك نۇقتا ئۆچۈر"},"anchorId":"لەڭگەرلىك نۇقتا ID سى بويىچە","anchorName":"لەڭگەرلىك نۇقتا ئاتى بويىچە","charset":"ھەرپ كودلىنىشى","cssClasses":"ئۇسلۇب خىلى ئاتى","emailAddress":"ئادرÛس","emailBody":"مەزمۇن","emailSubject":"ماۋزۇ","id":"ID","info":"ئۇلانما ئۇچۇرى","langCode":"تىل كودى","langDir":"تىل يۆنىلىشى","langDirLTR":"سولدىن ئوڭغا (LTR)","langDirRTL":"ئوڭدىن سولغا (RTL)","menu":"ئۇلانما تەھرىر","name":"ئات","noAnchors":"(بۇ پۈتۈكتە ئىشلەتكىلى بولىدىغان لەڭگەرلىك نۇقتا يوق)","noEmail":"ئÛلخەت ئادرÛسىنى كىرگۈزۈڭ","noUrl":"ئۇلانما ئادرÛسىنى كىرگۈزۈڭ","other":"‹باشقا›","popupDependent":"تەۋە (NS)","popupFeatures":"قاڭقىش كۆزنەك خاسلىقى","popupFullScreen":"پۈتۈن ئÛكران (IE)","popupLeft":"سول","popupLocationBar":"ئادرÛس بالداق","popupMenuBar":"تىزىملىك بالداق","popupResizable":"چوڭلۇقى ئۆزگەرتىشچان","popupScrollBars":"دومىلىما سۈرگۈچ","popupStatusBar":"ھالەت بالداق","popupToolbar":"قورال بالداق","popupTop":"ئوڭ","rel":"باغلىنىش","selectAnchor":"بىر لەڭگەرلىك نۇقتا تاللاڭ","styles":"قۇر ئىچىدىكى ئۇسلۇبى","tabIndex":"Tab تەرتىپى","target":"نىشان","targetFrame":"‹كاندۇك›","targetFrameName":"نىشان كاندۇك ئاتى","targetPopup":"‹قاڭقىش كۆزنەك›","targetPopupName":"قاڭقىش كۆزنەك ئاتى","title":"ئۇلانما","toAnchor":"بەت ئىچىدىكى لەڭگەرلىك نۇقتا ئۇلانمىسى","toEmail":"ئÛلخەت","toUrl":"ئادرÛس","toolbar":"ئۇلانما قىستۇر/تەھرىرلە","type":"ئۇلانما تىپى","unlink":"ئۇلانما بىكار قىل","upload":"يۈكلە"},"list":{"bulletedlist":"تۈر بەلگە تىزىمى","numberedlist":"تەرتىپ نومۇر تىزىمى"},"magicline":{"title":"بۇ جايغا ئابزاس قىستۇر"},"maximize":{"maximize":"چوڭايت","minimize":"كىچىكلەت"},"pastetext":{"button":"پىچىمى يوق تÛكىست سۈپىتىدە چاپلا","title":"پىچىمى يوق تÛكىست سۈپىتىدە چاپلا"},"pastefromword":{"confirmCleanup":"سىز چاپلىماقچى بولغان مەزمۇن MS Word تىن كەلگەندەك قىلىدۇ، MS Word پىچىمىنى تازىلىۋەتكەندىن ÙƒÛيىن ئاندىن چاپلامدۇ؟","error":"ئىچكى خاتالىق سەۋەبىدىن چاپلايدىغان سانلىق مەلۇماتنى تازىلىيالمايدۇ","title":"MS Word تىن چاپلا","toolbar":"MS Word تىن چاپلا"},"removeformat":{"toolbar":"پىچىمنى چىقىرىۋەت"},"sourcearea":{"toolbar":"مەنبە"},"specialchar":{"options":"ئالاھىدە ھەرپ تاللانمىسى","title":"ئالاھىدە ھەرپ تاللاڭ","toolbar":"ئالاھىدە ھەرپ قىستۇر"},"scayt":{"btn_about":"شۇئان ئىملا تەكشۈرۈش ھەققىدە","btn_dictionaries":"لۇغەت","btn_disable":"شۇئان ئىملا تەكشۈرۈشنى چەكلە","btn_enable":"شۇئان ئىملا تەكشۈرۈشنى قوزغات","btn_langs":"تىل","btn_options":"تاللانما","text_title":"شۇئان ئىملا تەكشۈر"},"stylescombo":{"label":"ئۇسلۇب","panelTitle":"ئۇسلۇب","panelTitle1":"بۆلەك دەرىجىسىدىكى ئÛÙ„ÛÙ…Ûنت ئۇسلۇبى","panelTitle2":"ئىچكى باغلانما ئÛÙ„ÛÙ…Ûنت ئۇسلۇبى","panelTitle3":"Ù†Û•Ú­ (Object) ئÛÙ„ÛÙ…Ûنت ئۇسلۇبى"},"table":{"border":"گىرۋەك","caption":"ماۋزۇ","cell":{"menu":"كاتەكچە","insertBefore":"سولغا كاتەكچە قىستۇر","insertAfter":"ئوڭغا كاتەكچە قىستۇر","deleteCell":"كەتەكچە ئۆچۈر","merge":"كاتەكچە بىرلەشتۈر","mergeRight":"كاتەكچىنى ئوڭغا بىرلەشتۈر","mergeDown":"كاتەكچىنى ئاستىغا بىرلەشتۈر","splitHorizontal":"كاتەكچىنى توغرىسىغا بىرلەشتۈر","splitVertical":"كاتەكچىنى بويىغا بىرلەشتۈر","title":"كاتەكچە خاسلىقى","cellType":"كاتەكچە تىپى","rowSpan":"بويىغا چات ئارىسى قۇر سانى","colSpan":"توغرىسىغا چات ئارىسى ئىستون سانى","wordWrap":"ئۆزلۈكىدىن قۇر قاتلا","hAlign":"توغرىسىغا توغرىلا","vAlign":"بويىغا توغرىلا","alignBaseline":"ئاساسىي سىزىق","bgColor":"تەگلىك رەڭگى","borderColor":"گىرۋەك رەڭگى","data":"سانلىق مەلۇمات","header":"جەدۋەل باشى","yes":"ھەئە","no":"ياق","invalidWidth":"كاتەكچە كەڭلىكى چوقۇم سان بولىدۇ","invalidHeight":"كاتەكچە ئÛگىزلىكى چوقۇم سان بولىدۇ","invalidRowSpan":"قۇر چات ئارىسى چوقۇم پۈتۈن سان بولىدۇ ","invalidColSpan":"ئىستون چات ئارىسى چوقۇم پۈتۈن سان بولىدۇ","chooseColor":"تاللاڭ"},"cellPad":"يان ئارىلىق","cellSpace":"ئارىلىق","column":{"menu":"ئىستون","insertBefore":"سولغا ئىستون قىستۇر","insertAfter":"ئوڭغا ئىستون قىستۇر","deleteColumn":"ئىستون ئۆچۈر"},"columns":"ئىستون سانى","deleteTable":"جەدۋەل ئۆچۈر","headers":"ماۋزۇ كاتەكچە","headersBoth":"بىرىنچى ئىستون Û‹Û• بىرىنچى قۇر","headersColumn":"بىرىنچى ئىستون","headersNone":"يوق","headersRow":"بىرىنچى قۇر","invalidBorder":"گىرۋەك توملۇقى چوقۇم سان بولىدۇ","invalidCellPadding":"كاتەكچىگە چوقۇم سان تولدۇرۇلىدۇ","invalidCellSpacing":"كاتەكچە ئارىلىقى چوقۇم سان بولىدۇ","invalidCols":"بەلگىلەنگەن قۇر سانى چوقۇم نۆلدىن Ú†ÙˆÚ­ بولىدۇ","invalidHeight":"جەدۋەل ئÛگىزلىكى چوقۇم سان بولىدۇ","invalidRows":"بەلگىلەنگەن ئىستون سانى چوقۇم نۆلدىن Ú†ÙˆÚ­ بولىدۇ","invalidWidth":"جەدۋەل كەڭلىكى چوقۇم سان بولىدۇ","menu":"جەدۋەل خاسلىقى","row":{"menu":"قۇر","insertBefore":"ئۈستىگە قۇر قىستۇر","insertAfter":"ئاستىغا قۇر قىستۇر","deleteRow":"قۇر ئۆچۈر"},"rows":"قۇر سانى","summary":"ئۈزۈندە","title":"جەدۋەل خاسلىقى","toolbar":"جەدۋەل","widthPc":"پىرسەنت","widthPx":"پىكسÛÙ„","widthUnit":"كەڭلىك بىرلىكى"},"undo":{"redo":"قايتىلا ","undo":"ÙŠÛنىۋال"},"wsc":{"btnIgnore":"پەرۋا قىلما","btnIgnoreAll":"ھەممىگە پەرۋا قىلما","btnReplace":"ئالماشتۇر","btnReplaceAll":"ھەممىنى ئالماشتۇر","btnUndo":"ÙŠÛنىۋال","changeTo":"ئۆزگەرت","errorLoading":"لازىملىق مۇلازىمÛتىرنى يۈكلىگەندە خاتالىق كۆرۈلدى: %s.","ieSpellDownload":"ئىملا تەكشۈرۈش قىستۇرمىسى تÛخى ئورنىتىلمىغان، ھازىرلا چۈشۈرەمسىز؟","manyChanges":"ئىملا تەكشۈرۈش تامام: %1 سۆزنى ئۆزگەرتتى","noChanges":"ئىملا تەكشۈرۈش تامام: Ú¾Ûچقانداق سۆزنى ئۆزگەرتمىدى","noMispell":"ئىملا تەكشۈرۈش تامام: ئىملا خاتالىقى بايقالمىدى","noSuggestions":"-تەكلىپ يوق-","notAvailable":"كەچۈرۈڭ، مۇلازىمÛتىرنى ۋاقتىنچە ئىشلەتكىلى بولمايدۇ","notInDic":"لۇغەتتە يوق","oneChange":"ئىملا تەكشۈرۈش تامام: بىر سۆزنى ئۆزگەرتتى","progress":"ئىملا تەكشۈرۈۋاتىدۇ…","title":"ئىملا تەكشۈر","toolbar":"ئىملا تەكشۈر"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/uk.js b/js/ckeditor/lang/uk.js
new file mode 100644
index 0000000..3c4bd88
--- /dev/null
+++ b/js/ckeditor/lang/uk.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['uk']={"editor":"ТекÑтовий редактор","editorPanel":"Панель текÑтового редактора","common":{"editorHelp":"натиÑніть ALT 0 Ð´Ð»Ñ Ð´Ð¾Ð²Ñ–Ð´ÐºÐ¸","browseServer":"ОглÑд Сервера","url":"URL","protocol":"Протокол","upload":"ÐадіÑлати","uploadSubmit":"ÐадіÑлати на Ñервер","image":"ЗображеннÑ","flash":"Flash","form":"Форма","checkbox":"Галочка","radio":"Кнопка вибору","textField":"ТекÑтове поле","textarea":"ТекÑтова облаÑÑ‚ÑŒ","hiddenField":"Приховане поле","button":"Кнопка","select":"СпиÑок","imageButton":"Кнопка із зображеннÑм","notSet":"<не визначено>","id":"Ідентифікатор","name":"Ім'Ñ","langDir":"ÐапрÑмок мови","langDirLtr":"Зліва направо (LTR)","langDirRtl":"Справа наліво (RTL)","langCode":"Код мови","longDescr":"Довгий Ð¾Ð¿Ð¸Ñ URL","cssClass":"ÐšÐ»Ð°Ñ CSS","advisoryTitle":"Заголовок","cssStyle":"Стиль CSS","ok":"ОК","cancel":"СкаÑувати","close":"Закрити","preview":"Попередній переглÑд","resize":"ПотÑгніть Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½Ð¸ розмірів","generalTab":"ОÑновне","advancedTab":"Додаткове","validateNumberFailed":"Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ Ñ” цілим чиÑлом.","confirmNewPage":"Ð’ÑÑ– незбережені зміни будуть втрачені. Ви впевнені, що хочете завантажити нову Ñторінку?","confirmCancel":"ДеÑкі опції змінено. Закрити вікно без Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½?","options":"Опції","target":"Ціль","targetNew":"Ðове вікно (_blank)","targetTop":"Поточне вікно (_top)","targetSelf":"Поточний фрейм/вікно (_self)","targetParent":"БатьківÑький фрейм/вікно (_parent)","langDirLTR":"Зліва направо (LTR)","langDirRTL":"Справа наліво (RTL)","styles":"Стиль CSS","cssClasses":"ÐšÐ»Ð°Ñ CSS","width":"Ширина","height":"ВиÑота","align":"ВирівнюваннÑ","alignLeft":"По лівому краю","alignRight":"По правому краю","alignCenter":"По центру","alignJustify":"По ширині","alignTop":"По верхньому краю","alignMiddle":"По Ñередині","alignBottom":"По нижньому краю","alignNone":"Ðема","invalidValue":"Ðевірне значеннÑ.","invalidHeight":"ВиÑота повинна бути цілим чиÑлом.","invalidWidth":"Ширина повинна бути цілим чиÑлом.","invalidCssLength":"ЗначеннÑ, вказане Ð´Ð»Ñ \"%1\" в полі повинно бути позитивним чиÑлом або без дійÑного виміру CSS блоку (px, %, in, cm, mm, em, ex, pt або pc).","invalidHtmlLength":"ЗначеннÑ, вказане Ð´Ð»Ñ \"%1\" в полі повинно бути позитивним чиÑлом або без дійÑного виміру HTML блоку (px або %).","invalidInlineStyle":"ЗначеннÑ, вказане Ð´Ð»Ñ Ð²Ð±ÑƒÐ´Ð¾Ð²Ð°Ð½Ð¾Ð³Ð¾ Ñтилю повинне ÑкладатиÑÑ Ð· одного чи кількох кортежів у форматі \"ім'Ñ : значеннÑ\", розділених крапкою з комою.","cssLengthTooltip":"Введіть номер Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð² пікÑелÑÑ… або чиÑло з дійÑною одиниці CSS (px, %, in, cm, mm, em, ex, pt або pc).","unavailable":"%1<span class=\"cke_accessibility\">, не доÑтупне</span>"},"about":{"copy":"Copyright &copy; $1. Ð’ÑÑ– права заÑтережено.","dlgTitle":"Про CKEditor","help":"Перевірте $1 Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð¼Ð¾Ð³Ð¸.","moreInfo":"Щодо інформації з Ð»Ñ–Ñ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ñ–Ñ‚Ð°Ð¹Ñ‚Ðµ на наш Ñайт:","title":"Про CKEditor","userGuide":"ІнÑÑ‚Ñ€ÑƒÐºÑ†Ñ–Ñ ÐšÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ CKEditor"},"basicstyles":{"bold":"Жирний","italic":"КурÑив","strike":"ЗакреÑлений","subscript":"Ðижній індекÑ","superscript":"Верхній індекÑ","underline":"ПідкреÑлений"},"blockquote":{"toolbar":"Цитата"},"clipboard":{"copy":"Копіювати","copyError":"ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸ Вашого браузера не дозволÑÑŽÑ‚ÑŒ редактору автоматично виконувати операції копіюваннÑ. Будь лаÑка, викориÑтовуйте клавіатуру Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ (Ctrl/Cmd+C).","cut":"Вирізати","cutError":"ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸ Вашого браузера не дозволÑÑŽÑ‚ÑŒ редактору автоматично виконувати операції вирізуваннÑ. Будь лаÑка, викориÑтовуйте клавіатуру Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ (Ctrl/Cmd+X)","paste":"Ð’Ñтавити","pasteArea":"ОблаÑÑ‚ÑŒ вÑтавки","pasteMsg":"Будь лаÑка, вÑтавте інформацію з буфера обміну в цю облаÑÑ‚ÑŒ, кориÑтуючиÑÑŒ комбінацією клавіш (<STRONG>Ctrl/Cmd+V</STRONG>), та натиÑніть <STRONG>OK</STRONG>.","securityMsg":"Редактор не може отримати прÑмий доÑтуп до буферу обміну у зв'Ñзку з налаштуваннÑми Вашого браузера. Вам потрібно вÑтавити інформацію в це вікно.","title":"Ð’Ñтавити"},"contextmenu":{"options":"Опції контекÑтного меню"},"button":{"selectedLabel":"%1 (Вибрано)"},"toolbar":{"toolbarCollapse":"Згорнути панель інÑтрументів","toolbarExpand":"Розгорнути панель інÑтрументів","toolbarGroups":{"document":"Документ","clipboard":"Буфер обміну / СкаÑувати","editing":"РедагуваннÑ","forms":"Форми","basicstyles":"ОÑновний Стиль","paragraph":"Параграф","links":"ПоÑиланнÑ","insert":"Ð’Ñтавити","styles":"Стилі","colors":"Кольори","tools":"ІнÑтрументи"},"toolbars":"Панель інÑтрументів редактора"},"elementspath":{"eleLabel":"ШлÑÑ…","eleTitle":"%1 елемент"},"format":{"label":"ФорматуваннÑ","panelTitle":"Ð¤Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð°Ð³Ñ€Ð°Ñ„Ð°","tag_address":"ÐдреÑа","tag_div":"Ðормальний (div)","tag_h1":"Заголовок 1","tag_h2":"Заголовок 2","tag_h3":"Заголовок 3","tag_h4":"Заголовок 4","tag_h5":"Заголовок 5","tag_h6":"Заголовок 6","tag_p":"Ðормальний","tag_pre":"Форматований"},"horizontalrule":{"toolbar":"Горизонтальна лініÑ"},"image":{"alertUrl":"Будь лаÑка, вкажіть URL зображеннÑ","alt":"Ðльтернативний текÑÑ‚","border":"Рамка","btnUpload":"ÐадіÑлати на Ñервер","button2Img":"Бажаєте перетворити обрану кнопку-Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ð° проÑте зображеннÑ?","hSpace":"Гориз. відÑтуп","img2Button":"Бажаєте перетворити обране Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ð° кнопку-зображеннÑ?","infoTab":"Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ зображеннÑ","linkTab":"ПоÑиланнÑ","lockRatio":"Зберегти пропорції","menu":"ВлаÑтивоÑÑ‚Ñ– зображеннÑ","resetSize":"ОчиÑтити Ð¿Ð¾Ð»Ñ Ñ€Ð¾Ð·Ð¼Ñ–Ñ€Ñ–Ð²","title":"ВлаÑтивоÑÑ‚Ñ– зображеннÑ","titleButton":"ВлаÑтивоÑÑ‚Ñ– кнопки із зображеннÑм","upload":"ÐадіÑлати","urlMissing":"Вкажіть URL зображеннÑ.","vSpace":"Верт. відÑтуп","validateBorder":"Ширина рамки повинна бути цілим чиÑлом.","validateHSpace":"Гориз. відÑтуп повинен бути цілим чиÑлом.","validateVSpace":"Верт. відÑтуп повинен бути цілим чиÑлом."},"indent":{"indent":"Збільшити відÑтуп","outdent":"Зменшити відÑтуп"},"fakeobjects":{"anchor":"Якір","flash":"Flash-анімаціÑ","hiddenfield":"Приховані ПолÑ","iframe":"IFrame","unknown":"Ðевідомий об'єкт"},"link":{"acccessKey":"ГарÑча клавіша","advanced":"Додаткове","advisoryContentType":"Тип вміÑту","advisoryTitle":"Заголовок","anchor":{"toolbar":"Ð’Ñтавити/Редагувати Ñкір","menu":"ВлаÑтивоÑÑ‚Ñ– ÑкорÑ","title":"ВлаÑтивоÑÑ‚Ñ– ÑкорÑ","name":"Ім'Ñ ÑкорÑ","errorName":"Будь лаÑка, вкажіть ім'Ñ ÑкорÑ","remove":"Прибрати Ñкір"},"anchorId":"За ідентифікатором елементу","anchorName":"За ім'Ñм елементу","charset":"КодуваннÑ","cssClasses":"ÐšÐ»Ð°Ñ CSS","emailAddress":"ÐдреÑа ел. пошти","emailBody":"Тіло повідомленнÑ","emailSubject":"Тема лиÑта","id":"Ідентифікатор","info":"Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ð¾ÑиланнÑ","langCode":"Код мови","langDir":"ÐапрÑмок мови","langDirLTR":"Зліва направо (LTR)","langDirRTL":"Справа наліво (RTL)","menu":"Ð’Ñтавити поÑиланнÑ","name":"Ім'Ñ","noAnchors":"(Ð’ цьому документі немає Ñкорів)","noEmail":"Будь лаÑка, вкажіть Ð°Ð´Ñ€ÐµÑ ÐµÐ». пошти","noUrl":"Будь лаÑка, вкажіть URL поÑиланнÑ","other":"<інший>","popupDependent":"Залежний (Netscape)","popupFeatures":"ВлаÑтивоÑÑ‚Ñ– випливаючого вікна","popupFullScreen":"Повний екран (IE)","popupLeft":"ÐŸÐ¾Ð·Ð¸Ñ†Ñ–Ñ Ð·Ð»Ñ–Ð²Ð°","popupLocationBar":"Панель локації","popupMenuBar":"Панель меню","popupResizable":"МаÑштабоване","popupScrollBars":"Стрічки прокрутки","popupStatusBar":"РÑдок ÑтатуÑу","popupToolbar":"Панель інÑтрументів","popupTop":"ÐŸÐ¾Ð·Ð¸Ñ†Ñ–Ñ Ð·Ð²ÐµÑ€Ñ…Ñƒ","rel":"Зв'Ñзок","selectAnchor":"Оберіть Ñкір","styles":"Стиль CSS","tabIndex":"ПоÑлідовніÑÑ‚ÑŒ переходу","target":"Ціль","targetFrame":"<фрейм>","targetFrameName":"Ім'Ñ Ñ†Ñ–Ð»ÑŒÐ¾Ð²Ð¾Ð³Ð¾ фрейму","targetPopup":"<випливаюче вікно>","targetPopupName":"Ім'Ñ Ð²Ð¸Ð¿Ð»Ð¸Ð²Ð°ÑŽÑ‡Ð¾Ð³Ð¾ вікна","title":"ПоÑиланнÑ","toAnchor":"Якір на цю Ñторінку","toEmail":"Ел. пошта","toUrl":"URL","toolbar":"Ð’Ñтавити/Редагувати поÑиланнÑ","type":"Тип поÑиланнÑ","unlink":"Видалити поÑиланнÑ","upload":"ÐадіÑлати"},"list":{"bulletedlist":"Маркірований ÑпиÑок","numberedlist":"Ðумерований ÑпиÑок"},"magicline":{"title":"Ð’Ñтавити абзац"},"maximize":{"maximize":"МакÑимізувати","minimize":"Мінімізувати"},"pastetext":{"button":"Ð’Ñтавити тільки текÑÑ‚","title":"Ð’Ñтавити тільки текÑÑ‚"},"pastefromword":{"confirmCleanup":"ТекÑÑ‚, що Ви намагаєтеÑÑŒ вÑтавити, Ñхожий на Ñкопійований з Word. Бажаєте очиÑтити його Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ вÑтавлÑннÑм?","error":"Ðеможливо очиÑтити Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· внутрішню помилку.","title":"Ð’Ñтавити з Word","toolbar":"Ð’Ñтавити з Word"},"removeformat":{"toolbar":"ОчиÑтити форматуваннÑ"},"sourcearea":{"toolbar":"Джерело"},"specialchar":{"options":"Опції","title":"Оберіть Ñпеціальний Ñимвол","toolbar":"Спеціальний Ñимвол"},"scayt":{"btn_about":"Про SCAYT","btn_dictionaries":"Словники","btn_disable":"Вимкнути SCAYT","btn_enable":"Ввімкнути SCAYT","btn_langs":"Мови","btn_options":"Опції","text_title":"Перефірка орфографії по мірі набору"},"stylescombo":{"label":"Стиль","panelTitle":"Стилі форматуваннÑ","panelTitle1":"Блочні Ñтилі","panelTitle2":"РÑдкові Ñтилі","panelTitle3":"Об'єктні Ñтилі"},"table":{"border":"Розмір рамки","caption":"Заголовок таблиці","cell":{"menu":"Комірки","insertBefore":"Ð’Ñтавити комірку перед","insertAfter":"Ð’Ñтавити комірку піÑлÑ","deleteCell":"Видалити комірки","merge":"Об'єднати комірки","mergeRight":"Об'єднати Ñправа","mergeDown":"Об'єднати донизу","splitHorizontal":"Розділити комірку по горизонталі","splitVertical":"Розділити комірку по вертикалі","title":"ВлаÑтивоÑÑ‚Ñ– комірки","cellType":"Тип комірки","rowSpan":"Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñ€Ñдків","colSpan":"Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñтовпців","wordWrap":"ÐвтоперенеÑÐµÐ½Ð½Ñ Ñ‚ÐµÐºÑту","hAlign":"Гориз. вирівнюваннÑ","vAlign":"Верт. вирівнюваннÑ","alignBaseline":"По базовій лінії","bgColor":"Колір фону","borderColor":"Колір рамки","data":"Дані","header":"Заголовок","yes":"Так","no":"ÐÑ–","invalidWidth":"Ширина комірки повинна бути цілим чиÑлом.","invalidHeight":"ВиÑота комірки повинна бути цілим чиÑлом.","invalidRowSpan":"КількіÑÑ‚ÑŒ об'єднуваних Ñ€Ñдків повинна бути цілим чиÑлом.","invalidColSpan":"КількіÑÑ‚ÑŒ об'єднуваних Ñтовбців повинна бути цілим чиÑлом.","chooseColor":"Обрати"},"cellPad":"Внутр. відÑтуп","cellSpace":"Проміжок","column":{"menu":"Стовбці","insertBefore":"Ð’Ñтавити Ñтовбець перед","insertAfter":"Ð’Ñтавити Ñтовбець піÑлÑ","deleteColumn":"Видалити Ñтовбці"},"columns":"Стовбці","deleteTable":"Видалити таблицю","headers":"Заголовки Ñтовбців/Ñ€Ñдків","headersBoth":"Стовбці Ñ– Ñ€Ñдки","headersColumn":"Стовбці","headersNone":"Без заголовків","headersRow":"РÑдки","invalidBorder":"Розмір рамки повинен бути цілим чиÑлом.","invalidCellPadding":"Внутр. відÑтуп комірки повинен бути цілим чиÑлом.","invalidCellSpacing":"Проміжок між комірками повинен бути цілим чиÑлом.","invalidCols":"КількіÑÑ‚ÑŒ Ñтовбців повинна бути більшою 0.","invalidHeight":"ВиÑота таблиці повинна бути цілим чиÑлом.","invalidRows":"КількіÑÑ‚ÑŒ Ñ€Ñдків повинна бути більшою 0.","invalidWidth":"Ширина таблиці повинна бути цілим чиÑлом.","menu":"ВлаÑтивоÑÑ‚Ñ– таблиці","row":{"menu":"РÑдки","insertBefore":"Ð’Ñтавити Ñ€Ñдок перед","insertAfter":"Ð’Ñтавити Ñ€Ñдок піÑлÑ","deleteRow":"Видалити Ñ€Ñдки"},"rows":"РÑдки","summary":"Детальний Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÑƒ таблиці","title":"ВлаÑтивоÑÑ‚Ñ– таблиці","toolbar":"ТаблицÑ","widthPc":"відÑотків","widthPx":"пікÑелів","widthUnit":"Одиниці вимір."},"undo":{"redo":"Повторити","undo":"Повернути"},"wsc":{"btnIgnore":"ПропуÑтити","btnIgnoreAll":"ПропуÑтити вÑе","btnReplace":"Замінити","btnReplaceAll":"Замінити вÑе","btnUndo":"Ðазад","changeTo":"Замінити на","errorLoading":"Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ : %s.","ieSpellDownload":"Модуль перевірки орфографії не вÑтановлено. Бажаєте завантажити його зараз?","manyChanges":"Перевірку орфографії завершено: 1% Ñлів(ова) змінено","noChanges":"Перевірку орфографії завершено: жодне Ñлово не змінено","noMispell":"Перевірку орфографії завершено: помилок не знайдено","noSuggestions":"- немає варіантів -","notAvailable":"Вибачте, але ÑÐµÑ€Ð²Ñ–Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– недоÑтупний.","notInDic":"Ðемає в Ñловнику","oneChange":"Перевірку орфографії завершено: змінено одне Ñлово","progress":"ВиконуєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° орфографії...","title":"Перевірка орфографії","toolbar":"Перевірити орфографію"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/vi.js b/js/ckeditor/lang/vi.js
new file mode 100644
index 0000000..5c73358
--- /dev/null
+++ b/js/ckeditor/lang/vi.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['vi']={"editor":"Bá»™ soạn thảo văn bản có định dạng","editorPanel":"Bảng Ä‘iá»u khiển Rich Text Editor","common":{"editorHelp":"Nhấn ALT + 0 để được giúp đỡ","browseServer":"Duyệt máy chủ","url":"URL","protocol":"Giao thức","upload":"Tải lên","uploadSubmit":"Tải lên máy chủ","image":"Hình ảnh","flash":"Flash","form":"Biểu mẫu","checkbox":"Nút kiểm","radio":"Nút chá»n","textField":"TrÆ°á»ng văn bản","textarea":"Vùng văn bản","hiddenField":"TrÆ°á»ng ẩn","button":"Nút","select":"Ô chá»n","imageButton":"Nút hình ảnh","notSet":"<không thiết lập>","id":"Äịnh danh","name":"Tên","langDir":"HÆ°á»›ng ngôn ngữ","langDirLtr":"Trái sang phải (LTR)","langDirRtl":"Phải sang trái (RTL)","langCode":"Mã ngôn ngữ","longDescr":"Mô tả URL","cssClass":"Lá»›p Stylesheet","advisoryTitle":"Nhan Ä‘á» hÆ°á»›ng dẫn","cssStyle":"Kiểu ","ok":"Äồng ý","cancel":"Bá» qua","close":"Äóng","preview":"Xem trÆ°á»›c","resize":"Kéo rê để thay đổi kích cỡ","generalTab":"Tab chung","advancedTab":"Tab mở rá»™ng","validateNumberFailed":"Giá trị này không phải là số.","confirmNewPage":"Má»i thay đổi không được lÆ°u lại, ná»™i dung này sẽ bị mất. Bạn có chắc chắn muốn tải má»™t trang má»›i?","confirmCancel":"Má»™t vài tùy chá»n đã bị thay đổi. Bạn có chắc chắn muốn đóng há»™p thoại?","options":"Tùy chá»n","target":"Äích đến","targetNew":"Cá»­a sổ má»›i (_blank)","targetTop":"Cá»­a sổ trên cùng (_top)","targetSelf":"Tại trang (_self)","targetParent":"Cá»­a sổ cha (_parent)","langDirLTR":"Trái sang phải (LTR)","langDirRTL":"Phải sang trái (RTL)","styles":"Kiểu","cssClasses":"Lá»›p CSS","width":"Chiá»u rá»™ng","height":"Chiá»u cao","align":"Vị trí","alignLeft":"Trái","alignRight":"Phải","alignCenter":"Giữa","alignJustify":"Sắp chữ","alignTop":"Trên","alignMiddle":"Giữa","alignBottom":"DÆ°á»›i","alignNone":"Không","invalidValue":"Giá trị không hợp lệ.","invalidHeight":"Chiá»u cao phải là số nguyên.","invalidWidth":"Chiá»u rá»™ng phải là số nguyên.","invalidCssLength":"Giá trị quy định cho trÆ°á»ng \"%1\" phải là má»™t số dÆ°Æ¡ng có hoặc không có má»™t Ä‘Æ¡n vị Ä‘o CSS hợp lệ (px, %, in, cm, mm, em, ex, pt, hoặc pc).","invalidHtmlLength":"Giá trị quy định cho trÆ°á»ng \"%1\" phải là má»™t số dÆ°Æ¡ng có hoặc không có má»™t Ä‘Æ¡n vị Ä‘o HTML hợp lệ (px hoặc %).","invalidInlineStyle":"Giá trị quy định cho kiểu ná»™i tuyến phải bao gồm má»™t hoặc nhiá»u dữ liệu vá»›i định dạng \"tên:giá trị\", cách nhau bằng dấu chấm phẩy.","cssLengthTooltip":"Nhập má»™t giá trị theo pixel hoặc má»™t số vá»›i má»™t Ä‘Æ¡n vị CSS hợp lệ (px, %, in, cm, mm, em, ex, pt, hoặc pc).","unavailable":"%1<span class=\"cke_accessibility\">, không có</span>"},"about":{"copy":"Bản quyá»n &copy; $1. Giữ toàn quyá»n.","dlgTitle":"Thông tin vá» CKEditor","help":"Kiểm tra $1 để được giúp đỡ.","moreInfo":"Vui lòng ghé thăm trang web của chúng tôi để có thông tin vá» giấy phép:","title":"Thông tin vá» CKEditor","userGuide":"HÆ°á»›ng dẫn sá»­ dụng CKEditor"},"basicstyles":{"bold":"Äậm","italic":"Nghiêng","strike":"Gạch xuyên ngang","subscript":"Chỉ số dÆ°á»›i","superscript":"Chỉ số trên","underline":"Gạch chân"},"blockquote":{"toolbar":"Khối trích dẫn"},"clipboard":{"copy":"Sao chép","copyError":"Các thiết lập bảo mật của trình duyệt không cho phép trình biên tập tá»± Ä‘á»™ng thá»±c thi lệnh sao chép. Hãy sá»­ dụng bàn phím cho lệnh này (Ctrl/Cmd+C).","cut":"Cắt","cutError":"Các thiết lập bảo mật của trình duyệt không cho phép trình biên tập tá»± Ä‘á»™ng thá»±c thi lệnh cắt. Hãy sá»­ dụng bàn phím cho lệnh này (Ctrl/Cmd+X).","paste":"Dán","pasteArea":"Khu vá»±c dán","pasteMsg":"Hãy dán ná»™i dung vào trong khung bên dÆ°á»›i, sá»­ dụng tổ hợp phím (<STRONG>Ctrl/Cmd+V</STRONG>) và nhấn vào nút <STRONG>Äồng ý</STRONG>.","securityMsg":"Do thiết lập bảo mật của trình duyệt nên trình biên tập không thể truy cập trá»±c tiếp vào ná»™i dung đã sao chép. Bạn cần phải dán lại ná»™i dung vào cá»­a sổ này.","title":"Dán"},"contextmenu":{"options":"Tùy chá»n menu bổ xung"},"button":{"selectedLabel":"%1 (Äã chá»n)"},"toolbar":{"toolbarCollapse":"Thu gá»n thanh công cụ","toolbarExpand":"Mở rá»™ng thnah công cụ","toolbarGroups":{"document":"Tài liệu","clipboard":"Clipboard/Undo","editing":"Chỉnh sá»­a","forms":"Bảng biểu","basicstyles":"Kiểu cÆ¡ bản","paragraph":"Äoạn","links":"Liên kết","insert":"Chèn","styles":"Kiểu","colors":"Màu sắc","tools":"Công cụ"},"toolbars":"Thanh công cụ"},"elementspath":{"eleLabel":"Nhãn thành phần","eleTitle":"%1 thành phần"},"format":{"label":"Äịnh dạng","panelTitle":"Äịnh dạng","tag_address":"Address","tag_div":"Bình thÆ°á»ng (DIV)","tag_h1":"Heading 1","tag_h2":"Heading 2","tag_h3":"Heading 3","tag_h4":"Heading 4","tag_h5":"Heading 5","tag_h6":"Heading 6","tag_p":"Bình thÆ°á»ng (P)","tag_pre":"Äã thiết lập"},"horizontalrule":{"toolbar":"Chèn Ä‘Æ°á»ng phân cách ngang"},"image":{"alertUrl":"Hãy Ä‘Æ°a vào Ä‘Æ°á»ng dẫn của ảnh","alt":"Chú thích ảnh","border":"ÄÆ°á»ng viá»n","btnUpload":"Tải lên máy chủ","button2Img":"Bạn có muốn chuyển nút bấm bằng ảnh được chá»n thành ảnh?","hSpace":"Khoảng đệm ngang","img2Button":"Bạn có muốn chuyển đổi ảnh được chá»n thành nút bấm bằng ảnh?","infoTab":"Thông tin của ảnh","linkTab":"Tab liên kết","lockRatio":"Giữ nguyên tá»· lệ","menu":"Thuá»™c tính của ảnh","resetSize":"Kích thÆ°á»›c gốc","title":"Thuá»™c tính của ảnh","titleButton":"Thuá»™c tính nút của ảnh","upload":"Tải lên","urlMissing":"Thiếu Ä‘Æ°á»ng dẫn hình ảnh","vSpace":"Khoảng đệm dá»c","validateBorder":"Chiá»u rá»™ng của Ä‘Æ°á»ng viá»n phải là má»™t số nguyên dÆ°Æ¡ng","validateHSpace":"Khoảng đệm ngang phải là má»™t số nguyên dÆ°Æ¡ng","validateVSpace":"Khoảng đệm dá»c phải là má»™t số nguyên dÆ°Æ¡ng"},"indent":{"indent":"Dịch vào trong","outdent":"Dịch ra ngoài"},"fakeobjects":{"anchor":"Äiểm neo","flash":"Flash","hiddenfield":"TrÆ°á»ng ẩn","iframe":"IFrame","unknown":"Äối tượng không rõ ràng"},"link":{"acccessKey":"Phím há»— trợ truy cập","advanced":"Mở rá»™ng","advisoryContentType":"Ná»™i dung hÆ°á»›ng dẫn","advisoryTitle":"Nhan Ä‘á» hÆ°á»›ng dẫn","anchor":{"toolbar":"Chèn/Sá»­a Ä‘iểm neo","menu":"Thuá»™c tính Ä‘iểm neo","title":"Thuá»™c tính Ä‘iểm neo","name":"Tên của Ä‘iểm neo","errorName":"Hãy nhập vào tên của Ä‘iểm neo","remove":"Xóa neo"},"anchorId":"Theo định danh thành phần","anchorName":"Theo tên Ä‘iểm neo","charset":"Bảng mã của tài nguyên được liên kết đến","cssClasses":"Lá»›p Stylesheet","emailAddress":"ThÆ° Ä‘iện tá»­","emailBody":"Ná»™i dung thông Ä‘iệp","emailSubject":"Tiêu Ä‘á» thông Ä‘iệp","id":"Äịnh danh","info":"Thông tin liên kết","langCode":"Mã ngôn ngữ","langDir":"HÆ°á»›ng ngôn ngữ","langDirLTR":"Trái sang phải (LTR)","langDirRTL":"Phải sang trái (RTL)","menu":"Sá»­a liên kết","name":"Tên","noAnchors":"(Không có Ä‘iểm neo nào trong tài liệu)","noEmail":"Hãy Ä‘Æ°a vào địa chỉ thÆ° Ä‘iện tá»­","noUrl":"Hãy Ä‘Æ°a vào Ä‘Æ°á»ng dẫn liên kết (URL)","other":"<khác>","popupDependent":"Phụ thuá»™c (Netscape)","popupFeatures":"Äặc Ä‘iểm của cá»­a sổ Popup","popupFullScreen":"Toàn màn hình (IE)","popupLeft":"Vị trí bên trái","popupLocationBar":"Thanh vị trí","popupMenuBar":"Thanh Menu","popupResizable":"Có thể thay đổi kích cỡ","popupScrollBars":"Thanh cuá»™n","popupStatusBar":"Thanh trạng thái","popupToolbar":"Thanh công cụ","popupTop":"Vị trí phía trên","rel":"Quan hệ","selectAnchor":"Chá»n má»™t Ä‘iểm neo","styles":"Kiểu (style)","tabIndex":"Chỉ số của Tab","target":"Äích","targetFrame":"<khung>","targetFrameName":"Tên khung đích","targetPopup":"<cá»­a sổ popup>","targetPopupName":"Tên cá»­a sổ Popup","title":"Liên kết","toAnchor":"Neo trong trang này","toEmail":"ThÆ° Ä‘iện tá»­","toUrl":"URL","toolbar":"Chèn/Sá»­a liên kết","type":"Kiểu liên kết","unlink":"Xoá liên kết","upload":"Tải lên"},"list":{"bulletedlist":"Chèn/Xoá Danh sách không thứ tá»±","numberedlist":"Chèn/Xoá Danh sách có thứ tá»±"},"magicline":{"title":"Chèn Ä‘oạn vào đây"},"maximize":{"maximize":"Phóng to tối Ä‘a","minimize":"Thu nhá»"},"pastetext":{"button":"Dán theo định dạng văn bản thuần","title":"Dán theo định dạng văn bản thuần"},"pastefromword":{"confirmCleanup":"Văn bản bạn muốn dán có kèm định dạng của Word. Bạn có muốn loại bỠđịnh dạng Word trÆ°á»›c khi dán?","error":"Không thể để làm sạch các dữ liệu dán do má»™t lá»—i ná»™i bá»™","title":"Dán vá»›i định dạng Word","toolbar":"Dán vá»›i định dạng Word"},"removeformat":{"toolbar":"Xoá định dạng"},"sourcearea":{"toolbar":"Mã HTML"},"specialchar":{"options":"Tùy chá»n các ký tá»± đặc biệt","title":"Hãy chá»n ký tá»± đặc biệt","toolbar":"Chèn ký tá»± đặc biệt"},"scayt":{"btn_about":"Thông tin vá» SCAYT","btn_dictionaries":"Từ Ä‘iển","btn_disable":"Tắt SCAYT","btn_enable":"Bật SCAYT","btn_langs":"Ngôn ngữ","btn_options":"Tùy chá»n","text_title":"Kiểm tra chính tả ngay khi gõ chữ (SCAYT)"},"stylescombo":{"label":"Kiểu","panelTitle":"Phong cách định dạng","panelTitle1":"Kiểu khối","panelTitle2":"Kiểu trá»±c tiếp","panelTitle3":"Kiểu đối tượng"},"table":{"border":"Kích thÆ°á»›c Ä‘Æ°á»ng viá»n","caption":"Äầu Ä‘á»","cell":{"menu":"Ô","insertBefore":"Chèn ô Phía trÆ°á»›c","insertAfter":"Chèn ô Phía sau","deleteCell":"Xoá ô","merge":"Kết hợp ô","mergeRight":"Kết hợp sang phải","mergeDown":"Kết hợp xuống dÆ°á»›i","splitHorizontal":"Phân tách ô theo chiá»u ngang","splitVertical":"Phân tách ô theo chiá»u dá»c","title":"Thuá»™c tính của ô","cellType":"Kiểu của ô","rowSpan":"Kết hợp hàng","colSpan":"Kết hợp cá»™t","wordWrap":"Chữ liá»n hàng","hAlign":"Canh lá» ngang","vAlign":"Canh lá» dá»c","alignBaseline":"ÄÆ°á»ng cÆ¡ sở","bgColor":"Màu ná»n","borderColor":"Màu viá»n","data":"Dữ liệu","header":"Äầu Ä‘á»","yes":"Có","no":"Không","invalidWidth":"Chiá»u rá»™ng của ô phải là má»™t số nguyên.","invalidHeight":"Chiá»u cao của ô phải là má»™t số nguyên.","invalidRowSpan":"Số hàng kết hợp phải là má»™t số nguyên.","invalidColSpan":"Số cá»™t kết hợp phải là má»™t số nguyên.","chooseColor":"Chá»n màu"},"cellPad":"Khoảng đệm giữ ô và ná»™i dung","cellSpace":"Khoảng cách giữa các ô","column":{"menu":"Cá»™t","insertBefore":"Chèn cá»™t phía trÆ°á»›c","insertAfter":"Chèn cá»™t phía sau","deleteColumn":"Xoá cá»™t"},"columns":"Số cá»™t","deleteTable":"Xóa bảng","headers":"Äầu Ä‘á»","headersBoth":"Cả hai","headersColumn":"Cá»™t đầu tiên","headersNone":"Không có","headersRow":"Hàng đầu tiên","invalidBorder":"Kích cỡ của Ä‘Æ°á»ng biên phải là má»™t số nguyên.","invalidCellPadding":"Khoảng đệm giữa ô và ná»™i dung phải là má»™t số nguyên.","invalidCellSpacing":"Khoảng cách giữa các ô phải là má»™t số nguyên.","invalidCols":"Số lượng cá»™t phải là má»™t số lá»›n hÆ¡n 0.","invalidHeight":"Chiá»u cao của bảng phải là má»™t số nguyên.","invalidRows":"Số lượng hàng phải là má»™t số lá»›n hÆ¡n 0.","invalidWidth":"Chiá»u rá»™ng của bảng phải là má»™t số nguyên.","menu":"Thuá»™c tính bảng","row":{"menu":"Hàng","insertBefore":"Chèn hàng phía trÆ°á»›c","insertAfter":"Chèn hàng phía sau","deleteRow":"Xoá hàng"},"rows":"Số hàng","summary":"Tóm lược","title":"Thuá»™c tính bảng","toolbar":"Bảng","widthPc":"Phần trăm (%)","widthPx":"Äiểm ảnh (px)","widthUnit":"ÄÆ¡n vị"},"undo":{"redo":"Làm lại thao tác","undo":"Khôi phục thao tác"},"wsc":{"btnIgnore":"Bá» qua","btnIgnoreAll":"Bá» qua tất cả","btnReplace":"Thay thế","btnReplaceAll":"Thay thế tất cả","btnUndo":"Phục hồi lại","changeTo":"Chuyển thành","errorLoading":"Lá»—i khi Ä‘ang nạp dịch vụ ứng dụng: %s.","ieSpellDownload":"Chức năng kiểm tra chính tả chÆ°a được cài đặt. Bạn có muốn tải vá» ngay bây giá»?","manyChanges":"Hoàn tất kiểm tra chính tả: %1 từ đã được thay đổi","noChanges":"Hoàn tất kiểm tra chính tả: Không có từ nào được thay đổi","noMispell":"Hoàn tất kiểm tra chính tả: Không có lá»—i chính tả","noSuggestions":"- Không Ä‘Æ°a ra gợi ý vá» từ -","notAvailable":"Xin lá»—i, dịch vụ này hiện tại không có.","notInDic":"Không có trong từ Ä‘iển","oneChange":"Hoàn tất kiểm tra chính tả: Má»™t từ đã được thay đổi","progress":"Äang tiến hành kiểm tra chính tả...","title":"Kiểm tra chính tả","toolbar":"Kiểm tra chính tả"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/zh-cn.js b/js/ckeditor/lang/zh-cn.js
new file mode 100644
index 0000000..12809e1
--- /dev/null
+++ b/js/ckeditor/lang/zh-cn.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['zh-cn']={"editor":"所è§å³æ‰€å¾—编辑器","editorPanel":"所è§å³æ‰€å¾—编辑器é¢æ¿","common":{"editorHelp":"按 ALT+0 获得帮助","browseServer":"æµè§ˆæœåŠ¡å™¨","url":"URL","protocol":"åè®®","upload":"上传","uploadSubmit":"上传到æœåŠ¡å™¨","image":"图åƒ","flash":"Flash","form":"表å•","checkbox":"å¤é€‰æ¡†","radio":"å•é€‰æŒ‰é’®","textField":"å•è¡Œæ–‡æœ¬","textarea":"多行文本","hiddenField":"éšè—域","button":"按钮","select":"列表/èœå•","imageButton":"图åƒæŒ‰é’®","notSet":"<没有设置>","id":"ID","name":"å称","langDir":"语言方å‘","langDirLtr":"ä»Žå·¦åˆ°å³ (LTR)","langDirRtl":"从å³åˆ°å·¦ (RTL)","langCode":"语言代ç ","longDescr":"详细说明 URL","cssClass":"æ ·å¼ç±»å称","advisoryTitle":"标题","cssStyle":"行内样å¼","ok":"确定","cancel":"å–消","close":"关闭","preview":"预览","resize":"拖拽以改å˜å¤§å°","generalTab":"常规","advancedTab":"高级","validateNumberFailed":"需è¦è¾“入数字格å¼","confirmNewPage":"当å‰æ–‡æ¡£å†…容未ä¿å­˜ï¼Œæ˜¯å¦ç¡®è®¤æ–°å»ºæ–‡æ¡£ï¼Ÿ","confirmCancel":"部分修改尚未ä¿å­˜ï¼Œæ˜¯å¦ç¡®è®¤å…³é—­å¯¹è¯æ¡†ï¼Ÿ","options":"选项","target":"目标窗å£","targetNew":"æ–°çª—å£ (_blank)","targetTop":"整页 (_top)","targetSelf":"æœ¬çª—å£ (_self)","targetParent":"çˆ¶çª—å£ (_parent)","langDirLTR":"ä»Žå·¦åˆ°å³ (LTR)","langDirRTL":"从å³åˆ°å·¦ (RTL)","styles":"æ ·å¼","cssClasses":"æ ·å¼ç±»","width":"宽度","height":"高度","align":"对é½æ–¹å¼","alignLeft":"左对é½","alignRight":"å³å¯¹é½","alignCenter":"居中","alignJustify":"两端对é½","alignTop":"顶端","alignMiddle":"居中","alignBottom":"底部","alignNone":"æ— ","invalidValue":"无效的值。","invalidHeight":"高度必须为数字格å¼","invalidWidth":"宽度必须为数字格å¼","invalidCssLength":"此“%1â€å­—段的值必须为正数,å¯ä»¥åŒ…å«æˆ–ä¸åŒ…å«ä¸€ä¸ªæœ‰æ•ˆçš„ CSS 长度å•ä½(px, %, in, cm, mm, em, ex, pt 或 pc)","invalidHtmlLength":"此“%1â€å­—段的值必须为正数,å¯ä»¥åŒ…å«æˆ–ä¸åŒ…å«ä¸€ä¸ªæœ‰æ•ˆçš„ HTML 长度å•ä½(px 或 %)","invalidInlineStyle":"内è”æ ·å¼å¿…须为格å¼æ˜¯ä»¥åˆ†å·åˆ†éš”的一个或多个“属性å : 属性值â€ã€‚","cssLengthTooltip":"输入一个表示åƒç´ å€¼çš„数字,或加上一个有效的 CSS 长度å•ä½(px, %, in, cm, mm, em, ex, pt 或 pc)。","unavailable":"%1<span class=\"cke_accessibility\">,ä¸å¯ç”¨</span>"},"about":{"copy":"版æƒæ‰€æœ‰ &copy; $1。<br />ä¿ç•™æ‰€æœ‰æƒåˆ©ã€‚","dlgTitle":"关于 CKEditor","help":"访问 $1 以获å–帮助。","moreInfo":"相关授æƒè®¸å¯ä¿¡æ¯è¯·è®¿é—®æˆ‘们的网站:","title":"关于 CKEditor","userGuide":"CKEditor 用户å‘导"},"basicstyles":{"bold":"加粗","italic":"倾斜","strike":"删除线","subscript":"下标","superscript":"上标","underline":"下划线"},"blockquote":{"toolbar":"å—引用"},"clipboard":{"copy":"å¤åˆ¶","copyError":"您的æµè§ˆå™¨å®‰å…¨è®¾ç½®ä¸å…许编辑器自动执行å¤åˆ¶æ“作,请使用键盘快æ·é”®(Ctrl/Cmd+C)æ¥å®Œæˆã€‚","cut":"剪切","cutError":"您的æµè§ˆå™¨å®‰å…¨è®¾ç½®ä¸å…许编辑器自动执行剪切æ“作,请使用键盘快æ·é”®(Ctrl/Cmd+X)æ¥å®Œæˆã€‚","paste":"粘贴","pasteArea":"粘贴区域","pasteMsg":"请使用键盘快æ·é”®(<STRONG>Ctrl/Cmd+V</STRONG>)把内容粘贴到下é¢çš„方框里,å†æŒ‰ <STRONG>确定</STRONG>","securityMsg":"因为您的æµè§ˆå™¨çš„安全设置原因,本编辑器ä¸èƒ½ç›´æŽ¥è®¿é—®æ‚¨çš„剪贴æ¿å†…容,你需è¦åœ¨æœ¬çª—å£é‡æ–°ç²˜è´´ä¸€æ¬¡ã€‚","title":"粘贴"},"contextmenu":{"options":"å¿«æ·èœå•é€‰é¡¹"},"button":{"selectedLabel":"已选中 %1 项"},"toolbar":{"toolbarCollapse":"折å å·¥å…·æ ","toolbarExpand":"展开工具æ ","toolbarGroups":{"document":"文档","clipboard":"剪贴æ¿/撤销","editing":"编辑","forms":"表å•","basicstyles":"基本格å¼","paragraph":"段è½","links":"链接","insert":"æ’å…¥","styles":"æ ·å¼","colors":"颜色","tools":"工具"},"toolbars":"工具æ "},"elementspath":{"eleLabel":"元素路径","eleTitle":"%1 元素"},"format":{"label":"æ ¼å¼","panelTitle":"æ ¼å¼","tag_address":"地å€","tag_div":"段è½(DIV)","tag_h1":"标题 1","tag_h2":"标题 2","tag_h3":"标题 3","tag_h4":"标题 4","tag_h5":"标题 5","tag_h6":"标题 6","tag_p":"普通","tag_pre":"已编排格å¼"},"horizontalrule":{"toolbar":"æ’入水平线"},"image":{"alertUrl":"请输入图åƒåœ°å€","alt":"替æ¢æ–‡æœ¬","border":"边框大å°","btnUpload":"上传到æœåŠ¡å™¨","button2Img":"确定è¦æŠŠå½“å‰å›¾åƒæŒ‰é’®è½¬æ¢ä¸ºæ™®é€šå›¾åƒå—?","hSpace":"水平间è·","img2Button":"确定è¦æŠŠå½“å‰å›¾åƒæ”¹å˜ä¸ºå›¾åƒæŒ‰é’®å—?","infoTab":"图åƒä¿¡æ¯","linkTab":"链接","lockRatio":"é”定比例","menu":"图åƒå±žæ€§","resetSize":"原始尺寸","title":"图åƒå±žæ€§","titleButton":"图åƒåŸŸå±žæ€§","upload":"上传","urlMissing":"缺少图åƒæºæ–‡ä»¶åœ°å€","vSpace":"åž‚ç›´é—´è·","validateBorder":"边框大å°å¿…须为整数格å¼","validateHSpace":"水平间è·å¿…须为整数格å¼","validateVSpace":"åž‚ç›´é—´è·å¿…须为整数格å¼"},"indent":{"indent":"增加缩进é‡","outdent":"å‡å°‘缩进é‡"},"fakeobjects":{"anchor":"锚点","flash":"Flash 动画","hiddenfield":"éšè—域","iframe":"IFrame","unknown":"未知对象"},"link":{"acccessKey":"访问键","advanced":"高级","advisoryContentType":"内容类型","advisoryTitle":"标题","anchor":{"toolbar":"æ’å…¥/编辑锚点链接","menu":"锚点链接属性","title":"锚点链接属性","name":"锚点å称","errorName":"请输入锚点å称","remove":"删除锚点"},"anchorId":"按锚点 ID","anchorName":"按锚点å称","charset":"字符编ç ","cssClasses":"æ ·å¼ç±»å称","emailAddress":"地å€","emailBody":"内容","emailSubject":"主题","id":"ID","info":"超链接信æ¯","langCode":"语言代ç ","langDir":"语言方å‘","langDirLTR":"ä»Žå·¦åˆ°å³ (LTR)","langDirRTL":"从å³åˆ°å·¦ (RTL)","menu":"编辑超链接","name":"å称","noAnchors":"(此文档没有å¯ç”¨çš„锚点)","noEmail":"请输入电å­é‚®ä»¶åœ°å€","noUrl":"请输入超链接地å€","other":"<其他>","popupDependent":"ä¾é™„ (NS)","popupFeatures":"弹出窗å£å±žæ€§","popupFullScreen":"å…¨å± (IE)","popupLeft":"å·¦","popupLocationBar":"地å€æ ","popupMenuBar":"èœå•æ ","popupResizable":"å¯ç¼©æ”¾","popupScrollBars":"滚动æ¡","popupStatusBar":"状æ€æ ","popupToolbar":"工具æ ","popupTop":"å³","rel":"å…³è”","selectAnchor":"选择一个锚点","styles":"行内样å¼","tabIndex":"Tab 键次åº","target":"目标","targetFrame":"<框架>","targetFrameName":"目标框架å称","targetPopup":"<弹出窗å£>","targetPopupName":"弹出窗å£å称","title":"超链接","toAnchor":"页内锚点链接","toEmail":"电å­é‚®ä»¶","toUrl":"地å€","toolbar":"æ’å…¥/编辑超链接","type":"超链接类型","unlink":"å–消超链接","upload":"上传"},"list":{"bulletedlist":"项目列表","numberedlist":"ç¼–å·åˆ—表"},"magicline":{"title":"在这æ’入段è½"},"maximize":{"maximize":"å…¨å±","minimize":"最å°åŒ–"},"pastetext":{"button":"粘贴为无格å¼æ–‡æœ¬","title":"粘贴为无格å¼æ–‡æœ¬"},"pastefromword":{"confirmCleanup":"您è¦ç²˜è´´çš„内容好åƒæ˜¯æ¥è‡ª MS Word,是å¦è¦æ¸…除 MS Word æ ¼å¼åŽå†ç²˜è´´ï¼Ÿ","error":"由于内部错误无法清ç†è¦ç²˜è´´çš„æ•°æ®","title":"从 MS Word 粘贴","toolbar":"从 MS Word 粘贴"},"removeformat":{"toolbar":"清除格å¼"},"sourcearea":{"toolbar":"æºç "},"specialchar":{"options":"特殊符å·é€‰é¡¹","title":"选择特殊符å·","toolbar":"æ’入特殊符å·"},"scayt":{"btn_about":"关于å³æ—¶æ‹¼å†™æ£€æŸ¥","btn_dictionaries":"å­—å…¸","btn_disable":"ç¦ç”¨å³æ—¶æ‹¼å†™æ£€æŸ¥","btn_enable":"å¯ç”¨å³æ—¶æ‹¼å†™æ£€æŸ¥","btn_langs":"语言","btn_options":"选项","text_title":"å³æ—¶æ‹¼å†™æ£€æŸ¥"},"stylescombo":{"label":"æ ·å¼","panelTitle":"æ ·å¼","panelTitle1":"å—级元素样å¼","panelTitle2":"内è”元素样å¼","panelTitle3":"对象元素样å¼"},"table":{"border":"边框","caption":"标题","cell":{"menu":"å•å…ƒæ ¼","insertBefore":"在左侧æ’å…¥å•å…ƒæ ¼","insertAfter":"在å³ä¾§æ’å…¥å•å…ƒæ ¼","deleteCell":"删除å•å…ƒæ ¼","merge":"åˆå¹¶å•å…ƒæ ¼","mergeRight":"å‘å³åˆå¹¶å•å…ƒæ ¼","mergeDown":"å‘下åˆå¹¶å•å…ƒæ ¼","splitHorizontal":"水平拆分å•å…ƒæ ¼","splitVertical":"垂直拆分å•å…ƒæ ¼","title":"å•å…ƒæ ¼å±žæ€§","cellType":"å•å…ƒæ ¼ç±»åž‹","rowSpan":"纵跨行数","colSpan":"横跨列数","wordWrap":"自动æ¢è¡Œ","hAlign":"水平对é½","vAlign":"垂直对é½","alignBaseline":"基线","bgColor":"背景颜色","borderColor":"边框颜色","data":"æ•°æ®","header":"表头","yes":"是","no":"å¦","invalidWidth":"å•å…ƒæ ¼å®½åº¦å¿…须为数字格å¼","invalidHeight":"å•å…ƒæ ¼é«˜åº¦å¿…须为数字格å¼","invalidRowSpan":"行跨度必须为整数格å¼","invalidColSpan":"列跨度必须为整数格å¼","chooseColor":"选择"},"cellPad":"è¾¹è·","cellSpace":"é—´è·","column":{"menu":"列","insertBefore":"在左侧æ’入列","insertAfter":"在å³ä¾§æ’入列","deleteColumn":"删除列"},"columns":"列数","deleteTable":"删除表格","headers":"标题å•å…ƒæ ¼","headersBoth":"第一列和第一行","headersColumn":"第一列","headersNone":"æ— ","headersRow":"第一行","invalidBorder":"边框粗细必须为数字格å¼","invalidCellPadding":"å•å…ƒæ ¼å¡«å……必须为数字格å¼","invalidCellSpacing":"å•å…ƒæ ¼é—´è·å¿…须为数字格å¼","invalidCols":"指定的行数必须大于零","invalidHeight":"表格高度必须为数字格å¼","invalidRows":"指定的列数必须大于零","invalidWidth":"表格宽度必须为数字格å¼","menu":"表格属性","row":{"menu":"è¡Œ","insertBefore":"在上方æ’入行","insertAfter":"在下方æ’入行","deleteRow":"删除行"},"rows":"行数","summary":"摘è¦","title":"表格属性","toolbar":"表格","widthPc":"百分比","widthPx":"åƒç´ ","widthUnit":"宽度å•ä½"},"undo":{"redo":"é‡åš","undo":"撤消"},"wsc":{"btnIgnore":"忽略","btnIgnoreAll":"全部忽略","btnReplace":"替æ¢","btnReplaceAll":"全部替æ¢","btnUndo":"撤消","changeTo":"更改为","errorLoading":"加载应该æœåŠ¡ä¸»æœºæ—¶å‡ºé”™: %s.","ieSpellDownload":"拼写检查æ’件还没安装, 您是å¦æƒ³çŽ°åœ¨å°±ä¸‹è½½?","manyChanges":"拼写检查完æˆ: 更改了 %1 个å•è¯","noChanges":"拼写检查完æˆ: 没有更改任何å•è¯","noMispell":"拼写检查完æˆ: 没有å‘现拼写错误","noSuggestions":"- 没有建议 -","notAvailable":"抱歉, æœåŠ¡ç›®å‰æš‚ä¸å¯ç”¨","notInDic":"没有在字典里","oneChange":"拼写检查完æˆ: 更改了一个å•è¯","progress":"正在进行拼写检查...","title":"拼写检查","toolbar":"拼写检查"}}; \ No newline at end of file
diff --git a/js/ckeditor/lang/zh.js b/js/ckeditor/lang/zh.js
new file mode 100644
index 0000000..20ce3d1
--- /dev/null
+++ b/js/ckeditor/lang/zh.js
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.lang['zh']={"editor":"RTF 編輯器","editorPanel":"RTF 編輯器é¢æ¿","common":{"editorHelp":"按下 ALT 0 å–得說明。","browseServer":"ç€è¦½ä¼ºæœå™¨","url":"URL","protocol":"通訊å”定","upload":"上傳","uploadSubmit":"傳é€è‡³ä¼ºæœå™¨","image":"圖åƒ","flash":"Flash","form":"表格","checkbox":"æ ¸å–方塊","radio":"é¸é …按鈕","textField":"文字欄ä½","textarea":"文字å€åŸŸ","hiddenField":"éš±è—欄ä½","button":"按鈕","select":"é¸å–欄ä½","imageButton":"å½±åƒæŒ‰éˆ•","notSet":"<未設定>","id":"ID","name":"å稱","langDir":"語言方å‘","langDirLtr":"ç”±å·¦è‡³å³ (LTR)","langDirRtl":"ç”±å³è‡³å·¦ (RTL)","langCode":"語言代碼","longDescr":"完整æè¿° URL","cssClass":"樣å¼è¡¨é¡žåˆ¥","advisoryTitle":"標題","cssStyle":"樣å¼","ok":"確定","cancel":"å–消","close":"關閉","preview":"é è¦½","resize":"調整大å°","generalTab":"一般","advancedTab":"進階","validateNumberFailed":"此值ä¸æ˜¯æ•¸å€¼ã€‚","confirmNewPage":"ç¾å­˜çš„修改尚未儲存,è¦é–‹æ–°æª”案?","confirmCancel":"部份é¸é …尚未儲存,è¦é—œé–‰å°è©±æ¡†ï¼Ÿ","options":"é¸é …","target":"目標","targetNew":"開新視窗 (_blank)","targetTop":"最上層視窗 (_top)","targetSelf":"相åŒè¦–窗 (_self)","targetParent":"父視窗 (_parent)","langDirLTR":"ç”±å·¦è‡³å³ (LTR)","langDirRTL":"ç”±å³è‡³å·¦ (RTL)","styles":"樣å¼","cssClasses":"樣å¼è¡¨é¡žåˆ¥","width":"寬度","height":"高度","align":"å°é½Šæ–¹å¼","alignLeft":"é å·¦å°é½Š","alignRight":"é å³å°é½Š","alignCenter":"置中å°é½Š","alignJustify":"å·¦å³å°é½Š","alignTop":"頂端","alignMiddle":"中間å°é½Š","alignBottom":"底端","alignNone":"ç„¡","invalidValue":"無效值。","invalidHeight":"高度必須為數字。","invalidWidth":"寬度必須為數字。","invalidCssLength":"「%1ã€çš„值應為正數,並å¯åŒ…å«æœ‰æ•ˆçš„ CSS å–®ä½ (px, %, in, cm, mm, em, ex, pt, 或 pc)。","invalidHtmlLength":"「%1ã€çš„值應為正數,並å¯åŒ…å«æœ‰æ•ˆçš„ HTML å–®ä½ (px 或 %)。","invalidInlineStyle":"行內樣å¼çš„值應包å«ä¸€å€‹ä»¥ä¸Šçš„變數值組,其格å¼å¦‚「å稱:值ã€ï¼Œä¸¦ä»¥åˆ†è™Ÿå€éš”之。","cssLengthTooltip":"請輸入數值,單ä½æ˜¯åƒç´ æˆ–有效的 CSS å–®ä½ (px, %, in, cm, mm, em, ex, pt, 或 pc)。","unavailable":"%1<span class=\"cke_accessibility\">,無法使用</span>"},"about":{"copy":"Copyright &copy; $1. All rights reserved.","dlgTitle":"關於 CKEditor","help":"檢閱 $1 尋求幫助。","moreInfo":"關於授權資訊,請åƒé–±æˆ‘們的網站:","title":"關於 CKEditor","userGuide":"CKEditor 使用者手冊"},"basicstyles":{"bold":"ç²—é«”","italic":"斜體","strike":"刪除線","subscript":"下標","superscript":"上標","underline":"底線"},"blockquote":{"toolbar":"引用段è½"},"clipboard":{"copy":"複製","copyError":"ç€è¦½å™¨çš„安全性設定ä¸å…許編輯器自動執行複製動作。請使用éµç›¤å¿«æ·éµ (Ctrl/Cmd+C) 複製。","cut":"剪下","cutError":"ç€è¦½å™¨çš„安全性設定ä¸å…許編輯器自動執行剪下動作。請使用é盤快æ·éµ (Ctrl/Cmd+X) 剪下。","paste":"貼上","pasteArea":"貼上å€","pasteMsg":"請使用éµç›¤å¿«æ·éµ (<strong>Ctrl/Cmd+V</strong>) 貼到下方å€åŸŸä¸­ä¸¦æŒ‰ä¸‹ã€Œç¢ºå®šã€ã€‚","securityMsg":"因為ç€è¦½å™¨çš„安全性設定,本編輯器無法直接存å–您的剪貼簿資料,請您自行在本視窗進行貼上動作。","title":"貼上"},"contextmenu":{"options":"內容功能表é¸é …"},"button":{"selectedLabel":"%1 (å·²é¸å–)"},"toolbar":{"toolbarCollapse":"摺疊工具列","toolbarExpand":"展開工具列","toolbarGroups":{"document":"文件","clipboard":"剪貼簿/復原","editing":"編輯é¸é …","forms":"æ ¼å¼","basicstyles":"基本樣å¼","paragraph":"段è½","links":"連çµ","insert":"æ’å…¥","styles":"樣å¼","colors":"é¡è‰²","tools":"工具"},"toolbars":"編輯器工具列"},"elementspath":{"eleLabel":"元件路徑","eleTitle":"%1 個元件"},"format":{"label":"æ ¼å¼","panelTitle":"段è½æ ¼å¼","tag_address":"地å€","tag_div":"標準 (DIV)","tag_h1":"標題 1","tag_h2":"標題 2","tag_h3":"標題 3","tag_h4":"標題 4","tag_h5":"標題 5","tag_h6":"標題 6","tag_p":"標準","tag_pre":"æ ¼å¼è¨­å®š"},"horizontalrule":{"toolbar":"æ’入水平線"},"image":{"alertUrl":"請輸入圖片 URL","alt":"替代文字","border":"框線","btnUpload":"傳é€åˆ°ä¼ºæœå™¨","button2Img":"è«‹å•æ‚¨ç¢ºå®šè¦å°‡ã€Œåœ–片按鈕ã€è½‰æ›æˆã€Œåœ–片ã€å—Žï¼Ÿ","hSpace":"HSpace","img2Button":"è«‹å•æ‚¨ç¢ºå®šè¦å°‡ã€Œåœ–片ã€è½‰æ›æˆã€Œåœ–片按鈕ã€å—Žï¼Ÿ","infoTab":"å½±åƒè³‡è¨Š","linkTab":"連çµ","lockRatio":"固定比例","menu":"å½±åƒå±¬æ€§","resetSize":"é‡è¨­å¤§å°","title":"å½±åƒå±¬æ€§","titleButton":"å½±åƒæŒ‰éˆ•å±¬æ€§","upload":"上傳","urlMissing":"éºå¤±åœ–片來æºä¹‹ URL ","vSpace":"VSpace","validateBorder":"框線必須是整數。","validateHSpace":"HSpace 必須是整數。","validateVSpace":"VSpace 必須是整數。"},"indent":{"indent":"增加縮排","outdent":"減少縮排"},"fakeobjects":{"anchor":"錨點","flash":"Flash å‹•ç•«","hiddenfield":"éš±è—欄ä½","iframe":"IFrame","unknown":"無法辨識的物件"},"link":{"acccessKey":"便æ·éµ","advanced":"進階","advisoryContentType":"建議內容類型","advisoryTitle":"標題","anchor":{"toolbar":"錨點","menu":"編輯錨點","title":"錨點內容","name":"錨點å稱","errorName":"請輸入錨點å稱","remove":"移除錨點"},"anchorId":"ä¾å…ƒä»¶ç·¨è™Ÿ","anchorName":"ä¾éŒ¨é»žå稱","charset":"連çµè³‡æºçš„字元集","cssClasses":"樣å¼è¡¨é¡žåˆ¥","emailAddress":"é›»å­éƒµä»¶åœ°å€","emailBody":"郵件本文","emailSubject":"郵件主旨","id":"ID","info":"連çµè³‡è¨Š","langCode":"語言碼","langDir":"語言方å‘","langDirLTR":"ç”±å·¦è‡³å³ (LTR)","langDirRTL":"ç”±å³è‡³å·¦ (RTL)","menu":"編輯連çµ","name":"å稱","noAnchors":"(本文件中無å¯ç”¨ä¹‹éŒ¨é»ž)","noEmail":"請輸入電å­éƒµä»¶","noUrl":"è«‹è¼¸å…¥é€£çµ URL","other":"<其他>","popupDependent":"ç¨ç«‹ (Netscape)","popupFeatures":"快顯視窗功能","popupFullScreen":"全螢幕 (IE)","popupLeft":"å·¦å´ä½ç½®","popupLocationBar":"ä½ç½®åˆ—","popupMenuBar":"功能表列","popupResizable":"å¯èª¿å¤§å°","popupScrollBars":"æ²è»¸","popupStatusBar":"狀態列","popupToolbar":"工具列","popupTop":"頂端ä½ç½®","rel":"關係","selectAnchor":"é¸å–一個錨點","styles":"樣å¼","tabIndex":"定ä½é †åº","target":"目標","targetFrame":"<框架>","targetFrameName":"目標框架å稱","targetPopup":"<快顯視窗>","targetPopupName":"快顯視窗å稱","title":"連çµ","toAnchor":"文字中的錨點連çµ","toEmail":"é›»å­éƒµä»¶","toUrl":"網å€","toolbar":"連çµ","type":"連çµé¡žåž‹","unlink":"å–消連çµ","upload":"上傳"},"list":{"bulletedlist":"æ’å…¥/移除項目符號清單","numberedlist":"æ’å…¥/移除編號清單清單"},"magicline":{"title":"在此æ’入段è½"},"maximize":{"maximize":"最大化","minimize":"最å°åŒ–"},"pastetext":{"button":"è²¼æˆç´”文字","title":"è²¼æˆç´”文字"},"pastefromword":{"confirmCleanup":"您想貼上的文字似乎是自 Word 複製而來,請å•æ‚¨æ˜¯å¦è¦å…ˆæ¸…除 Word çš„æ ¼å¼å¾Œå†è¡Œè²¼ä¸Šï¼Ÿ","error":"由於發生內部錯誤,無法清除清除 Word çš„æ ¼å¼ã€‚","title":"自 Word 貼上","toolbar":"自 Word 貼上"},"removeformat":{"toolbar":"移除格å¼"},"sourcearea":{"toolbar":"原始碼"},"specialchar":{"options":"特殊字元é¸é …","title":"é¸å–特殊字元","toolbar":"æ’入特殊字元"},"scayt":{"btn_about":"關於å³æ™‚拼寫檢查","btn_dictionaries":"å­—å…¸","btn_disable":"關閉å³æ™‚拼寫檢查","btn_enable":"啟用å³æ™‚拼寫檢查","btn_langs":"語言","btn_options":"é¸é …","text_title":"å³æ™‚拼寫檢查"},"stylescombo":{"label":"樣å¼","panelTitle":"Formatting Styles","panelTitle1":"å€å¡Šæ¨£å¼","panelTitle2":"內嵌樣å¼","panelTitle3":"物件樣å¼"},"table":{"border":"框線大å°","caption":"標題","cell":{"menu":"儲存格","insertBefore":"å‰æ–¹æ’入儲存格","insertAfter":"後方æ’入儲存格","deleteCell":"刪除儲存格","merge":"åˆä½µå„²å­˜æ ¼","mergeRight":"å‘å³åˆä½µ","mergeDown":"å‘下åˆä½µ","splitHorizontal":"水平分割儲存格","splitVertical":"垂直分割儲存格","title":"儲存格屬性","cellType":"儲存格類型","rowSpan":"Rows Span","colSpan":"Columns Span","wordWrap":"自動斷行","hAlign":"æ°´å¹³å°é½Š","vAlign":"åž‚ç›´å°é½Š","alignBaseline":"基準線","bgColor":"背景é¡è‰²","borderColor":"框線é¡è‰²","data":"資料","header":"Header","yes":"是","no":"å¦","invalidWidth":"儲存格寬度必須為數字。","invalidHeight":"儲存格高度必須為數字。","invalidRowSpan":"Rows span must be a whole number.","invalidColSpan":"Columns span must be a whole number.","chooseColor":"é¸æ“‡"},"cellPad":"Cell padding","cellSpace":"Cell spacing","column":{"menu":"è¡Œ","insertBefore":"Insert Column Before","insertAfter":"Insert Column After","deleteColumn":"Delete Columns"},"columns":"è¡Œ","deleteTable":"Delete Table","headers":"Headers","headersBoth":"Both","headersColumn":"First column","headersNone":"ç„¡","headersRow":"First Row","invalidBorder":"框線大å°å¿…須是整數。","invalidCellPadding":"Cell padding must be a positive number.","invalidCellSpacing":"Cell spacing must be a positive number.","invalidCols":"行數須為大於 0 的正整數。","invalidHeight":"表格高度必須為數字。","invalidRows":"Number of rows must be a number greater than 0.","invalidWidth":"表格寬度必須為數字。","menu":"表格屬性","row":{"menu":"列","insertBefore":"Insert Row Before","insertAfter":"Insert Row After","deleteRow":"Delete Rows"},"rows":"列","summary":"Summary","title":"表格屬性","toolbar":"表格","widthPc":"百分比","widthPx":"åƒç´ ","widthUnit":"寬度單ä½"},"undo":{"redo":"å–消復原","undo":"復原"},"wsc":{"btnIgnore":"忽略","btnIgnoreAll":"全部忽略","btnReplace":"å–代","btnReplaceAll":"全部å–代","btnUndo":"復原","changeTo":"更改為","errorLoading":"無法è¯ç³»ä¾æœå™¨: %s.","ieSpellDownload":"尚未安è£æ‹¼å­—檢查元件。您是å¦æƒ³è¦ç¾åœ¨ä¸‹è¼‰ï¼Ÿ","manyChanges":"拼字檢查完æˆï¼šæ›´æ”¹äº† %1 個單字","noChanges":"拼字檢查完æˆï¼šæœªæ›´æ”¹ä»»ä½•å–®å­—","noMispell":"拼字檢查完æˆï¼šæœªç™¼ç¾æ‹¼å­—錯誤","noSuggestions":"- 無建議值 -","notAvailable":"抱歉,æœå‹™ç›®å‰æš«ä¸å¯ç”¨","notInDic":"ä¸åœ¨å­—典中","oneChange":"拼字檢查完æˆï¼šæ›´æ”¹äº† 1 個單字","progress":"進行拼字檢查中…","title":"拼字檢查","toolbar":"拼字檢查"}}; \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js b/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js
new file mode 100644
index 0000000..09e51d2
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js
@@ -0,0 +1,10 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("a11yHelp",function(j){var a=j.lang.a11yhelp,l=CKEDITOR.tools.getNextId(),e={8:a.backspace,9:a.tab,13:a.enter,16:a.shift,17:a.ctrl,18:a.alt,19:a.pause,20:a.capslock,27:a.escape,33:a.pageUp,34:a.pageDown,35:a.end,36:a.home,37:a.leftArrow,38:a.upArrow,39:a.rightArrow,40:a.downArrow,45:a.insert,46:a["delete"],91:a.leftWindowKey,92:a.rightWindowKey,93:a.selectKey,96:a.numpad0,97:a.numpad1,98:a.numpad2,99:a.numpad3,100:a.numpad4,101:a.numpad5,102:a.numpad6,103:a.numpad7,104:a.numpad8,
+105:a.numpad9,106:a.multiply,107:a.add,109:a.subtract,110:a.decimalPoint,111:a.divide,112:a.f1,113:a.f2,114:a.f3,115:a.f4,116:a.f5,117:a.f6,118:a.f7,119:a.f8,120:a.f9,121:a.f10,122:a.f11,123:a.f12,144:a.numLock,145:a.scrollLock,186:a.semiColon,187:a.equalSign,188:a.comma,189:a.dash,190:a.period,191:a.forwardSlash,192:a.graveAccent,219:a.openBracket,220:a.backSlash,221:a.closeBracket,222:a.singleQuote};e[CKEDITOR.ALT]=a.alt;e[CKEDITOR.SHIFT]=a.shift;e[CKEDITOR.CTRL]=a.ctrl;var f=[CKEDITOR.ALT,CKEDITOR.SHIFT,
+CKEDITOR.CTRL],m=/\$\{(.*?)\}/g,p=function(){var a=j.keystrokeHandler.keystrokes,g={},c;for(c in a)g[a[c]]=c;return function(a,c){var b;if(g[c]){b=g[c];for(var h,i,k=[],d=0;d<f.length;d++)i=f[d],h=b/f[d],1<h&&2>=h&&(b-=i,k.push(e[i]));k.push(e[b]||String.fromCharCode(b));b=k.join("+")}else b=a;return b}}();return{title:a.title,minWidth:600,minHeight:400,contents:[{id:"info",label:j.lang.common.generalTab,expand:!0,elements:[{type:"html",id:"legends",style:"white-space:normal;",focus:function(){this.getElement().focus()},
+html:function(){for(var e='<div class="cke_accessibility_legend" role="document" aria-labelledby="'+l+'_arialbl" tabIndex="-1">%1</div><span id="'+l+'_arialbl" class="cke_voice_label">'+a.contents+" </span>",g=[],c=a.legend,j=c.length,f=0;f<j;f++){for(var b=c[f],h=[],i=b.items,k=i.length,d=0;d<k;d++){var n=i[d],o=n.legend.replace(m,p);o.match(m)||h.push("<dt>%1</dt><dd>%2</dd>".replace("%1",n.name).replace("%2",o))}g.push("<h1>%1</h1><dl>%2</dl>".replace("%1",b.name).replace("%2",h.join("")))}return e.replace("%1",
+g.join(""))}()+'<style type="text/css">.cke_accessibility_legend{width:600px;height:400px;padding-right:5px;overflow-y:auto;overflow-x:hidden;}.cke_browser_quirks .cke_accessibility_legend,{height:390px}.cke_accessibility_legend *{white-space:normal;}.cke_accessibility_legend h1{font-size: 20px;border-bottom: 1px solid #AAA;margin: 5px 0px 15px;}.cke_accessibility_legend dl{margin-left: 5px;}.cke_accessibility_legend dt{font-size: 13px;font-weight: bold;}.cke_accessibility_legend dd{margin:10px}</style>'}]}],
+buttons:[CKEDITOR.dialog.cancelButton]}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt b/js/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt
new file mode 100644
index 0000000..871e717
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+
+cs.js Found: 30 Missing: 0
+cy.js Found: 30 Missing: 0
+da.js Found: 12 Missing: 18
+de.js Found: 30 Missing: 0
+el.js Found: 25 Missing: 5
+eo.js Found: 30 Missing: 0
+fa.js Found: 30 Missing: 0
+fi.js Found: 30 Missing: 0
+fr.js Found: 30 Missing: 0
+gu.js Found: 12 Missing: 18
+he.js Found: 30 Missing: 0
+it.js Found: 30 Missing: 0
+mk.js Found: 5 Missing: 25
+nb.js Found: 30 Missing: 0
+nl.js Found: 30 Missing: 0
+no.js Found: 30 Missing: 0
+pt-br.js Found: 30 Missing: 0
+ro.js Found: 6 Missing: 24
+tr.js Found: 30 Missing: 0
+ug.js Found: 27 Missing: 3
+vi.js Found: 6 Missing: 24
+zh-cn.js Found: 30 Missing: 0
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/af.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/af.js
new file mode 100644
index 0000000..bbda109
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/af.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","af",{title:"Toeganglikheid instruksies",contents:"Hulp inhoud. Druk ESC om toe te maak.",legend:[{name:"Algemeen",items:[{name:"Bewerker balk",legend:"Druk ${toolbarFocus} om op die werkbalk te land. Beweeg na die volgende en voorige wekrbalkgroep met TAB and SHIFT-TAB. Beweeg na die volgende en voorige werkbalkknop met die regter of linker pyl. Druk SPASIE of ENTER om die knop te bevestig."},{name:"Bewerker dialoog",legend:"In 'n dialoog, druk TAB vir die volgende dialoog veld, SHIFT + TAB vir die vorige dialoog veld, ENTER om te bevestig enESC om die dialoog af te breek. Vir 'n dialoog met meer as een leisie, druk ALT + F10 om die leisielys te wys. Beweeg dan met TAB of Regter pyl na die volgende leise of SHIFT + TAB of Linker pyl na die voorige leisie. Druk SPACE of ENTER om na die leisie bladsy toe te gaan."},
+{name:"Bewerkerinhoudmenu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pouse",capslock:"Hoofletterslot",escape:"Ontsnap",pageUp:"Blaaiop",pageDown:"Blaaiaf",end:"Einde",home:"Tuis",leftArrow:"Linkspyl",upArrow:"Oppyl",rightArrow:"Regterpyl",downArrow:"Afpyl",insert:"Toevoeg","delete":"Verwyder",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Nommerblok 0",numpad1:"Nommerblok 1",
+numpad2:"Nommerblok 2",numpad3:"Nommerblok 3",numpad4:"Nommerblok 4",numpad5:"Nommerblok 5",numpad6:"Nommerblok 6",numpad7:"Nommerblok 7",numpad8:"Nommerblok 8",numpad9:"Nommerblok 9",multiply:"Maal",add:"Plus",subtract:"Minus",decimalPoint:"Desimaalepunt",divide:"Gedeeldeur",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Nommervergrendel",scrollLock:"Rolvergrendel",semiColon:"Kommapunt",equalSign:"Isgelykaan",comma:"Komma",dash:"Koppelteken",
+period:"Punt",forwardSlash:"Skuinsstreep",graveAccent:"Aksentteken",openBracket:"Oopblokhakkie",backSlash:"Trustreep",closeBracket:"Toeblokhakkie",singleQuote:"Enkelaanhaalingsteken"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js
new file mode 100644
index 0000000..2e902ae
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ar",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"عام",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"إضاÙØ©",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"تقسيم",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Ùاصلة",dash:"Dash",period:"نقطة",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js
new file mode 100644
index 0000000..44134d6
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","bg",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Общо",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js
new file mode 100644
index 0000000..7c168d4
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ca",{title:"Instruccions d'Accessibilitat",contents:"Continguts de l'Ajuda. Per tancar aquest quadre de diàleg premi ESC.",legend:[{name:"General",items:[{name:"Editor de barra d'eines",legend:"Premi ${toolbarFocus} per desplaçar-se per la barra d'eines. Vagi en el següent i anterior grup de barra d'eines amb TAB i SHIFT-TAB. Vagi en el següent i anterior botó de la barra d'eines amb RIGHT ARROW i LEFT ARROW. Premi SPACE o ENTER per activar el botó de la barra d'eines."},
+{name:"Editor de quadre de diàleg",legend:"Dins d'un quadre de diàleg, premi la tecla TAB per desplaçar-se al següent camp del quadre de diàleg, premi SHIFT + TAB per desplaçar-se a l'anterior camp, premi ENTER per acceptar el quadre de diàleg, premi ESC per cancel·lar el quadre de diàleg. Per els quadres de diàleg que tenen diverses pestanyes, premi ALT + F10 per anar a la llista de pestanyes. Després podrà desplaçar-se a la següent pestanya amb TAB o RIGHT ARROW. Anar a la pestanya anterior amb SHIFT + TAB o LEFT ARROW. Premi SPACE o ENTER per seleccionar la pestanya."},
+{name:"Editor de menú contextual",legend:"Premi ${contextMenu} o APPLICATION KEY per obrir el menú contextual. Després desplacis a la següent opció del menú amb TAB o DOWN ARROW. Desplacis a l'anterior opció amb SHIFT+TAB o UP ARROW. Premi SPACE o ENTER per seleccionar l'opció del menú. Obri el submenú de l'actual opció utilitzant SPACE o ENTER o RIGHT ARROW. Pot tornar a l'opció del menú pare amb ESC o LEFT ARROW. Tanqui el menú contextual amb ESC."},{name:"Editor de caixa de llista",legend:"Dins d'un quadre de llista, desplacis al següent element de la llista amb TAB o DOWN ARROW. Desplacis a l'anterior element de la llista amb SHIFT + TAB o UP ARROW. Premi SPACE o ENTER per seleccionar l'opció de la llista. Premi ESC per tancar el quadre de llista."},
+{name:"Editor de barra de ruta de l'element",legend:"Premi ${elementsPathFocus} per anar als elements de la barra de ruta. Desplacis al botó de l'element següent amb TAB o RIGHT ARROW. Desplacis a l'anterior botó amb SHIFT+TAB o LEFT ARROW. Premi SPACE o ENTER per seleccionar l'element a l'editor."}]},{name:"Ordres",items:[{name:"Desfer ordre",legend:"Premi ${undo}"},{name:"Refer ordre",legend:"Premi ${redo}"},{name:"Ordre negreta",legend:"Premi ${bold}"},{name:"Ordre cursiva",legend:"Premi ${italic}"},
+{name:"Ordre subratllat",legend:"Premi ${underline}"},{name:"Ordre enllaç",legend:"Premi ${link}"},{name:"Ordre amagar barra d'eines",legend:"Premi ${toolbarCollapse}"},{name:"Ordre per accedir a l'anterior espai enfocat",legend:"Premi ${accessPreviousSpace} per accedir a l'enfocament d'espai més proper inabastable abans del símbol d'intercalació, per exemple: dos elements HR adjacents. Repetiu la combinació de tecles per arribar a enfocaments d'espais distants."},{name:"Ordre per accedir al següent espai enfocat",
+legend:"Premi ${accessNextSpace} per accedir a l'enfocament d'espai més proper inabastable després del símbol d'intercalació, per exemple: dos elements HR adjacents. Repetiu la combinació de tecles per arribar a enfocaments d'espais distants."},{name:"Ajuda d'accessibilitat",legend:"Premi ${a11yHelp}"}]}],backspace:"Retrocés",tab:"Tabulació",enter:"Intro",shift:"Majúscules",ctrl:"Ctrl",alt:"Alt",pause:"Pausa",capslock:"Bloqueig de majúscules",escape:"Escape",pageUp:"Pàgina Amunt",pageDown:"Pàgina Avall",
+end:"Fi",home:"Inici",leftArrow:"Fletxa Esquerra",upArrow:"Fletxa Amunt",rightArrow:"Fletxa Dreta",downArrow:"Fletxa Avall",insert:"Inserir","delete":"Eliminar",leftWindowKey:"Tecla Windows Esquerra",rightWindowKey:"Tecla Windows Dreta",selectKey:"Tecla Seleccionar",numpad0:"Teclat Numèric 0",numpad1:"Teclat Numèric 1",numpad2:"Teclat Numèric 2",numpad3:"Teclat Numèric 3",numpad4:"Teclat Numèric 4",numpad5:"Teclat Numèric 5",numpad6:"Teclat Numèric 6",numpad7:"Teclat Numèric 7",numpad8:"Teclat Numèric 8",
+numpad9:"Teclat Numèric 9",multiply:"Multiplicació",add:"Suma",subtract:"Resta",decimalPoint:"Punt Decimal",divide:"Divisió",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Bloqueig Teclat Numèric",scrollLock:"Bloqueig de Desplaçament",semiColon:"Punt i Coma",equalSign:"Símbol Igual",comma:"Coma",dash:"Guió",period:"Punt",forwardSlash:"Barra Diagonal",graveAccent:"Accent Obert",openBracket:"Claudàtor Obert",backSlash:"Barra Invertida",
+closeBracket:"Claudàtor Tancat",singleQuote:"Cometa Simple"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js
new file mode 100644
index 0000000..2284c7e
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","cs",{title:"Instrukce pro přístupnost",contents:"Obsah nápovÄ›dy. Pro uzavÅ™ení tohoto dialogu stisknÄ›te klávesu ESC.",legend:[{name:"Obecné",items:[{name:"Panel nástrojů editoru",legend:"StisknÄ›te${toolbarFocus} k procházení panelu nástrojů. PÅ™ejdÄ›te na další a pÅ™edchozí skupiny pomocí TAB a SHIFT-TAB. PÅ™echod na další a pÅ™edchozí tlaÄítko panelu nástrojů je pomocí Å IPKA VPRAVO nebo Å IPKA VLEVO. Stisknutím mezerníku nebo klávesy ENTER tlaÄítko aktivujete."},{name:"Dialogové okno editoru",
+legend:"UvnitÅ™ dialogového okna stisknÄ›te TAB pro pÅ™esunutí na další pole, stisknÄ›te SHIFT + TAB pro pÅ™esun na pÅ™edchozí pole, stisknÄ›te ENTER pro odeslání dialogu, stisknÄ›te ESC pro jeho zruÅ¡ení. Pro dialogová okna, která mají mnoho karet stisknÄ›te ALT + F10 pr oprocházení seznamu karet. Pak se pÅ™esuňte na další kartu pomocí TAB nebo Å IPKA VPRAVO. Pro pÅ™esun na pÅ™edchozí stisknÄ›te SHIFT + TAB nebo Å IPKA VLEVO. StisknÄ›te MEZERNÃK nebo ENTER pro vybrání stránky karet."},{name:"Kontextové menu editoru",
+legend:"StisknÄ›te ${contextMenu} nebo klávesu APPLICATION k otevÅ™ení kontextového menu. Pak se pÅ™esuňte na další možnost menu pomocí TAB nebo Å IPKY DOLÅ®. PÅ™esuňte se na pÅ™edchozí možnost pomocí SHIFT+TAB nebo Å IPKY NAHORU. StisknÄ›te MEZERNÃK nebo ENTER pro zvolení možnosti menu. Podmenu souÄasné možnosti otevÅ™ete pomocí MEZERNÃKU nebo ENTER Äi Å IPKY DOLEVA. Kontextové menu uzavÅ™ete stiskem ESC."},{name:"RámeÄek seznamu editoru",legend:"UvnitÅ™ rámeÄku seznamu se pÅ™esunete na další položku menu pomocí TAB nebo Å IPKA DOLÅ®. Na pÅ™edchozí položku se pÅ™esunete SHIFT + TAB nebo Å IPKA NAHORU. StisknÄ›te MEZERNÃK nebo ENTER pro zvolení možnosti seznamu. StisknÄ›te ESC pro uzavÅ™ení seznamu."},
+{name:"LiÅ¡ta cesty prvku v editoru",legend:"StisknÄ›te ${elementsPathFocus} pro procházení liÅ¡ty cesty prvku. Na další tlaÄítko prvku se pÅ™esunete pomocí TAB nebo Å IPKA VPRAVO. Na pÅ™edchozí položku se pÅ™esunete pomocí SHIFT + TAB nebo Å IPKA VLEVO. StisknÄ›te MEZERNÃK nebo ENTER pro vybrání prvku v editoru."}]},{name:"Příkazy",items:[{name:" Příkaz ZpÄ›t",legend:"StisknÄ›te ${undo}"},{name:" Příkaz Znovu",legend:"StisknÄ›te ${redo}"},{name:" Příkaz TuÄné",legend:"StisknÄ›te ${bold}"},{name:" Příkaz Kurzíva",
+legend:"Stiskněte ${italic}"},{name:" Příkaz Podtržení",legend:"Stiskněte ${underline}"},{name:" Příkaz Odkaz",legend:"Stiskněte ${link}"},{name:" Příkaz Skrýt panel nástrojů",legend:"Stiskněte ${toolbarCollapse}"},{name:"Příkaz pro přístup k předchozímu prostoru zaměření",legend:"Stiskněte ${accessPreviousSpace} pro přístup k nejbližšímu nedosažitelnému prostoru zaměření před stříškou, například: dva přilehlé prvky HR. Pro dosažení vzdálených prostorů zaměření tuto kombinaci kláves opakujte."},{name:"Příkaz pro přístup k dalšímu prostoru zaměření",
+legend:"Stiskněte ${accessNextSpace} pro přístup k nejbližšímu nedosažitelnému prostoru zaměření po stříšce, například: dva přilehlé prvky HR. Pro dosažení vzdálených prostorů zaměření tuto kombinaci kláves opakujte."},{name:" Nápověda přístupnosti",legend:"Stiskněte ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tabulátor",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pauza",capslock:"Caps lock",escape:"Escape",pageUp:"Stránka nahoru",pageDown:"Stránka dolů",end:"Konec",home:"Domů",leftArrow:"Šipka vlevo",
+upArrow:"Šipka nahoru",rightArrow:"Šipka vpravo",downArrow:"Šipka dolů",insert:"Vložit","delete":"Smazat",leftWindowKey:"Levá klávesa Windows",rightWindowKey:"Pravá klávesa Windows",selectKey:"Vyberte klávesu",numpad0:"Numerická klávesa 0",numpad1:"Numerická klávesa 1",numpad2:"Numerická klávesa 2",numpad3:"Numerická klávesa 3",numpad4:"Numerická klávesa 4",numpad5:"Numerická klávesa 5",numpad6:"Numerická klávesa 6",numpad7:"Numerická klávesa 7",numpad8:"Numerická klávesa 8",numpad9:"Numerická klávesa 9",
+multiply:"Numerická klávesa násobení",add:"PÅ™idat",subtract:"Numerická klávesa odeÄítání",decimalPoint:"Desetinná teÄka",divide:"Numerická klávesa dÄ›lení",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num lock",scrollLock:"Scroll lock",semiColon:"StÅ™edník",equalSign:"Rovnítko",comma:"Čárka",dash:"PomlÄka",period:"TeÄka",forwardSlash:"Lomítko",graveAccent:"Přízvuk",openBracket:"OtevÅ™ená hranatá závorka",backSlash:"Obrácené lomítko",closeBracket:"UzavÅ™ená hranatá závorka",
+singleQuote:"Jednoduchá uvozovka"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js
new file mode 100644
index 0000000..2b040a1
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","cy",{title:"Canllawiau Hygyrchedd",contents:"Cynnwys Cymorth. I gau y deialog hwn, pwyswch ESC.",legend:[{name:"Cyffredinol",items:[{name:"Bar Offer y Golygydd",legend:"Pwyswch $ {toolbarFocus} i fynd at y bar offer. Symudwch i'r grŵp bar offer nesaf a blaenorol gyda TAB a SHIFT-TAB. Symudwch i'r botwm bar offer nesaf a blaenorol gyda SAETH DDE neu SAETH CHWITH. Pwyswch SPACE neu ENTER i wneud botwm y bar offer yn weithredol."},{name:"Deialog y Golygydd",legend:"Tu mewn i'r deialog, pwyswch TAB i fynd i'r maes nesaf ar y deialog, pwyswch SHIFT + TAB i symud i faes blaenorol, pwyswch ENTER i gyflwyno'r deialog, pwyswch ESC i ddiddymu'r deialog. Ar gyfer deialogau sydd â thudalennau aml-tab, pwyswch ALT + F10 i lywio'r tab-restr. Yna symudwch i'r tab nesaf gyda TAB neu SAETH DDE. Symudwch i dab blaenorol gyda SHIFT + TAB neu'r SAETH CHWITH. Pwyswch SPACE neu ENTER i ddewis y dudalen tab."},
+{name:"Dewislen Cyd-destun y Golygydd",legend:"Pwyswch $ {contextMenu} neu'r ALLWEDD 'APPLICATION' i agor y ddewislen cyd-destun. Yna symudwch i'r opsiwn ddewislen nesaf gyda'r TAB neu'r SAETH I LAWR. Symudwch i'r opsiwn blaenorol gyda SHIFT + TAB neu'r SAETH I FYNY. Pwyswch SPACE neu ENTER i ddewis yr opsiwn ddewislen. Agorwch is-dewislen yr opsiwn cyfredol gyda SPACE neu ENTER neu SAETH DDE. Ewch yn ôl i'r eitem ar y ddewislen uwch gydag ESC neu SAETH CHWITH. Ceuwch y ddewislen cyd-destun gydag ESC."},
+{name:"Blwch Rhestr y Golygydd",legend:"Tu mewn y blwch rhestr, ewch i'r eitem rhestr nesaf gyda TAB neu'r SAETH I LAWR. Symudwch i restr eitem flaenorol gyda SHIFT + TAB neu SAETH I FYNY. Pwyswch SPACE neu ENTER i ddewis yr opsiwn o'r rhestr. Pwyswch ESC i gau'r rhestr."},{name:"Bar Llwybr Elfen y Golygydd",legend:"Pwyswch ${elementsPathFocus} i fynd i'r bar llwybr elfennau. Symudwch i fotwm yr elfen nesaf gyda TAB neu SAETH DDE. Symudwch i fotwm blaenorol gyda SHIFT + TAB neu SAETH CHWITH. Pwyswch SPACE neu ENTER i ddewis yr elfen yn y golygydd."}]},
+{name:"Gorchmynion",items:[{name:"Gorchymyn dadwneud",legend:"Pwyswch ${undo}"},{name:"Gorchymyn ailadrodd",legend:"Pwyswch ${redo}"},{name:"Gorchymyn Bras",legend:"Pwyswch ${bold}"},{name:"Gorchymyn italig",legend:"Pwyswch ${italig}"},{name:"Gorchymyn tanlinellu",legend:"Pwyso ${underline}"},{name:"Gorchymyn dolen",legend:"Pwyswch ${link}"},{name:"Gorchymyn Cwympo'r Dewislen",legend:"Pwyswch ${toolbarCollapse}"},{name:"Myned i orchymyn bwlch ffocws blaenorol",legend:"Pwyswch ${accessPreviousSpace} i fyned i'r \"blwch ffocws sydd methu ei gyrraedd\" cyn y caret, er enghraifft: dwy elfen HR drws nesaf i'w gilydd. AIladroddwch y cyfuniad allwedd i gyrraedd bylchau ffocws pell."},
+{name:"Ewch i'r gorchymyn blwch ffocws nesaf",legend:"Pwyswch ${accessNextSpace} i fyned i'r blwch ffocws agosaf nad oes modd ei gyrraedd ar ôl y caret, er enghraifft: dwy elfen HR drws nesaf i'w gilydd. Ailadroddwch y cyfuniad allwedd i gyrraedd blychau ffocws pell."},{name:"Cymorth Hygyrchedd",legend:"Pwyswch ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",
+divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/da.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/da.js
new file mode 100644
index 0000000..f0dfe0f
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/da.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","da",{title:"Tilgængelighedsinstrukser",contents:"Onlinehjælp. For at lukke dette vindue klik ESC",legend:[{name:"Generelt",items:[{name:"Editor værktøjslinje",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Kommandoer",items:[{name:"Fortryd kommando",legend:"Klik på ${undo}"},{name:"Gentag kommando",legend:"Klik ${redo}"},{name:" Bold command",legend:"Klik ${bold}"},{name:" Italic command",legend:"Klik ${italic}"},{name:" Underline command",
+legend:"Klik ${underline}"},{name:" Link command",legend:"Klik ${link}"},{name:" Toolbar Collapse command",legend:"Klik ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Kilk ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Venstre pil",upArrow:"Pil op",rightArrow:"Højre pil",downArrow:"Pil ned",insert:"Insert","delete":"Delete",leftWindowKey:"Venstre Windows tast",rightWindowKey:"Højre Windows tast",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Gange",add:"Plus",subtract:"Minus",decimalPoint:"Decimal Point",divide:"Divider",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semikolon",equalSign:"Lighedstegn",comma:"Komma",dash:"Bindestreg",period:"Punktum",forwardSlash:"Skråstreg",
+graveAccent:"Grave Accent",openBracket:"Start klamme",backSlash:"Omvendt skråstreg",closeBracket:"Slut klamme",singleQuote:"Enkelt citationstegn"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/de.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/de.js
new file mode 100644
index 0000000..a35e480
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/de.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","de",{title:"Barrierefreiheitinformationen",contents:"Hilfeinhalt. Um den Dialog zu schliessen die Taste 'ESC' drücken.",legend:[{name:"Allgemein",items:[{name:"Editor Symbolleiste",legend:"Drücken Sie ${toolbarFocus} auf der Symbolleiste. Gehen Sie zur nächsten oder vorherigen Symbolleistengruppe mit TAB und SHIFT-TAB. Gehen Sie zur nächsten oder vorherigen Symbolleiste auf die Schaltfläche mit dem RECHTS- oder LINKS-Pfeil. Drücken Sie die Leertaste oder Eingabetaste, um die Schaltfläche in der Symbolleiste aktivieren."},
+{name:"Editor Dialog",legend:"Innerhalb des Dialogs drücken Sie TAB um zum nächsten Dialogfeld zu gelangen, drücken Sie SHIFT-TAG um zum vorherigen Feld zu wechseln, drücken Sie ENTER um den Dialog abzusenden und ESC um den Dialog zu abzubrechen. Um zwischen den Reitern innerhalb eines Dialogs zu wechseln drücken sie ALT-F10. Um zum nächsten Reiter zu gelangen können Sie TAB oder die rechte Pfeiltaste. Zurück gelangt man mit SHIFT-TAB oder der linken Pfeiltaste. Mit der Leertaste oder Enter kann man den Reiter auswählen."},
+{name:"Editor Kontextmenü",legend:"Dürcken Sie ${contextMenu} oder die Anwendungstaste um das Kontextmenü zu öffnen. Man kann die Pfeiltasten zum Wechsel benutzen. Mit der Leertaste oder der Enter-Taste kann man den Menüpunkt aufrufen. Schliessen Sie das Kontextmenü mit der ESC-Taste."},{name:"Editor Listen",legend:"Innerhalb einer Listenbox kann man mit der TAB-Taste oder den Pfeilrunter-Taste den nächsten Menüeintrag wählen. Mit der Shift-TAB Tastenkombination oder der Pfeilhoch-Taste gelangt man zum vorherigen Menüpunkt. Mit der Leertaste oder Enter kann man den Menüpunkt auswählen. Drücken Sie ESC zum Verlassen des Menüs."},
+{name:"Editor Elementpfadleiste",legend:"Drücken Sie ${elementsPathFocus} um sich durch die Pfadleiste zu bewegen. Um zum nächsten Element zu gelangen drücken Sie TAB oder die Pfeilrechts-Taste. Zum vorherigen Element gelangen Sie mit der SHIFT-TAB oder der Pfeillinks-Taste. Drücken Sie die Leertaste oder Enter um das Element auszuwählen."}]},{name:"Befehle",items:[{name:"Wiederholen Befehl",legend:"Drücken Sie ${undo}"},{name:"Rückgängig Befehl",legend:"Drücken Sie ${redo}"},{name:"Fettschrift Befehl",
+legend:"Drücken Sie ${bold}"},{name:"Italic Befehl",legend:"Drücken Sie ${italic}"},{name:"Unterstreichung Befehl",legend:"Drücken Sie ${underline}"},{name:"Link Befehl",legend:"Drücken Sie ${link}"},{name:"Symbolleiste zuammenklappen Befehl",legend:"Drücken Sie ${toolbarCollapse}"},{name:"Zugang bisheriger Fokussierung Raumbefehl ",legend:"Drücken Sie ${accessPreviousSpace} auf den am nächsten nicht erreichbar Fokus-Abstand vor die Einfügemarke zugreifen: zwei benachbarte HR-Elemente. Wiederholen Sie die Tastenkombination um entfernte Fokusräume zu erreichen. "},
+{name:"Zugang nächster Schwerpunkt Raumbefehl ",legend:"Drücken Sie $ { accessNextSpace }, um den nächsten unerreichbar Fokus Leerzeichen nach dem Cursor zum Beispiel auf: zwei benachbarten HR Elemente. Wiederholen Sie die Tastenkombination zum fernen Fokus Bereiche zu erreichen. "},{name:"Eingabehilfen",legend:"Drücken Sie ${a11yHelp}"}]}],backspace:"Rücklöschtaste",tab:"Tab",enter:"Eingabe",shift:"Umschalt",ctrl:"Strg",alt:"Alt",pause:"Pause",capslock:"Feststell",escape:"Escape",pageUp:"Bild auf",
+pageDown:"Bild ab",end:"Ende",home:"Pos1",leftArrow:"Linke Pfeiltaste",upArrow:"Obere Pfeiltaste",rightArrow:"Rechte Pfeiltaste",downArrow:"Untere Pfeiltaste",insert:"Einfügen","delete":"Entfernen",leftWindowKey:"Linke Windowstaste",rightWindowKey:"Rechte Windowstaste",selectKey:"Taste auswählen",numpad0:"Ziffernblock 0",numpad1:"Ziffernblock 1",numpad2:"Ziffernblock 2",numpad3:"Ziffernblock 3",numpad4:"Ziffernblock 4",numpad5:"Ziffernblock 5",numpad6:"Ziffernblock 6",numpad7:"Ziffernblock 7",numpad8:"Ziffernblock 8",
+numpad9:"Ziffernblock 9",multiply:"Multiplizieren",add:"Addieren",subtract:"Subtrahieren",decimalPoint:"Punkt",divide:"Dividieren",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Ziffernblock feststellen",scrollLock:"Rollen",semiColon:"Semikolon",equalSign:"Gleichheitszeichen",comma:"Komma",dash:"Bindestrich",period:"Punkt",forwardSlash:"Schrägstrich",graveAccent:"Gravis",openBracket:"Öffnende eckige Klammer",backSlash:"Rückwärtsgewandter Schrägstrich",
+closeBracket:"Schließende eckige Klammer",singleQuote:"Einfaches Anführungszeichen"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/el.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/el.js
new file mode 100644
index 0000000..c066d66
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/el.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","el",{title:"Οδηγίες ΠÏοσβασιμότητας",contents:"ΠεÏιεχόμενα Βοήθειας. Πατήστε ESC για κλείσιμο.",legend:[{name:"Γενικά",items:[{name:"ΕÏγαλειοθήκη ΕπεξεÏγαστή",legend:"Πατήστε ${toolbarFocus} για να πεÏιηγηθείτε στην γÏαμμή εÏγαλείων. Μετακινηθείτε ανάμεσα στις ομάδες της γÏαμμής εÏγαλείων με TAB και SHIFT-TAB. Μετακινηθείτε ανάμεσα στα κουμπιά εÏγαλείων με το ΔΕΞΙ ή ΑΡΙΣΤΕΡΟ ΒΕΛΑΚΙ. Πατήστε ΔΙΑΣΤΗΜΑ ή ENTER για να ενεÏγοποιήσετε το ενεÏγό κουμπί εÏγαλείου."},{name:"ΠαÏάθυÏο Διαλόγου ΕπεξεÏγαστή",
+legend:"Μέσα σε ένα παÏάθυÏο διαλόγου, πατήστε TAB για να μεταβείτε στο επόμενο πεδίο ή SHIFT + TAB για να μεταβείτε στο Ï€ÏοηγοÏμενο. Πατήστε ENTER για να υποβάλετε την φόÏμα. Πατήστε ESC για να ακυÏώσετε την διαδικασία της φόÏμας. Για παÏάθυÏα διαλόγων που έχουν πολλές σελίδες σε καÏτέλες πατήστε ALT + F10 για να μεταβείτε στην λίστα των καÏτελών. Στην συνέχεια μποÏείτε να μεταβείτε στην επόμενη καÏτέλα πατώντας το TAB ή το ΔΕΞΙ ΒΕΛΑΚΙ. ΜποÏείτε να μεταβείτε στην Ï€ÏοηγοÏμενη καÏτέλα πατώντας SHIFT + TAB ή το ΑΡΙΣΤΕΡΟ ΒΕΛΑΚΙ. Πατήστε ΔΙΑΣΤΗΜΑ ή ENTER για να επιλέξτε την καÏτέλα για Ï€Ïοβολή."},
+{name:"Αναδυόμενο ÎœÎµÎ½Î¿Ï Î•Ï€ÎµÎ¾ÎµÏγαστή",legend:"Πατήστε ${contextMenu} ή APPLICATION KEY για να ανοίξετε το αναδυόμενο μενοÏ. Μετά μετακινηθείτε στην επόμενη επιλογή του Î¼ÎµÎ½Î¿Ï Î¼Îµ TAB ή ΚΑΤΩ ΒΕΛΑΚΙ. Μετακινηθείτε στην Ï€ÏοηγοÏμενη επιλογή με SHIFT+TAB ή το ΠΑÎΩ ΒΕΛΑΚΙ. Πατήστε ΔΙΑΣΤΗΜΑ ή ENTER για να επιλέξτε το Ï„Ïέχων στοιχείο. Ανοίξτε το αναδυόμενο Î¼ÎµÎ½Î¿Ï Ï„Î·Ï‚ Ï„Ïέχουσας επιλογής με ΔΙΑΣΤΗΜΑ ή ENTER ή το ΔΕΞΙ ΒΕΛΑΚΙ. Μεταβείτε πίσω στο αÏχικό στοιχείο Î¼ÎµÎ½Î¿Ï Î¼Îµ το ESC ή το ΑΡΙΣΤΕΡΟ ΒΕΛΑΚΙ. Κλείστε το αναδυόμενο Î¼ÎµÎ½Î¿Ï Î¼Îµ ESC."},
+{name:"Κουτί Λίστας ΕπεξεÏγαστών",legend:"Μέσα σε ένα κουτί λίστας, μετακινηθείτε στο επόμενο στοιχείο με TAB ή ΚΑΤΩ ΒΕΛΑΚΙ. Μετακινηθείτε στο Ï€ÏοηγοÏμενο στοιχείο με SHIFT + TAB ή το ΠΑÎΩ ΒΕΛΑΚΙ. Πατήστε ΔΙΑΣΤΗΜΑ ή ENTER για να επιλέξετε ένα στοιχείο. Πατήστε ESC για να κλείσετε το κουτί της λίστας."},{name:"ΜπάÏα ΔιαδÏομών Στοιχείων ΕπεξεÏγαστή",legend:"Πατήστε ${elementsPathFocus} για να πεÏιηγηθείτε στην μπάÏα διαδÏομών στοιχείων του επεξεÏγαστή. Μετακινηθείτε στο κουμπί του επόμενου στοιχείου με το TAB ή το ΔΕΞΙ ΒΕΛΑΚΙ. Μετακινηθείτε στο κουμπί του Ï€ÏοηγοÏμενου στοιχείου με το SHIFT+TAB ή το ΑΡΙΣΤΕΡΟ ΒΕΛΑΚΙ. Πατήστε ΔΙΑΣΤΗΜΑ ή ENTER για να επιλέξετε το στοιχείο στον επεξεÏγαστή."}]},
+{name:"Εντολές",items:[{name:"Εντολή αναίÏεσης",legend:"Πατήστε ${undo}"},{name:"Εντολή επανάληψης",legend:"Πατήστε ${redo}"},{name:"Εντολή έντονης γÏαφής",legend:"Πατήστε ${bold}"},{name:"Εντολή πλάγιας γÏαφής",legend:"Πατήστε ${italic}"},{name:"Εντολή υπογÏάμμισης",legend:"Πατήστε ${underline}"},{name:"Εντολή συνδέσμου",legend:"Πατήστε ${link}"},{name:"Εντολή ΣÏμπτηξης ΕÏγαλειοθήκης",legend:"Πατήστε ${toolbarCollapse}"},{name:"ΠÏόσβαση στην Ï€ÏοηγοÏμενη εντολή του χώÏου εστίασης ",legend:"Πατήστε ${accessPreviousSpace} για να έχετε Ï€Ïόσβαση στον πιο κοντινό χώÏο εστίασης Ï€Ïιν το δÏομέα, για παÏάδειγμα: δÏο παÏακείμενα στοιχεία ΥΕ. Επαναλάβετε το συνδυασμό πλήκτÏων για να φθάσετε στους χώÏους μακÏινής εστίασης. "},
+{name:"ΠÏόσβαση στην επόμενη εντολή του χώÏου εστίασης",legend:"Πατήστε ${accessNextSpace} για να έχετε Ï€Ïόσβαση στον πιο κοντινό χώÏο εστίασης μετά το δÏομέα, για παÏάδειγμα: δÏο παÏακείμενα στοιχεία ΥΕ. Επαναλάβετε το συνδυασμό πλήκτÏων για τους χώÏους μακÏινής εστίασης. "},{name:"Βοήθεια ΠÏοσβασιμότητας",legend:"Πατήστε ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"ΑÏιστεÏÏŒ Βέλος",upArrow:"Πάνω Βέλος",rightArrow:"Δεξί Βέλος",downArrow:"Κάτω Βέλος",insert:"Insert ","delete":"Delete",leftWindowKey:"ΑÏιστεÏÏŒ ΠλήκτÏο Windows",rightWindowKey:"Δεξί ΠλήκτÏο Windows",selectKey:"ΠλήκτÏο Select",numpad0:"ΑÏιθμητικό πληκτÏολόγιο 0",numpad1:"ΑÏιθμητικό ΠληκτÏολόγιο 1",numpad2:"ΑÏιθμητικό πληκτÏολόγιο 2",numpad3:"ΑÏιθμητικό πληκτÏολόγιο 3",numpad4:"ΑÏιθμητικό πληκτÏολόγιο 4",numpad5:"ΑÏιθμητικό πληκτÏολόγιο 5",numpad6:"ΑÏιθμητικό πληκτÏολόγιο 6",
+numpad7:"ΑÏιθμητικό πληκτÏολόγιο 7",numpad8:"ΑÏιθμητικό πληκτÏολόγιο 8",numpad9:"ΑÏιθμητικό πληκτÏολόγιο 9",multiply:"Πολλαπλασιασμός",add:"ΠÏόσθεση",subtract:"ΑφαίÏεση",decimalPoint:"Υποδιαστολή",divide:"ΔιαίÏεση",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"6",f7:"7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"ΕÏωτηματικό",equalSign:"ΣÏμβολο Ισότητας",comma:"Κόμμα",dash:"ΠαÏλα",period:"Τελεία",forwardSlash:"Κάθετος",graveAccent:"ΒαÏεία",openBracket:"Άνοιγμα ΠαÏένθεσης",
+backSlash:"ΑνάστÏοφη Κάθετος",closeBracket:"Κλείσιμο ΠαÏένθεσης",singleQuote:"ΑπόστÏοφος"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js
new file mode 100644
index 0000000..2d158a0
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","en-gb",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"General",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/en.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/en.js
new file mode 100644
index 0000000..a68b39c
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/en.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","en",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"General",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js
new file mode 100644
index 0000000..0b0b933
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","eo",{title:"Uzindikoj pri atingeblo",contents:"Helpilenhavo. Por fermi tiun dialogon, premu la ESKAPAN klavon.",legend:[{name:"Äœeneralaĵoj",items:[{name:"Ilbreto de la redaktilo",legend:"Premu ${toolbarFocus} por atingi la ilbreton. MoviÄu al la sekva aÅ­ antaÅ­a grupoj de la ilbreto per la klavoj TABA kaj MAJUSKLIGA-TABA. MoviÄu al la sekva aÅ­ antaÅ­a butonoj de la ilbreto per la klavoj SAGO DEKSTREN kaj SAGO MALDEKSTREN. Premu la SPACETklavon aÅ­ la ENENklavon por aktivigi la ilbretbutonon."},
+{name:"Redaktildialogo",legend:"En dialogo, premu la TABAN klavon por navigi al la sekva dialogkampo, premu la MAJUSKLIGAN + TABAN klavojn por reveni al la antaÅ­a kampo, premu la ENENklavon por sendi la dialogon, premu la ESKAPAN klavon por nuligi la dialogon. Por dialogoj kun pluraj retpaÄoj sub langetoj, premu ALT + F10 por navigi al la langetlisto. Poste moviÄu al la sekva langeto per la klavo TABA aÅ­ SAGO DEKSTREN. MoviÄu al la antaÅ­a langeto per la klavoj MAJUSKLIGA + TABA aÅ­ SAGO MALDEKSTREN. Premu la SPACETklavon aÅ­ la ENENklavon por selekti la langetretpaÄon."},
+{name:"Kunteksta menuo de la redaktilo",legend:"Premu ${contextMenu} aÅ­ entajpu la KLAVKOMBINAÄ´ON por malfermi la kuntekstan menuon. Poste moviÄu al la sekva opcio de la menuo per la klavoj TABA aÅ­ SAGO SUBEN. MoviÄu al la antaÅ­a opcio per la klavoj MAJUSKLGA + TABA aÅ­ SAGO SUPREN. Premu la SPACETklavon aÅ­ ENENklavon por selekti la menuopcion. Malfermu la submenuon de la kuranta opcio per la SPACETklavo aÅ­ la ENENklavo aÅ­ la SAGO DEKSTREN. Revenu al la elemento de la patra menuo per la klavoj ESKAPA aÅ­ SAGO MALDEKSTREN. Fermu la kuntekstan menuon per la ESKAPA klavo."},
+{name:"Fallisto de la redaktilo",legend:"En fallisto, moviÄu al la sekva listelemento per la klavoj TABA aÅ­ SAGO SUBEN. MoviÄu al la antaÅ­a listelemento per la klavoj MAJUSKLIGA + TABA aÅ­ SAGO SUPREN. Premu la SPACETklavon aÅ­ ENENklavon por selekti la opcion en la listo. Premu la ESKAPAN klavon por fermi la falmenuon."},{name:"Breto indikanta la vojon al la redaktilelementoj",legend:"Premu ${elementsPathFocus} por navigi al la breto indikanta la vojon al la redaktilelementoj. MoviÄu al la butono de la sekva elemento per la klavoj TABA aÅ­ SAGO DEKSTREN. MoviÄu al la butono de la antaÅ­a elemento per la klavoj MAJUSKLIGA + TABA aÅ­ SAGO MALDEKSTREN. Premu la SPACETklavon aÅ­ ENENklavon por selekti la elementon en la redaktilo."}]},
+{name:"Komandoj",items:[{name:"Komando malfari",legend:"Premu ${undo}"},{name:"Komando refari",legend:"Premu ${redo}"},{name:"Komando grasa",legend:"Premu ${bold}"},{name:"Komando kursiva",legend:"Premu ${italic}"},{name:"Komando substreki",legend:"Premu ${underline}"},{name:"Komando ligilo",legend:"Premu ${link}"},{name:"Komando faldi la ilbreton",legend:"Premu ${toolbarCollapse}"},{name:"Komando por atingi la antaÅ­an fokusan spacon",legend:"Press ${accessPreviousSpace} por atingi la plej proksiman neatingeblan fokusan spacon antaÅ­ la kursoro, ekzemple : du kuntuÅiÄajn HR elementojn. Ripetu la klavkombinaĵon por atingi malproksimajn fokusajn spacojn."},
+{name:"Komando por atingi la sekvan fokusan spacon",legend:"Press ${accessNextSpace} por atingi la plej proksiman neatingeblan fokusan spacon post la kursoro, ekzemple : du kuntuÅiÄajn HR elementojn. Ripetu la klavkombinajôn por atingi malproksimajn fokusajn spacojn"},{name:"Helpilo pri atingeblo",legend:"Premu ${a11yHelp}"}]}],backspace:"RetropaÅo",tab:"Tabo",enter:"Enigi",shift:"Registrumo",ctrl:"Stirklavo",alt:"Alt-klavo",pause:"PaÅ­zo",capslock:"Majuskla baskulo",escape:"Eskapa klavo",pageUp:"AntaÅ­a PaÄo",
+pageDown:"Sekva PaÄo",end:"Fino",home:"Hejmo",leftArrow:"Sago Maldekstren",upArrow:"Sago Supren",rightArrow:"Sago Dekstren",downArrow:"Sago Suben",insert:"Enmeti","delete":"Forigi",leftWindowKey:"Maldekstra Windows-klavo",rightWindowKey:"Dekstra Windows-klavo",selectKey:"Selektklavo",numpad0:"Nombra Klavaro 0",numpad1:"Nombra Klavaro 1",numpad2:"Nombra Klavaro 2",numpad3:"Nombra Klavaro 3",numpad4:"Nombra Klavaro 4",numpad5:"Nombra Klavaro 5",numpad6:"Nombra Klavaro 6",numpad7:"Nombra Klavaro 7",
+numpad8:"Nombra Klavaro 8",numpad9:"Nombra Klavaro 9",multiply:"Obligi",add:"Almeti",subtract:"Subtrahi",decimalPoint:"Dekuma Punkto",divide:"Dividi",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Nombra Baskulo",scrollLock:"Ruluma Baskulo",semiColon:"Punktokomo",equalSign:"Egalsigno",comma:"Komo",dash:"Haltostreko",period:"Punkto",forwardSlash:"Oblikvo",graveAccent:"Malakuto",openBracket:"Malferma Krampo",backSlash:"Retroklino",closeBracket:"Ferma Krampo",
+singleQuote:"Citilo"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/es.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/es.js
new file mode 100644
index 0000000..507da4f
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/es.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","es",{title:"Instrucciones de accesibilidad",contents:"Ayuda. Para cerrar presione ESC.",legend:[{name:"General",items:[{name:"Barra de herramientas del editor",legend:'Presiona ${toolbarFocus} para navegar por la barra de herramientas. Para moverse por los distintos grupos de herramientas usa las teclas TAB y MAY-TAB. Para moverse por las distintas herramientas usa FLECHA DERECHA o FECHA IZQUIERDA. Presiona "espacio" o "intro" para activar la herramienta.'},{name:"Editor de diálogo",
+legend:"Dentro de un cuadro de diálogo, presione la tecla TAB para desplazarse al campo siguiente del cuadro de diálogo, pulse SHIFT + TAB para desplazarse al campo anterior, pulse ENTER para presentar cuadro de diálogo, pulse la tecla ESC para cancelar el diálogo. Para los diálogos que tienen varias páginas, presione ALT + F10 para navegar a la pestaña de la lista. Luego pasar a la siguiente pestaña con TAB o FLECHA DERECHA. Para ir a la ficha anterior con SHIFT + TAB o FLECHA IZQUIERDA. Presione ESPACIO o ENTRAR para seleccionar la página de ficha."},
+{name:"Editor del menú contextual",legend:"Presiona ${contextMenu} o TECLA MENÚ para abrir el menú contextual. Entonces muévete a la siguiente opción del menú con TAB o FLECHA ABAJO. Muévete a la opción previa con SHIFT + TAB o FLECHA ARRIBA. Presiona ESPACIO o ENTER para seleccionar la opción del menú. Abre el submenú de la opción actual con ESPACIO o ENTER o FLECHA DERECHA. Regresa al elemento padre del menú con ESC o FLECHA IZQUIERDA. Cierra el menú contextual con ESC."},{name:"Lista del Editor",
+legend:"Dentro de una lista, te mueves al siguiente elemento de la lista con TAB o FLECHA ABAJO. Te mueves al elemento previo de la lista con SHIFT + TAB o FLECHA ARRIBA. Presiona ESPACIO o ENTER para elegir la opción de la lista. Presiona ESC para cerrar la lista."},{name:"Barra de Ruta del Elemento en el Editor",legend:"Presiona ${elementsPathFocus} para navegar a los elementos de la barra de ruta. Te mueves al siguiente elemento botón con TAB o FLECHA DERECHA. Te mueves al botón previo con SHIFT + TAB o FLECHA IZQUIERDA. Presiona ESPACIO o ENTER para seleccionar el elemento en el editor."}]},
+{name:"Comandos",items:[{name:"Comando deshacer",legend:"Presiona ${undo}"},{name:"Comando rehacer",legend:"Presiona ${redo}"},{name:"Comando negrita",legend:"Presiona ${bold}"},{name:"Comando itálica",legend:"Presiona ${italic}"},{name:"Comando subrayar",legend:"Presiona ${underline}"},{name:"Comando liga",legend:"Presiona ${liga}"},{name:"Comando colapsar barra de herramientas",legend:"Presiona ${toolbarCollapse}"},{name:"Comando accesar el anterior espacio de foco",legend:"Presiona ${accessPreviousSpace} para accesar el espacio de foco no disponible más cercano anterior al cursor, por ejemplo: dos elementos HR adyacentes. Repite la combinación de teclas para alcanzar espacios de foco distantes."},
+{name:"Comando accesar el siguiente spacio de foco",legend:"Presiona ${accessNextSpace} para accesar el espacio de foco no disponible más cercano después del cursor, por ejemplo: dos elementos HR adyacentes. Repite la combinación de teclas para alcanzar espacios de foco distantes."},{name:"Ayuda de Accesibilidad",legend:"Presiona ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Mayús.",ctrl:"Ctrl",alt:"Alt",pause:"Pausa",capslock:"Bloq. Mayús.",escape:"Escape",pageUp:"Page Up",
+pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",
+divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Abrir llave",backSlash:"Backslash",closeBracket:"Cerrar llave",singleQuote:"Comillas simples"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/et.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/et.js
new file mode 100644
index 0000000..2dc5697
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/et.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","et",{title:"Accessibility Instructions",contents:"Abi sisu. Selle dialoogi sulgemiseks vajuta ESC klahvi.",legend:[{name:"Ãœldine",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js
new file mode 100644
index 0000000..81b3e79
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","fa",{title:"دستورالعمل‌های دسترسی",contents:"راهنمای Ùهرست مطالب. برای بستن این کادر محاوره‌ای ESC را Ùشار دهید.",legend:[{name:"عمومی",items:[{name:"نوار ابزار ویرایشگر",legend:"${toolbarFocus} را برای باز کردن نوار ابزار بÙشارید. با کلید Tab Ùˆ Shif-Tab در مجموعه نوار ابزار بعدی Ùˆ قبلی حرکت کنید. برای حرکت در کلید نوار ابزار قبلی Ùˆ بعدی با کلید جهت‌نمای راست Ùˆ Ú†Ù¾ جابجا شوید. کلید Space یا Enter را برای Ùعال کردن کلید نوار ابزار بÙشارید."},{name:"پنجره محاورهای ویرایشگر",
+legend:"در داخل یک پنجره محاورهای، کلید Tab را بÙشارید تا به پنجرهی بعدی بروید، Shift+Tab برای حرکت به Ùیلد قبلی، Ùشردن Enter برای ثبت اطلاعات پنجره، Ùشردن Esc برای لغو پنجره محاورهای Ùˆ برای پنجرههایی Ú©Ù‡ چندین برگه دارند، Ùشردن Alt+F10 جهت رÙتن به Tab-List. در نهایت حرکت به برگه بعدی با Tab یا کلید جهتنمای راست. حرکت به برگه قبلی با Shift+Tab یا کلید جهتنمای Ú†Ù¾. Ùشردن Space یا Enter برای انتخاب یک برگه."},{name:"منوی متنی ویرایشگر",legend:"${contextMenu} یا کلید برنامههای کاربردی را برای باز کردن منوی متن را بÙشارید. سپس میتوانید برای حرکت به گزینه بعدی منو با کلید Tab Ùˆ یا کلید جهتنمای پایین جابجا شوید. حرکت به گزینه قبلی با Shift+Tab یا کلید جهتنمای بالا. Ùشردن Space یا Enter برای انتخاب یک گزینه از منو. باز کردن زیر شاخه گزینه منو جاری با کلید Space یا Enter Ùˆ یا کلید جهتنمای راست Ùˆ Ú†Ù¾. بازگشت به منوی والد با کلید Esc یا کلید جهتنمای Ú†Ù¾. بستن منوی متن با Esc."},
+{name:"جعبه Ùهرست ویرایشگر",legend:"در داخل جعبه لیست، قلم دوم از اقلام لیست بعدی را با TAB Ùˆ یا Arrow Down حرکت دهید. انتقال به قلم دوم از اقلام لیست قبلی را با SHIFT + TAB یا UP ARROW. کلید Space یا ENTER را برای انتخاب گزینه لیست بÙشارید. کلید ESC را برای بستن جعبه لیست بÙشارید."},{name:"ویرایشگر عنصر نوار راه",legend:"برای رÙتن به مسیر عناصر ${elementsPathFocus} را بÙشارید. حرکت به کلید عنصر بعدی با کلید Tab یا کلید جهت‌نمای راست. برگشت به کلید قبلی با Shift+Tab یا کلید جهت‌نمای Ú†Ù¾. Ùشردن Space یا Enter برای انتخاب یک عنصر در ویرایشگر."}]},
+{name:"Ùرمان‌ها",items:[{name:"بازگشت به آخرین Ùرمان",legend:"Ùشردن ${undo}"},{name:"انجام مجدد Ùرمان",legend:"Ùشردن ${redo}"},{name:"Ùرمان درشت کردن متن",legend:"Ùشردن ${bold}"},{name:"Ùرمان کج کردن متن",legend:"Ùشردن ${italic}"},{name:"Ùرمان زیرخطدار کردن متن",legend:"Ùشردن ${underline}"},{name:"Ùرمان پیوند دادن",legend:"Ùشردن ${link}"},{name:"بستن نوار ابزار Ùرمان",legend:"Ùشردن ${toolbarCollapse}"},{name:"دسترسی به Ùرمان محل تمرکز قبلی",legend:"Ùشردن ${accessPreviousSpace} برای دسترسی به نزدیک‌ترین Ùضای قابل دسترسی تمرکز قبل از هشتک، برای مثال: دو عنصر مجاور HR -خط اÙÙ‚ÛŒ-. تکرار کلید ترکیبی برای رسیدن به Ùضاهای تمرکز از راه دور."},
+{name:"دسترسی به Ùضای دستور بعدی",legend:"برای دسترسی به نزدیک‌ترین Ùضای تمرکز غیر قابل دسترس، ${accessNextSpace} را پس از علامت هشتک بÙشارید، برای مثال: دو عنصر مجاور HR -خط اÙÙ‚ÛŒ-. کلید ترکیبی را برای رسیدن به Ùضای تمرکز تکرار کنید."},{name:"راهنمای دسترسی",legend:"Ùشردن ${a11yHelp}"}]}],backspace:"عقبگرد",tab:"برگه",enter:"ورود",shift:"تعویض",ctrl:"کنترل",alt:"دگرساز",pause:"توقÙ",capslock:"Caps Lock",escape:"گریز",pageUp:"صÙحه به بالا",pageDown:"صÙحه به پایین",end:"پایان",home:"خانه",leftArrow:"پیکان Ú†Ù¾",
+upArrow:"پیکان بالا",rightArrow:"پیکان راست",downArrow:"پیکان پایین",insert:"Insert","delete":"Delete",leftWindowKey:"کلید Ú†Ù¾ ویندوز",rightWindowKey:"کلید راست ویندوز",selectKey:"انتخاب کلید",numpad0:"کلید شماره 0",numpad1:"کلید شماره 1",numpad2:"کلید شماره 2",numpad3:"کلید شماره 3",numpad4:"کلید شماره 4",numpad5:"کلید شماره 5",numpad6:"کلید شماره 6",numpad7:"کلید شماره 7",numpad8:"کلید شماره 8",numpad9:"کلید شماره 9",multiply:"ضرب",add:"Add",subtract:"تÙریق",decimalPoint:"نقطه‌ی اعشار",divide:"جدا کردن",
+f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"علامت تساوی",comma:"کاما",dash:"خط تیره",period:"دوره",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js
new file mode 100644
index 0000000..cc73490
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","fi",{title:"Saavutettavuus ohjeet",contents:"Ohjeen sisällöt. Sulkeaksesi tämän dialogin paina ESC.",legend:[{name:"Yleinen",items:[{name:"Editorin työkalupalkki",legend:"Paina ${toolbarFocus} siirtyäksesi työkalupalkkiin. Siirry seuraavaan ja edelliseen työkalupalkin ryhmään TAB ja SHIFT-TAB näppäimillä. Siirry seuraavaan ja edelliseen työkalupainikkeeseen käyttämällä NUOLI OIKEALLE tai NUOLI VASEMMALLE näppäimillä. Paina VÄLILYÖNTI tai ENTER näppäintä aktivoidaksesi työkalupainikkeen."},
+{name:"Editorin dialogi",legend:"Dialogin sisällä, painamalla TAB siirryt seuraavaan dialogin kenttään, painamalla SHIFT+TAB siirryt aiempaan kenttään, painamalla ENTER lähetät dialogin, painamalla ESC peruutat dialogin. Dialogeille joissa on useita välilehtiä, paina ALT+F10 siirtyäksesi välillehtilistaan. Siirtyäksesi seuraavaan välilehteen paina TAB tai NUOLI OIKEALLE. Siirry edelliseen välilehteen painamalla SHIFT+TAB tai nuoli vasemmalle. Paina VÄLILYÖNTI tai ENTER valitaksesi välilehden."},{name:"Editorin oheisvalikko",
+legend:"Paina ${contextMenu} tai SOVELLUSPAINIKETTA avataksesi oheisvalikon. Liiku seuraavaan valikon vaihtoehtoon TAB tai NUOLI ALAS näppäimillä. Siirry edelliseen vaihtoehtoon SHIFT+TAB tai NUOLI YLÖS näppäimillä. Paina VÄLILYÖNTI tai ENTER valitaksesi valikon kohdan. Avataksesi nykyisen kohdan alivalikon paina VÄLILYÖNTI tai ENTER tai NUOLI OIKEALLE painiketta. Siirtyäksesi takaisin valikon ylemmälle tasolle paina ESC tai NUOLI vasemmalle. Oheisvalikko suljetaan ESC painikkeella."},{name:"Editorin listalaatikko",
+legend:"Listalaatikon sisällä siirry seuraavaan listan kohtaan TAB tai NUOLI ALAS painikkeilla. Siirry edelliseen listan kohtaan SHIFT+TAB tai NUOLI YLÖS painikkeilla. Paina VÄLILYÖNTI tai ENTER valitaksesi listan vaihtoehdon. Paina ESC sulkeaksesi listalaatikon."},{name:"Editorin elementtipolun palkki",legend:"Paina ${elementsPathFocus} siirtyäksesi elementtipolun palkkiin. Siirry seuraavaan elementtipainikkeeseen TAB tai NUOLI OIKEALLE painikkeilla. Siirry aiempaan painikkeeseen SHIFT+TAB tai NUOLI VASEMMALLE painikkeilla. Paina VÄLILYÖNTI tai ENTER valitaksesi elementin editorissa."}]},
+{name:"Komennot",items:[{name:"Peruuta komento",legend:"Paina ${undo}"},{name:"Tee uudelleen komento",legend:"Paina ${redo}"},{name:"Lihavoi komento",legend:"Paina ${bold}"},{name:"Kursivoi komento",legend:"Paina ${italic}"},{name:"Alleviivaa komento",legend:"Paina ${underline}"},{name:"Linkki komento",legend:"Paina ${link}"},{name:"Pienennä työkalupalkki komento",legend:"Paina ${toolbarCollapse}"},{name:"Siirry aiempaan fokustilaan komento",legend:"Paina ${accessPreviousSpace} siiryäksesi lähimpään kursorin edellä olevaan saavuttamattomaan fokustilaan, esimerkiksi: kaksi vierekkäistä HR elementtiä. Toista näppäinyhdistelmää päästäksesi kauempana oleviin fokustiloihin."},
+{name:"Siirry seuraavaan fokustilaan komento",legend:"Paina ${accessPreviousSpace} siiryäksesi lähimpään kursorin jälkeen olevaan saavuttamattomaan fokustilaan, esimerkiksi: kaksi vierekkäistä HR elementtiä. Toista näppäinyhdistelmää päästäksesi kauempana oleviin fokustiloihin."},{name:"Saavutettavuus ohjeet",legend:"Paina ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numeronäppäimistö 0",numpad1:"Numeronäppäimistö 1",numpad2:"Numeronäppäimistö 2",numpad3:"Numeronäppäimistö 3",numpad4:"Numeronäppäimistö 4",numpad5:"Numeronäppäimistö 5",numpad6:"Numeronäppäimistö 6",numpad7:"Numeronäppäimistö 7",numpad8:"Numeronäppäimistö 8",
+numpad9:"Numeronäppäimistö 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Puolipiste",equalSign:"Equal Sign",comma:"Pilkku",dash:"Dash",period:"Piste",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js
new file mode 100644
index 0000000..5d7061b
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","fr-ca",{title:"Instructions d'accessibilité",contents:"Contenu de l'aide. Pour fermer cette fenêtre, appuyez sur ESC.",legend:[{name:"Général",items:[{name:"Barre d'outil de l'éditeur",legend:"Appuyer sur ${toolbarFocus} pour accéder à la barre d'outils. Se déplacer vers les groupes suivant ou précédent de la barre d'outil avec les touches TAB et SHIFT-TAB. Se déplacer vers les boutons suivant ou précédent de la barre d'outils avec les touches FLECHE DROITE et FLECHE GAUCHE. Appuyer sur la barre d'espace ou la touche ENTRER pour activer le bouton de barre d'outils."},
+{name:"Dialogue de l'éditeur",legend:"A l'intérieur d'un dialogue, appuyer sur la touche TAB pour naviguer jusqu'au champ de dalogue suivant, appuyez sur les touches SHIFT + TAB pour revenir au champ précédent, appuyez sur la touche ENTRER pour soumettre le dialogue, appuyer sur la touche ESC pour annuler le dialogue. Pour les dialogues avec plusieurs pages d'onglets, appuyer sur ALT + F10 pour naviguer jusqu'à la liste des onglets. Puis se déplacer vers l'onglet suivant avec la touche TAB ou FLECHE DROITE. Se déplacer vers l'onglet précédent avec les touches SHIFT + TAB ou FLECHE GAUCHE. Appuyer sur la barre d'espace ou la touche ENTRER pour sélectionner la page de l'onglet."},
+{name:"Menu contextuel de l'éditeur",legend:"Appuyer sur ${contextMenu} ou entrer le RACCOURCI CLAVIER pour ouvrir le menu contextuel. Puis se déplacer vers l'option suivante du menu avec les touches TAB ou FLECHE BAS. Se déplacer vers l'option précédente avec les touches SHIFT+TAB ou FLECHE HAUT. appuyer sur la BARRE D'ESPACE ou la touche ENTREE pour sélectionner l'option du menu. Oovrir le sous-menu de l'option courante avec la BARRE D'ESPACE ou les touches ENTREE ou FLECHE DROITE. Revenir à l'élément de menu parent avec les touches ESC ou FLECHE GAUCHE. Fermer le menu contextuel avec ESC."},
+{name:"Menu déroulant de l'éditeur",legend:"A l'intérieur d'une liste en menu déroulant, se déplacer vers l'élément suivant de la liste avec les touches TAB ou FLECHE BAS. Se déplacer vers l'élément précédent de la liste avec les touches SHIFT + TAB ou FLECHE HAUT. Appuyer sur la BARRE D'ESPACE ou sur ENTREE pour sélectionner l'option dans la liste. Appuyer sur ESC pour fermer le menu déroulant."},{name:"Barre d'emplacement des éléments de l'éditeur",legend:"Appuyer sur ${elementsPathFocus} pour naviguer vers la barre d'emplacement des éléments de léditeur. Se déplacer vers le bouton d'élément suivant avec les touches TAB ou FLECHE DROITE. Se déplacer vers le bouton d'élément précédent avec les touches SHIFT+TAB ou FLECHE GAUCHE. Appuyer sur la BARRE D'ESPACE ou sur ENTREE pour sélectionner l'élément dans l'éditeur."}]},
+{name:"Commandes",items:[{name:"Annuler",legend:"Appuyer sur ${undo}"},{name:"Refaire",legend:"Appuyer sur ${redo}"},{name:"Gras",legend:"Appuyer sur ${bold}"},{name:"Italique",legend:"Appuyer sur ${italic}"},{name:"Souligné",legend:"Appuyer sur ${underline}"},{name:"Lien",legend:"Appuyer sur ${link}"},{name:"Enrouler la barre d'outils",legend:"Appuyer sur ${toolbarCollapse}"},{name:"Accéder à l'objet de focus précédent",legend:"Appuyer ${accessPreviousSpace} pour accéder au prochain espace disponible avant le curseur, par exemple: deux éléments HR adjacents. Répéter la combinaison pour joindre les éléments d'espaces distantes."},
+{name:"Accéder au prochain objet de focus",legend:"Appuyer ${accessNextSpace} pour accéder au prochain espace disponible après le curseur, par exemple: deux éléments HR adjacents. Répéter la combinaison pour joindre les éléments d'espaces distantes."},{name:"Aide d'accessibilité",legend:"Appuyer sur ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",
+leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",
+f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js
new file mode 100644
index 0000000..e65d98f
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","fr",{title:"Instructions d'accessibilité",contents:"Contenu de l'aide. Pour fermer ce dialogue, appuyez sur la touche Ech (Echappement).",legend:[{name:"Général",items:[{name:"Barre d'outils de l'éditeur",legend:"Appuyer sur ${toolbarFocus} pour accéder à la barre d'outils. Se déplacer vers les groupes suivant ou précédent de la barre d'outil avec les touches TAB et SHIFT-TAB. Se déplacer vers les boutons suivant ou précédent de la barre d'outils avec les touches FLECHE DROITE et FLECHE GAUCHE. Appuyer sur la barre d'espace ou la touche ENTRER pour activer le bouton de barre d'outils."},
+{name:"Dialogue de l'éditeur",legend:"A l'intérieur d'un dialogue, appuyer sur la touche TAB pour naviguer jusqu'au champ de dalogue suivant, appuyez sur les touches SHIFT + TAB pour revenir au champ précédent, appuyez sur la touche ENTRER pour soumettre le dialogue, appuyer sur la touche ESC pour annuler le dialogue. Pour les dialogues avec plusieurs pages d'onglets, appuyer sur ALT + F10 pour naviguer jusqu'à la liste des onglets. Puis se déplacer vers l'onglet suivant avec la touche TAB ou FLECHE DROITE. Se déplacer vers l'onglet précédent avec les touches SHIFT + TAB ou FLECHE GAUCHE. Appuyer sur la barre d'espace ou la touche ENTRER pour sélectionner la page de l'onglet."},
+{name:"Menu contextuel de l'éditeur",legend:"Appuyer sur ${contextMenu} ou entrer le RACCOURCI CLAVIER pour ouvrir le menu contextuel. Puis se déplacer vers l'option suivante du menu avec les touches TAB ou FLECHE BAS. Se déplacer vers l'option précédente avec les touches SHIFT+TAB ou FLECHE HAUT. appuyer sur la BARRE D'ESPACE ou la touche ENTREE pour sélectionner l'option du menu. Oovrir le sous-menu de l'option courante avec la BARRE D'ESPACE ou les touches ENTREE ou FLECHE DROITE. Revenir à l'élément de menu parent avec les touches Ech ou FLECHE GAUCHE. Fermer le menu contextuel avec Ech."},
+{name:"Zone de liste de l'éditeur",legend:"Dans la liste en menu déroulant, se déplacer vers l'élément suivant de la liste avec les touches TAB ou FLECHE BAS. Se déplacer vers l'élément précédent de la liste avec les touches MAJ + TAB ou FLECHE HAUT. Appuyer sur la BARRE D'ESPACE ou sur ENTREE pour sélectionner l'option dans la liste. Appuyer sur ESC pour fermer le menu déroulant."},{name:"Barre d'emplacement des éléments de l'éditeur",legend:"Appuyer sur ${elementsPathFocus} pour naviguer vers la barre d'emplacement des éléments de l'éditeur. Se déplacer vers le bouton d'élément suivant avec les touches TAB ou FLECHE DROITE. Se déplacer vers le bouton d'élément précédent avec les touches MAJ+TAB ou FLECHE GAUCHE. Appuyer sur la BARRE D'ESPACE ou sur ENTREE pour sélectionner l'élément dans l'éditeur."}]},
+{name:"Commandes",items:[{name:" Annuler la commande",legend:"Appuyer sur ${undo}"},{name:"Refaire la commande",legend:"Appuyer sur ${redo}"},{name:" Commande gras",legend:"Appuyer sur ${bold}"},{name:" Commande italique",legend:"Appuyer sur ${italic}"},{name:" Commande souligné",legend:"Appuyer sur ${underline}"},{name:" Commande lien",legend:"Appuyer sur ${link}"},{name:" Commande enrouler la barre d'outils",legend:"Appuyer sur ${toolbarCollapse}"},{name:"Accéder à la précédente commande d'espace de mise au point",
+legend:"Appuyez sur ${accessPreviousSpace} pour accéder à l'espace hors d'atteinte le plus proche avant le caret, par exemple: deux éléments HR adjacents. Répétez la combinaison de touches pour atteindre les espaces de mise au point distants."},{name:"Accès à la prochaine commande de l'espace de mise au point",legend:"Appuyez sur ${accessNextSpace} pour accéder au plus proche espace de mise au point hors d'atteinte après le caret, par exemple: deux éléments HR adjacents. répétez la combinaison de touches pour atteindre les espace de mise au point distants."},
+{name:" Aide Accessibilité",legend:"Appuyer sur ${a11yHelp}"}]}],backspace:"Retour arrière",tab:"Tabulation",enter:"Entrée",shift:"Majuscule",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Verr. Maj.",escape:"Echap",pageUp:"Page supérieure",pageDown:"Page inférieure",end:"Fin",home:"Retour",leftArrow:"Flèche gauche",upArrow:"Flèche haute",rightArrow:"Flèche droite",downArrow:"Flèche basse",insert:"Insertion","delete":"Supprimer",leftWindowKey:"Touche Windows gauche",rightWindowKey:"Touche Windows droite",
+selectKey:"Touche menu",numpad0:"Pavé numérique 0",numpad1:"Pavé numérique 1",numpad2:"Pavé numérique 2",numpad3:"Pavé numérique 3",numpad4:"Pavé numérique 4",numpad5:"Pavé numérique 5",numpad6:"Pavé numérique 6",numpad7:"Pavé numérique 7",numpad8:"Pavé numérique 8",numpad9:"Pavé numérique 9",multiply:"Multiplier",add:"Addition",subtract:"Soustraire",decimalPoint:"Point décimal",divide:"Diviser",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",
+numLock:"Verrouillage numérique",scrollLock:"Arrêt défilement",semiColon:"Point virgule",equalSign:"Signe égal",comma:"Virgule",dash:"Tiret",period:"Point",forwardSlash:"Barre oblique",graveAccent:"Accent grave",openBracket:"Parenthèse ouvrante",backSlash:"Barre oblique inverse",closeBracket:"Parenthèse fermante",singleQuote:"Apostrophe"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js
new file mode 100644
index 0000000..5fb9a0b
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","gl",{title:"Instrucións de accesibilidade",contents:"Axuda. Para pechar este diálogo prema ESC.",legend:[{name:"Xeral",items:[{name:"Barra de ferramentas do editor",legend:"Prema ${toolbarFocus} para navegar pola barra de ferramentas. Para moverse polos distintos grupos de ferramentas use as teclas TAB e MAIÚS+TAB. Para moverse polas distintas ferramentas use FRECHA DEREITA ou FRECHA ESQUERDA. Prema ESPAZO ou INTRO para activar o botón da barra de ferramentas."},
+{name:"Editor de diálogo",legend:"Dentro dun cadro de diálogo, prema a tecla TAB para desprazarse ao campo seguinte do cadro de diálogo, prema MAIÚS + TAB para desprazarse ao campo anterior, prema INTRO para presentar o cadro de diálogo, prema a tecla ESC para cancelar o diálogo. Para os diálogos que teñen varias páxinas, prema ALT + F10 para navegar á lapela da lista. Despois pasar á seguinte lapela con TAB ou FRECHA DEREITA. Para ir á lapela anterior con SHIFT + TAB ou FRECHA ESQUERDA. Prema ESPAZO ou INTRO para seleccionar a lapela da páxina."},
+{name:"Editor do menú contextual",legend:"Prema ${contextMenu} ou a TECLA MENÚ para abrir o menú contextual. A seguir móvase á seguinte opción do menú con TAB ou FRECHA ABAIXO. Móvase á opción anterior con MAIÚS + TAB ou FRECHA ARRIBA. Prema ESPAZO ou INTRO para seleccionar a opción do menú. Abra o submenú da opción actual con ESPAZO ou INTRO ou FRECHA DEREITA. Regrese ao elemento principal do menú con ESC ou FRECHA ESQUERDA. Peche o menú contextual con ESC."},{name:"Lista do editor",legend:"Dentro dunha lista, móvase ao seguinte elemento da lista con TAB ou FRECHA ABAIXO. Móvase ao elemento anterior da lista con MAIÚS + TAB ou FRECHA ARRIBA. Prema ESPAZO ou INTRO para escoller a opción da lista. Prema ESC para pechar a lista."},
+{name:"Barra da ruta ao elemento no editor",legend:"Prema ${elementsPathFocus} para navegar ata os elementos da barra de ruta. Móvase ao seguinte elemento botón con TAB ou FRECHA DEREITA. Móvase ao botón anterior con MAIÚS + TAB ou FRECHA ESQUERDA. Prema ESPAZO ou INTRO para seleccionar o elemento no editor."}]},{name:"Ordes",items:[{name:"Orde «desfacer»",legend:"Prema ${undo}"},{name:"Orde «refacer»",legend:"Prema ${redo}"},{name:"Orde «negra»",legend:"Prema ${bold}"},{name:"Orde «cursiva»",legend:"Prema ${italic}"},
+{name:"Orde «subliñar»",legend:"Prema ${underline}"},{name:"Orde «ligazón»",legend:"Prema ${link}"},{name:"Orde «contraer a barra de ferramentas»",legend:"Prema ${toolbarCollapse}"},{name:"Orde «acceder ao anterior espazo en foco»",legend:"Prema ${accessPreviousSpace} para acceder ao espazo máis próximo de foco inalcanzábel anterior ao cursor, por exemplo: dous elementos HR adxacentes. Repita a combinación de teclas para chegar a espazos de foco distantes."},{name:"Orde «acceder ao seguinte espazo en foco»",
+legend:"Prema ${accessNextSpace} para acceder ao espazo máis próximo de foco inalcanzábel posterior ao cursor, por exemplo: dous elementos HR adxacentes. Repita a combinación de teclas para chegar a espazos de foco distantes."},{name:"Axuda da accesibilidade",legend:"Prema ${a11yHelp}"}]}],backspace:"Ir atrás",tab:"Tabulador",enter:"Intro",shift:"Maiús",ctrl:"Ctrl",alt:"Alt",pause:"Pausa",capslock:"Bloq. Maiús",escape:"Escape",pageUp:"Páxina arriba",pageDown:"Páxina abaixo",end:"Fin",home:"Inicio",
+leftArrow:"Frecha esquerda",upArrow:"Frecha arriba",rightArrow:"Frecha dereita",downArrow:"Frecha abaixo",insert:"Inserir","delete":"Supr",leftWindowKey:"Tecla Windows esquerda",rightWindowKey:"Tecla Windows dereita",selectKey:"Escolla a tecla",numpad0:"Tec. numérico 0",numpad1:"Tec. numérico 1",numpad2:"Tec. numérico 2",numpad3:"Tec. numérico 3",numpad4:"Tec. numérico 4",numpad5:"Tec. numérico 5",numpad6:"Tec. numérico 6",numpad7:"Tec. numérico 7",numpad8:"Tec. numérico 8",numpad9:"Tec. numérico 9",
+multiply:"Multiplicar",add:"Sumar",subtract:"Restar",decimalPoint:"Punto decimal",divide:"Dividir",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Bloq. num.",scrollLock:"Bloq. despraz.",semiColon:"Punto e coma",equalSign:"Signo igual",comma:"Coma",dash:"Guión",period:"Punto",forwardSlash:"Barra inclinada",graveAccent:"Acento grave",openBracket:"Abrir corchete",backSlash:"Barra invertida",closeBracket:"Pechar corchete",singleQuote:"Comiña simple"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js
new file mode 100644
index 0000000..b731a88
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","gu",{title:"àªàª•à«àª•à«àª·à«‡àª¬àª¿àª²àª¿àªŸà«€ ની વિગતો",contents:"હેલà«àªª. આ બંધ કરવા ESC દબાવો.",legend:[{name:"જનરલ",items:[{name:"àªàª¡àª¿àªŸàª° ટૂલબાર",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"àªàª¡àª¿àªŸàª° ડાયલોગ",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"કમાંડસ",items:[{name:"અનà«àª¡à«àª‚ કમાંડ",legend:"$ દબાવો {undo}"},{name:"ફરી કરો કમાંડ",legend:"$ દબાવો {redo}"},{name:"બોલà«àª¦àª¨à«‹ કમાંડ",legend:"$ દબાવો {bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/he.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/he.js
new file mode 100644
index 0000000..5f87d21
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/he.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","he",{title:"הור×ות נגישות",contents:"הור×ות נגישות. לסגירה לחץ ×סקייפ (ESC).",legend:[{name:"כללי",items:[{name:"סרגל הכלי×",legend:"לחץ על ${toolbarFocus} כדי לנווט לסרגל הכלי×. עבור לכפתור ×”×‘× ×¢× ×ž×§×© הט×ב (TAB) ×ו ×—×¥ שמ×לי. עבור לכפתור ×”×§×•×“× ×¢× ×ž×§×© השיפט (SHIFT) + ט×ב (TAB) ×ו ×—×¥ ימני. לחץ רווח ×ו ×נטר (ENTER) כדי להפעיל ×ת הכפתור הנבחר."},{name:"די××œ×•×’×™× (חלונות תש×ול)",legend:"בתוך די×לוג, לחץ ט×ב (TAB) כדי לנווט לשדה הב×, לחץ שיפט (SHIFT) + ט×ב (TAB) כדי לנווט לשדה הקוד×, לחץ ×נטר (ENTER) כדי לשלוח ×ת הדי×לוג, לחץ ×סקייפ (ESC) כדי לבטל. בתוך די××œ×•×’×™× ×‘×¢×œ×™ מספר ט××‘×™× (לשוניות), לחץ ×לט (ALT) + F10 כדי לנווט לשורת הט×בי×. נווט לט×ב ×”×‘× ×¢× ×˜×ב (TAB) ×ו ×—×¥ שמ×לי. עבור לט×ב ×”×§×•×“× ×¢× ×©×™×¤×˜ (SHIFT) + ט×ב (TAB) ×ו ×—×¥ שמ×לי. לחץ רווח ×ו ×נטר (ENTER) כדי להיכנס לט×ב."},
+{name:"תפריט ההקשר (Context Menu)",legend:"לחץ ${contextMenu} ×ו APPLICATION KEYכדי לפתוח ×ת תפריט ההקשר. עבור ל×פשרות הב××” ×¢× ×˜×ב (TAB) ×ו ×—×¥ למטה. עבור ל×פשרות הקודמת ×¢× ×©×™×¤×˜ (SHIFT) + ט×ב (TAB) ×ו ×—×¥ למעלה. לחץ רווח ×ו ×נטר (ENTER) כדי לבחור ×ת ×”×פשרות. פתח ×ת תת התפריט (Sub-menu) של ×”×פשרות הנוכחית ×¢× ×¨×•×•×— ×ו ×נטר (ENTER) ×ו ×—×¥ שמ×לי. חזור לתפריט ×”×ב ×¢× ×סקייפ (ESC) ×ו ×—×¥ שמ×לי. סגור ×ת תפריט ההקשר ×¢× ×סקייפ (ESC)."},{name:"×ª×¤×¨×™×˜×™× ×¦×¤×™× (List boxes)",legend:"בתוך תפריט צף, עבור לפריט ×”×‘× ×¢× ×˜×ב (TAB) ×ו ×—×¥ למטה. עבור לתפריט ×”×§×•×“× ×¢× ×©×™×¤×˜ (SHIFT) + ט×ב (TAB) or ×—×¥ עליון. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"×¢×¥ ××œ×ž× ×˜×™× (Elements Path)",legend:"לחץ ${elementsPathFocus} כדי לנווט לעץ ×”×למנטי×. עבור לפריט ×”×‘× ×¢× ×˜×ב (TAB) ×ו ×—×¥ ימני. עבור לפריט ×”×§×•×“× ×¢× ×©×™×¤×˜ (SHIFT) + ט×ב (TAB) ×ו ×—×¥ שמ×לי. לחץ רווח ×ו ×נטר (ENTER) כדי לבחור ×ת ×”×למנט בעורך."}]},{name:"פקודות",items:[{name:" ביטול צעד ×חרון",legend:"לחץ ${undo}"},{name:" חזרה על צעד ×חרון",legend:"לחץ ${redo}"},{name:" הדגשה",legend:"לחץ ${bold}"},{name:" הטייה",legend:"לחץ ${italic}"},{name:" הוספת קו תחתון",legend:"לחץ ${underline}"},{name:" הוספת לינק",
+legend:"לחץ ${link}"},{name:" כיווץ סרגל הכלי×",legend:"לחץ ${toolbarCollapse}"},{name:"גישה ×œ×ž×™×§×•× ×”×ž×™×§×•×“ הקוד×",legend:"לחץ ${accessPreviousSpace} כדי לגשת ×œ×ž×™×§×•× ×”×ž×™×§×•×“ הל×-נגיש הקרוב לפני הסמן, למשל בין שני ××œ×ž× ×˜×™× ×¡×ž×•×›×™× ×ž×¡×•×’ HR. חזור על צירוף ×ž×§×©×™× ×–×” כדי להגיע למקומות מיקוד ×¨×—×•×§×™× ×™×•×ª×¨."},{name:"גישה ×œ×ž×™×§×•× ×”×ž×™×§×•×“ הב×",legend:"לחץ ${accessNextSpace} כדי לגשת ×œ×ž×™×§×•× ×”×ž×™×§×•×“ הל×-נגיש הקרוב ×חרי הסמן, למשל בין שני ××œ×ž× ×˜×™× ×¡×ž×•×›×™× ×ž×¡×•×’ HR. חזור על צירוף ×ž×§×©×™× ×–×” כדי להגיע למקומות מיקוד ×¨×—×•×§×™× ×™×•×ª×¨."},
+{name:" הור×ות נגישות",legend:"לחץ ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"×—×¥ שמ×לה",upArrow:"×—×¥ למעלה",rightArrow:"×—×¥ ימינה",downArrow:"×—×¥ למטה",insert:"הכנס","delete":"מחק",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"בחר מקש",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",
+numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"הוסף",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"סל×ש",graveAccent:"Grave Accent",
+openBracket:"Open Bracket",backSlash:"סל×ש הפוך",closeBracket:"Close Bracket",singleQuote:"ציטוט יחיד"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js
new file mode 100644
index 0000000..ef50a4b
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","hi",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"सामानà¥à¤¯",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js
new file mode 100644
index 0000000..5c8954f
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","hr",{title:"Upute dostupnosti",contents:"Sadržaj pomoći. Za zatvaranje pritisnite ESC.",legend:[{name:"Općenito",items:[{name:"Alatna traka",legend:"Pritisni ${toolbarFocus} za navigaciju do alatne trake. Pomicanje do prethodne ili sljedeće alatne grupe vrši se pomoću SHIFT-TAB i TAB. Pomicanje do prethodnog ili sljedećeg gumba u alatnoj traci vrši se pomoću lijeve i desne strelice kursora. Pritisnite SPACE ili ENTER za aktivaciju alatne trake."},{name:"Dijalog",
+legend:"Unutar dijaloga, pritisnite TAB za navigaciju do sljedećeg polja, pritisnite SHIFT + TAB za vraćanje na prethodno polje, pritisnite ENTER za slanje dijaloga ili ESC za zatvaranje dijaloga. Za dijaloge koji imaju višestruke kartice, pritisnite ALT + F10 za na navigaciju i zatim TAB ili lijeva strelica kursora ili SHIFT + TAB i desna strelica kursora. SPACE ili ENTER odabiru karticu."},{name:"Kontekstni izbornik",legend:"Pritisnite ${contextMenu} ili APPLICATION tipku za otvaranje kontekstnog izbornika. Pomicanje se vrši TAB ili strelicom kursora prema dolje ili SHIFT+TAB ili strelica kursora prema gore. SPACE ili ENTER odabiru opciju izbornika. Otvorite podizbornik trenutne opcije sa SPACE, ENTER ili desna strelica kursora. Povratak na prethodni izbornik vrši se sa ESC ili lijevom strelicom kursora. Zatvaranje se vrši pritiskom na tipku ESC."},
+{name:"Lista",legend:"Unutar list-boxa, pomicanje na sljedeću stavku vrši se sa TAB ili strelica kursora prema dolje. Na prethodnu sa SHIFT + TAB ili strelica prema gore. Pritiskom na SPACE ili ENTER odabire se stavka ili ESC za zatvaranje."},{name:"Traka putanje elemenata",legend:"Pritisnite ${elementsPathFocus} za navigaciju po putanji elemenata. Pritisnite TAB ili desnu strelicu kursora za pomicanje na sljedeći element ili SHIFT + TAB ili lijeva strelica kursora za pomicanje na prethodni element. Pritiskom na SPACE ili ENTER vrši se odabir elementa."}]},
+{name:"Naredbe",items:[{name:"Vrati naredbu",legend:"Pritisni ${undo}"},{name:"Ponovi naredbu",legend:"Pritisni ${redo}"},{name:"Bold naredba",legend:"Pritisni ${bold}"},{name:"Italic naredba",legend:"Pritisni ${italic}"},{name:"Underline naredba",legend:"Pritisni ${underline}"},{name:"Link naredba",legend:"Pritisni ${link}"},{name:"Smanji alatnu traku naredba",legend:"Pritisni ${toolbarCollapse}"},{name:"Access previous focus space naredba",legend:"Pritisni ${accessPreviousSpace} za pristup najbližem nedostupnom razmaku prije kursora, npr.: dva spojena HR elementa. Ponovnim pritiskom dohvatiti će se sljedeći nedostupni razmak."},
+{name:"Access next focus space naredba",legend:"Pritisni ${accessNextSpace} za pristup najbližem nedostupnom razmaku nakon kursora, npr.: dva spojena HR elementa. Ponovnim pritiskom dohvatiti će se sljedeći nedostupni razmak."},{name:"Pomoć za dostupnost",legend:"Pritisni ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",
+upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",
+f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js
new file mode 100644
index 0000000..5cda07c
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","hu",{title:"KisegítÅ‘ utasítások",contents:"Súgó tartalmak. A párbeszédablak bezárásához nyomjon ESC-et.",legend:[{name:"Ãltalános",items:[{name:"SzerkesztÅ‘ Eszköztár",legend:"Nyomjon ${toolbarFocus} hogy kijelölje az eszköztárat. A következÅ‘ és elÅ‘zÅ‘ eszköztár csoporthoz a TAB és SHIFT TAB-al juthat el. A következÅ‘ és elÅ‘zÅ‘ eszköztár gombhoz a BAL NYÃL vagy JOBB NYÃL gombbal juthat el. Nyomjon SPACE-t vagy ENTER-t hogy aktiválja az eszköztár gombot."},{name:"SzerkeszÅ‘ párbeszéd ablak",
+legend:"Párbeszédablakban nyomjon TAB-ot a következÅ‘ párbeszédmezÅ‘höz ugráshoz, nyomjon SHIFT + TAB-ot az elÅ‘zÅ‘ mezÅ‘höz ugráshoz, nyomjon ENTER-t a párbeszédablak elfogadásához, nyomjon ESC-et a párbeszédablak elvetéséhez. Azokhoz a párbeszédablakokhoz, amik több fület tartalmaznak, nyomjon ALT + F10-et hogy a fülekre ugorjon. Ezután a TAB-al vagy a JOBB NYÃLLAL a következÅ‘ fülre ugorhat. Az elÅ‘zÅ‘ fülre ugráshoz használja a SHIFT + TAB-ot vagy a BAL NYILAT. Nyomjon SPACE-t vagy ENTER-t hogy kijelölje a fület."},
+{name:"SzerkesztÅ‘ helyi menü",legend:"Nyomjon ${contextMenu}-t vagy ALKALMAZÃS BILLENTYÅ°T a helyi menü megnyitásához. Ezután a következÅ‘ menüpontra léphet a TAB vagy LEFELÉ NYÃLLAL. Az elÅ‘zÅ‘ opciót a SHIFT+TAB vagy FELFELÉ NYÃLLAL érheti el. Nyomjon SPACE-t vagy ENTER-t a menüpont kiválasztásához. A jelenlegi menüpont almenüjének megnyitásához nyomjon SPACE-t vagy ENTER-t, vagy JOBB NYILAT. A fÅ‘menühöz való visszatéréshez nyomjon ESC-et vagy BAL NYILAT. A helyi menü bezárása az ESC billentyűvel lehetséges."},
+{name:"SzerkesztÅ‘ lista",legend:"A listán belül a következÅ‘ elemre a TAB vagy LEFELÉ NYÃLLAL mozoghat. Az elÅ‘zÅ‘ elem kiválasztásához nyomjon SHIFT+TAB-ot vagy FELFELÉ NYILAT. Nyomjon SPACE-t vagy ENTER-t az elem kiválasztásához. Az ESC billentyű megnyomásával bezárhatja a listát."},{name:"SzerkesztÅ‘ elem utak sáv",legend:"Nyomj ${elementsPathFocus} hogy kijelöld a elemek út sávját. A következÅ‘ elem gombhoz a TAB-al vagy a JOBB NYÃLLAL juthatsz el. Az elÅ‘zÅ‘ gombhoz a SHIFT+TAB vagy BAL NYÃLLAL mehetsz. A SPACE vagy ENTER billentyűvel kiválaszthatod az elemet a szerkesztÅ‘ben."}]},
+{name:"Parancsok",items:[{name:"Parancs visszavonása",legend:"Nyomj ${undo}"},{name:"Parancs megismétlése",legend:"Nyomjon ${redo}"},{name:"Félkövér parancs",legend:"Nyomjon ${bold}"},{name:"Dőlt parancs",legend:"Nyomjon ${italic}"},{name:"Aláhúzott parancs",legend:"Nyomjon ${underline}"},{name:"Link parancs",legend:"Nyomjon ${link}"},{name:"Szerkesztősáv összecsukása parancs",legend:"Nyomjon ${toolbarCollapse}"},{name:"Hozzáférés az előző fókusz helyhez parancs",legend:"Nyomj ${accessNextSpace} hogy hozzáférj a legközelebbi elérhetetlen fókusz helyhez a hiányjel előtt, például: két szomszédos HR elemhez. Ismételd meg a billentyűkombinációt hogy megtaláld a távolabbi fókusz helyeket."},
+{name:"Hozzáférés a következő fókusz helyhez parancs",legend:"Nyomj ${accessNextSpace} hogy hozzáférj a legközelebbi elérhetetlen fókusz helyhez a hiányjel után, például: két szomszédos HR elemhez. Ismételd meg a billentyűkombinációt hogy megtaláld a távolabbi fókusz helyeket."},{name:"Kisegítő súgó",legend:"Nyomjon ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"balra nyíl",upArrow:"felfelé nyíl",rightArrow:"jobbra nyíl",downArrow:"lefelé nyíl",insert:"Insert","delete":"Delete",leftWindowKey:"bal Windows-billentyű",rightWindowKey:"jobb Windows-billentyű",selectKey:"Billentyű választása",numpad0:"Számbillentyűk 0",numpad1:"Számbillentyűk 1",numpad2:"Számbillentyűk 2",numpad3:"Számbillentyűk 3",numpad4:"Számbillentyűk 4",numpad5:"Számbillentyűk 5",numpad6:"Számbillentyűk 6",numpad7:"Számbillentyűk 7",numpad8:"Számbillentyűk 8",
+numpad9:"Számbillentyűk 9",multiply:"Szorzás",add:"Hozzáadás",subtract:"Kivonás",decimalPoint:"Tizedespont",divide:"Osztás",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Pontosvessző",equalSign:"Egyenlőségjel",comma:"Vessző",dash:"Kötőjel",period:"Pont",forwardSlash:"Perjel",graveAccent:"Visszafelé dőlő ékezet",openBracket:"Nyitó szögletes zárójel",backSlash:"fordított perjel",closeBracket:"Záró szögletes zárójel",
+singleQuote:"szimpla idézőjel"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/id.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/id.js
new file mode 100644
index 0000000..d0b62f4
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/id.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","id",{title:"Accessibility Instructions",contents:"Bantuan. Tekan ESC untuk menutup dialog ini.",legend:[{name:"Umum",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/it.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/it.js
new file mode 100644
index 0000000..c542d15
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/it.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","it",{title:"Istruzioni di Accessibilità",contents:"Contenuti di Aiuto. Per chiudere questa finestra premi ESC.",legend:[{name:"Generale",items:[{name:"Barra degli strumenti Editor",legend:"Premi ${toolbarFocus} per navigare fino alla barra degli strumenti. Muoviti tra i gruppi della barra degli strumenti con i tasti Tab e Maiusc-Tab. Spostati tra il successivo ed il precedente pulsante della barra degli strumenti usando le frecce direzionali Destra e Sinistra. Premi Spazio o Invio per attivare il pulsante della barra degli strumenti."},
+{name:"Finestra Editor",legend:"All'interno di una finestra di dialogo, premi Tab per navigare fino al campo successivo della finestra di dialogo, premi Maiusc-Tab per tornare al campo precedente, premi Invio per inviare la finestra di dialogo, premi Esc per uscire. Per le finestre che hanno schede multiple, premi Alt+F10 per navigare nella lista delle schede. Quindi spostati alla scheda successiva con il tasto Tab oppure con la Freccia Destra. Torna alla scheda precedente con Maiusc+Tab oppure con la Freccia Sinistra. Premi Spazio o Invio per scegliere la scheda."},
+{name:"Menù contestuale Editor",legend:"Premi ${contextMenu} o TASTO APPLICAZIONE per aprire il menu contestuale. Dunque muoviti all'opzione successiva del menu con il tasto TAB o con la Freccia Sotto. Muoviti all'opzione precedente con MAIUSC+TAB o con Freccia Sopra. Premi SPAZIO o INVIO per scegliere l'opzione di menu. Apri il sottomenu dell'opzione corrente con SPAZIO o INVIO oppure con la Freccia Destra. Torna indietro al menu superiore con ESC oppure Freccia Sinistra. Chiudi il menu contestuale con ESC."},
+{name:"Box Lista Editor",legend:"Dentro un box-lista, muoviti al prossimo elemento della lista con TAB o con la Freccia direzionale giù. Spostati all'elemento precedente con MAIUSC+TAB oppure con Freccia direzionale sopra. Premi SPAZIO o INVIO per scegliere l'opzione della lista. Premi ESC per chiudere il box-lista."},{name:"Barra percorso elementi editor",legend:"Premi ${elementsPathFocus} per navigare tra gli elementi della barra percorso. Muoviti al prossimo pulsante di elemento con TAB o la Freccia direzionale destra. Muoviti al pulsante precedente con MAIUSC+TAB o la Freccia Direzionale Sinistra. Premi SPAZIO o INVIO per scegliere l'elemento nell'editor."}]},
+{name:"Comandi",items:[{name:" Annulla comando",legend:"Premi ${undo}"},{name:" Ripeti comando",legend:"Premi ${redo}"},{name:" Comando Grassetto",legend:"Premi ${bold}"},{name:" Comando Corsivo",legend:"Premi ${italic}"},{name:" Comando Sottolineato",legend:"Premi ${underline}"},{name:" Comando Link",legend:"Premi ${link}"},{name:" Comando riduci barra degli strumenti",legend:"Premi ${toolbarCollapse}"},{name:"Comando di accesso al precedente spazio di focus",legend:"Premi ${accessPreviousSpace} per accedere il più vicino spazio di focus non raggiungibile prima del simbolo caret, per esempio due elementi HR adiacenti. Ripeti la combinazione di tasti per raggiungere spazi di focus distanti."},
+{name:"Comando di accesso al prossimo spazio di focus",legend:"Premi ${accessNextSpace} per accedere il più vicino spazio di focus non raggiungibile dopo il simbolo caret, per esempio due elementi HR adiacenti. Ripeti la combinazione di tasti per raggiungere spazi di focus distanti."},{name:" Aiuto Accessibilità",legend:"Premi ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Invio",shift:"Maiusc",ctrl:"Ctrl",alt:"Alt",pause:"Pausa",capslock:"Bloc Maiusc",escape:"Esc",pageUp:"Pagina sù",pageDown:"Pagina giù",
+end:"Fine",home:"Inizio",leftArrow:"Freccia sinistra",upArrow:"Freccia su",rightArrow:"Freccia destra",downArrow:"Freccia giù",insert:"Ins","delete":"Canc",leftWindowKey:"Tasto di Windows sinistro",rightWindowKey:"Tasto di Windows destro",selectKey:"Tasto di selezione",numpad0:"0 sul tastierino numerico",numpad1:"1 sul tastierino numerico",numpad2:"2 sul tastierino numerico",numpad3:"3 sul tastierino numerico",numpad4:"4 sul tastierino numerico",numpad5:"5 sul tastierino numerico",numpad6:"6 sul tastierino numerico",
+numpad7:"7 sul tastierino numerico",numpad8:"8 sul tastierino numerico",numpad9:"9 sul tastierino numerico",multiply:"Moltiplicazione",add:"Più",subtract:"Sottrazione",decimalPoint:"Punto decimale",divide:"Divisione",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Bloc Num",scrollLock:"Bloc Scorr",semiColon:"Punto-e-virgola",equalSign:"Segno di uguale",comma:"Virgola",dash:"Trattino",period:"Punto",forwardSlash:"Barra",graveAccent:"Accento grave",
+openBracket:"Parentesi quadra aperta",backSlash:"Barra rovesciata",closeBracket:"Parentesi quadra chiusa",singleQuote:"Apostrofo"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js
new file mode 100644
index 0000000..8114784
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ja",{title:"ユーザー補助ã®èª¬æ˜Ž",contents:"ヘルプ ã“ã®ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã‚’é–‰ã˜ã‚‹ã«ã¯ ESCを押ã—ã¦ãã ã•ã„。",legend:[{name:"全般",items:[{name:"エディターツールãƒãƒ¼",legend:"${toolbarFocus} を押ã™ã¨ãƒ„ールãƒãƒ¼ã®ã‚ªãƒ³/オフæ“作ãŒã§ãã¾ã™ã€‚カーソルをツールãƒãƒ¼ã®ã‚°ãƒ«ãƒ¼ãƒ—ã§ç§»å‹•ã•ã›ã‚‹ã«ã¯Tabã‹SHIFT+Tabを押ã—ã¾ã™ã€‚グループ内ã§ã‚«ãƒ¼ã‚½ãƒ«ã‚’移動ã•ã›ã‚‹ã«ã¯ã€å³ã‚«ãƒ¼ã‚½ãƒ«ã‹å·¦ã‚«ãƒ¼ã‚½ãƒ«ã‚’押ã—ã¾ã™ã€‚スペースキーやエンターを押ã™ã¨ãƒœã‚¿ãƒ³ã‚’有効/無効ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"},{name:"編集ダイアログ",legend:"ダイヤログ内ã§ã¯ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã®æ¬¡ã®é¸æŠžè‚¢ã«ç§»å‹•ã™ã‚‹ã«ã¯Tabを押ã—ã¾ã™ã€‚å‰ã®é¸æŠžè‚¢ã«ç§»å‹•ã™ã‚‹ã«ã¯ã€SHIFT+Tabを押ã—ã¾ã™ã€‚ダイアログを決定ã™ã‚‹ã«ã¯ã€ENTERを押ã—ã¾ã™ã€‚ESCã§ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã‚’キャンセルã§ãã¾ã™ã€‚複数ã®ã‚¿ãƒ–ãŒã‚るダイアログã§ã¯ã‚¿ãƒ–リストをæ“作ã™ã‚‹ã«ã¯ALT+F10を押ã—ã¾ã™ã€‚次ã®ã‚¿ãƒ–ã«ç§»å‹•ã™ã‚‹ã«ã¯Tabã‹å³ã‚«ãƒ¼ã‚½ãƒ«ã€å‰ã®ã‚¿ãƒ–ã«æˆ»ã‚‹ã«ã¯SHIFT+Tabã‹å·¦ã‚«ãƒ¼ã‚½ãƒ«ã§ã™ã€‚タブページを決定ã™ã‚‹ã«ã¯ã‚¹ãƒšãƒ¼ã‚¹ã‚‚ã—ãã¯ã€ENTERキーを押ã—ã¦ãã ã•ã„。"},
+{name:"エディターã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼",legend:"${contextMenu} キーã‹APPLICATION KEYを押ã™ã¨ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼ãŒé–‹ãã¾ã™ã€‚Tabã‹ä¸‹ã‚«ãƒ¼ã‚½ãƒ«ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼ã®ã‚ªãƒ—ションé¸æŠžãŒä¸‹ã«ç§»å‹•ã—ã¾ã™ã€‚戻るã«ã¯ã€SHIFT+Tabã‹ä¸Šã‚«ãƒ¼ã‚½ãƒ«ã§ã™ã€‚スペースもã—ãã¯ENTERキーã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼ã‚ªãƒ—ションを決定ã§ãã¾ã™ã€‚ç¾åœ¨é¸ã‚“ã§ã„るオプションã®ã‚µãƒ–メニューを開ãã«ã¯ã€ã‚¹ãƒšãƒ¼ã‚¹ã€ã‚‚ã—ãã¯å³ã‚«ãƒ¼ã‚½ãƒ«ã‚’押ã—ã¾ã™ã€‚サブメニューã‹ã‚‰è¦ªãƒ¡ãƒ‹ãƒ¥ãƒ¼ã«æˆ»ã‚‹ã«ã¯ã€ESCã‹å·¦ã‚«ãƒ¼ã‚½ãƒ«ã‚’押ã—ã¦ãã ã•ã„。ESCã§ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼è‡ªä½“をキャンセルã§ãã¾ã™ã€‚"},{name:"エディターリストボックス",legend:"リストボックス内ã§ç§»å‹•ã™ã‚‹ã«ã¯ã€Tabã‹ä¸‹ã‚«ãƒ¼ã‚½ãƒ«ã§æ¬¡ã®ã‚¢ã‚¤ãƒ†ãƒ ã¸ç§»å‹•ã—ã¾ã™ã€‚SHIFT+Tabã§å‰ã®ã‚¢ã‚¤ãƒ†ãƒ ã«æˆ»ã‚Šã¾ã™ã€‚リストã®ã‚ªãƒ—ションをé¸æŠžã™ã‚‹ã«ã¯ã€ã‚¹ãƒšãƒ¼ã‚¹ã‚‚ã—ãã¯ã€ENTERを押ã—ã¦ãã ã•ã„。リストボックスを閉ã˜ã‚‹ã«ã¯ã€ESCを押ã—ã¦ãã ã•ã„。"},{name:"エディターè¦ç´ ãƒ‘スãƒãƒ¼",legend:"${elementsPathFocus} を押ã™ã¨ã‚¨ãƒ¬ãƒ¡ãƒ³ãƒˆãƒ‘スãƒãƒ¼ã‚’æ“作出æ¥ã¾ã™ã€‚Tabã‹å³ã‚«ãƒ¼ã‚½ãƒ«ã§æ¬¡ã®ã‚¨ãƒ¬ãƒ¡ãƒ³ãƒˆã‚’é¸æŠžã§ãã¾ã™ã€‚å‰ã®ã‚¨ãƒ¬ãƒ¡ãƒ³ãƒˆã‚’é¸æŠžã™ã‚‹ã«ã¯ã€SHIFT+Tabã‹å·¦ã‚«ãƒ¼ã‚½ãƒ«ã§ã™ã€‚スペースもã—ãã¯ã€ENTERã§ã‚¨ãƒ‡ã‚£ã‚¿å†…ã®å¯¾è±¡ã‚¨ãƒ¬ãƒ¡ãƒ³ãƒˆã‚’é¸æŠžå‡ºæ¥ã¾ã™ã€‚"}]},
+{name:"コマンド",items:[{name:"å…ƒã«æˆ»ã™",legend:"${undo} をクリック"},{name:"ã‚„ã‚Šç›´ã—",legend:"${redo} をクリック"},{name:"太字",legend:"${bold} をクリック"},{name:"斜体 ",legend:"${italic} をクリック"},{name:"下線",legend:"${underline} をクリック"},{name:"リンク",legend:"${link} をクリック"},{name:"ツールãƒãƒ¼ã‚’縮ã‚ã‚‹",legend:"${toolbarCollapse} をクリック"},{name:"å‰ã®ã‚«ãƒ¼ã‚½ãƒ«ç§»å‹•ã®ã§ããªã„ãƒã‚¤ãƒ³ãƒˆã¸",legend:"${accessPreviousSpace} を押ã™ã¨ã‚«ãƒ¼ã‚½ãƒ«ã‚ˆã‚Šå‰ã«ã‚るカーソルキーã§å…¥ã‚Šè¾¼ã‚ãªã„スペースã¸ç§»å‹•ã§ãã¾ã™ã€‚例ãˆã°ã€HRエレメントãŒ2ã¤æŽ¥ã—ã¦ã„ã‚‹å ´åˆãªã©ã§ã™ã€‚離れãŸå ´æ‰€ã¸ã¯ã€è¤‡æ•°å›žã‚­ãƒ¼ã‚’押ã—ã¾ã™ã€‚"},{name:"次ã®ã‚«ãƒ¼ã‚½ãƒ«ç§»å‹•ã®ã§ããªã„ãƒã‚¤ãƒ³ãƒˆã¸",legend:"${accessNextSpace} を押ã™ã¨ã‚«ãƒ¼ã‚½ãƒ«ã‚ˆã‚Šå¾Œã‚ã«ã‚るカーソルキーã§å…¥ã‚Šè¾¼ã‚ãªã„スペースã¸ç§»å‹•ã§ãã¾ã™ã€‚例ãˆã°ã€HRエレメントãŒ2ã¤æŽ¥ã—ã¦ã„ã‚‹å ´åˆãªã©ã§ã™ã€‚離れãŸå ´æ‰€ã¸ã¯ã€è¤‡æ•°å›žã‚­ãƒ¼ã‚’押ã—ã¾ã™ã€‚"},
+{name:"ユーザー補助ヘルプ",legend:"${a11yHelp} をクリック"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"左矢å°",upArrow:"上矢å°",rightArrow:"å³çŸ¢å°",downArrow:"下矢å°",insert:"Insert","delete":"Delete",leftWindowKey:"å·¦Windowキー",rightWindowKey:"å³ã®Windowキー",selectKey:"Select",numpad0:"Num 0",numpad1:"Num 1",numpad2:"Num 2",numpad3:"Num 3",numpad4:"Num 4",numpad5:"Num 5",
+numpad6:"Num 6",numpad7:"Num 7",numpad8:"Num 8",numpad9:"Num 9",multiply:"掛ã‘ã‚‹",add:"足ã™",subtract:"引ã",decimalPoint:"å°æ•°ç‚¹",divide:"割る",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"セミコロン",equalSign:"イコール記å·",comma:"カンマ",dash:"ダッシュ",period:"ピリオド",forwardSlash:"フォワードスラッシュ",graveAccent:"グレイヴアクセント",openBracket:"é–‹ãカッコ",backSlash:"ãƒãƒƒã‚¯ã‚¹ãƒ©ãƒƒã‚·ãƒ¥",closeBracket:"é–‰ã˜ã‚«ãƒƒã‚³",singleQuote:"シングルクォート"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/km.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/km.js
new file mode 100644
index 0000000..1b32ae4
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/km.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","km",{title:"Accessibility Instructions",contents:"មាážáž·áž€áž¶â€‹áž‡áŸ†áž“ួយ។ ដើម្បី​បិទ​ផ្ទាំង​នáŸáŸ‡ សូម​ចុច ESC ។",legend:[{name:"ទូទៅ",items:[{name:"របារ​ឧបករណáŸâ€‹áž€áž˜áŸ’មវិធី​និពន្ធ",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"ផ្ទាំង​កម្មវិធីនិពន្ធ",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"ម៉ីនុយបរិបទអ្នកកែសម្រួល",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"ប្រអប់បញ្ជីអ្នកកែសម្រួល",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"ពាក្យបញ្ជា",items:[{name:"ការ​បញ្ជា​មិនធ្វើវិញ",legend:"ចុច ${undo}"},{name:"ការបញ្ជា​ធ្វើវិញ",legend:"ចុច ${redo}"},{name:"ការបញ្ជា​អក្សរ​ដិáž",legend:"ចុច ${bold}"},{name:"ការបញ្ជា​អក្សរ​ទ្រáŸáž",legend:"ចុច ${italic}"},{name:"ពាក្យបញ្ជា​បន្ទាážáŸ‹â€‹áž–ីក្រោម",
+legend:"ចុច ${underline}"},{name:"ពាក្យបញ្ជា​ážáŸ†ážŽ",legend:"ចុច ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:"ជំនួយ​ពី​ភាព​ងាយស្រួល",legend:"ជួយ ${a11yHelp}"}]}],backspace:"លុបážáž™áž€áŸ’រោយ",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"ផ្អាក",capslock:"Caps Lock",escape:"ចាកចáŸáž‰",pageUp:"ទំពáŸážšâ€‹áž›áž¾",pageDown:"ទំពáŸážšâ€‹áž€áŸ’រោម",end:"ចុង",home:"ផ្ទះ",leftArrow:"ព្រួញ​ឆ្វáŸáž„",upArrow:"ព្រួញ​លើ",rightArrow:"ព្រួញ​ស្ដាំ",downArrow:"ព្រួញ​ក្រោម",insert:"បញ្ចូល","delete":"លុប",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"ជ្រើស​គ្រាប់​ចុច",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"គុណ",add:"បន្ážáŸ‚ម",subtract:"ដក",decimalPoint:"ចំណុចទសភាគ",divide:"ចែក",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"បិទ​រំកិល",semiColon:"ចុច​ក្បៀស",equalSign:"សញ្ញា​អឺរ៉ូ",comma:"ក្បៀស",dash:"Dash",period:"ចុច",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",
+openBracket:"ážáž„្កៀប​បើក",backSlash:"Backslash",closeBracket:"ážáž„្កៀប​បិទ",singleQuote:"បន្ážáž€áŸ‹â€‹áž˜áž½áž™"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js
new file mode 100644
index 0000000..9dbb19f
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ko",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"ì¼ë°˜",items:[{name:"편집기 툴바",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"편집기 다ì´ì–¼ë¡œê·¸",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"편집기 환경 메뉴",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"편집기 ëª©ë¡ ë°•ìŠ¤",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"명령",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js
new file mode 100644
index 0000000..d3729c9
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ku",{title:"ڕێنمای لەبەردەستدابوون",contents:"پێکهاتەی یارمەتی. کلیك ESC بۆ داخستنی ئەم دیالۆگه.",legend:[{name:"گشتی",items:[{name:"تووڵامرازی دەستكاریكەر",legend:"کلیك ${toolbarFocus} بۆ ڕابەری تووڵامراز. بۆ گواستنەوەی پێشوو داهاتووی گرووپی تووڵامرازی داگرتنی کلیلی TAB لەگەڵ‌ SHIFT-TAB. بۆ گواستنەوەی پێشوو داهاتووی دووگمەی تووڵامرازی لەڕێی کلیلی تیری دەستی ڕاست یان کلیلی تیری دەستی چەپ. کلیکی کلیلی SPACE یان ENTER بۆ چالاککردنی دووگمەی تووڵامراز."},{name:"دیالۆگی دەستكاریكەر",
+legend:"لەهەمانکاتدا کە تۆ لەدیالۆگی, کلیکی کلیلی TAB بۆ ڕابەری خانەی دیالۆگێکی تر, داگرتنی کلیلی SHIFT + TAB بۆ گواستنەوەی بۆ خانەی پێشووتر, کلیكی کلیلی ENTER بۆ ڕازیکردنی دیالۆگەکە, کلیكی کلیلی ESC بۆ هەڵوەشاندنەوەی دیالۆگەکە. بۆ دیالۆگی لەبازدەری (تابی) زیاتر, کلیكی کلیلی ALT + F10 بۆ ڕابەری لیستی بازدەرەکان. بۆ چوونە بازدەری تابی داهاتوو کلیكی کلیلی TAB یان کلیلی تیری دەستی ڕاست. بۆچوونە بازدەری تابی پێشوو داگرتنی کلیلی SHIFT + TAB یان کلیلی تیری دەستی چەپ. کلیی کلیلی SPACE یان ENTER بۆ هه‌ڵبژاردنی بازدەر (تاب)."},
+{name:"پێڕستی سەرنووسەر",legend:"کلیك ${contextMenu} یان دوگمەی لیسته‌(Menu) بۆ کردنەوەی لیستەی دەق. بۆ چوونە هەڵبژاردەیەکی تر له‌ لیسته‌ کلیکی کلیلی TAB یان کلیلی تیری ڕوو لەخوارەوه‌ بۆ چوون بۆ هەڵبژاردەی پێشوو کلیکی کلیلی SHIFT+TAB یان کلیلی تیری ڕوو له‌ سەرەوە. داگرتنی کلیلی SPACE یان ENTER بۆ هەڵبژاردنی هەڵبژاردەی لیسته‌. بۆ کردنەوەی لقی ژێر لیسته‌ لەهەڵبژاردەی لیستە کلیکی کلیلی SPACE یان ENTER یان کلیلی تیری دەستی ڕاست. بۆ گەڕانەوه بۆ سەرەوەی لیسته‌ کلیکی کلیلی ESC یان کلیلی تیری دەستی چەپ. بۆ داخستنی لیستە کلیكی کلیلی ESC بکە."},
+{name:"لیستی سنووقی سەرنووسەر",legend:"لەناو سنوقی لیست, چۆن بۆ هەڵنبژاردەی لیستێکی تر کلیکی کلیلی TAB یان کلیلی تیری ڕوو لەخوار. چوون بۆ هەڵبژاردەی لیستی پێشوو کلیکی کلیلی SHIFT + TAB یان کلیلی تیری ڕوو لەسەرەوه‌. کلیکی کلیلی SPACE یان ENTER بۆ دیاریکردنی ‌هەڵبژاردەی لیست. کلیکی کلیلی ESC بۆ داخستنی سنوقی لیست."},{name:"تووڵامرازی توخم",legend:"کلیك ${elementsPathFocus} بۆ ڕابەری تووڵامرازی توخمەکان. چوون بۆ دوگمەی توخمێکی تر کلیکی کلیلی TAB یان کلیلی تیری دەستی ڕاست. چوون بۆ دوگمەی توخمی پێشوو کلیلی SHIFT+TAB یان کلیکی کلیلی تیری دەستی چەپ. داگرتنی کلیلی SPACE یان ENTER بۆ دیاریکردنی توخمەکه‌ لەسەرنووسه."}]},
+{name:"Ùەرمانەکان",items:[{name:"پووچکردنەوەی Ùەرمان",legend:"کلیك ${undo}"},{name:"هەڵگەڕانەوەی Ùەرمان",legend:"کلیك ${redo}"},{name:"Ùەرمانی دەقی Ù‚Û•ÚµÛ•Ùˆ",legend:"کلیك ${bold}"},{name:"Ùەرمانی دەقی لار",legend:"کلیك ${italic}"},{name:"Ùەرمانی ژێرهێڵ",legend:"کلیك ${underline}"},{name:"Ùەرمانی به‌ستەر",legend:"کلیك ${link}"},{name:"شاردەنەوەی تووڵامراز",legend:"کلیك ${toolbarCollapse}"},{name:"چوونەناو سەرنجدانی پێشوی Ùەرمانی بۆشایی",legend:"کلیک ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:"چوونەناو سەرنجدانی داهاتووی Ùەرمانی بۆشایی",legend:"کلیک ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:"دەستپێگەیشتنی یارمەتی",legend:"کلیك ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",
+upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"پەنجەرەی چەپ",rightWindowKey:"پەنجەرەی ڕاست",selectKey:"Select",numpad0:"Numpad 0",numpad1:"1",numpad2:"2",numpad3:"3",numpad4:"4",numpad5:"5",numpad6:"6",numpad7:"7",numpad8:"8",numpad9:"9",multiply:"*",add:"+",subtract:"-",decimalPoint:".",divide:"/",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",
+semiColon:";",equalSign:"=",comma:",",dash:"-",period:".",forwardSlash:"/",graveAccent:"`",openBracket:"[",backSlash:"\\\\",closeBracket:"}",singleQuote:"'"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js
new file mode 100644
index 0000000..18f95be
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","lt",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Bendros savybÄ—s",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js
new file mode 100644
index 0000000..faa05a2
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","lv",{title:"PieejamÄ«bas instrukcija",contents:"PalÄ«dzÄ«bas saturs. Lai aizvÄ“rtu ciet Å¡o dialogu nospiediet ESC.",legend:[{name:"Galvenais",items:[{name:"Redaktora rÄ«kjosla",legend:"Nospiediet ${toolbarFocus} lai pÄrvietotos uz rÄ«kjoslu. Lai pÄrvietotos uz nÄkoÅ¡o vai iepriekÅ¡Ä“jo rÄ«kjoslas grupu izmantojiet pogu TAB un SHIFT+TAB. Lai pÄrvietotos uz nÄkoÅ¡o vai iepriekÅ¡Ä“jo rÄ«kjoslas pogu izmantojiet Kreiso vai Labo bultiņu. Nospiediet Atstarpi vai ENTER lai aktivizÄ“tu rÄ«kjosla pogu."},
+{name:"Redaktora dialoga logs",legend:"Dialoga logÄ nospiediet pogu TAB lai pÄrvietotos uz nÄkoÅ¡o dialoga loga lauku, nospiediet SHIFT+TAB lai atgrieztos iepriekÅ¡Ä“jÄ laukÄ, nospiediet ENTER lai apstiprinÄtu dialoga datus, nospiediet ESC lai aizvÄ“rtu Å¡o dialogu. Dialogam kuram ir vairÄkas cilnes, nospiediet ALT+F10 lai pÄrvietotos uz nepiecieÅ¡amo cilni. Lai pÄrvietotos uz nÄkoÅ¡o cilni izmantojiet pogu TAB vai Labo bultiņu. Lai pÄrvietotos uz iepriekÅ¡Ä“jo cilni nospiediet SHIFT+TAB vai kreiso bultiņu. Nospiediet SPACE vai ENTER lai izvÄ“lÄ“tos lapas cilni."},
+{name:"Redaktora satura izvÄ“le",legend:"Nospiediet ${contextMenu} vai APPLICATION KEY lai atvÄ“rtu satura izvÄ“lni. Lai pÄrvietotos uz nÄkoÅ¡o izvÄ“lnes opciju izmantojiet pogu TAB vai pogu Bultiņu uz leju. Lai pÄrvietotos uz iepriekÅ¡Ä“jo opciju izmantojiet SHIFT+TAB vai pogu Bultiņa uz augÅ¡u. Nospiediet SPACE vai ENTER lai izvelÄ“tos izvÄ“lnes opciju. Atveriet tekoÅ¡ajÄ opcija apakÅ¡izvÄ“lni ar SAPCE vai ENTER ka ari to var izdarÄ«t ar Labo bultiņu. Lai atgrieztos atpakaļ uz sakuma izvÄ“lni nospiediet ESC vai Kreiso bultiņu. Lai aizvÄ“rtu ciet izvÄ“lnes saturu nospiediet ESC."},
+{name:"Redaktora saraksta lauks",legend:"Saraksta laukÄ, lai pÄrvietotos uz nÄkoÅ¡o saraksta elementu nospiediet TAB vai pogu Bultiņa uz leju. Lai pÄrvietotos uz iepriekÅ¡Ä“jo saraksta elementu nospiediet SHIFT+TAB vai pogu Bultiņa uz augÅ¡u. Nospiediet SPACE vai ENTER lai izvÄ“lÄ“tos saraksta opcijas. Nospiediet ESC lai aizvÄ“rtu saraksta lauku. "},{name:"Redaktora elementa ceļa josla",legend:"Nospiediet ${elementsPathFocus} lai pÄrvietotos uz elementa ceļa joslu. Lai pÄrvietotos uz nÄkoÅ¡o elementa pogu izmantojiet TAB vai Labo bultiņu. Lai pÄrvietotos uz iepriekÅ¡Ä“jo elementa pogu izmantojiet SHIFT + TAB vai Kreiso bultiņu. Nospiediet SPACE vai ENTER lai izvÄ“lÄ“tos elementu redaktorÄ."}]},
+{name:"Komandas",items:[{name:"Komanda atcelt darbÄ«bu",legend:"Nospiediet ${undo}"},{name:"Komanda atkÄrtot darbÄ«bu",legend:"Nospiediet ${redo}"},{name:"Treknraksta komanda",legend:"Nospiediet ${bold}"},{name:"KursÄ«va komanda",legend:"Nospiediet ${italic}"},{name:"ApakÅ¡svÄ«tras komanda ",legend:"Nospiediet ${underline}"},{name:"Hipersaites komanda",legend:"Nospiediet ${link}"},{name:"RÄ«kjoslas aizvÄ“rÅ¡anas komanda",legend:"Nospiediet ${toolbarCollapse}"},{name:"Piekļūt iepriekÅ¡Ä“jai fokusa vietas komandai",
+legend:"Nospiediet ${accessPreviousSpace} lai piekļūtu tuvÄkajai nepieejamajai fokusa vietai pirms kursora. PiemÄ“ram: diviem blakus esoÅ¡iem lÄ«nijas HR elementiem. AtkÄrtojiet taustiņu kombinÄciju lai piekļūtu pie tÄlÄkÄm vietÄm."},{name:"Piekļūt nÄkoÅ¡Ä fokusa apgabala komandai",legend:"Nospiediet ${accessNextSpace} lai piekļūtu tuvÄkajai nepieejamajai fokusa vietai pÄ“c kursora. PiemÄ“ram: diviem blakus esoÅ¡iem lÄ«nijas HR elementiem. AtkÄrtojiet taustiņu kombinÄciju lai piekļūtu pie tÄlÄkÄm vietÄm."},
+{name:"Pieejamības palīdzība",legend:"Nospiediet ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js
new file mode 100644
index 0000000..19eeec7
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","mk",{title:"ИнÑтрукции за приÑтапноÑÑ‚",contents:"Содржина на делот за помош. За да го затворите овој дијалот притиÑнете ESC.",legend:[{name:"Општо",items:[{name:"Мени за едиторот",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Дијалот за едиторот",
+legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},
+{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},
+{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",
+upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",
+f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js
new file mode 100644
index 0000000..8be11f8
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","mn",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Ерөнхий",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js
new file mode 100644
index 0000000..575362e
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","nb",{title:"Instruksjoner for tilgjengelighet",contents:"Innhold for hjelp. Trykk ESC for å lukke denne dialogen.",legend:[{name:"Generelt",items:[{name:"Verktøylinje for editor",legend:"Trykk ${toolbarFocus} for å navigere til verktøylinjen. Flytt til neste og forrige verktøylinjegruppe med TAB og SHIFT-TAB. Flytt til neste og forrige verktøylinjeknapp med HØYRE PILTAST og VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å aktivere verktøylinjeknappen."},{name:"Dialog for editor",
+legend:"Mens du er i en dialog, trykk TAB for å navigere til neste dialogfelt, press SHIFT + TAB for å flytte til forrige felt, trykk ENTER for å akseptere dialogen, trykk ESC for å avbryte dialogen. For dialoger med flere faner, trykk ALT + F10 for å navigere til listen over faner. Gå til neste fane med TAB eller HØYRE PILTAST. Gå til forrige fane med SHIFT + TAB eller VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å velge fanen."},{name:"Kontekstmeny for editor",legend:"Trykk ${contextMenu} eller MENYKNAPP for å åpne kontekstmeny. Gå til neste alternativ i menyen med TAB eller PILTAST NED. Gå til forrige alternativ med SHIFT+TAB eller PILTAST OPP. Trykk MELLOMROM eller ENTER for å velge menyalternativet. Åpne undermenyen på valgt alternativ med MELLOMROM eller ENTER eller HØYRE PILTAST. Gå tilbake til overordnet menyelement med ESC eller VENSTRE PILTAST. Lukk kontekstmenyen med ESC."},
+{name:"Listeboks for editor",legend:"I en listeboks, gå til neste alternativ i listen med TAB eller PILTAST NED. Gå til forrige alternativ i listen med SHIFT + TAB eller PILTAST OPP. Trykk MELLOMROM eller ENTER for å velge alternativet i listen. Trykk ESC for å lukke listeboksen."},{name:"Verktøylinje for elementsti",legend:"Trykk ${elementsPathFocus} for å navigere til verktøylinjen som viser elementsti. Gå til neste elementknapp med TAB eller HØYRE PILTAST. Gå til forrige elementknapp med SHIFT+TAB eller VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å velge elementet i editoren."}]},
+{name:"Hurtigtaster",items:[{name:"Angre",legend:"Trykk ${undo}"},{name:"Gjør om",legend:"Trykk ${redo}"},{name:"Fet tekst",legend:"Trykk ${bold}"},{name:"Kursiv tekst",legend:"Trykk ${italic}"},{name:"Understreking",legend:"Trykk ${underline}"},{name:"Lenke",legend:"Trykk ${link}"},{name:"Skjul verktøylinje",legend:"Trykk ${toolbarCollapse}"},{name:"Gå til forrige fokusområde",legend:"Trykk ${accessPreviousSpace} for å komme til nærmeste fokusområde før skrivemarkøren som ikke kan nås på vanlig måte, for eksempel to tilstøtende HR-elementer. Gjenta tastekombinasjonen for å komme til fokusområder lenger unna i dokumentet."},
+{name:"Gå til neste fokusområde",legend:"Trykk ${accessNextSpace} for å komme til nærmeste fokusområde etter skrivemarkøren som ikke kan nås på vanlig måte, for eksempel to tilstøtende HR-elementer. Gjenta tastekombinasjonen for å komme til fokusområder lenger unna i dokumentet."},{name:"Hjelp for tilgjengelighet",legend:"Trykk ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tabulator",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",
+pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Venstre piltast",upArrow:"Opp-piltast",rightArrow:"Høyre piltast",downArrow:"Ned-piltast",insert:"Insert","delete":"Delete",leftWindowKey:"Venstre Windows-tast",rightWindowKey:"Høyre Windows-tast",selectKey:"Select key",numpad0:"Numerisk tastatur 0",numpad1:"Numerisk tastatur 1",numpad2:"Numerisk tastatur 2",numpad3:"Numerisk tastatur 3",numpad4:"Numerisk tastatur 4",numpad5:"Numerisk tastatur 5",numpad6:"Numerisk tastatur 6",numpad7:"Numerisk tastatur 7",
+numpad8:"Numerisk tastatur 8",numpad9:"Numerisk tastatur 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semikolon",equalSign:"Likhetstegn",comma:"Komma",dash:"Bindestrek",period:"Punktum",forwardSlash:"Forover skråstrek",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Bakover skråstrek",
+closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js
new file mode 100644
index 0000000..31cb631
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","nl",{title:"Toegankelijkheidsinstructies",contents:"Help-inhoud. Druk op ESC om dit dialoog te sluiten.",legend:[{name:"Algemeen",items:[{name:"Werkbalk tekstverwerker",legend:"Druk op ${toolbarFocus} om naar de werkbalk te navigeren. Om te schakelen naar de volgende en vorige werkbalkgroep, gebruik TAB en SHIFT+TAB. Om te schakelen naar de volgende en vorige werkbalkknop, gebruik de PIJL RECHTS en PIJL LINKS. Druk op SPATIE of ENTER om een werkbalkknop te activeren."},
+{name:"Dialoog tekstverwerker",legend:"In een dialoogvenster, druk op TAB om te navigeren naar het volgende veld. Druk op SHIFT+TAB om naar het vorige veld te navigeren. Druk op ENTER om het dialoogvenster te verzenden. Druk op ESC om het dialoogvenster te sluiten. Voor dialoogvensters met meerdere tabbladen, druk op ALT+F10 om naar de tabset te navigeren. Schakel naar het volgende tabblad met TAB of PIJL RECHTS. Schakel naar het vorige tabblad met SHIFT+TAB of PIJL LINKS. Druk op SPATIE of ENTER om het tabblad te selecteren."},
+{name:"Contextmenu tekstverwerker",legend:"Druk op ${contextMenu} of APPLICATION KEY om het contextmenu te openen. Schakel naar de volgende menuoptie met TAB of PIJL OMLAAG. Schakel naar de vorige menuoptie met SHIFT+TAB of PIJL OMHOOG. Druk op SPATIE of ENTER om een menuoptie te selecteren. Op een submenu van de huidige optie met SPATIE, ENTER of PIJL RECHTS. Ga terug naar de bovenliggende menuoptie met ESC of PIJL LINKS. Sluit het contextmenu met ESC."},{name:"Keuzelijst tekstverwerker",legend:"In een keuzelijst, schakel naar het volgende item met TAB of PIJL OMLAAG. Schakel naar het vorige item met SHIFT+TAB of PIJL OMHOOG. Druk op SPATIE of ENTER om het item te selecteren. Druk op ESC om de keuzelijst te sluiten."},
+{name:"Elementenpad werkbalk tekstverwerker",legend:"Druk op ${elementsPathFocus} om naar het elementenpad te navigeren. Om te schakelen naar het volgende element, gebruik TAB of PIJL RECHTS. Om te schakelen naar het vorige element, gebruik SHIFT+TAB or PIJL LINKS. Druk op SPATIE of ENTER om een element te selecteren in de tekstverwerker."}]},{name:"Opdrachten",items:[{name:"Ongedaan maken opdracht",legend:"Druk op ${undo}"},{name:"Opnieuw uitvoeren opdracht",legend:"Druk op ${redo}"},{name:"Vetgedrukt opdracht",
+legend:"Druk op ${bold}"},{name:"Cursief opdracht",legend:"Druk op ${italic}"},{name:"Onderstrepen opdracht",legend:"Druk op ${underline}"},{name:"Link opdracht",legend:"Druk op ${link}"},{name:"Werkbalk inklappen opdracht",legend:"Druk op ${toolbarCollapse}"},{name:"Ga naar vorige focus spatie commando",legend:"Druk ${accessPreviousSpace} om toegang te verkrijgen tot de dichtstbijzijnde onbereikbare focus spatie voor de caret, bijvoorbeeld: twee aangrenzende HR elementen. Herhaal de toetscombinatie om de verste focus spatie te bereiken."},
+{name:"Ga naar volgende focus spatie commando",legend:"Druk ${accessNextSpace} om toegang te verkrijgen tot de dichtstbijzijnde onbereikbare focus spatie na de caret, bijvoorbeeld: twee aangrenzende HR elementen. Herhaal de toetscombinatie om de verste focus spatie te bereiken."},{name:"Toegankelijkheidshulp",legend:"Druk op ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"Pijl naar links",upArrow:"Pijl omhoog",rightArrow:"Pijl naar rechts",downArrow:"Pijl naar beneden",insert:"Invoegen","delete":"Verwijderen",leftWindowKey:"Linker Windows-toets",rightWindowKey:"Rechter Windows-toets",selectKey:"Selecteer toets",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Vermenigvuldigen",add:"Toevoegen",
+subtract:"Aftrekken",decimalPoint:"Decimaalteken",divide:"Delen",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Puntkomma",equalSign:"Is gelijk-teken",comma:"Komma",dash:"Koppelteken",period:"Punt",forwardSlash:"Slash",graveAccent:"Accent grave",openBracket:"Vierkant haakje openen",backSlash:"Backslash",closeBracket:"Vierkant haakje sluiten",singleQuote:"Apostrof"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/no.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/no.js
new file mode 100644
index 0000000..f3451ed
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/no.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","no",{title:"Instruksjoner for tilgjengelighet",contents:"Innhold for hjelp. Trykk ESC for å lukke denne dialogen.",legend:[{name:"Generelt",items:[{name:"Verktøylinje for editor",legend:"Trykk ${toolbarFocus} for å navigere til verktøylinjen. Flytt til neste og forrige verktøylinjegruppe med TAB og SHIFT-TAB. Flytt til neste og forrige verktøylinjeknapp med HØYRE PILTAST og VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å aktivere verktøylinjeknappen."},{name:"Dialog for editor",
+legend:"Mens du er i en dialog, trykk TAB for å navigere til neste dialogfelt, press SHIFT + TAB for å flytte til forrige felt, trykk ENTER for å akseptere dialogen, trykk ESC for å avbryte dialogen. For dialoger med flere faner, trykk ALT + F10 for å navigere til listen over faner. Gå til neste fane med TAB eller HØYRE PILTAST. Gå til forrige fane med SHIFT + TAB eller VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å velge fanen."},{name:"Kontekstmeny for editor",legend:"Trykk ${contextMenu} eller MENYKNAPP for å åpne kontekstmeny. Gå til neste alternativ i menyen med TAB eller PILTAST NED. Gå til forrige alternativ med SHIFT+TAB eller PILTAST OPP. Trykk MELLOMROM eller ENTER for å velge menyalternativet. Åpne undermenyen på valgt alternativ med MELLOMROM eller ENTER eller HØYRE PILTAST. Gå tilbake til overordnet menyelement med ESC eller VENSTRE PILTAST. Lukk kontekstmenyen med ESC."},
+{name:"Listeboks for editor",legend:"I en listeboks, gå til neste alternativ i listen med TAB eller PILTAST NED. Gå til forrige alternativ i listen med SHIFT + TAB eller PILTAST OPP. Trykk MELLOMROM eller ENTER for å velge alternativet i listen. Trykk ESC for å lukke listeboksen."},{name:"Verktøylinje for elementsti",legend:"Trykk ${elementsPathFocus} for å navigere til verktøylinjen som viser elementsti. Gå til neste elementknapp med TAB eller HØYRE PILTAST. Gå til forrige elementknapp med SHIFT+TAB eller VENSTRE PILTAST. Trykk MELLOMROM eller ENTER for å velge elementet i editoren."}]},
+{name:"Kommandoer",items:[{name:"Angre",legend:"Trykk ${undo}"},{name:"Gjør om",legend:"Trykk ${redo}"},{name:"Fet tekst",legend:"Trykk ${bold}"},{name:"Kursiv tekst",legend:"Trykk ${italic}"},{name:"Understreking",legend:"Trykk ${underline}"},{name:"Link",legend:"Trykk ${link}"},{name:"Skjul verktøylinje",legend:"Trykk ${toolbarCollapse}"},{name:"Gå til forrige fokusområde",legend:"Trykk ${accessPreviousSpace} for å komme til nærmeste fokusområde før skrivemarkøren som ikke kan nås på vanlig måte, for eksempel to tilstøtende HR-elementer. Gjenta tastekombinasjonen for å komme til fokusområder lenger unna i dokumentet."},
+{name:"Gå til neste fokusområde",legend:"Trykk ${accessNextSpace} for å komme til nærmeste fokusområde etter skrivemarkøren som ikke kan nås på vanlig måte, for eksempel to tilstøtende HR-elementer. Gjenta tastekombinasjonen for å komme til fokusområder lenger unna i dokumentet."},{name:"Hjelp for tilgjengelighet",legend:"Trykk ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",
+end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",
+divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js
new file mode 100644
index 0000000..723eaf7
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","pl",{title:"Instrukcje dotyczÄ…ce dostÄ™pnoÅ›ci",contents:"Zawartość pomocy. WciÅ›nij ESC, aby zamknąć to okno.",legend:[{name:"Informacje ogólne",items:[{name:"Pasek narzÄ™dzi edytora",legend:"WciÅ›nij ${toolbarFocus} aby przejść do paska narzÄ™dzi. Przejdź do nastÄ™pnej i poprzedniej grupy narzÄ™dzi używajÄ…c TAB oraz SHIFT-TAB. Przejdź do nastÄ™pnego i poprzedniego narzÄ™dzia używajÄ…c STRZAÅKI W PRAWO lub STRZAÅKI W LEWO. WciÅ›nij SPACJĘ lub ENTER, aby aktywować zaznaczone narzÄ™dzie."},
+{name:"Okno dialogowe edytora",legend:"BÄ™dÄ…c w oknie dialogowym wciÅ›nij TAB aby przejść do nastÄ™pnego pola dialogowego, wciÅ›nij SHIFT + TAB aby przejść do poprzedniego pola, wciÅ›nij ENTER aby wysÅ‚ać dialog, wciÅ›nij ESC aby anulować dialog. Dla okien dialogowych z wieloma zakÅ‚adkami, wciÅ›nij ALT + F10 aby przejść do listy zakÅ‚adek. Gdy to zrobisz przejdź do nastÄ™pnej zakÅ‚adki wciskajÄ…c TAB lub STRZAÅKĘ W PRAWO. Przejdź do poprzedniej zakÅ‚adki wciskajÄ…c SHIFT + TAB lub STRZAÅKĘ W LEWO. WciÅ›nij SPACJĘ lub ENTER aby wybrać zakÅ‚adkÄ™."},
+{name:"Menu kontekstowe edytora",legend:"WciÅ›nij ${contextMenu} lub PRZYCISK APLIKACJI aby otworzyć menu kontekstowe. Przejdź do nastÄ™pnej pozycji menu wciskajÄ…c TAB lub STRZAÅKĘ W DÓÅ. Przejdź do poprzedniej pozycji menu wciskajÄ…c SHIFT + TAB lub STRZAÅKĘ W GÓRĘ. WciÅ›nij SPACJĘ lub ENTER aby wygrać pozycjÄ™ menu. Otwórz pod-menu obecnej pozycji wciskajÄ…c SPACJĘ lub ENTER lub STRZAÅKĘ W PRAWO. Wróć do pozycji nadrzÄ™dnego menu wciskajÄ…c ESC lub STRZAÅKĘ W LEWO. Zamknij menu wciskajÄ…c ESC."},{name:"Lista w edytorze",
+legend:"W polu listy możesz przechodzić do nastÄ™pnego elementu za pomocÄ… klawisza TAB lub STRZAÅKI W DÓÅ. Poprzedni element osiÄ…gniesz za pomocÄ… SHIFT+TAB lub STRZAÅKI W GÓRĘ. Za pomocÄ… SPACJI lub ENTERA wybierzesz danÄ… opcjÄ™ z listy, a za pomocÄ… klawisza ESC opuÅ›cisz listÄ™."},{name:"Pasek Å›cieżki elementów edytora",legend:"NaciÅ›nij ${elementsPathFocus} w celu przejÅ›cia do paska Å›cieżki elementów edytora. W celu przejÅ›cia do kolejnego elementu naciÅ›nij klawisz Tab lub StrzaÅ‚ki w prawo. W celu przejÅ›cia do poprzedniego elementu naciÅ›nij klawisze Shift+Tab lub StrzaÅ‚ki w lewo. By wybrać element w edytorze, użyj klawisza Spacji lub Enter."}]},
+{name:"Polecenia",items:[{name:"Polecenie Cofnij",legend:"Naciśnij ${undo}"},{name:"Polecenie Ponów",legend:"Naciśnij ${redo}"},{name:"Polecenie Pogrubienie",legend:"Naciśnij ${bold}"},{name:"Polecenie Kursywa",legend:"Naciśnij ${italic}"},{name:"Polecenie Podkreślenie",legend:"Naciśnij ${underline}"},{name:"Polecenie Wstaw/ edytuj odnośnik",legend:"Naciśnij ${link}"},{name:"Polecenie schowaj pasek narzędzi",legend:"Naciśnij ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:"Pomoc dotycząca dostępności",legend:"Naciśnij ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Strzałka w lewo",
+upArrow:"Strzałka w górę",rightArrow:"Strzałka w prawo",downArrow:"Strzałka w dół",insert:"Insert","delete":"Delete",leftWindowKey:"Lewy klawisz Windows",rightWindowKey:"Prawy klawisz Windows",selectKey:"Klawisz wyboru",numpad0:"Klawisz 0 na klawiaturze numerycznej",numpad1:"Klawisz 1 na klawiaturze numerycznej",numpad2:"Klawisz 2 na klawiaturze numerycznej",numpad3:"Klawisz 3 na klawiaturze numerycznej",numpad4:"Klawisz 4 na klawiaturze numerycznej",numpad5:"Klawisz 5 na klawiaturze numerycznej",
+numpad6:"Klawisz 6 na klawiaturze numerycznej",numpad7:"Klawisz 7 na klawiaturze numerycznej",numpad8:"Klawisz 8 na klawiaturze numerycznej",numpad9:"Klawisz 9 na klawiaturze numerycznej",multiply:"Przemnóż",add:"Plus",subtract:"Minus",decimalPoint:"Separator dziesiętny",divide:"Podziel",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Średnik",equalSign:"Znak równości",comma:"Przecinek",dash:"Pauza",
+period:"Kropka",forwardSlash:"Ukośnik prawy",graveAccent:"Akcent słaby",openBracket:"Nawias kwadratowy otwierający",backSlash:"Ukośnik lewy",closeBracket:"Nawias kwadratowy zamykający",singleQuote:"Apostrof"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js
new file mode 100644
index 0000000..5647002
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","pt-br",{title:"Instruções de Acessibilidade",contents:"Conteúdo da Ajuda. Para fechar este diálogo pressione ESC.",legend:[{name:"Geral",items:[{name:"Barra de Ferramentas do Editor",legend:"Pressione ${toolbarFocus} para navegar para a barra de ferramentas. Mova para o anterior ou próximo grupo de ferramentas com TAB e SHIFT-TAB. Mova para o anterior ou próximo botão com SETA PARA DIREITA or SETA PARA ESQUERDA. Pressione ESPAÇO ou ENTER para ativar o botão da barra de ferramentas."},
+{name:"Diálogo do Editor",legend:"Dentro de um diálogo, pressione TAB para navegar para o próximo campo, pressione SHIFT + TAB para mover para o campo anterior, pressione ENTER para enviar o diálogo, pressione ESC para cancelar o diálogo. Para diálogos que tem múltiplas abas, pressione ALT + F10 para navegar para a lista de abas, então mova para a próxima aba com SHIFT + TAB ou SETA PARA ESQUERDA. Pressione ESPAÇO ou ENTER para selecionar a aba."},{name:"Menu de Contexto do Editor",legend:"Pressione ${contextMenu} ou TECLA DE MENU para abrir o menu de contexto, então mova para a próxima opção com TAB ou SETA PARA BAIXO. Mova para a anterior com SHIFT+TAB ou SETA PARA CIMA. Pressione ESPAÇO ou ENTER para selecionar a opção do menu. Abra o submenu da opção atual com ESPAÇO ou ENTER ou SETA PARA DIREITA. Volte para o menu pai com ESC ou SETA PARA ESQUERDA. Feche o menu de contexto com ESC."},
+{name:"Caixa de Lista do Editor",legend:"Dentro de uma caixa de lista, mova para o próximo item com TAB ou SETA PARA BAIXO. Mova para o item anterior com SHIFT + TAB ou SETA PARA CIMA. Pressione ESPAÇO ou ENTER para selecionar uma opção na lista. Pressione ESC para fechar a caixa de lista."},{name:"Barra de Caminho do Elementos do Editor",legend:"Pressione ${elementsPathFocus} para a barra de caminho dos elementos. Mova para o próximo botão de elemento com TAB ou SETA PARA DIREITA. Mova para o botão anterior com SHIFT+TAB ou SETA PARA ESQUERDA. Pressione ESPAÇO ou ENTER para selecionar o elemento no editor."}]},
+{name:"Comandos",items:[{name:" Comando Desfazer",legend:"Pressione ${undo}"},{name:" Comando Refazer",legend:"Pressione ${redo}"},{name:" Comando Negrito",legend:"Pressione ${bold}"},{name:" Comando Itálico",legend:"Pressione ${italic}"},{name:" Comando Sublinhado",legend:"Pressione ${underline}"},{name:" Comando Link",legend:"Pressione ${link}"},{name:" Comando Fechar Barra de Ferramentas",legend:"Pressione ${toolbarCollapse}"},{name:"Acessar o comando anterior de spaço de foco",legend:"Pressione ${accessNextSpace} para acessar o espaço de foco não alcançável mais próximo antes do cursor, por exemplo: dois elementos HR adjacentes. Repita a combinação de teclas para alcançar espaços de foco distantes."},
+{name:"Acessar próximo fomando de spaço de foco",legend:"Pressione ${accessNextSpace} para acessar o espaço de foco não alcançável mais próximo após o cursor, por exemplo: dois elementos HR adjacentes. Repita a combinação de teclas para alcançar espaços de foco distantes."},{name:" Ajuda de Acessibilidade",legend:"Pressione ${a11yHelp}"}]}],backspace:"Tecla Backspace",tab:"Tecla Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",
+pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Seta à Esquerda",upArrow:"Seta à Cima",rightArrow:"Seta à Direita",downArrow:"Seta à Baixo",insert:"Insert","delete":"Delete",leftWindowKey:"Tecla do Windows Esquerda",rightWindowKey:"Tecla do Windows Direita",selectKey:"Tecla Selecionar",numpad0:"0 do Teclado Numérico",numpad1:"1 do Teclado Numérico",numpad2:"2 do Teclado Numérico",numpad3:"3 do Teclado Numérico",numpad4:"4 do Teclado Numérico",numpad5:"5 do Teclado Numérico",numpad6:"6 do Teclado Numérico",
+numpad7:"7 do Teclado Numérico",numpad8:"8 do Teclado Numérico",numpad9:"9 do Teclado Numérico",multiply:"Multiplicar",add:"Mais",subtract:"Subtrair",decimalPoint:"Ponto",divide:"Dividir",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Ponto-e-vírgula",equalSign:"Igual",comma:"Vírgula",dash:"Hífen",period:"Ponto",forwardSlash:"Barra",graveAccent:"Acento Grave",openBracket:"Abrir Conchetes",
+backSlash:"Contra-barra",closeBracket:"Fechar Colchetes",singleQuote:"Aspas Simples"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js
new file mode 100644
index 0000000..810a854
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","pt",{title:"Instruções de acessibilidade",contents:"Conteúdo de ajuda. Use a tecla ESC para fechar esta janela.",legend:[{name:"Geral",items:[{name:"Barra de ferramentas do editor",legend:"Clique em ${toolbarFocus} para navegar para a barra de ferramentas. Vá para o grupo da barra de ferramentas anterior e seguinte com TAB e SHIFT+TAB. Vá para o botão da barra de ferramentas anterior com a SETA DIREITA ou ESQUERDA. Pressione ESPAÇO ou ENTER para ativar o botão da barra de ferramentas."},
+{name:"Janela do Editor",legend:"Dentro de uma janela, pressione TAB para navigar para o campo da janela seguinte, pressione SHIFT + TAB para mover para o campo anterior, pressione ENTER para submeter a janela, pressione ESC para cancelar a janela. Para as janelas que têm múltiplos páginas com separadores, pressione ALT + F10 para navegar para a lista do separador. Depois mova para o seguinte separador com TAB ou SETA DIREITA. Mover para o separador anterior com SHIFT + TAB ou SETA ESQUERDA. Pressione ESPAÇO ou ENTER para selecionar o separador da página."},
+{name:"Menu de Contexto do Editor",legend:"Clique em ${contextMenu} ou TECLA APLICAÇÃO para abrir o menu de contexto. Depois vá para a opção do menu seguinte com TAB ou SETA PARA BAIXO. Vá para a opção anterior com SHIFT+TAB ou SETA PARA CIMA. Pressione ESPAÇO ou ENTER para selecionar a opção do menu. Abra o submenu da opção atual com ESPAÇO, ENTER ou SETA DIREITA. GVá para o item do menu parente com ESC ou SETA ESQUERDA. Feche o menu de contexto com ESC."},{name:"Editor de caixa em lista",legend:"Dentro da caixa da lista, vá para o itemda lista seguinte com TAB ou SETA PARA BAIXO. Move Vá parao item da lista anterior com SHIFT+TAB ou SETA PARA BAIXO. Pressione ESPAÇO ou ENTER para selecionar a opção da lista. Pressione ESC para fechar a caisa da lista."},
+{name:"Caminho Barra Elemento Editor",legend:"Clique em ${elementsPathFocus} para navegar para a barra do caminho dos elementos. Vá para o botão do elemento seguinte com TAB ou SETA DIREITA. Vá para o botão anterior com SHIFT+TAB ou SETA ESQUERDA. Pressione ESPAÇO ou ENTER para selecionar o elemento no editor."}]},{name:"Comandos",items:[{name:"Comando de Anular",legend:"Carregar ${undo}"},{name:"Comando de Refazer",legend:"Pressione ${redo}"},{name:"Comando de Negrito",legend:"Pressione ${bold}"},
+{name:"Comando de Itálico",legend:"Pressione ${italic}"},{name:"Comando de Sublinhado",legend:"Pressione ${underline}"},{name:"Comando de Hiperligação",legend:"Pressione ${link}"},{name:"Comando de Ocultar Barra de Ferramentas",legend:"Pressione ${toolbarCollapse}"},{name:"Acesso comando do espaço focus anterior",legend:"Clique em ${accessPreviousSpace} para aceder ao espaço do focos inalcançável mais perto antes do sinal de omissão, por exemplo: dois elementos HR adjacentes. Repetir a combinação da chave para alcançar os espaços dos focos distantes."},
+{name:"Acesso comando do espaço focus seguinte",legend:"Pressione ${accessNextSpace} para aceder ao espaço do focos inalcançável mais perto depois do sinal de omissão, por exemplo: dois elementos HR adjacentes. Repetir a combinação da chave para alcançar os espaços dos focos distantes."},{name:"Ajuda a acessibilidade",legend:"Pressione ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pausa",capslock:"Maiúsculas",escape:"Esc",pageUp:"Page Up",
+pageDown:"Page Down",end:"Fim",home:"Entrada",leftArrow:"Seta esquerda",upArrow:"Seta para cima",rightArrow:"Seta direita",downArrow:"Seta para baixo",insert:"Inserir","delete":"Eliminar",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiplicar",add:"Adicionar",
+subtract:"Subtrair",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Vírgula",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Acento grave",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js
new file mode 100644
index 0000000..5a86ee2
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ro",{title:"Instrucțiuni de accesibilitate",contents:"Cuprins. Pentru a închide acest dialog, apăsați tasta ESC.",legend:[{name:"General",items:[{name:"Editează bara instrumente.",legend:"Apasă ${toolbarFocus} pentru a naviga prin bara de instrumente. Pentru a te mișca prin grupurile de instrumente folosește tastele TAB și SHIFT-TAB. Pentru a te mișca intre diverse instrumente folosește tastele SĂGEATĂ DREAPTA sau SĂGEATĂ STÂNGA. Apasă butonul SPAȚIU sau ENTER pentru activarea instrumentului."},
+{name:"Dialog editor",legend:"Într-un dialog, apasă TAB pentru a naviga spre câmpul următor de dialog, apasă SHIFT + TAB pentru a te duce la câmpul anterior, apasă ENTER pentru a trimite dialogul, apasă ESC pentru a anula dialogul. Pentru dialoguri care au mai multe subferestre, apasă ALT + F10 pentr a naviga în lista de subferestre. Treci la subferestrea următoare cu TAB sau SĂGEATĂ DREAPTA. Treci la subfereastra anterioară cu SHIFT + TAB sau SĂGEATĂ STÂNGA. Apasă SPAȚIU sau ENTER pentru a selecta subfereastra."},
+{name:"Editor meniu contextual",legend:"Apasă ${contextMenu} sau TASTA MENIU pentru a deschide meniul contextual. Treci la următoarea opțiune din meniu cu TAB sau SĂGEATĂ JOS. Treci la opțiunea anterioară cu SHIFT+TAB sau SĂGEATĂ SUS. Apasă SPAȚIU sau ENTER pentru a selecta opțiunea din meniu. Deschide sub-meniul opțiunii curente cu SPAȚIU sau ENTER sau SĂGEATĂ DREAPTA. Revino la elementul din meniul părinte cu ESC sau SĂGEATĂ STÂNGA. Închide meniul de context cu ESC."},{name:"Editor Casetă Listă",
+legend:"În interiorul unei liste, treci la următorull element cu TAB sau SĂGEATĂ JOS. Treci la elementul anterior din listă cu SHIFT + TAB sau SĂGEATĂ SUS. Apasă SPAȚIU sau ENTER pentru a selecta opțiunea din listă. Apasă ESC pentru a închide lista."},{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},
+{name:"Comenzi",items:[{name:" Undo command",legend:"Apasă ${undo}"},{name:"Comanda precedentă",legend:"Apasă ${redo}"},{name:"Comanda Îngroșat",legend:"Apasă ${bold}"},{name:"Comanda Inclinat",legend:"Apasă ${italic}"},{name:"Comanda Subliniere",legend:"Apasă ${underline}"},{name:"Comanda Legatură",legend:"Apasă ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",
+upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",
+f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js
new file mode 100644
index 0000000..414caa1
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ru",{title:"ГорÑчие клавиши",contents:"Помощь. Ð”Ð»Ñ Ð·Ð°ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñтого окна нажмите ESC.",legend:[{name:"ОÑновное",items:[{name:"Панель инÑтрументов",legend:"Ðажмите ${toolbarFocus} Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° к панели инÑтрументов. Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ группами панели инÑтрументов иÑпользуйте TAB и SHIFT-TAB. Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ кнопками панели иÑтрументов иÑпользуйте кнопки ВПРÐВО или ВЛЕВО. Ðажмите ПРОБЕЛ или ENTER Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка кнопки панели инÑтрументов."},{name:"Диалоги",legend:"Ð’ диалоговом окне, нажмите клавишу TAB Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° к Ñледующему диалоговому полю, нажмите клавиши SHIFT + TAB, чтобы перейти к предыдущему полю, нажмите ENTER, чтобы отправить данные, нажмите клавишу ESC, Ð´Ð»Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ñ‹. Ð”Ð»Ñ Ð¾ÐºÐ¾Ð½, которые имеют неÑколько вкладок, нажмите ALT + F10 Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° к ÑпиÑку вкладок. Переход к Ñледующей вкладке TAB ИЛИ ПРÐВУЮ СТРЕЛКУ. Переход к предыдущей вкладке Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ SHIFT + TAB или ЛЕВÐЯ СТРЕЛКÐ. Ðажмите ПРОБЕЛ или ENTER, чтобы выбрать вкладку."},
+{name:"КонтекÑтное меню",legend:'Ðажмите ${contextMenu} или клавишу APPLICATION, чтобы открыть контекÑтное меню. Затем перейдите к Ñледующему пункту меню Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ TAB или Ñтрелкой "Ð’ÐИЗ". Переход к предыдущей опции - SHIFT+TAB или Ñтрелкой "ВВЕРХ". Ðажмите SPACE, или ENTER, чтобы задейÑтвовать опцию меню. Открыть подменю текущей опции - SPACE или ENTER или Ñтрелкой "ВПРÐВО". Возврат к родительÑкому пункту меню - ESC или Ñтрелкой "ВЛЕВО". Закрытие контекÑтного меню - ESC.'},{name:"Редактор ÑпиÑка",
+legend:'Внутри окна ÑпиÑка, переход к Ñледующему пункту ÑпиÑка - TAB или Ñтрелкой "Ð’ÐИЗ". Переход к предыдущему пункту ÑпиÑка - SHIFT + TAB или Ñтрелкой "ВВЕРХ". Ðажмите SPACE, или ENTER, чтобы задейÑтвовать опцию ÑпиÑка. Ðажмите ESC, чтобы закрыть окно ÑпиÑка.'},{name:"Путь к Ñлементу",legend:'Ðажмите ${elementsPathFocus}, чтобы перейти к панели пути Ñлементов. Переход к Ñледующей кнопке Ñлемента - TAB или Ñтрелкой "ВПРÐВО". Переход к предыдущей кнопку - SHIFT+TAB или Ñтрелкой "ВЛЕВО". Ðажмите SPACE, или ENTER, чтобы выбрать Ñлемент в редакторе.'}]},
+{name:"Команды",items:[{name:"Отменить",legend:"Ðажмите ${undo}"},{name:"Повторить",legend:"Ðажмите ${redo}"},{name:"Полужирный",legend:"Ðажмите ${bold}"},{name:"КурÑив",legend:"Ðажмите ${italic}"},{name:"Подчеркнутый",legend:"Ðажмите ${underline}"},{name:"ГиперÑÑылка",legend:"Ðажмите ${link}"},{name:"Свернуть панель инÑтрументов",legend:"Ðажмите ${toolbarCollapse}"},{name:"Команды доÑтупа к предыдущему фокуÑному проÑтранÑтву",legend:'Ðажмите ${accessPreviousSpace}, чтобы обратитьÑÑ Ðº ближайшему недоÑтижимому фокуÑному проÑтранÑтву перед Ñимволом "^", например: два Ñмежных HR Ñлемента. Повторите комбинацию клавиш, чтобы доÑтичь отдаленных фокуÑных проÑтранÑтв.'},
+{name:"Команды доÑтупа к Ñледующему фокуÑному проÑтранÑтву",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:"Справка по горÑчим клавишам",legend:"Ðажмите ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Esc",pageUp:"Page Up",pageDown:"Page Down",end:"End",
+home:"Home",leftArrow:"Стрелка влево",upArrow:"Стрелка вверх",rightArrow:"Стрелка вправо",downArrow:"Стрелка вниз",insert:"Insert","delete":"Delete",leftWindowKey:"Ð›ÐµÐ²Ð°Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ° Windows",rightWindowKey:"ÐŸÑ€Ð°Ð²Ð°Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ° Windows",selectKey:"Выбрать",numpad0:"Цифра 0",numpad1:"Цифра 1",numpad2:"Цифра 2",numpad3:"Цифра 3",numpad4:"Цифра 4",numpad5:"Цифра 5",numpad6:"Цифра 6",numpad7:"Цифра 7",numpad8:"Цифра 8",numpad9:"Цифра 9",multiply:"Умножить",add:"ПлюÑ",subtract:"ВычеÑÑ‚ÑŒ",decimalPoint:"ДеÑÑÑ‚Ð¸Ñ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ°",
+divide:"Делить",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Точка Ñ Ð·Ð°Ð¿Ñтой",equalSign:"Равно",comma:"ЗапÑтаÑ",dash:"Тире",period:"Точка",forwardSlash:"ÐÐ°ÐºÐ»Ð¾Ð½Ð½Ð°Ñ Ñ‡ÐµÑ€Ñ‚Ð°",graveAccent:"ÐпоÑтроф",openBracket:"Открыть Ñкобку",backSlash:"ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ Ð½Ð°ÐºÐ»Ð¾Ð½Ð½Ð°Ñ Ñ‡ÐµÑ€Ñ‚Ð°",closeBracket:"Закрыть Ñкобку",singleQuote:"ÐžÐ´Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/si.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/si.js
new file mode 100644
index 0000000..07d567a
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/si.js
@@ -0,0 +1,10 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","si",{title:"ළඟ෠වියහà·à¶šà·’ ",contents:"උදව් සඳහ෠අන්තර්ගතය.නික්මයෙමට ESC බොත්තම ඔබන්න",legend:[{name:"පොදු කරුණු",items:[{name:"සංස්කරණ මෙවලම් ",legend:"ඔබන්න ${මෙවලම් තීරු අවධà·à¶±à¶º} මෙවලම් තීරුවේ එහ෠මෙහ෠යෑමට.ඉදිරියට යෑමට හ෠ආපසු යෑමට මෙවලම් තීරුකà·à¶«à·Šà¶©à¶º à·„à· TAB à·„à· SHIFT-TAB .ඉදිරියට යෑමට හ෠ආපසු යෑමට මෙවලම් තීරු බොත්තම සමග RIGHT ARROW à·„à· LEFT ARROW.මෙවලම් තීරු බොත්තම සක්â€à¶»à·’ය කර ගà·à¶±à·“මට SPACE à·„à· ENTER බොත්තම ඔබන්න."},{name:"සංස්කරණ ",legend:"දෙබසක් තුළ, ඊළඟ දෙබස් පෙදෙසට යෑමට TAB බොත්තම ඔබන්න, කලින් පෙදෙසට යෑමට SHIFT + TAB බොත්තම ද, දෙබස් ඉදිරිපත් කිරීමට ENTER බොත්තම ද, දෙබස් නà·à·€à¶­à·“මට ESCබොත්තම ද, දෙබස් සහිත ගොනු, පිටු à·€à·à¶©à·’ සංක්â€à¶ºà¶ºà·à·€à¶šà·Š ලබ෠ගෙනිමට,ගොනු තුළ එහà·à¶¸à·™à·„෠යෑමට ALT + F10 බොත්තම් ද, ඊළඟ ගොනුවට යෑමට TAB à·„à· RIGTH ARROW බොත්තම ඔබන්න. පෙර ගොනුවට යෑමට SHIFT + TAB à·„à· LEFT ARROW බොත්තම් ද ,ගොනු පිටු තේරීමට SPACE à·„à· ENTER බොත්තම් ද ඔබන්න."},
+{name:"සංස්කරණ අඩංගුවට ",legend:"ඔබන්න ${අන්තර්ගත මෙනුව} à·„à· APPLICATION KEY අන්තර්ගත-මෙනුව විවුරතකිරීමට. ඊළඟ මෙනුව-ව්කල්පයන්ට යෑමට TAB à·„à· DOWN ARROW බොත්තම ද, පෙර විකල්පයන්ටයෑමට SHIFT+TAB à·„à· UP ARROW බොත්තම ද, මෙනුව-ව්කල්පයන් තේරීමට SPACE à·„à· ENTER බොත්තම ද, දà·à¶±à¶§ විවුර්තව ඇති උප-මෙනුවක වීකල්ප තේරීමට SPACE à·„à· ENTER à·„à· RIGHT ARROW ද, නà·à·€à¶­ පෙර ප්â€à¶»à¶°à·à¶± මෙනුවට යෑමට ESC à·„à· LEFT ARROW බොත්තම ද. අන්තර්ගත-මෙනුව à·€à·à·ƒà·“මට ESC බොත්තම ද ඔබන්න."},{name:"සංස්කරණ තේරුම් ",legend:"තේරුම් කොටුව තුළ , ඊළඟ අයිතමයට යෑමට TAB à·„à· DOWN ARROW , පෙර අයිතමයට යෑමට SHIFT + TAB à·„à· UP ARROW . අයිතම විකල්පයන් තේරීමට SPACE à·„à· ENTER ,තේරුම් කොටුව à·€à·à·ƒà·“මට ESC බොත්තම් ද ඔබන්න."},
+{name:"සංස්කරණ අංග සහිත ",legend:"ඔබන්න ${මෙවලම් තීරු අවධà·à¶±à¶º} මෙවලම් තීරුවේ එහ෠මෙහ෠යෑමට.ඉදිරියට යෑමට හ෠ආපසු යෑමට මෙවලම් තීරුකà·à¶«à·Šà¶©à¶º à·„à· TAB à·„à· SHIFT-TAB .ඉදිරියට යෑමට හ෠ආපසු යෑමට මෙවලම් තීරු බොත්තම සමග RIGHT ARROW à·„à· LEFT ARROW.මෙවලම් තීරු බොත්තම සක්â€à¶»à·’ය කර ගà·à¶±à·“මට SPACE à·„à· ENTER බොත්තම ඔබන්න."}]},{name:"විධà·à¶±",items:[{name:"විධà·à¶±à¶º වෙනස් ",legend:"ඔබන්න ${වෙනස් කිරීම}"},{name:"විධà·à¶± නà·à·€à¶­à·Š පෙර පරිදිම වෙනස්කර ගà·à¶±à·“ම.",legend:"ඔබන්න ${නà·à·€à¶­à·Š පෙර පරිදිම වෙනස්කර ගà·à¶±à·“ම}"},{name:"තද අකුරින් විධà·à¶±",legend:"ඔබන්න ${තද }"},
+{name:"බà·à¶°à·“ අකුරු විධà·à¶±",legend:"ඔබන්න ${බà·à¶°à·“ අකුරු }"},{name:"යටින් ඉරි ඇද ඇති විධà·à¶±.",legend:"ඔබන්න ${යටින් ඉරි ඇද ඇති}"},{name:"සම්බන්ධිත විධà·à¶±",legend:"ඔබන්න ${සම්බන්ධ }"},{name:"මෙවලම් තීරු à·„à·à¶šà·”ලුම් විධà·à¶±",legend:"ඔබන්න ${මෙවලම් තීරු à·„à·à¶šà·”ලුම් }"},{name:"යොමුවීමට පෙර à·€à·à¶¯à¶œà¶­à·Š විධà·à¶±",legend:"ඔබන්න ${යොමුවීමට ඊළඟ }"},{name:"යොමුවීමට ඊළග à·€à·à¶¯à¶œà¶­à·Š විධà·à¶±",legend:"ඔබන්න ${යොමුවීමට ඊළඟ }"},{name:"ප්â€à¶»à·€à·šà· ",legend:"ඔබන්න ${a11y }"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",
+alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",
+numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js
new file mode 100644
index 0000000..3984709
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sk",{title:"InÅ¡trukcie prístupnosti",contents:"Pomocný obsah. Pre zatvorenie tohto okna, stlaÄte ESC.",legend:[{name:"VÅ¡eobecne",items:[{name:"LiÅ¡ta nástrojov editora",legend:"StlaÄte ${toolbarFocus} pre navigáciu na liÅ¡tu nástrojov. Medzi ÄalÅ¡ou a predchádzajúcou liÅ¡tou nástrojov sa pohybujete s TAB a SHIFT-TAB. Medzi Äalším a predchádzajúcim tlaÄidlom na liÅ¡te nástrojov sa pohybujete s pravou šípkou a ľavou šípkou. StlaÄte medzerník alebo ENTER pre aktiváciu tlaÄidla liÅ¡ty nástrojov."},
+{name:"Editorový dialóg",legend:"V dialogu, stlaÄte TAB pre navigáciu na ÄalÅ¡ie dialógové pole, stlaÄte STIFT + TAB pre presun na predchádzajúce pole, stlaÄte ENTER pre odoslanie dialógu, stlaÄte ESC pre zruÅ¡enie dialógu. Pre dialógy, ktoré majú viac záložiek, stlaÄte ALT + F10 pre navigácou do zoznamu záložiek. Potom sa posúvajte k ÄalÅ¡ej žáložke pomocou TAB alebo pravou šípkou. Pre presun k predchádzajúcej záložke, stlaÄte SHIFT + TAB alebo ľavú šípku. StlaÄte medzerník alebo ENTER pre vybranie záložky."},
+{name:"Editorové kontextové menu",legend:"StlaÄte ${contextMenu} alebo APPLICATION KEY pre otvorenie kontextového menu. Potom sa presúvajte na ÄalÅ¡ie možnosti menu s TAB alebo dolnou šípkou. Presunte sa k predchádzajúcej možnosti s SHIFT + TAB alebo hornou šípkou. StlaÄte medzerník alebo ENTER pre výber možnosti menu. Otvorte pod-menu danej možnosti s medzerníkom, alebo ENTER, alebo pravou šípkou. Vráťte sa späť do položky rodiÄovského menu s ESC alebo ľavou šípkou. Zatvorte kontextové menu s ESC."},
+{name:"Editorov box zoznamu",legend:"V boxe zoznamu, presuňte sa na ÄalÅ¡iu položku v zozname s TAB alebo dolnou šípkou. Presuňte sa k predchádzajúcej položke v zozname so SHIFT + TAB alebo hornou šípkou. StlaÄte medzerník alebo ENTER pre výber možnosti zoznamu. StlaÄte ESC pre zatvorenie boxu zoznamu."},{name:"Editorove pásmo cesty prvku",legend:"StlaÄte ${elementsPathFocus} pre navigovanie na pásmo cesty elementu. Presuňte sa na tlaÄidlo ÄalÅ¡ieho prvku s TAB alebo pravou šípkou. Presuňte sa k predchádzajúcemu tlaÄidlu s SHIFT + TAB alebo ľavou šípkou. StlaÄte medzerník alebo ENTER pre výber prvku v editore."}]},
+{name:"Príkazy",items:[{name:"VrátiÅ¥ príkazy",legend:"StlaÄte ${undo}"},{name:"Nanovo vrátiÅ¥ príkaz",legend:"StlaÄte ${redo}"},{name:"Príkaz na stuÄnenie",legend:"StlaÄte ${bold}"},{name:"Príkaz na kurzívu",legend:"StlaÄte ${italic}"},{name:"Príkaz na podÄiarknutie",legend:"StlaÄte ${underline}"},{name:"Príkaz na odkaz",legend:"StlaÄte ${link}"},{name:"Príkaz na zbalenie liÅ¡ty nástrojov",legend:"StlaÄte ${toolbarCollapse}"},{name:"PrejsÅ¥ na predchádzajúcu zamerateľnú medzeru príkazu",legend:"StlaÄte ${accessPreviousSpace} pre prístup na najbližšie nedosiahnuteľné zamerateľné medzery pred vsuvkuo. Napríklad: dve za sebou idúce horizontálne Äiary. Opakujte kombináciu klávesov pre dosiahnutie vzdialených zamerateľných medzier."},
+{name:"PrejsÅ¥ na Äalší ",legend:"StlaÄte ${accessNextSpace} pre prístup na najbližšie nedosiahnuteľné zamerateľné medzery po vsuvke. Napríklad: dve za sebou idúce horizontálne Äiary. Opakujte kombináciu klávesov pre dosiahnutie vzdialených zamerateľných medzier."},{name:"Pomoc prístupnosti",legend:"StlaÄte ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Stránka hore",pageDown:"Stránka dole",
+end:"End",home:"Home",leftArrow:"Šípka naľavo",upArrow:"Šípka hore",rightArrow:"Šípka napravo",downArrow:"Šípka dole",insert:"Insert","delete":"Delete",leftWindowKey:"Ľavé Windows tlaÄidlo",rightWindowKey:"Pravé Windows tlaÄidlo",selectKey:"TlaÄidlo Select",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Násobenie",add:"SÄítanie",subtract:"OdÄítanie",
+decimalPoint:"Desatinná Äiarka",divide:"Delenie",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"BodkoÄiarka",equalSign:"Rovná sa",comma:"ÄŒiarka",dash:"PomĺÄka",period:"Bodka",forwardSlash:"Lomítko",graveAccent:"Zdôrazňovanie prízvuku",openBracket:"Hranatá zátvorka otváracia",backSlash:"Backslash",closeBracket:"Hranatá zátvorka zatváracia",singleQuote:"Jednoduché úvodzovky"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js
new file mode 100644
index 0000000..9d1f03a
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sl",{title:"Navodila Dostopnosti",contents:"Vsebina PomoÄi. ÄŒe želite zapreti to pogovorno okno pritisnite ESC.",legend:[{name:"SploÅ¡no",items:[{name:"Urejevalna Orodna Vrstica",legend:"Pritisnite ${toolbarFocus} za pomik v orodno vrstico. Z TAB in SHIFT-TAB se pomikate na naslednjo in prejÅ¡njo skupino orodne vrstice. Z DESNO PUÅ ÄŒICO ali LEVO PUÅ ÄŒICO se pomikate na naslednji in prejÅ¡nji gumb orodne vrstice. Pritisnite SPACE ali ENTER, da aktivirate gumb orodne vrstice."},
+{name:"Urejevalno Pogovorno Okno",legend:"Znotraj pogovornega okna, pritisnite tipko TAB za pomik na naslednjo pogovorno polje, pritisnite SHIFT + TAB za pomik v prejÅ¡nje polje, pritisnite tipko ENTER za predložitev pogovornega okna, pritisnite tipko ESC, da prekliÄete okno. Za okna, ki imajo veÄ zavihkov, pritisnite ALT + F10, da pojdete na seznam zavihkov. Na naslednji zavihek se premaknete s tipko TAB ali DESNO PUÅ ÄŒICO. Z SHIFT + TAB ali LEVO PUÅ ÄŒICO pa se premaknete na prejÅ¡nji zavihek. Pritisnite tipko SPACE ali ENTER za izbiro zavihka."},
+{name:"Urejevalni Kontekstni Meni",legend:"Pritisnite ${contextMenu} ali APPLICATION KEY, da odprete kontekstni meni. Nato se premaknite na naslednjo možnost menija s tipko TAB ali PUÅ ÄŒICA DOL. Premakniti se na prejÅ¡njo možnost z SHIFT + TAB ali PUÅ ÄŒICA GOR. Pritisnite SPACE ali ENTER za izbiro možnosti menija. Odprite podmeni trenutne možnosti menija s tipko SPACE ali ENTER ali DESNA PUÅ ÄŒICA. Vrnite se na matiÄni element menija s tipko ESC ali LEVA PUÅ ÄŒICA. Zaprite kontekstni meni z ESC."},{name:"Urejevalno Seznamsko Polje",
+legend:"Znotraj seznama, se premaknete na naslednji element seznama s tipko TAB ali PUŠČICO DOL. Z SHIFT + TAB ali PUŠČICO GOR se premaknete na prejšnji element seznama. Pritisnite tipko SPACE ali ENTER za izbiro elementa. Pritisnite tipko ESC, da zaprete seznam."},{name:"Urejevalna vrstica poti elementa",legend:"Pritisnite ${elementsPathFocus} za pomikanje po vrstici elementnih poti. S TAB ali DESNA PUŠČICA se premaknete na naslednji gumb elementa. Z SHIFT + TAB ali LEVO PUŠČICO se premaknete na prejšnji gumb elementa. Pritisnite SPACE ali ENTER za izbiro elementa v urejevalniku."}]},
+{name:"Ukazi",items:[{name:"Razveljavi ukaz",legend:"Pritisnite ${undo}"},{name:"Ponovi ukaz",legend:"Pritisnite ${redo}"},{name:"Krepki ukaz",legend:"Pritisnite ${bold}"},{name:"LežeÄi ukaz",legend:"Pritisnite ${italic}"},{name:"Poudarni ukaz",legend:"Pritisnite ${underline}"},{name:"Ukaz povezave",legend:"Pritisnite ${link}"},{name:"SkrÄi Orodno Vrstico Ukaz",legend:"Pritisnite ${toolbarCollapse}"},{name:"Dostop do prejÅ¡njega ukaza ostrenja",legend:"Pritisnite ${accessPreviousSpace} za dostop do najbližjega nedosegljivega osredotoÄenega prostora pred streÅ¡ico, npr.: dva sosednja HR elementa. Ponovite kombinacijo tipk, da dosežete oddaljene osredotoÄene prostore."},
+{name:"Dostop do naslednjega ukaza ostrenja",legend:"Pritisnite ${accessNextSpace} za dostop do najbližjega nedosegljivega osredotoÄenega prostora po streÅ¡ici, npr.: dva sosednja HR elementa. Ponovite kombinacijo tipk, da dosežete oddaljene osredotoÄene prostore."},{name:"PomoÄ Dostopnosti",legend:"Pritisnite ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",
+home:"Home",leftArrow:"Levo puÅ¡Äica",upArrow:"Gor puÅ¡Äica",rightArrow:"Desno puÅ¡Äica",downArrow:"Dol puÅ¡Äica",insert:"Insert","delete":"Delete",leftWindowKey:"Leva Windows tipka",rightWindowKey:"Desna Windows tipka",selectKey:"Select tipka",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Zmnoži",add:"Dodaj",subtract:"OdÅ¡tej",decimalPoint:"Decimalna vejica",
+divide:"Deli",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"PodpiÄje",equalSign:"enaÄaj",comma:"Vejica",dash:"Vezaj",period:"Pika",forwardSlash:"Desna poÅ¡evnica",graveAccent:"Krativec",openBracket:"Oklepaj",backSlash:"Leva poÅ¡evnica",closeBracket:"Oklepaj",singleQuote:"OpuÅ¡Äaj"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sq.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sq.js
new file mode 100644
index 0000000..12a8175
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sq.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sq",{title:"Udhëzimet e Qasjes",contents:"Përmbajtja ndihmëse. Për ta mbyllur dialogun shtyp ESC.",legend:[{name:"Të përgjithshme",items:[{name:"Shiriti i Redaktuesit",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Dialogu i Redaktuesit",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Komandat",items:[{name:"Rikthe komandën",legend:"Shtyp ${undo}"},{name:"Ribëj komandën",legend:"Shtyp ${redo}"},{name:"Komanda e trashjes së tekstit",legend:"Shtyp ${bold}"},{name:"Komanda kursive",legend:"Shtyp ${italic}"},
+{name:"Komanda e nënvijëzimit",legend:"Shtyp ${underline}"},{name:"Komanda e Nyjes",legend:"Shtyp ${link}"},{name:" Toolbar Collapse command",legend:"Shtyp ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:"Ndihmë Qasjeje",legend:"Shtyp ${a11yHelp}"}]}],backspace:"Prapa",tab:"Fletë",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Shenja majtas",upArrow:"Shenja sipër",rightArrow:"Shenja djathtas",downArrow:"Shenja poshtë",insert:"Shto","delete":"Grise",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Shto",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Presje",dash:"vizë",period:"Pikë",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Hape kllapën",backSlash:"Backslash",closeBracket:"Mbylle kllapën",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr-latn.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr-latn.js
new file mode 100644
index 0000000..0518371
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr-latn.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sr-latn",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Opšte",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr.js
new file mode 100644
index 0000000..f0ad0df
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sr.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sr",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Опште",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Commands",items:[{name:" Undo command",legend:"Press ${undo}"},{name:" Redo command",legend:"Press ${redo}"},{name:" Bold command",legend:"Press ${bold}"},{name:" Italic command",legend:"Press ${italic}"},{name:" Underline command",
+legend:"Press ${underline}"},{name:" Link command",legend:"Press ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/sv.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sv.js
new file mode 100644
index 0000000..17736f9
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/sv.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","sv",{title:"Hjälpmedelsinstruktioner",contents:"Hjälpinnehåll. För att stänga denna dialogruta trycker du på ESC.",legend:[{name:"Allmänt",items:[{name:"Editor verktygsfält",legend:"Tryck på ${toolbarFocus} för att navigera till verktygsfältet. Flytta till nästa och föregående verktygsfältsgrupp med TAB och SHIFT-TAB. Flytta till nästa och föregående knapp i verktygsfältet med HÖGERPIL eller VÄNSTERPIL. Tryck Space eller ENTER för att aktivera knappen i verktygsfältet."},
+{name:"Dialogeditor",legend:"Inuti en dialogruta, tryck TAB för att navigera till nästa fält i dialogrutan. Du trycker SKIFT + TAB för att flytta till föregående fält. Tryck ENTER för att skicka. Du avbryter och stänger dialogen med ESC. För dialogrutor som har flera flikar, tryck ALT + F10 navigera till fliklistan. Flytta sedan till nästa flik med HÖGERPIL. Flytta till föregående flik med SHIFT + TAB eller VÄNSTERPIL. Tryck Space eller ENTER för att välja fliken."},{name:"Editor för innehållsmeny",
+legend:"Tryck på $ {contextMenu} eller PROGRAMTANGENTEN för att öppna snabbmenyn. Flytta sedan till nästa menyalternativ med TAB eller NEDPIL. Flytta till föregående alternativ med SHIFT + TABB eller UPPIL. Tryck Space eller ENTER för att välja menyalternativ. Öppna undermeny av nuvarande alternativ med SPACE eller ENTER eller HÖGERPIL. Gå tillbaka till överordnade menyalternativ med ESC eller VÄNSTERPIL. Stäng snabbmenyn med ESC."},{name:"Editor för List Box",legend:"Inuti en list-box, gå till nästa listobjekt med TAB eller NEDPIL. Flytta till föregående listobjekt med SHIFT + TAB eller UPPIL. Tryck Space eller ENTER för att välja listan alternativet. Tryck ESC för att stänga listan-boxen."},
+{name:"Editor för elementens sökväg",legend:"Tryck på $ {elementsPathFocus} för att navigera till verktygsfältet för elementens sökvägar. Flytta till nästa elementknapp med TAB eller HÖGERPIL. Flytta till föregående knapp med SKIFT + TAB eller VÄNSTERPIL. Tryck Space eller ENTER för att välja element i redigeraren."}]},{name:"Kommandon",items:[{name:"Kommandot ångra",legend:"Tryck på ${undo}"},{name:"Kommandot gör om",legend:"Tryck på ${redo}"},{name:"Kommandot fet stil",legend:"Tryck på ${bold}"},
+{name:"Kommandot kursiv",legend:"Tryck på ${italic}"},{name:"Kommandot understruken",legend:"Tryck på ${underline}"},{name:"Kommandot länk",legend:"Tryck på ${link}"},{name:"Verktygsfält Dölj kommandot",legend:"Tryck på ${toolbarCollapse}"},{name:"Gå till föregående fokus plats",legend:"Tryck på ${accessPreviousSpace} för att gå till närmast onåbara utrymme före markören, exempel: två intilliggande HR element. Repetera tangentkombinationen för att gå till nästa."},{name:"Tillgå nästa fokuskommandots utrymme",
+legend:"Tryck ${accessNextSpace} på för att komma åt den närmaste onåbar fokus utrymme efter cirkumflex, till exempel: två intilliggande HR element. Upprepa tangentkombinationen för att nå avlägsna fokus utrymmen."},{name:"Hjälp om tillgänglighet",legend:"Tryck ${a11yHelp}"}]}],backspace:"Backsteg",tab:"Tab",enter:"Retur",shift:"Skift",ctrl:"Ctrl",alt:"Alt",pause:"Paus",capslock:"Caps lock",escape:"Escape",pageUp:"Sida Up",pageDown:"Sida Ned",end:"Slut",home:"Hem",leftArrow:"Vänsterpil",upArrow:"Uppil",
+rightArrow:"Högerpil",downArrow:"Nedåtpil",insert:"Infoga","delete":"Radera",leftWindowKey:"Vänster Windowstangent",rightWindowKey:"Höger Windowstangent",selectKey:"Välj tangent",numpad0:"Nummer 0",numpad1:"Nummer 1",numpad2:"Nummer 2",numpad3:"Nummer 3",numpad4:"Nummer 4",numpad5:"Nummer 5",numpad6:"Nummer 6",numpad7:"Nummer 7",numpad8:"Nummer 8",numpad9:"Nummer 9",multiply:"Multiplicera",add:"Addera",subtract:"Minus",decimalPoint:"Decimalpunkt",divide:"Dividera",f1:"F1",f2:"F2",f3:"F3",f4:"F4",
+f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semikolon",equalSign:"Lika med tecken",comma:"Komma",dash:"Minus",period:"Punkt",forwardSlash:"Snedstreck framåt",graveAccent:"Accent",openBracket:"Öppningsparentes",backSlash:"Snedstreck bakåt",closeBracket:"Slutparentes",singleQuote:"Enkelt Citattecken"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/th.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/th.js
new file mode 100644
index 0000000..3aa4b71
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/th.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","th",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"ทั่วไป",items:[{name:"à¹à¸–บเครื่องมือสำหรับเครื่องมือช่วยพิมพ์",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"คำสั่ง",items:[{name:"เลิà¸à¸—ำคำสั่ง",legend:"วาง ${undo}"},{name:"คำสั่งสำหรับทำซ้ำ",legend:"วาง ${redo}"},{name:"คำสั่งสำหรับตัวหนา",legend:"วาง ${bold}"},{name:"คำสั่งสำหรับตัวเอียง",legend:"วาง ${italic}"},{name:"คำสั่งสำหรับขีดเส้นใต้",
+legend:"วาง ${underline}"},{name:"คำสั่งสำหรับลิงà¸à¹Œ",legend:"วาง ${link}"},{name:" Toolbar Collapse command",legend:"Press ${toolbarCollapse}"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"Press ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/tr.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/tr.js
new file mode 100644
index 0000000..423a51c
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/tr.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","tr",{title:"Erişilebilirlik Talimatları",contents:"Yardım içeriği. Bu pencereyi kapatmak için ESC tuşuna basın.",legend:[{name:"Genel",items:[{name:"Düzenleyici Araç Çubuğu",legend:"Araç çubuğunda gezinmek için ${toolbarFocus} basın. TAB ve SHIFT-TAB ile önceki ve sonraki araç çubuğu grubuna taşıyın. SAĞ OK veya SOL OK ile önceki ve sonraki bir araç çubuğu düğmesini hareket ettirin. SPACE tuşuna basın veya araç çubuğu düğmesini etkinleştirmek için ENTER tuşna basın."},
+{name:"Diyalog Düzenleyici",legend:"Dialog penceresi içinde, sonraki iletişim alanına gitmek için SEKME tuşuna basın, önceki alana geçmek için SHIFT + TAB tuşuna basın, pencereyi göndermek için ENTER tuşuna basın, dialog penceresini iptal etmek için ESC tuşuna basın. Birden çok sekme sayfaları olan diyalogların, sekme listesine gitmek için ALT + F10 tuşlarına basın. Sonra TAB veya SAĞ OK sonraki sekmeye taşıyın. SHIFT + TAB veya SOL OK ile önceki sekmeye geçin. Sekme sayfayı seçmek için SPACE veya ENTER tuşuna basın."},
+{name:"İçerik Menü Editörü",legend:"İçerik menüsünü açmak için ${contextMenu} veya UYGULAMA TUŞU'na basın. Daha sonra SEKME veya AŞAĞI OK ile bir sonraki menü seçeneği taşıyın. SHIFT + TAB veya YUKARI OK ile önceki seçeneğe gider. Menü seçeneğini seçmek için SPACE veya ENTER tuşuna basın. Seçili seçeneğin alt menüsünü SPACE ya da ENTER veya SAĞ OK açın. Üst menü öğesini geçmek için ESC veya SOL OK ile geri dönün. ESC ile bağlam menüsünü kapatın."},{name:"Liste Kutusu Editörü",legend:"Liste kutusu içinde, bir sonraki liste öğesine SEKME VEYA AŞAĞI OK ile taşıyın. SHIFT + TAB veya YUKARI önceki liste öğesi taşıyın. Liste seçeneği seçmek için SPACE veya ENTER tuşuna basın. Liste kutusunu kapatmak için ESC tuşuna basın."},
+{name:"Element Yol Çubuğu Editörü",legend:"Elementlerin yol çubuğunda gezinmek için ${ElementsPathFocus} basın. SEKME veya SAĞ OK ile sonraki element düğmesine taşıyın. SHIFT + TAB veya SOL OK önceki düğmeye hareket ettirin. Editör içindeki elementi seçmek için ENTER veya SPACE tuşuna basın."}]},{name:"Komutlar",items:[{name:"Komutu geri al",legend:"$(undo)'ya basın"},{name:"Komutu geri al",legend:"${redo} basın"},{name:" Kalın komut",legend:"${bold} basın"},{name:" İtalik komutu",legend:"${italic} basın"},
+{name:" Alttan çizgi komutu",legend:"${underline} basın"},{name:" Bağlantı komutu",legend:"${link} basın"},{name:" Araç çubuğu Toplama komutu",legend:"${toolbarCollapse} basın"},{name:"Önceki komut alanına odaklan",legend:"Düzeltme imleçinden önce, en yakın uzaktaki alana erişmek için ${accessPreviousSpace} basın, örneğin: iki birleşik HR elementleri. Aynı tuş kombinasyonu tekrarıyla diğer alanlarada ulaşın."},{name:"Sonraki komut alanına odaklan",legend:"Düzeltme imleçinden sonra, en yakın uzaktaki alana erişmek için ${accessNextSpace} basın, örneğin: iki birleşik HR elementleri. Aynı tuş kombinasyonu tekrarıyla diğer alanlarada ulaşın."},
+{name:"Erişilebilirlik Yardımı",legend:"${a11yHelp}'e basın"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Sayfa Yukarı",pageDown:"Sayfa Aşağı",end:"End",home:"Home",leftArrow:"Sol ok",upArrow:"Yukarı ok",rightArrow:"Sağ ok",downArrow:"Aşağı ok",insert:"Insert","delete":"Silme",leftWindowKey:"Sol windows tuşu",rightWindowKey:"Sağ windows tuşu",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Çarpma",add:"Toplama",subtract:"Çıkarma",decimalPoint:"Ondalık işareti",divide:"Bölme",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Noktalı virgül",equalSign:"Eşittir",comma:"Virgül",dash:"Eksi",period:"Nokta",forwardSlash:"Forward Slash",
+graveAccent:"Grave Accent",openBracket:"Parantez aç",backSlash:"Backslash",closeBracket:"Parantez kapa",singleQuote:"Tek tırnak"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/tt.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/tt.js
new file mode 100644
index 0000000..3fe133d
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/tt.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","tt",{title:"Accessibility Instructions",contents:"Help Contents. To close this dialog press ESC.",legend:[{name:"Гомуми",items:[{name:"Editor Toolbar",legend:"Press ${toolbarFocus} to navigate to the toolbar. Move to the next and previous toolbar group with TAB and SHIFT-TAB. Move to the next and previous toolbar button with RIGHT ARROW or LEFT ARROW. Press SPACE or ENTER to activate the toolbar button."},{name:"Editor Dialog",legend:"Inside a dialog, press TAB to navigate to next dialog field, press SHIFT + TAB to move to previous field, press ENTER to submit dialog, press ESC to cancel dialog. For dialogs that have multiple tab pages, press ALT + F10 to navigate to tab-list. Then move to next tab with TAB OR RIGTH ARROW. Move to previous tab with SHIFT + TAB or LEFT ARROW. Press SPACE or ENTER to select the tab page."},
+{name:"Editor Context Menu",legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Then move to next menu option with TAB or DOWN ARROW. Move to previous option with SHIFT+TAB or UP ARROW. Press SPACE or ENTER to select the menu option. Open sub-menu of current option with SPACE or ENTER or RIGHT ARROW. Go back to parent menu item with ESC or LEFT ARROW. Close context menu with ESC."},{name:"Editor List Box",legend:"Inside a list-box, move to next list item with TAB OR DOWN ARROW. Move to previous list item with SHIFT + TAB or UP ARROW. Press SPACE or ENTER to select the list option. Press ESC to close the list-box."},
+{name:"Editor Element Path Bar",legend:"Press ${elementsPathFocus} to navigate to the elements path bar. Move to next element button with TAB or RIGHT ARROW. Move to previous button with SHIFT+TAB or LEFT ARROW. Press SPACE or ENTER to select the element in editor."}]},{name:"Командалар",items:[{name:"Кайтару",legend:"${undo} баÑыгыз"},{name:"Кабатлау",legend:"${redo} баÑыгыз"},{name:"Калын",legend:"${bold} баÑыгыз"},{name:"КурÑив",legend:"${italic} баÑыгыз"},{name:"ÐÑтына Ñызылган",legend:"${underline} баÑыгыз"},
+{name:"Сылталама",legend:"${link} баÑыгыз"},{name:" Toolbar Collapse command",legend:"${toolbarCollapse} баÑыгыз"},{name:" Access previous focus space command",legend:"Press ${accessPreviousSpace} to access the closest unreachable focus space before the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},{name:" Access next focus space command",legend:"Press ${accessNextSpace} to access the closest unreachable focus space after the caret, for example: two adjacent HR elements. Repeat the key combination to reach distant focus spaces."},
+{name:" Accessibility Help",legend:"${a11yHelp} баÑыгыз"}]}],backspace:"Кайтару",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Тыныш",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Сул Ñкка ук",upArrow:"Ó¨Ñкә таба ук",rightArrow:"Уң Ñкка ук",downArrow:"ÐÑка таба ук",insert:"Ó¨ÑÑ‚Ó™Ò¯","delete":"Бетерү",leftWindowKey:"Сул Windows төймəÑе",rightWindowKey:"Уң Windows төймəÑе",selectKey:"Select төймəÑе",numpad0:"Numpad 0",numpad1:"Numpad 1",
+numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Тапкырлау",add:"Кушу",subtract:"Ðлу",decimalPoint:"Унарлы нокта",divide:"Бүлү",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Ðокталы өтер",equalSign:"Тигезлек билгеÑе",comma:"Өтер",dash:"Сызык",period:"Дәрәҗә",forwardSlash:"Кыек Ñызык",
+graveAccent:"ГравиÑ",openBracket:"Ò–Ó™Ñ Ð°Ñ‡Ñƒ",backSlash:"Кире кыек Ñызык",closeBracket:"Ò–Ó™Ñ Ñбу",singleQuote:"Бер иңле куштырнаклар"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/ug.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ug.js
new file mode 100644
index 0000000..2ee8a06
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/ug.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","ug",{title:"قوشۇمچە چۈشەندۈرۈش",contents:"ياردەم مەزمۇنى. بۇ سۆزلەشكۈنى ياپماقچى بولسىڭىز ESC نى بÛسىڭ.",legend:[{name:"ئادەتتىكى",items:[{name:"قورال بالداق تەھرىر",legend:"${toolbarFocus} بÛسىلسا قورال بالداققا ÙŠÛتەكلەيدۇ، TAB ياكى SHIFT+TAB ئارقىلىق قورال بالداق گۇرۇپپىسى تاللىنىدۇ، ئوڭ سول يا ئوقتا توپچا تاللىنىدۇ، بوشلۇق ياكى Enter كۇنۇپكىسىدا تاللانغان توپچىنى قوللىنىدۇ."},{name:"تەھرىرلىگۈچ سۆزلەشكۈسى",legend:"سۆزلەشكۈدە TAB كۇنۇپكىسىدا ÙƒÛيىنكى سۆز بۆلىكىگە يۆتكىلىدۇ، SHIFT + TAB بىرىكمە كۇنۇپكىسىدا ئالدىنقى سۆز بۆلىكىگە يۆتكىلىدۇ، ENTER كۇنۇپكىسىدا سۆزلەشكۈنى تاپشۇرىدۇ، ESC كۇنۇپكىسى سۆزلەشكۈدىن ۋاز ÙƒÛچىدۇ. ÙƒÛ†Ù¾ بەتكۈچلۈك سۆزلەشكۈگە نىسبەتەن، ALT + F10 دا بەتكۈچ تىزىمىغا يۆتكەيدۇ. ئاندىن TAB كۇنۇپكىسى ياكى ئوڭ يا ئوق كۇنۇپكىسى ÙƒÛيىنكى بەتكۈچكە يۆتكەيدۇ؛ SHIFT + TAB كۇنۇپكىسى ياكى سول يا ئوق كۇنۇپكىسى ئالدىنقى بەتكۈچكە يۆتكەيدۇ. بوشلۇق كۇنۇپكىسى ياكى ENTER كۇنۇپكىسى بەتكۈچنى تاللايدۇ."},
+{name:"تەھرىرلىگۈچ تىل مۇھىت تىزىملىكى",legend:"${contextMenu} ياكى ئەپ كۇنۇپكىسىدا تىل مۇھىت تىزىملىكىنى ئاچىدۇ. ئاندىن TAB ياكى ئاستى يا ئوق كۇنۇپكىسىدا ÙƒÛيىنكى تىزىملىك تۈرىگە يۆتكەيدۇ؛ SHIFT+TAB ياكى ئۈستى يا ئوق كۇنۇپكىسىدا ئالدىنقى تىزىملىك تۈرىگە يۆتكەيدۇ. بوشلۇق ياكى ENTER كۇنۇپكىسىدا تىزىملىك تۈرىنى تاللايدۇ. بوشلۇق، ENTER ياكى ئوڭ يا ئوق كۇنۇپكىسىدا تارماق تىزىملىكنى ئاچىدۇ. قايتىش تىزىملىكىگە ESC ياكى سول يا ئوق كۇنۇپكىسى ئىشلىتىلىدۇ. ESC كۇنۇپكىسىدا تىل مۇھىت تىزىملىكى تاقىلىدۇ."},{name:"تەھرىرلىگۈچ تىزىمى",
+legend:"تىزىم قۇتىسىدا، ÙƒÛيىنكى تىزىم تۈرىگە يۆتكەشتە TAB ياكى ئاستى يا ئوق كۇنۇپكىسى ئىشلىتىلىدۇ. ئالدىنقى تىزىم تۈرىگە يۆتكەشتە SHIFT + TAB ياكى ئۈستى يا ئوق كۇنۇپكىسى ئىشلىتىلىدۇ. بوشلۇق ياكى ENTER كۇنۇپكىسىدا تىزىم تۈرىنى تاللايدۇ.ESC كۇنۇپكىسىدا تىزىم قۇتىسىنى يىغىدۇ."},{name:"تەھرىرلىگۈچ ئÛÙ„ÛÙ…Ûنت يول بالداق",legend:"${elementsPathFocus} بÛسىلسا ئÛÙ„ÛÙ…Ûنت يول بالداققا ÙŠÛتەكلەيدۇ، TAB ياكى ئوڭ يا ئوقتا ÙƒÛيىنكى ئÛÙ„ÛÙ…Ûنت تاللىنىدۇ، SHIFT+TAB ياكى سول يا ئوقتا ئالدىنقى ئÛÙ„ÛÙ…Ûنت تاللىنىدۇ، بوشلۇق ياكى Enter كۇنۇپكىسىدا تەھرىرلىگۈچتىكى ئÛÙ„ÛÙ…Ûنت تاللىنىدۇ."}]},
+{name:"بۇيرۇق",items:[{name:"بۇيرۇقتىن ÙŠÛنىۋال",legend:"${undo} نى بÛسىڭ"},{name:"قايتىلاش بۇيرۇقى",legend:"${redo} نى بÛسىڭ"},{name:"توملىتىش بۇيرۇقى",legend:"${bold} نى بÛسىڭ"},{name:"يانتۇ بۇيرۇقى",legend:"${italic} نى بÛسىڭ"},{name:"ئاستى سىزىق بۇيرۇقى",legend:"${underline} نى بÛسىڭ"},{name:"ئۇلانما بۇيرۇقى",legend:"${link} نى بÛسىڭ"},{name:"قورال بالداق قاتلاش بۇيرۇقى",legend:"${toolbarCollapse} نى بÛسىڭ"},{name:"ئالدىنقى Ùوكۇس نۇقتىسىنى زىيارەت قىلىدىغان بۇيرۇق",legend:"${accessPreviousSpace} بÛسىپ ^ بەلگىسىگە ئەڭ ÙŠÛقىن زىيارەت قىلغىلى بولمايدىغان Ùوكۇس نۇقتا رايونىنىڭ ئالدىنى زىيارەت قىلىدۇ، مەسىلەن: ئۆز ئارا قوشنا ئىككى HR ئÛÙ„ÛÙ…Ûنت. بۇ بىرىكمە كۇنۇپكا تەكرارلانسا يىراقتىكى Ùوكۇس نۇقتا رايونىغا يەتكىلى بولىدۇ."},
+{name:"ÙƒÛيىنكى Ùوكۇس نۇقتىسىنى زىيارەت قىلىدىغان بۇيرۇق",legend:"${accessNextSpace} بÛسىپ ^ بەلگىسىگە ئەڭ ÙŠÛقىن زىيارەت قىلغىلى بولمايدىغان Ùوكۇس نۇقتا رايونىنىڭ كەينىنى زىيارەت قىلىدۇ، مەسىلەن: ئۆز ئارا قوشنا ئىككى HR ئÛÙ„ÛÙ…Ûنت. بۇ بىرىكمە كۇنۇپكا تەكرارلانسا يىراقتىكى Ùوكۇس نۇقتا رايونىغا يەتكىلى بولىدۇ."},{name:"توسالغۇسىز لايىھە چۈشەندۈرۈشى",legend:"${a11yHelp} نى بÛسىڭ"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",
+pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Left Arrow",upArrow:"Up Arrow",rightArrow:"Right Arrow",downArrow:"Down Arrow",insert:"Insert","delete":"Delete",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"Add",subtract:"Subtract",
+decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"Equal Sign",comma:"Comma",dash:"Dash",period:"Period",forwardSlash:"Forward Slash",graveAccent:"Grave Accent",openBracket:"Open Bracket",backSlash:"Backslash",closeBracket:"Close Bracket",singleQuote:"Single Quote"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/uk.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/uk.js
new file mode 100644
index 0000000..83a48b8
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/uk.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","uk",{title:"Спеціальні ІнÑтрукції",contents:"Довідка. ÐатиÑніть ESC Ñ– вона зникне.",legend:[{name:"ОÑновне",items:[{name:"Панель Редактора",legend:"ÐатиÑніть ${toolbarFocus} Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ до панелі інÑтрументів. Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¼Ñ–Ð¶ групами панелі інÑтрументів викориÑтовуйте TAB Ñ– SHIFT-TAB. Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¼Ñ–Ð¶ кнопками панелі Ñ–Ñтрументів викориÑтовуйте кнопки СТРІЛКРВПРÐВО або ВЛІВО. ÐатиÑніть ПРОПУСК або ENTER Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку кнопки панелі інÑтрументів"},{name:"Діалог Редактора",
+legend:"У діалозі натиÑніть клавішу TAB Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ до наÑтупного полÑ, натиÑніть SHIFT + TAB, щоб перейти до попереднього полÑ, натиÑніть ENTER, щоб відправити дані, натиÑніть ESC, щоб ÑкаÑувати. Ð”Ð»Ñ Ð²Ñ–ÐºÐ¾Ð½, Ñкі мають кілька вкладок, натиÑніть ALT + F10 Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ñƒ до ÑпиÑку вкладок. Перехід до наÑтупної вкладки TAB ÐБО СТРІЛКРВПРÐВО. Перехід до попередньої вкладки за допомогою SHIFT + TAB або СТРІЛКРВЛІВО. ÐатиÑніть ПРОПУСК або ENTER, щоб вибрати вкладку."},{name:"КонтекÑтне Меню Редактора",
+legend:"Press ${contextMenu} or APPLICATION KEY to open context-menu. Потім перейдіть до наÑтупного пункту меню за допомогою TAB або СТРІЛКИ Ð’ÐИЗ. ÐатиÑніть ПРОПУСК або ENTER Ð´Ð»Ñ Ð²Ð¸Ð±Ð¾Ñ€Ñƒ параметру меню. Відкрийте підменю поточного параметру, натиÑнувши ПРОПУСК або ENTER або СТРІЛКУ ВПРÐВО. Перейдіть до батьківÑького елемента меню, натиÑнувши ESC або СТРІЛКУ ВЛІВО. Закрийте контекÑтне меню, натиÑнувши ESC."},{name:"Скринька СпиÑків Редактора",legend:"Ð’Ñередині ÑпиÑку переходимо до наÑтупного пункту ÑпиÑку клавішею TAB або СТРІЛКРВÐИЗ. Перейти до попереднього елемента ÑпиÑку можна SHIFT + TAB або СТРІЛКРВГОРУ. ÐатиÑніть ПРОПУСК або ENTER, щоб вибрати параметр ÑпиÑку. ÐатиÑніть клавішу ESC, щоб закрити ÑпиÑок."},
+{name:"ШлÑÑ… до елемента редактора",legend:"ÐатиÑніть ${elementsPathFocus} Ð´Ð»Ñ Ð½Ð°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ— між елементами панелі. Перейдіть до наÑтупного елемента кнопкою TAB або СТРІЛКРВПРÐВО. Перейдіть до попереднього елемента кнопкою SHIFT+TAB або СТРІЛКРВЛІВО. ÐатиÑніть ПРОПУСК або ENTER Ð´Ð»Ñ Ð²Ð¸Ð±Ð¾Ñ€Ñƒ елемента в редакторі."}]},{name:"Команди",items:[{name:"Відмінити команду",legend:"ÐатиÑніть ${undo}"},{name:"Повторити",legend:"ÐатиÑніть ${redo}"},{name:"Жирний",legend:"ÐатиÑніть ${bold}"},{name:"КурÑив",legend:"ÐатиÑніть ${italic}"},
+{name:"ПідкреÑлений",legend:"ÐатиÑніть ${underline}"},{name:"ПоÑиланнÑ",legend:"ÐатиÑніть ${link}"},{name:"Згорнути панель інÑтрументів",legend:"ÐатиÑніть ${toolbarCollapse}"},{name:"ДоÑтуп до попереднього міÑÑ†Ñ Ñ„Ð¾ÐºÑƒÑуваннÑ",legend:"ÐатиÑніть ${accessNextSpace} Ð´Ð»Ñ Ð´Ð¾Ñтупу до найближчої недоÑÑжної облаÑÑ‚Ñ– фокуÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´ кареткою, наприклад: два ÑуÑідні елементи HR. Повторіть комбінацію клавіш Ð´Ð»Ñ Ð´Ð¾ÑÑÐ³Ð½ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¸Ñ… облаÑтей фокуÑуваннÑ."},{name:"ДоÑтуп до наÑтупного міÑÑ†Ñ Ñ„Ð¾ÐºÑƒÑуваннÑ",legend:"ÐатиÑніть ${accessNextSpace} Ð´Ð»Ñ Ð´Ð¾Ñтупу до найближчої недоÑÑжної облаÑÑ‚Ñ– фокуÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–ÑÐ»Ñ ÐºÐ°Ñ€ÐµÑ‚ÐºÐ¸, наприклад: два ÑуÑідні елементи HR. Повторіть комбінацію клавіш Ð´Ð»Ñ Ð´Ð¾ÑÑÐ³Ð½ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¸Ñ… облаÑтей фокуÑуваннÑ."},
+{name:"Допомога з доÑтупноÑÑ‚Ñ–",legend:"ÐатиÑніть ${a11yHelp}"}]}],backspace:"Backspace",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Esc",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"Ліва Ñтрілка",upArrow:"Стрілка вгору",rightArrow:"Права Ñтрілка",downArrow:"Стрілка вниз",insert:"Ð’Ñтавити","delete":"Видалити",leftWindowKey:"Ліва клавіша Windows",rightWindowKey:"Права клавіша Windows",selectKey:"Виберіть клавішу",numpad0:"Numpad 0",
+numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"МноженнÑ",add:"Додати",subtract:"ВідніманнÑ",decimalPoint:"ДеÑÑткова кома",divide:"ДіленнÑ",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Крапка з комою",equalSign:"Знак рівноÑÑ‚Ñ–",comma:"Кома",dash:"Тире",period:"Період",
+forwardSlash:"КоÑа риÑка",graveAccent:"ГравіÑ",openBracket:"Відкрити дужку",backSlash:"Зворотна коÑа риÑка",closeBracket:"Закрити дужку",singleQuote:"Одинарні лапки"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/vi.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/vi.js
new file mode 100644
index 0000000..a7c61cc
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/vi.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","vi",{title:"HÆ°á»›ng dẫn trợ năng",contents:"Ná»™i dung Há»— trợ. Nhấn ESC để đóng há»™p thoại.",legend:[{name:"Chung",items:[{name:"Thanh công cụ soạn thảo",legend:"Nhấn ${toolbarFocus} để Ä‘iá»u hÆ°á»›ng đến thanh công cụ. Nhấn TAB và SHIFT-TAB để chuyển đến nhóm thanh công cụ khác. Nhấn MŨI TÊN PHẢI hoặc MŨI TÊN TRÃI để chuyển sang nút khác trên thanh công cụ. Nhấn PHÃM CÃCH hoặc ENTER để kích hoạt nút trên thanh công cụ."},{name:"Há»™p thoại Biên t",legend:"Bên trong má»™t há»™p thoại, nhấn TAB để chuyển sang trÆ°á»ng tiếp theo, nhấn SHIFT + TAB để quay lại trÆ°á»ng phía trÆ°á»›c, nhấn ENTER để chấp nhận, nhấn ESC để đóng há»™p thoại. Äối vá»›i các há»™p thoại có nhiá»u tab, nhấn ALT + F10 để chuyển đến danh sách các tab. Sau đó nhấn TAB hoặc MŨI TÊN SANG PHẢI để chuyển sang tab tiếp theo. Nhấn SHIFT + TAB hoặc MŨI TÊN SANG TRÃI để chuyển sang tab trÆ°á»›c đó. Nhấn DẤU CÃCH hoặc ENTER để chá»n tab."},
+{name:"Trình Ä‘Æ¡n Ngữ cảnh cBá»™ soạn thảo",legend:"Nhấn ${contextMenu} hoặc PHÃM ỨNG DỤNG để mở thá»±c Ä‘Æ¡n ngữ cảnh. Sau đó nhấn TAB hoặc MŨI TÊN XUá»NG để di chuyển đến tuỳ chá»n tiếp theo của thá»±c Ä‘Æ¡n. Nhấn SHIFT+TAB hoặc MŨI TÊN LÊN để quay lại tuỳ chá»n trÆ°á»›c. Nhấn DẤU CÃCH hoặc ENTER để chá»n tuỳ chá»n của thá»±c Ä‘Æ¡n. Nhấn DẤU CÃCH hoặc ENTER hoặc MŨI TÊN SANG PHẢI để mở thá»±c Ä‘Æ¡n con của tuỳ chá»n hiện tại. Nhấn ESC hoặc MŨI TÊN SANG TRÃI để quay trở lại thá»±c Ä‘Æ¡n gốc. Nhấn ESC để đóng thá»±c Ä‘Æ¡n ngữ cảnh."},
+{name:"Há»™p danh sách trình biên tập",legend:"Trong má»™t danh sách chá»n, di chuyển đối tượng tiếp theo vá»›i phím Tab hoặc phím mÅ©i tên hÆ°á»›ng xuống. Di chuyển đến đối tượng trÆ°á»›c đó bằng cách nhấn tổ hợp phím Shift+Tab hoặc mÅ©i tên hÆ°á»›ng lên. Phím khoảng cách hoặc phím Enter để chá»n các tùy chá»n trong danh sách. Nhấn phím Esc để đóng lại danh sách chá»n."},{name:"Thanh Ä‘Æ°á»ng dẫn các đối tượng",legend:"Nhấn ${elementsPathFocus} để Ä‘iá»u hÆ°á»›ng các đối tượng trong thanh Ä‘Æ°á»ng dẫn. Di chuyển đến đối tượng tiếp theo bằng phím Tab hoặc phím mÅ©i tên bên phải. Di chuyển đến đối tượng trÆ°á»›c đó bằng tổ hợp phím Shift+Tab hoặc phím mÅ©i tên bên trái. Nhấn phím khoảng cách hoặc Enter để chá»n đối tượng trong trình soạn thảo."}]},
+{name:"Lệnh",items:[{name:"Làm lại lện",legend:"Ấn ${undo}"},{name:"Làm lại lệnh",legend:"Ấn ${redo}"},{name:"Lệnh in đậm",legend:"Ấn ${bold}"},{name:"Lệnh in nghiêng",legend:"Ấn ${italic}"},{name:"Lệnh gạch dÆ°á»›i",legend:"Ấn ${underline}"},{name:"Lệnh liên kết",legend:"Nhấn ${link}"},{name:"Lệnh hiển thị thanh công cụ",legend:"Nhấn${toolbarCollapse}"},{name:"Truy cập đến lệnh tập trung vào khoảng cách trÆ°á»›c đó",legend:"Ấn ${accessPreviousSpace} để truy cập đến phần tập trung khoảng cách sau phần còn sót lại của khoảng cách gần nhất vốn không tác Ä‘á»™ng đến được , thí dụ: hai yếu tố Ä‘iá»u chỉnh HR. Lặp lại các phím kết há»ep này để vÆ°Æ¡n đến phần khoảng cách."},
+{name:"Truy cập phần đối tượng lệnh khoảng trống",legend:"Ấn ${accessNextSpace} để truy cập đến phần tập trung khoảng cách sau phần còn sót lại của khoảng cách gần nhất vốn không tác Ä‘á»™ng đến được , thí dụ: hai yếu tố Ä‘iá»u chỉnh HR. Lặp lại các phím kết há»ep này để vÆ°Æ¡n đến phần khoảng cách."},{name:"Trợ giúp liên quan",legend:"Nhấn ${a11yHelp}"}]}],backspace:"Phím Backspace",tab:"Phím Tab",enter:"Phím Tab",shift:"Phím Shift",ctrl:"Phím Ctrl",alt:"Phím Alt",pause:"Phím Pause",capslock:"Phím Caps Lock",
+escape:"Phím Escape",pageUp:"Phím Page Up",pageDown:"Phím Page Down",end:"Phím End",home:"Phím Home",leftArrow:"Phím Left Arrow",upArrow:"Phím Up Arrow",rightArrow:"Phím Right Arrow",downArrow:"Phím Down Arrow",insert:"Chèn","delete":"Xóa",leftWindowKey:"Phím Left Windows",rightWindowKey:"Phím Right Windows ",selectKey:"Chá»n phím",numpad0:"Phím 0",numpad1:"Phím 1",numpad2:"Phím 2",numpad3:"Phím 3",numpad4:"Phím 4",numpad5:"Phím 5",numpad6:"Phím 6",numpad7:"Phím 7",numpad8:"Phím 8",numpad9:"Phím 9",
+multiply:"Nhân",add:"Thêm",subtract:"Trừ",decimalPoint:"Äiểm số thập phân",divide:"Chia",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Dấu chấm phẩy",equalSign:"Äăng nhập bằng",comma:"Dấu phẩy",dash:"Dấu gạch ngang",period:"Phím .",forwardSlash:"Phím /",graveAccent:"Phím `",openBracket:"Open Bracket",backSlash:"Dấu gạch chéo ngược",closeBracket:"Gần giá đỡ",singleQuote:"Trích dẫn"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js
new file mode 100644
index 0000000..9cc5ce3
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","zh-cn",{title:"辅助功能说明",contents:"帮助内容。è¦å…³é—­æ­¤å¯¹è¯æ¡†è¯·æŒ‰ ESC 键。",legend:[{name:"常规",items:[{name:"编辑器工具æ ",legend:"按 ${toolbarFocus} 导航到工具æ ï¼Œä½¿ç”¨ TAB 键和 SHIFT+TAB 组åˆé”®ç§»åŠ¨åˆ°ä¸Šä¸€ä¸ªå’Œä¸‹ä¸€ä¸ªå·¥å…·æ ç»„。使用左å³ç®­å¤´é”®ç§»åŠ¨åˆ°ä¸Šä¸€ä¸ªå’Œä¸‹ä¸€ä¸ªå·¥å…·æ æŒ‰é’®ã€‚按空格键或回车键以选中工具æ æŒ‰é’®ã€‚"},{name:"编辑器对è¯æ¡†",legend:"在对è¯æ¡†å†…,TAB 键移动到下一个字段,SHIFT + TAB 组åˆé”®ç§»åŠ¨åˆ°ä¸Šä¸€ä¸ªå­—段,ENTER é”®æ交对è¯æ¡†ï¼ŒESC é”®å–消对è¯æ¡†ã€‚对于有多选项å¡çš„对è¯æ¡†ï¼Œç”¨ALT + F10æ¥ç§»åˆ°é€‰é¡¹å¡åˆ—表。然åŽç”¨ TAB 键或者å‘å³ç®­å¤´æ¥ç§»åŠ¨åˆ°ä¸‹ä¸€ä¸ªé€‰é¡¹å¡ï¼›SHIFT + TAB 组åˆé”®æˆ–者å‘左箭头移动到上一个选项å¡ã€‚用 SPACE 键或者 ENTER 键选择选项å¡ã€‚"},{name:"编辑器上下文èœå•",legend:"用 ${contextMenu} 或者“应用程åºé”®â€æ‰“开上下文èœå•ã€‚然åŽç”¨ TAB 键或者下箭头键æ¥ç§»åŠ¨åˆ°ä¸‹ä¸€ä¸ªèœå•é¡¹ï¼›SHIFT + TAB 组åˆé”®æˆ–者上箭头键移动到上一个èœå•é¡¹ã€‚用 SPACE 键或者 ENTER 键选择èœå•é¡¹ã€‚用 SPACE 键,ENTER 键或者å³ç®­å¤´é”®æ‰“å¼€å­èœå•ã€‚返回èœå•ç”¨ ESC 键或者左箭头键。用 ESC 键关闭上下文èœå•ã€‚"},
+{name:"编辑器列表框",legend:"在列表框中,移到下一列表项用 TAB 键或者下箭头键。移到上一列表项用SHIFT + TAB 组åˆé”®æˆ–者上箭头键,用 SPACE 键或者 ENTER 键选择列表项。用 ESC 键收起列表框。"},{name:"编辑器元素路径æ ",legend:"按 ${elementsPathFocus} 以导航到元素路径æ ï¼Œä½¿ç”¨ TAB 键或å³ç®­å¤´é”®é€‰æ‹©ä¸‹ä¸€ä¸ªå…ƒç´ ï¼Œä½¿ç”¨ SHIFT+TAB 组åˆé”®æˆ–左箭头键选择上一个元素,按空格键或回车键以选定编辑器里的元素。"}]},{name:"命令",items:[{name:" 撤消命令",legend:"按 ${undo}"},{name:" é‡åšå‘½ä»¤",legend:"按 ${redo}"},{name:" 加粗命令",legend:"按 ${bold}"},{name:" 倾斜命令",legend:"按 ${italic}"},{name:" 下划线命令",legend:"按 ${underline}"},{name:" 链接命令",legend:"按 ${link}"},{name:" 工具æ æŠ˜å å‘½ä»¤",legend:"按 ${toolbarCollapse}"},
+{name:"访问å‰ä¸€ä¸ªç„¦ç‚¹åŒºåŸŸçš„命令",legend:"按 ${accessPreviousSpace} 访问^符å·å‰æœ€è¿‘çš„ä¸å¯è®¿é—®çš„焦点区域,例如:两个相邻的 HR 元素。é‡å¤æ­¤ç»„åˆæŒ‰é”®å¯ä»¥åˆ°è¾¾è¿œå¤„的焦点区域。"},{name:"访问下一个焦点区域命令",legend:"按 ${accessNextSpace} 以访问^符å·åŽæœ€è¿‘çš„ä¸å¯è®¿é—®çš„焦点区域。例如:两个相邻的 HR 元素。é‡å¤æ­¤ç»„åˆæŒ‰é”®å¯ä»¥åˆ°è¾¾è¿œå¤„的焦点区域。"},{name:"辅助功能帮助",legend:"按 ${a11yHelp}"}]}],backspace:"退格键",tab:"Tab é”®",enter:"回车键",shift:"Shift é”®",ctrl:"Ctrl é”®",alt:"Alt é”®",pause:"æš‚åœé”®",capslock:"大写é”定键",escape:"Esc é”®",pageUp:"上翻页键",pageDown:"下翻页键",end:"行尾键",home:"行首键",leftArrow:"å‘左箭头键",upArrow:"å‘上箭头键",rightArrow:"å‘å³ç®­å¤´é”®",downArrow:"å‘下箭头键",
+insert:"æ’入键","delete":"删除键",leftWindowKey:"å·¦ WIN é”®",rightWindowKey:"å³ WIN é”®",selectKey:"选择键",numpad0:"å°é”®ç›˜ 0 é”®",numpad1:"å°é”®ç›˜ 1 é”®",numpad2:"å°é”®ç›˜ 2 é”®",numpad3:"å°é”®ç›˜ 3 é”®",numpad4:"å°é”®ç›˜ 4 é”®",numpad5:"å°é”®ç›˜ 5 é”®",numpad6:"å°é”®ç›˜ 6 é”®",numpad7:"å°é”®ç›˜ 7 é”®",numpad8:"å°é”®ç›˜ 8 é”®",numpad9:"å°é”®ç›˜ 9 é”®",multiply:"星å·é”®",add:"加å·é”®",subtract:"å‡å·é”®",decimalPoint:"å°æ•°ç‚¹é”®",divide:"除å·é”®",f1:"F1 é”®",f2:"F2 é”®",f3:"F3 é”®",f4:"F4 é”®",f5:"F5 é”®",f6:"F6 é”®",f7:"F7 é”®",f8:"F8 é”®",f9:"F9 é”®",f10:"F10 é”®",f11:"F11 é”®",f12:"F12 é”®",numLock:"æ•°å­—é”定键",scrollLock:"滚动é”定键",
+semiColon:"分å·é”®",equalSign:"ç­‰å·é”®",comma:"逗å·é”®",dash:"短划线键",period:"å¥å·é”®",forwardSlash:"æ–œæ é”®",graveAccent:"é‡éŸ³ç¬¦é”®",openBracket:"左中括å·é”®",backSlash:"åæ–œæ é”®",closeBracket:"å³ä¸­æ‹¬å·é”®",singleQuote:"å•å¼•å·é”®"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js b/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js
new file mode 100644
index 0000000..b495f41
--- /dev/null
+++ b/js/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("a11yhelp","zh",{title:"輔助工具指å—",contents:"說明內容。若è¦é—œé–‰æ­¤å°è©±æ¡†è«‹æŒ‰ã€ŒESCã€ã€‚",legend:[{name:"一般",items:[{name:"編輯器工具列",legend:"請按「${toolbarFocus}ã€ä»¥ç€è¦½å·¥å…·åˆ—。\r\n利用「TABã€æˆ–「SHIFT+TABã€ä»¥ä¾¿ç§»å‹•åˆ°ä¸‹ä¸€å€‹æˆ–å‰ä¸€å€‹å·¥å…·åˆ—群組。\r\n利用「→ã€æˆ–「â†ã€ä»¥ä¾¿ç§»å‹•åˆ°ä¸‹ä¸€å€‹æˆ–å‰ä¸€å€‹å·¥å…·åˆ—按鈕。\r\n請按下「空白éµã€æˆ–「ENTERã€éµå•Ÿå‹•å·¥å…·åˆ—按鈕。"},{name:"編輯器å°è©±æ–¹å¡Š",legend:"在å°è©±æ¡†ä¸­ï¼Œè«‹æŒ‰ TAB éµä»¥ä¾¿ç§»å‹•åˆ°ä¸‹å€‹æ¬„ä½ï¼Œè«‹æŒ‰ SHIFT + TAB 以便移動到å‰å€‹æ¬„ä½ï¼›è«‹æŒ‰ ENTER 以æ交å°è©±æ¡†è³‡æ–™ï¼Œæˆ–按下 ESC å–消å°è©±æ¡†ã€‚\r\n若是有多個é æ¡†çš„å°è©±æ¡†ï¼Œè«‹æŒ‰ ALT + F10 以移動到é æ¡†åˆ—表,並以 TAB 或是 → æ–¹å‘éµç§»å‹•åˆ°ä¸‹å€‹é æ¡†ã€‚以 SHIFT + TAB 或是 ↠方å‘éµç§»å‹•åˆ°å‰å€‹é æ¡†ã€‚按下 ç©ºç™½éµ æˆ–æ˜¯ ENTER 以é¸å–é æ¡†ã€‚"},{name:"編輯器內容功能表",
+legend:"請按下「${contextMenu}ã€æˆ–是「應用程å¼éµã€ä»¥é–‹å•Ÿå…§å®¹é¸å–®ã€‚以「TABã€æˆ–是「↓ã€éµç§»å‹•åˆ°ä¸‹ä¸€å€‹é¸å–®é¸é …。以「SHIFT + TABã€æˆ–是「↑ã€éµç§»å‹•åˆ°ä¸Šä¸€å€‹é¸å–®é¸é …。按下「空白éµã€æˆ–是「ENTERã€éµä»¥é¸å–é¸å–®é¸é …。以「空白éµã€æˆ–「ENTERã€æˆ–「→ã€é–‹å•Ÿç›®å‰é¸é …之å­é¸å–®ã€‚以「ESCã€æˆ–「â†ã€å›žåˆ°çˆ¶é¸å–®ã€‚以「ESCã€éµé—œé–‰å…§å®¹é¸å–®ã€ã€‚"},{name:"編輯器清單方塊",legend:"在列表中,請利用 TAB 或 ↓ æ–¹å‘éµä»¥ç§»å‹•åˆ°ä¸‹ä¸€å€‹é …目;或利用 SHIFT + TAB 或 ↑ æ–¹å‘éµç§»å‹•åˆ°å‰ä¸€å€‹é …目。請按下 ç©ºç™½éµ æˆ–æ˜¯ ENTER 以é¸å–項目。請按 ESC 關閉列表。"},{name:"編輯器元件路徑工具列",legend:"請按「${elementsPathFocus}ã€ä»¥ç€è¦½å…ƒç´ è·¯å¾‘工具列。\r\n利用「TABã€æˆ–「→ã€ä»¥ä¾¿ç§»å‹•åˆ°ä¸‹ä¸€å€‹å…ƒç´ æŒ‰éˆ•ã€‚\r\n利用「SHIFT+TABã€æˆ–「â†ã€ä»¥ä¾¿ç§»å‹•åˆ°å‰ä¸€å€‹å…ƒç´ æŒ‰éˆ•ã€‚\r\n請按下「空白éµã€æˆ–「ENTERã€éµé¸æ“‡ç·¨è¼¯å™¨ä¸­çš„元素。"}]},{name:"命令",items:[{name:"復原命令",
+legend:"請按下「${undo}ã€"},{name:"é‡è¤‡å‘½ä»¤",legend:"請按下「 ${redo}ã€"},{name:"粗體命令",legend:"請按下「${bold}ã€"},{name:"斜體",legend:"請按下「${italic}ã€"},{name:"底線命令",legend:"請按下「${underline}ã€"},{name:"連çµ",legend:"請按下「${link}ã€"},{name:"éš±è—工具列",legend:"請按下「${toolbarCollapse}ã€"},{name:"å­˜å–å‰ä¸€å€‹ç„¦é»žç©ºé–“命令",legend:"請按下 ${accessPreviousSpace} 以存å–最近但無法é è¿‘之æ’字符號å‰çš„焦點空間。舉例:二個相鄰的 HR 元素。\r\né‡è¤‡æŒ‰éµä»¥å­˜å–較é çš„焦點空間。"},{name:"å­˜å–下一個焦點空間命令",legend:"請按下 ${accessNextSpace} 以存å–最近但無法é è¿‘之æ’字符號後的焦點空間。舉例:二個相鄰的 HR 元素。\r\né‡è¤‡æŒ‰éµä»¥å­˜å–較é çš„焦點空間。"},{name:"å”助工具說明",legend:"請按下「${a11yHelp}ã€"}]}],
+backspace:"退格éµ",tab:"Tab",enter:"Enter",shift:"Shift",ctrl:"Ctrl",alt:"Alt",pause:"Pause",capslock:"Caps Lock",escape:"Escape",pageUp:"Page Up",pageDown:"Page Down",end:"End",home:"Home",leftArrow:"å‘左箭號",upArrow:"å‘上éµè™Ÿ",rightArrow:"å‘å³éµè™Ÿ",downArrow:"å‘下éµè™Ÿ",insert:"æ’å…¥","delete":"刪除",leftWindowKey:"Left Windows key",rightWindowKey:"Right Windows key",selectKey:"Select key",numpad0:"Numpad 0",numpad1:"Numpad 1",numpad2:"Numpad 2",numpad3:"Numpad 3",numpad4:"Numpad 4",numpad5:"Numpad 5",numpad6:"Numpad 6",
+numpad7:"Numpad 7",numpad8:"Numpad 8",numpad9:"Numpad 9",multiply:"Multiply",add:"新增",subtract:"Subtract",decimalPoint:"Decimal Point",divide:"Divide",f1:"F1",f2:"F2",f3:"F3",f4:"F4",f5:"F5",f6:"F6",f7:"F7",f8:"F8",f9:"F9",f10:"F10",f11:"F11",f12:"F12",numLock:"Num Lock",scrollLock:"Scroll Lock",semiColon:"Semicolon",equalSign:"等號",comma:"逗號",dash:"虛線",period:"å¥é»ž",forwardSlash:"斜線",graveAccent:"抑音符號",openBracket:"左方括號",backSlash:"å斜線",closeBracket:"å³æ–¹æ‹¬è™Ÿ",singleQuote:"單引號"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/about/dialogs/about.js b/js/ckeditor/plugins/about/dialogs/about.js
new file mode 100644
index 0000000..6e78f9c
--- /dev/null
+++ b/js/ckeditor/plugins/about/dialogs/about.js
@@ -0,0 +1,7 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("about",function(a){var a=a.lang.about,b=CKEDITOR.getUrl(CKEDITOR.plugins.get("about").path+"dialogs/"+(CKEDITOR.env.hidpi?"hidpi/":"")+"logo_ckeditor.png");return{title:CKEDITOR.env.ie?a.dlgTitle:a.title,minWidth:390,minHeight:230,contents:[{id:"tab1",label:"",title:"",expand:!0,padding:0,elements:[{type:"html",html:'<style type="text/css">.cke_about_container{color:#000 !important;padding:10px 10px 0;margin-top:5px}.cke_about_container p{margin: 0 0 10px;}.cke_about_container .cke_about_logo{height:81px;background-color:#fff;background-image:url('+
+b+");"+(CKEDITOR.env.hidpi?"background-size:163px 58px;":"")+'background-position:center; background-repeat:no-repeat;margin-bottom:10px;}.cke_about_container a{cursor:pointer !important;color:#00B2CE !important;text-decoration:underline !important;}</style><div class="cke_about_container"><div class="cke_about_logo"></div><p>CKEditor '+CKEDITOR.version+" (revision "+CKEDITOR.revision+')<br><a target="_blank" href="http://ckeditor.com/">http://ckeditor.com</a></p><p>'+a.help.replace("$1",'<a target="_blank" href="http://docs.ckeditor.com/user">'+
+a.userGuide+"</a>")+"</p><p>"+a.moreInfo+'<br><a target="_blank" href="http://ckeditor.com/about/license">http://ckeditor.com/about/license</a></p><p>'+a.copy.replace("$1",'<a target="_blank" href="http://cksource.com/">CKSource</a> - Frederico Knabben')+"</p></div>"}]}],buttons:[CKEDITOR.dialog.cancelButton]}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.png b/js/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.png
new file mode 100644
index 0000000..10cc736
--- /dev/null
+++ b/js/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.png
Binary files differ
diff --git a/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png b/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png
new file mode 100644
index 0000000..f186eb8
--- /dev/null
+++ b/js/ckeditor/plugins/about/dialogs/logo_ckeditor.png
Binary files differ
diff --git a/js/ckeditor/plugins/clipboard/dialogs/paste.js b/js/ckeditor/plugins/clipboard/dialogs/paste.js
new file mode 100644
index 0000000..441502b
--- /dev/null
+++ b/js/ckeditor/plugins/clipboard/dialogs/paste.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("paste",function(c){function h(a){var b=new CKEDITOR.dom.document(a.document),f=b.getBody(),d=b.getById("cke_actscrpt");d&&d.remove();f.setAttribute("contenteditable",!0);if(CKEDITOR.env.ie&&8>CKEDITOR.env.version)b.getWindow().on("blur",function(){b.$.selection.empty()});b.on("keydown",function(a){var a=a.data,b;switch(a.getKeystroke()){case 27:this.hide();b=1;break;case 9:case CKEDITOR.SHIFT+9:this.changeFocus(1),b=1}b&&a.preventDefault()},this);c.fire("ariaWidget",new CKEDITOR.dom.element(a.frameElement));
+b.getWindow().getFrame().removeCustomData("pendingFocus")&&f.focus()}var e=c.lang.clipboard;c.on("pasteDialogCommit",function(a){a.data&&c.fire("paste",{type:"auto",dataValue:a.data})},null,null,1E3);return{title:e.title,minWidth:CKEDITOR.env.ie&&CKEDITOR.env.quirks?370:350,minHeight:CKEDITOR.env.quirks?250:245,onShow:function(){this.parts.dialog.$.offsetHeight;this.setupContent();this.parts.title.setHtml(this.customTitle||e.title);this.customTitle=null},onLoad:function(){(CKEDITOR.env.ie7Compat||
+CKEDITOR.env.ie6Compat)&&"rtl"==c.lang.dir&&this.parts.contents.setStyle("overflow","hidden")},onOk:function(){this.commitContent()},contents:[{id:"general",label:c.lang.common.generalTab,elements:[{type:"html",id:"securityMsg",html:'<div style="white-space:normal;width:340px">'+e.securityMsg+"</div>"},{type:"html",id:"pasteMsg",html:'<div style="white-space:normal;width:340px">'+e.pasteMsg+"</div>"},{type:"html",id:"editing_area",style:"width:100%;height:100%",html:"",focus:function(){var a=this.getInputElement(),
+b=a.getFrameDocument().getBody();!b||b.isReadOnly()?a.setCustomData("pendingFocus",1):b.focus()},setup:function(){var a=this.getDialog(),b='<html dir="'+c.config.contentsLangDirection+'" lang="'+(c.config.contentsLanguage||c.langCode)+'"><head><style>body{margin:3px;height:95%}</style></head><body><script id="cke_actscrpt" type="text/javascript">window.parent.CKEDITOR.tools.callFunction('+CKEDITOR.tools.addFunction(h,a)+",this);<\/script></body></html>",f=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie?
+"javascript:void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+'})())"':"",d=CKEDITOR.dom.element.createFromHtml('<iframe class="cke_pasteframe" frameborder="0" allowTransparency="true" src="'+f+'" role="region" aria-label="'+e.pasteArea+'" aria-describedby="'+a.getContentElement("general","pasteMsg").domId+'" aria-multiple="true"></iframe>');d.on("load",function(a){a.removeListener();a=d.getFrameDocument();a.write(b);c.focusManager.add(a.getBody());
+CKEDITOR.env.air&&h.call(this,a.getWindow().$)},a);d.setCustomData("dialog",a);a=this.getElement();a.setHtml("");a.append(d);if(CKEDITOR.env.ie){var g=CKEDITOR.dom.element.createFromHtml('<span tabindex="-1" style="position:absolute" role="presentation"></span>');g.on("focus",function(){setTimeout(function(){d.$.contentWindow.focus()})});a.append(g);this.focus=function(){g.focus();this.fire("focus")}}this.getInputElement=function(){return d};CKEDITOR.env.ie&&(a.setStyle("display","block"),a.setStyle("height",
+d.$.offsetHeight+2+"px"))},commit:function(){var a=this.getDialog().getParentEditor(),b=this.getInputElement().getFrameDocument().getBody(),c=b.getBogus(),d;c&&c.remove();d=b.getHtml();setTimeout(function(){a.fire("pasteDialogCommit",d)},0)}}]}]}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/dialog/dialogDefinition.js b/js/ckeditor/plugins/dialog/dialogDefinition.js
new file mode 100644
index 0000000..525bb7a
--- /dev/null
+++ b/js/ckeditor/plugins/dialog/dialogDefinition.js
@@ -0,0 +1,4 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
diff --git a/js/ckeditor/plugins/fakeobjects/images/spacer.gif b/js/ckeditor/plugins/fakeobjects/images/spacer.gif
new file mode 100644
index 0000000..5bfd67a
--- /dev/null
+++ b/js/ckeditor/plugins/fakeobjects/images/spacer.gif
Binary files differ
diff --git a/js/ckeditor/plugins/icons.png b/js/ckeditor/plugins/icons.png
new file mode 100644
index 0000000..ee02970
--- /dev/null
+++ b/js/ckeditor/plugins/icons.png
Binary files differ
diff --git a/js/ckeditor/plugins/icons_hidpi.png b/js/ckeditor/plugins/icons_hidpi.png
new file mode 100644
index 0000000..0466c2b
--- /dev/null
+++ b/js/ckeditor/plugins/icons_hidpi.png
Binary files differ
diff --git a/js/ckeditor/plugins/image/dialogs/image.js b/js/ckeditor/plugins/image/dialogs/image.js
new file mode 100644
index 0000000..e9d85eb
--- /dev/null
+++ b/js/ckeditor/plugins/image/dialogs/image.js
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){var r=function(c,j){function r(){var a=arguments,b=this.getContentElement("advanced","txtdlgGenStyle");b&&b.commit.apply(b,a);this.foreach(function(b){b.commit&&"txtdlgGenStyle"!=b.id&&b.commit.apply(b,a)})}function i(a){if(!s){s=1;var b=this.getDialog(),d=b.imageElement;if(d){this.commit(f,d);for(var a=[].concat(a),e=a.length,c,g=0;g<e;g++)(c=b.getContentElement.apply(b,a[g].split(":")))&&c.setup(f,d)}s=0}}var f=1,k=/^\s*(\d+)((px)|\%)?\s*$/i,v=/(^\s*(\d+)((px)|\%)?\s*$)|^$/i,o=/^\d+px$/,
+w=function(){var a=this.getValue(),b=this.getDialog(),d=a.match(k);d&&("%"==d[2]&&l(b,!1),a=d[1]);b.lockRatio&&(d=b.originalElement,"true"==d.getCustomData("isReady")&&("txtHeight"==this.id?(a&&"0"!=a&&(a=Math.round(d.$.width*(a/d.$.height))),isNaN(a)||b.setValueOf("info","txtWidth",a)):(a&&"0"!=a&&(a=Math.round(d.$.height*(a/d.$.width))),isNaN(a)||b.setValueOf("info","txtHeight",a))));g(b)},g=function(a){if(!a.originalElement||!a.preview)return 1;a.commitContent(4,a.preview);return 0},s,l=function(a,
+b){if(!a.getContentElement("info","ratioLock"))return null;var d=a.originalElement;if(!d)return null;if("check"==b){if(!a.userlockRatio&&"true"==d.getCustomData("isReady")){var e=a.getValueOf("info","txtWidth"),c=a.getValueOf("info","txtHeight"),d=1E3*d.$.width/d.$.height,f=1E3*e/c;a.lockRatio=!1;!e&&!c?a.lockRatio=!0:!isNaN(d)&&!isNaN(f)&&Math.round(d)==Math.round(f)&&(a.lockRatio=!0)}}else void 0!==b?a.lockRatio=b:(a.userlockRatio=1,a.lockRatio=!a.lockRatio);e=CKEDITOR.document.getById(p);a.lockRatio?
+e.removeClass("cke_btn_unlocked"):e.addClass("cke_btn_unlocked");e.setAttribute("aria-checked",a.lockRatio);CKEDITOR.env.hc&&e.getChild(0).setHtml(a.lockRatio?CKEDITOR.env.ie?"â– ":"â–£":CKEDITOR.env.ie?"â–¡":"â–¢");return a.lockRatio},x=function(a){var b=a.originalElement;if("true"==b.getCustomData("isReady")){var d=a.getContentElement("info","txtWidth"),e=a.getContentElement("info","txtHeight");d&&d.setValue(b.$.width);e&&e.setValue(b.$.height)}g(a)},y=function(a,b){function d(a,b){var d=a.match(k);return d?
+("%"==d[2]&&(d[1]+="%",l(e,!1)),d[1]):b}if(a==f){var e=this.getDialog(),c="",g="txtWidth"==this.id?"width":"height",h=b.getAttribute(g);h&&(c=d(h,c));c=d(b.getStyle(g),c);this.setValue(c)}},t,q=function(){var a=this.originalElement,b=CKEDITOR.document.getById(m);a.setCustomData("isReady","true");a.removeListener("load",q);a.removeListener("error",h);a.removeListener("abort",h);b&&b.setStyle("display","none");this.dontResetSize||x(this);this.firstLoad&&CKEDITOR.tools.setTimeout(function(){l(this,"check")},
+0,this);this.dontResetSize=this.firstLoad=!1;g(this)},h=function(){var a=this.originalElement,b=CKEDITOR.document.getById(m);a.removeListener("load",q);a.removeListener("error",h);a.removeListener("abort",h);a=CKEDITOR.getUrl(CKEDITOR.plugins.get("image").path+"images/noimage.png");this.preview&&this.preview.setAttribute("src",a);b&&b.setStyle("display","none");l(this,!1)},n=function(a){return CKEDITOR.tools.getNextId()+"_"+a},p=n("btnLockSizes"),u=n("btnResetSize"),m=n("ImagePreviewLoader"),A=n("previewLink"),
+z=n("previewImage");return{title:c.lang.image["image"==j?"title":"titleButton"],minWidth:420,minHeight:360,onShow:function(){this.linkEditMode=this.imageEditMode=this.linkElement=this.imageElement=!1;this.lockRatio=!0;this.userlockRatio=0;this.dontResetSize=!1;this.firstLoad=!0;this.addLink=!1;var a=this.getParentEditor(),b=a.getSelection(),d=(b=b&&b.getSelectedElement())&&a.elementPath(b).contains("a",1),c=CKEDITOR.document.getById(m);c&&c.setStyle("display","none");t=new CKEDITOR.dom.element("img",
+a.document);this.preview=CKEDITOR.document.getById(z);this.originalElement=a.document.createElement("img");this.originalElement.setAttribute("alt","");this.originalElement.setCustomData("isReady","false");if(d){this.linkElement=d;this.linkEditMode=!0;c=d.getChildren();if(1==c.count()){var g=c.getItem(0).getName();if("img"==g||"input"==g)this.imageElement=c.getItem(0),"img"==this.imageElement.getName()?this.imageEditMode="img":"input"==this.imageElement.getName()&&(this.imageEditMode="input")}"image"==
+j&&this.setupContent(2,d)}if(this.customImageElement)this.imageEditMode="img",this.imageElement=this.customImageElement,delete this.customImageElement;else if(b&&"img"==b.getName()&&!b.data("cke-realelement")||b&&"input"==b.getName()&&"image"==b.getAttribute("type"))this.imageEditMode=b.getName(),this.imageElement=b;this.imageEditMode?(this.cleanImageElement=this.imageElement,this.imageElement=this.cleanImageElement.clone(!0,!0),this.setupContent(f,this.imageElement)):this.imageElement=a.document.createElement("img");
+l(this,!0);CKEDITOR.tools.trim(this.getValueOf("info","txtUrl"))||(this.preview.removeAttribute("src"),this.preview.setStyle("display","none"))},onOk:function(){if(this.imageEditMode){var a=this.imageEditMode;"image"==j&&"input"==a&&confirm(c.lang.image.button2Img)?(this.imageElement=c.document.createElement("img"),this.imageElement.setAttribute("alt",""),c.insertElement(this.imageElement)):"image"!=j&&"img"==a&&confirm(c.lang.image.img2Button)?(this.imageElement=c.document.createElement("input"),
+this.imageElement.setAttributes({type:"image",alt:""}),c.insertElement(this.imageElement)):(this.imageElement=this.cleanImageElement,delete this.cleanImageElement)}else"image"==j?this.imageElement=c.document.createElement("img"):(this.imageElement=c.document.createElement("input"),this.imageElement.setAttribute("type","image")),this.imageElement.setAttribute("alt","");this.linkEditMode||(this.linkElement=c.document.createElement("a"));this.commitContent(f,this.imageElement);this.commitContent(2,this.linkElement);
+this.imageElement.getAttribute("style")||this.imageElement.removeAttribute("style");this.imageEditMode?!this.linkEditMode&&this.addLink?(c.insertElement(this.linkElement),this.imageElement.appendTo(this.linkElement)):this.linkEditMode&&!this.addLink&&(c.getSelection().selectElement(this.linkElement),c.insertElement(this.imageElement)):this.addLink?this.linkEditMode?c.insertElement(this.imageElement):(c.insertElement(this.linkElement),this.linkElement.append(this.imageElement,!1)):c.insertElement(this.imageElement)},
+onLoad:function(){"image"!=j&&this.hidePage("Link");var a=this._.element.getDocument();this.getContentElement("info","ratioLock")&&(this.addFocusable(a.getById(u),5),this.addFocusable(a.getById(p),5));this.commitContent=r},onHide:function(){this.preview&&this.commitContent(8,this.preview);this.originalElement&&(this.originalElement.removeListener("load",q),this.originalElement.removeListener("error",h),this.originalElement.removeListener("abort",h),this.originalElement.remove(),this.originalElement=
+!1);delete this.imageElement},contents:[{id:"info",label:c.lang.image.infoTab,accessKey:"I",elements:[{type:"vbox",padding:0,children:[{type:"hbox",widths:["280px","110px"],align:"right",children:[{id:"txtUrl",type:"text",label:c.lang.common.url,required:!0,onChange:function(){var a=this.getDialog(),b=this.getValue();if(0<b.length){var a=this.getDialog(),d=a.originalElement;a.preview&&a.preview.removeStyle("display");d.setCustomData("isReady","false");var c=CKEDITOR.document.getById(m);c&&c.setStyle("display",
+"");d.on("load",q,a);d.on("error",h,a);d.on("abort",h,a);d.setAttribute("src",b);a.preview&&(t.setAttribute("src",b),a.preview.setAttribute("src",t.$.src),g(a))}else a.preview&&(a.preview.removeAttribute("src"),a.preview.setStyle("display","none"))},setup:function(a,b){if(a==f){var d=b.data("cke-saved-src")||b.getAttribute("src");this.getDialog().dontResetSize=!0;this.setValue(d);this.setInitValue()}},commit:function(a,b){a==f&&(this.getValue()||this.isChanged())?(b.data("cke-saved-src",this.getValue()),
+b.setAttribute("src",this.getValue())):8==a&&(b.setAttribute("src",""),b.removeAttribute("src"))},validate:CKEDITOR.dialog.validate.notEmpty(c.lang.image.urlMissing)},{type:"button",id:"browse",style:"display:inline-block;margin-top:14px;",align:"center",label:c.lang.common.browseServer,hidden:!0,filebrowser:"info:txtUrl"}]}]},{id:"txtAlt",type:"text",label:c.lang.image.alt,accessKey:"T","default":"",onChange:function(){g(this.getDialog())},setup:function(a,b){a==f&&this.setValue(b.getAttribute("alt"))},
+commit:function(a,b){a==f?(this.getValue()||this.isChanged())&&b.setAttribute("alt",this.getValue()):4==a?b.setAttribute("alt",this.getValue()):8==a&&b.removeAttribute("alt")}},{type:"hbox",children:[{id:"basic",type:"vbox",children:[{type:"hbox",requiredContent:"img{width,height}",widths:["50%","50%"],children:[{type:"vbox",padding:1,children:[{type:"text",width:"45px",id:"txtWidth",label:c.lang.common.width,onKeyUp:w,onChange:function(){i.call(this,"advanced:txtdlgGenStyle")},validate:function(){var a=
+this.getValue().match(v);(a=!!(a&&0!==parseInt(a[1],10)))||alert(c.lang.common.invalidWidth);return a},setup:y,commit:function(a,b,d){var e=this.getValue();a==f?(e&&c.activeFilter.check("img{width,height}")?b.setStyle("width",CKEDITOR.tools.cssLength(e)):b.removeStyle("width"),!d&&b.removeAttribute("width")):4==a?e.match(k)?b.setStyle("width",CKEDITOR.tools.cssLength(e)):(a=this.getDialog().originalElement,"true"==a.getCustomData("isReady")&&b.setStyle("width",a.$.width+"px")):8==a&&(b.removeAttribute("width"),
+b.removeStyle("width"))}},{type:"text",id:"txtHeight",width:"45px",label:c.lang.common.height,onKeyUp:w,onChange:function(){i.call(this,"advanced:txtdlgGenStyle")},validate:function(){var a=this.getValue().match(v);(a=!!(a&&0!==parseInt(a[1],10)))||alert(c.lang.common.invalidHeight);return a},setup:y,commit:function(a,b,d){var e=this.getValue();a==f?(e&&c.activeFilter.check("img{width,height}")?b.setStyle("height",CKEDITOR.tools.cssLength(e)):b.removeStyle("height"),!d&&b.removeAttribute("height")):
+4==a?e.match(k)?b.setStyle("height",CKEDITOR.tools.cssLength(e)):(a=this.getDialog().originalElement,"true"==a.getCustomData("isReady")&&b.setStyle("height",a.$.height+"px")):8==a&&(b.removeAttribute("height"),b.removeStyle("height"))}}]},{id:"ratioLock",type:"html",style:"margin-top:30px;width:40px;height:40px;",onLoad:function(){var a=CKEDITOR.document.getById(u),b=CKEDITOR.document.getById(p);a&&(a.on("click",function(a){x(this);a.data&&a.data.preventDefault()},this.getDialog()),a.on("mouseover",
+function(){this.addClass("cke_btn_over")},a),a.on("mouseout",function(){this.removeClass("cke_btn_over")},a));b&&(b.on("click",function(a){l(this);var b=this.originalElement,c=this.getValueOf("info","txtWidth");if(b.getCustomData("isReady")=="true"&&c){b=b.$.height/b.$.width*c;if(!isNaN(b)){this.setValueOf("info","txtHeight",Math.round(b));g(this)}}a.data&&a.data.preventDefault()},this.getDialog()),b.on("mouseover",function(){this.addClass("cke_btn_over")},b),b.on("mouseout",function(){this.removeClass("cke_btn_over")},
+b))},html:'<div><a href="javascript:void(0)" tabindex="-1" title="'+c.lang.image.lockRatio+'" class="cke_btn_locked" id="'+p+'" role="checkbox"><span class="cke_icon"></span><span class="cke_label">'+c.lang.image.lockRatio+'</span></a><a href="javascript:void(0)" tabindex="-1" title="'+c.lang.image.resetSize+'" class="cke_btn_reset" id="'+u+'" role="button"><span class="cke_label">'+c.lang.image.resetSize+"</span></a></div>"}]},{type:"vbox",padding:1,children:[{type:"text",id:"txtBorder",requiredContent:"img{border-width}",
+width:"60px",label:c.lang.image.border,"default":"",onKeyUp:function(){g(this.getDialog())},onChange:function(){i.call(this,"advanced:txtdlgGenStyle")},validate:CKEDITOR.dialog.validate.integer(c.lang.image.validateBorder),setup:function(a,b){if(a==f){var d;d=(d=(d=b.getStyle("border-width"))&&d.match(/^(\d+px)(?: \1 \1 \1)?$/))&&parseInt(d[1],10);isNaN(parseInt(d,10))&&(d=b.getAttribute("border"));this.setValue(d)}},commit:function(a,b,d){var c=parseInt(this.getValue(),10);a==f||4==a?(isNaN(c)?!c&&
+this.isChanged()&&b.removeStyle("border"):(b.setStyle("border-width",CKEDITOR.tools.cssLength(c)),b.setStyle("border-style","solid")),!d&&a==f&&b.removeAttribute("border")):8==a&&(b.removeAttribute("border"),b.removeStyle("border-width"),b.removeStyle("border-style"),b.removeStyle("border-color"))}},{type:"text",id:"txtHSpace",requiredContent:"img{margin-left,margin-right}",width:"60px",label:c.lang.image.hSpace,"default":"",onKeyUp:function(){g(this.getDialog())},onChange:function(){i.call(this,
+"advanced:txtdlgGenStyle")},validate:CKEDITOR.dialog.validate.integer(c.lang.image.validateHSpace),setup:function(a,b){if(a==f){var d,c;d=b.getStyle("margin-left");c=b.getStyle("margin-right");d=d&&d.match(o);c=c&&c.match(o);d=parseInt(d,10);c=parseInt(c,10);d=d==c&&d;isNaN(parseInt(d,10))&&(d=b.getAttribute("hspace"));this.setValue(d)}},commit:function(a,b,d){var c=parseInt(this.getValue(),10);a==f||4==a?(isNaN(c)?!c&&this.isChanged()&&(b.removeStyle("margin-left"),b.removeStyle("margin-right")):
+(b.setStyle("margin-left",CKEDITOR.tools.cssLength(c)),b.setStyle("margin-right",CKEDITOR.tools.cssLength(c))),!d&&a==f&&b.removeAttribute("hspace")):8==a&&(b.removeAttribute("hspace"),b.removeStyle("margin-left"),b.removeStyle("margin-right"))}},{type:"text",id:"txtVSpace",requiredContent:"img{margin-top,margin-bottom}",width:"60px",label:c.lang.image.vSpace,"default":"",onKeyUp:function(){g(this.getDialog())},onChange:function(){i.call(this,"advanced:txtdlgGenStyle")},validate:CKEDITOR.dialog.validate.integer(c.lang.image.validateVSpace),
+setup:function(a,b){if(a==f){var c,e;c=b.getStyle("margin-top");e=b.getStyle("margin-bottom");c=c&&c.match(o);e=e&&e.match(o);c=parseInt(c,10);e=parseInt(e,10);c=c==e&&c;isNaN(parseInt(c,10))&&(c=b.getAttribute("vspace"));this.setValue(c)}},commit:function(a,b,c){var e=parseInt(this.getValue(),10);a==f||4==a?(isNaN(e)?!e&&this.isChanged()&&(b.removeStyle("margin-top"),b.removeStyle("margin-bottom")):(b.setStyle("margin-top",CKEDITOR.tools.cssLength(e)),b.setStyle("margin-bottom",CKEDITOR.tools.cssLength(e))),
+!c&&a==f&&b.removeAttribute("vspace")):8==a&&(b.removeAttribute("vspace"),b.removeStyle("margin-top"),b.removeStyle("margin-bottom"))}},{id:"cmbAlign",requiredContent:"img{float}",type:"select",widths:["35%","65%"],style:"width:90px",label:c.lang.common.align,"default":"",items:[[c.lang.common.notSet,""],[c.lang.common.alignLeft,"left"],[c.lang.common.alignRight,"right"]],onChange:function(){g(this.getDialog());i.call(this,"advanced:txtdlgGenStyle")},setup:function(a,b){if(a==f){var c=b.getStyle("float");
+switch(c){case "inherit":case "none":c=""}!c&&(c=(b.getAttribute("align")||"").toLowerCase());this.setValue(c)}},commit:function(a,b,c){var e=this.getValue();if(a==f||4==a){if(e?b.setStyle("float",e):b.removeStyle("float"),!c&&a==f)switch(e=(b.getAttribute("align")||"").toLowerCase(),e){case "left":case "right":b.removeAttribute("align")}}else 8==a&&b.removeStyle("float")}}]}]},{type:"vbox",height:"250px",children:[{type:"html",id:"htmlPreview",style:"width:95%;",html:"<div>"+CKEDITOR.tools.htmlEncode(c.lang.common.preview)+
+'<br><div id="'+m+'" class="ImagePreviewLoader" style="display:none"><div class="loading">&nbsp;</div></div><div class="ImagePreviewBox"><table><tr><td><a href="javascript:void(0)" target="_blank" onclick="return false;" id="'+A+'"><img id="'+z+'" alt="" /></a>'+(c.config.image_previewText||"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas feugiat consequat diam. Maecenas metus. Vivamus diam purus, cursus a, commodo non, facilisis vitae, nulla. Aenean dictum lacinia tortor. Nunc iaculis, nibh non iaculis aliquam, orci felis euismod neque, sed ornare massa mauris sed velit. Nulla pretium mi et risus. Fusce mi pede, tempor id, cursus ac, ullamcorper nec, enim. Sed tortor. Curabitur molestie. Duis velit augue, condimentum at, ultrices a, luctus ut, orci. Donec pellentesque egestas eros. Integer cursus, augue in cursus faucibus, eros pede bibendum sem, in tempus tellus justo quis ligula. Etiam eget tortor. Vestibulum rutrum, est ut placerat elementum, lectus nisl aliquam velit, tempor aliquam eros nunc nonummy metus. In eros metus, gravida a, gravida sed, lobortis id, turpis. Ut ultrices, ipsum at venenatis fringilla, sem nulla lacinia tellus, eget aliquet turpis mauris non enim. Nam turpis. Suspendisse lacinia. Curabitur ac tortor ut ipsum egestas elementum. Nunc imperdiet gravida mauris.")+
+"</td></tr></table></div></div>"}]}]}]},{id:"Link",requiredContent:"a[href]",label:c.lang.image.linkTab,padding:0,elements:[{id:"txtUrl",type:"text",label:c.lang.common.url,style:"width: 100%","default":"",setup:function(a,b){if(2==a){var c=b.data("cke-saved-href");c||(c=b.getAttribute("href"));this.setValue(c)}},commit:function(a,b){if(2==a&&(this.getValue()||this.isChanged())){var d=this.getValue();b.data("cke-saved-href",d);b.setAttribute("href",d);if(this.getValue()||!c.config.image_removeLinkByEmptyURL)this.getDialog().addLink=
+!0}}},{type:"button",id:"browse",filebrowser:{action:"Browse",target:"Link:txtUrl",url:c.config.filebrowserImageBrowseLinkUrl},style:"float:right",hidden:!0,label:c.lang.common.browseServer},{id:"cmbTarget",type:"select",requiredContent:"a[target]",label:c.lang.common.target,"default":"",items:[[c.lang.common.notSet,""],[c.lang.common.targetNew,"_blank"],[c.lang.common.targetTop,"_top"],[c.lang.common.targetSelf,"_self"],[c.lang.common.targetParent,"_parent"]],setup:function(a,b){2==a&&this.setValue(b.getAttribute("target")||
+"")},commit:function(a,b){2==a&&(this.getValue()||this.isChanged())&&b.setAttribute("target",this.getValue())}}]},{id:"Upload",hidden:!0,filebrowser:"uploadButton",label:c.lang.image.upload,elements:[{type:"file",id:"upload",label:c.lang.image.btnUpload,style:"height:40px",size:38},{type:"fileButton",id:"uploadButton",filebrowser:"info:txtUrl",label:c.lang.image.btnUpload,"for":["Upload","upload"]}]},{id:"advanced",label:c.lang.common.advancedTab,elements:[{type:"hbox",widths:["50%","25%","25%"],
+children:[{type:"text",id:"linkId",requiredContent:"img[id]",label:c.lang.common.id,setup:function(a,b){a==f&&this.setValue(b.getAttribute("id"))},commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("id",this.getValue())}},{id:"cmbLangDir",type:"select",requiredContent:"img[dir]",style:"width : 100px;",label:c.lang.common.langDir,"default":"",items:[[c.lang.common.notSet,""],[c.lang.common.langDirLtr,"ltr"],[c.lang.common.langDirRtl,"rtl"]],setup:function(a,b){a==f&&this.setValue(b.getAttribute("dir"))},
+commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("dir",this.getValue())}},{type:"text",id:"txtLangCode",requiredContent:"img[lang]",label:c.lang.common.langCode,"default":"",setup:function(a,b){a==f&&this.setValue(b.getAttribute("lang"))},commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("lang",this.getValue())}}]},{type:"text",id:"txtGenLongDescr",requiredContent:"img[longdesc]",label:c.lang.common.longDescr,setup:function(a,b){a==f&&this.setValue(b.getAttribute("longDesc"))},
+commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("longDesc",this.getValue())}},{type:"hbox",widths:["50%","50%"],children:[{type:"text",id:"txtGenClass",requiredContent:"img(cke-xyz)",label:c.lang.common.cssClass,"default":"",setup:function(a,b){a==f&&this.setValue(b.getAttribute("class"))},commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("class",this.getValue())}},{type:"text",id:"txtGenTitle",requiredContent:"img[title]",label:c.lang.common.advisoryTitle,
+"default":"",onChange:function(){g(this.getDialog())},setup:function(a,b){a==f&&this.setValue(b.getAttribute("title"))},commit:function(a,b){a==f?(this.getValue()||this.isChanged())&&b.setAttribute("title",this.getValue()):4==a?b.setAttribute("title",this.getValue()):8==a&&b.removeAttribute("title")}}]},{type:"text",id:"txtdlgGenStyle",requiredContent:"img{cke-xyz}",label:c.lang.common.cssStyle,validate:CKEDITOR.dialog.validate.inlineStyle(c.lang.common.invalidInlineStyle),"default":"",setup:function(a,
+b){if(a==f){var c=b.getAttribute("style");!c&&b.$.style.cssText&&(c=b.$.style.cssText);this.setValue(c);var e=b.$.style.height,c=b.$.style.width,e=(e?e:"").match(k),c=(c?c:"").match(k);this.attributesInStyle={height:!!e,width:!!c}}},onChange:function(){i.call(this,"info:cmbFloat info:cmbAlign info:txtVSpace info:txtHSpace info:txtBorder info:txtWidth info:txtHeight".split(" "));g(this)},commit:function(a,b){a==f&&(this.getValue()||this.isChanged())&&b.setAttribute("style",this.getValue())}}]}]}};
+CKEDITOR.dialog.add("image",function(c){return r(c,"image")});CKEDITOR.dialog.add("imagebutton",function(c){return r(c,"imagebutton")})})(); \ No newline at end of file
diff --git a/js/ckeditor/plugins/image/images/noimage.png b/js/ckeditor/plugins/image/images/noimage.png
new file mode 100644
index 0000000..1598113
--- /dev/null
+++ b/js/ckeditor/plugins/image/images/noimage.png
Binary files differ
diff --git a/js/ckeditor/plugins/link/dialogs/anchor.js b/js/ckeditor/plugins/link/dialogs/anchor.js
new file mode 100644
index 0000000..f5fe2ea
--- /dev/null
+++ b/js/ckeditor/plugins/link/dialogs/anchor.js
@@ -0,0 +1,7 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("anchor",function(c){function d(a,b){return a.createFakeElement(a.document.createElement("a",{attributes:b}),"cke_anchor","anchor")}return{title:c.lang.link.anchor.title,minWidth:300,minHeight:60,onOk:function(){var a=CKEDITOR.tools.trim(this.getValueOf("info","txtName")),a={id:a,name:a,"data-cke-saved-name":a};if(this._.selectedElement)this._.selectedElement.data("cke-realelement")?(a=d(c,a),a.replace(this._.selectedElement),CKEDITOR.env.ie&&c.getSelection().selectElement(a)):
+this._.selectedElement.setAttributes(a);else{var b=c.getSelection(),b=b&&b.getRanges()[0];b.collapsed?(a=d(c,a),b.insertNode(a)):(CKEDITOR.env.ie&&9>CKEDITOR.env.version&&(a["class"]="cke_anchor"),a=new CKEDITOR.style({element:"a",attributes:a}),a.type=CKEDITOR.STYLE_INLINE,c.applyStyle(a))}},onHide:function(){delete this._.selectedElement},onShow:function(){var a=c.getSelection(),b=a.getSelectedElement(),d=b&&b.data("cke-realelement"),e=d?CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,b):CKEDITOR.plugins.link.getSelectedLink(c);
+e&&(this._.selectedElement=e,this.setValueOf("info","txtName",e.data("cke-saved-name")||""),!d&&a.selectElement(e),b&&(this._.selectedElement=b));this.getContentElement("info","txtName").focus()},contents:[{id:"info",label:c.lang.link.anchor.title,accessKey:"I",elements:[{type:"text",id:"txtName",label:c.lang.link.anchor.name,required:!0,validate:function(){return!this.getValue()?(alert(c.lang.link.anchor.errorName),!1):!0}}]}]}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/link/dialogs/link.js b/js/ckeditor/plugins/link/dialogs/link.js
new file mode 100644
index 0000000..3ee3209
--- /dev/null
+++ b/js/ckeditor/plugins/link/dialogs/link.js
@@ -0,0 +1,26 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){CKEDITOR.dialog.add("link",function(g){var l=CKEDITOR.plugins.link,m=function(){var a=this.getDialog(),b=a.getContentElement("target","popupFeatures"),a=a.getContentElement("target","linkTargetName"),k=this.getValue();if(b&&a)switch(b=b.getElement(),b.hide(),a.setValue(""),k){case "frame":a.setLabel(g.lang.link.targetFrameName);a.getElement().show();break;case "popup":b.show();a.setLabel(g.lang.link.targetPopupName);a.getElement().show();break;default:a.setValue(k),a.getElement().hide()}},
+f=function(a){a.target&&this.setValue(a.target[this.id]||"")},h=function(a){a.advanced&&this.setValue(a.advanced[this.id]||"")},i=function(a){a.target||(a.target={});a.target[this.id]=this.getValue()||""},j=function(a){a.advanced||(a.advanced={});a.advanced[this.id]=this.getValue()||""},c=g.lang.common,b=g.lang.link,d;return{title:b.title,minWidth:350,minHeight:230,contents:[{id:"info",label:b.info,title:b.info,elements:[{id:"linkType",type:"select",label:b.type,"default":"url",items:[[b.toUrl,"url"],
+[b.toAnchor,"anchor"],[b.toEmail,"email"]],onChange:function(){var a=this.getDialog(),b=["urlOptions","anchorOptions","emailOptions"],k=this.getValue(),e=a.definition.getContents("upload"),e=e&&e.hidden;"url"==k?(g.config.linkShowTargetTab&&a.showPage("target"),e||a.showPage("upload")):(a.hidePage("target"),e||a.hidePage("upload"));for(e=0;e<b.length;e++){var c=a.getContentElement("info",b[e]);c&&(c=c.getElement().getParent().getParent(),b[e]==k+"Options"?c.show():c.hide())}a.layout()},setup:function(a){this.setValue(a.type||
+"url")},commit:function(a){a.type=this.getValue()}},{type:"vbox",id:"urlOptions",children:[{type:"hbox",widths:["25%","75%"],children:[{id:"protocol",type:"select",label:c.protocol,"default":"http://",items:[["http://‎","http://"],["https://‎","https://"],["ftp://‎","ftp://"],["news://‎","news://"],[b.other,""]],setup:function(a){a.url&&this.setValue(a.url.protocol||"")},commit:function(a){a.url||(a.url={});a.url.protocol=this.getValue()}},{type:"text",id:"url",label:c.url,required:!0,onLoad:function(){this.allowOnChange=
+!0},onKeyUp:function(){this.allowOnChange=!1;var a=this.getDialog().getContentElement("info","protocol"),b=this.getValue(),k=/^((javascript:)|[#\/\.\?])/i,c=/^(http|https|ftp|news):\/\/(?=.)/i.exec(b);c?(this.setValue(b.substr(c[0].length)),a.setValue(c[0].toLowerCase())):k.test(b)&&a.setValue("");this.allowOnChange=!0},onChange:function(){if(this.allowOnChange)this.onKeyUp()},validate:function(){var a=this.getDialog();return a.getContentElement("info","linkType")&&"url"!=a.getValueOf("info","linkType")?
+!0:!g.config.linkJavaScriptLinksAllowed&&/javascript\:/.test(this.getValue())?(alert(c.invalidValue),!1):this.getDialog().fakeObj?!0:CKEDITOR.dialog.validate.notEmpty(b.noUrl).apply(this)},setup:function(a){this.allowOnChange=!1;a.url&&this.setValue(a.url.url);this.allowOnChange=!0},commit:function(a){this.onChange();a.url||(a.url={});a.url.url=this.getValue();this.allowOnChange=!1}}],setup:function(){this.getDialog().getContentElement("info","linkType")||this.getElement().show()}},{type:"button",
+id:"browse",hidden:"true",filebrowser:"info:url",label:c.browseServer}]},{type:"vbox",id:"anchorOptions",width:260,align:"center",padding:0,children:[{type:"fieldset",id:"selectAnchorText",label:b.selectAnchor,setup:function(){d=l.getEditorAnchors(g);this.getElement()[d&&d.length?"show":"hide"]()},children:[{type:"hbox",id:"selectAnchor",children:[{type:"select",id:"anchorName","default":"",label:b.anchorName,style:"width: 100%;",items:[[""]],setup:function(a){this.clear();this.add("");if(d)for(var b=
+0;b<d.length;b++)d[b].name&&this.add(d[b].name);a.anchor&&this.setValue(a.anchor.name);(a=this.getDialog().getContentElement("info","linkType"))&&"email"==a.getValue()&&this.focus()},commit:function(a){a.anchor||(a.anchor={});a.anchor.name=this.getValue()}},{type:"select",id:"anchorId","default":"",label:b.anchorId,style:"width: 100%;",items:[[""]],setup:function(a){this.clear();this.add("");if(d)for(var b=0;b<d.length;b++)d[b].id&&this.add(d[b].id);a.anchor&&this.setValue(a.anchor.id)},commit:function(a){a.anchor||
+(a.anchor={});a.anchor.id=this.getValue()}}],setup:function(){this.getElement()[d&&d.length?"show":"hide"]()}}]},{type:"html",id:"noAnchors",style:"text-align: center;",html:'<div role="note" tabIndex="-1">'+CKEDITOR.tools.htmlEncode(b.noAnchors)+"</div>",focus:!0,setup:function(){this.getElement()[d&&d.length?"hide":"show"]()}}],setup:function(){this.getDialog().getContentElement("info","linkType")||this.getElement().hide()}},{type:"vbox",id:"emailOptions",padding:1,children:[{type:"text",id:"emailAddress",
+label:b.emailAddress,required:!0,validate:function(){var a=this.getDialog();return!a.getContentElement("info","linkType")||"email"!=a.getValueOf("info","linkType")?!0:CKEDITOR.dialog.validate.notEmpty(b.noEmail).apply(this)},setup:function(a){a.email&&this.setValue(a.email.address);(a=this.getDialog().getContentElement("info","linkType"))&&"email"==a.getValue()&&this.select()},commit:function(a){a.email||(a.email={});a.email.address=this.getValue()}},{type:"text",id:"emailSubject",label:b.emailSubject,
+setup:function(a){a.email&&this.setValue(a.email.subject)},commit:function(a){a.email||(a.email={});a.email.subject=this.getValue()}},{type:"textarea",id:"emailBody",label:b.emailBody,rows:3,"default":"",setup:function(a){a.email&&this.setValue(a.email.body)},commit:function(a){a.email||(a.email={});a.email.body=this.getValue()}}],setup:function(){this.getDialog().getContentElement("info","linkType")||this.getElement().hide()}}]},{id:"target",requiredContent:"a[target]",label:b.target,title:b.target,
+elements:[{type:"hbox",widths:["50%","50%"],children:[{type:"select",id:"linkTargetType",label:c.target,"default":"notSet",style:"width : 100%;",items:[[c.notSet,"notSet"],[b.targetFrame,"frame"],[b.targetPopup,"popup"],[c.targetNew,"_blank"],[c.targetTop,"_top"],[c.targetSelf,"_self"],[c.targetParent,"_parent"]],onChange:m,setup:function(a){a.target&&this.setValue(a.target.type||"notSet");m.call(this)},commit:function(a){a.target||(a.target={});a.target.type=this.getValue()}},{type:"text",id:"linkTargetName",
+label:b.targetFrameName,"default":"",setup:function(a){a.target&&this.setValue(a.target.name)},commit:function(a){a.target||(a.target={});a.target.name=this.getValue().replace(/\W/gi,"")}}]},{type:"vbox",width:"100%",align:"center",padding:2,id:"popupFeatures",children:[{type:"fieldset",label:b.popupFeatures,children:[{type:"hbox",children:[{type:"checkbox",id:"resizable",label:b.popupResizable,setup:f,commit:i},{type:"checkbox",id:"status",label:b.popupStatusBar,setup:f,commit:i}]},{type:"hbox",
+children:[{type:"checkbox",id:"location",label:b.popupLocationBar,setup:f,commit:i},{type:"checkbox",id:"toolbar",label:b.popupToolbar,setup:f,commit:i}]},{type:"hbox",children:[{type:"checkbox",id:"menubar",label:b.popupMenuBar,setup:f,commit:i},{type:"checkbox",id:"fullscreen",label:b.popupFullScreen,setup:f,commit:i}]},{type:"hbox",children:[{type:"checkbox",id:"scrollbars",label:b.popupScrollBars,setup:f,commit:i},{type:"checkbox",id:"dependent",label:b.popupDependent,setup:f,commit:i}]},{type:"hbox",
+children:[{type:"text",widths:["50%","50%"],labelLayout:"horizontal",label:c.width,id:"width",setup:f,commit:i},{type:"text",labelLayout:"horizontal",widths:["50%","50%"],label:b.popupLeft,id:"left",setup:f,commit:i}]},{type:"hbox",children:[{type:"text",labelLayout:"horizontal",widths:["50%","50%"],label:c.height,id:"height",setup:f,commit:i},{type:"text",labelLayout:"horizontal",label:b.popupTop,widths:["50%","50%"],id:"top",setup:f,commit:i}]}]}]}]},{id:"upload",label:b.upload,title:b.upload,hidden:!0,
+filebrowser:"uploadButton",elements:[{type:"file",id:"upload",label:c.upload,style:"height:40px",size:29},{type:"fileButton",id:"uploadButton",label:c.uploadSubmit,filebrowser:"info:url","for":["upload","upload"]}]},{id:"advanced",label:b.advanced,title:b.advanced,elements:[{type:"vbox",padding:1,children:[{type:"hbox",widths:["45%","35%","20%"],children:[{type:"text",id:"advId",requiredContent:"a[id]",label:b.id,setup:h,commit:j},{type:"select",id:"advLangDir",requiredContent:"a[dir]",label:b.langDir,
+"default":"",style:"width:110px",items:[[c.notSet,""],[b.langDirLTR,"ltr"],[b.langDirRTL,"rtl"]],setup:h,commit:j},{type:"text",id:"advAccessKey",requiredContent:"a[accesskey]",width:"80px",label:b.acccessKey,maxLength:1,setup:h,commit:j}]},{type:"hbox",widths:["45%","35%","20%"],children:[{type:"text",label:b.name,id:"advName",requiredContent:"a[name]",setup:h,commit:j},{type:"text",label:b.langCode,id:"advLangCode",requiredContent:"a[lang]",width:"110px","default":"",setup:h,commit:j},{type:"text",
+label:b.tabIndex,id:"advTabIndex",requiredContent:"a[tabindex]",width:"80px",maxLength:5,setup:h,commit:j}]}]},{type:"vbox",padding:1,children:[{type:"hbox",widths:["45%","55%"],children:[{type:"text",label:b.advisoryTitle,requiredContent:"a[title]","default":"",id:"advTitle",setup:h,commit:j},{type:"text",label:b.advisoryContentType,requiredContent:"a[type]","default":"",id:"advContentType",setup:h,commit:j}]},{type:"hbox",widths:["45%","55%"],children:[{type:"text",label:b.cssClasses,requiredContent:"a(cke-xyz)",
+"default":"",id:"advCSSClasses",setup:h,commit:j},{type:"text",label:b.charset,requiredContent:"a[charset]","default":"",id:"advCharset",setup:h,commit:j}]},{type:"hbox",widths:["45%","55%"],children:[{type:"text",label:b.rel,requiredContent:"a[rel]","default":"",id:"advRel",setup:h,commit:j},{type:"text",label:b.styles,requiredContent:"a{cke-xyz}","default":"",id:"advStyles",validate:CKEDITOR.dialog.validate.inlineStyle(g.lang.common.invalidInlineStyle),setup:h,commit:j}]}]}]}],onShow:function(){var a=
+this.getParentEditor(),b=a.getSelection(),c=null;(c=l.getSelectedLink(a))&&c.hasAttribute("href")?b.getSelectedElement()||b.selectElement(c):c=null;a=l.parseLinkAttributes(a,c);this._.selectedElement=c;this.setupContent(a)},onOk:function(){var a={};this.commitContent(a);var b=g.getSelection(),c=l.getLinkAttributes(g,a);if(this._.selectedElement){var e=this._.selectedElement,d=e.data("cke-saved-href"),f=e.getHtml();e.setAttributes(c.set);e.removeAttributes(c.removed);if(d==f||"email"==a.type&&-1!=
+f.indexOf("@"))e.setHtml("email"==a.type?a.email.address:c.set["data-cke-saved-href"]),b.selectElement(e);delete this._.selectedElement}else b=b.getRanges()[0],b.collapsed&&(a=new CKEDITOR.dom.text("email"==a.type?a.email.address:c.set["data-cke-saved-href"],g.document),b.insertNode(a),b.selectNodeContents(a)),c=new CKEDITOR.style({element:"a",attributes:c.set}),c.type=CKEDITOR.STYLE_INLINE,c.applyToRange(b,g),b.select()},onLoad:function(){g.config.linkShowAdvancedTab||this.hidePage("advanced");g.config.linkShowTargetTab||
+this.hidePage("target")},onFocus:function(){var a=this.getContentElement("info","linkType");a&&"url"==a.getValue()&&(a=this.getContentElement("info","url"),a.select())}}})})(); \ No newline at end of file
diff --git a/js/ckeditor/plugins/link/images/anchor.png b/js/ckeditor/plugins/link/images/anchor.png
new file mode 100644
index 0000000..6d861a0
--- /dev/null
+++ b/js/ckeditor/plugins/link/images/anchor.png
Binary files differ
diff --git a/js/ckeditor/plugins/link/images/hidpi/anchor.png b/js/ckeditor/plugins/link/images/hidpi/anchor.png
new file mode 100644
index 0000000..f504843
--- /dev/null
+++ b/js/ckeditor/plugins/link/images/hidpi/anchor.png
Binary files differ
diff --git a/js/ckeditor/plugins/magicline/images/hidpi/icon-rtl.png b/js/ckeditor/plugins/magicline/images/hidpi/icon-rtl.png
new file mode 100644
index 0000000..4a8d2bf
--- /dev/null
+++ b/js/ckeditor/plugins/magicline/images/hidpi/icon-rtl.png
Binary files differ
diff --git a/js/ckeditor/plugins/magicline/images/hidpi/icon.png b/js/ckeditor/plugins/magicline/images/hidpi/icon.png
new file mode 100644
index 0000000..b981bb5
--- /dev/null
+++ b/js/ckeditor/plugins/magicline/images/hidpi/icon.png
Binary files differ
diff --git a/js/ckeditor/plugins/magicline/images/icon-rtl.png b/js/ckeditor/plugins/magicline/images/icon-rtl.png
new file mode 100644
index 0000000..55b5b5f
--- /dev/null
+++ b/js/ckeditor/plugins/magicline/images/icon-rtl.png
Binary files differ
diff --git a/js/ckeditor/plugins/magicline/images/icon.png b/js/ckeditor/plugins/magicline/images/icon.png
new file mode 100644
index 0000000..e063433
--- /dev/null
+++ b/js/ckeditor/plugins/magicline/images/icon.png
Binary files differ
diff --git a/js/ckeditor/plugins/pastefromword/filter/default.js b/js/ckeditor/plugins/pastefromword/filter/default.js
new file mode 100644
index 0000000..9c58a16
--- /dev/null
+++ b/js/ckeditor/plugins/pastefromword/filter/default.js
@@ -0,0 +1,31 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){function y(a){for(var a=a.toUpperCase(),c=z.length,b=0,f=0;f<c;++f)for(var d=z[f],e=d[1].length;a.substr(0,e)==d[1];a=a.substr(e))b+=d[0];return b}function A(a){for(var a=a.toUpperCase(),c=B.length,b=1,f=1;0<a.length;f*=c)b+=B.indexOf(a.charAt(a.length-1))*f,a=a.substr(0,a.length-1);return b}var C=CKEDITOR.htmlParser.fragment.prototype,o=CKEDITOR.htmlParser.element.prototype;C.onlyChild=o.onlyChild=function(){var a=this.children;return 1==a.length&&a[0]||null};o.removeAnyChildWithName=
+function(a){for(var c=this.children,b=[],f,d=0;d<c.length;d++)f=c[d],f.name&&(f.name==a&&(b.push(f),c.splice(d--,1)),b=b.concat(f.removeAnyChildWithName(a)));return b};o.getAncestor=function(a){for(var c=this.parent;c&&(!c.name||!c.name.match(a));)c=c.parent;return c};C.firstChild=o.firstChild=function(a){for(var c,b=0;b<this.children.length;b++)if(c=this.children[b],a(c)||c.name&&(c=c.firstChild(a)))return c;return null};o.addStyle=function(a,c,b){var f="";if("string"==typeof c)f+=a+":"+c+";";else{if("object"==
+typeof a)for(var d in a)a.hasOwnProperty(d)&&(f+=d+":"+a[d]+";");else f+=a;b=c}this.attributes||(this.attributes={});a=this.attributes.style||"";a=(b?[f,a]:[a,f]).join(";");this.attributes.style=a.replace(/^;+|;(?=;)/g,"")};o.getStyle=function(a){var c=this.attributes.style;if(c)return c=CKEDITOR.tools.parseCssText(c,1),c[a]};CKEDITOR.dtd.parentOf=function(a){var c={},b;for(b in this)-1==b.indexOf("$")&&this[b][a]&&(c[b]=1);return c};var H=/^([.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz){1}?/i,
+D=/^(?:\b0[^\s]*\s*){1,4}$/,x={ol:{decimal:/\d+/,"lower-roman":/^m{0,4}(cm|cd|d?c{0,3})(xc|xl|l?x{0,3})(ix|iv|v?i{0,3})$/,"upper-roman":/^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/,"lower-alpha":/^[a-z]+$/,"upper-alpha":/^[A-Z]+$/},ul:{disc:/[l\u00B7\u2002]/,circle:/[\u006F\u00D8]/,square:/[\u006E\u25C6]/}},z=[[1E3,"M"],[900,"CM"],[500,"D"],[400,"CD"],[100,"C"],[90,"XC"],[50,"L"],[40,"XL"],[10,"X"],[9,"IX"],[5,"V"],[4,"IV"],[1,"I"]],B="ABCDEFGHIJKLMNOPQRSTUVWXYZ",s=0,t=null,w,E=CKEDITOR.plugins.pastefromword=
+{utils:{createListBulletMarker:function(a,c){var b=new CKEDITOR.htmlParser.element("cke:listbullet");b.attributes={"cke:listsymbol":a[0]};b.add(new CKEDITOR.htmlParser.text(c));return b},isListBulletIndicator:function(a){if(/mso-list\s*:\s*Ignore/i.test(a.attributes&&a.attributes.style))return!0},isContainingOnlySpaces:function(a){var c;return(c=a.onlyChild())&&/^(:?\s|&nbsp;)+$/.test(c.value)},resolveList:function(a){var c=a.attributes,b;if((b=a.removeAnyChildWithName("cke:listbullet"))&&b.length&&
+(b=b[0]))return a.name="cke:li",c.style&&(c.style=E.filters.stylesFilter([["text-indent"],["line-height"],[/^margin(:?-left)?$/,null,function(a){a=a.split(" ");a=CKEDITOR.tools.convertToPx(a[3]||a[1]||a[0]);!s&&(null!==t&&a>t)&&(s=a-t);t=a;c["cke:indent"]=s&&Math.ceil(a/s)+1||1}],[/^mso-list$/,null,function(a){var a=a.split(" "),b=Number(a[0].match(/\d+/)),a=Number(a[1].match(/\d+/));1==a&&(b!==w&&(c["cke:reset"]=1),w=b);c["cke:indent"]=a}]])(c.style,a)||""),c["cke:indent"]||(t=0,c["cke:indent"]=
+1),CKEDITOR.tools.extend(c,b.attributes),!0;w=t=s=null;return!1},getStyleComponents:function(){var a=CKEDITOR.dom.element.createFromHtml('<div style="position:absolute;left:-9999px;top:-9999px;"></div>',CKEDITOR.document);CKEDITOR.document.getBody().append(a);return function(c,b,f){a.setStyle(c,b);for(var c={},b=f.length,d=0;d<b;d++)c[f[d]]=a.getStyle(f[d]);return c}}(),listDtdParents:CKEDITOR.dtd.parentOf("ol")},filters:{flattenList:function(a,c){var c="number"==typeof c?c:1,b=a.attributes,f;switch(b.type){case "a":f=
+"lower-alpha";break;case "1":f="decimal"}for(var d=a.children,e,h=0;h<d.length;h++)if(e=d[h],e.name in CKEDITOR.dtd.$listItem){var j=e.attributes,g=e.children,m=g[g.length-1];m.name in CKEDITOR.dtd.$list&&(a.add(m,h+1),--g.length||d.splice(h--,1));e.name="cke:li";b.start&&!h&&(j.value=b.start);E.filters.stylesFilter([["tab-stops",null,function(a){(a=a.split(" ")[1].match(H))&&(t=CKEDITOR.tools.convertToPx(a[0]))}],1==c?["mso-list",null,function(a){a=a.split(" ");a=Number(a[0].match(/\d+/));a!==w&&
+(j["cke:reset"]=1);w=a}]:null])(j.style);j["cke:indent"]=c;j["cke:listtype"]=a.name;j["cke:list-style-type"]=f}else if(e.name in CKEDITOR.dtd.$list){arguments.callee.apply(this,[e,c+1]);d=d.slice(0,h).concat(e.children).concat(d.slice(h+1));a.children=[];e=0;for(g=d.length;e<g;e++)a.add(d[e]);d=a.children}delete a.name;b["cke:list"]=1},assembleList:function(a){for(var c=a.children,b,f,d,e,h,j,a=[],g,m,i,l,k,p,n=0;n<c.length;n++)if(b=c[n],"cke:li"==b.name)if(b.name="li",f=b.attributes,i=(i=f["cke:listsymbol"])&&
+i.match(/^(?:[(]?)([^\s]+?)([.)]?)$/),l=k=p=null,f["cke:ignored"])c.splice(n--,1);else{f["cke:reset"]&&(j=e=h=null);d=Number(f["cke:indent"]);d!=e&&(m=g=null);if(i){if(m&&x[m][g].test(i[1]))l=m,k=g;else for(var q in x)for(var u in x[q])if(x[q][u].test(i[1]))if("ol"==q&&/alpha|roman/.test(u)){if(g=/roman/.test(u)?y(i[1]):A(i[1]),!p||g<p)p=g,l=q,k=u}else{l=q;k=u;break}!l&&(l=i[2]?"ol":"ul")}else l=f["cke:listtype"]||"ol",k=f["cke:list-style-type"];m=l;g=k||("ol"==l?"decimal":"disc");k&&k!=("ol"==l?
+"decimal":"disc")&&b.addStyle("list-style-type",k);if("ol"==l&&i){switch(k){case "decimal":p=Number(i[1]);break;case "lower-roman":case "upper-roman":p=y(i[1]);break;case "lower-alpha":case "upper-alpha":p=A(i[1])}b.attributes.value=p}if(j){if(d>e)a.push(j=new CKEDITOR.htmlParser.element(l)),j.add(b),h.add(j);else{if(d<e){e-=d;for(var r;e--&&(r=j.parent);)j=r.parent}j.add(b)}c.splice(n--,1)}else a.push(j=new CKEDITOR.htmlParser.element(l)),j.add(b),c[n]=j;h=b;e=d}else j&&(j=e=h=null);for(n=0;n<a.length;n++)if(j=
+a[n],q=j.children,g=g=void 0,u=j.children.length,r=g=void 0,c=/list-style-type:(.*?)(?:;|$)/,e=CKEDITOR.plugins.pastefromword.filters.stylesFilter,g=j.attributes,!c.exec(g.style)){for(h=0;h<u;h++)if(g=q[h],g.attributes.value&&Number(g.attributes.value)==h+1&&delete g.attributes.value,g=c.exec(g.attributes.style))if(g[1]==r||!r)r=g[1];else{r=null;break}if(r){for(h=0;h<u;h++)g=q[h].attributes,g.style&&(g.style=e([["list-style-type"]])(g.style)||"");j.addStyle("list-style-type",r)}}w=t=s=null},falsyFilter:function(){return!1},
+stylesFilter:function(a,c){return function(b,f){var d=[];(b||"").replace(/&quot;/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(b,e,g){e=e.toLowerCase();"font-family"==e&&(g=g.replace(/["']/g,""));for(var m,i,l,k=0;k<a.length;k++)if(a[k]&&(b=a[k][0],m=a[k][1],i=a[k][2],l=a[k][3],e.match(b)&&(!m||g.match(m)))){e=l||e;c&&(i=i||g);"function"==typeof i&&(i=i(g,f,e));i&&i.push&&(e=i[0],i=i[1]);"string"==typeof i&&d.push([e,i]);return}!c&&d.push([e,g])});for(var e=0;e<d.length;e++)d[e]=
+d[e].join(":");return d.length?d.join(";")+";":!1}},elementMigrateFilter:function(a,c){return a?function(b){var f=c?(new CKEDITOR.style(a,c))._.definition:a;b.name=f.element;CKEDITOR.tools.extend(b.attributes,CKEDITOR.tools.clone(f.attributes));b.addStyle(CKEDITOR.style.getStyleText(f))}:function(){}},styleMigrateFilter:function(a,c){var b=this.elementMigrateFilter;return a?function(f,d){var e=new CKEDITOR.htmlParser.element(null),h={};h[c]=f;b(a,h)(e);e.children=d.children;d.children=[e];e.filter=
+function(){};e.parent=d}:function(){}},bogusAttrFilter:function(a,c){if(-1==c.name.indexOf("cke:"))return!1},applyStyleFilter:null},getRules:function(a,c){var b=CKEDITOR.dtd,f=CKEDITOR.tools.extend({},b.$block,b.$listItem,b.$tableContent),d=a.config,e=this.filters,h=e.falsyFilter,j=e.stylesFilter,g=e.elementMigrateFilter,m=CKEDITOR.tools.bind(this.filters.styleMigrateFilter,this.filters),i=this.utils.createListBulletMarker,l=e.flattenList,k=e.assembleList,p=this.utils.isListBulletIndicator,n=this.utils.isContainingOnlySpaces,
+q=this.utils.resolveList,u=function(a){a=CKEDITOR.tools.convertToPx(a);return isNaN(a)?a:a+"px"},r=this.utils.getStyleComponents,t=this.utils.listDtdParents,o=!1!==d.pasteFromWordRemoveFontStyles,s=!1!==d.pasteFromWordRemoveStyles;return{elementNames:[[/meta|link|script/,""]],root:function(a){a.filterChildren(c);k(a)},elements:{"^":function(a){var c;CKEDITOR.env.gecko&&(c=e.applyStyleFilter)&&c(a)},$:function(a){var v=a.name||"",e=a.attributes;v in f&&e.style&&(e.style=j([[/^(:?width|height)$/,null,
+u]])(e.style)||"");if(v.match(/h\d/)){a.filterChildren(c);if(q(a))return;g(d["format_"+v])(a)}else if(v in b.$inline)a.filterChildren(c),n(a)&&delete a.name;else if(-1!=v.indexOf(":")&&-1==v.indexOf("cke")){a.filterChildren(c);if("v:imagedata"==v){if(v=a.attributes["o:href"])a.attributes.src=v;a.name="img";return}delete a.name}v in t&&(a.filterChildren(c),k(a))},style:function(a){if(CKEDITOR.env.gecko){var a=(a=a.onlyChild().value.match(/\/\* Style Definitions \*\/([\s\S]*?)\/\*/))&&a[1],c={};a&&
+(a.replace(/[\n\r]/g,"").replace(/(.+?)\{(.+?)\}/g,function(a,b,F){for(var b=b.split(","),a=b.length,d=0;d<a;d++)CKEDITOR.tools.trim(b[d]).replace(/^(\w+)(\.[\w-]+)?$/g,function(a,b,d){b=b||"*";d=d.substring(1,d.length);d.match(/MsoNormal/)||(c[b]||(c[b]={}),d?c[b][d]=F:c[b]=F)})}),e.applyStyleFilter=function(a){var b=c["*"]?"*":a.name,d=a.attributes&&a.attributes["class"];b in c&&(b=c[b],"object"==typeof b&&(b=b[d]),b&&a.addStyle(b,!0))})}return!1},p:function(a){if(/MsoListParagraph/i.exec(a.attributes["class"])||
+a.getStyle("mso-list")){var b=a.firstChild(function(a){return a.type==CKEDITOR.NODE_TEXT&&!n(a.parent)});(b=b&&b.parent)&&b.addStyle("mso-list","Ignore")}a.filterChildren(c);q(a)||(d.enterMode==CKEDITOR.ENTER_BR?(delete a.name,a.add(new CKEDITOR.htmlParser.element("br"))):g(d["format_"+(d.enterMode==CKEDITOR.ENTER_P?"p":"div")])(a))},div:function(a){var c=a.onlyChild();if(c&&"table"==c.name){var b=a.attributes;c.attributes=CKEDITOR.tools.extend(c.attributes,b);b.style&&c.addStyle(b.style);c=new CKEDITOR.htmlParser.element("div");
+c.addStyle("clear","both");a.add(c);delete a.name}},td:function(a){a.getAncestor("thead")&&(a.name="th")},ol:l,ul:l,dl:l,font:function(a){if(p(a.parent))delete a.name;else{a.filterChildren(c);var b=a.attributes,d=b.style,e=a.parent;"font"==e.name?(CKEDITOR.tools.extend(e.attributes,a.attributes),d&&e.addStyle(d),delete a.name):(d=(d||"").split(";"),b.color&&("#000000"!=b.color&&d.push("color:"+b.color),delete b.color),b.face&&(d.push("font-family:"+b.face),delete b.face),b.size&&(d.push("font-size:"+
+(3<b.size?"large":3>b.size?"small":"medium")),delete b.size),a.name="span",a.addStyle(d.join(";")))}},span:function(a){if(p(a.parent))return!1;a.filterChildren(c);if(n(a))return delete a.name,null;if(p(a)){var b=a.firstChild(function(a){return a.value||"img"==a.name}),e=(b=b&&(b.value||"l."))&&b.match(/^(?:[(]?)([^\s]+?)([.)]?)$/);if(e)return b=i(e,b),(a=a.getAncestor("span"))&&/ mso-hide:\s*all|display:\s*none /.test(a.attributes.style)&&(b.attributes["cke:ignored"]=1),b}if(e=(b=a.attributes)&&b.style)b.style=
+j([["line-height"],[/^font-family$/,null,!o?m(d.font_style,"family"):null],[/^font-size$/,null,!o?m(d.fontSize_style,"size"):null],[/^color$/,null,!o?m(d.colorButton_foreStyle,"color"):null],[/^background-color$/,null,!o?m(d.colorButton_backStyle,"color"):null]])(e,a)||"";b.style||delete b.style;CKEDITOR.tools.isEmpty(b)&&delete a.name;return null},b:g(d.coreStyles_bold),i:g(d.coreStyles_italic),u:g(d.coreStyles_underline),s:g(d.coreStyles_strike),sup:g(d.coreStyles_superscript),sub:g(d.coreStyles_subscript),
+a:function(a){a=a.attributes;a.href&&a.href.match(/^file:\/\/\/[\S]+#/i)&&(a.href=a.href.replace(/^file:\/\/\/[^#]+/i,""))},"cke:listbullet":function(a){a.getAncestor(/h\d/)&&!d.pasteFromWordNumberedHeadingToList&&delete a.name}},attributeNames:[[/^onmouse(:?out|over)/,""],[/^onload$/,""],[/(?:v|o):\w+/,""],[/^lang/,""]],attributes:{style:j(s?[[/^list-style-type$/,null],[/^margin$|^margin-(?!bottom|top)/,null,function(a,b,c){if(b.name in{p:1,div:1}){b="ltr"==d.contentsLangDirection?"margin-left":
+"margin-right";if("margin"==c)a=r(c,a,[b])[b];else if(c!=b)return null;if(a&&!D.test(a))return[b,a]}return null}],[/^clear$/],[/^border.*|margin.*|vertical-align|float$/,null,function(a,b){if("img"==b.name)return a}],[/^width|height$/,null,function(a,b){if(b.name in{table:1,td:1,th:1,img:1})return a}]]:[[/^mso-/],[/-color$/,null,function(a){if("transparent"==a)return!1;if(CKEDITOR.env.gecko)return a.replace(/-moz-use-text-color/g,"transparent")}],[/^margin$/,D],["text-indent","0cm"],["page-break-before"],
+["tab-stops"],["display","none"],o?[/font-?/]:null],s),width:function(a,c){if(c.name in b.$tableContent)return!1},border:function(a,c){if(c.name in b.$tableContent)return!1},"class":h,bgcolor:h,valign:s?h:function(a,b){b.addStyle("vertical-align",a);return!1}},comment:!CKEDITOR.env.ie?function(a,b){var c=a.match(/<img.*?>/),d=a.match(/^\[if !supportLists\]([\s\S]*?)\[endif\]$/);return d?(d=(c=d[1]||c&&"l.")&&c.match(/>(?:[(]?)([^\s]+?)([.)]?)</),i(d,c)):CKEDITOR.env.gecko&&c?(c=CKEDITOR.htmlParser.fragment.fromHtml(c[0]).children[0],
+(d=(d=(d=b.previous)&&d.value.match(/<v:imagedata[^>]*o:href=['"](.*?)['"]/))&&d[1])&&(c.attributes.src=d),c):!1}:h}}},G=function(){this.dataFilter=new CKEDITOR.htmlParser.filter};G.prototype={toHtml:function(a){var a=CKEDITOR.htmlParser.fragment.fromHtml(a),c=new CKEDITOR.htmlParser.basicWriter;a.writeHtml(c,this.dataFilter);return c.getHtml(!0)}};CKEDITOR.cleanWord=function(a,c){CKEDITOR.env.gecko&&(a=a.replace(/(<\!--\[if[^<]*?\])--\>([\S\s]*?)<\!--(\[endif\]--\>)/gi,"$1$2$3"));CKEDITOR.env.webkit&&
+(a=a.replace(/(class="MsoListParagraph[^>]+><\!--\[if !supportLists\]--\>)([^<]+<span[^<]+<\/span>)(<\!--\[endif\]--\>)/gi,"$1<span>$2</span>$3"));var b=new G,f=b.dataFilter;f.addRules(CKEDITOR.plugins.pastefromword.getRules(c,f));c.fire("beforeCleanWord",{filter:f});try{a=b.toHtml(a)}catch(d){alert(c.lang.pastefromword.error)}a=a.replace(/cke:.*?".*?"/g,"");a=a.replace(/style=""/g,"");return a=a.replace(/<span>/g,"")}})(); \ No newline at end of file
diff --git a/js/ckeditor/plugins/scayt/LICENSE.md b/js/ckeditor/plugins/scayt/LICENSE.md
new file mode 100644
index 0000000..610c807
--- /dev/null
+++ b/js/ckeditor/plugins/scayt/LICENSE.md
@@ -0,0 +1,28 @@
+Software License Agreement
+==========================
+
+**CKEditor SCAYT Plugin**
+Copyright &copy; 2012, [CKSource](http://cksource.com) - Frederico Knabben. All rights reserved.
+
+Licensed under the terms of any of the following licenses at your choice:
+
+* GNU General Public License Version 2 or later (the "GPL"):
+ http://www.gnu.org/licenses/gpl.html
+
+* GNU Lesser General Public License Version 2.1 or later (the "LGPL"):
+ http://www.gnu.org/licenses/lgpl.html
+
+* Mozilla Public License Version 1.1 or later (the "MPL"):
+ http://www.mozilla.org/MPL/MPL-1.1.html
+
+You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using, reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of this software, indicating your license choice.
+
+Sources of Intellectual Property Included in this plugin
+--------------------------------------------------------
+
+Where not otherwise indicated, all plugin content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, the plugin will incorporate work done by developers outside of CKSource with their express permission.
+
+Trademarks
+----------
+
+CKEditor is a trademark of CKSource - Frederico Knabben. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
diff --git a/js/ckeditor/plugins/scayt/README.md b/js/ckeditor/plugins/scayt/README.md
new file mode 100644
index 0000000..1b3de25
--- /dev/null
+++ b/js/ckeditor/plugins/scayt/README.md
@@ -0,0 +1,25 @@
+CKEditor SCAYT Plugin
+=====================
+
+This plugin brings Spell Check As You Type (SCAYT) into up to CKEditor 4+.
+
+SCAYT is a "installation-less", using the web-services of [WebSpellChecker.net](http://www.webspellchecker.net/). It's an out of the box solution.
+
+Installation
+------------
+
+1. Clone/copy this repository contents in a new "plugins/scayt" folder in your CKEditor installation.
+2. Enable the "scayt" plugin in the CKEditor configuration file (config.js):
+
+ config.extraPlugins = 'scayt';
+
+That's all. SCAYT will appear on the editor toolbar and will be ready to use.
+
+License
+-------
+
+Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html).
+
+See LICENSE.md for more information.
+
+Developed in cooperation with [WebSpellChecker.net](http://www.webspellchecker.net/).
diff --git a/js/ckeditor/plugins/scayt/dialogs/options.js b/js/ckeditor/plugins/scayt/dialogs/options.js
new file mode 100644
index 0000000..8178e1a
--- /dev/null
+++ b/js/ckeditor/plugins/scayt/dialogs/options.js
@@ -0,0 +1,17 @@
+CKEDITOR.dialog.add("scaytDialog",function(f){var g=f.scayt,k='<p><img src="'+g.getLogo()+'" /></p><p>'+g.getLocal("version")+g.getVersion()+"</p><p>"+g.getLocal("text_copyrights")+"</p>",l=CKEDITOR.document,i={isChanged:function(){return null===this.newLang||this.currentLang===this.newLang?!1:!0},currentLang:g.getLang(),newLang:null,reset:function(){this.currentLang=g.getLang();this.newLang=null},id:"lang"},k=[{id:"options",label:g.getLocal("tab_options"),onShow:function(){},elements:[{type:"vbox",
+id:"scaytOptions",children:function(){var a=g.getApplicationConfig(),e=[],c={"ignore-all-caps-words":"label_allCaps","ignore-domain-names":"label_ignoreDomainNames","ignore-words-with-mixed-cases":"label_mixedCase","ignore-words-with-numbers":"label_mixedWithDigits"},d;for(d in a){var b={type:"checkbox"};b.id=d;b.label=g.getLocal(c[d]);e.push(b)}return e}(),onShow:function(){this.getChild();for(var a=f.scayt,e=0;e<this.getChild().length;e++)this.getChild()[e].setValue(a.getApplicationConfig()[this.getChild()[e].id])}}]},
+{id:"langs",label:g.getLocal("tab_languages"),elements:[{id:"leftLangColumn",type:"vbox",align:"left",widths:["100"],children:[{type:"html",id:"langBox",style:"overflow: hidden; white-space: normal;",html:'<div><div style="float:left;width:45%;margin-left:5px;" id="left-col-'+f.name+'"></div><div style="float:left;width:45%;margin-left:15px;" id="right-col-'+f.name+'"></div></div>',onShow:function(){var a=f.scayt.getLang();l.getById("scaytLang_"+a).$.checked=!0}}]}]},{id:"dictionaries",label:g.getLocal("tab_dictionaries"),
+elements:[{type:"vbox",id:"rightCol_col__left",children:[{type:"html",id:"dictionaryNote",html:""},{type:"text",id:"dictionaryName",label:g.getLocal("label_fieldNameDic")||"Dictionary name",onShow:function(a){var e=a.sender,c=f.scayt;setTimeout(function(){e.getContentElement("dictionaries","dictionaryNote").getElement().setText("");null!=c.getUserDictionaryName()&&""!=c.getUserDictionaryName()&&e.getContentElement("dictionaries","dictionaryName").setValue(c.getUserDictionaryName())},0)}},{type:"hbox",
+id:"notExistDic",align:"left",style:"width:auto;",widths:["50%","50%"],children:[{type:"button",id:"createDic",label:g.getLocal("btn_createDic"),title:g.getLocal("btn_createDic"),onClick:function(){var a=this.getDialog(),e=j,c=f.scayt,d=a.getContentElement("dictionaries","dictionaryName").getValue();c.createUserDictionary(d,function(b){b.error||e.toggleDictionaryButtons.call(a,!0);b.dialog=a;b.command="create";b.name=d;f.fire("scaytUserDictionaryAction",b)},function(b){b.dialog=a;b.command="create";
+b.name=d;f.fire("scaytUserDictionaryActionError",b)})}},{type:"button",id:"restoreDic",label:g.getLocal("btn_restoreDic"),title:g.getLocal("btn_restoreDic"),onClick:function(){var a=this.getDialog(),e=f.scayt,c=j,d=a.getContentElement("dictionaries","dictionaryName").getValue();e.restoreUserDictionary(d,function(b){b.dialog=a;b.error||c.toggleDictionaryButtons.call(a,!0);b.command="restore";b.name=d;f.fire("scaytUserDictionaryAction",b)},function(b){b.dialog=a;b.command="restore";b.name=d;f.fire("scaytUserDictionaryActionError",
+b)})}}]},{type:"hbox",id:"existDic",align:"left",style:"width:auto;",widths:["50%","50%"],children:[{type:"button",id:"removeDic",label:g.getLocal("btn_deleteDic"),title:g.getLocal("btn_deleteDic"),onClick:function(){var a=this.getDialog(),e=f.scayt,c=j,d=a.getContentElement("dictionaries","dictionaryName"),b=d.getValue();e.removeUserDictionary(b,function(e){d.setValue("");e.error||c.toggleDictionaryButtons.call(a,!1);e.dialog=a;e.command="remove";e.name=b;f.fire("scaytUserDictionaryAction",e)},function(c){c.dialog=
+a;c.command="remove";c.name=b;f.fire("scaytUserDictionaryActionError",c)})}},{type:"button",id:"renameDic",label:g.getLocal("btn_renameDic"),title:g.getLocal("btn_renameDic"),onClick:function(){var a=this.getDialog(),e=f.scayt,c=a.getContentElement("dictionaries","dictionaryName").getValue();e.renameUserDictionary(c,function(d){d.dialog=a;d.command="rename";d.name=c;f.fire("scaytUserDictionaryAction",d)},function(d){d.dialog=a;d.command="rename";d.name=c;f.fire("scaytUserDictionaryActionError",d)})}}]},
+{type:"html",id:"dicInfo",html:'<div id="dic_info_editor1" style="margin:5px auto; width:95%;white-space:normal;">'+g.getLocal("text_descriptionDic")+"</div>"}]}]},{id:"about",label:g.getLocal("tab_about"),elements:[{type:"html",id:"about",style:"margin: 5px 5px;",html:'<div><div id="scayt_about_">'+k+"</div></div>"}]}];f.on("scaytUserDictionaryAction",function(a){var e=SCAYT.prototype.UILib,c=a.data.dialog,d=c.getContentElement("dictionaries","dictionaryNote").getElement(),b=a.editor.scayt,f;void 0===
+a.data.error?(f=b.getLocal("message_success_"+a.data.command+"Dic"),f=f.replace("%s",a.data.name),d.setText(f),e.css(d.$,{color:"blue"})):(""===a.data.name?d.setText(b.getLocal("message_info_emptyDic")):(f=b.getLocal("message_error_"+a.data.command+"Dic"),f=f.replace("%s",a.data.name),d.setText(f)),e.css(d.$,{color:"red"}),null!=b.getUserDictionaryName()&&""!=b.getUserDictionaryName()?c.getContentElement("dictionaries","dictionaryName").setValue(b.getUserDictionaryName()):c.getContentElement("dictionaries",
+"dictionaryName").setValue(""))});f.on("scaytUserDictionaryActionError",function(a){var e=SCAYT.prototype.UILib,c=a.data.dialog,d=c.getContentElement("dictionaries","dictionaryNote").getElement(),b=a.editor.scayt,f;""===a.data.name?d.setText(b.getLocal("message_info_emptyDic")):(f=b.getLocal("message_error_"+a.data.command+"Dic"),f=f.replace("%s",a.data.name),d.setText(f));e.css(d.$,{color:"red"});null!=b.getUserDictionaryName()&&""!=b.getUserDictionaryName()?c.getContentElement("dictionaries","dictionaryName").setValue(b.getUserDictionaryName()):
+c.getContentElement("dictionaries","dictionaryName").setValue("")});var j={title:g.getLocal("text_title"),resizable:CKEDITOR.DIALOG_RESIZE_BOTH,minWidth:340,minHeight:260,onLoad:function(){if(0!=f.config.scayt_uiTabs[1]){var a=j,e=a.getLangBoxes.call(this);e.getParent().setStyle("white-space","normal");a.renderLangList(e);this.definition.minWidth=this.getSize().width;this.resize(this.definition.minWidth,this.definition.minHeight)}},onCancel:function(){i.reset()},onHide:function(){f.unlockSelection()},
+onShow:function(){f.fire("scaytDialogShown",this);if(0!=f.config.scayt_uiTabs[2]){var a=f.scayt,e=this.getContentElement("dictionaries","dictionaryName"),c=this.getContentElement("dictionaries","existDic").getElement().getParent(),d=this.getContentElement("dictionaries","notExistDic").getElement().getParent();c.hide();d.hide();null!=a.getUserDictionaryName()&&""!=a.getUserDictionaryName()?(this.getContentElement("dictionaries","dictionaryName").setValue(a.getUserDictionaryName()),c.show()):(e.setValue(""),
+d.show())}},onOk:function(){var a=j,e=f.scayt;this.getContentElement("options","scaytOptions");a=a.getChangedOption.call(this);e.commitOption({changedOptions:a})},toggleDictionaryButtons:function(a){var e=this.getContentElement("dictionaries","existDic").getElement().getParent(),c=this.getContentElement("dictionaries","notExistDic").getElement().getParent();a?(e.show(),c.hide()):(e.hide(),c.show())},getChangedOption:function(){var a={};if(1==f.config.scayt_uiTabs[0])for(var e=this.getContentElement("options",
+"scaytOptions").getChild(),c=0;c<e.length;c++)e[c].isChanged()&&(a[e[c].id]=e[c].getValue());i.isChanged()&&(a[i.id]=f.config.scayt_sLang=i.currentLang=i.newLang);return a},buildRadioInputs:function(a,e){var c=new CKEDITOR.dom.element("div");CKEDITOR.document.createElement("div");var d="scaytLang_"+e,b=CKEDITOR.dom.element.createFromHtml('<input id="'+d+'" type="radio" value="'+e+'" name="scayt_lang" />'),g=new CKEDITOR.dom.element("label"),h=f.scayt;c.setStyles({"white-space":"normal",position:"relative",
+"padding-bottom":"2px"});b.on("click",function(a){i.newLang=a.sender.getValue()});g.appendText(a);g.setAttribute("for",d);c.append(b);c.append(g);e===h.getLang()&&(b.setAttribute("checked",!0),b.setAttribute("defaultChecked","defaultChecked"));return c},renderLangList:function(a){var e=a.find("#left-col-"+f.name).getItem(0),a=a.find("#right-col-"+f.name).getItem(0),c=g.getLangList(),d={},b=[],i=0,h;for(h in c.ltr)d[h]=c.ltr[h];for(h in c.rtl)d[h]=c.rtl[h];for(h in d)b.push([h,d[h]]);b.sort(function(a,
+c){var b=0;a[1]>c[1]?b=1:a[1]<c[1]&&(b=-1);return b});d={};for(c=0;c<b.length;c++)d[b[c][0]]=b[c][1];b=Math.round(b.length/2);for(h in d)i++,this.buildRadioInputs(d[h],h).appendTo(i<=b?e:a)},getLangBoxes:function(){return this.getContentElement("langs","langBox").getElement()},contents:function(a,e){var c=[],d=e.config.scayt_uiTabs;if(d){for(var b in d)1==d[b]&&c.push(a[b]);c.push(a[a.length-1])}else return a;return c}(k,f)};return j}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/scayt/dialogs/toolbar.css b/js/ckeditor/plugins/scayt/dialogs/toolbar.css
new file mode 100644
index 0000000..861f43e
--- /dev/null
+++ b/js/ckeditor/plugins/scayt/dialogs/toolbar.css
@@ -0,0 +1,71 @@
+a
+{
+ text-decoration:none;
+ padding: 2px 4px 4px 6px;
+ display : block;
+ border-width: 1px;
+ border-style: solid;
+ margin : 0px;
+}
+
+a.cke_scayt_toogle:hover,
+a.cke_scayt_toogle:focus,
+a.cke_scayt_toogle:active
+{
+ border-color: #316ac5;
+ background-color: #dff1ff;
+ color : #000;
+ cursor: pointer;
+ margin : 0px;
+}
+a.cke_scayt_toogle {
+ color : #316ac5;
+ border-color: #fff;
+}
+.scayt_enabled a.cke_scayt_item {
+ color : #316ac5;
+ border-color: #fff;
+ margin : 0px;
+}
+.scayt_disabled a.cke_scayt_item {
+ color : gray;
+ border-color : #fff;
+}
+.scayt_enabled a.cke_scayt_item:hover,
+.scayt_enabled a.cke_scayt_item:focus,
+.scayt_enabled a.cke_scayt_item:active
+{
+ border-color: #316ac5;
+ background-color: #dff1ff;
+ color : #000;
+ cursor: pointer;
+}
+.scayt_disabled a.cke_scayt_item:hover,
+.scayt_disabled a.cke_scayt_item:focus,
+.scayt_disabled a.cke_scayt_item:active
+{
+ border-color: gray;
+ background-color: #dff1ff;
+ color : gray;
+ cursor: no-drop;
+}
+.cke_scayt_set_on, .cke_scayt_set_off
+{
+ display: none;
+}
+.scayt_enabled .cke_scayt_set_on
+{
+ display: none;
+}
+.scayt_disabled .cke_scayt_set_on
+{
+ display: inline;
+}
+.scayt_disabled .cke_scayt_set_off
+{
+ display: none;
+}
+.scayt_enabled .cke_scayt_set_off
+{
+ display: inline;
+}
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/_translationstatus.txt b/js/ckeditor/plugins/specialchar/dialogs/lang/_translationstatus.txt
new file mode 100644
index 0000000..3ad20f5
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/_translationstatus.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+
+cs.js Found: 118 Missing: 0
+cy.js Found: 118 Missing: 0
+de.js Found: 118 Missing: 0
+el.js Found: 16 Missing: 102
+eo.js Found: 118 Missing: 0
+et.js Found: 31 Missing: 87
+fa.js Found: 24 Missing: 94
+fi.js Found: 23 Missing: 95
+fr.js Found: 118 Missing: 0
+hr.js Found: 23 Missing: 95
+it.js Found: 118 Missing: 0
+nb.js Found: 118 Missing: 0
+nl.js Found: 118 Missing: 0
+no.js Found: 118 Missing: 0
+tr.js Found: 118 Missing: 0
+ug.js Found: 39 Missing: 79
+zh-cn.js Found: 118 Missing: 0
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/af.js b/js/ckeditor/plugins/specialchar/dialogs/lang/af.js
new file mode 100644
index 0000000..27bb901
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/af.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","af",{euro:"Euroteken",lsquo:"Linker enkelkwotasie",rsquo:"Regter enkelkwotasie",ldquo:"Linker dubbelkwotasie",rdquo:"Regter dubbelkwotasie",ndash:"Kortkoppelteken",mdash:"Langkoppelteken",iexcl:"Omgekeerdeuitroepteken",cent:"Centteken",pound:"Pondteken",curren:"Geldeenheidteken",yen:"Yenteken",brvbar:"Gebreekte balk",sect:"Afdeelingsteken",uml:"Deelteken",copy:"Kopieregteken",ordf:"Vroulikekenteken",laquo:"Linkgeoorienteerde aanhaalingsteken",not:"Verbodeteken",
+reg:"Regestrasieteken",macr:"Lengteteken",deg:"Gradeteken",sup2:"Kwadraatteken",sup3:"Kubiekteken",acute:"Akuutaksentteken",micro:"Mikroteken",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ar.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ar.js
new file mode 100644
index 0000000..8c3cc20
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ar.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ar",{euro:"رمز اليورو",lsquo:"علامة تنصيص Ùردية علي اليسار",rsquo:"علامة تنصيص Ùردية علي اليمين",ldquo:"علامة تنصيص مزدوجة علي اليسار",rdquo:"علامة تنصيص مزدوجة علي اليمين",ndash:"En dash",mdash:"Em dash",iexcl:"علامة تعجب مقلوبة",cent:"رمز السنت",pound:"رمز الاسترليني",curren:"رمز العملة",yen:"رمز الين",brvbar:"شريط مقطوع",sect:"رمز القسم",uml:"Diaeresis",copy:"علامة حقوق الطبع",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"ليست علامة",reg:"علامة مسجّلة",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"علامة الإستÙهام غير صحيحة",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/bg.js b/js/ckeditor/plugins/specialchar/dialogs/lang/bg.js
new file mode 100644
index 0000000..74cc149
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/bg.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","bg",{euro:"Евро знак",lsquo:"ЛÑва маркировка за цитат",rsquo:"ДÑÑна маркировка за цитат",ldquo:"ЛÑва двойна кавичка за цитат",rdquo:"ДÑÑна двойна кавичка за цитат",ndash:"\\\\",mdash:"/",iexcl:"Обърната питанка",cent:"Знак за цент",pound:"Знак за паунд",curren:"Валутен знак",yen:"Знак за йена",brvbar:"ПрекъÑната линиÑ",sect:"Знак за ÑекциÑ",uml:"Diaeresis",copy:"Знак за Copyright",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registered sign",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ca.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ca.js
new file mode 100644
index 0000000..46dcb0a
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ca.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ca",{euro:"Símbol d'euro",lsquo:"Signe de cometa simple esquerra",rsquo:"Signe de cometa simple dreta",ldquo:"Signe de cometa doble esquerra",rdquo:"Signe de cometa doble dreta",ndash:"Guió",mdash:"Guió baix",iexcl:"Signe d'exclamació inversa",cent:"Símbol de percentatge",pound:"Símbol de lliura",curren:"Símbol de moneda",yen:"Símbol de Yen",brvbar:"Barra trencada",sect:"Símbol de secció",uml:"Dièresi",copy:"Símbol de Copyright",ordf:"Indicador ordinal femení",
+laquo:"Signe de cometes angulars esquerra",not:"Símbol de negació",reg:"Símbol registrat",macr:"Macron",deg:"Símbol de grau",sup2:"Superíndex dos",sup3:"Superíndex tres",acute:"Accent agut",micro:"Símbol de micro",para:"Símbol de calderó",middot:"Punt volat",cedil:"Ce trencada",sup1:"Superíndex u",ordm:"Indicador ordinal masculí",raquo:"Signe de cometes angulars dreta",frac14:"Fracció vulgar un quart",frac12:"Fracció vulgar una meitat",frac34:"Fracció vulgar tres quarts",iquest:"Símbol d'interrogació invertit",
+Agrave:"Lletra majúscula llatina A amb accent greu",Aacute:"Lletra majúscula llatina A amb accent agut",Acirc:"Lletra majúscula llatina A amb circumflex",Atilde:"Lletra majúscula llatina A amb titlla",Auml:"Lletra majúscula llatina A amb dièresi",Aring:"Lletra majúscula llatina A amb anell superior",AElig:"Lletra majúscula llatina Æ",Ccedil:"Lletra majúscula llatina C amb ce trencada",Egrave:"Lletra majúscula llatina E amb accent greu",Eacute:"Lletra majúscula llatina E amb accent agut",Ecirc:"Lletra majúscula llatina E amb circumflex",
+Euml:"Lletra majúscula llatina E amb dièresi",Igrave:"Lletra majúscula llatina I amb accent greu",Iacute:"Lletra majúscula llatina I amb accent agut",Icirc:"Lletra majúscula llatina I amb circumflex",Iuml:"Lletra majúscula llatina I amb dièresi",ETH:"Lletra majúscula llatina Eth",Ntilde:"Lletra majúscula llatina N amb titlla",Ograve:"Lletra majúscula llatina O amb accent greu",Oacute:"Lletra majúscula llatina O amb accent agut",Ocirc:"Lletra majúscula llatina O amb circumflex",Otilde:"Lletra majúscula llatina O amb titlla",
+Ouml:"Lletra majúscula llatina O amb dièresi",times:"Símbol de multiplicació",Oslash:"Lletra majúscula llatina O amb barra",Ugrave:"Lletra majúscula llatina U amb accent greu",Uacute:"Lletra majúscula llatina U amb accent agut",Ucirc:"Lletra majúscula llatina U amb circumflex",Uuml:"Lletra majúscula llatina U amb dièresi",Yacute:"Lletra majúscula llatina Y amb accent agut",THORN:"Lletra majúscula llatina Thorn",szlig:"Lletra minúscula llatina sharp s",agrave:"Lletra minúscula llatina a amb accent greu",
+aacute:"Lletra minúscula llatina a amb accent agut",acirc:"Lletra minúscula llatina a amb circumflex",atilde:"Lletra minúscula llatina a amb titlla",auml:"Lletra minúscula llatina a amb dièresi",aring:"Lletra minúscula llatina a amb anell superior",aelig:"Lletra minúscula llatina æ",ccedil:"Lletra minúscula llatina c amb ce trencada",egrave:"Lletra minúscula llatina e amb accent greu",eacute:"Lletra minúscula llatina e amb accent agut",ecirc:"Lletra minúscula llatina e amb circumflex",euml:"Lletra minúscula llatina e amb dièresi",
+igrave:"Lletra minúscula llatina i amb accent greu",iacute:"Lletra minúscula llatina i amb accent agut",icirc:"Lletra minúscula llatina i amb circumflex",iuml:"Lletra minúscula llatina i amb dièresi",eth:"Lletra minúscula llatina eth",ntilde:"Lletra minúscula llatina n amb titlla",ograve:"Lletra minúscula llatina o amb accent greu",oacute:"Lletra minúscula llatina o amb accent agut",ocirc:"Lletra minúscula llatina o amb circumflex",otilde:"Lletra minúscula llatina o amb titlla",ouml:"Lletra minúscula llatina o amb dièresi",
+divide:"Símbol de divisió",oslash:"Lletra minúscula llatina o amb barra",ugrave:"Lletra minúscula llatina u amb accent greu",uacute:"Lletra minúscula llatina u amb accent agut",ucirc:"Lletra minúscula llatina u amb circumflex",uuml:"Lletra minúscula llatina u amb dièresi",yacute:"Lletra minúscula llatina y amb accent agut",thorn:"Lletra minúscula llatina thorn",yuml:"Lletra minúscula llatina y amb dièresi",OElig:"Lligadura majúscula llatina OE",oelig:"Lligadura minúscula llatina oe",372:"Lletra majúscula llatina W amb circumflex",
+374:"Lletra majúscula llatina Y amb circumflex",373:"Lletra minúscula llatina w amb circumflex",375:"Lletra minúscula llatina y amb circumflex",sbquo:"Signe de cita simple baixa-9",8219:"Signe de cita simple alta-invertida-9",bdquo:"Signe de cita doble baixa-9",hellip:"Punts suspensius",trade:"Símbol de marca registrada",9658:"Punter negre apuntant cap a la dreta",bull:"Vinyeta",rarr:"Fletxa cap a la dreta",rArr:"Doble fletxa cap a la dreta",hArr:"Doble fletxa esquerra dreta",diams:"Vestit negre diamant",
+asymp:"Gairebé igual a"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/cs.js b/js/ckeditor/plugins/specialchar/dialogs/lang/cs.js
new file mode 100644
index 0000000..c8d129e
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/cs.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","cs",{euro:"Znak eura",lsquo:"PoÄáteÄní uvozovka jednoduchá",rsquo:"Koncová uvozovka jednoduchá",ldquo:"PoÄáteÄní uvozovka dvojitá",rdquo:"Koncová uvozovka dvojitá",ndash:"En pomlÄka",mdash:"Em pomlÄka",iexcl:"Obrácený vykÅ™iÄník",cent:"Znak centu",pound:"Znak libry",curren:"Znak mÄ›ny",yen:"Znak jenu",brvbar:"PÅ™eruÅ¡ená svislá Äára",sect:"Znak oddílu",uml:"PÅ™ehláska",copy:"Znak copyrightu",ordf:"Ženský indikátor rodu",laquo:"Znak dvojitých lomených uvozovek vlevo",
+not:"Logistický zápor",reg:"Znak registrace",macr:"PomlÄka nad",deg:"Znak stupnÄ›",sup2:"Dvojka jako horní index",sup3:"Trojka jako horní index",acute:"Čárka nad vpravo",micro:"Znak mikro",para:"Znak odstavce",middot:"TeÄka uprostÅ™ed",cedil:"Ocásek vlevo",sup1:"JedniÄka jako horní index",ordm:"Mužský indikátor rodu",raquo:"Znak dvojitých lomených uvozovek vpravo",frac14:"ObyÄejný zlomek jedna Ätvrtina",frac12:"ObyÄejný zlomek jedna polovina",frac34:"ObyÄejný zlomek tÅ™i Ätvrtiny",iquest:"Znak obráceného otazníku",
+Agrave:"Velké písmeno latinky A s Äárkou nad vlevo",Aacute:"Velké písmeno latinky A s Äárkou nad vpravo",Acirc:"Velké písmeno latinky A s vokánÄ›m",Atilde:"Velké písmeno latinky A s tildou",Auml:"Velké písmeno latinky A s dvÄ›ma teÄkami",Aring:"Velké písmeno latinky A s kroužkem nad",AElig:"Velké písmeno latinky Ae",Ccedil:"Velké písmeno latinky C s ocáskem vlevo",Egrave:"Velké písmeno latinky E s Äárkou nad vlevo",Eacute:"Velké písmeno latinky E s Äárkou nad vpravo",Ecirc:"Velké písmeno latinky E s vokánÄ›m",
+Euml:"Velké písmeno latinky E s dvÄ›ma teÄkami",Igrave:"Velké písmeno latinky I s Äárkou nad vlevo",Iacute:"Velké písmeno latinky I s Äárkou nad vpravo",Icirc:"Velké písmeno latinky I s vokánÄ›m",Iuml:"Velké písmeno latinky I s dvÄ›ma teÄkami",ETH:"Velké písmeno latinky Eth",Ntilde:"Velké písmeno latinky N s tildou",Ograve:"Velké písmeno latinky O s Äárkou nad vlevo",Oacute:"Velké písmeno latinky O s Äárkou nad vpravo",Ocirc:"Velké písmeno latinky O s vokánÄ›m",Otilde:"Velké písmeno latinky O s tildou",
+Ouml:"Velké písmeno latinky O s dvÄ›ma teÄkami",times:"Znak násobení",Oslash:"Velké písmeno latinky O pÅ™eÅ¡krtnuté",Ugrave:"Velké písmeno latinky U s Äárkou nad vlevo",Uacute:"Velké písmeno latinky U s Äárkou nad vpravo",Ucirc:"Velké písmeno latinky U s vokánÄ›m",Uuml:"Velké písmeno latinky U s dvÄ›ma teÄkami",Yacute:"Velké písmeno latinky Y s Äárkou nad vpravo",THORN:"Velké písmeno latinky Thorn",szlig:"Malé písmeno latinky ostré s",agrave:"Malé písmeno latinky a s Äárkou nad vlevo",aacute:"Malé písmeno latinky a s Äárkou nad vpravo",
+acirc:"Malé písmeno latinky a s vokánÄ›m",atilde:"Malé písmeno latinky a s tildou",auml:"Malé písmeno latinky a s dvÄ›ma teÄkami",aring:"Malé písmeno latinky a s kroužkem nad",aelig:"Malé písmeno latinky ae",ccedil:"Malé písmeno latinky c s ocáskem vlevo",egrave:"Malé písmeno latinky e s Äárkou nad vlevo",eacute:"Malé písmeno latinky e s Äárkou nad vpravo",ecirc:"Malé písmeno latinky e s vokánÄ›m",euml:"Malé písmeno latinky e s dvÄ›ma teÄkami",igrave:"Malé písmeno latinky i s Äárkou nad vlevo",iacute:"Malé písmeno latinky i s Äárkou nad vpravo",
+icirc:"Malé písmeno latinky i s vokánÄ›m",iuml:"Malé písmeno latinky i s dvÄ›ma teÄkami",eth:"Malé písmeno latinky eth",ntilde:"Malé písmeno latinky n s tildou",ograve:"Malé písmeno latinky o s Äárkou nad vlevo",oacute:"Malé písmeno latinky o s Äárkou nad vpravo",ocirc:"Malé písmeno latinky o s vokánÄ›m",otilde:"Malé písmeno latinky o s tildou",ouml:"Malé písmeno latinky o s dvÄ›ma teÄkami",divide:"Znak dÄ›lení",oslash:"Malé písmeno latinky o pÅ™eÅ¡krtnuté",ugrave:"Malé písmeno latinky u s Äárkou nad vlevo",
+uacute:"Malé písmeno latinky u s Äárkou nad vpravo",ucirc:"Malé písmeno latinky u s vokánÄ›m",uuml:"Malé písmeno latinky u s dvÄ›ma teÄkami",yacute:"Malé písmeno latinky y s Äárkou nad vpravo",thorn:"Malé písmeno latinky thorn",yuml:"Malé písmeno latinky y s dvÄ›ma teÄkami",OElig:"Velká ligatura latinky OE",oelig:"Malá ligatura latinky OE",372:"Velké písmeno latinky W s vokánÄ›m",374:"Velké písmeno latinky Y s vokánÄ›m",373:"Malé písmeno latinky w s vokánÄ›m",375:"Malé písmeno latinky y s vokánÄ›m",sbquo:"Dolní 9 uvozovka jednoduchá",
+8219:"Horní obrácená 9 uvozovka jednoduchá",bdquo:"Dolní 9 uvozovka dvojitá",hellip:"TrojteÄkový úvod",trade:"Obchodní znaÄka",9658:"ÄŒerný ukazatel směřující vpravo",bull:"KoleÄko",rarr:"Å ipka vpravo",rArr:"Dvojitá Å¡ipka vpravo",hArr:"Dvojitá Å¡ipka vlevo a vpravo",diams:"ÄŒerné piky",asymp:"Téměř se rovná"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/cy.js b/js/ckeditor/plugins/specialchar/dialogs/lang/cy.js
new file mode 100644
index 0000000..b873ac9
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/cy.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","cy",{euro:"Arwydd yr Ewro",lsquo:"Dyfynnod chwith unigol",rsquo:"Dyfynnod dde unigol",ldquo:"Dyfynnod chwith dwbl",rdquo:"Dyfynnod dde dwbl",ndash:"Cysylltnod en",mdash:"Cysylltnod em",iexcl:"Ebychnod gwrthdro",cent:"Arwydd sent",pound:"Arwydd punt",curren:"Arwydd arian cyfred",yen:"Arwydd yen",brvbar:"Bar toriedig",sect:"Arwydd adran",uml:"Didolnod",copy:"Arwydd hawlfraint",ordf:"Dangosydd benywaidd",laquo:"Dyfynnod dwbl ar ongl i'r chwith",not:"Arwydd Nid",
+reg:"Arwydd cofrestredig",macr:"Macron",deg:"Arwydd gradd",sup2:"Dau uwchsgript",sup3:"Tri uwchsgript",acute:"Acen ddyrchafedig",micro:"Arwydd micro",para:"Arwydd pilcrow",middot:"Dot canol",cedil:"Sedila",sup1:"Un uwchsgript",ordm:"Dangosydd gwrywaidd",raquo:"Dyfynnod dwbl ar ongl i'r dde",frac14:"Ffracsiwn cyffredin un cwarter",frac12:"Ffracsiwn cyffredin un hanner",frac34:"Ffracsiwn cyffredin tri chwarter",iquest:"Marc cwestiwn gwrthdroëdig",Agrave:"Priflythyren A Lladinaidd gydag acen ddisgynedig",
+Aacute:"Priflythyren A Lladinaidd gydag acen ddyrchafedig",Acirc:"Priflythyren A Lladinaidd gydag acen grom",Atilde:"Priflythyren A Lladinaidd gyda thild",Auml:"Priflythyren A Lladinaidd gyda didolnod",Aring:"Priflythyren A Lladinaidd gyda chylch uwchben",AElig:"Priflythyren Æ Lladinaidd",Ccedil:"Priflythyren C Lladinaidd gyda sedila",Egrave:"Priflythyren E Lladinaidd gydag acen ddisgynedig",Eacute:"Priflythyren E Lladinaidd gydag acen ddyrchafedig",Ecirc:"Priflythyren E Lladinaidd gydag acen grom",
+Euml:"Priflythyren E Lladinaidd gyda didolnod",Igrave:"Priflythyren I Lladinaidd gydag acen ddisgynedig",Iacute:"Priflythyren I Lladinaidd gydag acen ddyrchafedig",Icirc:"Priflythyren I Lladinaidd gydag acen grom",Iuml:"Priflythyren I Lladinaidd gyda didolnod",ETH:"Priflythyren Eth",Ntilde:"Priflythyren N Lladinaidd gyda thild",Ograve:"Priflythyren O Lladinaidd gydag acen ddisgynedig",Oacute:"Priflythyren O Lladinaidd gydag acen ddyrchafedig",Ocirc:"Priflythyren O Lladinaidd gydag acen grom",Otilde:"Priflythyren O Lladinaidd gyda thild",
+Ouml:"Priflythyren O Lladinaidd gyda didolnod",times:"Arwydd lluosi",Oslash:"Priflythyren O Lladinaidd gyda strôc",Ugrave:"Priflythyren U Lladinaidd gydag acen ddisgynedig",Uacute:"Priflythyren U Lladinaidd gydag acen ddyrchafedig",Ucirc:"Priflythyren U Lladinaidd gydag acen grom",Uuml:"Priflythyren U Lladinaidd gyda didolnod",Yacute:"Priflythyren Y Lladinaidd gydag acen ddyrchafedig",THORN:"Priflythyren Thorn",szlig:"Llythyren s fach Lladinaidd siarp ",agrave:"Llythyren a fach Lladinaidd gydag acen ddisgynedig",
+aacute:"Llythyren a fach Lladinaidd gydag acen ddyrchafedig",acirc:"Llythyren a fach Lladinaidd gydag acen grom",atilde:"Llythyren a fach Lladinaidd gyda thild",auml:"Llythyren a fach Lladinaidd gyda didolnod",aring:"Llythyren a fach Lladinaidd gyda chylch uwchben",aelig:"Llythyren æ fach Lladinaidd",ccedil:"Llythyren c fach Lladinaidd gyda sedila",egrave:"Llythyren e fach Lladinaidd gydag acen ddisgynedig",eacute:"Llythyren e fach Lladinaidd gydag acen ddyrchafedig",ecirc:"Llythyren e fach Lladinaidd gydag acen grom",
+euml:"Llythyren e fach Lladinaidd gyda didolnod",igrave:"Llythyren i fach Lladinaidd gydag acen ddisgynedig",iacute:"Llythyren i fach Lladinaidd gydag acen ddyrchafedig",icirc:"Llythyren i fach Lladinaidd gydag acen grom",iuml:"Llythyren i fach Lladinaidd gyda didolnod",eth:"Llythyren eth fach",ntilde:"Llythyren n fach Lladinaidd gyda thild",ograve:"Llythyren o fach Lladinaidd gydag acen ddisgynedig",oacute:"Llythyren o fach Lladinaidd gydag acen ddyrchafedig",ocirc:"Llythyren o fach Lladinaidd gydag acen grom",
+otilde:"Llythyren o fach Lladinaidd gyda thild",ouml:"Llythyren o fach Lladinaidd gyda didolnod",divide:"Arwydd rhannu",oslash:"Llythyren o fach Lladinaidd gyda strôc",ugrave:"Llythyren u fach Lladinaidd gydag acen ddisgynedig",uacute:"Llythyren u fach Lladinaidd gydag acen ddyrchafedig",ucirc:"Llythyren u fach Lladinaidd gydag acen grom",uuml:"Llythyren u fach Lladinaidd gyda didolnod",yacute:"Llythyren y fach Lladinaidd gydag acen ddisgynedig",thorn:"Llythyren o fach Lladinaidd gyda strôc",yuml:"Llythyren y fach Lladinaidd gyda didolnod",
+OElig:"Priflythyren cwlwm OE Lladinaidd ",oelig:"Priflythyren cwlwm oe Lladinaidd ",372:"Priflythyren W gydag acen grom",374:"Priflythyren Y gydag acen grom",373:"Llythyren w fach gydag acen grom",375:"Llythyren y fach gydag acen grom",sbquo:"Dyfynnod sengl 9-isel",8219:"Dyfynnod sengl 9-uchel cildro",bdquo:"Dyfynnod dwbl 9-isel",hellip:"Coll geiriau llorweddol",trade:"Arwydd marc masnachol",9658:"Pwyntydd du i'r dde",bull:"Bwled",rarr:"Saeth i'r dde",rArr:"Saeth ddwbl i'r dde",hArr:"Saeth ddwbl i'r chwith",
+diams:"Siwt diemwnt du",asymp:"Bron yn hafal iddo"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/da.js b/js/ckeditor/plugins/specialchar/dialogs/lang/da.js
new file mode 100644
index 0000000..e20f604
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/da.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","da",{euro:"Euro-tegn",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"Bindestreg",mdash:"Tankestreg",iexcl:"Inverted exclamation mark",cent:"Cent-tegn",pound:"Pund-tegn",curren:"Kurs-tegn",yen:"Yen-tegn",brvbar:"Brudt streg",sect:"Paragraftegn",uml:"Diaeresis",copy:"Copyright-tegn",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registreret varemærke tegn",macr:"Macron",deg:"Grad-tegn",sup2:"Superscript to",sup3:"Superscript tre",acute:"Acute accent",micro:"Mikro-tegn",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript et",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Varemærke-tegn",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/de.js b/js/ckeditor/plugins/specialchar/dialogs/lang/de.js
new file mode 100644
index 0000000..a056347
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/de.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","de",{euro:"Euro Zeichen",lsquo:"Hochkomma links",rsquo:"Hochkomma rechts",ldquo:"Anführungszeichen links",rdquo:"Anführungszeichen rechts",ndash:"kleiner Strich",mdash:"mittlerer Strich",iexcl:"invertiertes Ausrufezeichen",cent:"Cent",pound:"Pfund",curren:"Währung",yen:"Yen",brvbar:"gestrichelte Linie",sect:"§ Zeichen",uml:"Diäresis",copy:"Copyright",ordf:"Feminine ordinal Anzeige",laquo:"Nach links zeigenden Doppel-Winkel Anführungszeichen",not:"Not-Zeichen",
+reg:"Registriert",macr:"Längezeichen",deg:"Grad",sup2:"Hoch 2",sup3:"Hoch 3",acute:"Akzentzeichen ",micro:"Micro",para:"Pilcrow-Zeichen",middot:"Mittelpunkt",cedil:"Cedilla",sup1:"Hoch 1",ordm:"Männliche Ordnungszahl Anzeige",raquo:"Nach rechts zeigenden Doppel-Winkel Anführungszeichen",frac14:"ein Viertel",frac12:"Hälfte",frac34:"Dreiviertel",iquest:"Umgekehrtes Fragezeichen",Agrave:"Lateinischer Buchstabe A mit AkzentGrave",Aacute:"Lateinischer Buchstabe A mit Akutakzent",Acirc:"Lateinischer Buchstabe A mit Zirkumflex",
+Atilde:"Lateinischer Buchstabe A mit Tilde",Auml:"Lateinischer Buchstabe A mit Trema",Aring:"Lateinischer Buchstabe A mit Ring oben",AElig:"Lateinischer Buchstabe Æ",Ccedil:"Lateinischer Buchstabe C mit Cedille",Egrave:"Lateinischer Buchstabe E mit AkzentGrave",Eacute:"Lateinischer Buchstabe E mit Akutakzent",Ecirc:"Lateinischer Buchstabe E mit Zirkumflex",Euml:"Lateinischer Buchstabe E Trema",Igrave:"Lateinischer Buchstabe I mit AkzentGrave",Iacute:"Lateinischer Buchstabe I mit Akutakzent",Icirc:"Lateinischer Buchstabe I mit Zirkumflex",
+Iuml:"Lateinischer Buchstabe I mit Trema",ETH:"Lateinischer Buchstabe Eth",Ntilde:"Lateinischer Buchstabe N mit Tilde",Ograve:"Lateinischer Buchstabe O mit AkzentGrave",Oacute:"Lateinischer Buchstabe O mit Akutakzent",Ocirc:"Lateinischer Buchstabe O mit Zirkumflex",Otilde:"Lateinischer Buchstabe O mit Tilde",Ouml:"Lateinischer Buchstabe O mit Trema",times:"Multiplikation",Oslash:"Lateinischer Buchstabe O durchgestrichen",Ugrave:"Lateinischer Buchstabe U mit Akzentgrave",Uacute:"Lateinischer Buchstabe U mit Akutakzent",
+Ucirc:"Lateinischer Buchstabe U mit Zirkumflex",Uuml:"Lateinischer Buchstabe a mit Trema",Yacute:"Lateinischer Buchstabe a mit Akzent",THORN:"Lateinischer Buchstabe mit Dorn",szlig:"Kleiner lateinischer Buchstabe scharfe s",agrave:"Kleiner lateinischer Buchstabe a mit Accent grave",aacute:"Kleiner lateinischer Buchstabe a mit Akut",acirc:"Lateinischer Buchstabe a mit Zirkumflex",atilde:"Lateinischer Buchstabe a mit Tilde",auml:"Kleiner lateinischer Buchstabe a mit Trema",aring:"Kleiner lateinischer Buchstabe a mit Ring oben",
+aelig:"Lateinischer Buchstabe æ",ccedil:"Kleiner lateinischer Buchstabe c mit Cedille",egrave:"Kleiner lateinischer Buchstabe e mit Accent grave",eacute:"Kleiner lateinischer Buchstabe e mit Akut",ecirc:"Kleiner lateinischer Buchstabe e mit Zirkumflex",euml:"Kleiner lateinischer Buchstabe e mit Trema",igrave:"Kleiner lateinischer Buchstabe i mit AkzentGrave",iacute:"Kleiner lateinischer Buchstabe i mit Akzent",icirc:"Kleiner lateinischer Buchstabe i mit Zirkumflex",iuml:"Kleiner lateinischer Buchstabe i mit Trema",
+eth:"Kleiner lateinischer Buchstabe eth",ntilde:"Kleiner lateinischer Buchstabe n mit Tilde",ograve:"Kleiner lateinischer Buchstabe o mit Accent grave",oacute:"Kleiner lateinischer Buchstabe o mit Akzent",ocirc:"Kleiner lateinischer Buchstabe o mit Zirkumflex",otilde:"Lateinischer Buchstabe i mit Tilde",ouml:"Kleiner lateinischer Buchstabe o mit Trema",divide:"Divisionszeichen",oslash:"Kleiner lateinischer Buchstabe o durchgestrichen",ugrave:"Kleiner lateinischer Buchstabe u mit Accent grave",uacute:"Kleiner lateinischer Buchstabe u mit Akut",
+ucirc:"Kleiner lateinischer Buchstabe u mit Zirkumflex",uuml:"Kleiner lateinischer Buchstabe u mit Trema",yacute:"Kleiner lateinischer Buchstabe y mit Akut",thorn:"Kleiner lateinischer Buchstabe Dorn",yuml:"Kleiner lateinischer Buchstabe y mit Trema",OElig:"Lateinischer Buchstabe Ligatur OE",oelig:"Kleiner lateinischer Buchstabe Ligatur OE",372:"Lateinischer Buchstabe W mit Zirkumflex",374:"Lateinischer Buchstabe Y mit Zirkumflex",373:"Kleiner lateinischer Buchstabe w mit Zirkumflex",375:"Kleiner lateinischer Buchstabe y mit Zirkumflex",
+sbquo:"Tiefergestelltes Komma",8219:"Rumgedrehtes Komma",bdquo:"Doppeltes Anführungszeichen unten",hellip:"horizontale Auslassungspunkte",trade:"Handelszeichen",9658:"Dreickspfeil rechts",bull:"Bullet",rarr:"Pfeil rechts",rArr:"Doppelpfeil rechts",hArr:"Doppelpfeil links",diams:"Karo",asymp:"Ungefähr"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/el.js b/js/ckeditor/plugins/specialchar/dialogs/lang/el.js
new file mode 100644
index 0000000..31e69ab
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/el.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","el",{euro:"ΣÏμβολο ΕυÏÏŽ",lsquo:"ΑÏιστεÏός χαÏακτήÏας Î¼Î¿Î½Î¿Ï ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï",rsquo:"Δεξιός χαÏακτήÏας Î¼Î¿Î½Î¿Ï ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï",ldquo:"ΑÏιστεÏός χαÏακτήÏας Î´Î¹Ï€Î»Î¿Ï ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï",rdquo:"Δεξιός χαÏακτήÏας Î´Î¹Ï€Î»Î¿Ï ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï",ndash:"ΠαÏλα en",mdash:"ΠαÏλα em",iexcl:"Ανάποδο θαυμαστικό",cent:"ΣÏμβολο σεντ",pound:"ΣÏμβολο λίÏας",curren:"ΣÏμβολο συναλλαγματικής μονάδας",yen:"ΣÏμβολο Γιεν",brvbar:"Σπασμένη μπάÏα",sect:"ΣÏμβολο τμήματος",uml:"ΔιαίÏεση",copy:"ΣÏμβολο πνευματικών δικαιωμάτων",
+ordf:"Feminine ordinal indicator",laquo:"ΑÏιστεÏός χαÏακτήÏας Î´Î¹Ï€Î»Î¿Ï ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï",not:"ΣÏμβολο άÏνησης",reg:"ΣÏμβολο σημάτων κατατεθέν",macr:"ΜακÏόν",deg:"ΣÏμβολο βαθμοÏ",sup2:"Εκτεθειμένο δÏο",sup3:"Εκτεθειμένο Ï„Ïία",acute:"Οξεία",micro:"ΣÏμβολο μικÏοÏ",para:"ΣÏμβολο παÏαγÏάφου",middot:"Μέση τελεία",cedil:"ΥπογεγÏαμμένη",sup1:"Εκτεθειμένο ένα",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Γνήσιο κλάσμα ενός τετάÏτου",frac12:"Γνήσιο κλάσμα ενός δεÏτεÏου",
+frac34:"Γνήσιο κλάσμα Ï„Ïιών τετάÏτων",iquest:"Ανάποδο θαυμαστικό",Agrave:"Λατινικό κεφαλαίο γÏάμμα A με βαÏεία",Aacute:"Λατινικό κεφαλαίο γÏάμμα A με οξεία",Acirc:"Λατινικό κεφαλαίο γÏάμμα A με πεÏισπωμένη",Atilde:"Λατινικό κεφαλαίο γÏάμμα A με πεÏισπωμένη",Auml:"Λατινικό κεφαλαίο γÏάμμα A με διαλυτικά",Aring:"Λατινικό κεφαλαίο γÏάμμα A με δακτÏλιο επάνω",AElig:"Λατινικό κεφαλαίο γÏάμμα Æ",Ccedil:"Λατινικό κεφαλαίο γÏάμμα C με υπογεγÏαμμένη",Egrave:"Λατινικό κεφαλαίο γÏάμμα E με βαÏεία",Eacute:"Λατινικό κεφαλαίο γÏάμμα E με οξεία",
+Ecirc:"Λατινικό κεφαλαίο γÏάμμα Ε με πεÏισπωμένη ",Euml:"Λατινικό κεφαλαίο γÏάμμα Ε με διαλυτικά",Igrave:"Λατινικό κεφαλαίο γÏάμμα I με βαÏεία",Iacute:"Λατινικό κεφαλαίο γÏάμμα I με οξεία",Icirc:"Λατινικό κεφαλαίο γÏάμμα I με πεÏισπωμένη",Iuml:"Λατινικό κεφαλαίο γÏάμμα I με διαλυτικά ",ETH:"Λατινικό κεφαλαίο γÏάμμα Eth",Ntilde:"Λατινικό κεφαλαίο γÏάμμα N με πεÏισπωμένη",Ograve:"Λατινικό κεφαλαίο γÏάμμα O με βαÏεία",Oacute:"Λατινικό κεφαλαίο γÏάμμα O με οξεία",Ocirc:"Λατινικό κεφαλαίο γÏάμμα O με πεÏισπωμένη ",
+Otilde:"Λατινικό κεφαλαίο γÏάμμα O με πεÏισπωμένη",Ouml:"Λατινικό κεφαλαίο γÏάμμα O με διαλυτικά",times:"ΣÏμβολο πολλαπλασιασμοÏ",Oslash:"Λατινικό κεφαλαίο γÏάμμα O με μολυβιά",Ugrave:"Λατινικό κεφαλαίο γÏάμμα U με βαÏεία",Uacute:"Λατινικό κεφαλαίο γÏάμμα U με οξεία",Ucirc:"Λατινικό κεφαλαίο γÏάμμα U με πεÏισπωμένη",Uuml:"Λατινικό κεφαλαίο γÏάμμα U με διαλυτικά",Yacute:"Λατινικό κεφαλαίο γÏάμμα Y με οξεία",THORN:"Λατινικό κεφαλαίο γÏάμμα Thorn",szlig:"Λατινικό μικÏÏŒ γÏάμμα απότομο s",agrave:"Λατινικό μικÏÏŒ γÏάμμα a με βαÏεία",
+aacute:"Λατινικό μικÏÏŒ γÏάμμα a με οξεία",acirc:"Λατινικό μικÏÏŒ γÏάμμα a με πεÏισπωμένη",atilde:"Λατινικό μικÏÏŒ γÏάμμα a με πεÏισπωμένη",auml:"Λατινικό μικÏÏŒ γÏάμμα a με διαλυτικά",aring:"Λατινικό μικÏÏŒ γÏάμμα a με δακτÏλιο πάνω",aelig:"Λατινικό μικÏÏŒ γÏάμμα æ",ccedil:"Λατινικό μικÏÏŒ γÏάμμα c με υπογεγÏαμμένη",egrave:"Λατινικό μικÏÏŒ γÏάμμα ε με βαÏεία",eacute:"Λατινικό μικÏÏŒ γÏάμμα e με οξεία",ecirc:"Λατινικό μικÏÏŒ γÏάμμα e με πεÏισπωμένη",euml:"Λατινικό μικÏÏŒ γÏάμμα e με διαλυτικά",igrave:"Λατινικό μικÏÏŒ γÏάμμα i με βαÏεία",
+iacute:"Λατινικό μικÏÏŒ γÏάμμα i με οξεία",icirc:"Λατινικό μικÏÏŒ γÏάμμα i με πεÏισπωμένη",iuml:"Λατινικό μικÏÏŒ γÏάμμα i με διαλυτικά",eth:"Λατινικό μικÏÏŒ γÏάμμα eth",ntilde:"Λατινικό μικÏÏŒ γÏάμμα n με πεÏισπωμένη",ograve:"Λατινικό μικÏÏŒ γÏάμμα o με βαÏεία",oacute:"Λατινικό μικÏÏŒ γÏάμμα o με οξεία ",ocirc:"Λατινικό πεζό γÏάμμα o με πεÏισπωμένη",otilde:"Λατινικό μικÏÏŒ γÏάμμα o με πεÏισπωμένη ",ouml:"Λατινικό μικÏÏŒ γÏάμμα o με διαλυτικά",divide:"ΣÏμβολο διαίÏεσης",oslash:"Λατινικό μικÏÏŒ γÏάμμα o με πεÏισπωμένη",
+ugrave:"Λατινικό μικÏÏŒ γÏάμμα u με βαÏεία",uacute:"Λατινικό μικÏÏŒ γÏάμμα u με οξεία",ucirc:"Λατινικό μικÏÏŒ γÏάμμα u με πεÏισπωμένη",uuml:"Λατινικό μικÏÏŒ γÏάμμα u με διαλυτικά",yacute:"Λατινικό μικÏÏŒ γÏάμμα y με οξεία",thorn:"Λατινικό μικÏÏŒ γÏάμμα thorn",yuml:"Λατινικό μικÏÏŒ γÏάμμα y με διαλυτικά",OElig:"Λατινικό κεφαλαίο σÏμπλεγμα ΟΕ",oelig:"Λατινικό μικÏÏŒ σÏμπλεγμα oe",372:"Λατινικό κεφαλαίο γÏάμμα W με πεÏισπωμένη",374:"Λατινικό κεφαλαίο γÏάμμα Y με πεÏισπωμένη",373:"Λατινικό μικÏÏŒ γÏάμμα w με πεÏισπωμένη",
+375:"Λατινικό μικÏÏŒ γÏάμμα y με πεÏισπωμένη",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"ΟÏιζόντια αποσιωπητικά",trade:"ΣÏμβολο εμποÏÎ¹ÎºÎ¿Ï ÎºÎ±Ï„Î±Ï„ÎµÎ¸Î­Î½",9658:"ΜαÏÏος δείκτης που δείχνει Ï€Ïος τα δεξιά",bull:"Κουκκίδα",rarr:"Δεξί βελάκι",rArr:"Διπλό δεξί βελάκι",hArr:"Διπλό βελάκι αÏιστεÏά-δεξιά",diams:"ΜαÏÏο διαμάντι",asymp:"Σχεδόν ίσο με"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/en-gb.js b/js/ckeditor/plugins/specialchar/dialogs/lang/en-gb.js
new file mode 100644
index 0000000..08de561
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/en-gb.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","en-gb",{euro:"Euro sign",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Cent sign",pound:"Pound sign",curren:"Currency sign",yen:"Yen sign",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Copyright sign",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registered sign",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/en.js b/js/ckeditor/plugins/specialchar/dialogs/lang/en.js
new file mode 100644
index 0000000..418406d
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/en.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","en",{euro:"Euro sign",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Cent sign",pound:"Pound sign",curren:"Currency sign",yen:"Yen sign",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Copyright sign",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registered sign",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/eo.js b/js/ckeditor/plugins/specialchar/dialogs/lang/eo.js
new file mode 100644
index 0000000..c5803d5
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/eo.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","eo",{euro:"Eŭrosigno",lsquo:"Supra 6-citilo",rsquo:"Supra 9-citilo",ldquo:"Supra 66-citilo",rdquo:"Supra 99-citilo",ndash:"Streketo",mdash:"Substreko",iexcl:"Renversita krisigno",cent:"Cendosigno",pound:"Pundosigno",curren:"Monersigno",yen:"Enosigno",brvbar:"Rompita vertikala streko",sect:"Kurba paragrafo",uml:"Tremao",copy:"Kopirajtosigno",ordf:"Adjektiva numerfinaĵo",laquo:"Duobla malplio-citilo",not:"Negohoko",reg:"Registrita marko",macr:"Superstreko",deg:"Gradosigno",
+sup2:"Supra indico 2",sup3:"Supra indico 3",acute:"Dekstra korno",micro:"Mikrosigno",para:"Rekta paragrafo",middot:"Meza punkto",cedil:"Zoeto",sup1:"Supra indico 1",ordm:"Substantiva numerfinaĵo",raquo:"Duobla plio-citilo",frac14:"Kvaronosigno",frac12:"Duonosigno",frac34:"Trikvaronosigno",iquest:"renversita demandosigno",Agrave:"Latina ĉeflitero A kun liva korno",Aacute:"Latina ĉeflitero A kun dekstra korno",Acirc:"Latina ĉeflitero A kun ĉapelo",Atilde:"Latina ĉeflitero A kun tildo",Auml:"Latina ĉeflitero A kun tremao",
+Aring:"Latina ĉeflitero A kun superringo",AElig:"Latina ĉeflitera ligaturo Æ",Ccedil:"Latina ĉeflitero C kun zoeto",Egrave:"Latina ĉeflitero E kun liva korno",Eacute:"Latina ĉeflitero E kun dekstra korno",Ecirc:"Latina ĉeflitero E kun ĉapelo",Euml:"Latina ĉeflitero E kun tremao",Igrave:"Latina ĉeflitero I kun liva korno",Iacute:"Latina ĉeflitero I kun dekstra korno",Icirc:"Latina ĉeflitero I kun ĉapelo",Iuml:"Latina ĉeflitero I kun tremao",ETH:"Latina ĉeflitero islanda edo",Ntilde:"Latina ĉeflitero N kun tildo",
+Ograve:"Latina ĉeflitero O kun liva korno",Oacute:"Latina ĉeflitero O kun dekstra korno",Ocirc:"Latina ĉeflitero O kun ĉapelo",Otilde:"Latina ĉeflitero O kun tildo",Ouml:"Latina ĉeflitero O kun tremao",times:"Multipliko",Oslash:"Latina ĉeflitero O trastrekita",Ugrave:"Latina ĉeflitero U kun liva korno",Uacute:"Latina ĉeflitero U kun dekstra korno",Ucirc:"Latina ĉeflitero U kun ĉapelo",Uuml:"Latina ĉeflitero U kun tremao",Yacute:"Latina ĉeflitero Y kun dekstra korno",THORN:"Latina ĉeflitero islanda dorno",
+szlig:"Latina etlitero germana sozo (akra s)",agrave:"Latina etlitero a kun liva korno",aacute:"Latina etlitero a kun dekstra korno",acirc:"Latina etlitero a kun ĉapelo",atilde:"Latina etlitero a kun tildo",auml:"Latina etlitero a kun tremao",aring:"Latina etlitero a kun superringo",aelig:"Latina etlitera ligaturo æ",ccedil:"Latina etlitero c kun zoeto",egrave:"Latina etlitero e kun liva korno",eacute:"Latina etlitero e kun dekstra korno",ecirc:"Latina etlitero e kun ĉapelo",euml:"Latina etlitero e kun tremao",
+igrave:"Latina etlitero i kun liva korno",iacute:"Latina etlitero i kun dekstra korno",icirc:"Latina etlitero i kun ĉapelo",iuml:"Latina etlitero i kun tremao",eth:"Latina etlitero islanda edo",ntilde:"Latina etlitero n kun tildo",ograve:"Latina etlitero o kun liva korno",oacute:"Latina etlitero o kun dekstra korno",ocirc:"Latina etlitero o kun ĉapelo",otilde:"Latina etlitero o kun tildo",ouml:"Latina etlitero o kun tremao",divide:"Dividosigno",oslash:"Latina etlitero o trastrekita",ugrave:"Latina etlitero u kun liva korno",
+uacute:"Latina etlitero u kun dekstra korno",ucirc:"Latina etlitero u kun ĉapelo",uuml:"Latina etlitero u kun tremao",yacute:"Latina etlitero y kun dekstra korno",thorn:"Latina etlitero islanda dorno",yuml:"Latina etlitero y kun tremao",OElig:"Latina ĉeflitera ligaturo Œ",oelig:"Latina etlitera ligaturo œ",372:"Latina ĉeflitero W kun ĉapelo",374:"Latina ĉeflitero Y kun ĉapelo",373:"Latina etlitero w kun ĉapelo",375:"Latina etlitero y kun ĉapelo",sbquo:"Suba 9-citilo",8219:"Supra renversita 9-citilo",
+bdquo:"Suba 99-citilo",hellip:"Tripunkto",trade:"Varmarka signo",9658:"Nigra sago dekstren",bull:"Bulmarko",rarr:"Sago dekstren",rArr:"Duobla sago dekstren",hArr:"Duobla sago maldekstren",diams:"Nigra kvadrato",asymp:"PreskaÅ­ egala"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/es.js b/js/ckeditor/plugins/specialchar/dialogs/lang/es.js
new file mode 100644
index 0000000..e91f1a0
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/es.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","es",{euro:"Símbolo de euro",lsquo:"Comilla simple izquierda",rsquo:"Comilla simple derecha",ldquo:"Comilla doble izquierda",rdquo:"Comilla doble derecha",ndash:"Guión corto",mdash:"Guión medio largo",iexcl:"Signo de admiración invertido",cent:"Símbolo centavo",pound:"Símbolo libra",curren:"Símbolo moneda",yen:"Símbolo yen",brvbar:"Barra vertical rota",sect:"Símbolo sección",uml:"Diéresis",copy:"Signo de derechos de autor",ordf:"Indicador ordinal femenino",laquo:"Abre comillas angulares",
+not:"Signo negación",reg:"Signo de marca registrada",macr:"Guión alto",deg:"Signo de grado",sup2:"Superíndice dos",sup3:"Superíndice tres",acute:"Acento agudo",micro:"Signo micro",para:"Signo de pi",middot:"Punto medio",cedil:"Cedilla",sup1:"Superíndice uno",ordm:"Indicador orginal masculino",raquo:"Cierra comillas angulares",frac14:"Fracción ordinaria de un quarto",frac12:"Fracción ordinaria de una mitad",frac34:"Fracción ordinaria de tres cuartos",iquest:"Signo de interrogación invertido",Agrave:"Letra A latina mayúscula con acento grave",
+Aacute:"Letra A latina mayúscula con acento agudo",Acirc:"Letra A latina mayúscula con acento circunflejo",Atilde:"Letra A latina mayúscula con tilde",Auml:"Letra A latina mayúscula con diéresis",Aring:"Letra A latina mayúscula con aro arriba",AElig:"Letra Æ latina mayúscula",Ccedil:"Letra C latina mayúscula con cedilla",Egrave:"Letra E latina mayúscula con acento grave",Eacute:"Letra E latina mayúscula con acento agudo",Ecirc:"Letra E latina mayúscula con acento circunflejo",Euml:"Letra E latina mayúscula con diéresis",
+Igrave:"Letra I latina mayúscula con acento grave",Iacute:"Letra I latina mayúscula con acento agudo",Icirc:"Letra I latina mayúscula con acento circunflejo",Iuml:"Letra I latina mayúscula con diéresis",ETH:"Letra Eth latina mayúscula",Ntilde:"Letra N latina mayúscula con tilde",Ograve:"Letra O latina mayúscula con acento grave",Oacute:"Letra O latina mayúscula con acento agudo",Ocirc:"Letra O latina mayúscula con acento circunflejo",Otilde:"Letra O latina mayúscula con tilde",Ouml:"Letra O latina mayúscula con diéresis",
+times:"Signo de multiplicación",Oslash:"Letra O latina mayúscula con barra inclinada",Ugrave:"Letra U latina mayúscula con acento grave",Uacute:"Letra U latina mayúscula con acento agudo",Ucirc:"Letra U latina mayúscula con acento circunflejo",Uuml:"Letra U latina mayúscula con diéresis",Yacute:"Letra Y latina mayúscula con acento agudo",THORN:"Letra Thorn latina mayúscula",szlig:"Letra s latina fuerte pequeña",agrave:"Letra a latina pequeña con acento grave",aacute:"Letra a latina pequeña con acento agudo",
+acirc:"Letra a latina pequeña con acento circunflejo",atilde:"Letra a latina pequeña con tilde",auml:"Letra a latina pequeña con diéresis",aring:"Letra a latina pequeña con aro arriba",aelig:"Letra æ latina pequeña",ccedil:"Letra c latina pequeña con cedilla",egrave:"Letra e latina pequeña con acento grave",eacute:"Letra e latina pequeña con acento agudo",ecirc:"Letra e latina pequeña con acento circunflejo",euml:"Letra e latina pequeña con diéresis",igrave:"Letra i latina pequeña con acento grave",
+iacute:"Letra i latina pequeña con acento agudo",icirc:"Letra i latina pequeña con acento circunflejo",iuml:"Letra i latina pequeña con diéresis",eth:"Letra eth latina pequeña",ntilde:"Letra n latina pequeña con tilde",ograve:"Letra o latina pequeña con acento grave",oacute:"Letra o latina pequeña con acento agudo",ocirc:"Letra o latina pequeña con acento circunflejo",otilde:"Letra o latina pequeña con tilde",ouml:"Letra o latina pequeña con diéresis",divide:"Signo de división",oslash:"Letra o latina minúscula con barra inclinada",
+ugrave:"Letra u latina pequeña con acento grave",uacute:"Letra u latina pequeña con acento agudo",ucirc:"Letra u latina pequeña con acento circunflejo",uuml:"Letra u latina pequeña con diéresis",yacute:"Letra u latina pequeña con acento agudo",thorn:"Letra thorn latina minúscula",yuml:"Letra y latina pequeña con diéresis",OElig:"Diptongo OE latino en mayúscula",oelig:"Diptongo oe latino en minúscula",372:"Letra W latina mayúscula con acento circunflejo",374:"Letra Y latina mayúscula con acento circunflejo",
+373:"Letra w latina pequeña con acento circunflejo",375:"Letra y latina pequeña con acento circunflejo",sbquo:"Comilla simple baja-9",8219:"Comilla simple alta invertida-9",bdquo:"Comillas dobles bajas-9",hellip:"Puntos suspensivos horizontales",trade:"Signo de marca registrada",9658:"Apuntador negro apuntando a la derecha",bull:"Viñeta",rarr:"Flecha a la derecha",rArr:"Flecha doble a la derecha",hArr:"Flecha izquierda derecha doble",diams:"Diamante negro",asymp:"Casi igual a"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/et.js b/js/ckeditor/plugins/specialchar/dialogs/lang/et.js
new file mode 100644
index 0000000..2a20883
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/et.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","et",{euro:"Euromärk",lsquo:"Alustav ühekordne jutumärk",rsquo:"Lõpetav ühekordne jutumärk",ldquo:"Alustav kahekordne jutumärk",rdquo:"Lõpetav kahekordne jutumärk",ndash:"Enn-kriips",mdash:"Emm-kriips",iexcl:"Pööratud hüüumärk",cent:"Sendimärk",pound:"Naela märk",curren:"Valuutamärk",yen:"Jeeni märk",brvbar:"Katkestatud kriips",sect:"Lõigu märk",uml:"Täpid",copy:"Autoriõiguse märk",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Ei-märk",reg:"Registered sign",macr:"Macron",deg:"Kraadimärk",sup2:"Ülaindeks kaks",sup3:"Ülaindeks kolm",acute:"Acute accent",micro:"Mikro-märk",para:"Pilcrow sign",middot:"Keskpunkt",cedil:"Cedilla",sup1:"Ülaindeks üks",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Ladina suur A tildega",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Täppidega ladina suur O",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Kandilise katusega suur ladina U",Uuml:"Täppidega ladina suur U",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Ladina väike terav s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Kandilise katusega ladina väike a",atilde:"Tildega ladina väike a",auml:"Täppidega ladina väike a",aring:"Latin small letter a with ring above",
+aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",ntilde:"Latin small letter n with tilde",
+ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Jagamismärk",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",
+thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",trade:"Kaubamärgi märk",9658:"Black right-pointing pointer",
+bull:"Kuul",rarr:"Nool paremale",rArr:"Topeltnool paremale",hArr:"Topeltnool vasakule",diams:"Black diamond suit",asymp:"Ligikaudu võrdne"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/fa.js b/js/ckeditor/plugins/specialchar/dialogs/lang/fa.js
new file mode 100644
index 0000000..c3efb3c
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/fa.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","fa",{euro:"نشان یورو",lsquo:"علامت نقل قول تکی چپ",rsquo:"علامت نقل قول تکی راست",ldquo:"علامت نقل قول دوتایی چپ",rdquo:"علامت نقل قول دوتایی راست",ndash:"خط تیره En",mdash:"خط تیره Em",iexcl:"علامت تعجب وارونه",cent:"نشان سنت",pound:"نشان پوند",curren:"نشان ارز",yen:"نشان ین",brvbar:"نوار شکسته",sect:"نشان بخش",uml:"نشان سواگیری",copy:"نشان کپی رایت",ordf:"شاخص ترتیبی مونث",laquo:"اشاره چپ مکرر برای زاویه علامت نقل قول",not:"نشان ثبت نشده",reg:"نشان ثبت شده",
+macr:"نشان خط بالای حرÙ",deg:"نشان درجه",sup2:"بالانویس دو",sup3:"بالانویس سه",acute:"لهجه غلیظ",micro:"نشان مایکرو",para:"نشان محل بند",middot:"نقطه میانی",cedil:"سدیل",sup1:"بالانویس 1",ordm:"شاخص ترتیبی مذکر",raquo:"نشان زاویه‌دار دوتایی نقل قول راست چین",frac14:"واحد عامیانه 1/4",frac12:"واحد عامینه نصÙ",frac34:"واحد عامیانه 3/4",iquest:"علامت سوال معکوس",Agrave:"حر٠A بزرگ لاتین با تلÙظ غلیظ",Aacute:"حر٠A بزرگ لاتین با تلÙظ شدید",Acirc:"حر٠A بزرگ لاتین با دور",Atilde:"حر٠A بزرگ لاتین با صدای کامی",
+Auml:"حر٠A بزرگ لاتین با نشان سواگیری",Aring:"حر٠A بزرگ لاتین با حلقه بالا",AElig:"حر٠Æ بزرگ لاتین",Ccedil:"حر٠C بزرگ لاتین با نشان سواگیری",Egrave:"حر٠E بزرگ لاتین با تلÙظ درشت",Eacute:"حر٠E بزرگ لاتین با تلÙظ زیر",Ecirc:"حر٠E بزرگ لاتین با خمان",Euml:"حر٠E بزرگ لاتین با نشان سواگیری",Igrave:"حر٠I بزرگ لاتین با تلÙظ درشت",Iacute:"حر٠I بزرگ لاتین با تلÙظ ریز",Icirc:"حر٠I بزرگ لاتین با خمان",Iuml:"حر٠I بزرگ لاتین با نشان سواگیری",ETH:"حر٠لاتین بزرگ واکه ترتیبی",Ntilde:"حر٠N بزرگ لاتین با مد",
+Ograve:"حر٠O بزرگ لاتین با تلÙظ درشت",Oacute:"حر٠O بزرگ لاتین با تلÙظ ریز",Ocirc:"حر٠O بزرگ لاتین با خمان",Otilde:"حر٠O بزرگ لاتین با مد",Ouml:"حر٠O بزرگ لاتین با نشان سواگیری",times:"نشان ضربدر",Oslash:"حر٠O بزرگ لاتین با میان خط",Ugrave:"حر٠U بزرگ لاتین با تلÙظ درشت",Uacute:"حر٠U بزرگ لاتین با تلÙظ ریز",Ucirc:"حر٠U بزرگ لاتین با خمان",Uuml:"حر٠U بزرگ لاتین با نشان سواگیری",Yacute:"حر٠Y بزرگ لاتین با تلÙظ ریز",THORN:"حر٠بزرگ لاتین خاردار",szlig:"حر٠کوچک لاتین شارپ s",agrave:"حر٠a Ú©ÙˆÚ†Ú© لاتین با تلÙظ درشت",
+aacute:"حر٠a Ú©ÙˆÚ†Ú© لاتین با تلÙظ ریز",acirc:"حر٠a Ú©ÙˆÚ†Ú© لاتین با خمان",atilde:"حر٠a Ú©ÙˆÚ†Ú© لاتین با صدای کامی",auml:"حر٠a Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",aring:"حر٠a Ú©ÙˆÚ†Ú© لاتین گوشواره دار",aelig:"حر٠کوچک لاتین æ",ccedil:"حر٠c Ú©ÙˆÚ†Ú© لاتین با نشان سدیل",egrave:"حر٠e Ú©ÙˆÚ†Ú© لاتین با تلÙظ درشت",eacute:"حر٠e Ú©ÙˆÚ†Ú© لاتین با تلÙظ ریز",ecirc:"حر٠e Ú©ÙˆÚ†Ú© لاتین با خمان",euml:"حر٠e Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",igrave:"حر٠i Ú©ÙˆÚ†Ú© لاتین با تلÙظ درشت",iacute:"حر٠i Ú©ÙˆÚ†Ú© لاتین با تلÙظ ریز",icirc:"حر٠i Ú©ÙˆÚ†Ú© لاتین با خمان",
+iuml:"حر٠i Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",eth:"حر٠کوچک لاتین eth",ntilde:"حر٠n Ú©ÙˆÚ†Ú© لاتین با صدای کامی",ograve:"حر٠o Ú©ÙˆÚ†Ú© لاتین با تلÙظ درشت",oacute:"حر٠o Ú©ÙˆÚ†Ú© لاتین با تلÙظ زیر",ocirc:"حر٠o Ú©ÙˆÚ†Ú© لاتین با خمان",otilde:"حر٠o Ú©ÙˆÚ†Ú© لاتین با صدای کامی",ouml:"حر٠o Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",divide:"نشان بخش",oslash:"حر٠o Ú©ÙˆÚ†Ú© لاتین با میان خط",ugrave:"حر٠u Ú©ÙˆÚ†Ú© لاتین با تلÙظ درشت",uacute:"حر٠u Ú©ÙˆÚ†Ú© لاتین با تلÙظ ریز",ucirc:"حر٠u Ú©ÙˆÚ†Ú© لاتین با خمان",uuml:"حر٠u Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",yacute:"حر٠y Ú©ÙˆÚ†Ú© لاتین با تلÙظ ریز",
+thorn:"حر٠کوچک لاتین خاردار",yuml:"حر٠y Ú©ÙˆÚ†Ú© لاتین با نشان سواگیری",OElig:"بند بزرگ لاتین OE",oelig:"بند Ú©ÙˆÚ†Ú© لاتین oe",372:"حر٠W بزرگ لاتین با خمان",374:"حر٠Y بزرگ لاتین با خمان",373:"حر٠w Ú©ÙˆÚ†Ú© لاتین با خمان",375:"حر٠y Ú©ÙˆÚ†Ú© لاتین با خمان",sbquo:"نشان نقل قول تکی زیر-9",8219:"نشان نقل قول تکی high-reversed-9",bdquo:"نقل قول دوتایی پایین-9",hellip:"حذ٠اÙÙ‚ÛŒ",trade:"نشان تجاری",9658:"نشانگر سیاه جهت راست",bull:"گلوله",rarr:"Ùلش راست",rArr:"Ùلش دوتایی راست",hArr:"Ùلش دوتایی Ú†Ù¾ راست",diams:"نشان الماس سیاه",
+asymp:"تقریبا برابر با"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/fi.js b/js/ckeditor/plugins/specialchar/dialogs/lang/fi.js
new file mode 100644
index 0000000..79f4a7d
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/fi.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","fi",{euro:"Euron merkki",lsquo:"Vasen yksittäinen lainausmerkki",rsquo:"Oikea yksittäinen lainausmerkki",ldquo:"Vasen kaksoislainausmerkki",rdquo:"Oikea kaksoislainausmerkki",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Sentin merkki",pound:"Punnan merkki",curren:"Valuuttamerkki",yen:"Yenin merkki",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Copyright sign",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Rekisteröity merkki",macr:"Macron",deg:"Asteen merkki",sup2:"Yläindeksi kaksi",sup3:"Yläindeksi kolme",acute:"Acute accent",micro:"Mikron merkki",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Yläindeksi yksi",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Ylösalaisin oleva kysymysmerkki",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Kertomerkki",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Jakomerkki",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Tavaramerkki merkki",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Nuoli oikealle",rArr:"Kaksoisnuoli oikealle",hArr:"Kaksoisnuoli oikealle ja vasemmalle",diams:"Black diamond suit",asymp:"Noin"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/fr-ca.js b/js/ckeditor/plugins/specialchar/dialogs/lang/fr-ca.js
new file mode 100644
index 0000000..24a0d0d
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/fr-ca.js
@@ -0,0 +1,10 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","fr-ca",{euro:"Symbole Euro",lsquo:"Guillemet simple ouvrant",rsquo:"Guillemet simple fermant",ldquo:"Guillemet double ouvrant",rdquo:"Guillemet double fermant",ndash:"Tiret haut",mdash:"Tiret",iexcl:"Point d'exclamation inversé",cent:"Symbole de cent",pound:"Symbole de Livre Sterling",curren:"Symbole monétaire",yen:"Symbole du Yen",brvbar:"Barre scindée",sect:"Symbole de section",uml:"Tréma",copy:"Symbole de copyright",ordf:"Indicateur ordinal féminin",laquo:"Guillemet français ouvrant",
+not:"Indicateur de négation",reg:"Symbole de marque déposée",macr:"Macron",deg:"Degré",sup2:"Exposant 2",sup3:"Exposant 3",acute:"Accent aigüe",micro:"Symbole micro",para:"Paragraphe",middot:"Point médian",cedil:"Cédille",sup1:"Exposant 1",ordm:"Indicateur ordinal masculin",raquo:"Guillemet français fermant",frac14:"Un quart",frac12:"Une demi",frac34:"Trois quart",iquest:"Point d'interrogation inversé",Agrave:"A accent grave",Aacute:"A accent aigüe",Acirc:"A circonflexe",Atilde:"A tilde",Auml:"A tréma",
+Aring:"A avec un rond au dessus",AElig:"Æ majuscule",Ccedil:"C cédille",Egrave:"E accent grave",Eacute:"E accent aigüe",Ecirc:"E accent circonflexe",Euml:"E tréma",Igrave:"I accent grave",Iacute:"I accent aigüe",Icirc:"I accent circonflexe",Iuml:"I tréma",ETH:"Lettre majuscule islandaise ED",Ntilde:"N tilde",Ograve:"O accent grave",Oacute:"O accent aigüe",Ocirc:"O accent circonflexe",Otilde:"O tilde",Ouml:"O tréma",times:"Symbole de multiplication",Oslash:"O barré",Ugrave:"U accent grave",Uacute:"U accent aigüe",
+Ucirc:"U accent circonflexe",Uuml:"U tréma",Yacute:"Y accent aigüe",THORN:"Lettre islandaise Thorn majuscule",szlig:"Lettre minuscule allemande s dur",agrave:"a accent grave",aacute:"a accent aigüe",acirc:"a accent circonflexe",atilde:"a tilde",auml:"a tréma",aring:"a avec un cercle au dessus",aelig:"æ",ccedil:"c cédille",egrave:"e accent grave",eacute:"e accent aigüe",ecirc:"e accent circonflexe",euml:"e tréma",igrave:"i accent grave",iacute:"i accent aigüe",icirc:"i accent circonflexe",iuml:"i tréma",
+eth:"Lettre minuscule islandaise ED",ntilde:"n tilde",ograve:"o accent grave",oacute:"o accent aigüe",ocirc:"O accent circonflexe",otilde:"O tilde",ouml:"O tréma",divide:"Symbole de division",oslash:"o barré",ugrave:"u accent grave",uacute:"u accent aigüe",ucirc:"u accent circonflexe",uuml:"u tréma",yacute:"y accent aigüe",thorn:"Lettre islandaise thorn minuscule",yuml:"y tréma",OElig:"ligature majuscule latine Œ",oelig:"ligature minuscule latine œ",372:"W accent circonflexe",374:"Y accent circonflexe",
+373:"w accent circonflexe",375:"y accent circonflexe",sbquo:"Guillemet simple fermant",8219:"Guillemet-virgule supérieur culbuté",bdquo:"Guillemet-virgule double inférieur",hellip:"Points de suspension",trade:"Symbole de marque déposée",9658:"Flèche noire pointant vers la droite",bull:"Puce",rarr:"Flèche vers la droite",rArr:"Flèche double vers la droite",hArr:"Flèche double vers la gauche",diams:"Carreau",asymp:"Presque égal"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/fr.js b/js/ckeditor/plugins/specialchar/dialogs/lang/fr.js
new file mode 100644
index 0000000..b224b1e
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/fr.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","fr",{euro:"Symbole Euro",lsquo:"Guillemet simple ouvrant",rsquo:"Guillemet simple fermant",ldquo:"Guillemet double ouvrant",rdquo:"Guillemet double fermant",ndash:"Tiret haut",mdash:"Tiret cadratin",iexcl:"Point d'exclamation inversé",cent:"Symbole Cent",pound:"Symbole Livre Sterling",curren:"Symbole monétaire",yen:"Symbole Yen",brvbar:"Barre verticale scindée",sect:"Section",uml:"Tréma",copy:"Symbole Copyright",ordf:"Indicateur ordinal féminin",laquo:"Guillemet français ouvrant",
+not:"Crochet de négation",reg:"Marque déposée",macr:"Macron",deg:"Degré",sup2:"Exposant 2",sup3:"\\tExposant 3",acute:"Accent aigu",micro:"Omicron",para:"Paragraphe",middot:"Point médian",cedil:"Cédille",sup1:"\\tExposant 1",ordm:"Indicateur ordinal masculin",raquo:"Guillemet français fermant",frac14:"Un quart",frac12:"Un demi",frac34:"Trois quarts",iquest:"Point d'interrogation inversé",Agrave:"A majuscule accent grave",Aacute:"A majuscule accent aigu",Acirc:"A majuscule accent circonflexe",Atilde:"A majuscule avec caron",
+Auml:"A majuscule tréma",Aring:"A majuscule avec un rond au-dessus",AElig:"Æ majuscule ligaturés",Ccedil:"C majuscule cédille",Egrave:"E majuscule accent grave",Eacute:"E majuscule accent aigu",Ecirc:"E majuscule accent circonflexe",Euml:"E majuscule tréma",Igrave:"I majuscule accent grave",Iacute:"I majuscule accent aigu",Icirc:"I majuscule accent circonflexe",Iuml:"I majuscule tréma",ETH:"Lettre majuscule islandaise ED",Ntilde:"N majuscule avec caron",Ograve:"O majuscule accent grave",Oacute:"O majuscule accent aigu",
+Ocirc:"O majuscule accent circonflexe",Otilde:"O majuscule avec caron",Ouml:"O majuscule tréma",times:"Multiplication",Oslash:"O majuscule barré",Ugrave:"U majuscule accent grave",Uacute:"U majuscule accent aigu",Ucirc:"U majuscule accent circonflexe",Uuml:"U majuscule tréma",Yacute:"Y majuscule accent aigu",THORN:"Lettre islandaise Thorn majuscule",szlig:"Lettre minuscule allemande s dur",agrave:"a minuscule accent grave",aacute:"a minuscule accent aigu",acirc:"a minuscule accent circonflexe",atilde:"a minuscule avec caron",
+auml:"a minuscule tréma",aring:"a minuscule avec un rond au-dessus",aelig:"æ minuscule ligaturés",ccedil:"c minuscule cédille",egrave:"e minuscule accent grave",eacute:"e minuscule accent aigu",ecirc:"e minuscule accent circonflexe",euml:"e minuscule tréma",igrave:"i minuscule accent grave",iacute:"i minuscule accent aigu",icirc:"i minuscule accent circonflexe",iuml:"i minuscule tréma",eth:"Lettre minuscule islandaise ED",ntilde:"n minuscule avec caron",ograve:"o minuscule accent grave",oacute:"o minuscule accent aigu",
+ocirc:"o minuscule accent circonflexe",otilde:"o minuscule avec caron",ouml:"o minuscule tréma",divide:"Division",oslash:"o minuscule barré",ugrave:"u minuscule accent grave",uacute:"u minuscule accent aigu",ucirc:"u minuscule accent circonflexe",uuml:"u minuscule tréma",yacute:"y minuscule accent aigu",thorn:"Lettre islandaise thorn minuscule",yuml:"y minuscule tréma",OElig:"ligature majuscule latine Œ",oelig:"ligature minuscule latine œ",372:"W majuscule accent circonflexe",374:"Y majuscule accent circonflexe",
+373:"w minuscule accent circonflexe",375:"y minuscule accent circonflexe",sbquo:"Guillemet simple fermant (anglais)",8219:"Guillemet-virgule supérieur culbuté",bdquo:"Guillemet-virgule double inférieur",hellip:"Points de suspension",trade:"Marque commerciale (trade mark)",9658:"Flèche noire pointant vers la droite",bull:"Gros point médian",rarr:"Flèche vers la droite",rArr:"Double flèche vers la droite",hArr:"Double flèche vers la gauche",diams:"Carreau noir",asymp:"Presque égal"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/gl.js b/js/ckeditor/plugins/specialchar/dialogs/lang/gl.js
new file mode 100644
index 0000000..797ecc5
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/gl.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","gl",{euro:"Símbolo do euro",lsquo:"Comiña simple esquerda",rsquo:"Comiña simple dereita",ldquo:"Comiñas dobres esquerda",rdquo:"Comiñas dobres dereita",ndash:"Guión",mdash:"Raia",iexcl:"Signo de admiración invertido",cent:"Símbolo do centavo",pound:"Símbolo da libra",curren:"Símbolo de moeda",yen:"Símbolo do yen",brvbar:"Barra vertical rota",sect:"Símbolo de sección",uml:"Diérese",copy:"Símbolo de dereitos de autoría",ordf:"Indicador ordinal feminino",laquo:"Comiñas latinas, apertura",
+not:"Signo negación",reg:"Símbolo de marca rexistrada",macr:"Guión alto",deg:"Signo de grao",sup2:"Superíndice dous",sup3:"Superíndice tres",acute:"Acento agudo",micro:"Signo de micro",para:"Signo de pi",middot:"Punto medio",cedil:"Cedilla",sup1:"Superíndice un",ordm:"Indicador ordinal masculino",raquo:"Comiñas latinas, peche",frac14:"Fracción ordinaria de un cuarto",frac12:"Fracción ordinaria de un medio",frac34:"Fracción ordinaria de tres cuartos",iquest:"Signo de interrogación invertido",Agrave:"Letra A latina maiúscula con acento grave",
+Aacute:"Letra A latina maiúscula con acento agudo",Acirc:"Letra A latina maiúscula con acento circunflexo",Atilde:"Letra A latina maiúscula con til",Auml:"Letra A latina maiúscula con diérese",Aring:"Letra A latina maiúscula con aro enriba",AElig:"Letra Æ latina maiúscula",Ccedil:"Letra C latina maiúscula con cedilla",Egrave:"Letra E latina maiúscula con acento grave",Eacute:"Letra E latina maiúscula con acento agudo",Ecirc:"Letra E latina maiúscula con acento circunflexo",Euml:"Letra E latina maiúscula con diérese",
+Igrave:"Letra I latina maiúscula con acento grave",Iacute:"Letra I latina maiúscula con acento agudo",Icirc:"Letra I latina maiúscula con acento circunflexo",Iuml:"Letra I latina maiúscula con diérese",ETH:"Letra Ed latina maiúscula",Ntilde:"Letra N latina maiúscula con til",Ograve:"Letra O latina maiúscula con acento grave",Oacute:"Letra O latina maiúscula con acento agudo",Ocirc:"Letra O latina maiúscula con acento circunflexo",Otilde:"Letra O latina maiúscula con til",Ouml:"Letra O latina maiúscula con diérese",
+times:"Signo de multiplicación",Oslash:"Letra O latina maiúscula con barra transversal",Ugrave:"Letra U latina maiúscula con acento grave",Uacute:"Letra U latina maiúscula con acento agudo",Ucirc:"Letra U latina maiúscula con acento circunflexo",Uuml:"Letra U latina maiúscula con diérese",Yacute:"Letra Y latina maiúscula con acento agudo",THORN:"Letra Thorn latina maiúscula",szlig:"Letra s latina forte minúscula",agrave:"Letra a latina minúscula con acento grave",aacute:"Letra a latina minúscula con acento agudo",
+acirc:"Letra a latina minúscula con acento circunflexo",atilde:"Letra a latina minúscula con til",auml:"Letra a latina minúscula con diérese",aring:"Letra a latina minúscula con aro enriba",aelig:"Letra æ latina minúscula",ccedil:"Letra c latina minúscula con cedilla",egrave:"Letra e latina minúscula con acento grave",eacute:"Letra e latina minúscula con acento agudo",ecirc:"Letra e latina minúscula con acento circunflexo",euml:"Letra e latina minúscula con diérese",igrave:"Letra i latina minúscula con acento grave",
+iacute:"Letra i latina minúscula con acento agudo",icirc:"Letra i latina minúscula con acento circunflexo",iuml:"Letra i latina minúscula con diérese",eth:"Letra ed latina minúscula",ntilde:"Letra n latina minúscula con til",ograve:"Letra o latina minúscula con acento grave",oacute:"Letra o latina minúscula con acento agudo",ocirc:"Letra o latina minúscula con acento circunflexo",otilde:"Letra o latina minúscula con til",ouml:"Letra o latina minúscula con diérese",divide:"Signo de división",oslash:"Letra o latina minúscula con barra transversal",
+ugrave:"Letra u latina minúscula con acento grave",uacute:"Letra u latina minúscula con acento agudo",ucirc:"Letra u latina minúscula con acento circunflexo",uuml:"Letra u latina minúscula con diérese",yacute:"Letra y latina minúscula con acento agudo",thorn:"Letra Thorn latina minúscula",yuml:"Letra y latina minúscula con diérese",OElig:"Ligadura OE latina maiúscula",oelig:"Ligadura oe latina minúscula",372:"Letra W latina maiúscula con acento circunflexo",374:"Letra Y latina maiúscula con acento circunflexo",
+373:"Letra w latina minúscula con acento circunflexo",375:"Letra y latina minúscula con acento circunflexo",sbquo:"Comiña simple baixa, de apertura",8219:"Comiña simple alta, de peche",bdquo:"Comiñas dobres baixas, de apertura",hellip:"Elipse, puntos suspensivos",trade:"Signo de marca rexistrada",9658:"Apuntador negro apuntando á dereita",bull:"Viñeta",rarr:"Frecha á dereita",rArr:"Frecha dobre á dereita",hArr:"Frecha dobre da esquerda á dereita",diams:"Diamante negro",asymp:"Case igual a"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/he.js b/js/ckeditor/plugins/specialchar/dialogs/lang/he.js
new file mode 100644
index 0000000..7593589
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/he.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","he",{euro:"יורו",lsquo:"סימן ציטוט יחיד שמ×לי",rsquo:"סימן ציטוט יחיד ימני",ldquo:"סימן ציטוט כפול שמ×לי",rdquo:"סימן ציטוט כפול ימני",ndash:"קו מפריד קצר",mdash:"קו מפריד ×רוך",iexcl:"סימן קרי××” הפוך",cent:"סנט",pound:"פ×ונד",curren:"מטבע",yen:"ין",brvbar:"קו שבור",sect:"סימן מקטע",uml:"שתי נקודות ×ופקיות (Diaeresis)",copy:"סימן זכויות ×™×•×¦×¨×™× (Copyright)",ordf:"סימן ×ורדינ×לי נקבי",laquo:"סימן ציטוט זווית כפולה לשמ×ל",not:"סימן שלילה מתמטי",reg:"סימן רשו×",
+macr:"מקרון (×”×’×™×” ×רוכה)",deg:"מעלות",sup2:"2 בכתיב עילי",sup3:"3 בכתיב עילי",acute:"סימן דגוש (Acute)",micro:"מיקרו",para:"סימון פסקה",middot:"נקודה ×מצעית",cedil:"סדיליה",sup1:"1 בכתיב עילי",ordm:"סימן ×ורדינ×לי זכרי",raquo:"סימן ציטוט זווית כפולה לימין",frac14:"רבע בשבר פשוט",frac12:"חצי בשבר פשוט",frac34:"שלושה ×¨×‘×¢×™× ×‘×©×‘×¨ פשוט",iquest:"סימן ש×לה הפוך",Agrave:"×ות לטינית A ×¢× ×’×¨×© (Grave)",Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",
+Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"×ות לטינית Æ גדולה",Ccedil:"Latin capital letter C with cedilla",Egrave:"×ות לטינית E ×¢× ×’×¨×© (Grave)",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"×ות לטינית I ×¢× ×’×¨×© (Grave)",Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",
+ETH:"×ות לטינית Eth גדולה",Ntilde:"Latin capital letter N with tilde",Ograve:"×ות לטינית O ×¢× ×’×¨×© (Grave)",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"סימן כפל",Oslash:"Latin capital letter O with stroke",Ugrave:"×ות לטינית U ×¢× ×’×¨×© (Grave)",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",
+Yacute:"Latin capital letter Y with acute accent",THORN:"×ות לטינית Thorn גדולה",szlig:"×ות לטינית s חדה קטנה",agrave:"×ות לטינית a ×¢× ×’×¨×© (Grave)",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",aring:"Latin small letter a with ring above",aelig:"×ות לטינית æ קטנה",ccedil:"Latin small letter c with cedilla",egrave:"×ות לטינית e ×¢× ×’×¨×© (Grave)",eacute:"Latin small letter e with acute accent",
+ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"×ות לטינית i ×¢× ×’×¨×© (Grave)",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"×ות לטינית eth קטנה",ntilde:"Latin small letter n with tilde",ograve:"×ות לטינית o ×¢× ×’×¨×© (Grave)",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",
+divide:"סימן חלוקה",oslash:"Latin small letter o with stroke",ugrave:"×ות לטינית u ×¢× ×’×¨×© (Grave)",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",thorn:"×ות לטינית thorn קטנה",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",
+373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"סימן ציטוט נמוך יחיד",8219:"סימן ציטוט",bdquo:"סימן ציטוט נמוך כפול",hellip:"שלוש נקודות",trade:"סימן טריידמ×רק",9658:"סמן שחור לצד ימין",bull:"תבליט (רשימה)",rarr:"×—×¥ לימין",rArr:"×—×¥ כפול לימין",hArr:"×—×¥ כפול לימין ושמ×ל",diams:"×™×”×œ×•× ×ž×œ×",asymp:"כמעט שווה"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/hr.js b/js/ckeditor/plugins/specialchar/dialogs/lang/hr.js
new file mode 100644
index 0000000..6575432
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/hr.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","hr",{euro:"Euro znak",lsquo:"Lijevi jednostruki navodnik",rsquo:"Desni jednostruki navodnik",ldquo:"Lijevi dvostruki navodnik",rdquo:"Desni dvostruki navodnik",ndash:"En crtica",mdash:"Em crtica",iexcl:"Naopaki uskliÄnik",cent:"Cent znak",pound:"Funta znak",curren:"Znak valute",yen:"Yen znak",brvbar:"Potrgana preÄka",sect:"Znak odjeljka",uml:"Prijeglasi",copy:"Copyright znak",ordf:"Feminine ordinal indicator",laquo:"Lijevi dvostruki uglati navodnik",not:"Not znak",
+reg:"Registered znak",macr:"Macron",deg:"Stupanj znak",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Mikro znak",para:"Pilcrow sign",middot:"Srednja toÄka",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Desni dvostruku uglati navodnik",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Naopaki upitnik",Agrave:"Veliko latinsko slovo A s akcentom",Aacute:"LatiniÄno veliko slovo A sa oÅ¡trim naglaskom",
+Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",Iacute:"Latin capital letter I with acute accent",
+Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",Ugrave:"Latin capital letter U with grave accent",
+Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",aring:"Latin small letter a with ring above",
+aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",ntilde:"Latin small letter n with tilde",
+ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",
+thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",trade:"Trade mark sign",9658:"Black right-pointing pointer",
+bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/hu.js b/js/ckeditor/plugins/specialchar/dialogs/lang/hu.js
new file mode 100644
index 0000000..4455483
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/hu.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","hu",{euro:"Euró jel",lsquo:"Bal szimpla idézőjel",rsquo:"Jobb szimpla idézőjel",ldquo:"Bal dupla idézőjel",rdquo:"Jobb dupla idézőjel",ndash:"Rövid gondolatjel",mdash:"Hosszú gondolatjel",iexcl:"Fordított felkiáltójel",cent:"Cent jel",pound:"Font jel",curren:"Valuta jel",yen:"Yen jel",brvbar:"Hosszú kettőspont",sect:"Paragrafus jel",uml:"Kettős hangzó jel",copy:"Szerzői jog jel",ordf:"Női sorrend mutatója",laquo:"Balra mutató duplanyíl",not:"Feltételes kötőjel",
+reg:"Bejegyzett védjegy jele",macr:"Hosszúsági jel",deg:"Fok jel",sup2:"Négyzeten jel",sup3:"Köbön jel",acute:"Éles ékezet",micro:"Mikro-jel",para:"Bekezdés jel",middot:"Közép pont",cedil:"Cédille",sup1:"Elsőn jel",ordm:"Férfi sorrend mutatója",raquo:"Jobbra mutató duplanyíl",frac14:"Egy negyed jel",frac12:"Egy ketted jel",frac34:"Három negyed jel",iquest:"Fordított kérdőjel",Agrave:"Latin nagy A fordított ékezettel",Aacute:"Latin nagy A normál ékezettel",Acirc:"Latin nagy A hajtott ékezettel",Atilde:"Latin nagy A hullámjellel",
+Auml:"Latin nagy A kettőspont ékezettel",Aring:"Latin nagy A gyűrű ékezettel",AElig:"Latin nagy Æ betű",Ccedil:"Latin nagy C cedillával",Egrave:"Latin nagy E fordított ékezettel",Eacute:"Latin nagy E normál ékezettel",Ecirc:"Latin nagy E hajtott ékezettel",Euml:"Latin nagy E dupla kettőspont ékezettel",Igrave:"Latin nagy I fordított ékezettel",Iacute:"Latin nagy I normál ékezettel",Icirc:"Latin nagy I hajtott ékezettel",Iuml:"Latin nagy I kettőspont ékezettel",ETH:"Latin nagy Eth betű",Ntilde:"Latin nagy N hullámjellel",
+Ograve:"Latin nagy O fordított ékezettel",Oacute:"Latin nagy O normál ékezettel",Ocirc:"Latin nagy O hajtott ékezettel",Otilde:"Latin nagy O hullámjellel",Ouml:"Latin nagy O kettőspont ékezettel",times:"Szorzás jel",Oslash:"Latin O betű áthúzással",Ugrave:"Latin nagy U fordított ékezettel",Uacute:"Latin nagy U normál ékezettel",Ucirc:"Latin nagy U hajtott ékezettel",Uuml:"Latin nagy U kettőspont ékezettel",Yacute:"Latin nagy Y normál ékezettel",THORN:"Latin nagy Thorn betű",szlig:"Latin kis s betű",
+agrave:"Latin kis a fordított ékezettel",aacute:"Latin kis a normál ékezettel",acirc:"Latin kis a hajtott ékezettel",atilde:"Latin kis a hullámjellel",auml:"Latin kis a kettőspont ékezettel",aring:"Latin kis a gyűrű ékezettel",aelig:"Latin kis æ betű",ccedil:"Latin kis c cedillával",egrave:"Latin kis e fordított ékezettel",eacute:"Latin kis e normál ékezettel",ecirc:"Latin kis e hajtott ékezettel",euml:"Latin kis e dupla kettőspont ékezettel",igrave:"Latin kis i fordított ékezettel",iacute:"Latin kis i normál ékezettel",
+icirc:"Latin kis i hajtott ékezettel",iuml:"Latin kis i kettőspont ékezettel",eth:"Latin kis eth betű",ntilde:"Latin kis n hullámjellel",ograve:"Latin kis o fordított ékezettel",oacute:"Latin kis o normál ékezettel",ocirc:"Latin kis o hajtott ékezettel",otilde:"Latin kis o hullámjellel",ouml:"Latin kis o kettőspont ékezettel",divide:"Osztásjel",oslash:"Latin kis o betű áthúzással",ugrave:"Latin kis u fordított ékezettel",uacute:"Latin kis u normál ékezettel",ucirc:"Latin kis u hajtott ékezettel",
+uuml:"Latin kis u kettőspont ékezettel",yacute:"Latin kis y normál ékezettel",thorn:"Latin kis thorn jel",yuml:"Latin kis y kettőspont ékezettel",OElig:"Latin nagy OE-jel",oelig:"Latin kis oe-jel",372:"Latin nagy W hajtott ékezettel",374:"Latin nagy Y hajtott ékezettel",373:"Latin kis w hajtott ékezettel",375:"Latin kis y hajtott ékezettel",sbquo:"Nyitó nyomdai szimpla idézőjel",8219:"Záró nyomdai záró idézőjel",bdquo:"Nyitó nyomdai dupla idézőjel",hellip:"Három pont",trade:"Kereskedelmi védjegy jele",
+9658:"Jobbra mutató fekete mutató",bull:"Golyó",rarr:"Jobbra mutató nyíl",rArr:"Jobbra mutató duplanyíl",hArr:"Bal-jobb duplanyíl",diams:"Fekete gyémánt jel",asymp:"Majdnem egyenlő jel"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/id.js b/js/ckeditor/plugins/specialchar/dialogs/lang/id.js
new file mode 100644
index 0000000..1b4bd67
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/id.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","id",{euro:"Tanda Euro",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Cent sign",pound:"Pound sign",curren:"Currency sign",yen:"Tanda Yen",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Tanda Hak Cipta",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Tanda Telah Terdaftar",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/it.js b/js/ckeditor/plugins/specialchar/dialogs/lang/it.js
new file mode 100644
index 0000000..6b09097
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/it.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","it",{euro:"Simbolo Euro",lsquo:"Virgoletta singola sinistra",rsquo:"Virgoletta singola destra",ldquo:"Virgolette aperte",rdquo:"Virgolette chiuse",ndash:"Trattino",mdash:"Trattino lungo",iexcl:"Punto esclavamativo invertito",cent:"Simbolo Cent",pound:"Simbolo Sterlina",curren:"Simbolo Moneta",yen:"Simbolo Yen",brvbar:"Barra interrotta",sect:"Simbolo di sezione",uml:"Dieresi",copy:"Simbolo Copyright",ordf:"Indicatore ordinale femminile",laquo:"Virgolette basse aperte",
+not:"Nessun segno",reg:"Simbolo Registrato",macr:"Macron",deg:"Simbolo Grado",sup2:"Apice Due",sup3:"Apice Tre",acute:"Accento acuto",micro:"Simbolo Micro",para:"Simbolo Paragrafo",middot:"Punto centrale",cedil:"Cediglia",sup1:"Apice Uno",ordm:"Indicatore ordinale maschile",raquo:"Virgolette basse chiuse",frac14:"Frazione volgare un quarto",frac12:"Frazione volgare un mezzo",frac34:"Frazione volgare tre quarti",iquest:"Punto interrogativo invertito",Agrave:"Lettera maiuscola latina A con accento grave",
+Aacute:"Lettera maiuscola latina A con accento acuto",Acirc:"Lettera maiuscola latina A con accento circonflesso",Atilde:"Lettera maiuscola latina A con tilde",Auml:"Lettera maiuscola latina A con dieresi",Aring:"Lettera maiuscola latina A con anello sopra",AElig:"Lettera maiuscola latina AE",Ccedil:"Lettera maiuscola latina C con cediglia",Egrave:"Lettera maiuscola latina E con accento grave",Eacute:"Lettera maiuscola latina E con accento acuto",Ecirc:"Lettera maiuscola latina E con accento circonflesso",
+Euml:"Lettera maiuscola latina E con dieresi",Igrave:"Lettera maiuscola latina I con accento grave",Iacute:"Lettera maiuscola latina I con accento acuto",Icirc:"Lettera maiuscola latina I con accento circonflesso",Iuml:"Lettera maiuscola latina I con dieresi",ETH:"Lettera maiuscola latina Eth",Ntilde:"Lettera maiuscola latina N con tilde",Ograve:"Lettera maiuscola latina O con accento grave",Oacute:"Lettera maiuscola latina O con accento acuto",Ocirc:"Lettera maiuscola latina O con accento circonflesso",
+Otilde:"Lettera maiuscola latina O con tilde",Ouml:"Lettera maiuscola latina O con dieresi",times:"Simbolo di moltiplicazione",Oslash:"Lettera maiuscola latina O barrata",Ugrave:"Lettera maiuscola latina U con accento grave",Uacute:"Lettera maiuscola latina U con accento acuto",Ucirc:"Lettera maiuscola latina U con accento circonflesso",Uuml:"Lettera maiuscola latina U con accento circonflesso",Yacute:"Lettera maiuscola latina Y con accento acuto",THORN:"Lettera maiuscola latina Thorn",szlig:"Lettera latina minuscola doppia S",
+agrave:"Lettera minuscola latina a con accento grave",aacute:"Lettera minuscola latina a con accento acuto",acirc:"Lettera minuscola latina a con accento circonflesso",atilde:"Lettera minuscola latina a con tilde",auml:"Lettera minuscola latina a con dieresi",aring:"Lettera minuscola latina a con anello superiore",aelig:"Lettera minuscola latina ae",ccedil:"Lettera minuscola latina c con cediglia",egrave:"Lettera minuscola latina e con accento grave",eacute:"Lettera minuscola latina e con accento acuto",
+ecirc:"Lettera minuscola latina e con accento circonflesso",euml:"Lettera minuscola latina e con dieresi",igrave:"Lettera minuscola latina i con accento grave",iacute:"Lettera minuscola latina i con accento acuto",icirc:"Lettera minuscola latina i con accento circonflesso",iuml:"Lettera minuscola latina i con dieresi",eth:"Lettera minuscola latina eth",ntilde:"Lettera minuscola latina n con tilde",ograve:"Lettera minuscola latina o con accento grave",oacute:"Lettera minuscola latina o con accento acuto",
+ocirc:"Lettera minuscola latina o con accento circonflesso",otilde:"Lettera minuscola latina o con tilde",ouml:"Lettera minuscola latina o con dieresi",divide:"Simbolo di divisione",oslash:"Lettera minuscola latina o barrata",ugrave:"Lettera minuscola latina u con accento grave",uacute:"Lettera minuscola latina u con accento acuto",ucirc:"Lettera minuscola latina u con accento circonflesso",uuml:"Lettera minuscola latina u con dieresi",yacute:"Lettera minuscola latina y con accento acuto",thorn:"Lettera minuscola latina thorn",
+yuml:"Lettera minuscola latina y con dieresi",OElig:"Legatura maiuscola latina OE",oelig:"Legatura minuscola latina oe",372:"Lettera maiuscola latina W con accento circonflesso",374:"Lettera maiuscola latina Y con accento circonflesso",373:"Lettera minuscola latina w con accento circonflesso",375:"Lettera minuscola latina y con accento circonflesso",sbquo:"Singola virgoletta bassa low-9",8219:"Singola virgoletta bassa low-9 inversa",bdquo:"Doppia virgoletta bassa low-9",hellip:"Ellissi orizzontale",
+trade:"Simbolo TM",9658:"Puntatore nero rivolto verso destra",bull:"Punto",rarr:"Freccia verso destra",rArr:"Doppia freccia verso destra",hArr:"Doppia freccia sinistra destra",diams:"Simbolo nero diamante",asymp:"Quasi uguale a"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ja.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ja.js
new file mode 100644
index 0000000..b32e890
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ja.js
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ja",{euro:"ユーロ記å·",lsquo:"左シングル引用符",rsquo:"å³ã‚·ãƒ³ã‚°ãƒ«å¼•ç”¨ç¬¦",ldquo:"左ダブル引用符",rdquo:"å³ãƒ€ãƒ–ル引用符",ndash:"åŠè§’ダッシュ",mdash:"全角ダッシュ",iexcl:"逆ã•æ„Ÿå˜†ç¬¦",cent:"セント記å·",pound:"ãƒãƒ³ãƒ‰è¨˜å·",curren:"通貨記å·",yen:"円記å·",brvbar:"上下ã«åˆ†ã‹ã‚ŒãŸç¸¦æ£’",sect:"節記å·",uml:"分音記å·(ウムラウト)",copy:"著作権表示記å·",ordf:"女性åºæ•°æ¨™è­˜",laquo:" 始ã‚二é‡å±±æ‹¬å¼§å¼•ç”¨è¨˜å·",not:"è«–ç†å¦å®šè¨˜å·",reg:"登録商標記å·",macr:"長音符",deg:"度記å·",sup2:"上ã¤ã2, 2ä¹—",sup3:"上ã¤ã3, 3ä¹—",acute:"æšéŸ³ç¬¦",micro:"ミクロン記å·",para:"段è½è¨˜å·",middot:"中黒",cedil:"セディラ",sup1:"上ã¤ã1",ordm:"男性åºæ•°æ¨™è­˜",raquo:"終ã‚り二é‡å±±æ‹¬å¼§å¼•ç”¨è¨˜å·",
+frac14:"四分ã®ä¸€",frac12:"二分ã®ä¸€",frac34:"四分ã®ä¸‰",iquest:"逆疑å•ç¬¦",Agrave:"抑音符ã¤ã大文字A",Aacute:"æšéŸ³ç¬¦ã¤ã大文字A",Acirc:"曲折アクセントã¤ã大文字A",Atilde:"ãƒãƒ«ãƒ€ã¤ã大文字A",Auml:"分音記å·ã¤ã大文字A",Aring:"リングã¤ã大文字A",AElig:"Aã¨Eã®åˆå­—",Ccedil:"セディラã¤ã大文字C",Egrave:"抑音符ã¤ã大文字E",Eacute:"æšéŸ³ç¬¦ã¤ã大文字E",Ecirc:"曲折アクセントã¤ã大文字E",Euml:"分音記å·ã¤ã大文字E",Igrave:"抑音符ã¤ã大文字I",Iacute:"æšéŸ³ç¬¦ã¤ã大文字I",Icirc:"曲折アクセントã¤ã大文字I",Iuml:"分音記å·ã¤ã大文字I",ETH:"[アイスランド語]大文字ETH",Ntilde:"ãƒãƒ«ãƒ€ã¤ã大文字N",Ograve:"抑音符ã¤ã大文字O",Oacute:"æšéŸ³ç¬¦ã¤ã大文字O",Ocirc:"曲折アクセントã¤ã大文字O",Otilde:"ãƒãƒ«ãƒ€ã¤ã大文字O",Ouml:" 分音記å·ã¤ã大文字O",
+times:"乗算記å·",Oslash:"打ã¡æ¶ˆã—ç·šã¤ã大文字O",Ugrave:"抑音符ã¤ã大文字U",Uacute:"æšéŸ³ç¬¦ã¤ã大文字U",Ucirc:"曲折アクセントã¤ã大文字U",Uuml:"分音記å·ã¤ã大文字U",Yacute:"æšéŸ³ç¬¦ã¤ã大文字Y",THORN:"[アイスランド語]大文字THORN",szlig:"ドイツ語エスツェット",agrave:"抑音符ã¤ãå°æ–‡å­—a",aacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—a",acirc:"曲折アクセントã¤ãå°æ–‡å­—a",atilde:"ãƒãƒ«ãƒ€ã¤ãå°æ–‡å­—a",auml:"分音記å·ã¤ãå°æ–‡å­—a",aring:"リングã¤ãå°æ–‡å­—a",aelig:"aã¨eã®åˆå­—",ccedil:"セディラã¤ãå°æ–‡å­—c",egrave:"抑音符ã¤ãå°æ–‡å­—e",eacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—e",ecirc:"曲折アクセントã¤ãå°æ–‡å­—e",euml:"分音記å·ã¤ãå°æ–‡å­—e",igrave:"抑音符ã¤ãå°æ–‡å­—i",iacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—i",icirc:"曲折アクセントã¤ãå°æ–‡å­—i",iuml:"分音記å·ã¤ãå°æ–‡å­—i",eth:"アイスランド語å°æ–‡å­—eth",
+ntilde:"ãƒãƒ«ãƒ€ã¤ãå°æ–‡å­—n",ograve:"抑音符ã¤ãå°æ–‡å­—o",oacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—o",ocirc:"曲折アクセントã¤ãå°æ–‡å­—o",otilde:"ãƒãƒ«ãƒ€ã¤ãå°æ–‡å­—o",ouml:"分音記å·ã¤ãå°æ–‡å­—o",divide:"除算記å·",oslash:"打ã¡æ¶ˆã—ç·šã¤ãå°æ–‡å­—o",ugrave:"抑音符ã¤ãå°æ–‡å­—u",uacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—u",ucirc:"曲折アクセントã¤ãå°æ–‡å­—u",uuml:"分音記å·ã¤ãå°æ–‡å­—u",yacute:"æšéŸ³ç¬¦ã¤ãå°æ–‡å­—y",thorn:"アイスランド語å°æ–‡å­—thorn",yuml:"分音記å·ã¤ãå°æ–‡å­—y",OElig:"Oã¨Eã®åˆå­—",oelig:"oã¨eã®åˆå­—",372:"曲折アクセントã¤ã大文字W",374:"曲折アクセントã¤ã大文字Y",373:"曲折アクセントã¤ãå°æ–‡å­—w",375:"曲折アクセントã¤ãå°æ–‡å­—y",sbquo:"シングル下引用符",8219:"å·¦å³é€†ã®å·¦å¼•ç”¨ç¬¦",bdquo:"ダブル下引用符",hellip:"三点リーダ",trade:"商標記å·",9658:"å³é»’三角ãƒã‚¤ãƒ³ã‚¿",bull:"黒丸",
+rarr:"å³çŸ¢å°",rArr:"å³äºŒé‡çŸ¢å°",hArr:"å·¦å³äºŒé‡çŸ¢å°",diams:"ダイヤ",asymp:"漸近"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/km.js b/js/ckeditor/plugins/specialchar/dialogs/lang/km.js
new file mode 100644
index 0000000..8d6a3d1
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/km.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","km",{euro:"សញ្ញា​អឺរ៉ូ",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"សញ្ញា​សáŸáž“",pound:"សញ្ញា​ផោន",curren:"សញ្ញា​រូបិយបណ្ណ",yen:"សញ្ញា​យ៉áŸáž“",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"សញ្ញា​រក្សា​សិទ្ធិ",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registered sign",macr:"Macron",deg:"សញ្ញា​ដឺក្រáŸ",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"សញ្ញា​មីក្រូ",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ku.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ku.js
new file mode 100644
index 0000000..5bed679
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ku.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ku",{euro:"نیشانەی یۆرۆ",lsquo:"نیشانەی Ùاریزەی سەرووژێری تاکی Ú†Û•Ù¾",rsquo:"نیشانەی Ùاریزەی سەرووژێری تاکی ڕاست",ldquo:"نیشانەی Ùاریزەی سەرووژێری دووهێندەی چه‌پ",rdquo:"نیشانەی Ùاریزەی سەرووژێری دووهێندەی ڕاست",ndash:"تەقەڵی کورت",mdash:"تەقەڵی درێژ",iexcl:"نیشانەی Ù‡Û•ÚµÛ•ÙˆÚ¯ÛŽÚ•ÛŒ سەرسوڕهێنەر",cent:"نیشانەی سەنت",pound:"نیشانەی پاوەند",curren:"نیشانەی دراو",yen:"نیشانەی یەنی ژاپۆنی",brvbar:"شریتی ئەستوونی پچڕاو",sect:"نیشانەی دوو s لەسەریەک",uml:"خاڵ",copy:"نیشانەی ماÙÛŒ چاپ",
+ordf:"Ù‡ÛŽÚµ لەسەر پیتی a",laquo:"دوو تیری بەدووایەکی Ú†Û•Ù¾",not:"نیشانەی نەخێر",reg:"نیشانەی R لەناو بازنەدا",macr:"ماکڕۆن",deg:"نیشانەی پلە",sup2:"سەرنووسی دوو",sup3:"سەرنووسی سێ",acute:"لاری تیژ",micro:"نیشانەی u لق درێژی Ú†Û•Ù¾ÛŒ خواروو",para:"نیشانەی پەڕەگراÙ",middot:"ناوەڕاستی خاڵ",cedil:"نیشانەی c ژێر چووکرە",sup1:"سەرنووسی یەک",ordm:"Ù‡ÛŽÚµ لەژێر پیتی o",raquo:"دوو تیری بەدووایەکی ڕاست",frac14:"یەک لەسەر چووار",frac12:"یەک لەسەر دوو",frac34:"سێ لەسەر چووار",iquest:"هێمای هەڵەوگێری پرسیار",Agrave:"پیتی لاتینی A-ÛŒ گەورە Ù„Û•Ú¯Û•Úµ ڕوومەتداری لار",
+Aacute:"پیتی لاتینی A-ی گەورە لەگەڵ ڕوومەتداری تیژ",Acirc:"پیتی لاتینی A-ی گەورە لەگەڵ نیشانە لەسەری",Atilde:"پیتی لاتینی A-ی گەورە لەگەڵ زەڕە",Auml:"پیتی لاتینی A-ی گەورە لەگەڵ نیشانە لەسەری",Aring:"پیتی لاتینی گەورەی Å",AElig:"پیتی لاتینی گەورەی Æ",Ccedil:"پیتی لاتینی C-ی گەورە لەگەڵ ژێر چووکرە",Egrave:"پیتی لاتینی E-ی گەورە لەگەڵ ڕوومەتداری لار",Eacute:"پیتی لاتینی E-ی گەورە لەگەڵ ڕوومەتداری تیژ",Ecirc:"پیتی لاتینی E-ی گەورە لەگەڵ نیشانە لەسەری",Euml:"پیتی لاتینی E-ی گەورە لەگەڵ نیشانە لەسەری",
+Igrave:"پیتی لاتینی I-ی گەورە لەگەڵ ڕوومەتداری لار",Iacute:"پیتی لاتینی I-ی گەورە لەگەڵ ڕوومەتداری تیژ",Icirc:"پیتی لاتینی I-ی گەورە لەگەڵ نیشانە لەسەری",Iuml:"پیتی لاتینی I-ی گەورە لەگەڵ نیشانە لەسەری",ETH:"پیتی لاتینی E-ی گەورەی",Ntilde:"پیتی لاتینی N-ی گەورە لەگەڵ زەڕە",Ograve:"پیتی لاتینی O-ی گەورە لەگەڵ ڕوومەتداری لار",Oacute:"پیتی لاتینی O-ی گەورە لەگەڵ ڕوومەتداری تیژ",Ocirc:"پیتی لاتینی O-ی گەورە لەگەڵ نیشانە لەسەری",Otilde:"پیتی لاتینی O-ی گەورە لەگەڵ زەڕە",Ouml:"پیتی لاتینی O-ی گەورە لەگەڵ نیشانە لەسەری",
+times:"نیشانەی لێکدان",Oslash:"پیتی لاتینی گەورەی Ø لەگەڵ هێمای دڵ وەستان",Ugrave:"پیتی لاتینی U-ی گەورە لەگەڵ ڕوومەتداری لار",Uacute:"پیتی لاتینی U-ی گەورە لەگەڵ ڕوومەتداری تیژ",Ucirc:"پیتی لاتینی U-ی گەورە لەگەڵ نیشانە لەسەری",Uuml:"پیتی لاتینی U-ی گەورە لەگەڵ نیشانە لەسەری",Yacute:"پیتی لاتینی Y-ی گەورە لەگەڵ ڕوومەتداری تیژ",THORN:"پیتی لاتینی دڕکی گەورە",szlig:"پیتی لاتنی نووک تیژی s",agrave:"پیتی لاتینی a-ی بچووک لەگەڵ ڕوومەتداری لار",aacute:"پیتی لاتینی a-ی بچووك لەگەڵ ڕوومەتداری تیژ",acirc:"پیتی لاتینی a-ی بچووك لەگەڵ نیشانە لەسەری",
+atilde:"پیتی لاتینی a-ی بچووك لەگەڵ زەڕە",auml:"پیتی لاتینی a-ی بچووك لەگەڵ نیشانە لەسەری",aring:"پیتی لاتینی å-ی بچووك",aelig:"پیتی لاتینی æ-ی بچووك",ccedil:"پیتی لاتینی c-ی بچووك لەگەڵ ژێر چووکرە",egrave:"پیتی لاتینی e-ی بچووك لەگەڵ ڕوومەتداری لار",eacute:"پیتی لاتینی e-ی بچووك لەگەڵ ڕوومەتداری تیژ",ecirc:"پیتی لاتینی e-ی بچووك لەگەڵ نیشانە لەسەری",euml:"پیتی لاتینی e-ی بچووك لەگەڵ نیشانە لەسەری",igrave:"پیتی لاتینی i-ی بچووك لەگەڵ ڕوومەتداری لار",iacute:"پیتی لاتینی i-ی بچووك لەگەڵ ڕوومەتداری تیژ",
+icirc:"پیتی لاتینی i-ی بچووك لەگەڵ نیشانە لەسەری",iuml:"پیتی لاتینی i-ی بچووك لەگەڵ نیشانە لەسەری",eth:"پیتی لاتینی e-ی بچووك",ntilde:"پیتی لاتینی n-ی بچووك لەگەڵ زەڕە",ograve:"پیتی لاتینی o-ی بچووك لەگەڵ ڕوومەتداری لار",oacute:"پیتی لاتینی o-ی بچووك له‌گەڵ ڕوومەتداری تیژ",ocirc:"پیتی لاتینی o-ی بچووك لەگەڵ نیشانە لەسەری",otilde:"پیتی لاتینی o-ی بچووك لەگەڵ زەڕە",ouml:"پیتی لاتینی o-ی بچووك لەگەڵ نیشانە لەسەری",divide:"نیشانەی دابەش",oslash:"پیتی لاتینی گەورەی ø لەگەڵ هێمای دڵ وەستان",ugrave:"پیتی لاتینی u-ی بچووك لەگەڵ ڕوومەتداری لار",
+uacute:"پیتی لاتینی u-ی بچووك لەگەڵ ڕوومەتداری تیژ",ucirc:"پیتی لاتینی u-ی بچووك لەگەڵ نیشانە لەسەری",uuml:"پیتی لاتینی u-ی بچووك لەگەڵ نیشانە لەسەری",yacute:"پیتی لاتینی y-ی بچووك لەگەڵ ڕوومەتداری تیژ",thorn:"پیتی لاتینی دڕکی بچووك",yuml:"پیتی لاتینی y-ی بچووك لەگەڵ نیشانە لەسەری",OElig:"پیتی لاتینی گەورەی پێکەوەنووسراوی OE",oelig:"پیتی لاتینی بچووکی پێکەوەنووسراوی oe",372:"پیتی لاتینی W-ی گەورە لەگەڵ نیشانە لەسەری",374:"پیتی لاتینی Y-ی گەورە لەگەڵ نیشانە لەسەری",373:"پیتی لاتینی w-ی بچووکی لەگەڵ نیشانە لەسەری",
+375:"پیتی لاتینی y-ÛŒ بچووکی Ù„Û•Ú¯Û•Úµ نیشانە لەسەری",sbquo:"نیشانەی Ùاریزەی نزم",8219:"نیشانەی Ùاریزەی بەرزی پێچەوانە",bdquo:"دوو Ùاریزەی تەنیش یەك",hellip:"ئاسۆیی بازنە",trade:"نیشانەی بازرگانی",9658:"ئاراستەی Ú•Û•Ø´ÛŒ دەستی ڕاست",bull:"Ùیشەك",rarr:"تیری دەستی ڕاست",rArr:"دووتیری دەستی ڕاست",hArr:"دوو تیری ڕاست Ùˆ Ú†Û•Ù¾",diams:"Ú•Û•Ø´ÛŒ پاقڵاوەیی",asymp:"نیشانەی یەکسانە"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/lt.js b/js/ckeditor/plugins/specialchar/dialogs/lang/lt.js
new file mode 100644
index 0000000..f575dfd
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/lt.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","lt",{euro:"Euro ženklas",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Cento ženklas",pound:"Svaro ženklas",curren:"Valiutos ženklas",yen:"Jenos ženklas",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Copyright sign",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Ne ženklas",reg:"Registered sign",macr:"Makronas",deg:"Laipsnio ženklas",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Mikro ženklas",para:"Pilcrow sign",middot:"Vidurinis taškas",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/lv.js b/js/ckeditor/plugins/specialchar/dialogs/lang/lv.js
new file mode 100644
index 0000000..7ea58a0
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/lv.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","lv",{euro:"Euro zÄ«me",lsquo:"KreisÄ vienkÄrtÄ«ga pÄ“diņa",rsquo:"LabÄ vienkÄrtÄ«ga pÄ“diņa",ldquo:"KreisÄ dubult pÄ“diņa",rdquo:"LabÄ dubult pÄ“diņa",ndash:"En svÄ«tra",mdash:"Em svÄ«tra",iexcl:"Apgriezta izsaukuma zÄ«me",cent:"Centu naudas zÄ«me",pound:"Sterliņu mÄrciņu naudas zÄ«me",curren:"ValÅ«tas zÄ«me",yen:"Jenu naudas zÄ«me",brvbar:"VertikÄla pÄrrauta lÄ«nija",sect:"ParagrÄfa zÄ«me",uml:"Diakritiska zÄ«me",copy:"AutortiesÄ«bu zÄ«me",ordf:"SieviÅ¡Ä·as kÄrtas rÄdÄ«tÄjs",
+laquo:"KreisÄ dubult stÅ«ra pÄ“diņu zÄ«me",not:"NeparakstÄ«ts",reg:"ReÄ£istrÄ“ta zÄ«me",macr:"GarumzÄ«me",deg:"GrÄdu zÄ«me",sup2:"AugÅ¡raksts divi",sup3:"AugÅ¡raksts trÄ«s",acute:"AkÅ«ta uzsvara zÄ«me",micro:"Mikro zÄ«me",para:"Rindkopas zÄ«me ",middot:"VidÄ“js punkts",cedil:"Āķītis zem burta",sup1:"AugÅ¡raksts viens",ordm:"VÄ«riÅ¡Ä·Ä«gas kÄrtas rÄdÄ«tÄjs",raquo:"LabÄ dubult stÅ«ra pÄ“diņu zÄ«me",frac14:"VulgÄra frakcija 1/4",frac12:"VulgÄra frakcija 1/2",frac34:"VulgÄra frakcija 3/4",iquest:"Apgriezta jautÄjuma zÄ«me",Agrave:"Lielais latīņu burts A ar uzsvara zÄ«mi",
+Aacute:"Lielais latīņu burts A ar akÅ«tu uzsvara zÄ«mi",Acirc:"Lielais latīņu burts A ar diakritisku zÄ«mi",Atilde:"Lielais latīņu burts A ar tildi ",Auml:"Lielais latīņu burts A ar diakritisko zÄ«mi",Aring:"Lielais latīņu burts A ar aplÄ«ti augÅ¡Ä",AElig:"Lielais latīņu burts Æ",Ccedil:"Lielais latīņu burts C ar ÄÄ·Ä«ti zem burta",Egrave:"Lielais latīņu burts E ar apostrofu",Eacute:"Lielais latīņu burts E ar akÅ«tu uzsvara zÄ«mi",Ecirc:"Lielais latīņu burts E ar diakritisko zÄ«mi",Euml:"Lielais latīņu burts E ar diakritisko zÄ«mi",
+Igrave:"Lielais latīņu burts I ar uzsvaras zīmi",Iacute:"Lielais latīņu burts I ar akūtu uzsvara zīmi",Icirc:"Lielais latīņu burts I ar diakritisko zīmi",Iuml:"Lielais latīņu burts I ar diakritisko zīmi",ETH:"Lielais latīņu burts Eth",Ntilde:"Lielais latīņu burts N ar tildi",Ograve:"Lielais latīņu burts O ar uzsvara zīmi",Oacute:"Lielais latīņu burts O ar akūto uzsvara zīmi",Ocirc:"Lielais latīņu burts O ar diakritisko zīmi",Otilde:"Lielais latīņu burts O ar tildi",Ouml:"Lielais latīņu burts O ar diakritisko zīmi",
+times:"ReizinÄÅ¡anas zÄ«me ",Oslash:"Lielais latīņu burts O ar iesvÄ«trojumu",Ugrave:"Lielais latīņu burts U ar uzsvaras zÄ«mi",Uacute:"Lielais latīņu burts U ar akÅ«to uzsvars zÄ«mi",Ucirc:"Lielais latīņu burts U ar diakritisko zÄ«mi",Uuml:"Lielais latīņu burts U ar diakritisko zÄ«mi",Yacute:"Lielais latīņu burts Y ar akÅ«to uzsvaras zÄ«mi",THORN:"Lielais latīņu burts torn",szlig:"Mazs latīņu burts ar ligatÅ«ru",agrave:"Mazs latīņu burts a ar uzsvara zÄ«mi",aacute:"Mazs latīņu burts a ar akÅ«to uzsvara zÄ«mi",
+acirc:"Mazs latīņu burts a ar diakritisko zÄ«mi",atilde:"Mazs latīņu burts a ar tildi",auml:"Mazs latīņu burts a ar diakritisko zÄ«mi",aring:"Mazs latīņu burts a ar aplÄ«ti augÅ¡Ä",aelig:"Mazs latīņu burts æ",ccedil:"Mazs latīņu burts c ar ÄÄ·Ä«ti zem burta",egrave:"Mazs latīņu burts e ar uzsvara zÄ«mi ",eacute:"Mazs latīņu burts e ar akÅ«tu uzsvara zÄ«mi",ecirc:"Mazs latīņu burts e ar diakritisko zÄ«mi",euml:"Mazs latīņu burts e ar diakritisko zÄ«mi",igrave:"Mazs latīņu burts i ar uzsvara zÄ«mi ",iacute:"Mazs latīņu burts i ar akÅ«tu uzsvara zÄ«mi",
+icirc:"Mazs latīņu burts i ar diakritisko zīmi",iuml:"Mazs latīņu burts i ar diakritisko zīmi",eth:"Mazs latīņu burts eth",ntilde:"Mazs latīņu burts n ar tildi",ograve:"Mazs latīņu burts o ar uzsvara zīmi ",oacute:"Mazs latīņu burts o ar akūtu uzsvara zīmi",ocirc:"Mazs latīņu burts o ar diakritisko zīmi",otilde:"Mazs latīņu burts o ar tildi",ouml:"Mazs latīņu burts o ar diakritisko zīmi",divide:"Dalīšanas zīme",oslash:"Mazs latīņu burts o ar iesvītrojumu",ugrave:"Mazs latīņu burts u ar uzsvara zīmi ",
+uacute:"Mazs latīņu burts u ar akūtu uzsvara zīmi",ucirc:"Mazs latīņu burts u ar diakritisko zīmi",uuml:"Mazs latīņu burts u ar diakritisko zīmi",yacute:"Mazs latīņu burts y ar akūtu uzsvaras zīmi",thorn:"Mazs latīņu burts torns",yuml:"Mazs latīņu burts y ar diakritisko zīmi",OElig:"Liela latīņu ligatūra OE",oelig:"Maza latīņu ligatūra oe",372:"Liels latīņu burts W ar diakritisko zīmi ",374:"Liels latīņu burts Y ar diakritisko zīmi ",373:"Mazs latīņu burts w ar diakritisko zīmi ",375:"Mazs latīņu burts y ar diakritisko zīmi ",
+sbquo:"Mazas-9 vienkÄrtÄ«gas pÄ“diņas",8219:"Lielas-9 vienkÄrtÄ«gas apgrieztas pÄ“diņas",bdquo:"Mazas-9 dubultas pÄ“diņas",hellip:"HorizontÄli daudzpunkti",trade:"PreÄu zÄ«mes zÄ«me",9658:"Melns pa labi pagriezts radÄ«tÄjs",bull:"Lode",rarr:"Bulta pa labi",rArr:"Dubulta Bulta pa labi",hArr:"Bulta pa kreisi",diams:"Dubulta Bulta pa kreisi",asymp:"GandrÄ«z vienÄds ar"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/nb.js b/js/ckeditor/plugins/specialchar/dialogs/lang/nb.js
new file mode 100644
index 0000000..f9fd879
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/nb.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","nb",{euro:"Eurosymbol",lsquo:"Venstre enkelt anførselstegn",rsquo:"Høyre enkelt anførselstegn",ldquo:"Venstre dobbelt anførselstegn",rdquo:"Høyre anførsesltegn",ndash:"Kort tankestrek",mdash:"Lang tankestrek",iexcl:"Omvendt utropstegn",cent:"Centsymbol",pound:"Pundsymbol",curren:"Valutategn",yen:"Yensymbol",brvbar:"Brutt loddrett strek",sect:"Paragraftegn",uml:"Tøddel",copy:"Copyrighttegn",ordf:"Feminin ordensindikator",laquo:"Venstre anførselstegn",not:"Negasjonstegn",
+reg:"Registrert varemerke-tegn",macr:"Makron",deg:"Gradsymbol",sup2:"Hevet totall",sup3:"Hevet tretall",acute:"Akutt aksent",micro:"Mikrosymbol",para:"Avsnittstegn",middot:"Midtstilt prikk",cedil:"Cedille",sup1:"Hevet ettall",ordm:"Maskulin ordensindikator",raquo:"Høyre anførselstegn",frac14:"Fjerdedelsbrøk",frac12:"Halvbrøk",frac34:"Tre fjerdedelers brøk",iquest:"Omvendt spørsmålstegn",Agrave:"Stor A med grav aksent",Aacute:"Stor A med akutt aksent",Acirc:"Stor A med cirkumfleks",Atilde:"Stor A med tilde",
+Auml:"Stor A med tøddel",Aring:"Stor Å",AElig:"Stor Æ",Ccedil:"Stor C med cedille",Egrave:"Stor E med grav aksent",Eacute:"Stor E med akutt aksent",Ecirc:"Stor E med cirkumfleks",Euml:"Stor E med tøddel",Igrave:"Stor I med grav aksent",Iacute:"Stor I med akutt aksent",Icirc:"Stor I med cirkumfleks",Iuml:"Stor I med tøddel",ETH:"Stor Edd/stungen D",Ntilde:"Stor N med tilde",Ograve:"Stor O med grav aksent",Oacute:"Stor O med akutt aksent",Ocirc:"Stor O med cirkumfleks",Otilde:"Stor O med tilde",Ouml:"Stor O med tøddel",
+times:"Multiplikasjonstegn",Oslash:"Stor Ø",Ugrave:"Stor U med grav aksent",Uacute:"Stor U med akutt aksent",Ucirc:"Stor U med cirkumfleks",Uuml:"Stor U med tøddel",Yacute:"Stor Y med akutt aksent",THORN:"Stor Thorn",szlig:"Liten dobbelt-s/Eszett",agrave:"Liten a med grav aksent",aacute:"Liten a med akutt aksent",acirc:"Liten a med cirkumfleks",atilde:"Liten a med tilde",auml:"Liten a med tøddel",aring:"Liten å",aelig:"Liten æ",ccedil:"Liten c med cedille",egrave:"Liten e med grav aksent",eacute:"Liten e med akutt aksent",
+ecirc:"Liten e med cirkumfleks",euml:"Liten e med tøddel",igrave:"Liten i med grav aksent",iacute:"Liten i med akutt aksent",icirc:"Liten i med cirkumfleks",iuml:"Liten i med tøddel",eth:"Liten edd/stungen d",ntilde:"Liten n med tilde",ograve:"Liten o med grav aksent",oacute:"Liten o med akutt aksent",ocirc:"Liten o med cirkumfleks",otilde:"Liten o med tilde",ouml:"Liten o med tøddel",divide:"Divisjonstegn",oslash:"Liten ø",ugrave:"Liten u med grav aksent",uacute:"Liten u med akutt aksent",ucirc:"Liten u med cirkumfleks",
+uuml:"Liten u med tøddel",yacute:"Liten y med akutt aksent",thorn:"Liten thorn",yuml:"Liten y med tøddel",OElig:"Stor ligatur av O og E",oelig:"Liten ligatur av o og e",372:"Stor W med cirkumfleks",374:"Stor Y med cirkumfleks",373:"Liten w med cirkumfleks",375:"Liten y med cirkumfleks",sbquo:"Enkelt lavt 9-anførselstegn",8219:"Enkelt høyt reversert 9-anførselstegn",bdquo:"Dobbelt lavt 9-anførselstegn",hellip:"Ellipse",trade:"Varemerkesymbol",9658:"Svart høyrevendt peker",bull:"Tykk interpunkt",rarr:"Høyrevendt pil",
+rArr:"Dobbel høyrevendt pil",hArr:"Dobbel venstrevendt pil",diams:"Svart ruter",asymp:"Omtrent likhetstegn"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/nl.js b/js/ckeditor/plugins/specialchar/dialogs/lang/nl.js
new file mode 100644
index 0000000..ba75bf3
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/nl.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","nl",{euro:"Euro-teken",lsquo:"Linker enkel aanhalingsteken",rsquo:"Rechter enkel aanhalingsteken",ldquo:"Linker dubbel aanhalingsteken",rdquo:"Rechter dubbel aanhalingsteken",ndash:"En dash",mdash:"Em dash",iexcl:"Omgekeerd uitroepteken",cent:"Cent-teken",pound:"Pond-teken",curren:"Valuta-teken",yen:"Yen-teken",brvbar:"Gebroken streep",sect:"Paragraaf-teken",uml:"Trema",copy:"Copyright-teken",ordf:"Vrouwelijk ordinaal",laquo:"Linker guillemet",not:"Ongelijk-teken",
+reg:"Geregistreerd handelsmerk-teken",macr:"Macron",deg:"Graden-teken",sup2:"Superscript twee",sup3:"Superscript drie",acute:"Accent aigu",micro:"Micro-teken",para:"Alinea-teken",middot:"Halfhoge punt",cedil:"Cedille",sup1:"Superscript een",ordm:"Mannelijk ordinaal",raquo:"Rechter guillemet",frac14:"Breuk kwart",frac12:"Breuk half",frac34:"Breuk driekwart",iquest:"Omgekeerd vraagteken",Agrave:"Latijnse hoofdletter A met een accent grave",Aacute:"Latijnse hoofdletter A met een accent aigu",Acirc:"Latijnse hoofdletter A met een circonflexe",
+Atilde:"Latijnse hoofdletter A met een tilde",Auml:"Latijnse hoofdletter A met een trema",Aring:"Latijnse hoofdletter A met een corona",AElig:"Latijnse hoofdletter Æ",Ccedil:"Latijnse hoofdletter C met een cedille",Egrave:"Latijnse hoofdletter E met een accent grave",Eacute:"Latijnse hoofdletter E met een accent aigu",Ecirc:"Latijnse hoofdletter E met een circonflexe",Euml:"Latijnse hoofdletter E met een trema",Igrave:"Latijnse hoofdletter I met een accent grave",Iacute:"Latijnse hoofdletter I met een accent aigu",
+Icirc:"Latijnse hoofdletter I met een circonflexe",Iuml:"Latijnse hoofdletter I met een trema",ETH:"Latijnse hoofdletter Eth",Ntilde:"Latijnse hoofdletter N met een tilde",Ograve:"Latijnse hoofdletter O met een accent grave",Oacute:"Latijnse hoofdletter O met een accent aigu",Ocirc:"Latijnse hoofdletter O met een circonflexe",Otilde:"Latijnse hoofdletter O met een tilde",Ouml:"Latijnse hoofdletter O met een trema",times:"Maal-teken",Oslash:"Latijnse hoofdletter O met een schuine streep",Ugrave:"Latijnse hoofdletter U met een accent grave",
+Uacute:"Latijnse hoofdletter U met een accent aigu",Ucirc:"Latijnse hoofdletter U met een circonflexe",Uuml:"Latijnse hoofdletter U met een trema",Yacute:"Latijnse hoofdletter Y met een accent aigu",THORN:"Latijnse hoofdletter Thorn",szlig:"Latijnse kleine ringel-s",agrave:"Latijnse kleine letter a met een accent grave",aacute:"Latijnse kleine letter a met een accent aigu",acirc:"Latijnse kleine letter a met een circonflexe",atilde:"Latijnse kleine letter a met een tilde",auml:"Latijnse kleine letter a met een trema",
+aring:"Latijnse kleine letter a met een corona",aelig:"Latijnse kleine letter æ",ccedil:"Latijnse kleine letter c met een cedille",egrave:"Latijnse kleine letter e met een accent grave",eacute:"Latijnse kleine letter e met een accent aigu",ecirc:"Latijnse kleine letter e met een circonflexe",euml:"Latijnse kleine letter e met een trema",igrave:"Latijnse kleine letter i met een accent grave",iacute:"Latijnse kleine letter i met een accent aigu",icirc:"Latijnse kleine letter i met een circonflexe",
+iuml:"Latijnse kleine letter i met een trema",eth:"Latijnse kleine letter eth",ntilde:"Latijnse kleine letter n met een tilde",ograve:"Latijnse kleine letter o met een accent grave",oacute:"Latijnse kleine letter o met een accent aigu",ocirc:"Latijnse kleine letter o met een circonflexe",otilde:"Latijnse kleine letter o met een tilde",ouml:"Latijnse kleine letter o met een trema",divide:"Deel-teken",oslash:"Latijnse kleine letter o met een schuine streep",ugrave:"Latijnse kleine letter u met een accent grave",
+uacute:"Latijnse kleine letter u met een accent aigu",ucirc:"Latijnse kleine letter u met een circonflexe",uuml:"Latijnse kleine letter u met een trema",yacute:"Latijnse kleine letter y met een accent aigu",thorn:"Latijnse kleine letter thorn",yuml:"Latijnse kleine letter y met een trema",OElig:"Latijnse hoofdletter Å’",oelig:"Latijnse kleine letter Å“",372:"Latijnse hoofdletter W met een circonflexe",374:"Latijnse hoofdletter Y met een circonflexe",373:"Latijnse kleine letter w met een circonflexe",
+375:"Latijnse kleine letter y met een circonflexe",sbquo:"Lage enkele aanhalingsteken",8219:"Hoge omgekeerde enkele aanhalingsteken",bdquo:"Lage dubbele aanhalingsteken",hellip:"Beletselteken",trade:"Trademark-teken",9658:"Zwarte driehoek naar rechts",bull:"Bullet",rarr:"Pijl naar rechts",rArr:"Dubbele pijl naar rechts",hArr:"Dubbele pijl naar links",diams:"Zwart ruitje",asymp:"Benaderingsteken"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/no.js b/js/ckeditor/plugins/specialchar/dialogs/lang/no.js
new file mode 100644
index 0000000..404b4fd
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/no.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","no",{euro:"Eurosymbol",lsquo:"Venstre enkelt anførselstegn",rsquo:"Høyre enkelt anførselstegn",ldquo:"Venstre dobbelt anførselstegn",rdquo:"Høyre anførsesltegn",ndash:"Kort tankestrek",mdash:"Lang tankestrek",iexcl:"Omvendt utropstegn",cent:"Centsymbol",pound:"Pundsymbol",curren:"Valutategn",yen:"Yensymbol",brvbar:"Brutt loddrett strek",sect:"Paragraftegn",uml:"Tøddel",copy:"Copyrighttegn",ordf:"Feminin ordensindikator",laquo:"Venstre anførselstegn",not:"Negasjonstegn",
+reg:"Registrert varemerke-tegn",macr:"Makron",deg:"Gradsymbol",sup2:"Hevet totall",sup3:"Hevet tretall",acute:"Akutt aksent",micro:"Mikrosymbol",para:"Avsnittstegn",middot:"Midtstilt prikk",cedil:"Cedille",sup1:"Hevet ettall",ordm:"Maskulin ordensindikator",raquo:"Høyre anførselstegn",frac14:"Fjerdedelsbrøk",frac12:"Halvbrøk",frac34:"Tre fjerdedelers brøk",iquest:"Omvendt spørsmålstegn",Agrave:"Stor A med grav aksent",Aacute:"Stor A med akutt aksent",Acirc:"Stor A med cirkumfleks",Atilde:"Stor A med tilde",
+Auml:"Stor A med tøddel",Aring:"Stor Å",AElig:"Stor Æ",Ccedil:"Stor C med cedille",Egrave:"Stor E med grav aksent",Eacute:"Stor E med akutt aksent",Ecirc:"Stor E med cirkumfleks",Euml:"Stor E med tøddel",Igrave:"Stor I med grav aksent",Iacute:"Stor I med akutt aksent",Icirc:"Stor I med cirkumfleks",Iuml:"Stor I med tøddel",ETH:"Stor Edd/stungen D",Ntilde:"Stor N med tilde",Ograve:"Stor O med grav aksent",Oacute:"Stor O med akutt aksent",Ocirc:"Stor O med cirkumfleks",Otilde:"Stor O med tilde",Ouml:"Stor O med tøddel",
+times:"Multiplikasjonstegn",Oslash:"Stor Ø",Ugrave:"Stor U med grav aksent",Uacute:"Stor U med akutt aksent",Ucirc:"Stor U med cirkumfleks",Uuml:"Stor U med tøddel",Yacute:"Stor Y med akutt aksent",THORN:"Stor Thorn",szlig:"Liten dobbelt-s/Eszett",agrave:"Liten a med grav aksent",aacute:"Liten a med akutt aksent",acirc:"Liten a med cirkumfleks",atilde:"Liten a med tilde",auml:"Liten a med tøddel",aring:"Liten å",aelig:"Liten æ",ccedil:"Liten c med cedille",egrave:"Liten e med grav aksent",eacute:"Liten e med akutt aksent",
+ecirc:"Liten e med cirkumfleks",euml:"Liten e med tøddel",igrave:"Liten i med grav aksent",iacute:"Liten i med akutt aksent",icirc:"Liten i med cirkumfleks",iuml:"Liten i med tøddel",eth:"Liten edd/stungen d",ntilde:"Liten n med tilde",ograve:"Liten o med grav aksent",oacute:"Liten o med akutt aksent",ocirc:"Liten o med cirkumfleks",otilde:"Liten o med tilde",ouml:"Liten o med tøddel",divide:"Divisjonstegn",oslash:"Liten ø",ugrave:"Liten u med grav aksent",uacute:"Liten u med akutt aksent",ucirc:"Liten u med cirkumfleks",
+uuml:"Liten u med tøddel",yacute:"Liten y med akutt aksent",thorn:"Liten thorn",yuml:"Liten y med tøddel",OElig:"Stor ligatur av O og E",oelig:"Liten ligatur av o og e",372:"Stor W med cirkumfleks",374:"Stor Y med cirkumfleks",373:"Liten w med cirkumfleks",375:"Liten y med cirkumfleks",sbquo:"Enkelt lavt 9-anførselstegn",8219:"Enkelt høyt reversert 9-anførselstegn",bdquo:"Dobbelt lavt 9-anførselstegn",hellip:"Ellipse",trade:"Varemerkesymbol",9658:"Svart høyrevendt peker",bull:"Tykk interpunkt",rarr:"Høyrevendt pil",
+rArr:"Dobbel høyrevendt pil",hArr:"Dobbel venstrevendt pil",diams:"Svart ruter",asymp:"Omtrent likhetstegn"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/pl.js b/js/ckeditor/plugins/specialchar/dialogs/lang/pl.js
new file mode 100644
index 0000000..76e2acd
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/pl.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","pl",{euro:"Znak euro",lsquo:"Cudzysłów pojedynczy otwierający",rsquo:"Cudzysłów pojedynczy zamykający",ldquo:"Cudzysłów apostrofowy otwierający",rdquo:"Cudzysłów apostrofowy zamykający",ndash:"Półpauza",mdash:"Pauza",iexcl:"Odwrócony wykrzyknik",cent:"Znak centa",pound:"Znak funta",curren:"Znak waluty",yen:"Znak jena",brvbar:"Przerwana pionowa kreska",sect:"Paragraf",uml:"Diereza",copy:"Znak praw autorskich",ordf:"Wskaźnik rodzaju żeńskiego liczebnika porządkowego",
+laquo:"Lewy cudzysłów ostrokątny",not:"Znak negacji",reg:"Zastrzeżony znak towarowy",macr:"Makron",deg:"Znak stopnia",sup2:"Druga potęga",sup3:"Trzecia potęga",acute:"Akcent ostry",micro:"Znak mikro",para:"Znak akapitu",middot:"Kropka środkowa",cedil:"Cedylla",sup1:"Pierwsza potęga",ordm:"Wskaźnik rodzaju męskiego liczebnika porządkowego",raquo:"Prawy cudzysłów ostrokątny",frac14:"Ułamek zwykły jedna czwarta",frac12:"Ułamek zwykły jedna druga",frac34:"Ułamek zwykły trzy czwarte",iquest:"Odwrócony znak zapytania",
+Agrave:"Wielka litera A z akcentem ciężkim",Aacute:"Wielka litera A z akcentem ostrym",Acirc:"Wielka litera A z akcentem przeciągłym",Atilde:"Wielka litera A z tyldą",Auml:"Wielka litera A z dierezą",Aring:"Wielka litera A z kółkiem",AElig:"Wielka ligatura Æ",Ccedil:"Wielka litera C z cedyllą",Egrave:"Wielka litera E z akcentem ciężkim",Eacute:"Wielka litera E z akcentem ostrym",Ecirc:"Wielka litera E z akcentem przeciągłym",Euml:"Wielka litera E z dierezą",Igrave:"Wielka litera I z akcentem ciężkim",
+Iacute:"Wielka litera I z akcentem ostrym",Icirc:"Wielka litera I z akcentem przeciągłym",Iuml:"Wielka litera I z dierezą",ETH:"Wielka litera Eth",Ntilde:"Wielka litera N z tyldą",Ograve:"Wielka litera O z akcentem ciężkim",Oacute:"Wielka litera O z akcentem ostrym",Ocirc:"Wielka litera O z akcentem przeciągłym",Otilde:"Wielka litera O z tyldą",Ouml:"Wielka litera O z dierezą",times:"Znak mnożenia wektorowego",Oslash:"Wielka litera O z przekreśleniem",Ugrave:"Wielka litera U z akcentem ciężkim",Uacute:"Wielka litera U z akcentem ostrym",
+Ucirc:"Wielka litera U z akcentem przeciągłym",Uuml:"Wielka litera U z dierezą",Yacute:"Wielka litera Y z akcentem ostrym",THORN:"Wielka litera Thorn",szlig:"Mała litera ostre s (eszet)",agrave:"Mała litera a z akcentem ciężkim",aacute:"Mała litera a z akcentem ostrym",acirc:"Mała litera a z akcentem przeciągłym",atilde:"Mała litera a z tyldą",auml:"Mała litera a z dierezą",aring:"Mała litera a z kółkiem",aelig:"Mała ligatura æ",ccedil:"Mała litera c z cedyllą",egrave:"Mała litera e z akcentem ciężkim",
+eacute:"Mała litera e z akcentem ostrym",ecirc:"Mała litera e z akcentem przeciągłym",euml:"Mała litera e z dierezą",igrave:"Mała litera i z akcentem ciężkim",iacute:"Mała litera i z akcentem ostrym",icirc:"Mała litera i z akcentem przeciągłym",iuml:"Mała litera i z dierezą",eth:"Mała litera eth",ntilde:"Mała litera n z tyldą",ograve:"Mała litera o z akcentem ciężkim",oacute:"Mała litera o z akcentem ostrym",ocirc:"Mała litera o z akcentem przeciągłym",otilde:"Mała litera o z tyldą",ouml:"Mała litera o z dierezą",
+divide:"Anglosaski znak dzielenia",oslash:"Mała litera o z przekreśleniem",ugrave:"Mała litera u z akcentem ciężkim",uacute:"Mała litera u z akcentem ostrym",ucirc:"Mała litera u z akcentem przeciągłym",uuml:"Mała litera u z dierezą",yacute:"Mała litera y z akcentem ostrym",thorn:"Mała litera thorn",yuml:"Mała litera y z dierezą",OElig:"Wielka ligatura OE",oelig:"Mała ligatura oe",372:"Wielka litera W z akcentem przeciągłym",374:"Wielka litera Y z akcentem przeciągłym",373:"Mała litera w z akcentem przeciągłym",
+375:"Mała litera y z akcentem przeciągłym",sbquo:"Pojedynczy apostrof dolny",8219:"Pojedynczy apostrof górny",bdquo:"Podwójny apostrof dolny",hellip:"Wielokropek",trade:"Znak towarowy",9658:"Czarny wskaźnik wskazujący w prawo",bull:"Punktor",rarr:"Strzałka w prawo",rArr:"Podwójna strzałka w prawo",hArr:"Podwójna strzałka w lewo",diams:"Czarny znak karo",asymp:"Znak prawie równe"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/pt-br.js b/js/ckeditor/plugins/specialchar/dialogs/lang/pt-br.js
new file mode 100644
index 0000000..41f8c33
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/pt-br.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","pt-br",{euro:"Euro",lsquo:"Aspas simples esquerda",rsquo:"Aspas simples direita",ldquo:"Aspas duplas esquerda",rdquo:"Aspas duplas direita",ndash:"Traço",mdash:"Travessão",iexcl:"Ponto de exclamação invertido",cent:"Cent",pound:"Cerquilha",curren:"Dinheiro",yen:"Yen",brvbar:"Bara interrompida",sect:"Símbolo de Parágrafo",uml:"Trema",copy:"Direito de Cópia",ordf:"Indicador ordinal feminino",laquo:"Aspas duplas angulares esquerda",not:"Negação",reg:"Marca Registrada",
+macr:"Mácron",deg:"Grau",sup2:"2 Superscrito",sup3:"3 Superscrito",acute:"Acento agudo",micro:"Micro",para:"Pé de mosca",middot:"Ponto mediano",cedil:"Cedilha",sup1:"1 Superscrito",ordm:"Indicador ordinal masculino",raquo:"Aspas duplas angulares direita",frac14:"Um quarto",frac12:"Um meio",frac34:"Três quartos",iquest:"Interrogação invertida",Agrave:"A maiúsculo com acento grave",Aacute:"A maiúsculo com acento agudo",Acirc:"A maiúsculo com acento circunflexo",Atilde:"A maiúsculo com til",Auml:"A maiúsculo com trema",
+Aring:"A maiúsculo com anel acima",AElig:"Æ maiúsculo",Ccedil:"Ç maiúlculo",Egrave:"E maiúsculo com acento grave",Eacute:"E maiúsculo com acento agudo",Ecirc:"E maiúsculo com acento circumflexo",Euml:"E maiúsculo com trema",Igrave:"I maiúsculo com acento grave",Iacute:"I maiúsculo com acento agudo",Icirc:"I maiúsculo com acento circunflexo",Iuml:"I maiúsculo com crase",ETH:"Eth maiúsculo",Ntilde:"N maiúsculo com til",Ograve:"O maiúsculo com acento grave",Oacute:"O maiúsculo com acento agudo",Ocirc:"O maiúsculo com acento circunflexo",
+Otilde:"O maiúsculo com til",Ouml:"O maiúsculo com trema",times:"Multiplicação",Oslash:"Diâmetro",Ugrave:"U maiúsculo com acento grave",Uacute:"U maiúsculo com acento agudo",Ucirc:"U maiúsculo com acento circunflexo",Uuml:"U maiúsculo com trema",Yacute:"Y maiúsculo com acento agudo",THORN:"Thorn maiúsculo",szlig:"Eszett minúsculo",agrave:"a minúsculo com acento grave",aacute:"a minúsculo com acento agudo",acirc:"a minúsculo com acento circunflexo",atilde:"a minúsculo com til",auml:"a minúsculo com trema",
+aring:"a minúsculo com anel acima",aelig:"æ minúsculo",ccedil:"ç minúsculo",egrave:"e minúsculo com acento grave",eacute:"e minúsculo com acento agudo",ecirc:"e minúsculo com acento circunflexo",euml:"e minúsculo com trema",igrave:"i minúsculo com acento grave",iacute:"i minúsculo com acento agudo",icirc:"i minúsculo com acento circunflexo",iuml:"i minúsculo com trema",eth:"eth minúsculo",ntilde:"n minúsculo com til",ograve:"o minúsculo com acento grave",oacute:"o minúsculo com acento agudo",ocirc:"o minúsculo com acento circunflexo",
+otilde:"o minúsculo com til",ouml:"o minúsculo com trema",divide:"Divisão",oslash:"o minúsculo com cortado ou diâmetro",ugrave:"u minúsculo com acento grave",uacute:"u minúsculo com acento agudo",ucirc:"u minúsculo com acento circunflexo",uuml:"u minúsculo com trema",yacute:"y minúsculo com acento agudo",thorn:"thorn minúsculo",yuml:"y minúsculo com trema",OElig:"Ligação tipográfica OE maiúscula",oelig:"Ligação tipográfica oe minúscula",372:"W maiúsculo com acento circunflexo",374:"Y maiúsculo com acento circunflexo",
+373:"w minúsculo com acento circunflexo",375:"y minúsculo com acento circunflexo",sbquo:"Aspas simples inferior direita",8219:"Aspas simples superior esquerda",bdquo:"Aspas duplas inferior direita",hellip:"Reticências",trade:"Trade mark",9658:"Ponta de seta preta para direita",bull:"Ponto lista",rarr:"Seta para direita",rArr:"Seta dupla para direita",hArr:"Seta dupla direita e esquerda",diams:"Ouros",asymp:"Aproximadamente"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/pt.js b/js/ckeditor/plugins/specialchar/dialogs/lang/pt.js
new file mode 100644
index 0000000..9c73f0c
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/pt.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","pt",{euro:"Símbolo do Euro",lsquo:"Aspa esquerda simples",rsquo:"Aspa direita simples",ldquo:"Aspa esquerda dupla",rdquo:"Aspa direita dupla",ndash:"Travessão Simples",mdash:"Travessão Longo",iexcl:"Ponto de exclamação invertido",cent:"Símbolo do Cêntimo",pound:"Símbolo da Libra",curren:"Símbolo de Moeda",yen:"Símbolo do Iene",brvbar:"Barra quebrada",sect:"Símbolo de Secção",uml:"Trema",copy:"Símbolo dos Direitos de Autor",ordf:"Indicador ordinal feminino",
+laquo:"Aspa esquerda ângulo duplo",not:"Não Símbolo",reg:"Símbolo de Registado",macr:"Mácron",deg:"Símbolo de Grau",sup2:"Expoente 2",sup3:"Expoente 3",acute:"Acento agudo",micro:"Símbolo de Micro",para:"Símbolo de Parágrafo",middot:"Ponto do Meio",cedil:"Cedilha",sup1:"Expoente 1",ordm:"Indicador ordinal masculino",raquo:"Aspas ângulo duplo pra Direita",frac14:"Fração vulgar 1/4",frac12:"Fração vulgar 1/2",frac34:"Fração vulgar 3/4",iquest:"Ponto de interrogação invertido",Agrave:"Letra maiúscula latina A com acento grave",
+Aacute:"Letra maiúscula latina A com acento agudo",Acirc:"Letra maiúscula latina A com circunflexo",Atilde:"Letra maiúscula latina A com til",Auml:"Letra maiúscula latina A com trema",Aring:"Letra maiúscula latina A com sinal diacrítico",AElig:"Letra maiúscula latina Æ",Ccedil:"Letra maiúscula latina C com cedilha",Egrave:"Letra maiúscula latina E com acento grave",Eacute:"Letra maiúscula latina E com acento agudo",Ecirc:"Letra maiúscula latina E com circunflexo",Euml:"Letra maiúscula latina E com trema",
+Igrave:"Letra maiúscula latina I com acento grave",Iacute:"Letra maiúscula latina I com acento agudo",Icirc:"Letra maiúscula latina I com cincunflexo",Iuml:"Letra maiúscula latina I com trema",ETH:"Letra maiúscula latina Eth (Ãð)",Ntilde:"Letra maiúscula latina N com til",Ograve:"Letra maiúscula latina O com acento grave",Oacute:"Letra maiúscula latina O com acento agudo",Ocirc:"Letra maiúscula latina I com circunflexo",Otilde:"Letra maiúscula latina O com til",Ouml:"Letra maiúscula latina O com trema",
+times:"Símbolo de multiplicação",Oslash:"Letra maiúscula O com barra",Ugrave:"Letra maiúscula latina U com acento grave",Uacute:"Letra maiúscula latina U com acento agudo",Ucirc:"Letra maiúscula latina U com circunflexo",Uuml:"Letra maiúscula latina E com trema",Yacute:"Letra maiúscula latina Y com acento agudo",THORN:"Letra maiúscula latina Rúnico",szlig:"Letra minúscula latina s forte",agrave:"Letra minúscula latina a com acento grave",aacute:"Letra minúscula latina a com acento agudo",acirc:"Letra minúscula latina a com circunflexo",
+atilde:"Letra minúscula latina a com til",auml:"Letra minúscula latina a com trema",aring:"Letra minúscula latina a com sinal diacrítico",aelig:"Letra minúscula latina æ",ccedil:"Letra minúscula latina c com cedilha",egrave:"Letra minúscula latina e com acento grave",eacute:"Letra minúscula latina e com acento agudo",ecirc:"Letra minúscula latina e com circunflexo",euml:"Letra minúscula latina e com trema",igrave:"Letra minúscula latina i com acento grave",iacute:"Letra minúscula latina i com acento agudo",
+icirc:"Letra minúscula latina i com circunflexo",iuml:"Letra pequena latina i com trema",eth:"Letra minúscula latina eth",ntilde:"Letra minúscula latina n com til",ograve:"Letra minúscula latina o com acento grave",oacute:"Letra minúscula latina o com acento agudo",ocirc:"Letra minúscula latina o com circunflexo",otilde:"Letra minúscula latina o com til",ouml:"Letra minúscula latina o com trema",divide:"Símbolo de divisão",oslash:"Letra minúscula latina o com barra",ugrave:"Letra minúscula latina u com acento grave",
+uacute:"Letra minúscula latina u com acento agudo",ucirc:"Letra minúscula latina u com circunflexo",uuml:"Letra minúscula latina u com trema",yacute:"Letra minúscula latina y com acento agudo",thorn:"Letra minúscula latina Rúnico",yuml:"Letra minúscula latina y com trema",OElig:"Ligadura maiúscula latina OE",oelig:"Ligadura minúscula latina oe",372:"Letra maiúscula latina W com circunflexo",374:"Letra maiúscula latina Y com circunflexo",373:"Letra minúscula latina w com circunflexo",375:"Letra minúscula latina y com circunflexo",
+sbquo:"Aspa Simples inferior-9",8219:"Aspa Simples superior invertida-9",bdquo:"Aspa duplas inferior-9",hellip:"Elipse Horizontal ",trade:"Símbolo de Marca Registada",9658:"Ponteiro preto direito",bull:"Marca",rarr:"Seta para a direita",rArr:"Seta dupla para a direita",hArr:"Seta dupla direita esquerda",diams:"Naipe diamante preto",asymp:"Quase igual a "}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ru.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ru.js
new file mode 100644
index 0000000..30633fd
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ru.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ru",{euro:"Знак евро",lsquo:"Ð›ÐµÐ²Ð°Ñ Ð¾Ð´Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",rsquo:"ÐŸÑ€Ð°Ð²Ð°Ñ Ð¾Ð´Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",ldquo:"Ð›ÐµÐ²Ð°Ñ Ð´Ð²Ð¾Ð¹Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",rdquo:"Ð›ÐµÐ²Ð°Ñ Ð´Ð²Ð¾Ð¹Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",ndash:"Среднее тире",mdash:"Длинное тире",iexcl:"перевёрнутый воÑклицательный знак",cent:"Цент",pound:"Фунт",curren:"Знак валюты",yen:"Йена",brvbar:"Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ñ‡ÐµÑ€Ñ‚Ð° Ñ Ñ€Ð°Ð·Ñ€Ñ‹Ð²Ð¾Ð¼",sect:"Знак параграфа",uml:"Умлаут",copy:"Знак охраны авторÑкого права",ordf:"Указатель Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð¶ÐµÐ½Ñкого рода ...аÑ",laquo:"Ð›ÐµÐ²Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°-«ёлочка»",
+not:"Отрицание",reg:"Знак охраны Ñмежных прав\\t",macr:"Макрон",deg:"ГрадуÑ",sup2:"ÐадÑтрочное два",sup3:"ÐадÑтрочное три",acute:"Ðкут",micro:"Микро",para:"Ðбзац",middot:"Интерпункт",cedil:"Седиль",sup1:"ÐадÑÑ‚Ñ€Ð¾Ñ‡Ð½Ð°Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ð°",ordm:"ПорÑдковое чиÑлительное",raquo:"ÐŸÑ€Ð°Ð²Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°-«ёлочка»",frac14:"Одна четвертаÑ",frac12:"Одна втораÑ",frac34:"Три четвёртых",iquest:"Перевёрнутый вопроÑительный знак",Agrave:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Ð Ñ Ð°Ð¿Ð¾Ñтрофом",Aacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° A Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",Acirc:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Ð Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",
+Atilde:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Ð Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",Auml:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Ð Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",Aring:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Ð Ñ ÐºÐ¾Ð»ÑŒÑ†Ð¾Ð¼ над ней",AElig:"ЛатинÑÐºÐ°Ñ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð±ÑƒÐºÐ²Ð° Æ",Ccedil:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° C Ñ Ñедилью",Egrave:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Е Ñ Ð°Ð¿Ð¾Ñтрофом",Eacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Е Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",Ecirc:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Е Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",Euml:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Е Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",Igrave:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° I Ñ Ð°Ð¿Ð¾Ñтрофом",Iacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° I Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",
+Icirc:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° I Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",Iuml:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° I Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",ETH:"ЛатинÑÐºÐ°Ñ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð±ÑƒÐºÐ²Ð° Eth",Ntilde:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° N Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",Ograve:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° O Ñ Ð°Ð¿Ð¾Ñтрофом",Oacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° O Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",Ocirc:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° O Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",Otilde:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° O Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",Ouml:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° O Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",times:"Знак умножениÑ",Oslash:"ЛатинÑÐºÐ°Ñ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð¿ÐµÑ€ÐµÑ‡ÐµÑ€ÐºÐ½ÑƒÑ‚Ð°Ñ O",Ugrave:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° U Ñ Ð°Ð¿Ð¾Ñтрофом",
+Uacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° U Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",Ucirc:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° U Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",Uuml:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° U Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",Yacute:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Y Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",THORN:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Thorn",szlig:"Знак диеза",agrave:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ Ð°Ð¿Ð¾Ñтрофом",aacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",acirc:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",atilde:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",auml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",aring:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° a Ñ ÐºÐ¾Ð»ÑŒÑ†Ð¾Ð¼",
+aelig:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° æ",ccedil:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° Ñ Ñ Ñедилью",egrave:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° е Ñ Ð°Ð¿Ð¾Ñтрофом",eacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° е Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",ecirc:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° е Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",euml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° е Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",igrave:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° i Ñ Ð°Ð¿Ð¾Ñтрофом",iacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° i Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",icirc:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° i Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",iuml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° i Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",eth:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° eth",
+ntilde:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° n Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",ograve:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° o Ñ Ð°Ð¿Ð¾Ñтрофом",oacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° o Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",ocirc:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° o Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",otilde:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° o Ñ Ñ‚Ð¸Ð»ÑŒÐ´Ð¾Ð¹",ouml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° o Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",divide:"Знак делениÑ",oslash:"ЛатинÑÐºÐ°Ñ ÑÑ‚Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÑ‡ÐµÑ€ÐºÐ½ÑƒÑ‚Ð°Ñ o",ugrave:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° u Ñ Ð°Ð¿Ð¾Ñтрофом",uacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° u Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",ucirc:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° u Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",
+uuml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° u Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",yacute:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° y Ñ ÑƒÐ´Ð°Ñ€ÐµÐ½Ð¸ÐµÐ¼",thorn:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° thorn",yuml:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° y Ñ Ñ‚Ñ€ÐµÐ¼Ð¾Ð¹",OElig:"ЛатинÑÐºÐ°Ñ Ð¿Ñ€Ð¾Ð¿Ð¸ÑÐ½Ð°Ñ Ð»Ð¸Ð³Ð°Ñ‚ÑƒÑ€Ð° OE",oelig:"ЛатинÑÐºÐ°Ñ ÑÑ‚Ñ€Ð¾Ñ‡Ð½Ð°Ñ Ð»Ð¸Ð³Ð°Ñ‚ÑƒÑ€Ð° oe",372:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° W Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",374:"ЛатинÑÐºÐ°Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ð±ÑƒÐºÐ²Ð° Y Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",373:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° w Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",375:"ЛатинÑÐºÐ°Ñ Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ Ð±ÑƒÐºÐ²Ð° y Ñ Ñ†Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑом",sbquo:"ÐижнÑÑ Ð¾Ð´Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",8219:"ÐŸÑ€Ð°Ð²Ð°Ñ Ð¾Ð´Ð¸Ð½Ð°Ñ€Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",
+bdquo:"Ð›ÐµÐ²Ð°Ñ Ð´Ð²Ð¾Ð¹Ð½Ð°Ñ ÐºÐ°Ð²Ñ‹Ñ‡ÐºÐ°",hellip:"Горизонтальное многоточие",trade:"Товарный знак",9658:"Черный указатель вправо",bull:"Маркер ÑпиÑка",rarr:"Стрелка вправо",rArr:"Ð”Ð²Ð¾Ð¹Ð½Ð°Ñ Ñтрелка вправо",hArr:"Ð”Ð²Ð¾Ð¹Ð½Ð°Ñ Ñтрелка влево-вправо",diams:"Черный ромб",asymp:"Примерно равно"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/si.js b/js/ckeditor/plugins/specialchar/dialogs/lang/si.js
new file mode 100644
index 0000000..12789fb
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/si.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","si",{euro:"යුර෠සලකුණ",lsquo:"වමේ තනි උපුට෠දක්වීම ",rsquo:"දකුණේ තනි උපුට෠දක්වීම ",ldquo:"වමේ දිත්ව උපුට෠දක්වීම ",rdquo:"දකුණේ දිත්ව උපුට෠දක්වීම ",ndash:"En dash",mdash:"Em dash",iexcl:"යටිකුරු හර්ෂදී ",cent:"Cent sign",pound:"Pound sign",curren:"මුල්â€à¶ºà¶¸à¶º ",yen:"යෙන් ",brvbar:"Broken bar",sect:"තෙරේම් ",uml:"Diaeresis",copy:"පිටපත් අයිතිය ",ordf:"දර්à·à¶šà¶º",laquo:"Left-pointing double angle quotation mark",not:"සලකුණක් නොවේ",reg:"සලකුණක් ලියà·à¶´à¶¯à·’ංචි කිරීම",
+macr:"මුද්â€à¶»à·’ත ",deg:"සලකුණේ ",sup2:"උඩු ලකුණු දෙක",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",Aacute:"Latin capital letter A with acute accent",
+Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",Iacute:"Latin capital letter I with acute accent",
+Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",Ugrave:"Latin capital letter U with grave accent",
+Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",aring:"Latin small letter a with ring above",
+aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",ntilde:"Latin small letter n with tilde",
+ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",
+thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",trade:"Trade mark sign",9658:"Black right-pointing pointer",
+bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/sk.js b/js/ckeditor/plugins/specialchar/dialogs/lang/sk.js
new file mode 100644
index 0000000..6e5b534
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/sk.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","sk",{euro:"Znak eura",lsquo:"Ľavá jednoduchá úvodzovka",rsquo:"Pravá jednoduchá úvodzovka",ldquo:"Pravá dvojitá úvodzovka",rdquo:"Pravá dvojitá úvodzovka",ndash:"En pomlÄka",mdash:"Em pomlÄka",iexcl:"Obrátený výkriÄník",cent:"Znak centu",pound:"Znak libry",curren:"Znak meny",yen:"Znak jenu",brvbar:"PreruÅ¡ená zvislá Äiara",sect:"Znak odseku",uml:"Prehláska",copy:"Znak copyrightu",ordf:"Ženský indikátor rodu",laquo:"Znak dvojitých lomených úvodzoviek vľavo",not:"Logistický zápor",
+reg:"Znak registrácie",macr:"PomlÄka nad",deg:"Znak stupňa",sup2:"Dvojka ako horný index",sup3:"Trojka ako horný index",acute:"Dĺžeň",micro:"Znak mikro",para:"Znak odstavca",middot:"Bodka uprostred",cedil:"Chvost vľavo",sup1:"Jednotka ako horný index",ordm:"Mužský indikátor rodu",raquo:"Znak dvojitých lomených úvodzoviek vpravo",frac14:"ObyÄajný zlomok jedna Å¡tvrtina",frac12:"ObyÄajný zlomok jedna polovica",frac34:"ObyÄajný zlomok tri Å¡tvrtiny",iquest:"OtoÄený otáznik",Agrave:"Veľké písmeno latinky A s accentom",
+Aacute:"Veľké písmeno latinky A s dĺžňom",Acirc:"Veľké písmeno latinky A s mäkÄeňom",Atilde:"Veľké písmeno latinky A s tildou",Auml:"Veľké písmeno latinky A s dvoma bodkami",Aring:"Veľké písmeno latinky A s krúžkom nad",AElig:"Veľké písmeno latinky Æ",Ccedil:"Veľké písmeno latinky C s chvostom vľavo",Egrave:"Veľké písmeno latinky E s accentom",Eacute:"Veľké písmeno latinky E s dĺžňom",Ecirc:"Veľké písmeno latinky E s mäkÄeňom",Euml:"Veľké písmeno latinky E s dvoma bodkami",Igrave:"Veľké písmeno latinky I s accentom",
+Iacute:"Veľké písmeno latinky I s dĺžňom",Icirc:"Veľké písmeno latinky I s mäkÄeňom",Iuml:"Veľké písmeno latinky I s dvoma bodkami",ETH:"Veľké písmeno latinky Eth",Ntilde:"Veľké písmeno latinky N s tildou",Ograve:"Veľké písmeno latinky O s accentom",Oacute:"Veľké písmeno latinky O s dĺžňom",Ocirc:"Veľké písmeno latinky O s mäkÄeňom",Otilde:"Veľké písmeno latinky O s tildou",Ouml:"Veľké písmeno latinky O s dvoma bodkami",times:"Znak násobenia",Oslash:"Veľké písmeno latinky O preÅ¡krtnuté",Ugrave:"Veľké písmeno latinky U s accentom",
+Uacute:"Veľké písmeno latinky U s dĺžňom",Ucirc:"Veľké písmeno latinky U s mäkÄeňom",Uuml:"Veľké písmeno latinky U s dvoma bodkami",Yacute:"Veľké písmeno latinky Y s dĺžňom",THORN:"Veľké písmeno latinky Thorn",szlig:"Malé písmeno latinky ostré s",agrave:"Malé písmeno latinky a s accentom",aacute:"Malé písmeno latinky a s dĺžňom",acirc:"Malé písmeno latinky a s mäkÄeňom",atilde:"Malé písmeno latinky a s tildou",auml:"Malé písmeno latinky a s dvoma bodkami",aring:"Malé písmeno latinky a s krúžkom nad",
+aelig:"Malé písmeno latinky æ",ccedil:"Malé písmeno latinky c s chvostom vľavo",egrave:"Malé písmeno latinky e s accentom",eacute:"Malé písmeno latinky e s dĺžňom",ecirc:"Malé písmeno latinky e s mäkÄeňom",euml:"Malé písmeno latinky e s dvoma bodkami",igrave:"Malé písmeno latinky i s accentom",iacute:"Malé písmeno latinky i s dĺžňom",icirc:"Malé písmeno latinky i s mäkÄeňom",iuml:"Malé písmeno latinky i s dvoma bodkami",eth:"Malé písmeno latinky eth",ntilde:"Malé písmeno latinky n s tildou",ograve:"Malé písmeno latinky o s accentom",
+oacute:"Malé písmeno latinky o s dĺžňom",ocirc:"Malé písmeno latinky o s mäkÄeňom",otilde:"Malé písmeno latinky o s tildou",ouml:"Malé písmeno latinky o s dvoma bodkami",divide:"Znak delenia",oslash:"Malé písmeno latinky o preÅ¡krtnuté",ugrave:"Malé písmeno latinky u s accentom",uacute:"Malé písmeno latinky u s dĺžňom",ucirc:"Malé písmeno latinky u s mäkÄeňom",uuml:"Malé písmeno latinky u s dvoma bodkami",yacute:"Malé písmeno latinky y s dĺžňom",thorn:"Malé písmeno latinky thorn",yuml:"Malé písmeno latinky y s dvoma bodkami",
+OElig:"Veľká ligatúra latinky OE",oelig:"Malá ligatúra latinky OE",372:"Veľké písmeno latinky W s mäkÄeňom",374:"Veľké písmeno latinky Y s mäkÄeňom",373:"Malé písmeno latinky w s mäkÄeňom",375:"Malé písmeno latinky y s mäkÄeňom",sbquo:"Dolná jednoduchá 9-úvodzovka",8219:"Horná jednoduchá otoÄená 9-úvodzovka",bdquo:"Dolná dvojitá 9-úvodzovka",hellip:"Trojbodkový úvod",trade:"Znak ibchodnej znaÄky",9658:"ÄŒierny ukazovateľ smerujúci vpravo",bull:"Kruh",rarr:"Šípka vpravo",rArr:"Dvojitá Å¡ipka vpravo",
+hArr:"Dvojitá šipka vľavo a vpravo",diams:"Čierne piky",asymp:"Skoro sa rovná"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/sl.js b/js/ckeditor/plugins/specialchar/dialogs/lang/sl.js
new file mode 100644
index 0000000..bdebbd1
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/sl.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","sl",{euro:"Evro znak",lsquo:"Levi enojni narekovaj",rsquo:"Desni enojni narekovaj",ldquo:"Levi dvojni narekovaj",rdquo:"Desni dvojni narekovaj",ndash:"En pomiÅ¡ljaj",mdash:"Em pomiÅ¡ljaj",iexcl:"Obrnjen klicaj",cent:"Cent znak",pound:"Funt znak",curren:"Znak valute",yen:"Jen znak",brvbar:"Zlomljena Ärta",sect:"Znak oddelka",uml:"Diaeresis",copy:"Znak avtorskih pravic",ordf:"Ženski zaporedni kazalnik",laquo:"Levi obrnjen dvojni kotni narekovaj",not:"Ne znak",reg:"Registrirani znak",
+macr:"Macron",deg:"Znak stopinj",sup2:"Nadpisano dva",sup3:"Nadpisano tri",acute:"Ostrivec",micro:"Mikro znak",para:"Pilcrow znak",middot:"Sredinska pika",cedil:"Cedilla",sup1:"Nadpisano ena",ordm:"MoÅ¡ki zaporedni kazalnik",raquo:"Desno obrnjen dvojni kotni narekovaj",frac14:"Ena Äetrtina",frac12:"Ena polovica",frac34:"Tri Äetrtine",iquest:"Obrnjen vpraÅ¡aj",Agrave:"Velika latinska Ärka A s krativcem",Aacute:"Velika latinska Ärka A z ostrivcem",Acirc:"Velika latinska Ärka A s streÅ¡ico",Atilde:"Velika latinska Ärka A z tildo",
+Auml:"Velika latinska Ärka A z diaeresis-om",Aring:"Velika latinska Ärka A z obroÄem",AElig:"Velika latinska Ärka Æ",Ccedil:"Velika latinska Ärka C s cedillo",Egrave:"Velika latinska Ärka E s krativcem",Eacute:"Velika latinska Ärka E z ostrivcem",Ecirc:"Velika latinska Ärka E s streÅ¡ico",Euml:"Velika latinska Ärka E z diaeresis-om",Igrave:"Velika latinska Ärka I s krativcem",Iacute:"Velika latinska Ärka I z ostrivcem",Icirc:"Velika latinska Ärka I s streÅ¡ico",Iuml:"Velika latinska Ärka I z diaeresis-om",
+ETH:"Velika latinska Ärka Eth",Ntilde:"Velika latinska Ärka N s tildo",Ograve:"Velika latinska Ärka O s krativcem",Oacute:"Velika latinska Ärka O z ostrivcem",Ocirc:"Velika latinska Ärka O s streÅ¡ico",Otilde:"Velika latinska Ärka O s tildo",Ouml:"Velika latinska Ärka O z diaeresis-om",times:"Znak za množenje",Oslash:"Velika preÄrtana latinska Ärka O",Ugrave:"Velika latinska Ärka U s krativcem",Uacute:"Velika latinska Ärka U z ostrivcem",Ucirc:"Velika latinska Ärka U s streÅ¡ico",Uuml:"Velika latinska Ärka U z diaeresis-om",
+Yacute:"Velika latinska Ärka Y z ostrivcem",THORN:"Velika latinska Ärka Thorn",szlig:"Mala ostra latinska Ärka s",agrave:"Mala latinska Ärka a s krativcem",aacute:"Mala latinska Ärka a z ostrivcem",acirc:"Mala latinska Ärka a s streÅ¡ico",atilde:"Mala latinska Ärka a s tildo",auml:"Mala latinska Ärka a z diaeresis-om",aring:"Mala latinska Ärka a z obroÄem",aelig:"Mala latinska Ärka æ",ccedil:"Mala latinska Ärka c s cedillo",egrave:"Mala latinska Ärka e s krativcem",eacute:"Mala latinska Ärka e z ostrivcem",
+ecirc:"Mala latinska Ärka e s streÅ¡ico",euml:"Mala latinska Ärka e z diaeresis-om",igrave:"Mala latinska Ärka i s krativcem",iacute:"Mala latinska Ärka i z ostrivcem",icirc:"Mala latinska Ärka i s streÅ¡ico",iuml:"Mala latinska Ärka i z diaeresis-om",eth:"Mala latinska Ärka eth",ntilde:"Mala latinska Ärka n s tildo",ograve:"Mala latinska Ärka o s krativcem",oacute:"Mala latinska Ärka o z ostrivcem",ocirc:"Mala latinska Ärka o s streÅ¡ico",otilde:"Mala latinska Ärka o s tildo",ouml:"Mala latinska Ärka o z diaeresis-om",
+divide:"Znak za deljenje",oslash:"Mala preÄrtana latinska Ärka o",ugrave:"Mala latinska Ärka u s krativcem",uacute:"Mala latinska Ärka u z ostrivcem",ucirc:"Mala latinska Ärka u s streÅ¡ico",uuml:"Mala latinska Ärka u z diaeresis-om",yacute:"Mala latinska Ärka y z ostrivcem",thorn:"Mala latinska Ärka thorn",yuml:"Mala latinska Ärka y z diaeresis-om",OElig:"Velika latinska ligatura OE",oelig:"Mala latinska ligatura oe",372:"Velika latinska Ärka W s streÅ¡ico",374:"Velika latinska Ärka Y s streÅ¡ico",
+373:"Mala latinska Ärka w s streÅ¡ico",375:"Mala latinska Ärka y s streÅ¡ico",sbquo:"Enojni nizki-9 narekovaj",8219:"Enojni visoki-obrnjen-9 narekovaj",bdquo:"Dvojni nizki-9 narekovaj",hellip:"Horizontalni izpust",trade:"Znak blagovne znamke",9658:"ÄŒrni desno-usmerjen kazalec",bull:"Krogla",rarr:"Desno-usmerjena puÅ¡Äica",rArr:"Desno-usmerjena dvojna puÅ¡Äica",hArr:"Leva in desna dvojna puÅ¡Äica",diams:"ÄŒrna kara",asymp:"Skoraj enako"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/sq.js b/js/ckeditor/plugins/specialchar/dialogs/lang/sq.js
new file mode 100644
index 0000000..967a048
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/sq.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","sq",{euro:"Shenja e Euros",lsquo:"Thonjëza majtas me një vi",rsquo:"Thonjëza djathtas me një vi",ldquo:"Thonjëza majtas",rdquo:"Thonjëza djathtas",ndash:"En viza lidhëse",mdash:"Em viza lidhëse",iexcl:"Pikëçuditëse e përmbysur",cent:"Shenja e Centit",pound:"Shejna e Funtit",curren:"Shenja e valutës",yen:"Shenja e Jenit",brvbar:"Viza e këputur",sect:"Shenja e pjesës",uml:"Diaeresis",copy:"Shenja e të drejtave të kopjimit",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Nuk ka shenjë",reg:"Shenja e të regjistruarit",macr:"Macron",deg:"Shenja e shkallës",sup2:"Super-skripta dy",sup3:"Super-skripta tre",acute:"Theks i mprehtë",micro:"Shjenja e Mikros",para:"Pilcrow sign",middot:"Pika e Mesme",cedil:"Hark nën shkronja",sup1:"Super-skripta një",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Thyesa një të katrat",frac12:"Thyesa një të dytat",frac34:"Thyesa tre të katrat",iquest:"Pikëpyetje e përmbysur",Agrave:"Shkronja e madhe latine A me theks të rëndë",
+Aacute:"Shkronja e madhe latine A me theks akute",Acirc:"Shkronja e madhe latine A me theks lakor",Atilde:"Shkronja e madhe latine A me tildë",Auml:"Shkronja e madhe latine A me dy pika",Aring:"Shkronja e madhe latine A me unazë mbi",AElig:"Shkronja e madhe latine Æ",Ccedil:"Shkronja e madhe latine C me hark poshtë",Egrave:"Shkronja e madhe latine E me theks të rëndë",Eacute:"Shkronja e madhe latine E me theks akute",Ecirc:"Shkronja e madhe latine E me theks lakor",Euml:"Shkronja e madhe latine E me dy pika",
+Igrave:"Shkronja e madhe latine I me theks të rëndë",Iacute:"Shkronja e madhe latine I me theks akute",Icirc:"Shkronja e madhe latine I me theks lakor",Iuml:"Shkronja e madhe latine I me dy pika",ETH:"Shkronja e madhe latine Eth",Ntilde:"Shkronja e madhe latine N me tildë",Ograve:"Shkronja e madhe latine O me theks të rëndë",Oacute:"Shkronja e madhe latine O me theks akute",Ocirc:"Shkronja e madhe latine O me theks lakor",Otilde:"Shkronja e madhe latine O me tildë",Ouml:"Shkronja e madhe latine O me dy pika",
+times:"Shenja e shumëzimit",Oslash:"Shkronja e madhe latine O me vizë në mes",Ugrave:"Shkronja e madhe latine U me theks të rëndë",Uacute:"Shkronja e madhe latine U me theks akute",Ucirc:"Shkronja e madhe latine U me theks lakor",Uuml:"Shkronja e madhe latine U me dy pika",Yacute:"Shkronja e madhe latine Y me theks akute",THORN:"Shkronja e madhe latine Thorn",szlig:"Shkronja e vogë latine s e mprehtë",agrave:"Shkronja e vogë latine a me theks të rëndë",aacute:"Shkronja e vogë latine a me theks të mprehtë",
+acirc:"Shkronja e vogël latine a me theks lakor",atilde:"Shkronja e vogël latine a me tildë",auml:"Shkronja e vogël latine a me dy pika",aring:"Shkronja e vogë latine a me unazë mbi",aelig:"Shkronja e vogë latine æ",ccedil:"Shkronja e vogël latine c me hark poshtë",egrave:"Shkronja e vogë latine e me theks të rëndë",eacute:"Shkronja e vogë latine e me theks të mprehtë",ecirc:"Shkronja e vogël latine e me theks lakor",euml:"Shkronja e vogël latine e me dy pika",igrave:"Shkronja e vogë latine i me theks të rëndë",
+iacute:"Shkronja e vogë latine i me theks të mprehtë",icirc:"Shkronja e vogël latine i me theks lakor",iuml:"Shkronja e vogël latine i me dy pika",eth:"Shkronja e vogë latine eth",ntilde:"Shkronja e vogël latine n me tildë",ograve:"Shkronja e vogë latine o me theks të rëndë",oacute:"Shkronja e vogë latine o me theks të mprehtë",ocirc:"Shkronja e vogël latine o me theks lakor",otilde:"Shkronja e vogël latine o me tildë",ouml:"Shkronja e vogël latine o me dy pika",divide:"Shenja ndarëse",oslash:"Shkronja e vogël latine o me vizë në mes",
+ugrave:"Shkronja e vogë latine u me theks të rëndë",uacute:"Shkronja e vogë latine u me theks të mprehtë",ucirc:"Shkronja e vogël latine u me theks lakor",uuml:"Shkronja e vogël latine u me dy pika",yacute:"Shkronja e vogë latine y me theks të mprehtë",thorn:"Shkronja e vogël latine thorn",yuml:"Shkronja e vogël latine y me dy pika",OElig:"Shkronja e madhe e bashkuar latine OE",oelig:"Shkronja e vogël e bashkuar latine oe",372:"Shkronja e madhe latine W me theks lakor",374:"Shkronja e madhe latine Y me theks lakor",
+373:"Shkronja e vogël latine w me theks lakor",375:"Shkronja e vogël latine y me theks lakor",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",trade:"Shenja e Simbolit Tregtarë",9658:"Black right-pointing pointer",bull:"Pulla",rarr:"Shigjeta djathtas",rArr:"Shenja të dyfishta djathtas",hArr:"Shigjeta e dyfishë majtas-djathtas",diams:"Black diamond suit",asymp:"Gati e barabar me"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/sv.js b/js/ckeditor/plugins/specialchar/dialogs/lang/sv.js
new file mode 100644
index 0000000..d177c86
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/sv.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","sv",{euro:"Eurotecken",lsquo:"Enkelt vänster citattecken",rsquo:"Enkelt höger citattecken",ldquo:"Dubbelt vänster citattecken",rdquo:"Dubbelt höger citattecken",ndash:"Snedstreck",mdash:"Långt tankstreck",iexcl:"Inverterad utropstecken",cent:"Centtecken",pound:"Pundtecken",curren:"Valutatecken",yen:"Yentecken",brvbar:"Brutet lodrätt streck",sect:"Paragraftecken",uml:"Diaeresis",copy:"Upphovsrättstecken",ordf:"Feminit ordningstalsindikator",laquo:"Vänsterställt dubbelt vinkelcitationstecken",
+not:"Icke-tecken",reg:"Registrerad",macr:"Macron",deg:"Grader",sup2:"Upphöjt två",sup3:"Upphöjt tre",acute:"Akut accent",micro:"Mikrotecken",para:"Alinea",middot:"Centrerad prick",cedil:"Cedilj",sup1:"Upphöjt en",ordm:"Maskulina ordningsändelsen",raquo:"Högerställt dubbelt vinkelcitationstecken",frac14:"Bråktal - en kvart",frac12:"Bråktal - en halv",frac34:"Bråktal - tre fjärdedelar",iquest:"Inverterat frågetecken",Agrave:"Stort A med grav accent",Aacute:"Stort A med akutaccent",Acirc:"Stort A med circumflex",
+Atilde:"Stort A med tilde",Auml:"Stort A med diaresis",Aring:"Stort A med ring ovan",AElig:"Stort Æ",Ccedil:"Stort C med cedilj",Egrave:"Stort E med grav accent",Eacute:"Stort E med aktuaccent",Ecirc:"Stort E med circumflex",Euml:"Stort E med diaeresis",Igrave:"Stort I med grav accent",Iacute:"Stort I med akutaccent",Icirc:"Stort I med circumflex",Iuml:"Stort I med diaeresis",ETH:"Stort Eth",Ntilde:"Stort N med tilde",Ograve:"Stort O med grav accent",Oacute:"Stort O med aktuaccent",Ocirc:"Stort O med circumflex",
+Otilde:"Stort O med tilde",Ouml:"Stort O med diaeresis",times:"Multiplicera",Oslash:"Stor Ø",Ugrave:"Stort U med grav accent",Uacute:"Stort U med akutaccent",Ucirc:"Stort U med circumflex",Uuml:"Stort U med diaeresis",Yacute:"Stort Y med akutaccent",THORN:"Stort Thorn",szlig:"Litet dubbel-s/Eszett",agrave:"Litet a med grav accent",aacute:"Litet a med akutaccent",acirc:"Litet a med circumflex",atilde:"Litet a med tilde",auml:"Litet a med diaeresis",aring:"Litet a med ring ovan",aelig:"Bokstaven æ",
+ccedil:"Litet c med cedilj",egrave:"Litet e med grav accent",eacute:"Litet e med akutaccent",ecirc:"Litet e med circumflex",euml:"Litet e med diaeresis",igrave:"Litet i med grav accent",iacute:"Litet i med akutaccent",icirc:"LItet i med circumflex",iuml:"Litet i med didaeresis",eth:"Litet eth",ntilde:"Litet n med tilde",ograve:"LItet o med grav accent",oacute:"LItet o med akutaccent",ocirc:"Litet o med circumflex",otilde:"LItet o med tilde",ouml:"Litet o med diaeresis",divide:"Division",oslash:"ø",
+ugrave:"Litet u med grav accent",uacute:"Litet u med akutaccent",ucirc:"LItet u med circumflex",uuml:"Litet u med diaeresis",yacute:"Litet y med akutaccent",thorn:"Litet thorn",yuml:"Litet y med diaeresis",OElig:"Stor ligatur av OE",oelig:"Liten ligatur av oe",372:"Stort W med circumflex",374:"Stort Y med circumflex",373:"Litet w med circumflex",375:"Litet y med circumflex",sbquo:"Enkelt lågt 9-citationstecken",8219:"Enkelt högt bakvänt 9-citationstecken",bdquo:"Dubbelt lågt 9-citationstecken",hellip:"Horisontellt uteslutningstecken",
+trade:"Varumärke",9658:"Svart högervänd pekare",bull:"Listpunkt",rarr:"Högerpil",rArr:"Dubbel högerpil",hArr:"Dubbel vänsterpil",diams:"Svart ruter",asymp:"Ungefär lika med"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/th.js b/js/ckeditor/plugins/specialchar/dialogs/lang/th.js
new file mode 100644
index 0000000..1d43ac9
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/th.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","th",{euro:"Euro sign",lsquo:"Left single quotation mark",rsquo:"Right single quotation mark",ldquo:"Left double quotation mark",rdquo:"Right double quotation mark",ndash:"En dash",mdash:"Em dash",iexcl:"Inverted exclamation mark",cent:"Cent sign",pound:"Pound sign",curren:"สัà¸à¸¥à¸±à¸à¸©à¸“์สà¸à¸¸à¸¥à¹€à¸‡à¸´à¸™",yen:"สัà¸à¸¥à¸±à¸à¸©à¸“์เงินเยน",brvbar:"Broken bar",sect:"Section sign",uml:"Diaeresis",copy:"Copyright sign",ordf:"Feminine ordinal indicator",laquo:"Left-pointing double angle quotation mark",
+not:"Not sign",reg:"Registered sign",macr:"Macron",deg:"Degree sign",sup2:"Superscript two",sup3:"Superscript three",acute:"Acute accent",micro:"Micro sign",para:"Pilcrow sign",middot:"Middle dot",cedil:"Cedilla",sup1:"Superscript one",ordm:"Masculine ordinal indicator",raquo:"Right-pointing double angle quotation mark",frac14:"Vulgar fraction one quarter",frac12:"Vulgar fraction one half",frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",
+Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",
+Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",
+Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",
+aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",
+ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",
+yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",
+trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"สัà¸à¸¥à¸±à¸à¸©à¸“์หัวข้อย่อย",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/tr.js b/js/ckeditor/plugins/specialchar/dialogs/lang/tr.js
new file mode 100644
index 0000000..65c1a19
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/tr.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","tr",{euro:"Euro işareti",lsquo:"Sol tek tırnak işareti",rsquo:"Sağ tek tırnak işareti",ldquo:"Sol çift tırnak işareti",rdquo:"Sağ çift tırnak işareti",ndash:"En tire",mdash:"Em tire",iexcl:"Ters ünlem işareti",cent:"Cent işareti",pound:"Pound işareti",curren:"Para birimi işareti",yen:"Yen işareti",brvbar:"Kırık bar",sect:"Bölüm işareti",uml:"İki sesli harfin ayrılması",copy:"Telif hakkı işareti",ordf:"Dişil sıralı gösterge",laquo:"Sol-işaret çift açı tırnak işareti",
+not:"Not işareti",reg:"Kayıtlı işareti",macr:"Makron",deg:"Derece işareti",sup2:"İkili üstsimge",sup3:"Üçlü üstsimge",acute:"Aksan işareti",micro:"Mikro işareti",para:"Pilcrow işareti",middot:"Orta nokta",cedil:"Kedilla",sup1:"Üstsimge",ordm:"Eril sıralı gösterge",raquo:"Sağ işaret çift açı tırnak işareti",frac14:"Bayağı kesrin dörtte biri",frac12:"Bayağı kesrin bir yarım",frac34:"Bayağı kesrin dörtte üç",iquest:"Ters soru işareti",Agrave:"Aksanlı latin harfi",Aacute:"Aşırı aksanıyla Latin harfi",
+Acirc:"Çarpık Latin harfi",Atilde:"Tilde latin harfi",Auml:"Sesli harf ayrılımlıı latin harfi",Aring:"Halkalı latin büyük A harfi",AElig:"Latin büyük Æ harfi",Ccedil:"Latin büyük C harfi ile kedilla",Egrave:"Aksanlı latin büyük E harfi",Eacute:"Aşırı vurgulu latin büyük E harfi",Ecirc:"Çarpık latin büyük E harfi",Euml:"Sesli harf ayrılımlıı latin büyük E harfi",Igrave:"Aksanlı latin büyük I harfi",Iacute:"Aşırı aksanlı latin büyük I harfi",Icirc:"Çarpık latin büyük I harfi",Iuml:"Sesli harf ayrılımlıı latin büyük I harfi",
+ETH:"Latin büyük Eth harfi",Ntilde:"Tildeli latin büyük N harfi",Ograve:"Aksanlı latin büyük O harfi",Oacute:"Aşırı aksanlı latin büyük O harfi",Ocirc:"Çarpık latin büyük O harfi",Otilde:"Tildeli latin büyük O harfi",Ouml:"Sesli harf ayrılımlı latin büyük O harfi",times:"Çarpma işareti",Oslash:"Vurgulu latin büyük O harfi",Ugrave:"Aksanlı latin büyük U harfi",Uacute:"Aşırı aksanlı latin büyük U harfi",Ucirc:"Çarpık latin büyük U harfi",Uuml:"Sesli harf ayrılımlı latin büyük U harfi",Yacute:"Aşırı aksanlı latin büyük Y harfi",
+THORN:"Latin büyük Thorn harfi",szlig:"Latin küçük keskin s harfi",agrave:"Aksanlı latin küçük a harfi",aacute:"Aşırı aksanlı latin küçük a harfi",acirc:"Çarpık latin küçük a harfi",atilde:"Tildeli latin küçük a harfi",auml:"Sesli harf ayrılımlı latin küçük a harfi",aring:"Halkalı latin küçük a harfi",aelig:"Latin büyük æ harfi",ccedil:"Kedillalı latin küçük c harfi",egrave:"Aksanlı latin küçük e harfi",eacute:"Aşırı aksanlı latin küçük e harfi",ecirc:"Çarpık latin küçük e harfi",euml:"Sesli harf ayrılımlı latin küçük e harfi",
+igrave:"Aksanlı latin küçük i harfi",iacute:"Aşırı aksanlı latin küçük i harfi",icirc:"Çarpık latin küçük i harfi",iuml:"Sesli harf ayrılımlı latin küçük i harfi",eth:"Latin küçük eth harfi",ntilde:"Tildeli latin küçük n harfi",ograve:"Aksanlı latin küçük o harfi",oacute:"Aşırı aksanlı latin küçük o harfi",ocirc:"Çarpık latin küçük o harfi",otilde:"Tildeli latin küçük o harfi",ouml:"Sesli harf ayrılımlı latin küçük o harfi",divide:"Bölme işareti",oslash:"Vurgulu latin küçük o harfi",ugrave:"Aksanlı latin küçük u harfi",
+uacute:"Aşırı aksanlı latin küçük u harfi",ucirc:"Çarpık latin küçük u harfi",uuml:"Sesli harf ayrılımlı latin küçük u harfi",yacute:"Aşırı aksanlı latin küçük y harfi",thorn:"Latin küçük thorn harfi",yuml:"Sesli harf ayrılımlı latin küçük y harfi",OElig:"Latin büyük bağlı OE harfi",oelig:"Latin küçük bağlı oe harfi",372:"Çarpık latin büyük W harfi",374:"Çarpık latin büyük Y harfi",373:"Çarpık latin küçük w harfi",375:"Çarpık latin küçük y harfi",sbquo:"Tek düşük-9 tırnak işareti",8219:"Tek yüksek-ters-9 tırnak işareti",
+bdquo:"Çift düşük-9 tırnak işareti",hellip:"Yatay elips",trade:"Marka tescili işareti",9658:"Siyah sağ işaret işaretçisi",bull:"Koyu nokta",rarr:"Sağa doğru ok",rArr:"Sağa doğru çift ok",hArr:"Sol, sağ çift ok",diams:"Siyah elmas takımı",asymp:"Hemen hemen eşit"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/tt.js b/js/ckeditor/plugins/specialchar/dialogs/lang/tt.js
new file mode 100644
index 0000000..303d655
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/tt.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","tt",{euro:"Евро тамгаÑÑ‹",lsquo:"Сул бер иңле куштырнаклар",rsquo:"Уң бер иңле куштырнаклар",ldquo:"Сул ике иңле куштырнаклар",rdquo:"Уң ике иңле куштырнаклар",ndash:"КыÑка Ñызык",mdash:"Озын Ñызык",iexcl:"Әйләндерелгән өндәү билгеÑе",cent:"Цент тамгаÑÑ‹",pound:"Фунт тамгаÑÑ‹",curren:"Ðкча берәмлеге тамгаÑÑ‹",yen:"Иена тамгаÑÑ‹",brvbar:"Broken bar",sect:"Параграф билгеÑе",uml:"ДиерезиÑ",copy:"Хокук иÑÑе булу билгеÑе",ordf:"Feminine ordinal indicator",laquo:"Ðчылучы чыршыÑыман Ò—Ó™Ñ",
+not:"Юклык ишарəÑе",reg:"Теркәләнгән булу билгеÑе",macr:"Макрон",deg:"Ð“Ñ€Ð°Ð´ÑƒÑ Ð±Ð¸Ð»Ð³ÐµÑе",sup2:"Икенче Ó©Ñке индекÑ",sup3:"Өченче Ó©Ñке индекÑ",acute:"БаÑым билгеÑе",micro:"Микро билгеÑе",para:"Параграф билгеÑе",middot:"Уртадагы нокта",cedil:"Седиль",sup1:"Беренче Ó©Ñке индекÑ",ordm:"Masculine ordinal indicator",raquo:"Ябылучы чыршыÑыман Ò—Ó™Ñ",frac14:"Гади дүрттән бер билгеÑе",frac12:"Гади икедән бер билгеÑе",frac34:"Гади дүрттән өч билгеÑе",iquest:"Әйләндерелгән өндәү билгеÑе",Agrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин A баш хәрефе",
+Aacute:"БаÑым билгеÑе белән латин A баш хәрефе",Acirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин A баш хәрефе",Atilde:"Тильда белән латин A баш хәрефе",Auml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин A баш хәрефе",Aring:"Ó¨Ñтендә боҗра булган латин A баш хәрефе",AElig:"Латин Æ баш хәрефе",Ccedil:"Седиль белән латин C баш хәрефе",Egrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин E баш хәрефе",Eacute:"БаÑым билгеÑе белән латин E баш хәрефе",Ecirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин E баш хәрефе",Euml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин E баш хәрефе",Igrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин I баш хәрефе",
+Iacute:"БаÑым билгеÑе белән латин I баш хәрефе",Icirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин I баш хәрефе",Iuml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин I баш хәрефе",ETH:"Латин Eth баш хәрефе",Ntilde:"Тильда белән латин N баш хәрефе",Ograve:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин O баш хәрефе",Oacute:"БаÑым билгеÑе белән латин O баш хәрефе",Ocirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин O баш хәрефе",Otilde:"Тильда белән латин O баш хәрефе",Ouml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин O баш хәрефе",times:"Тапкырлау билгеÑе",Oslash:"Сызык белән латин O баш хәрефе",Ugrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин U баш хәрефе",
+Uacute:"БаÑым билгеÑе белән латин U баш хәрефе",Ucirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин U баш хәрефе",Uuml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин U баш хәрефе",Yacute:"БаÑым билгеÑе белән латин Y баш хәрефе",THORN:"Латин Thorn баш хәрефе",szlig:"Латин beta юл хәрефе",agrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин a юл хәрефе",aacute:"БаÑым билгеÑе белән латин a юл хәрефе",acirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин a юл хәрефе",atilde:"Тильда белән латин a юл хәрефе",auml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин a юл хәрефе",aring:"Ó¨Ñтендә боҗра булган латин a юл хәрефе",aelig:"Латин æ юл хәрефе",
+ccedil:"Седиль белән латин c юл хәрефе",egrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин e юл хәрефе",eacute:"БаÑым билгеÑе белән латин e юл хәрефе",ecirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин e юл хәрефе",euml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин e юл хәрефе",igrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин i юл хәрефе",iacute:"БаÑым билгеÑе белән латин i юл хәрефе",icirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин i юл хәрефе",iuml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин i юл хәрефе",eth:"Латин eth юл хәрефе",ntilde:"Тильда белән латин n юл хәрефе",ograve:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин o юл хәрефе",oacute:"БаÑым билгеÑе белән латин o юл хәрефе",
+ocirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин o юл хәрефе",otilde:"Тильда белән латин o юл хәрефе",ouml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин o юл хәрефе",divide:"Бүлү билгеÑе",oslash:"Сызык белән латин o юл хәрефе",ugrave:"Ð“Ñ€Ð°Ð²Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин u юл хәрефе",uacute:"БаÑым билгеÑе белән латин u юл хәрефе",ucirc:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин u юл хәрефе",uuml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин u юл хәрефе",yacute:"БаÑым билгеÑе белән латин y юл хәрефе",thorn:"Латин thorn юл хәрефе",yuml:"Ð”Ð¸ÐµÑ€ÐµÐ·Ð¸Ñ Ð±ÐµÐ»Ó™Ð½ латин y юл хәрефе",OElig:"Латин лигатура OE баш хәрефе",
+oelig:"Латин лигатура oe юл хәрефе",372:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин W баш хәрефе",374:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин Y баш хәрефе",373:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин w юл хәрефе",375:"Ð¦Ð¸Ñ€ÐºÑƒÐ¼Ñ„Ð»ÐµÐºÑ Ð±ÐµÐ»Ó™Ð½ латин y юл хәрефе",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Ятма ÑллипÑ",trade:"Сәүдә маркаÑÑ‹ билгеÑе",9658:"Black right-pointing pointer",bull:"Маркер",rarr:"Уң Ñкка ук",rArr:"Уң Ñкка икеләтә ук",hArr:"Ике Ñкка икеләтә ук",diams:"Black diamond suit",
+asymp:"Ñкынча"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/ug.js b/js/ckeditor/plugins/specialchar/dialogs/lang/ug.js
new file mode 100644
index 0000000..757e83f
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/ug.js
@@ -0,0 +1,13 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","ug",{euro:"ياۋرو بەلگىسى",lsquo:"يالاڭ Ù¾Û•Ø´ سول",rsquo:"يالاڭ Ù¾Û•Ø´ ئوڭ",ldquo:"قوش Ù¾Û•Ø´ سول",rdquo:"قوش Ù¾Û•Ø´ ئوڭ",ndash:"سىزىقچە",mdash:"سىزىق",iexcl:"ئۈندەش",cent:"تىيىن بەلگىسى",pound:"Ùوند ستÛرلىڭ",curren:"Ù¾Û‡Ù„ بەلگىسى",yen:"ياپونىيە يىنى",brvbar:"ئۈزۈك بالداق",sect:"پاراگرا٠بەلگىسى",uml:"تاۋۇش ئايرىش بەلگىسى",copy:"نەشر ھوقۇقى بەلگىسى",ordf:"Feminine ordinal indicator",laquo:"قوش تىرناق سول",not:"غەيرى بەلگە",reg:"خەتلەتكەن تاۋار ماركىسى",macr:"سوزۇش بەلگىسى",
+deg:"گىرادۇس بەلگىسى",sup2:"يۇقىرى ئىندÛكىس 2",sup3:"يۇقىرى ئىندÛكىس 3",acute:"ئۇرغۇ بەلگىسى",micro:"Micro sign",para:"ئابزاس بەلگىسى",middot:"ئوتتۇرا Ú†Ûكىت",cedil:"ئاستىغا قوشۇلىدىغان بەلگە",sup1:"يۇقىرى ئىندÛكىس 1",ordm:"Masculine ordinal indicator",raquo:"قوش تىرناق ئوڭ",frac14:"ئاددىي كەسىر تۆتتىن بىر",frac12:"ئاددىي كەسىر ئىككىدىن بىر",frac34:"ئاددىي كەسىر ئۈچتىن تۆرت",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",Aacute:"Latin capital letter A with acute accent",
+Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"Latin capital letter A with diaeresis",Aring:"Latin capital letter A with ring above",AElig:"Latin Capital letter Æ",Ccedil:"Latin capital letter C with cedilla",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",Igrave:"Latin capital letter I with grave accent",Iacute:"Latin capital letter I with acute accent",
+Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"قوش پەش ئوڭ",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",times:"Multiplication sign",Oslash:"Latin capital letter O with stroke",Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",
+Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",auml:"Latin small letter a with diaeresis",aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",
+ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",eth:"Latin small letter eth",ntilde:"تىك موللاق سوئال بەلگىسى",ograve:"Latin small letter o with grave accent",
+oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"بۆلۈش بەلگىسى",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",
+yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",hellip:"Horizontal ellipsis",trade:"خەتلەتكەن تاۋار ماركىسى بەلگىسى",9658:"Black right-pointing pointer",
+bull:"Bullet",rarr:"ئوڭ يا ئوق",rArr:"ئوڭ قوش سىزىق يا ئوق",hArr:"ئوڭ سول قوش سىزىق يا ئوق",diams:"ئۇيۇل غىچ",asymp:"تەخمىنەن تەڭ"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/uk.js b/js/ckeditor/plugins/specialchar/dialogs/lang/uk.js
new file mode 100644
index 0000000..2343e56
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/uk.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","uk",{euro:"Знак євро",lsquo:"Ліві одинарні лапки",rsquo:"Праві одинарні лапки",ldquo:"Ліві подвійні лапки",rdquo:"Праві подвійні лапки",ndash:"Середнє тире",mdash:"Довге тире",iexcl:"Перевернутий знак оклику",cent:"Знак цента",pound:"Знак фунта",curren:"Знак валюти",yen:"Знак єни",brvbar:"ПереривчаÑта вертикальна лініÑ",sect:"Знак параграфу",uml:"Умлаут",copy:"Знак авторÑьких прав",ordf:"Жіночий порÑдковий вказівник",laquo:"ліві вказівні подвійні кутові дужки",
+not:"ЗапереченнÑ",reg:"Знак охорони Ñуміжних прав",macr:"Макрон",deg:"Знак градуÑа",sup2:"два у верхньому індекÑÑ–",sup3:"три у верхньому індекÑÑ–",acute:"Знак акута",micro:"Знак мікро",para:"Знак абзацу",middot:"Інтерпункт",cedil:"Седиль",sup1:"Один у верхньому індекÑÑ–",ordm:"Чоловічий порÑдковий вказівник",raquo:"праві вказівні подвійні кутові дужки",frac14:"Одна четвертина",frac12:"Одна друга",frac34:"три четвертих",iquest:"Перевернутий знак питаннÑ",Agrave:"Велика латинÑька A з гравіÑом",Aacute:"Велика латинÑька Рз акутом",
+Acirc:"Велика латинÑька Рз циркумфлекÑом",Atilde:"Велика латинÑька Рз тильдою",Auml:"Велике латинÑьке Рз умлаутом",Aring:"Велика латинÑька A з кільцем згори",AElig:"Велика латинÑька Æ",Ccedil:"Велика латинÑька C з Ñедиллю",Egrave:"Велика латинÑька E з гравіÑом",Eacute:"Велика латинÑька E з акутом",Ecirc:"Велика латинÑька E з циркумфлекÑом",Euml:"Велика латинÑька Рз умлаутом",Igrave:"Велика латинÑька I з гравіÑом",Iacute:"Велика латинÑька I з акутом",Icirc:"Велика латинÑька I з циркумфлекÑом",
+Iuml:"Велика латинÑька І з умлаутом",ETH:"Велика латинÑька Eth",Ntilde:"Велика латинÑька N з тильдою",Ograve:"Велика латинÑька O з гравіÑом",Oacute:"Велика латинÑька O з акутом",Ocirc:"Велика латинÑька O з циркумфлекÑом",Otilde:"Велика латинÑька O з тильдою",Ouml:"Велика латинÑька О з умлаутом",times:"Знак множеннÑ",Oslash:"Велика латинÑька перекреÑлена O ",Ugrave:"Велика латинÑька U з гравіÑом",Uacute:"Велика латинÑька U з акутом",Ucirc:"Велика латинÑька U з циркумфлекÑом",Uuml:"Велика латинÑька U з умлаутом",
+Yacute:"Велика латинÑька Y з акутом",THORN:"Велика латинÑька Торн",szlig:"Мала латинÑька еÑцет",agrave:"Мала латинÑька a з гравіÑом",aacute:"Мала латинÑька a з акутом",acirc:"Мала латинÑька a з циркумфлекÑом",atilde:"Мала латинÑька a з тильдою",auml:"Мала латинÑька a з умлаутом",aring:"Мала латинÑька a з кільцем згори",aelig:"Мала латинÑька æ",ccedil:"Мала латинÑька C з Ñедиллю",egrave:"Мала латинÑька e з гравіÑом",eacute:"Мала латинÑька e з акутом",ecirc:"Мала латинÑька e з циркумфлекÑом",euml:"Мала латинÑька e з умлаутом",
+igrave:"Мала латинÑька i з гравіÑом",iacute:"Мала латинÑька i з акутом",icirc:"Мала латинÑька i з циркумфлекÑом",iuml:"Мала латинÑька i з умлаутом",eth:"Мала латинÑька Eth",ntilde:"Мала латинÑька n з тильдою",ograve:"Мала латинÑька o з гравіÑом",oacute:"Мала латинÑька o з акутом",ocirc:"Мала латинÑька o з циркумфлекÑом",otilde:"Мала латинÑька o з тильдою",ouml:"Мала латинÑька o з умлаутом",divide:"Знак діленнÑ",oslash:"Мала латинÑька перекреÑлена o",ugrave:"Мала латинÑька u з гравіÑом",uacute:"Мала латинÑька u з акутом",
+ucirc:"Мала латинÑька u з циркумфлекÑом",uuml:"Мала латинÑька u з умлаутом",yacute:"Мала латинÑька y з акутом",thorn:"Мала латинÑька торн",yuml:"Мала латинÑька y з умлаутом",OElig:"Велика латинÑька лігатура OE",oelig:"Мала латинÑька лігатура oe",372:"Велика латинÑька W з циркумфлекÑом",374:"Велика латинÑька Y з циркумфлекÑом",373:"Мала латинÑька w з циркумфлекÑом",375:"Мала латинÑька y з циркумфлекÑом",sbquo:"Одиничні нижні лабки",8219:"Верхні одиничні обернені лабки",bdquo:"Подвійні нижні лабки",
+hellip:"Три крапки",trade:"Знак торгової марки",9658:"Чорний правий вказівник",bull:"Маркер ÑпиÑку",rarr:"Стрілка вправо",rArr:"Подвійна Ñтрілка вправо",hArr:"Подвійна Ñтрілка вліво-вправо",diams:"Чорний діамонт",asymp:"Ðаближено дорівнює"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/vi.js b/js/ckeditor/plugins/specialchar/dialogs/lang/vi.js
new file mode 100644
index 0000000..a71305b
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/vi.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","vi",{euro:"Ký hiệu Euro",lsquo:"Dấu ngoặc Ä‘Æ¡n trái",rsquo:"Dấu ngoặc Ä‘Æ¡n phải",ldquo:"Dấu ngoặc đôi trái",rdquo:"Dấu ngoặc đôi phải",ndash:"Gạch ngang tiếng anh",mdash:"Gạch ngang Em",iexcl:"Chuyển đổi dấu chấm than",cent:"Ký tá»± tiá»n Mỹ",pound:"Ký tá»± tiá»n Anh",curren:"Ký tá»± tiá»n tệ",yen:"Ký tá»± tiá»n Yên Nhật",brvbar:"Thanh há»ng",sect:"Ký tá»± khu vá»±c",uml:"Dấu tách đôi",copy:"Ký tá»± bản quyá»n",ordf:"Phần chỉ thị giống cái",laquo:"Chá»n dấu ngoặc đôi trái",not:"Không có ký tá»±",
+reg:"Ký tá»± đăng ký",macr:"Dấu nguyên âm dài",deg:"Ký tá»± Ä‘á»™",sup2:"Chữ trồi lên trên dạng 2",sup3:"Chữ trồi lên trên dạng 3",acute:"Dấu trá»ng âm",micro:"Ký tá»± micro",para:"Ký tá»± Ä‘oạn văn",middot:"Dấu chấm tròn",cedil:"Dấu móc lÆ°á»›i",sup1:"Ký tá»± trồi lên cấp 1",ordm:"Ký tá»± biểu hiện giống Ä‘á»±c",raquo:"Chá»n dấu ngoặc đôi phải",frac14:"Tỉ lệ má»™t phần tÆ°",frac12:"Tỉ lệ má»™t ná»­a",frac34:"Tỉ lệ ba phần tÆ°",iquest:"Chuyển đổi dấu chấm há»i",Agrave:"Ký tá»± la-tinh viết hoa A vá»›i dấu huyá»n",Aacute:"Ký tá»± la-tinh viết hoa A vá»›i dấu sắc",
+Acirc:"Ký tá»± la-tinh viết hoa A vá»›i dấu mÅ©",Atilde:"Ký tá»± la-tinh viết hoa A vá»›i dấu ngã",Auml:"Ký tá»± la-tinh viết hoa A vá»›i dấu hai chấm trên đầu",Aring:"Ký tá»± la-tinh viết hoa A vá»›i biểu tượng vòng tròn trên đầu",AElig:"Ký tá»± la-tinh viết hoa của Æ",Ccedil:"Ký tá»± la-tinh viết hoa C vá»›i dấu móc bên dÆ°á»›i",Egrave:"Ký tá»± la-tinh viết hoa E vá»›i dấu huyá»n",Eacute:"Ký tá»± la-tinh viết hoa E vá»›i dấu sắc",Ecirc:"Ký tá»± la-tinh viết hoa E vá»›i dấu mÅ©",Euml:"Ký tá»± la-tinh viết hoa E vá»›i dấu hai chấm trên đầu",
+Igrave:"Ký tá»± la-tinh viết hoa I vá»›i dấu huyá»n",Iacute:"Ký tá»± la-tinh viết hoa I vá»›i dấu sắc",Icirc:"Ký tá»± la-tinh viết hoa I vá»›i dấu mÅ©",Iuml:"Ký tá»± la-tinh viết hoa I vá»›i dấu hai chấm trên đầu",ETH:"Viết hoa của ký tá»± Eth",Ntilde:"Ký tá»± la-tinh viết hoa N vá»›i dấu ngã",Ograve:"Ký tá»± la-tinh viết hoa O vá»›i dấu huyá»n",Oacute:"Ký tá»± la-tinh viết hoa O vá»›i dấu sắc",Ocirc:"Ký tá»± la-tinh viết hoa O vá»›i dấu mÅ©",Otilde:"Ký tá»± la-tinh viết hoa O vá»›i dấu ngã",Ouml:"Ký tá»± la-tinh viết hoa O vá»›i dấu hai chấm trên đầu",
+times:"Ký tá»± phép toán nhân",Oslash:"Ký tá»± la-tinh viết hoa A vá»›i dấu ngã xuống",Ugrave:"Ký tá»± la-tinh viết hoa U vá»›i dấu huyá»n",Uacute:"Ký tá»± la-tinh viết hoa U vá»›i dấu sắc",Ucirc:"Ký tá»± la-tinh viết hoa U vá»›i dấu mÅ©",Uuml:"Ký tá»± la-tinh viết hoa U vá»›i dấu hai chấm trên đầu",Yacute:"Ký tá»± la-tinh viết hoa Y vá»›i dấu sắc",THORN:"Phần viết hoa của ký tá»± Thorn",szlig:"Ký tá»± viết nhá» la-tinh của chữ s",agrave:"Ký tá»± la-tinh thÆ°á»ng vá»›i dấu huyá»n",aacute:"Ký tá»± la-tinh thÆ°á»ng vá»›i dấu sắc",acirc:"Ký tá»± la-tinh thÆ°á»ng vá»›i dấu mÅ©",
+atilde:"Ký tá»± la-tinh thÆ°á»ng vá»›i dấu ngã",auml:"Ký tá»± la-tinh thÆ°á»ng vá»›i dấu hai chấm trên đầu",aring:"Ký tá»± la-tinh viết thÆ°á»ng vá»›i biểu tượng vòng tròn trên đầu",aelig:"Ký tá»± la-tinh viết thÆ°á»ng của æ",ccedil:"Ký tá»± la-tinh viết thÆ°á»ng của c vá»›i dấu móc bên dÆ°á»›i",egrave:"Ký tá»± la-tinh viết thÆ°á»ng e vá»›i dấu huyá»n",eacute:"Ký tá»± la-tinh viết thÆ°á»ng e vá»›i dấu sắc",ecirc:"Ký tá»± la-tinh viết thÆ°á»ng e vá»›i dấu mÅ©",euml:"Ký tá»± la-tinh viết thÆ°á»ng e vá»›i dấu hai chấm trên đầu",igrave:"Ký tá»± la-tinh viết thÆ°á»ng i vá»›i dấu huyá»n",
+iacute:"Ký tá»± la-tinh viết thÆ°á»ng i vá»›i dấu sắc",icirc:"Ký tá»± la-tinh viết thÆ°á»ng i vá»›i dấu mÅ©",iuml:"Ký tá»± la-tinh viết thÆ°á»ng i vá»›i dấu hai chấm trên đầu",eth:"Ký tá»± la-tinh viết thÆ°á»ng của eth",ntilde:"Ký tá»± la-tinh viết thÆ°á»ng n vá»›i dấu ngã",ograve:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu huyá»n",oacute:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu sắc",ocirc:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu mÅ©",otilde:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu ngã",ouml:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu hai chấm trên đầu",
+divide:"Ký hiệu phép tính chia",oslash:"Ký tá»± la-tinh viết thÆ°á»ng o vá»›i dấu ngã",ugrave:"Ký tá»± la-tinh viết thÆ°á»ng u vá»›i dấu huyá»n",uacute:"Ký tá»± la-tinh viết thÆ°á»ng u vá»›i dấu sắc",ucirc:"Ký tá»± la-tinh viết thÆ°á»ng u vá»›i dấu mÅ©",uuml:"Ký tá»± la-tinh viết thÆ°á»ng u vá»›i dấu hai chấm trên đầu",yacute:"Ký tá»± la-tinh viết thÆ°á»ng y vá»›i dấu sắc",thorn:"Ký tá»± la-tinh viết thÆ°á»ng của chữ thorn",yuml:"Ký tá»± la-tinh viết thÆ°á»ng y vá»›i dấu hai chấm trên đầu",OElig:"Ký tá»± la-tinh viết hoa gạch nối OE",oelig:"Ký tá»± la-tinh viết thÆ°á»ng gạch nối OE",
+372:"Ký tá»± la-tinh viết hoa W vá»›i dấu mÅ©",374:"Ký tá»± la-tinh viết hoa Y vá»›i dấu mÅ©",373:"Ký tá»± la-tinh viết thÆ°á»ng w vá»›i dấu mÅ©",375:"Ký tá»± la-tinh viết thÆ°á»ng y vá»›i dấu mÅ©",sbquo:"Dấu ngoặc Ä‘Æ¡n thấp số-9",8219:"Dấu ngoặc Ä‘Æ¡n đảo ngược số-9",bdquo:"Gấp đôi dấu ngoặc Ä‘Æ¡n số-9",hellip:"TÄ©nh dược chiá»u ngang",trade:"Ký tá»± thÆ°Æ¡ng hiệu",9658:"Ký tá»± trá» vá» hÆ°á»›ng bên phải màu Ä‘en",bull:"Ký hiệu",rarr:"MÅ©i tên hÆ°á»›ng bên phải",rArr:"MÅ©i tên hÆ°á»›ng bên phải dạng đôi",hArr:"MÅ©i tên hÆ°á»›ng bên trái dạng đôi",diams:"Ký hiệu hình thoi",
+asymp:"Gần bằng với"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/zh-cn.js b/js/ckeditor/plugins/specialchar/dialogs/lang/zh-cn.js
new file mode 100644
index 0000000..d794a3d
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/zh-cn.js
@@ -0,0 +1,9 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","zh-cn",{euro:"欧元符å·",lsquo:"å·¦å•å¼•å·",rsquo:"å³å•å¼•å·",ldquo:"å·¦åŒå¼•å·",rdquo:"å³åŒå¼•å·",ndash:"短划线",mdash:"长划线",iexcl:"ç«–ç¿»å¹å·",cent:"分å¸ç¬¦å·",pound:"英镑符å·",curren:"è´§å¸ç¬¦å·",yen:"日元符å·",brvbar:"é—´æ–­æ¡",sect:"节标记",uml:"分音符",copy:"版æƒæ‰€æœ‰æ ‡è®°",ordf:"阴性顺åºæŒ‡ç¤ºç¬¦",laquo:"左指åŒå°–引å·",not:"éžæ ‡è®°",reg:"注册标记",macr:"长音符",deg:"度标记",sup2:"上标二",sup3:"上标三",acute:"é”音符",micro:"微符",para:"段è½æ ‡è®°",middot:"中间点",cedil:"下加符",sup1:"上标一",ordm:"阳性顺åºæŒ‡ç¤ºç¬¦",raquo:"å³æŒ‡åŒå°–引å·",frac14:"普通分数四分之一",frac12:"普通分数二分之一",frac34:"普通分数四分之三",iquest:"竖翻问å·",
+Agrave:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ A",Aacute:"带é”音符的拉ä¸æ–‡å¤§å†™å­—æ¯ A",Acirc:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ A",Atilde:"带颚化符的拉ä¸æ–‡å¤§å†™å­—æ¯ A",Auml:"带分音符的拉ä¸æ–‡å¤§å†™å­—æ¯ A",Aring:"带上圆圈的拉ä¸æ–‡å¤§å†™å­—æ¯ A",AElig:"拉ä¸æ–‡å¤§å†™å­—æ¯ Ae",Ccedil:"带下加符的拉ä¸æ–‡å¤§å†™å­—æ¯ C",Egrave:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ E",Eacute:"带é”音符的拉ä¸æ–‡å¤§å†™å­—æ¯ E",Ecirc:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ E",Euml:"带分音符的拉ä¸æ–‡å¤§å†™å­—æ¯ E",Igrave:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ I",Iacute:"带é”音符的拉ä¸æ–‡å¤§å†™å­—æ¯ I",Icirc:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ I",Iuml:"带分音符的拉ä¸æ–‡å¤§å†™å­—æ¯ I",ETH:"拉ä¸æ–‡å¤§å†™å­—æ¯ Eth",Ntilde:"带颚化符的拉ä¸æ–‡å¤§å†™å­—æ¯ N",Ograve:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ O",Oacute:"带é”音符的拉ä¸æ–‡å¤§å†™å­—æ¯ O",Ocirc:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ O",Otilde:"带颚化符的拉ä¸æ–‡å¤§å†™å­—æ¯ O",
+Ouml:"带分音符的拉ä¸æ–‡å¤§å†™å­—æ¯ O",times:"乘å·",Oslash:"带粗线的拉ä¸æ–‡å¤§å†™å­—æ¯ O",Ugrave:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ U",Uacute:"带é”音符的拉ä¸æ–‡å¤§å†™å­—æ¯ U",Ucirc:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ U",Uuml:"带分音符的拉ä¸æ–‡å¤§å†™å­—æ¯ U",Yacute:"带抑音符的拉ä¸æ–‡å¤§å†™å­—æ¯ Y",THORN:"拉ä¸æ–‡å¤§å†™å­—æ¯ Thorn",szlig:"拉ä¸æ–‡å°å†™å­—æ¯æ¸…音 S",agrave:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ A",aacute:"带é”音符的拉ä¸æ–‡å°å†™å­—æ¯ A",acirc:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ A",atilde:"带颚化符的拉ä¸æ–‡å°å†™å­—æ¯ A",auml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ A",aring:"带上圆圈的拉ä¸æ–‡å°å†™å­—æ¯ A",aelig:"拉ä¸æ–‡å°å†™å­—æ¯ Ae",ccedil:"带下加符的拉ä¸æ–‡å°å†™å­—æ¯ C",egrave:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ E",eacute:"带é”音符的拉ä¸æ–‡å°å†™å­—æ¯ E",ecirc:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ E",euml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ E",igrave:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ I",
+iacute:"带é”音符的拉ä¸æ–‡å°å†™å­—æ¯ I",icirc:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ I",iuml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ I",eth:"拉ä¸æ–‡å°å†™å­—æ¯ Eth",ntilde:"带颚化符的拉ä¸æ–‡å°å†™å­—æ¯ N",ograve:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ O",oacute:"带é”音符的拉ä¸æ–‡å°å†™å­—æ¯ O",ocirc:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ O",otilde:"带颚化符的拉ä¸æ–‡å°å†™å­—æ¯ O",ouml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ O",divide:"除å·",oslash:"带粗线的拉ä¸æ–‡å°å†™å­—æ¯ O",ugrave:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ U",uacute:"带é”音符的拉ä¸æ–‡å°å†™å­—æ¯ U",ucirc:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ U",uuml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ U",yacute:"带抑音符的拉ä¸æ–‡å°å†™å­—æ¯ Y",thorn:"拉ä¸æ–‡å°å†™å­—æ¯ Thorn",yuml:"带分音符的拉ä¸æ–‡å°å†™å­—æ¯ Y",OElig:"拉ä¸æ–‡å¤§å†™è¿žå­— Oe",oelig:"拉ä¸æ–‡å°å†™è¿žå­— Oe",372:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ W",374:"带扬抑符的拉ä¸æ–‡å¤§å†™å­—æ¯ Y",
+373:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ W",375:"带扬抑符的拉ä¸æ–‡å°å†™å­—æ¯ Y",sbquo:"å•ä¸‹ 9 形引å·",8219:"å•é«˜æ¨ªç¿» 9 形引å·",bdquo:"åŒä¸‹ 9 形引å·",hellip:"æ°´å¹³çœç•¥å·",trade:"商标标志",9658:"实心å³æŒ‡æŒ‡é’ˆ",bull:"加é‡å·",rarr:"å‘å³ç®­å¤´",rArr:"å‘å³åŒçº¿ç®­å¤´",hArr:"å·¦å³åŒçº¿ç®­å¤´",diams:"实心方å—纸牌",asymp:"约等于"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/lang/zh.js b/js/ckeditor/plugins/specialchar/dialogs/lang/zh.js
new file mode 100644
index 0000000..94c0cfb
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/lang/zh.js
@@ -0,0 +1,12 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.plugins.setLang("specialchar","zh",{euro:"æ­å…ƒç¬¦è™Ÿ",lsquo:"左單引號",rsquo:"å³å–®å¼•è™Ÿ",ldquo:"左雙引號",rdquo:"å³é›™å¼•è™Ÿ",ndash:"短破折號",mdash:"長破折號",iexcl:"倒置的驚嘆號",cent:"美分符號",pound:"英鎊符號",curren:"貨幣符號",yen:"日圓符號",brvbar:"Broken bar",sect:"章節符號",uml:"分音符號",copy:"版權符號",ordf:"雌性符號",laquo:"左雙角括號",not:"Not 符號",reg:"註冊商標符號",macr:"長音符號",deg:"度數符號",sup2:"上標字 2",sup3:"上標字 3",acute:"尖音符號",micro:"Micro sign",para:"段è½ç¬¦è™Ÿ",middot:"中間點",cedil:"å­—æ¯ C 下é¢çš„尾型符號 ",sup1:"上標",ordm:"雄性符號",raquo:"å³é›™è§’括號",frac14:"四分之一符號",frac12:"Vulgar fraction one half",
+frac34:"Vulgar fraction three quarters",iquest:"Inverted question mark",Agrave:"Latin capital letter A with grave accent",Aacute:"Latin capital letter A with acute accent",Acirc:"Latin capital letter A with circumflex",Atilde:"Latin capital letter A with tilde",Auml:"拉ä¸å¤§å¯«å­—æ¯ E 帶分音符號",Aring:"拉ä¸å¤§å¯«å­—æ¯ A 帶上圓圈",AElig:"拉ä¸å¤§å¯«å­—æ¯ Ã†",Ccedil:"拉ä¸å¤§å¯«å­—æ¯ C 帶下尾符號",Egrave:"Latin capital letter E with grave accent",Eacute:"Latin capital letter E with acute accent",Ecirc:"Latin capital letter E with circumflex",Euml:"Latin capital letter E with diaeresis",
+Igrave:"Latin capital letter I with grave accent",Iacute:"Latin capital letter I with acute accent",Icirc:"Latin capital letter I with circumflex",Iuml:"Latin capital letter I with diaeresis",ETH:"Latin capital letter Eth",Ntilde:"Latin capital letter N with tilde",Ograve:"Latin capital letter O with grave accent",Oacute:"Latin capital letter O with acute accent",Ocirc:"Latin capital letter O with circumflex",Otilde:"Latin capital letter O with tilde",Ouml:"Latin capital letter O with diaeresis",
+times:"乘號",Oslash:"拉ä¸å¤§å¯«å­—æ¯ O 帶粗線符號",Ugrave:"Latin capital letter U with grave accent",Uacute:"Latin capital letter U with acute accent",Ucirc:"Latin capital letter U with circumflex",Uuml:"Latin capital letter U with diaeresis",Yacute:"Latin capital letter Y with acute accent",THORN:"Latin capital letter Thorn",szlig:"Latin small letter sharp s",agrave:"Latin small letter a with grave accent",aacute:"Latin small letter a with acute accent",acirc:"Latin small letter a with circumflex",atilde:"Latin small letter a with tilde",
+auml:"Latin small letter a with diaeresis",aring:"Latin small letter a with ring above",aelig:"Latin small letter æ",ccedil:"Latin small letter c with cedilla",egrave:"Latin small letter e with grave accent",eacute:"Latin small letter e with acute accent",ecirc:"Latin small letter e with circumflex",euml:"Latin small letter e with diaeresis",igrave:"Latin small letter i with grave accent",iacute:"Latin small letter i with acute accent",icirc:"Latin small letter i with circumflex",iuml:"Latin small letter i with diaeresis",
+eth:"Latin small letter eth",ntilde:"Latin small letter n with tilde",ograve:"Latin small letter o with grave accent",oacute:"Latin small letter o with acute accent",ocirc:"Latin small letter o with circumflex",otilde:"Latin small letter o with tilde",ouml:"Latin small letter o with diaeresis",divide:"Division sign",oslash:"Latin small letter o with stroke",ugrave:"Latin small letter u with grave accent",uacute:"Latin small letter u with acute accent",ucirc:"Latin small letter u with circumflex",
+uuml:"Latin small letter u with diaeresis",yacute:"Latin small letter y with acute accent",thorn:"Latin small letter thorn",yuml:"Latin small letter y with diaeresis",OElig:"Latin capital ligature OE",oelig:"Latin small ligature oe",372:"Latin capital letter W with circumflex",374:"Latin capital letter Y with circumflex",373:"Latin small letter w with circumflex",375:"Latin small letter y with circumflex",sbquo:"Single low-9 quotation mark",8219:"Single high-reversed-9 quotation mark",bdquo:"Double low-9 quotation mark",
+hellip:"Horizontal ellipsis",trade:"Trade mark sign",9658:"Black right-pointing pointer",bull:"Bullet",rarr:"Rightwards arrow",rArr:"Rightwards double arrow",hArr:"Left right double arrow",diams:"Black diamond suit",asymp:"Almost equal to"}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/specialchar/dialogs/specialchar.js b/js/ckeditor/plugins/specialchar/dialogs/specialchar.js
new file mode 100644
index 0000000..b343a83
--- /dev/null
+++ b/js/ckeditor/plugins/specialchar/dialogs/specialchar.js
@@ -0,0 +1,14 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("specialchar",function(i){var e,l=i.lang.specialchar,k=function(c){var b,c=c.data?c.data.getTarget():new CKEDITOR.dom.element(c);if("a"==c.getName()&&(b=c.getChild(0).getHtml()))c.removeClass("cke_light_background"),e.hide(),c=i.document.createElement("span"),c.setHtml(b),i.insertText(c.getText())},m=CKEDITOR.tools.addFunction(k),j,g=function(c,b){var a,b=b||c.data.getTarget();"span"==b.getName()&&(b=b.getParent());if("a"==b.getName()&&(a=b.getChild(0).getHtml())){j&&d(null,j);
+var f=e.getContentElement("info","htmlPreview").getElement();e.getContentElement("info","charPreview").getElement().setHtml(a);f.setHtml(CKEDITOR.tools.htmlEncode(a));b.getParent().addClass("cke_light_background");j=b}},d=function(c,b){b=b||c.data.getTarget();"span"==b.getName()&&(b=b.getParent());"a"==b.getName()&&(e.getContentElement("info","charPreview").getElement().setHtml("&nbsp;"),e.getContentElement("info","htmlPreview").getElement().setHtml("&nbsp;"),b.getParent().removeClass("cke_light_background"),
+j=void 0)},n=CKEDITOR.tools.addFunction(function(c){var c=new CKEDITOR.dom.event(c),b=c.getTarget(),a;a=c.getKeystroke();var f="rtl"==i.lang.dir;switch(a){case 38:if(a=b.getParent().getParent().getPrevious())a=a.getChild([b.getParent().getIndex(),0]),a.focus(),d(null,b),g(null,a);c.preventDefault();break;case 40:if(a=b.getParent().getParent().getNext())if((a=a.getChild([b.getParent().getIndex(),0]))&&1==a.type)a.focus(),d(null,b),g(null,a);c.preventDefault();break;case 32:k({data:c});c.preventDefault();
+break;case f?37:39:if(a=b.getParent().getNext())a=a.getChild(0),1==a.type?(a.focus(),d(null,b),g(null,a),c.preventDefault(!0)):d(null,b);else if(a=b.getParent().getParent().getNext())(a=a.getChild([0,0]))&&1==a.type?(a.focus(),d(null,b),g(null,a),c.preventDefault(!0)):d(null,b);break;case f?39:37:(a=b.getParent().getPrevious())?(a=a.getChild(0),a.focus(),d(null,b),g(null,a),c.preventDefault(!0)):(a=b.getParent().getParent().getPrevious())?(a=a.getLast().getChild(0),a.focus(),d(null,b),g(null,a),c.preventDefault(!0)):
+d(null,b)}});return{title:l.title,minWidth:430,minHeight:280,buttons:[CKEDITOR.dialog.cancelButton],charColumns:17,onLoad:function(){for(var c=this.definition.charColumns,b=i.config.specialChars,a=CKEDITOR.tools.getNextId()+"_specialchar_table_label",f=['<table role="listbox" aria-labelledby="'+a+'" style="width: 320px; height: 100%; border-collapse: separate;" align="center" cellspacing="2" cellpadding="2" border="0">'],d=0,g=b.length,h,e;d<g;){f.push('<tr role="presentation">');for(var j=0;j<c;j++,
+d++){if(h=b[d]){h instanceof Array?(e=h[1],h=h[0]):(e=h.replace("&","").replace(";","").replace("#",""),e=l[e]||h);var k="cke_specialchar_label_"+d+"_"+CKEDITOR.tools.getNextNumber();f.push('<td class="cke_dark_background" style="cursor: default" role="presentation"><a href="javascript: void(0);" role="option" aria-posinset="'+(d+1)+'"',' aria-setsize="'+g+'"',' aria-labelledby="'+k+'"',' class="cke_specialchar" title="',CKEDITOR.tools.htmlEncode(e),'" onkeydown="CKEDITOR.tools.callFunction( '+n+
+', event, this )" onclick="CKEDITOR.tools.callFunction('+m+', this); return false;" tabindex="-1"><span style="margin: 0 auto;cursor: inherit">'+h+'</span><span class="cke_voice_label" id="'+k+'">'+e+"</span></a>")}else f.push('<td class="cke_dark_background">&nbsp;');f.push("</td>")}f.push("</tr>")}f.push("</tbody></table>",'<span id="'+a+'" class="cke_voice_label">'+l.options+"</span>");this.getContentElement("info","charContainer").getElement().setHtml(f.join(""))},contents:[{id:"info",label:i.lang.common.generalTab,
+title:i.lang.common.generalTab,padding:0,align:"top",elements:[{type:"hbox",align:"top",widths:["320px","90px"],children:[{type:"html",id:"charContainer",html:"",onMouseover:g,onMouseout:d,focus:function(){var c=this.getElement().getElementsByTag("a").getItem(0);setTimeout(function(){c.focus();g(null,c)},0)},onShow:function(){var c=this.getElement().getChild([0,0,0,0,0]);setTimeout(function(){c.focus();g(null,c)},0)},onLoad:function(c){e=c.sender}},{type:"hbox",align:"top",widths:["100%"],children:[{type:"vbox",
+align:"top",children:[{type:"html",html:"<div></div>"},{type:"html",id:"charPreview",className:"cke_dark_background",style:"border:1px solid #eeeeee;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:'Microsoft Sans Serif',Arial,Helvetica,Verdana;text-align:center;",html:"<div>&nbsp;</div>"},{type:"html",id:"htmlPreview",className:"cke_dark_background",style:"border:1px solid #eeeeee;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:'Microsoft Sans Serif',Arial,Helvetica,Verdana;text-align:center;",
+html:"<div>&nbsp;</div>"}]}]}]}]}]}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/table/dialogs/table.js b/js/ckeditor/plugins/table/dialogs/table.js
new file mode 100644
index 0000000..2a33528
--- /dev/null
+++ b/js/ckeditor/plugins/table/dialogs/table.js
@@ -0,0 +1,21 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+(function(){function r(a){for(var e=0,l=0,k=0,m,g=a.$.rows.length;k<g;k++){m=a.$.rows[k];for(var d=e=0,c,b=m.cells.length;d<b;d++)c=m.cells[d],e+=c.colSpan;e>l&&(l=e)}return l}function o(a){return function(){var e=this.getValue(),e=!!(CKEDITOR.dialog.validate.integer()(e)&&0<e);e||(alert(a),this.select());return e}}function n(a,e){var l=function(g){return new CKEDITOR.dom.element(g,a.document)},n=a.editable(),m=a.plugins.dialogadvtab;return{title:a.lang.table.title,minWidth:310,minHeight:CKEDITOR.env.ie?
+310:280,onLoad:function(){var g=this,a=g.getContentElement("advanced","advStyles");if(a)a.on("change",function(){var a=this.getStyle("width",""),b=g.getContentElement("info","txtWidth");b&&b.setValue(a,!0);a=this.getStyle("height","");(b=g.getContentElement("info","txtHeight"))&&b.setValue(a,!0)})},onShow:function(){var g=a.getSelection(),d=g.getRanges(),c,b=this.getContentElement("info","txtRows"),h=this.getContentElement("info","txtCols"),p=this.getContentElement("info","txtWidth"),f=this.getContentElement("info",
+"txtHeight");"tableProperties"==e&&((g=g.getSelectedElement())&&g.is("table")?c=g:0<d.length&&(CKEDITOR.env.webkit&&d[0].shrink(CKEDITOR.NODE_ELEMENT),c=a.elementPath(d[0].getCommonAncestor(!0)).contains("table",1)),this._.selectedElement=c);c?(this.setupContent(c),b&&b.disable(),h&&h.disable()):(b&&b.enable(),h&&h.enable());p&&p.onChange();f&&f.onChange()},onOk:function(){var g=a.getSelection(),d=this._.selectedElement&&g.createBookmarks(),c=this._.selectedElement||l("table"),b={};this.commitContent(b,
+c);if(b.info){b=b.info;if(!this._.selectedElement)for(var h=c.append(l("tbody")),e=parseInt(b.txtRows,10)||0,f=parseInt(b.txtCols,10)||0,i=0;i<e;i++)for(var j=h.append(l("tr")),k=0;k<f;k++)j.append(l("td")).appendBogus();e=b.selHeaders;if(!c.$.tHead&&("row"==e||"both"==e)){j=new CKEDITOR.dom.element(c.$.createTHead());h=c.getElementsByTag("tbody").getItem(0);h=h.getElementsByTag("tr").getItem(0);for(i=0;i<h.getChildCount();i++)f=h.getChild(i),f.type==CKEDITOR.NODE_ELEMENT&&!f.data("cke-bookmark")&&
+(f.renameNode("th"),f.setAttribute("scope","col"));j.append(h.remove())}if(null!==c.$.tHead&&!("row"==e||"both"==e)){j=new CKEDITOR.dom.element(c.$.tHead);h=c.getElementsByTag("tbody").getItem(0);for(k=h.getFirst();0<j.getChildCount();){h=j.getFirst();for(i=0;i<h.getChildCount();i++)f=h.getChild(i),f.type==CKEDITOR.NODE_ELEMENT&&(f.renameNode("td"),f.removeAttribute("scope"));h.insertBefore(k)}j.remove()}if(!this.hasColumnHeaders&&("col"==e||"both"==e))for(j=0;j<c.$.rows.length;j++)f=new CKEDITOR.dom.element(c.$.rows[j].cells[0]),
+f.renameNode("th"),f.setAttribute("scope","row");if(this.hasColumnHeaders&&!("col"==e||"both"==e))for(i=0;i<c.$.rows.length;i++)j=new CKEDITOR.dom.element(c.$.rows[i]),"tbody"==j.getParent().getName()&&(f=new CKEDITOR.dom.element(j.$.cells[0]),f.renameNode("td"),f.removeAttribute("scope"));b.txtHeight?c.setStyle("height",b.txtHeight):c.removeStyle("height");b.txtWidth?c.setStyle("width",b.txtWidth):c.removeStyle("width");c.getAttribute("style")||c.removeAttribute("style")}if(this._.selectedElement)try{g.selectBookmarks(d)}catch(m){}else a.insertElement(c),
+setTimeout(function(){var g=new CKEDITOR.dom.element(c.$.rows[0].cells[0]),b=a.createRange();b.moveToPosition(g,CKEDITOR.POSITION_AFTER_START);b.select()},0)},contents:[{id:"info",label:a.lang.table.title,elements:[{type:"hbox",widths:[null,null],styles:["vertical-align:top"],children:[{type:"vbox",padding:0,children:[{type:"text",id:"txtRows","default":3,label:a.lang.table.rows,required:!0,controlStyle:"width:5em",validate:o(a.lang.table.invalidRows),setup:function(a){this.setValue(a.$.rows.length)},
+commit:k},{type:"text",id:"txtCols","default":2,label:a.lang.table.columns,required:!0,controlStyle:"width:5em",validate:o(a.lang.table.invalidCols),setup:function(a){this.setValue(r(a))},commit:k},{type:"html",html:"&nbsp;"},{type:"select",id:"selHeaders",requiredContent:"th","default":"",label:a.lang.table.headers,items:[[a.lang.table.headersNone,""],[a.lang.table.headersRow,"row"],[a.lang.table.headersColumn,"col"],[a.lang.table.headersBoth,"both"]],setup:function(a){var d=this.getDialog();d.hasColumnHeaders=
+!0;for(var c=0;c<a.$.rows.length;c++){var b=a.$.rows[c].cells[0];if(b&&"th"!=b.nodeName.toLowerCase()){d.hasColumnHeaders=!1;break}}null!==a.$.tHead?this.setValue(d.hasColumnHeaders?"both":"row"):this.setValue(d.hasColumnHeaders?"col":"")},commit:k},{type:"text",id:"txtBorder",requiredContent:"table[border]","default":a.filter.check("table[border]")?1:0,label:a.lang.table.border,controlStyle:"width:3em",validate:CKEDITOR.dialog.validate.number(a.lang.table.invalidBorder),setup:function(a){this.setValue(a.getAttribute("border")||
+"")},commit:function(a,d){this.getValue()?d.setAttribute("border",this.getValue()):d.removeAttribute("border")}},{id:"cmbAlign",type:"select",requiredContent:"table[align]","default":"",label:a.lang.common.align,items:[[a.lang.common.notSet,""],[a.lang.common.alignLeft,"left"],[a.lang.common.alignCenter,"center"],[a.lang.common.alignRight,"right"]],setup:function(a){this.setValue(a.getAttribute("align")||"")},commit:function(a,d){this.getValue()?d.setAttribute("align",this.getValue()):d.removeAttribute("align")}}]},
+{type:"vbox",padding:0,children:[{type:"hbox",widths:["5em"],children:[{type:"text",id:"txtWidth",requiredContent:"table{width}",controlStyle:"width:5em",label:a.lang.common.width,title:a.lang.common.cssLengthTooltip,"default":a.filter.check("table{width}")?500>n.getSize("width")?"100%":500:0,getValue:q,validate:CKEDITOR.dialog.validate.cssLength(a.lang.common.invalidCssLength.replace("%1",a.lang.common.width)),onChange:function(){var a=this.getDialog().getContentElement("advanced","advStyles");a&&
+a.updateStyle("width",this.getValue())},setup:function(a){this.setValue(a.getStyle("width"))},commit:k}]},{type:"hbox",widths:["5em"],children:[{type:"text",id:"txtHeight",requiredContent:"table{height}",controlStyle:"width:5em",label:a.lang.common.height,title:a.lang.common.cssLengthTooltip,"default":"",getValue:q,validate:CKEDITOR.dialog.validate.cssLength(a.lang.common.invalidCssLength.replace("%1",a.lang.common.height)),onChange:function(){var a=this.getDialog().getContentElement("advanced","advStyles");
+a&&a.updateStyle("height",this.getValue())},setup:function(a){(a=a.getStyle("height"))&&this.setValue(a)},commit:k}]},{type:"html",html:"&nbsp;"},{type:"text",id:"txtCellSpace",requiredContent:"table[cellspacing]",controlStyle:"width:3em",label:a.lang.table.cellSpace,"default":a.filter.check("table[cellspacing]")?1:0,validate:CKEDITOR.dialog.validate.number(a.lang.table.invalidCellSpacing),setup:function(a){this.setValue(a.getAttribute("cellSpacing")||"")},commit:function(a,d){this.getValue()?d.setAttribute("cellSpacing",
+this.getValue()):d.removeAttribute("cellSpacing")}},{type:"text",id:"txtCellPad",requiredContent:"table[cellpadding]",controlStyle:"width:3em",label:a.lang.table.cellPad,"default":a.filter.check("table[cellpadding]")?1:0,validate:CKEDITOR.dialog.validate.number(a.lang.table.invalidCellPadding),setup:function(a){this.setValue(a.getAttribute("cellPadding")||"")},commit:function(a,d){this.getValue()?d.setAttribute("cellPadding",this.getValue()):d.removeAttribute("cellPadding")}}]}]},{type:"html",align:"right",
+html:""},{type:"vbox",padding:0,children:[{type:"text",id:"txtCaption",requiredContent:"caption",label:a.lang.table.caption,setup:function(a){this.enable();a=a.getElementsByTag("caption");if(0<a.count()){var a=a.getItem(0),d=a.getFirst(CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT));d&&!d.equals(a.getBogus())?(this.disable(),this.setValue(a.getText())):(a=CKEDITOR.tools.trim(a.getText()),this.setValue(a))}},commit:function(e,d){if(this.isEnabled()){var c=this.getValue(),b=d.getElementsByTag("caption");
+if(c)0<b.count()?(b=b.getItem(0),b.setHtml("")):(b=new CKEDITOR.dom.element("caption",a.document),d.getChildCount()?b.insertBefore(d.getFirst()):b.appendTo(d)),b.append(new CKEDITOR.dom.text(c,a.document));else if(0<b.count())for(c=b.count()-1;0<=c;c--)b.getItem(c).remove()}}},{type:"text",id:"txtSummary",requiredContent:"table[summary]",label:a.lang.table.summary,setup:function(a){this.setValue(a.getAttribute("summary")||"")},commit:function(a,d){this.getValue()?d.setAttribute("summary",this.getValue()):
+d.removeAttribute("summary")}}]}]},m&&m.createAdvancedTab(a,null,"table")]}}var q=CKEDITOR.tools.cssLength,k=function(a){var e=this.id;a.info||(a.info={});a.info[e]=this.getValue()};CKEDITOR.dialog.add("table",function(a){return n(a,"table")});CKEDITOR.dialog.add("tableProperties",function(a){return n(a,"tableProperties")})})(); \ No newline at end of file
diff --git a/js/ckeditor/plugins/tabletools/dialogs/tableCell.js b/js/ckeditor/plugins/tabletools/dialogs/tableCell.js
new file mode 100644
index 0000000..6efe765
--- /dev/null
+++ b/js/ckeditor/plugins/tabletools/dialogs/tableCell.js
@@ -0,0 +1,17 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("cellProperties",function(g){function d(a){return function(b){for(var c=a(b[0]),d=1;d<b.length;d++)if(a(b[d])!==c){c=null;break}"undefined"!=typeof c&&(this.setValue(c),CKEDITOR.env.gecko&&("select"==this.type&&!c)&&(this.getInputElement().$.selectedIndex=-1))}}function j(a){if(a=l.exec(a.getStyle("width")||a.getAttribute("width")))return a[2]}var h=g.lang.table,c=h.cell,e=g.lang.common,i=CKEDITOR.dialog.validate,l=/^(\d+(?:\.\d+)?)(px|%)$/,f={type:"html",html:"&nbsp;"},m="rtl"==
+g.lang.dir,k=g.plugins.colordialog;return{title:c.title,minWidth:CKEDITOR.env.ie&&CKEDITOR.env.quirks?450:410,minHeight:CKEDITOR.env.ie&&(CKEDITOR.env.ie7Compat||CKEDITOR.env.quirks)?230:220,contents:[{id:"info",label:c.title,accessKey:"I",elements:[{type:"hbox",widths:["40%","5%","40%"],children:[{type:"vbox",padding:0,children:[{type:"hbox",widths:["70%","30%"],children:[{type:"text",id:"width",width:"100px",label:e.width,validate:i.number(c.invalidWidth),onLoad:function(){var a=this.getDialog().getContentElement("info",
+"widthType").getElement(),b=this.getInputElement(),c=b.getAttribute("aria-labelledby");b.setAttribute("aria-labelledby",[c,a.$.id].join(" "))},setup:d(function(a){var b=parseInt(a.getAttribute("width"),10),a=parseInt(a.getStyle("width"),10);return!isNaN(a)?a:!isNaN(b)?b:""}),commit:function(a){var b=parseInt(this.getValue(),10),c=this.getDialog().getValueOf("info","widthType")||j(a);isNaN(b)?a.removeStyle("width"):a.setStyle("width",b+c);a.removeAttribute("width")},"default":""},{type:"select",id:"widthType",
+label:g.lang.table.widthUnit,labelStyle:"visibility:hidden","default":"px",items:[[h.widthPx,"px"],[h.widthPc,"%"]],setup:d(j)}]},{type:"hbox",widths:["70%","30%"],children:[{type:"text",id:"height",label:e.height,width:"100px","default":"",validate:i.number(c.invalidHeight),onLoad:function(){var a=this.getDialog().getContentElement("info","htmlHeightType").getElement(),b=this.getInputElement(),c=b.getAttribute("aria-labelledby");b.setAttribute("aria-labelledby",[c,a.$.id].join(" "))},setup:d(function(a){var b=
+parseInt(a.getAttribute("height"),10),a=parseInt(a.getStyle("height"),10);return!isNaN(a)?a:!isNaN(b)?b:""}),commit:function(a){var b=parseInt(this.getValue(),10);isNaN(b)?a.removeStyle("height"):a.setStyle("height",CKEDITOR.tools.cssLength(b));a.removeAttribute("height")}},{id:"htmlHeightType",type:"html",html:"<br />"+h.widthPx}]},f,{type:"select",id:"wordWrap",label:c.wordWrap,"default":"yes",items:[[c.yes,"yes"],[c.no,"no"]],setup:d(function(a){var b=a.getAttribute("noWrap");if("nowrap"==a.getStyle("white-space")||
+b)return"no"}),commit:function(a){"no"==this.getValue()?a.setStyle("white-space","nowrap"):a.removeStyle("white-space");a.removeAttribute("noWrap")}},f,{type:"select",id:"hAlign",label:c.hAlign,"default":"",items:[[e.notSet,""],[e.alignLeft,"left"],[e.alignCenter,"center"],[e.alignRight,"right"],[e.alignJustify,"justify"]],setup:d(function(a){var b=a.getAttribute("align");return a.getStyle("text-align")||b||""}),commit:function(a){var b=this.getValue();b?a.setStyle("text-align",b):a.removeStyle("text-align");
+a.removeAttribute("align")}},{type:"select",id:"vAlign",label:c.vAlign,"default":"",items:[[e.notSet,""],[e.alignTop,"top"],[e.alignMiddle,"middle"],[e.alignBottom,"bottom"],[c.alignBaseline,"baseline"]],setup:d(function(a){var b=a.getAttribute("vAlign"),a=a.getStyle("vertical-align");switch(a){case "top":case "middle":case "bottom":case "baseline":break;default:a=""}return a||b||""}),commit:function(a){var b=this.getValue();b?a.setStyle("vertical-align",b):a.removeStyle("vertical-align");a.removeAttribute("vAlign")}}]},
+f,{type:"vbox",padding:0,children:[{type:"select",id:"cellType",label:c.cellType,"default":"td",items:[[c.data,"td"],[c.header,"th"]],setup:d(function(a){return a.getName()}),commit:function(a){a.renameNode(this.getValue())}},f,{type:"text",id:"rowSpan",label:c.rowSpan,"default":"",validate:i.integer(c.invalidRowSpan),setup:d(function(a){if((a=parseInt(a.getAttribute("rowSpan"),10))&&1!=a)return a}),commit:function(a){var b=parseInt(this.getValue(),10);b&&1!=b?a.setAttribute("rowSpan",this.getValue()):
+a.removeAttribute("rowSpan")}},{type:"text",id:"colSpan",label:c.colSpan,"default":"",validate:i.integer(c.invalidColSpan),setup:d(function(a){if((a=parseInt(a.getAttribute("colSpan"),10))&&1!=a)return a}),commit:function(a){var b=parseInt(this.getValue(),10);b&&1!=b?a.setAttribute("colSpan",this.getValue()):a.removeAttribute("colSpan")}},f,{type:"hbox",padding:0,widths:["60%","40%"],children:[{type:"text",id:"bgColor",label:c.bgColor,"default":"",setup:d(function(a){var b=a.getAttribute("bgColor");
+return a.getStyle("background-color")||b}),commit:function(a){this.getValue()?a.setStyle("background-color",this.getValue()):a.removeStyle("background-color");a.removeAttribute("bgColor")}},k?{type:"button",id:"bgColorChoose","class":"colorChooser",label:c.chooseColor,onLoad:function(){this.getElement().getParent().setStyle("vertical-align","bottom")},onClick:function(){g.getColorFromDialog(function(a){a&&this.getDialog().getContentElement("info","bgColor").setValue(a);this.focus()},this)}}:f]},f,
+{type:"hbox",padding:0,widths:["60%","40%"],children:[{type:"text",id:"borderColor",label:c.borderColor,"default":"",setup:d(function(a){var b=a.getAttribute("borderColor");return a.getStyle("border-color")||b}),commit:function(a){this.getValue()?a.setStyle("border-color",this.getValue()):a.removeStyle("border-color");a.removeAttribute("borderColor")}},k?{type:"button",id:"borderColorChoose","class":"colorChooser",label:c.chooseColor,style:(m?"margin-right":"margin-left")+": 10px",onLoad:function(){this.getElement().getParent().setStyle("vertical-align",
+"bottom")},onClick:function(){g.getColorFromDialog(function(a){a&&this.getDialog().getContentElement("info","borderColor").setValue(a);this.focus()},this)}}:f]}]}]}]}],onShow:function(){this.cells=CKEDITOR.plugins.tabletools.getSelectedCells(this._.editor.getSelection());this.setupContent(this.cells)},onOk:function(){for(var a=this._.editor.getSelection(),b=a.createBookmarks(),c=this.cells,d=0;d<c.length;d++)this.commitContent(c[d]);this._.editor.forceNextSelectionCheck();a.selectBookmarks(b);this._.editor.selectionChange()},
+onLoad:function(){var a={};this.foreach(function(b){b.setup&&b.commit&&(b.setup=CKEDITOR.tools.override(b.setup,function(c){return function(){c.apply(this,arguments);a[b.id]=b.getValue()}}),b.commit=CKEDITOR.tools.override(b.commit,function(c){return function(){a[b.id]!==b.getValue()&&c.apply(this,arguments)}}))})}}}); \ No newline at end of file
diff --git a/js/ckeditor/plugins/wsc/LICENSE.md b/js/ckeditor/plugins/wsc/LICENSE.md
new file mode 100644
index 0000000..c7d374a
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/LICENSE.md
@@ -0,0 +1,28 @@
+Software License Agreement
+==========================
+
+**CKEditor WSC Plugin**
+Copyright &copy; 2012, [CKSource](http://cksource.com) - Frederico Knabben. All rights reserved.
+
+Licensed under the terms of any of the following licenses at your choice:
+
+* GNU General Public License Version 2 or later (the "GPL"):
+ http://www.gnu.org/licenses/gpl.html
+
+* GNU Lesser General Public License Version 2.1 or later (the "LGPL"):
+ http://www.gnu.org/licenses/lgpl.html
+
+* Mozilla Public License Version 1.1 or later (the "MPL"):
+ http://www.mozilla.org/MPL/MPL-1.1.html
+
+You are not required to, but if you want to explicitly declare the license you have chosen to be bound to when using, reproducing, modifying and distributing this software, just include a text file titled "legal.txt" in your version of this software, indicating your license choice.
+
+Sources of Intellectual Property Included in this plugin
+--------------------------------------------------------
+
+Where not otherwise indicated, all plugin content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, the plugin will incorporate work done by developers outside of CKSource with their express permission.
+
+Trademarks
+----------
+
+CKEditor is a trademark of CKSource - Frederico Knabben. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders.
diff --git a/js/ckeditor/plugins/wsc/README.md b/js/ckeditor/plugins/wsc/README.md
new file mode 100644
index 0000000..46eeafb
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/README.md
@@ -0,0 +1,25 @@
+CKEditor WebSpellChecker Plugin
+===============================
+
+This plugin brings Web Spell Checker (WSC) into CKEditor.
+
+WSC is "installation-less", using the web-services of [WebSpellChecker.net](http://www.webspellchecker.net/). It's an out of the box solution.
+
+Installation
+------------
+
+1. Clone/copy this repository contents in a new "plugins/wsc" folder in your CKEditor installation.
+2. Enable the "wsc" plugin in the CKEditor configuration file (config.js):
+
+ config.extraPlugins = 'wsc';
+
+That's all. WSC will appear on the editor toolbar and will be ready to use.
+
+License
+-------
+
+Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html).
+
+See LICENSE.md for more information.
+
+Developed in cooperation with [WebSpellChecker.net](http://www.webspellchecker.net/).
diff --git a/js/ckeditor/plugins/wsc/dialogs/ciframe.html b/js/ckeditor/plugins/wsc/dialogs/ciframe.html
new file mode 100644
index 0000000..82df25b
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/dialogs/ciframe.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+function gup( name )
+{
+ name = name.replace( /[\[]/, '\\\[' ).replace( /[\]]/, '\\\]' ) ;
+ var regexS = '[\\?&]' + name + '=([^&#]*)' ;
+ var regex = new RegExp( regexS ) ;
+ var results = regex.exec( window.location.href ) ;
+
+ if ( results )
+ return results[ 1 ] ;
+ else
+ return '' ;
+}
+
+var interval;
+
+function sendData2Master()
+{
+ var destination = window.parent.parent ;
+ try
+ {
+ if ( destination.XDTMaster )
+ {
+ var t = destination.XDTMaster.read( [ gup( 'cmd' ), gup( 'data' ) ] ) ;
+ window.clearInterval( interval ) ;
+ }
+ }
+ catch (e) {}
+}
+
+function OnMessage (event) {
+ var message = event.data;
+ var destination = window.parent.parent;
+ destination.XDTMaster.read( [ 'end', message, 'fpm' ] ) ;
+}
+
+function listenPostMessage() {
+ if (window.addEventListener) { // all browsers except IE before version 9
+ window.addEventListener ("message", OnMessage, false);
+ }else {
+ if (window.attachEvent) { // IE before version 9
+ window.attachEvent("onmessage", OnMessage);
+ }
+ }
+}
+
+function onLoad()
+{
+ interval = window.setInterval( sendData2Master, 100 );
+ listenPostMessage();
+}
+
+</script>
+</head>
+<body onload="onLoad()"><p></p></body>
+</html>
diff --git a/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html b/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html
new file mode 100644
index 0000000..c2d82aa
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/dialogs/tmpFrameset.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<!--
+Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+
+function doLoadScript( url )
+{
+ if ( !url )
+ return false ;
+
+ var s = document.createElement( "script" ) ;
+ s.type = "text/javascript" ;
+ s.src = url ;
+ document.getElementsByTagName( "head" )[ 0 ].appendChild( s ) ;
+
+ return true ;
+}
+
+var opener;
+function tryLoad()
+{
+ opener = window.parent;
+
+ // get access to global parameters
+ var oParams = window.opener.oldFramesetPageParams;
+
+ // make frameset rows string prepare
+ var sFramesetRows = ( parseInt( oParams.firstframeh, 10 ) || '30') + ",*," + ( parseInt( oParams.thirdframeh, 10 ) || '150' ) + ',0' ;
+ document.getElementById( 'itFrameset' ).rows = sFramesetRows ;
+
+ // dynamic including init frames and crossdomain transport code
+ // from config sproxy_js_frameset url
+ var addScriptUrl = oParams.sproxy_js_frameset ;
+ doLoadScript( addScriptUrl ) ;
+}
+
+ </script>
+</head>
+
+<frameset id="itFrameset" onload="tryLoad();" border="0" rows="30,*,*,0">
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="0" marginwidth="2" src="" name="navbar"></frame>
+ <frame scrolling="auto" framespacing="0" frameborder="0" noresize="noresize" marginheight="0" marginwidth="0" src="" name="mid"></frame>
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="1" marginwidth="1" src="" name="bot"></frame>
+ <frame scrolling="no" framespacing="0" frameborder="0" noresize="noresize" marginheight="1" marginwidth="1" src="" name="spellsuggestall"></frame>
+</frameset>
+</html>
diff --git a/js/ckeditor/plugins/wsc/dialogs/wsc.css b/js/ckeditor/plugins/wsc/dialogs/wsc.css
new file mode 100644
index 0000000..496d731
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/dialogs/wsc.css
@@ -0,0 +1,82 @@
+/*
+Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+html, body
+{
+ background-color: transparent;
+ margin: 0px;
+ padding: 0px;
+}
+
+body
+{
+ padding: 10px;
+}
+
+body, td, input, select, textarea
+{
+ font-size: 11px;
+ font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;
+}
+
+.midtext
+{
+ padding:0px;
+ margin:10px;
+}
+
+.midtext p
+{
+ padding:0px;
+ margin:10px;
+}
+
+.Button
+{
+ border: #737357 1px solid;
+ color: #3b3b1f;
+ background-color: #c7c78f;
+}
+
+.PopupTabArea
+{
+ color: #737357;
+ background-color: #e3e3c7;
+}
+
+.PopupTitleBorder
+{
+ border-bottom: #d5d59d 1px solid;
+}
+.PopupTabEmptyArea
+{
+ padding-left: 10px;
+ border-bottom: #d5d59d 1px solid;
+}
+
+.PopupTab, .PopupTabSelected
+{
+ border-right: #d5d59d 1px solid;
+ border-top: #d5d59d 1px solid;
+ border-left: #d5d59d 1px solid;
+ padding: 3px 5px 3px 5px;
+ color: #737357;
+}
+
+.PopupTab
+{
+ margin-top: 1px;
+ border-bottom: #d5d59d 1px solid;
+ cursor: pointer;
+}
+
+.PopupTabSelected
+{
+ font-weight: bold;
+ cursor: default;
+ padding-top: 4px;
+ border-bottom: #f1f1e3 1px solid;
+ background-color: #f1f1e3;
+}
diff --git a/js/ckeditor/plugins/wsc/dialogs/wsc.js b/js/ckeditor/plugins/wsc/dialogs/wsc.js
new file mode 100644
index 0000000..b53a48c
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/dialogs/wsc.js
@@ -0,0 +1,74 @@
+/*
+ Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+(function(){function q(a){return a&&a.domId&&a.getInputElement().$?a.getInputElement():a&&a.$?a:!1}function z(a){if(!a)throw"Languages-by-groups list are required for construct selectbox";var c=[],d="",f;for(f in a)for(var g in a[f]){var h=a[f][g];"en_US"==h?d=h:c.push(h)}c.sort();d&&c.unshift(d);return{getCurrentLangGroup:function(c){a:{for(var d in a)for(var f in a[d])if(f.toUpperCase()===c.toUpperCase()){c=d;break a}c=""}return c},setLangList:function(){var c={},d;for(d in a)for(var f in a[d])c[a[d][f]]=
+f;return c}()}}var e=function(){var a=function(a,b,f){var f=f||{},g=f.expires;if("number"==typeof g&&g){var h=new Date;h.setTime(h.getTime()+1E3*g);g=f.expires=h}g&&g.toUTCString&&(f.expires=g.toUTCString());var b=encodeURIComponent(b),a=a+"="+b,e;for(e in f)b=f[e],a+="; "+e,!0!==b&&(a+="="+b);document.cookie=a};return{postMessage:{init:function(a){window.addEventListener?window.addEventListener("message",a,!1):window.attachEvent("onmessage",a)},send:function(a){var b=Object.prototype.toString,f=
+a.fn||null,g=a.id||"",e=a.target||window,i=a.message||{id:g};a.message&&"[object Object]"==b.call(a.message)&&(a.message.id||(a.message.id=g),i=a.message);a=window.JSON.stringify(i,f);e.postMessage(a,"*")},unbindHandler:function(a){window.removeEventListener?window.removeEventListener("message",a,!1):window.detachEvent("onmessage",a)}},hash:{create:function(){},parse:function(){}},cookie:{set:a,get:function(a){return(a=document.cookie.match(RegExp("(?:^|; )"+a.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,
+"\\$1")+"=([^;]*)")))?decodeURIComponent(a[1]):void 0},remove:function(c){a(c,"",{expires:-1})}},misc:{findFocusable:function(a){var b=null;a&&(b=a.find("a[href], area[href], input, select, textarea, button, *[tabindex], *[contenteditable]"));return b},isVisible:function(a){return!(0===a.offsetWidth||0==a.offsetHeight||"none"===(document.defaultView&&document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(a,null).display:a.currentStyle?a.currentStyle.display:a.style.display))},
+hasClass:function(a,b){return!(!a.className||!a.className.match(RegExp("(\\s|^)"+b+"(\\s|$)")))}}}}(),a=a||{};a.TextAreaNumber=null;a.load=!0;a.cmd={SpellTab:"spell",Thesaurus:"thes",GrammTab:"grammar"};a.dialog=null;a.optionNode=null;a.selectNode=null;a.grammerSuggest=null;a.textNode={};a.iframeMain=null;a.dataTemp="";a.div_overlay=null;a.textNodeInfo={};a.selectNode={};a.selectNodeResponce={};a.langList=null;a.langSelectbox=null;a.banner="";a.show_grammar=null;a.div_overlay_no_check=null;a.targetFromFrame=
+{};a.onLoadOverlay=null;a.LocalizationComing={};a.OverlayPlace=null;a.LocalizationButton={ChangeTo:{instance:null,text:"Change to"},ChangeAll:{instance:null,text:"Change All"},IgnoreWord:{instance:null,text:"Ignore word"},IgnoreAllWords:{instance:null,text:"Ignore all words"},Options:{instance:null,text:"Options",optionsDialog:{instance:null}},AddWord:{instance:null,text:"Add word"},FinishChecking:{instance:null,text:"Finish Checking"}};a.LocalizationLabel={ChangeTo:{instance:null,text:"Change to"},
+Suggestions:{instance:null,text:"Suggestions"}};var A=function(b){var c,d;for(d in b)c=b[d].instance.getElement().getFirst()||b[d].instance.getElement(),c.setText(a.LocalizationComing[d])},B=function(b){for(var c in b){if(!b[c].instance.setLabel)break;b[c].instance.setLabel(a.LocalizationComing[c])}},j,r;a.framesetHtml=function(b){return"<iframe id="+a.iframeNumber+"_"+b+' frameborder="0" allowtransparency="1" style="width:100%;border: 1px solid #AEB3B9;overflow: auto;background:#fff; border-radius: 3px;"></iframe>'};
+a.setIframe=function(b,c){var d;d=a.framesetHtml(c);var f=a.iframeNumber+"_"+c;b.getElement().setHtml(d);d=document.getElementById(f);d=d.contentWindow?d.contentWindow:d.contentDocument.document?d.contentDocument.document:d.contentDocument;d.document.open();d.document.write('<!DOCTYPE html><html><head><meta charset="UTF-8"><title>iframe</title><style>html,body{margin: 0;height: 100%;font: 13px/1.555 "Trebuchet MS", sans-serif;}a{color: #888;font-weight: bold;text-decoration: none;border-bottom: 1px solid #888;}.main-box {color:#252525;padding: 3px 5px;text-align: justify;}.main-box p{margin: 0 0 14px;}.main-box .cerr{color: #f00000;border-bottom-color: #f00000;}</style></head><body><div id="content" class="main-box"></div><iframe src="" frameborder="0" id="spelltext" name="spelltext" style="display:none; width: 100%" ></iframe><iframe src="" frameborder="0" id="loadsuggestfirst" name="loadsuggestfirst" style="display:none; width: 100%" ></iframe><iframe src="" frameborder="0" id="loadspellsuggestall" name="loadspellsuggestall" style="display:none; width: 100%" ></iframe><iframe src="" frameborder="0" id="loadOptionsForm" name="loadOptionsForm" style="display:none; width: 100%" ></iframe><script>(function(window) {var ManagerPostMessage = function() {var _init = function(handler) {if (document.addEventListener) {window.addEventListener("message", handler, false);} else {window.attachEvent("onmessage", handler);};};var _sendCmd = function(o) {var str,type = Object.prototype.toString,fn = o.fn || null,id = o.id || "",target = o.target || window,message = o.message || { "id": id };if (o.message && type.call(o.message) == "[object Object]") {(o.message["id"]) ? o.message["id"] : o.message["id"] = id;message = o.message;};str = JSON.stringify(message, fn);target.postMessage(str, "*");};return {init: _init,send: _sendCmd};};var manageMessageTmp = new ManagerPostMessage;var appString = (function(){var spell = parent.CKEDITOR.config.wsc.DefaultParams.scriptPath;var serverUrl = parent.CKEDITOR.config.wsc.DefaultParams.serviceHost;return serverUrl + spell;})();function loadScript(src, callback) {var scriptTag = document.createElement("script");scriptTag.type = "text/javascript";callback ? callback : callback = function() {};if(scriptTag.readyState) {scriptTag.onreadystatechange = function() {if (scriptTag.readyState == "loaded" ||scriptTag.readyState == "complete") {scriptTag.onreadystatechange = null;setTimeout(function(){scriptTag.parentNode.removeChild(scriptTag)},1);callback();}};}else{scriptTag.onload = function() {setTimeout(function(){scriptTag.parentNode.removeChild(scriptTag)},1);callback();};};scriptTag.src = src;document.getElementsByTagName("head")[0].appendChild(scriptTag);};window.onload = function(){loadScript(appString, function(){manageMessageTmp.send({"id": "iframeOnload","target": window.parent});});}})(this);<\/script></body></html>');
+d.document.close()};a.setCurrentIframe=function(b){a.setIframe(a.dialog._.contents[b].Content,b)};a.setHeightBannerFrame=function(){var b=a.dialog.getContentElement("SpellTab","banner").getElement(),c=a.dialog.getContentElement("GrammTab","banner").getElement(),d=a.dialog.getContentElement("Thesaurus","banner").getElement();b.setStyle("height","90px");c.setStyle("height","90px");d.setStyle("height","90px")};a.setHeightFrame=function(){document.getElementById(a.iframeNumber+"_"+a.dialog._.currentTabId).style.height=
+"240px"};a.sendData=function(b){var c=b._.currentTabId,d=b._.contents[c].Content,f,g;a.setIframe(d,c);var e=function(e){e=e||window.event;e.data.getTarget().is("a")&&c!=b._.currentTabId&&(c=b._.currentTabId,d=b._.contents[c].Content,f=a.iframeNumber+"_"+c,a.div_overlay.setEnable(),d.getElement().getChildCount()?v(a.targetFromFrame[f],a.cmd[c]):(a.setIframe(d,c),g=document.getElementById(f),a.targetFromFrame[f]=g.contentWindow))};b.parts.tabs.removeListener("click",e);b.parts.tabs.on("click",e)};a.buildSelectLang=
+function(a){var c=new CKEDITOR.dom.element("div"),d=new CKEDITOR.dom.element("select"),a="wscLang"+a;c.addClass("cke_dialog_ui_input_select");c.setAttribute("role","presentation");c.setStyles({height:"auto",position:"absolute",right:"0",top:"-1px",width:"160px","white-space":"normal"});d.setAttribute("id",a);d.addClass("cke_dialog_ui_input_select");d.setStyles({width:"160px"});c.append(d);return c};a.buildOptionLang=function(b,c){var d=document.getElementById("wscLang"+c),f=document.createDocumentFragment(),
+g,e,i=[];if(0===d.options.length){for(g in b)i.push([g,b[g]]);i.sort();for(var k=0;k<i.length;k++)g=document.createElement("option"),g.setAttribute("value",i[k][1]),e=document.createTextNode(i[k][0]),g.appendChild(e),i[k][1]==a.selectingLang&&g.setAttribute("selected","selected"),f.appendChild(g);d.appendChild(f)}};a.buildOptionSynonyms=function(b){var b=a.selectNodeResponce[b],c=q(a.selectNode.synonyms);a.selectNode.synonyms.clear();for(var d=0;d<b.length;d++){var f=document.createElement("option");
+f.text=b[d];f.value=b[d];c.$.add(f,d)}a.selectNode.synonyms.getInputElement().$.firstChild.selected=!0;a.textNode.Thesaurus.setValue(a.selectNode.synonyms.getInputElement().getValue())};var s=function(a){var c=document,d=a.target||c.body,f=a.id||"overlayBlock",g=a.opacity||"0.9",a=a.background||"#f1f1f1",e=c.getElementById(f),i=e||c.createElement("div");i.style.cssText="position: absolute;top:30px;bottom:41px;left:1px;right:1px;z-index: 10020;padding:0;margin:0;background:"+a+";opacity: "+g+";filter: alpha(opacity="+
+100*g+");display: none;";i.id=f;e||d.appendChild(i);return{setDisable:function(){i.style.display="none"},setEnable:function(){i.style.display="block"}}},C=function(b,c,d){var f=new CKEDITOR.dom.element("div"),e=new CKEDITOR.dom.element("input"),h=new CKEDITOR.dom.element("label"),i="wscGrammerSuggest"+b+"_"+c;f.addClass("cke_dialog_ui_input_radio");f.setAttribute("role","presentation");f.setStyles({width:"97%",padding:"5px","white-space":"normal"});e.setAttributes({type:"radio",value:c,name:"wscGrammerSuggest",
+id:i});e.setStyles({"float":"left"});e.on("click",function(b){a.textNode.GrammTab.setValue(b.sender.getValue())});d&&e.setAttribute("checked",!0);e.addClass("cke_dialog_ui_radio_input");h.appendText(b);h.setAttribute("for",i);h.setStyles({display:"block","line-height":"16px","margin-left":"18px","white-space":"normal"});f.append(e);f.append(h);return f},w=function(a){a=a||"true";null!==a&&"false"==a&&m()},n=function(b){var c=new z(b),b="wscLang"+a.dialog.getParentEditor().name,b=document.getElementById(b),
+d=a.iframeNumber+"_"+a.dialog._.currentTabId;a.buildOptionLang(c.setLangList,a.dialog.getParentEditor().name);x[c.getCurrentLangGroup(a.selectingLang)]();w(a.show_grammar);b.onchange=function(){x[c.getCurrentLangGroup(this.value)]();w(a.show_grammar);a.div_overlay.setEnable();a.selectingLang=this.value;e.postMessage.send({message:{changeLang:a.selectingLang,text:a.dataTemp},target:a.targetFromFrame[d],id:"selectionLang_outer__page"})}},D=function(b){if("no_any_suggestions"==b){b="No suggestions";
+a.LocalizationButton.ChangeTo.instance.disable();a.LocalizationButton.ChangeAll.instance.disable();var c=function(b){b=a.LocalizationButton[b].instance;b.getElement().hasClass("cke_disabled")?b.getElement().setStyle("color","#a0a0a0"):b.disable()};c("ChangeTo");c("ChangeAll")}else a.LocalizationButton.ChangeTo.instance.enable(),a.LocalizationButton.ChangeAll.instance.enable(),a.LocalizationButton.ChangeTo.instance.getElement().setStyle("color","#333"),a.LocalizationButton.ChangeAll.instance.getElement().setStyle("color",
+"#333");return b},F={iframeOnload:function(){a.div_overlay.setEnable();var b=a.dialog._.currentTabId;v(a.targetFromFrame[a.iframeNumber+"_"+b],a.cmd[b])},suggestlist:function(b){delete b.id;a.div_overlay_no_check.setDisable();t();n(a.langList);var c=D(b.word),d="";c instanceof Array&&(c=b.word[0]);d=c=c.split(",");a.textNode.SpellTab.setValue(d[0]);b=q(r);r.clear();for(c=0;c<d.length;c++){var f=document.createElement("option");f.text=d[c];f.value=d[c];b.$.add(f,c)}l();a.div_overlay.setDisable()},
+grammerSuggest:function(b){delete b.id;delete b.mocklangs;t();n(a.langList);var c=b.grammSuggest[0];a.grammerSuggest.getElement().setHtml("");a.textNode.GrammTab.reset();a.textNode.GrammTab.setValue(c);a.textNodeInfo.GrammTab.getElement().setHtml("");a.textNodeInfo.GrammTab.getElement().setText(b.info);for(var b=b.grammSuggest,c=b.length,d=!0,f=0;f<c;f++)a.grammerSuggest.getElement().append(C(b[f],b[f],d)),d=!1;l();a.div_overlay.setDisable()},thesaurusSuggest:function(b){delete b.id;delete b.mocklangs;
+t();n(a.langList);a.selectNodeResponce=b;a.textNode.Thesaurus.reset();var c=q(a.selectNode.categories),d=0;a.selectNode.categories.clear();for(var f in b){var e=document.createElement("option");e.text=f;e.value=f;c.$.add(e,d);d++}b=a.selectNode.categories.getInputElement().getChildren().$[0].value;a.selectNode.categories.getInputElement().getChildren().$[0].selected=!0;a.buildOptionSynonyms(b);l();a.div_overlay.setDisable()},finish:function(b){delete b.id;E();b=a.dialog.getContentElement(a.dialog._.currentTabId,
+"BlockFinishChecking").getElement();b.removeStyle("display");b.removeStyle("position");b.removeStyle("left");b.show();a.div_overlay.setDisable()},settext:function(b){delete b.id;a.dialog.getParentEditor().getCommand("checkspell");var c=a.dialog.getParentEditor();try{c.focus()}catch(d){}c.setData(b.text,function(){a.dataTemp="";c.unlockSelection();c.fire("saveSnapshot");a.dialog.hide()})},ReplaceText:function(b){delete b.id;a.div_overlay.setEnable();a.dataTemp=b.text;a.selectingLang=b.currentLang;
+window.setTimeout(function(){try{a.div_overlay.setDisable()}catch(b){}},500);A(a.LocalizationButton);B(a.LocalizationLabel)},options_checkbox_send:function(b){delete b.id;b={osp:e.cookie.get("osp"),udn:e.cookie.get("udn"),cust_dic_ids:a.cust_dic_ids};e.postMessage.send({message:b,target:a.targetFromFrame[a.iframeNumber+"_"+a.dialog._.currentTabId],id:"options_outer__page"})},getOptions:function(b){var c=b.DefOptions.udn;a.LocalizationComing=b.DefOptions.localizationButtonsAndText;a.show_grammar=b.show_grammar;
+a.langList=b.lang;if(a.bnr=b.bannerId){a.setHeightBannerFrame();var d=b.banner;a.dialog.getContentElement(a.dialog._.currentTabId,"banner").getElement().setHtml(d)}else a.setHeightFrame();"undefined"==c&&(a.userDictionaryName?(c=a.userDictionaryName,d={osp:e.cookie.get("osp"),udn:a.userDictionaryName,cust_dic_ids:a.cust_dic_ids,id:"options_dic_send",udnCmd:"create"},e.postMessage.send({message:d,target:a.targetFromFrame[void 0]})):c="");e.cookie.set("osp",b.DefOptions.osp);e.cookie.set("udn",c);e.cookie.set("cust_dic_ids",
+b.DefOptions.cust_dic_ids);e.postMessage.send({id:"giveOptions"})},options_dic_send:function(){var b={osp:e.cookie.get("osp"),udn:e.cookie.get("udn"),cust_dic_ids:a.cust_dic_ids,id:"options_dic_send",udnCmd:e.cookie.get("udnCmd")};e.postMessage.send({message:b,target:a.targetFromFrame[a.iframeNumber+"_"+a.dialog._.currentTabId]})},data:function(a){delete a.id},giveOptions:function(){},setOptionsConfirmF:function(){},setOptionsConfirmT:function(){j.setValue("")},clickBusy:function(){a.div_overlay.setEnable()},
+suggestAllCame:function(){a.div_overlay.setDisable();a.div_overlay_no_check.setDisable()},TextCorrect:function(){n(a.langList)}},y=function(a){a=a||window.event;if((a=window.JSON.parse(a.data))&&a.id)F[a.id](a)},v=function(b,c,d,f){c=c||CKEDITOR.config.wsc_cmd;d=d||a.dataTemp;e.postMessage.send({message:{customerId:a.wsc_customerId,text:d,txt_ctrl:a.TextAreaNumber,cmd:c,cust_dic_ids:a.cust_dic_ids,udn:a.userDictionaryName,slang:a.selectingLang,reset_suggest:f||!1},target:b,id:"data_outer__page"});
+a.div_overlay.setEnable()},x={superset:function(){a.dialog.showPage("Thesaurus");a.dialog.showPage("GrammTab");o()},usual:function(){u();m();o()},rtl:function(){u();m();o()}},G=function(b){var c=new function(a){var b={};return{getCmdByTab:function(c){for(var e in a)b[a[e]]=e;return b[c]}}}(a.cmd);b.selectPage(c.getCmdByTab(CKEDITOR.config.wsc_cmd));a.sendData(b)},u=function(){a.dialog.hidePage("Thesaurus")},m=function(){a.dialog.hidePage("GrammTab")},o=function(){a.dialog.showPage("SpellTab")},l=
+function(){var b=a.dialog.getContentElement(a.dialog._.currentTabId,"bottomGroup").getElement();b.removeStyle("display");b.removeStyle("position");b.removeStyle("left");b.show()},E=function(){var b=a.dialog.getContentElement(a.dialog._.currentTabId,"bottomGroup").getElement(),c=document.activeElement,d;b.setStyles({display:"block",position:"absolute",left:"-9999px"});setTimeout(function(){b.removeStyle("display");b.removeStyle("position");b.removeStyle("left");b.hide();a.dialog._.editor.focusManager.currentActive.focusNext();
+d=e.misc.findFocusable(a.dialog.parts.contents);if(!e.misc.hasClass(c,"cke_dialog_tab")&&!e.misc.hasClass(c,"cke_dialog_contents_body")&&e.misc.isVisible(c))try{c.focus()}catch(f){}else for(var g=0,h;g<d.count();g++)if(h=d.getItem(g),e.misc.isVisible(h.$)){try{h.$.focus()}catch(i){}break}},0)},t=function(){var b=a.dialog.getContentElement(a.dialog._.currentTabId,"BlockFinishChecking").getElement(),c=document.activeElement,d;b.setStyles({display:"block",position:"absolute",left:"-9999px"});setTimeout(function(){b.removeStyle("display");
+b.removeStyle("position");b.removeStyle("left");b.hide();a.dialog._.editor.focusManager.currentActive.focusNext();d=e.misc.findFocusable(a.dialog.parts.contents);if(!e.misc.hasClass(c,"cke_dialog_tab")&&!e.misc.hasClass(c,"cke_dialog_contents_body")&&e.misc.isVisible(c))try{c.focus()}catch(f){}else for(var g=0,h;g<d.count();g++)if(h=d.getItem(g),e.misc.isVisible(h.$)){try{h.$.focus()}catch(i){}break}},0)};CKEDITOR.dialog.add("checkspell",function(b){var c=function(){this.getElement().focus();a.div_overlay.setEnable();
+var c=a.dialog._.currentTabId,f=a.iframeNumber+"_"+c,g=a.textNode[c].getValue(),h=this.getElement().getAttribute("title-cmd");e.postMessage.send({message:{cmd:h,tabId:c,new_word:g},target:a.targetFromFrame[f],id:"cmd_outer__page"});("ChangeTo"==h||"ChangeAll"==h)&&b.fire("saveSnapshot");"FinishChecking"==h&&b.config.wsc_onFinish.call(CKEDITOR.document.getWindow().getFrame())};return{title:b.config.wsc_dialogTitle||b.lang.wsc.title,minWidth:560,minHeight:444,buttons:[CKEDITOR.dialog.cancelButton],
+onLoad:function(){a.dialog=this;u();m();o()},onShow:function(){b.lockSelection(b.getSelection());a.TextAreaNumber="cke_textarea_"+CKEDITOR.currentInstance.name;e.postMessage.init(y);a.dataTemp=CKEDITOR.currentInstance.getData();a.OverlayPlace=a.dialog.parts.tabs.getParent().$;if(CKEDITOR&&CKEDITOR.config){a.wsc_customerId=b.config.wsc_customerId;a.cust_dic_ids=b.config.wsc_customDictionaryIds;a.userDictionaryName=b.config.wsc_userDictionaryName;a.defaultLanguage=CKEDITOR.config.defaultLanguage;var c=
+"file:"==document.location.protocol?"http:":document.location.protocol;CKEDITOR.scriptLoader.load(b.config.wsc_customLoaderScript||c+"//loader.webspellchecker.net/sproxy_fck/sproxy.php?plugin=fck2&customerid="+a.wsc_customerId+"&cmd=script&doc=wsc&schema=22",function(c){CKEDITOR.config&&CKEDITOR.config.wsc&&CKEDITOR.config.wsc.DefaultParams?(a.serverLocationHash=CKEDITOR.config.wsc.DefaultParams.serviceHost,a.logotype=CKEDITOR.config.wsc.DefaultParams.logoPath,a.loadIcon=CKEDITOR.config.wsc.DefaultParams.iconPath,
+a.loadIconEmptyEditor=CKEDITOR.config.wsc.DefaultParams.iconPathEmptyEditor,a.LangComparer=new CKEDITOR.config.wsc.DefaultParams._SP_FCK_LangCompare):(a.serverLocationHash=DefaultParams.serviceHost,a.logotype=DefaultParams.logoPath,a.loadIcon=DefaultParams.iconPath,a.loadIconEmptyEditor=DefaultParams.iconPathEmptyEditor,a.LangComparer=new _SP_FCK_LangCompare);a.pluginPath=CKEDITOR.getUrl(b.plugins.wsc.path);a.iframeNumber=a.TextAreaNumber;a.templatePath=a.pluginPath+"dialogs/tmp.html";a.LangComparer.setDefaulLangCode(a.defaultLanguage);
+a.currentLang=b.config.wsc_lang||a.LangComparer.getSPLangCode(b.langCode);a.selectingLang=a.currentLang;a.div_overlay=new s({opacity:"1",background:"#fff url("+a.loadIcon+") no-repeat 50% 50%",target:a.OverlayPlace});var d=a.dialog.parts.tabs.getId(),d=CKEDITOR.document.getById(d);d.setStyle("width","97%");d.getElementsByTag("DIV").count()||d.append(a.buildSelectLang(a.dialog.getParentEditor().name));a.div_overlay_no_check=new s({opacity:"1",id:"no_check_over",background:"#fff url("+a.loadIconEmptyEditor+
+") no-repeat 50% 50%",target:a.OverlayPlace});c&&(G(a.dialog),a.dialog.setupContent(a.dialog))})}else a.dialog.hide()},onHide:function(){var c=CKEDITOR.plugins.scayt,f=b.scayt;b.unlockSelection();c&&(f&&c.state[b.name]&&f.setMarkupPaused)&&f.setMarkupPaused(!1);a.dataTemp="";e.postMessage.unbindHandler(y)},contents:[{id:"SpellTab",label:"SpellChecker",accessKey:"S",elements:[{type:"html",id:"banner",label:"banner",style:"",html:"<div></div>"},{type:"html",id:"Content",label:"spellContent",html:"",
+setup:function(b){var b=a.iframeNumber+"_"+b._.currentTabId,c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"hbox",id:"bottomGroup",style:"width:560px; margin: 0 auto;",widths:["50%","50%"],children:[{type:"hbox",id:"leftCol",align:"left",width:"50%",children:[{type:"vbox",id:"rightCol1",widths:["50%","50%"],children:[{type:"text",id:"text",label:a.LocalizationLabel.ChangeTo.text+":",labelLayout:"horizontal",labelStyle:"font: 12px/25px arial, sans-serif;",width:"140px","default":"",
+onShow:function(){a.textNode.SpellTab=this;a.LocalizationLabel.ChangeTo.instance=this},onHide:function(){this.reset()}},{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"text",id:"labelSuggestions",label:a.LocalizationLabel.Suggestions.text+":",onShow:function(){a.LocalizationLabel.Suggestions.instance=this;this.getInputElement().setStyles({display:"none"})}},{type:"html",id:"logo",html:'<img width="99" height="68" border="0" src="" title="WebSpellChecker.net" alt="WebSpellChecker.net" style="display: inline-block;">',
+setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"left"})}}]},{type:"select",id:"list_of_suggestions",labelStyle:"font: 12px/25px arial, sans-serif;",size:"6",inputStyle:"width: 140px; height: auto;",items:[["loading..."]],onShow:function(){r=this},onChange:function(){a.textNode.SpellTab.setValue(this.getValue())}}]}]}]},{type:"hbox",id:"rightCol",align:"right",width:"50%",children:[{type:"vbox",id:"rightCol_col__left",widths:["50%","50%","50%",
+"50%"],children:[{type:"button",id:"ChangeTo",label:a.LocalizationButton.ChangeTo.text,title:"Change to",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.ChangeTo.instance=this},onClick:c},{type:"button",id:"ChangeAll",label:a.LocalizationButton.ChangeAll.text,title:"Change All",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.ChangeAll.instance=this},onClick:c},{type:"button",
+id:"AddWord",label:a.LocalizationButton.AddWord.text,title:"Add word",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.AddWord.instance=this},onClick:c},{type:"button",id:"FinishChecking",label:a.LocalizationButton.FinishChecking.text,title:"Finish Checking",style:"width: 100%;margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.FinishChecking.instance=this},onClick:c}]},{type:"vbox",
+id:"rightCol_col__right",widths:["50%","50%","50%"],children:[{type:"button",id:"IgnoreWord",label:a.LocalizationButton.IgnoreWord.text,title:"Ignore word",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.IgnoreWord.instance=this},onClick:c},{type:"button",id:"IgnoreAllWords",label:a.LocalizationButton.IgnoreAllWords.text,title:"Ignore all words",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);
+a.LocalizationButton.IgnoreAllWords.instance=this},onClick:c},{type:"button",id:"option",label:a.LocalizationButton.Options.text,title:"Option",style:"width: 100%;",onLoad:function(){a.LocalizationButton.Options.instance=this;"file:"==document.location.protocol&&this.disable()},onClick:function(){this.getElement().focus();"file:"==document.location.protocol?alert("WSC: Options functionality is disabled when runing from file system"):(p=document.activeElement,b.openDialog("options"))}}]}]}]},{type:"hbox",
+id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;",widths:["70%","30%"],onShow:function(){this.getElement().setStyles({display:"block",position:"absolute",left:"-9999px"})},onHide:l,children:[{type:"hbox",id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",setup:function(){this.getChild()[0].getElement().$.src=a.logotype;this.getChild()[0].getElement().getParent().setStyles({"text-align":"center"})},children:[{type:"html",id:"logo",html:'<img width="99" height="68" border="0" src="" title="WebSpellChecker.net" alt="WebSpellChecker.net" style="display: inline-block;">'}]}]},
+{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"button",id:"Option_button",label:a.LocalizationButton.Options.text,title:"Option",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);"file:"==document.location.protocol&&this.disable()},onClick:function(){this.getElement().focus();"file:"==document.location.protocol?alert("WSC: Options functionality is disabled when runing from file system"):
+(p=document.activeElement,b.openDialog("options"))}},{type:"button",id:"FinishChecking",label:a.LocalizationButton.FinishChecking.text,title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]},{id:"GrammTab",label:"Grammar",accessKey:"G",elements:[{type:"html",id:"banner",label:"banner",style:"",html:"<div></div>"},{type:"html",id:"Content",label:"GrammarContent",html:"",setup:function(){var b=a.iframeNumber+"_"+a.dialog._.currentTabId,
+c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"vbox",id:"bottomGroup",style:"width:560px; margin: 0 auto;",children:[{type:"hbox",id:"leftCol",widths:["66%","34%"],children:[{type:"vbox",children:[{type:"text",id:"text",label:"Change to:",labelLayout:"horizontal",labelStyle:"font: 12px/25px arial, sans-serif;",inputStyle:"float: right; width: 200px;","default":"",onShow:function(){a.textNode.GrammTab=this},onHide:function(){this.reset()}},{type:"html",id:"html_text",html:"<div style='min-height: 17px; line-height: 17px; padding: 5px; text-align: left;background: #F1F1F1;color: #595959; white-space: normal!important;'></div>",
+onShow:function(){a.textNodeInfo.GrammTab=this}},{type:"html",id:"radio",html:"",onShow:function(){a.grammerSuggest=this}}]},{type:"vbox",children:[{type:"button",id:"ChangeTo",label:"Change to",title:"Change to",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c},{type:"button",id:"IgnoreWord",label:"Ignore word",title:"Ignore word",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},
+onClick:c},{type:"button",id:"IgnoreAllWords",label:"Ignore Problem",title:"Ignore Problem",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c},{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 133px; float: right; margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]},{type:"hbox",id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;",
+widths:["70%","30%"],onShow:function(){this.getElement().setStyles({display:"block",position:"absolute",left:"-9999px"})},onHide:l,children:[{type:"hbox",id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",children:[{type:"html",id:"logo",html:'<img width="99" height="68" border="0" src="" title="WebSpellChecker.net" alt="WebSpellChecker.net" style="display: inline-block;">',setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}}]}]},
+{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]},{id:"Thesaurus",label:"Thesaurus",accessKey:"T",elements:[{type:"html",id:"banner",label:"banner",style:"",html:"<div></div>"},{type:"html",id:"Content",label:"spellContent",html:"",setup:function(){var b=
+a.iframeNumber+"_"+a.dialog._.currentTabId,c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"vbox",id:"bottomGroup",style:"width:560px; margin: -10px auto; overflow: hidden;",children:[{type:"hbox",widths:["75%","25%"],children:[{type:"vbox",children:[{type:"hbox",widths:["65%","35%"],children:[{type:"text",id:"ChangeTo",label:"Change to:",labelLayout:"horizontal",inputStyle:"width: 160px;",labelStyle:"font: 12px/25px arial, sans-serif;","default":"",onShow:function(){a.textNode.Thesaurus=
+this},onHide:function(){this.reset()}},{type:"button",id:"ChangeTo",label:"Change to",title:"Change to",style:"width: 121px; margin-top: 1px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]},{type:"hbox",children:[{type:"select",id:"categories",label:"Categories:",labelStyle:"font: 12px/25px arial, sans-serif;",size:"5",inputStyle:"width: 180px; height: auto;",items:[],onShow:function(){a.selectNode.categories=this},onChange:function(){a.buildOptionSynonyms(this.getValue())}},
+{type:"select",id:"synonyms",label:"Synonyms:",labelStyle:"font: 12px/25px arial, sans-serif;",size:"5",inputStyle:"width: 180px; height: auto;",items:[],onShow:function(){a.selectNode.synonyms=this;a.textNode.Thesaurus.setValue(this.getValue())},onChange:function(){a.textNode.Thesaurus.setValue(this.getValue())}}]}]},{type:"vbox",width:"120px",style:"margin-top:46px;",children:[{type:"html",id:"logotype",label:"WebSpellChecker.net",html:'<img width="99" height="68" border="0" src="" title="WebSpellChecker.net" alt="WebSpellChecker.net" style="display: inline-block;">',
+setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}},{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 121px; float: right; margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]},{type:"hbox",id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;",widths:["70%","30%"],onShow:function(){this.getElement().setStyles({display:"block",
+position:"absolute",left:"-9999px"})},children:[{type:"hbox",id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",children:[{type:"html",id:"logo",html:'<img width="99" height="68" border="0" src="" title="WebSpellChecker.net" alt="WebSpellChecker.net" style="display: inline-block;">',setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}}]}]},{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",
+id:"rightCol_col__left",children:[{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]}]}});var p=null;CKEDITOR.dialog.add("options",function(){var b=null,c={},d={},f=null,g=null;e.cookie.get("udn");e.cookie.get("osp");var h=function(){g=this.getElement().getAttribute("title-cmd");var a=[];a[0]=d.IgnoreAllCapsWords;a[1]=d.IgnoreWordsNumbers;a[2]=d.IgnoreMixedCaseWords;
+a[3]=d.IgnoreDomainNames;a=a.toString().replace(/,/g,"");e.cookie.set("osp",a);e.cookie.set("udnCmd",g?g:"ignore");"delete"!=g&&(a="",""!==j.getValue()&&(a=j.getValue()),e.cookie.set("udn",a));e.postMessage.send({id:"options_dic_send"})},i=function(){f.getElement().setHtml(a.LocalizationComing.error);f.getElement().show()};return{title:a.LocalizationComing.Options,minWidth:430,minHeight:130,resizable:CKEDITOR.DIALOG_RESIZE_NONE,contents:[{id:"OptionsTab",label:"Options",accessKey:"O",elements:[{type:"hbox",
+id:"options_error",children:[{type:"html",style:"display: block;text-align: center;white-space: normal!important; font-size: 12px;color:red",html:"<div></div>",onShow:function(){f=this}}]},{type:"vbox",id:"Options_content",children:[{type:"hbox",id:"Options_manager",widths:["52%","48%"],children:[{type:"fieldset",label:"Spell Checking Options",style:"border: none;margin-top: 13px;padding: 10px 0 10px 10px",onShow:function(){this.getInputElement().$.children[0].innerHTML=a.LocalizationComing.SpellCheckingOptions},
+children:[{type:"vbox",id:"Options_checkbox",children:[{type:"checkbox",id:"IgnoreAllCapsWords",label:"Ignore All-Caps Words",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox",id:"IgnoreWordsNumbers",label:"Ignore Words with Numbers",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",
+style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox",id:"IgnoreMixedCaseWords",label:"Ignore Mixed-Case Words",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox",id:"IgnoreDomainNames",label:"Ignore Domain Names",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",
+style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}}]}]},{type:"vbox",id:"Options_DictionaryName",children:[{type:"text",id:"DictionaryName",style:"margin-bottom: 10px",label:"Dictionary Name:",labelLayout:"vertical",labelStyle:"font: 12px/25px arial, sans-serif;","default":"",onLoad:function(){j=this;this.setValue(a.userDictionaryName?a.userDictionaryName:(e.cookie.get("udn"),this.getValue()))},onShow:function(){j=this;this.setValue(!e.cookie.get("udn")?
+this.getValue():e.cookie.get("udn"));this.setLabel(a.LocalizationComing.DictionaryName)},onHide:function(){this.reset()}},{type:"hbox",id:"Options_buttons",children:[{type:"vbox",id:"Options_leftCol_col",widths:["50%","50%"],children:[{type:"button",id:"create",label:"Create",title:"Create",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){(this.getElement().getFirst()||this.getElement()).setText(a.LocalizationComing.Create)},onClick:h},
+{type:"button",id:"restore",label:"Restore",title:"Restore",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){(this.getElement().getFirst()||this.getElement()).setText(a.LocalizationComing.Restore)},onClick:h}]},{type:"vbox",id:"Options_rightCol_col",widths:["50%","50%"],children:[{type:"button",id:"rename",label:"Rename",title:"Rename",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){(this.getElement().getFirst()||
+this.getElement()).setText(a.LocalizationComing.Rename)},onClick:h},{type:"button",id:"delete",label:"Remove",title:"Remove",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){(this.getElement().getFirst()||this.getElement()).setText(a.LocalizationComing.Remove)},onClick:h}]}]}]}]},{type:"hbox",id:"Options_text",children:[{type:"html",style:"text-align: justify;margin-top: 15px;white-space: normal!important; font-size: 12px;color:#777;",html:"<div>"+
+a.LocalizationComing.OptionsTextIntro+"</div>",onShow:function(){this.getElement().setText(a.LocalizationComing.OptionsTextIntro)}}]}]}]}],buttons:[CKEDITOR.dialog.okButton,CKEDITOR.dialog.cancelButton],onOk:function(){var a=[];a[0]=d.IgnoreAllCapsWords;a[1]=d.IgnoreWordsNumbers;a[2]=d.IgnoreMixedCaseWords;a[3]=d.IgnoreDomainNames;a=a.toString().replace(/,/g,"");e.cookie.set("osp",a);e.cookie.set("udn",j.getValue());e.postMessage.send({id:"options_checkbox_send"});f.getElement().hide();f.getElement().setHtml(" ")},
+onLoad:function(){b=this;c.IgnoreAllCapsWords=b.getContentElement("OptionsTab","IgnoreAllCapsWords");c.IgnoreWordsNumbers=b.getContentElement("OptionsTab","IgnoreWordsNumbers");c.IgnoreMixedCaseWords=b.getContentElement("OptionsTab","IgnoreMixedCaseWords");c.IgnoreDomainNames=b.getContentElement("OptionsTab","IgnoreDomainNames")},onShow:function(){e.postMessage.init(i);var b=e.cookie.get("osp").split("");d.IgnoreAllCapsWords=b[0];d.IgnoreWordsNumbers=b[1];d.IgnoreMixedCaseWords=b[2];d.IgnoreDomainNames=
+b[3];!parseInt(d.IgnoreAllCapsWords,10)?c.IgnoreAllCapsWords.setValue("",!1):c.IgnoreAllCapsWords.setValue("checked",!1);!parseInt(d.IgnoreWordsNumbers,10)?c.IgnoreWordsNumbers.setValue("",!1):c.IgnoreWordsNumbers.setValue("checked",!1);!parseInt(d.IgnoreMixedCaseWords,10)?c.IgnoreMixedCaseWords.setValue("",!1):c.IgnoreMixedCaseWords.setValue("checked",!1);!parseInt(d.IgnoreDomainNames,10)?c.IgnoreDomainNames.setValue("",!1):c.IgnoreDomainNames.setValue("checked",!1);d.IgnoreAllCapsWords=!c.IgnoreAllCapsWords.getValue()?
+0:1;d.IgnoreWordsNumbers=!c.IgnoreWordsNumbers.getValue()?0:1;d.IgnoreMixedCaseWords=!c.IgnoreMixedCaseWords.getValue()?0:1;d.IgnoreDomainNames=!c.IgnoreDomainNames.getValue()?0:1;c.IgnoreAllCapsWords.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreAllCapsWords;c.IgnoreWordsNumbers.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreWordsWithNumbers;c.IgnoreMixedCaseWords.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreMixedCaseWords;c.IgnoreDomainNames.getElement().$.lastChild.innerHTML=
+a.LocalizationComing.IgnoreDomainNames},onHide:function(){e.postMessage.unbindHandler(i);if(p)try{p.focus()}catch(a){}}}});CKEDITOR.dialog.on("resize",function(b){var b=b.data,c=b.dialog,d=CKEDITOR.document.getById(a.iframeNumber+"_"+c._.currentTabId);"checkspell"==c._.name&&(a.bnr?d&&d.setSize("height",b.height-310):d&&d.setSize("height",b.height-220))});CKEDITOR.on("dialogDefinition",function(b){if("checkspell"===b.data.name){var c=b.data.definition;a.onLoadOverlay=new s({opacity:"1",background:"#fff",
+target:c.dialog.parts.tabs.getParent().$});a.onLoadOverlay.setEnable();c.dialog.on("cancel",function(){c.dialog.getParentEditor().config.wsc_onClose.call(this.document.getWindow().getFrame());a.div_overlay.setDisable();a.onLoadOverlay.setDisable();return!1},this,null,-1)}})})(); \ No newline at end of file
diff --git a/js/ckeditor/plugins/wsc/dialogs/wsc_ie.js b/js/ckeditor/plugins/wsc/dialogs/wsc_ie.js
new file mode 100644
index 0000000..10487d1
--- /dev/null
+++ b/js/ckeditor/plugins/wsc/dialogs/wsc_ie.js
@@ -0,0 +1,11 @@
+/*
+ Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add("checkspell",function(a){function c(a,c){var d=0;return function(){"function"==typeof window.doSpell?("undefined"!=typeof e&&window.clearInterval(e),j(a)):180==d++&&window._cancelOnError(c)}}function j(c){var f=new window._SP_FCK_LangCompare,b=CKEDITOR.getUrl(a.plugins.wsc.path+"dialogs/"),e=b+"tmpFrameset.html";window.gFCKPluginName="wsc";f.setDefaulLangCode(a.config.defaultLanguage);window.doSpell({ctrl:g,lang:a.config.wsc_lang||f.getSPLangCode(a.langCode),intLang:a.config.wsc_uiLang||
+f.getSPLangCode(a.langCode),winType:d,onCancel:function(){c.hide()},onFinish:function(b){a.focus();c.getParentEditor().setData(b.value);c.hide()},staticFrame:e,framesetPath:e,iframePath:b+"ciframe.html",schemaURI:b+"wsc.css",userDictionaryName:a.config.wsc_userDictionaryName,customDictionaryName:a.config.wsc_customDictionaryIds&&a.config.wsc_customDictionaryIds.split(","),domainName:a.config.wsc_domainName});CKEDITOR.document.getById(h).setStyle("display","none");CKEDITOR.document.getById(d).setStyle("display",
+"block")}var b=CKEDITOR.tools.getNextNumber(),d="cke_frame_"+b,g="cke_data_"+b,h="cke_error_"+b,e,b=document.location.protocol||"http:",i=a.lang.wsc.notAvailable,k='<textarea style="display: none" id="'+g+'" rows="10" cols="40"> </textarea><div id="'+h+'" style="display:none;color:red;font-size:16px;font-weight:bold;padding-top:160px;text-align:center;z-index:11;"></div><iframe src="" style="width:100%;background-color:#f1f1e3;" frameborder="0" name="'+d+'" id="'+d+'" allowtransparency="1"></iframe>',
+l=a.config.wsc_customLoaderScript||b+"//loader.webspellchecker.net/sproxy_fck/sproxy.php?plugin=fck2&customerid="+a.config.wsc_customerId+"&cmd=script&doc=wsc&schema=22";a.config.wsc_customLoaderScript&&(i+='<p style="color:#000;font-size:11px;font-weight: normal;text-align:center;padding-top:10px">'+a.lang.wsc.errorLoading.replace(/%s/g,a.config.wsc_customLoaderScript)+"</p>");window._cancelOnError=function(c){if("undefined"==typeof window.WSC_Error){CKEDITOR.document.getById(d).setStyle("display",
+"none");var b=CKEDITOR.document.getById(h);b.setStyle("display","block");b.setHtml(c||a.lang.wsc.notAvailable)}};return{title:a.config.wsc_dialogTitle||a.lang.wsc.title,minWidth:485,minHeight:380,buttons:[CKEDITOR.dialog.cancelButton],onShow:function(){var b=this.getContentElement("general","content").getElement();b.setHtml(k);b.getChild(2).setStyle("height",this._.contentSize.height+"px");"function"!=typeof window.doSpell&&CKEDITOR.document.getHead().append(CKEDITOR.document.createElement("script",
+{attributes:{type:"text/javascript",src:l}}));b=a.getData();CKEDITOR.document.getById(g).setValue(b);e=window.setInterval(c(this,i),250)},onHide:function(){window.ooo=void 0;window.int_framsetLoaded=void 0;window.framesetLoaded=void 0;window.is_window_opened=!1},contents:[{id:"general",label:a.config.wsc_dialogTitle||a.lang.wsc.title,padding:0,elements:[{type:"html",id:"content",html:""}]}]}});
+CKEDITOR.dialog.on("resize",function(a){var a=a.data,c=a.dialog;"checkspell"==c._.name&&((c=(c=c.getContentElement("general","content").getElement())&&c.getChild(2))&&c.setSize("height",a.height),c&&c.setSize("width",a.width))}); \ No newline at end of file
diff --git a/js/ckeditor/samples/ajax.html b/js/ckeditor/samples/ajax.html
new file mode 100644
index 0000000..967e841
--- /dev/null
+++ b/js/ckeditor/samples/ajax.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Ajax &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+ <script>
+
+ var editor, html = '';
+
+ function createEditor() {
+ if ( editor )
+ return;
+
+ // Create a new editor inside the <div id="editor">, setting its value to html
+ var config = {};
+ editor = CKEDITOR.appendTo( 'editor', config, html );
+ }
+
+ function removeEditor() {
+ if ( !editor )
+ return;
+
+ // Retrieve the editor contents. In an Ajax application, this data would be
+ // sent to the server or used in any other way.
+ document.getElementById( 'editorcontents' ).innerHTML = html = editor.getData();
+ document.getElementById( 'contents' ).style.display = '';
+
+ // Destroy the editor.
+ editor.destroy();
+ editor = null;
+ }
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Create and Destroy Editor Instances for Ajax Applications
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to create and destroy CKEditor instances on the fly. After the removal of CKEditor the content created inside the editing
+ area will be displayed in a <code>&lt;div&gt;</code> element.
+ </p>
+ <p>
+ For details of how to create this setup check the source code of this sample page
+ for JavaScript code responsible for the creation and destruction of a CKEditor instance.
+ </p>
+ </div>
+ <p>Click the buttons to create and remove a CKEditor instance.</p>
+ <p>
+ <input onclick="createEditor();" type="button" value="Create Editor">
+ <input onclick="removeEditor();" type="button" value="Remove Editor">
+ </p>
+ <!-- This div will hold the editor. -->
+ <div id="editor">
+ </div>
+ <div id="contents" style="display: none">
+ <p>
+ Edited Contents:
+ </p>
+ <!-- This div will be used to display the editor contents. -->
+ <div id="editorcontents">
+ </div>
+ </div>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/api.html b/js/ckeditor/samples/api.html
new file mode 100644
index 0000000..50f568e
--- /dev/null
+++ b/js/ckeditor/samples/api.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>API Usage &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <script>
+
+// The instanceReady event is fired, when an instance of CKEditor has finished
+// its initialization.
+CKEDITOR.on( 'instanceReady', function( ev ) {
+ // Show the editor name and description in the browser status bar.
+ document.getElementById( 'eMessage' ).innerHTML = 'Instance <code>' + ev.editor.name + '<\/code> loaded.';
+
+ // Show this sample buttons.
+ document.getElementById( 'eButtons' ).style.display = 'block';
+});
+
+function InsertHTML() {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+ var value = document.getElementById( 'htmlArea' ).value;
+
+ // Check the active editing mode.
+ if ( editor.mode == 'wysiwyg' )
+ {
+ // Insert HTML code.
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertHtml
+ editor.insertHtml( value );
+ }
+ else
+ alert( 'You must be in WYSIWYG mode!' );
+}
+
+function InsertText() {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+ var value = document.getElementById( 'txtArea' ).value;
+
+ // Check the active editing mode.
+ if ( editor.mode == 'wysiwyg' )
+ {
+ // Insert as plain text.
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-insertText
+ editor.insertText( value );
+ }
+ else
+ alert( 'You must be in WYSIWYG mode!' );
+}
+
+function SetContents() {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+ var value = document.getElementById( 'htmlArea' ).value;
+
+ // Set editor contents (replace current contents).
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setData
+ editor.setData( value );
+}
+
+function GetContents() {
+ // Get the editor instance that you want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+
+ // Get editor contents
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getData
+ alert( editor.getData() );
+}
+
+function ExecuteCommand( commandName ) {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+
+ // Check the active editing mode.
+ if ( editor.mode == 'wysiwyg' )
+ {
+ // Execute the command.
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-execCommand
+ editor.execCommand( commandName );
+ }
+ else
+ alert( 'You must be in WYSIWYG mode!' );
+}
+
+function CheckDirty() {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+ // Checks whether the current editor contents present changes when compared
+ // to the contents loaded into the editor at startup
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-checkDirty
+ alert( editor.checkDirty() );
+}
+
+function ResetDirty() {
+ // Get the editor instance that we want to interact with.
+ var editor = CKEDITOR.instances.editor1;
+ // Resets the "dirty state" of the editor (see CheckDirty())
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-resetDirty
+ editor.resetDirty();
+ alert( 'The "IsDirty" status has been reset' );
+}
+
+function Focus() {
+ CKEDITOR.instances.editor1.focus();
+}
+
+function onFocus() {
+ document.getElementById( 'eMessage' ).innerHTML = '<b>' + this.name + ' is focused </b>';
+}
+
+function onBlur() {
+ document.getElementById( 'eMessage' ).innerHTML = this.name + ' lost focus';
+}
+
+ </script>
+
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Using CKEditor JavaScript API
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to use the
+ <a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.editor">CKEditor JavaScript API</a>
+ to interact with the editor at runtime.
+ </p>
+ <p>
+ For details on how to create this setup check the source code of this sample page.
+ </p>
+ </div>
+
+ <!-- This <div> holds alert messages to be display in the sample page. -->
+ <div id="alerts">
+ <noscript>
+ <p>
+ <strong>CKEditor requires JavaScript to run</strong>. In a browser with no JavaScript
+ support, like yours, you should still see the contents (HTML data) and you should
+ be able to edit it normally, without a rich editor interface.
+ </p>
+ </noscript>
+ </div>
+ <form action="../../../samples/sample_posteddata.php" method="post">
+ <textarea cols="100" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+
+ <script>
+ // Replace the <textarea id="editor1"> with an CKEditor instance.
+ CKEDITOR.replace( 'editor1', {
+ on: {
+ focus: onFocus,
+ blur: onBlur,
+
+ // Check for availability of corresponding plugins.
+ pluginsLoaded: function( evt ) {
+ var doc = CKEDITOR.document, ed = evt.editor;
+ if ( !ed.getCommand( 'bold' ) )
+ doc.getById( 'exec-bold' ).hide();
+ if ( !ed.getCommand( 'link' ) )
+ doc.getById( 'exec-link' ).hide();
+ }
+ }
+ });
+ </script>
+
+ <p id="eMessage">
+ </p>
+
+ <div id="eButtons" style="display: none">
+ <input id="exec-bold" onclick="ExecuteCommand('bold');" type="button" value="Execute &quot;bold&quot; Command">
+ <input id="exec-link" onclick="ExecuteCommand('link');" type="button" value="Execute &quot;link&quot; Command">
+ <input onclick="Focus();" type="button" value="Focus">
+ <br><br>
+ <input onclick="InsertHTML();" type="button" value="Insert HTML">
+ <input onclick="SetContents();" type="button" value="Set Editor Contents">
+ <input onclick="GetContents();" type="button" value="Get Editor Contents (HTML)">
+ <br>
+ <textarea cols="100" id="htmlArea" rows="3">&lt;h2&gt;Test&lt;/h2&gt;&lt;p&gt;This is some &lt;a href="/Test1.html"&gt;sample&lt;/a&gt; HTML code.&lt;/p&gt;</textarea>
+ <br>
+ <br>
+ <input onclick="InsertText();" type="button" value="Insert Text">
+ <br>
+ <textarea cols="100" id="txtArea" rows="3"> First line with some leading whitespaces.
+
+Second line of text preceded by two line breaks.</textarea>
+ <br>
+ <br>
+ <input onclick="CheckDirty();" type="button" value="checkDirty()">
+ <input onclick="ResetDirty();" type="button" value="resetDirty()">
+ </div>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/appendto.html b/js/ckeditor/samples/appendto.html
new file mode 100644
index 0000000..8ed16b6
--- /dev/null
+++ b/js/ckeditor/samples/appendto.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Append To Page Element Using JavaScript Code &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Append To Page Element Using JavaScript Code
+ </h1>
+ <div id="section1">
+ <div class="description">
+ <p>
+ The <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR-method-appendTo">CKEDITOR.appendTo()</a></code> method serves to to place editors inside existing DOM elements. Unlike <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR-method-replace">CKEDITOR.replace()</a></code>,
+ a target container to be replaced is no longer necessary. A new editor
+ instance is inserted directly wherever it is desired.
+ </p>
+<pre class="samples">CKEDITOR.appendTo( '<em>container_id</em>',
+ { /* Configuration options to be used. */ }
+ 'Editor content to be used.'
+);</pre>
+ </div>
+ <script>
+
+ // This call can be placed at any point after the
+ // DOM element to append CKEditor to or inside the <head><script>
+ // in a window.onload event handler.
+
+ // Append a CKEditor instance using the default configuration and the
+ // provided content to the <div> element of ID "section1".
+ CKEDITOR.appendTo( 'section1',
+ null,
+ '<p>This is some <strong>sample text</strong>. You are using <a href="http://ckeditor.com/">CKEditor</a>.</p>'
+ );
+
+ </script>
+ </div>
+ <br>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/assets/inlineall/logo.png b/js/ckeditor/samples/assets/inlineall/logo.png
new file mode 100644
index 0000000..b4d5979
--- /dev/null
+++ b/js/ckeditor/samples/assets/inlineall/logo.png
Binary files differ
diff --git a/js/ckeditor/samples/assets/outputxhtml/outputxhtml.css b/js/ckeditor/samples/assets/outputxhtml/outputxhtml.css
new file mode 100644
index 0000000..1b3bf64
--- /dev/null
+++ b/js/ckeditor/samples/assets/outputxhtml/outputxhtml.css
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ *
+ * Styles used by the XHTML 1.1 sample page (xhtml.html).
+ */
+
+/**
+ * Basic definitions for the editing area.
+ */
+body
+{
+ font-family: Arial, Verdana, sans-serif;
+ font-size: 80%;
+ color: #000000;
+ background-color: #ffffff;
+ padding: 5px;
+ margin: 0px;
+}
+
+/**
+ * Core styles.
+ */
+
+.Bold
+{
+ font-weight: bold;
+}
+
+.Italic
+{
+ font-style: italic;
+}
+
+.Underline
+{
+ text-decoration: underline;
+}
+
+.StrikeThrough
+{
+ text-decoration: line-through;
+}
+
+.Subscript
+{
+ vertical-align: sub;
+ font-size: smaller;
+}
+
+.Superscript
+{
+ vertical-align: super;
+ font-size: smaller;
+}
+
+/**
+ * Font faces.
+ */
+
+.FontComic
+{
+ font-family: 'Comic Sans MS';
+}
+
+.FontCourier
+{
+ font-family: 'Courier New';
+}
+
+.FontTimes
+{
+ font-family: 'Times New Roman';
+}
+
+/**
+ * Font sizes.
+ */
+
+.FontSmaller
+{
+ font-size: smaller;
+}
+
+.FontLarger
+{
+ font-size: larger;
+}
+
+.FontSmall
+{
+ font-size: 8pt;
+}
+
+.FontBig
+{
+ font-size: 14pt;
+}
+
+.FontDouble
+{
+ font-size: 200%;
+}
+
+/**
+ * Font colors.
+ */
+.FontColor1
+{
+ color: #ff9900;
+}
+
+.FontColor2
+{
+ color: #0066cc;
+}
+
+.FontColor3
+{
+ color: #ff0000;
+}
+
+.FontColor1BG
+{
+ background-color: #ff9900;
+}
+
+.FontColor2BG
+{
+ background-color: #0066cc;
+}
+
+.FontColor3BG
+{
+ background-color: #ff0000;
+}
+
+/**
+ * Indentation.
+ */
+
+.Indent1
+{
+ margin-left: 40px;
+}
+
+.Indent2
+{
+ margin-left: 80px;
+}
+
+.Indent3
+{
+ margin-left: 120px;
+}
+
+/**
+ * Alignment.
+ */
+
+.JustifyLeft
+{
+ text-align: left;
+}
+
+.JustifyRight
+{
+ text-align: right;
+}
+
+.JustifyCenter
+{
+ text-align: center;
+}
+
+.JustifyFull
+{
+ text-align: justify;
+}
+
+/**
+ * Other.
+ */
+
+code
+{
+ font-family: courier, monospace;
+ background-color: #eeeeee;
+ padding-left: 1px;
+ padding-right: 1px;
+ border: #c0c0c0 1px solid;
+}
+
+kbd
+{
+ padding: 0px 1px 0px 1px;
+ border-width: 1px 2px 2px 1px;
+ border-style: solid;
+}
+
+blockquote
+{
+ color: #808080;
+}
diff --git a/js/ckeditor/samples/assets/posteddata.php b/js/ckeditor/samples/assets/posteddata.php
new file mode 100644
index 0000000..1e1406f
--- /dev/null
+++ b/js/ckeditor/samples/assets/posteddata.php
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<?php
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+?>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Sample &mdash; CKEditor</title>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ CKEditor &mdash; Posted Data
+ </h1>
+ <table border="1" cellspacing="0" id="outputSample">
+ <colgroup><col width="120"></colgroup>
+ <thead>
+ <tr>
+ <th>Field&nbsp;Name</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+<?php
+
+if (!empty($_POST))
+{
+ foreach ( $_POST as $key => $value )
+ {
+ if ( ( !is_string($value) && !is_numeric($value) ) || !is_string($key) )
+ continue;
+
+ if ( get_magic_quotes_gpc() )
+ $value = htmlspecialchars( stripslashes((string)$value) );
+ else
+ $value = htmlspecialchars( (string)$value );
+?>
+ <tr>
+ <th style="vertical-align: top"><?php echo htmlspecialchars( (string)$key ); ?></th>
+ <td><pre class="samples"><?php echo $value; ?></pre></td>
+ </tr>
+ <?php
+ }
+}
+?>
+ </table>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/assets/sample.css b/js/ckeditor/samples/assets/sample.css
new file mode 100644
index 0000000..a47e4dd
--- /dev/null
+++ b/js/ckeditor/samples/assets/sample.css
@@ -0,0 +1,3 @@
+/**
+ * Required by tests (dom/document.html).
+ */
diff --git a/js/ckeditor/samples/assets/sample.jpg b/js/ckeditor/samples/assets/sample.jpg
new file mode 100644
index 0000000..9498271
--- /dev/null
+++ b/js/ckeditor/samples/assets/sample.jpg
Binary files differ
diff --git a/js/ckeditor/samples/assets/uilanguages/languages.js b/js/ckeditor/samples/assets/uilanguages/languages.js
new file mode 100644
index 0000000..df9c682
--- /dev/null
+++ b/js/ckeditor/samples/assets/uilanguages/languages.js
@@ -0,0 +1,7 @@
+/*
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+var CKEDITOR_LANGS=function(){var c={af:"Afrikaans",ar:"Arabic",bg:"Bulgarian",bn:"Bengali/Bangla",bs:"Bosnian",ca:"Catalan",cs:"Czech",cy:"Welsh",da:"Danish",de:"German",el:"Greek",en:"English","en-au":"English (Australia)","en-ca":"English (Canadian)","en-gb":"English (United Kingdom)",eo:"Esperanto",es:"Spanish",et:"Estonian",eu:"Basque",fa:"Persian",fi:"Finnish",fo:"Faroese",fr:"French","fr-ca":"French (Canada)",gl:"Galician",gu:"Gujarati",he:"Hebrew",hi:"Hindi",hr:"Croatian",hu:"Hungarian",id:"Indonesian",
+is:"Icelandic",it:"Italian",ja:"Japanese",ka:"Georgian",km:"Khmer",ko:"Korean",ku:"Kurdish",lt:"Lithuanian",lv:"Latvian",mk:"Macedonian",mn:"Mongolian",ms:"Malay",nb:"Norwegian Bokmal",nl:"Dutch",no:"Norwegian",pl:"Polish",pt:"Portuguese (Portugal)","pt-br":"Portuguese (Brazil)",ro:"Romanian",ru:"Russian",si:"Sinhala",sk:"Slovak",sq:"Albanian",sl:"Slovenian",sr:"Serbian (Cyrillic)","sr-latn":"Serbian (Latin)",sv:"Swedish",th:"Thai",tr:"Turkish",tt:"Tatar",ug:"Uighur",uk:"Ukrainian",vi:"Vietnamese",
+zh:"Chinese Traditional","zh-cn":"Chinese Simplified"},b=[],a;for(a in CKEDITOR.lang.languages)b.push({code:a,name:c[a]||a});b.sort(function(a,b){return a.name<b.name?-1:1});return b}(); \ No newline at end of file
diff --git a/js/ckeditor/samples/datafiltering.html b/js/ckeditor/samples/datafiltering.html
new file mode 100644
index 0000000..1165594
--- /dev/null
+++ b/js/ckeditor/samples/datafiltering.html
@@ -0,0 +1,401 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Data Filtering &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+ <script>
+ // Remove advanced tabs for all editors.
+ CKEDITOR.config.removeDialogTabs = 'image:advanced;link:advanced;flash:advanced;creatediv:advanced;editdiv:advanced';
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Data Filtering and Features Activation
+ </h1>
+ <div class="description">
+ <p>
+ This sample page demonstrates the idea of Advanced Content Filter
+ (<abbr title="Advanced Content Filter">ACF</abbr>), a sophisticated
+ tool that takes control over what kind of data is accepted by the editor and what
+ kind of output is produced.
+ </p>
+ <h2>When and what is being filtered?</h2>
+ <p>
+ <abbr title="Advanced Content Filter">ACF</abbr> controls
+ <strong>every single source of data</strong> that comes to the editor.
+ It process both HTML that is inserted manually (i.e. pasted by the user)
+ and programmatically like:
+ </p>
+<pre class="samples">
+editor.setData( '&lt;p&gt;Hello world!&lt;/p&gt;' );
+</pre>
+ <p>
+ <abbr title="Advanced Content Filter">ACF</abbr> discards invalid,
+ useless HTML tags and attributes so the editor remains "clean" during
+ runtime. <abbr title="Advanced Content Filter">ACF</abbr> behaviour
+ can be configured and adjusted for a particular case to prevent the
+ output HTML (i.e. in CMS systems) from being polluted.
+
+ This kind of filtering is a first, client-side line of defense
+ against "<a href="http://en.wikipedia.org/wiki/Tag_soup">tag soups</a>",
+ the tool that precisely restricts which tags, attributes and styles
+ are allowed (desired). When properly configured, <abbr title="Advanced Content Filter">ACF</abbr>
+ is an easy and fast way to produce a high-quality, intentionally filtered HTML.
+ </p>
+
+ <h3>How to configure or disable ACF?</h3>
+ <p>
+ Advanced Content Filter is enabled by default, working in "automatic mode", yet
+ it provides a set of easy rules that allow adjusting filtering rules
+ and disabling the entire feature when necessary. The config property
+ responsible for this feature is <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">config.allowedContent</a></code>.
+ </p>
+ <p>
+ By "automatic mode" is meant that loaded plugins decide which kind
+ of content is enabled and which is not. For example, if the link
+ plugin is loaded it implies that <code>&lt;a&gt;</code> tag is
+ automatically allowed. Each plugin is given a set
+ of predefined <abbr title="Advanced Content Filter">ACF</abbr> rules
+ that control the editor until <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">
+ config.allowedContent</a></code>
+ is defined manually.
+ </p>
+ <p>
+ Let's assume our intention is to restrict the editor to accept (produce) <strong>paragraphs
+ only: no attributes, no styles, no other tags</strong>.
+ With <abbr title="Advanced Content Filter">ACF</abbr>
+ this is very simple. Basically set <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">
+ config.allowedContent</a></code> to <code>'p'</code>:
+ </p>
+<pre class="samples">
+var editor = CKEDITOR.replace( <em>textarea_id</em>, {
+ <strong>allowedContent: 'p'</strong>
+} );
+</pre>
+ <p>
+ Now try to play with allowed content:
+ </p>
+<pre class="samples">
+// Trying to insert disallowed tag and attribute.
+editor.setData( '&lt;p <strong>style="color: red"</strong>&gt;Hello <strong>&lt;em&gt;world&lt;/em&gt;</strong>!&lt;/p&gt;' );
+alert( editor.getData() );
+
+// Filtered data is returned.
+"&lt;p&gt;Hello world!&lt;/p&gt;"
+</pre>
+ <p>
+ What happened? Since <code>config.allowedContent: 'p'</code> is set the editor assumes
+ that only plain <code>&lt;p&gt;</code> are accepted. Nothing more. This is why
+ <code>style</code> attribute and <code>&lt;em&gt;</code> tag are gone. The same
+ filtering would happen if we pasted disallowed HTML into this editor.
+ </p>
+ <p>
+ This is just a small sample of what <abbr title="Advanced Content Filter">ACF</abbr>
+ can do. To know more, please refer to the sample section below and
+ <a href="http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter">the official Advanced Content Filter guide</a>.
+ </p>
+ <p>
+ You may, of course, want CKEditor to avoid filtering of any kind.
+ To get rid of <abbr title="Advanced Content Filter">ACF</abbr>,
+ basically set <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">
+ config.allowedContent</a></code> to <code>true</code> like this:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( <em>textarea_id</em>, {
+ <strong>allowedContent: true</strong>
+} );
+</pre>
+
+ <h2>Beyond data flow: Features activation</h2>
+ <p>
+ <abbr title="Advanced Content Filter">ACF</abbr> is far more than
+ <abbr title="Input/Output">I/O</abbr> control: the entire
+ <abbr title="User Interface">UI</abbr> of the editor is adjusted to what
+ filters restrict. For example: if <code>&lt;a&gt;</code> tag is
+ <strong>disallowed</strong>
+ by <abbr title="Advanced Content Filter">ACF</abbr>,
+ then accordingly <code>link</code> command, toolbar button and link dialog
+ are also disabled. Editor is smart: it knows which features must be
+ removed from the interface to match filtering rules.
+ </p>
+ <p>
+ CKEditor can be far more specific. If <code>&lt;a&gt;</code> tag is
+ <strong>allowed</strong> by filtering rules to be used but it is restricted
+ to have only one attribute (<code>href</code>)
+ <code>config.allowedContent = 'a[!href]'</code>, then
+ "Target" tab of the link dialog is automatically disabled as <code>target</code>
+ attribute isn't included in <abbr title="Advanced Content Filter">ACF</abbr> rules
+ for <code>&lt;a&gt;</code>. This behaviour applies to dialog fields, context
+ menus and toolbar buttons.
+ </p>
+
+ <h2>Sample configurations</h2>
+ <p>
+ There are several editor instances below that present different
+ <abbr title="Advanced Content Filter">ACF</abbr> setups. <strong>All of them,
+ except the last inline instance, share the same HTML content</strong> to visualize
+ how different filtering rules affect the same input data.
+ </p>
+ </div>
+
+ <div>
+ <label for="editor1">
+ Editor 1:
+ </label>
+ <div class="description">
+ <p>
+ This editor is using default configuration ("automatic mode"). It means that
+ <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">
+ config.allowedContent</a></code> is defined by loaded plugins.
+ Each plugin extends filtering rules to make it's own associated content
+ available for the user.
+ </p>
+ </div>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+
+ <script>
+
+ CKEDITOR.replace( 'editor1' );
+
+ </script>
+ </div>
+
+ <br>
+
+ <div>
+ <label for="editor2">
+ Editor 2:
+ </label>
+ <div class="description">
+ <p>
+ This editor is using a custom configuration for
+ <abbr title="Advanced Content Filter">ACF</abbr>:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( 'editor2', {
+ allowedContent:
+ 'h1 h2 h3 p blockquote strong em;' +
+ 'a[!href];' +
+ 'img(left,right)[!src,alt,width,height];' +
+ 'table tr th td caption;' +
+ 'span{!font-family};' +'
+ 'span{!color};' +
+ 'span(!marker);' +
+ 'del ins'
+} );
+</pre>
+ <p>
+ The following rules may require additional explanation:
+ </p>
+ <ul>
+ <li>
+ <code>h1 h2 h3 p blockquote strong em</code> - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+ </li>
+ <li>
+ <code>a[!href]</code> - <code>href</code> attribute is obligatory
+ for <code>&lt;a&gt;</code> tag. Tags without this attribute
+ are disarded. No other attribute will be accepted.
+ </li>
+ <li>
+ <code>img(left,right)[!src,alt,width,height]</code> - <code>src</code>
+ attribute is obligatory for <code>&lt;img&gt;</code> tag.
+ <code>alt</code>, <code>width</code>, <code>height</code>
+ and <code>class</code> attributes are accepted but
+ <code>class</code> must be either <code>class="left"</code>
+ or <code>class="right"</code>
+ </li>
+ <li>
+ <code>table tr th td caption</code> - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+ </li>
+ <li>
+ <code>span{!font-family}</code>, <code>span{!color}</code>,
+ <code>span(!marker)</code> - <code>&lt;span&gt;</code> tags
+ will be accepted if either <code>font-family</code> or
+ <code>color</code> style is set or <code>class="marker"</code>
+ is present.
+ </li>
+ <li>
+ <code>del ins</code> - These tags
+ are accepted by the editor. Any tag attributes will be discarded.
+ </li>
+ </ul>
+ <p>
+ Please note that <strong><abbr title="User Interface">UI</abbr> of the
+ editor is different</strong>. It's a response to what happened to the filters.
+ Since <code>text-align</code> isn't allowed, the align toolbar is gone.
+ The same thing happened to subscript/superscript, strike, underline
+ (<code>&lt;u&gt;</code>, <code>&lt;sub&gt;</code>, <code>&lt;sup&gt;</code>
+ are disallowed by <code><a class="samples"
+ href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent">
+ config.allowedContent</a></code>) and many other buttons.
+ </p>
+ </div>
+ <textarea cols="80" id="editor2" name="editor2" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor2', {
+ allowedContent:
+ 'h1 h2 h3 p blockquote strong em;' +
+ 'a[!href];' +
+ 'img(left,right)[!src,alt,width,height];' +
+ 'table tr th td caption;' +
+ 'span{!font-family};' +
+ 'span{!color};' +
+ 'span(!marker);' +
+ 'del ins'
+ } );
+
+ </script>
+ </div>
+
+ <br>
+
+ <div>
+ <label for="editor3">
+ Editor 3:
+ </label>
+ <div class="description">
+ <p>
+ This editor is using a custom configuration for
+ <abbr title="Advanced Content Filter">ACF</abbr>.
+ Note that filters can be configured as an object literal
+ as an alternative to a string-based definition.
+ </p>
+<pre class="samples">
+CKEDITOR.replace( 'editor3', {
+ allowedContent: {
+ 'b i ul ol big small': true,
+ 'h1 h2 h3 p blockquote li': {
+ styles: 'text-align'
+ },
+ a: { attributes: '!href,target' },
+ img: {
+ attributes: '!src,alt',
+ styles: 'width,height',
+ classes: 'left,right'
+ }
+ }
+} );
+</pre>
+ </div>
+ <textarea cols="80" id="editor3" name="editor3" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor3', {
+ allowedContent: {
+ 'b i ul ol big small': true,
+ 'h1 h2 h3 p blockquote li': {
+ styles: 'text-align'
+ },
+ a: { attributes: '!href,target' },
+ img: {
+ attributes: '!src,alt',
+ styles: 'width,height',
+ classes: 'left,right'
+ }
+ }
+ } );
+
+ </script>
+ </div>
+
+ <br>
+
+ <div>
+ <label for="editor4">
+ Editor 4:
+ </label>
+ <div class="description">
+ <p>
+ This editor is using a custom set of plugins and buttons.
+ </p>
+<pre class="samples">
+CKEDITOR.replace( 'editor4', {
+ removePlugins: 'bidi,font,forms,flash,horizontalrule,iframe,justify,table,tabletools,smiley',
+ removeButtons: 'Anchor,Underline,Strike,Subscript,Superscript,Image',
+ format_tags: 'p;h1;h2;h3;pre;address'
+} );
+</pre>
+ <p>
+ As you can see, removing plugins and buttons implies filtering.
+ Several tags are not allowed in the editor because there's no
+ plugin/button that is responsible for creating and editing this
+ kind of content (for example: the image is missing because
+ of <code>removeButtons: 'Image'</code>). The conclusion is that
+ <abbr title="Advanced Content Filter">ACF</abbr> works "backwards"
+ as well: <strong>modifying <abbr title="User Interface">UI</abbr>
+ elements is changing allowed content rules</strong>.
+ </p>
+ </div>
+ <textarea cols="80" id="editor4" name="editor4" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor4', {
+ removePlugins: 'bidi,div,font,forms,flash,horizontalrule,iframe,justify,table,tabletools,smiley',
+ removeButtons: 'Anchor,Underline,Strike,Subscript,Superscript,Image',
+ format_tags: 'p;h1;h2;h3;pre;address'
+ } );
+
+ </script>
+ </div>
+
+ <br>
+
+ <div>
+ <label for="editor5">
+ Editor 5:
+ </label>
+ <div class="description">
+ <p>
+ This editor is built on editable <code>&lt;h1&gt;</code> element.
+ <abbr title="Advanced Content Filter">ACF</abbr> takes care of
+ what can be included in <code>&lt;h1&gt;</code>. Note that there
+ are no block styles in Styles combo. Also why lists, indentation,
+ blockquote, div, form and other buttons are missing.
+ </p>
+ <p>
+ <abbr title="Advanced Content Filter">ACF</abbr> makes sure that
+ no disallowed tags will come to <code>&lt;h1&gt;</code> so the final
+ markup is valid. If the user tried to paste some invalid HTML
+ into this editor (let's say a list), it would be automatically
+ converted into plain text.
+ </p>
+ </div>
+ <h1 id="editor5" contenteditable="true">
+ <em>Apollo 11</em> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC.
+ </h1>
+ </div>
+
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/divreplace.html b/js/ckeditor/samples/divreplace.html
new file mode 100644
index 0000000..b87086a
--- /dev/null
+++ b/js/ckeditor/samples/divreplace.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Replace DIV &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ div.editable
+ {
+ border: solid 2px transparent;
+ padding-left: 15px;
+ padding-right: 15px;
+ }
+
+ div.editable:hover
+ {
+ border-color: black;
+ }
+
+ </style>
+ <script>
+
+ // Uncomment the following code to test the "Timeout Loading Method".
+ // CKEDITOR.loadFullCoreTimeout = 5;
+
+ window.onload = function() {
+ // Listen to the double click event.
+ if ( window.addEventListener )
+ document.body.addEventListener( 'dblclick', onDoubleClick, false );
+ else if ( window.attachEvent )
+ document.body.attachEvent( 'ondblclick', onDoubleClick );
+
+ };
+
+ function onDoubleClick( ev ) {
+ // Get the element which fired the event. This is not necessarily the
+ // element to which the event has been attached.
+ var element = ev.target || ev.srcElement;
+
+ // Find out the div that holds this element.
+ var name;
+
+ do {
+ element = element.parentNode;
+ }
+ while ( element && ( name = element.nodeName.toLowerCase() ) &&
+ ( name != 'div' || element.className.indexOf( 'editable' ) == -1 ) && name != 'body' );
+
+ if ( name == 'div' && element.className.indexOf( 'editable' ) != -1 )
+ replaceDiv( element );
+ }
+
+ var editor;
+
+ function replaceDiv( div ) {
+ if ( editor )
+ editor.destroy();
+
+ editor = CKEDITOR.replace( div );
+ }
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Replace DIV with CKEditor on the Fly
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to automatically replace <code>&lt;div&gt;</code> elements
+ with a CKEditor instance on the fly, following user's doubleclick. The content
+ that was previously placed inside the <code>&lt;div&gt;</code> element will now
+ be moved into CKEditor editing area.
+ </p>
+ <p>
+ For details on how to create this setup check the source code of this sample page.
+ </p>
+ </div>
+ <p>
+ Double-click any of the following <code>&lt;div&gt;</code> elements to transform them into
+ editor instances.
+ </p>
+ <div class="editable">
+ <h3>
+ Part 1
+ </h3>
+ <p>
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+ </p>
+ </div>
+ <div class="editable">
+ <h3>
+ Part 2
+ </h3>
+ <p>
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+ </p>
+ <p>
+ Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus
+ sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum
+ vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate.
+ </p>
+ </div>
+ <div class="editable">
+ <h3>
+ Part 3
+ </h3>
+ <p>
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi
+ semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna
+ rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla
+ nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce
+ eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus.
+ </p>
+ </div>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/index.html b/js/ckeditor/samples/index.html
new file mode 100644
index 0000000..3b6f9af
--- /dev/null
+++ b/js/ckeditor/samples/index.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>CKEditor Samples</title>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ CKEditor Samples
+ </h1>
+ <div class="twoColumns">
+ <div class="twoColumnsLeft">
+ <h2 class="samples">
+ Basic Samples
+ </h2>
+ <dl class="samples">
+ <dt><a class="samples" href="replacebyclass.html">Replace textarea elements by class name</a></dt>
+ <dd>Automatic replacement of all textarea elements of a given class with a CKEditor instance.</dd>
+
+ <dt><a class="samples" href="replacebycode.html">Replace textarea elements by code</a></dt>
+ <dd>Replacement of textarea elements with CKEditor instances by using a JavaScript call.</dd>
+
+ <dt><a class="samples" href="jquery.html">Create editors with jQuery</a></dt>
+ <dd>Creating standard and inline CKEditor instances with jQuery adapter.</dd>
+ </dl>
+
+ <h2 class="samples">
+ Basic Customization
+ </h2>
+ <dl class="samples">
+ <dt><a class="samples" href="uicolor.html">User Interface color</a></dt>
+ <dd>Changing CKEditor User Interface color and adding a toolbar button that lets the user set the UI color.</dd>
+
+ <dt><a class="samples" href="uilanguages.html">User Interface languages</a></dt>
+ <dd>Changing CKEditor User Interface language and adding a drop-down list that lets the user choose the UI language.</dd>
+ </dl>
+
+
+ <h2 class="samples">Plugins</h2>
+<dl class="samples">
+<dt><a class="samples" href="plugins/magicline/magicline.html">Magicline plugin</a></dt>
+<dd>Using the Magicline plugin to access difficult focus spaces.</dd>
+
+<dt><a class="samples" href="plugins/wysiwygarea/fullpage.html">Full page support</a></dt>
+<dd>CKEditor inserted with a JavaScript call and used to edit the whole page from &lt;html&gt; to &lt;/html&gt;.</dd>
+</dl>
+ </div>
+ <div class="twoColumnsRight">
+ <h2 class="samples">
+ Inline Editing
+ </h2>
+ <dl class="samples">
+ <dt><a class="samples" href="inlineall.html">Massive inline editor creation</a></dt>
+ <dd>Turn all elements with <code>contentEditable = true</code> attribute into inline editors.</dd>
+
+ <dt><a class="samples" href="inlinebycode.html">Convert element into an inline editor by code</a></dt>
+ <dd>Conversion of DOM elements into inline CKEditor instances by using a JavaScript call.</dd>
+
+ <dt><a class="samples" href="inlinetextarea.html">Replace textarea with inline editor</a> <span class="new">New!</span></dt>
+ <dd>A form with a textarea that is replaced by an inline editor at runtime.</dd>
+
+
+ </dl>
+
+ <h2 class="samples">
+ Advanced Samples
+ </h2>
+ <dl class="samples">
+ <dt><a class="samples" href="datafiltering.html">Data filtering and features activation</a> <span class="new">New!</span></dt>
+ <dd>Data filtering and automatic features activation basing on configuration.</dd>
+
+ <dt><a class="samples" href="divreplace.html">Replace DIV elements on the fly</a></dt>
+ <dd>Transforming a <code>div</code> element into an instance of CKEditor with a mouse click.</dd>
+
+ <dt><a class="samples" href="appendto.html">Append editor instances</a></dt>
+ <dd>Appending editor instances to existing DOM elements.</dd>
+
+ <dt><a class="samples" href="ajax.html">Create and destroy editor instances for Ajax applications</a></dt>
+ <dd>Creating and destroying CKEditor instances on the fly and saving the contents entered into the editor window.</dd>
+
+ <dt><a class="samples" href="api.html">Basic usage of the API</a></dt>
+ <dd>Using the CKEditor JavaScript API to interact with the editor at runtime.</dd>
+
+ <dt><a class="samples" href="xhtmlstyle.html">XHTML-compliant style</a></dt>
+ <dd>Configuring CKEditor to produce XHTML 1.1 compliant attributes and styles.</dd>
+
+ <dt><a class="samples" href="readonly.html">Read-only mode</a></dt>
+ <dd>Using the readOnly API to block introducing changes to the editor contents.</dd>
+
+ <dt><a class="samples" href="tabindex.html">"Tab" key-based navigation</a></dt>
+ <dd>Navigating among editor instances with tab key.</dd>
+
+
+
+<dt><a class="samples" href="plugins/dialog/dialog.html">Using the JavaScript API to customize dialog windows</a></dt>
+<dd>Using the dialog windows API to customize dialog windows without changing the original editor code.</dd>
+
+<dt><a class="samples" href="plugins/enterkey/enterkey.html">Using the &quot;Enter&quot; key in CKEditor</a></dt>
+<dd>Configuring the behavior of <em>Enter</em> and <em>Shift+Enter</em> keys.</dd>
+
+<dt><a class="samples" href="plugins/htmlwriter/outputforflash.html">Output for Flash</a></dt>
+<dd>Configuring CKEditor to produce HTML code that can be used with Adobe Flash.</dd>
+
+<dt><a class="samples" href="plugins/htmlwriter/outputhtml.html">Output HTML</a></dt>
+<dd>Configuring CKEditor to produce legacy HTML 4 code.</dd>
+
+<dt><a class="samples" href="plugins/toolbar/toolbar.html">Toolbar Configurations</a></dt>
+<dd>Configuring CKEditor to display full or custom toolbar layout.</dd>
+
+ </dl>
+ </div>
+ </div>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/inlineall.html b/js/ckeditor/samples/inlineall.html
new file mode 100644
index 0000000..fc33619
--- /dev/null
+++ b/js/ckeditor/samples/inlineall.html
@@ -0,0 +1,311 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Massive inline editing &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <script>
+
+ // This code is generally not necessary, but it is here to demonstrate
+ // how to customize specific editor instances on the fly. This fits well
+ // this demo because we have editable elements (like headers) that
+ // require less features.
+
+ // The "instanceCreated" event is fired for every editor instance created.
+ CKEDITOR.on( 'instanceCreated', function( event ) {
+ var editor = event.editor,
+ element = editor.element;
+
+ // Customize editors for headers and tag list.
+ // These editors don't need features like smileys, templates, iframes etc.
+ if ( element.is( 'h1', 'h2', 'h3' ) || element.getAttribute( 'id' ) == 'taglist' ) {
+ // Customize the editor configurations on "configLoaded" event,
+ // which is fired after the configuration file loading and
+ // execution. This makes it possible to change the
+ // configurations before the editor initialization takes place.
+ editor.on( 'configLoaded', function() {
+
+ // Remove unnecessary plugins to make the editor simpler.
+ editor.config.removePlugins = 'colorbutton,find,flash,font,' +
+ 'forms,iframe,image,newpage,removeformat,' +
+ 'smiley,specialchar,stylescombo,templates';
+
+ // Rearrange the layout of the toolbar.
+ editor.config.toolbarGroups = [
+ { name: 'editing', groups: [ 'basicstyles', 'links' ] },
+ { name: 'undo' },
+ { name: 'clipboard', groups: [ 'selection', 'clipboard' ] },
+ { name: 'about' }
+ ];
+ });
+ }
+ });
+
+ </script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ /* The following styles are just to make the page look nice. */
+
+ /* Workaround to show Arial Black in Firefox. */
+ @font-face
+ {
+ font-family: 'arial-black';
+ src: local('Arial Black');
+ }
+
+ *[contenteditable="true"]
+ {
+ padding: 10px;
+ }
+
+ #container
+ {
+ width: 960px;
+ margin: 30px auto 0;
+ }
+
+ #header
+ {
+ overflow: hidden;
+ padding: 0 0 30px;
+ border-bottom: 5px solid #05B2D2;
+ position: relative;
+ }
+
+ #headerLeft,
+ #headerRight
+ {
+ width: 49%;
+ overflow: hidden;
+ }
+
+ #headerLeft
+ {
+ float: left;
+ padding: 10px 1px 1px;
+ }
+
+ #headerLeft h2,
+ #headerLeft h3
+ {
+ text-align: right;
+ margin: 0;
+ overflow: hidden;
+ font-weight: normal;
+ }
+
+ #headerLeft h2
+ {
+ font-family: "Arial Black",arial-black;
+ font-size: 4.6em;
+ line-height: 1.1;
+ text-transform: uppercase;
+ }
+
+ #headerLeft h3
+ {
+ font-size: 2.3em;
+ line-height: 1.1;
+ margin: .2em 0 0;
+ color: #666;
+ }
+
+ #headerRight
+ {
+ float: right;
+ padding: 1px;
+ }
+
+ #headerRight p
+ {
+ line-height: 1.8;
+ text-align: justify;
+ margin: 0;
+ }
+
+ #headerRight p + p
+ {
+ margin-top: 20px;
+ }
+
+ #headerRight > div
+ {
+ padding: 20px;
+ margin: 0 0 0 30px;
+ font-size: 1.4em;
+ color: #666;
+ }
+
+ #columns
+ {
+ color: #333;
+ overflow: hidden;
+ padding: 20px 0;
+ }
+
+ #columns > div
+ {
+ float: left;
+ width: 33.3%;
+ }
+
+ #columns #column1 > div
+ {
+ margin-left: 1px;
+ }
+
+ #columns #column3 > div
+ {
+ margin-right: 1px;
+ }
+
+ #columns > div > div
+ {
+ margin: 0px 10px;
+ padding: 10px 20px;
+ }
+
+ #columns blockquote
+ {
+ margin-left: 15px;
+ }
+
+ #tagLine
+ {
+ border-top: 5px solid #05B2D2;
+ padding-top: 20px;
+ }
+
+ #taglist {
+ display: inline-block;
+ margin-left: 20px;
+ font-weight: bold;
+ margin: 0 0 0 20px;
+ }
+
+ </style>
+</head>
+<body>
+<div>
+ <h1 class="samples"><a href="index.html">CKEditor Samples</a> &raquo; Massive inline editing</h1>
+ <div class="description">
+ <p>This sample page demonstrates the inline editing feature - CKEditor instances will be created automatically from page elements with <strong>contentEditable</strong> attribute set to value <strong>true</strong>:</p>
+ <pre class="samples">&lt;div <strong>contenteditable="true</strong>" &gt; ... &lt;/div&gt;</pre>
+ <p>Click inside of any element below to start editing.</p>
+ </div>
+</div>
+<div id="container">
+ <div id="header">
+ <div id="headerLeft">
+ <h2 id="sampleTitle" contenteditable="true">
+ CKEditor<br> Goes Inline!
+ </h2>
+ <h3 contenteditable="true">
+ Lorem ipsum dolor sit amet dolor duis blandit vestibulum faucibus a, tortor.
+ </h3>
+ </div>
+ <div id="headerRight">
+ <div contenteditable="true">
+ <p>
+ Lorem ipsum dolor sit amet enim. Etiam ullamcorper. Suspendisse a pellentesque dui, non felis. Maecenas malesuada elit lectus felis, malesuada ultricies.
+ </p>
+ <p>
+ Curabitur et ligula. Ut molestie a, ultricies porta urna. Vestibulum commodo volutpat a, convallis ac, laoreet enim. Phasellus fermentum in, dolor. Pellentesque facilisis. Nulla imperdiet sit amet magna. Vestibulum dapibus, mauris nec malesuada fames ac.
+ </p>
+ </div>
+ </div>
+ </div>
+ <div id="columns">
+ <div id="column1">
+ <div contenteditable="true">
+ <h3>
+ Fusce vitae porttitor
+ </h3>
+ <p>
+ <strong>
+ Lorem ipsum dolor sit amet dolor. Duis blandit vestibulum faucibus a, tortor.
+ </strong>
+ </p>
+ <p>
+ Proin nunc justo felis mollis tincidunt, risus risus pede, posuere cubilia Curae, Nullam euismod, enim. Etiam nibh ultricies dolor ac dignissim erat volutpat. Vivamus fermentum <a href="http://ckeditor.com/">nisl nulla sem in</a> metus. Maecenas wisi. Donec nec erat volutpat.
+ </p>
+ <blockquote>
+ <p>
+ Fusce vitae porttitor a, euismod convallis nisl, blandit risus tortor, pretium.
+ Vehicula vitae, imperdiet vel, ornare enim vel sodales rutrum
+ </p>
+ </blockquote>
+ <blockquote>
+ <p>
+ Libero nunc, rhoncus ante ipsum non ipsum. Nunc eleifend pede turpis id sollicitudin fringilla. Phasellus ultrices, velit ac arcu.
+ </p>
+ </blockquote>
+ <p>Pellentesque nunc. Donec suscipit erat. Pellentesque habitant morbi tristique ullamcorper.</p>
+ <p><s>Mauris mattis feugiat lectus nec mauris. Nullam vitae ante.</s></p>
+ </div>
+ </div>
+ <div id="column2">
+ <div contenteditable="true">
+ <h3>
+ Integer condimentum sit amet
+ </h3>
+ <p>
+ <strong>Aenean nonummy a, mattis varius. Cras aliquet.</strong>
+ Praesent <a href="http://ckeditor.com/">magna non mattis ac, rhoncus nunc</a>, rhoncus eget, cursus pulvinar mollis.</p>
+ <p>Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.</p>
+ <p>Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.</p>
+ </div>
+ <div contenteditable="true">
+ <h3>
+ Praesent wisi accumsan sit amet nibh
+ </h3>
+ <p>Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.</p>
+ <p style="margin-left: 40px; ">Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce <a href="http://ckeditor.com/">gravida, erat vitae augue</a>. Fusce urna fringilla gravida.</p>
+ <p>In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.</p>
+ </div>
+ </div>
+ <div id="column3">
+ <div contenteditable="true">
+ <p>
+ <img src="assets/inlineall/logo.png" alt="CKEditor logo" style="float:left">
+ </p>
+ <p>Quisque justo neque, mattis sed, fermentum ultrices <strong>posuere cubilia Curae</strong>, Vestibulum elit metus, quis placerat ut, lectus. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi.</p>
+ <h3>
+ Nullam laoreet vel consectetuer tellus suscipit
+ </h3>
+ <ul>
+ <li>Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis.</li>
+ <li>Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi.</li>
+ <li>Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.</li>
+ </ul>
+ <p>Quisque justo neque, mattis sed, <a href="http://ckeditor.com/">fermentum ultrices posuere cubilia</a> Curae, Vestibulum elit metus, quis placerat ut, lectus.</p>
+ <p>Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis.</p>
+ <p>Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi. Sed in nonummy faucibus turpis. Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.</p>
+ </div>
+ </div>
+ </div>
+ <div id="tagLine">
+ Tags of this article:
+ <p id="taglist" contenteditable="true">
+ inline, editing, floating, CKEditor
+ </p>
+ </div>
+</div>
+<div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">
+ http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a>
+ - Frederico Knabben. All rights reserved.
+ </p>
+</div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/inlinebycode.html b/js/ckeditor/samples/inlinebycode.html
new file mode 100644
index 0000000..c1df0e9
--- /dev/null
+++ b/js/ckeditor/samples/inlinebycode.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Inline Editing by Code &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ #editable
+ {
+ padding: 10px;
+ float: left;
+ }
+
+ </style>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Inline Editing by Code
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to create an inline editor instance of CKEditor. It is created
+ with a JavaScript call using the following code:
+ </p>
+<pre class="samples">
+// This property tells CKEditor to not activate every element with contenteditable=true element.
+CKEDITOR.disableAutoInline = true;
+
+var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );
+</pre>
+ <p>
+ Note that <code>editable</code> in the code above is the <code>id</code>
+ attribute of the <code>&lt;div&gt;</code> element to be converted into an inline instance.
+ </p>
+ </div>
+ <div id="editable" contenteditable="true">
+ <h1><img alt="Saturn V carrying Apollo 11" class="right" src="assets/sample.jpg" /> Apollo 11</h1>
+
+ <p><b>Apollo 11</b> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
+
+ <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.</p>
+
+ <h2>Broadcasting and <em>quotes</em> <a id="quotes" name="quotes"></a></h2>
+
+ <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
+
+ <blockquote>
+ <p>One small step for [a] man, one giant leap for mankind.</p>
+ </blockquote>
+
+ <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p>
+
+ <blockquote>
+ <p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p>
+ </blockquote>
+
+ <h2>Technical details <a id="tech-details" name="tech-details"></a></h2>
+
+ <table align="right" border="1" bordercolor="#ccc" cellpadding="5" cellspacing="0" style="border-collapse:collapse;margin:10px 0 10px 15px;">
+ <caption><strong>Mission crew</strong></caption>
+ <thead>
+ <tr>
+ <th scope="col">Position</th>
+ <th scope="col">Astronaut</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Commander</td>
+ <td>Neil A. Armstrong</td>
+ </tr>
+ <tr>
+ <td>Command Module Pilot</td>
+ <td>Michael Collins</td>
+ </tr>
+ <tr>
+ <td>Lunar Module Pilot</td>
+ <td>Edwin &quot;Buzz&quot; E. Aldrin, Jr.</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>Launched by a <strong>Saturn V</strong> rocket from <a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center">Kennedy Space Center</a> in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of <a href="http://en.wikipedia.org/wiki/NASA" title="NASA">NASA</a>&#39;s Apollo program. The Apollo spacecraft had three parts:</p>
+
+ <ol>
+ <li><strong>Command Module</strong> with a cabin for the three astronauts which was the only part which landed back on Earth</li>
+ <li><strong>Service Module</strong> which supported the Command Module with propulsion, electrical power, oxygen and water</li>
+ <li><strong>Lunar Module</strong> for landing on the Moon.</li>
+ </ol>
+
+ <p>After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the <a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis">Sea of Tranquility</a>. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the <a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean">Pacific Ocean</a> on July 24.</p>
+
+ <hr />
+ <p style="text-align: right;"><small>Source: <a href="http://en.wikipedia.org/wiki/Apollo_11">Wikipedia.org</a></small></p>
+ </div>
+
+ <script>
+ // We need to turn off the automatic editor creation first.
+ CKEDITOR.disableAutoInline = true;
+
+ var editor = CKEDITOR.inline( 'editable' );
+ </script>
+ <div id="footer">
+ <hr>
+ <p contenteditable="true">
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">
+ http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a>
+ - Frederico Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/inlinetextarea.html b/js/ckeditor/samples/inlinetextarea.html
new file mode 100644
index 0000000..9e3d077
--- /dev/null
+++ b/js/ckeditor/samples/inlinetextarea.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Replace Textarea with Inline Editor &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ /* Style the CKEditor element to look like a textfield */
+ .cke_textarea_inline
+ {
+ padding: 10px;
+ height: 200px;
+ overflow: auto;
+
+ border: 1px solid gray;
+ -webkit-appearance: textfield;
+ }
+
+ </style>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Replace Textarea with Inline Editor
+ </h1>
+ <div class="description">
+ <p>
+ You can also create an inline editor from a <code>textarea</code>
+ element. In this case the <code>textarea</code> will be replaced
+ by a <code>div</code> element with inline editing enabled.
+ </p>
+<pre class="samples">
+// "article-body" is the name of a textarea element.
+var editor = CKEDITOR.inline( 'article-body' );
+</pre>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <h2>This is a sample form with some fields</h2>
+ <p>
+ Title:<br>
+ <input type="text" name="title" value="Sample Form"></p>
+ <p>
+ Article Body (Textarea converted to CKEditor):<br>
+ <textarea name="article-body" style="height: 200px">
+ &lt;h2&gt;Technical details &lt;a id="tech-details" name="tech-details"&gt;&lt;/a&gt;&lt;/h2&gt;
+
+ &lt;table align="right" border="1" bordercolor="#ccc" cellpadding="5" cellspacing="0" style="border-collapse:collapse;margin:10px 0 10px 15px;"&gt;
+ &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt;
+ &lt;thead&gt;
+ &lt;tr&gt;
+ &lt;th scope="col"&gt;Position&lt;/th&gt;
+ &lt;th scope="col"&gt;Astronaut&lt;/th&gt;
+ &lt;/tr&gt;
+ &lt;/thead&gt;
+ &lt;tbody&gt;
+ &lt;tr&gt;
+ &lt;td&gt;Commander&lt;/td&gt;
+ &lt;td&gt;Neil A. Armstrong&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;Command Module Pilot&lt;/td&gt;
+ &lt;td&gt;Michael Collins&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;Lunar Module Pilot&lt;/td&gt;
+ &lt;td&gt;Edwin &quot;Buzz&quot; E. Aldrin, Jr.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/tbody&gt;
+ &lt;/table&gt;
+
+ &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href="http://en.wikipedia.org/wiki/Kennedy_Space_Center" title="Kennedy Space Center"&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href="http://en.wikipedia.org/wiki/NASA" title="NASA"&gt;NASA&lt;/a&gt;&#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt;
+
+ &lt;ol&gt;
+ &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt;
+ &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt;
+ &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt;
+ &lt;/ol&gt;
+
+ &lt;p&gt;After being sent to the Moon by the Saturn V&#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href="http://en.wikipedia.org/wiki/Mare_Tranquillitatis" title="Mare Tranquillitatis"&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href="http://en.wikipedia.org/wiki/Pacific_Ocean" title="Pacific Ocean"&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt;
+
+ &lt;hr /&gt;
+ &lt;p style="text-align: right;"&gt;&lt;small&gt;Source: &lt;a href="http://en.wikipedia.org/wiki/Apollo_11"&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+
+ <script>
+ CKEDITOR.inline( 'article-body' );
+ </script>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">
+ http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a>
+ - Frederico Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/jquery.html b/js/ckeditor/samples/jquery.html
new file mode 100644
index 0000000..74e128a
--- /dev/null
+++ b/js/ckeditor/samples/jquery.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>jQuery Adapter &mdash; CKEditor Sample</title>
+ <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
+ <script src="../ckeditor.js"></script>
+ <script src="../adapters/jquery.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ #editable
+ {
+ padding: 10px;
+ float: left;
+ }
+
+ </style>
+ <script>
+
+ CKEDITOR.disableAutoInline = true;
+
+ $( document ).ready( function() {
+ $( '#editor1' ).ckeditor(); // Use CKEDITOR.replace() if element is <textarea>.
+ $( '#editable' ).ckeditor(); // Use CKEDITOR.inline().
+ } );
+
+ function setValue() {
+ $( '#editor1' ).val( $( 'input#val' ).val() );
+ }
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html" id="a-test">CKEditor Samples</a> &raquo; Create Editors with jQuery
+ </h1>
+ <form action="sample_posteddata.php" method="post">
+ <div class="description">
+ <p>
+ This sample shows how to use the <a href="http://docs.ckeditor.com/#!/guide/dev_jquery">jQuery adapter</a>.
+ Note that you have to include both CKEditor and jQuery scripts before including the adapter.
+ </p>
+
+<pre class="samples">
+&lt;script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"&gt;&lt;/script&gt;
+&lt;script src="/ckeditor/ckeditor.js"&gt;&lt;/script&gt;
+&lt;script src="/ckeditor/adapters/jquery.js"&gt;&lt;/script&gt;
+</pre>
+
+ <p>Then you can replace HTML elements with a CKEditor instance using the <code>ckeditor()</code> method.</p>
+
+<pre class="samples">
+$( document ).ready( function() {
+ $( 'textarea#editor1' ).ckeditor();
+} );
+</pre>
+ </div>
+
+ <h2 class="samples">Inline Example</h2>
+
+ <div id="editable" contenteditable="true">
+ <p><img alt="Saturn V carrying Apollo 11" class="right" src="assets/sample.jpg"/><b>Apollo 11</b> was the spaceflight that landed the first humans, Americans <a href="http://en.wikipedia.org/wiki/Neil_Armstrong" title="Neil Armstrong">Neil Armstrong</a> and <a href="http://en.wikipedia.org/wiki/Buzz_Aldrin" title="Buzz Aldrin">Buzz Aldrin</a>, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.</p>
+ <p>Armstrong spent about <s>three and a half</s> two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&nbsp;kg) of lunar material for return to Earth. A third member of the mission, <a href="http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)" title="Michael Collins (astronaut)">Michael Collins</a>, piloted the <a href="http://en.wikipedia.org/wiki/Apollo_Command/Service_Module" title="Apollo Command/Service Module">command</a> spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.
+ <p>Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:</p>
+ <blockquote><p>One small step for [a] man, one giant leap for mankind.</p></blockquote> <p>Apollo 11 effectively ended the <a href="http://en.wikipedia.org/wiki/Space_Race" title="Space Race">Space Race</a> and fulfilled a national goal proposed in 1961 by the late U.S. President <a href="http://en.wikipedia.org/wiki/John_F._Kennedy" title="John F. Kennedy">John F. Kennedy</a> in a speech before the United States Congress:</p> <blockquote><p>[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.</p></blockquote>
+ </div>
+
+ <br style="clear: both">
+
+ <h2 class="samples">Classic (iframe-based) Example</h2>
+
+ <textarea cols="80" id="editor1" name="editor1" rows="10">
+ &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+
+ <p style="overflow: hidden">
+ <input style="float: left" type="submit" value="Submit">
+ <span style="float: right">
+ <input type="text" id="val" value="I'm using jQuery val()!" size="30">
+ <input onclick="setValue();" type="button" value="Set value">
+ </span>
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/dialog/assets/my_dialog.js b/js/ckeditor/samples/plugins/dialog/assets/my_dialog.js
new file mode 100644
index 0000000..8a9ea63
--- /dev/null
+++ b/js/ckeditor/samples/plugins/dialog/assets/my_dialog.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+
+CKEDITOR.dialog.add( 'myDialog', function() {
+ return {
+ title: 'My Dialog',
+ minWidth: 400,
+ minHeight: 200,
+ contents: [
+ {
+ id: 'tab1',
+ label: 'First Tab',
+ title: 'First Tab',
+ elements: [
+ {
+ id: 'input1',
+ type: 'text',
+ label: 'Text Field'
+ },
+ {
+ id: 'select1',
+ type: 'select',
+ label: 'Select Field',
+ items: [
+ [ 'option1', 'value1' ],
+ [ 'option2', 'value2' ]
+ ]
+ }
+ ]
+ },
+ {
+ id: 'tab2',
+ label: 'Second Tab',
+ title: 'Second Tab',
+ elements: [
+ {
+ id: 'button1',
+ type: 'button',
+ label: 'Button Field'
+ }
+ ]
+ }
+ ]
+ };
+} );
+
diff --git a/js/ckeditor/samples/plugins/dialog/dialog.html b/js/ckeditor/samples/plugins/dialog/dialog.html
new file mode 100644
index 0000000..a85c566
--- /dev/null
+++ b/js/ckeditor/samples/plugins/dialog/dialog.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Using API to Customize Dialog Windows &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <link rel="stylesheet" href="../../../samples/sample.css">
+ <meta name="ckeditor-sample-name" content="Using the JavaScript API to customize dialog windows">
+ <meta name="ckeditor-sample-group" content="Advanced Samples">
+ <meta name="ckeditor-sample-description" content="Using the dialog windows API to customize dialog windows without changing the original editor code.">
+ <style>
+
+ .cke_button__mybutton_icon
+ {
+ display: none !important;
+ }
+
+ .cke_button__mybutton_label
+ {
+ display: inline !important;
+ }
+
+ </style>
+ <script>
+
+ CKEDITOR.on( 'instanceCreated', function( ev ){
+ var editor = ev.editor;
+
+ // Listen for the "pluginsLoaded" event, so we are sure that the
+ // "dialog" plugin has been loaded and we are able to do our
+ // customizations.
+ editor.on( 'pluginsLoaded', function() {
+
+ // If our custom dialog has not been registered, do that now.
+ if ( !CKEDITOR.dialog.exists( 'myDialog' ) ) {
+ // We need to do the following trick to find out the dialog
+ // definition file URL path. In the real world, you would simply
+ // point to an absolute path directly, like "/mydir/mydialog.js".
+ var href = document.location.href.split( '/' );
+ href.pop();
+ href.push( 'assets/my_dialog.js' );
+ href = href.join( '/' );
+
+ // Finally, register the dialog.
+ CKEDITOR.dialog.add( 'myDialog', href );
+ }
+
+ // Register the command used to open the dialog.
+ editor.addCommand( 'myDialogCmd', new CKEDITOR.dialogCommand( 'myDialog' ) );
+
+ // Add the a custom toolbar buttons, which fires the above
+ // command..
+ editor.ui.add( 'MyButton', CKEDITOR.UI_BUTTON, {
+ label: 'My Dialog',
+ command: 'myDialogCmd'
+ });
+ });
+ });
+
+ // When opening a dialog, its "definition" is created for it, for
+ // each editor instance. The "dialogDefinition" event is then
+ // fired. We should use this event to make customizations to the
+ // definition of existing dialogs.
+ CKEDITOR.on( 'dialogDefinition', function( ev ) {
+ // Take the dialog name and its definition from the event data.
+ var dialogName = ev.data.name;
+ var dialogDefinition = ev.data.definition;
+
+ // Check if the definition is from the dialog we're
+ // interested on (the "Link" dialog).
+ if ( dialogName == 'myDialog' && ev.editor.name == 'editor2' ) {
+ // Get a reference to the "Link Info" tab.
+ var infoTab = dialogDefinition.getContents( 'tab1' );
+
+ // Add a new text field to the "tab1" tab page.
+ infoTab.add( {
+ type: 'text',
+ label: 'My Custom Field',
+ id: 'customField',
+ 'default': 'Sample!',
+ validate: function() {
+ if ( ( /\d/ ).test( this.getValue() ) )
+ return 'My Custom Field must not contain digits';
+ }
+ });
+
+ // Remove the "select1" field from the "tab1" tab.
+ infoTab.remove( 'select1' );
+
+ // Set the default value for "input1" field.
+ var input1 = infoTab.get( 'input1' );
+ input1[ 'default' ] = 'www.example.com';
+
+ // Remove the "tab2" tab page.
+ dialogDefinition.removeContents( 'tab2' );
+
+ // Add a new tab to the "Link" dialog.
+ dialogDefinition.addContents( {
+ id: 'customTab',
+ label: 'My Tab',
+ accessKey: 'M',
+ elements: [
+ {
+ id: 'myField1',
+ type: 'text',
+ label: 'My Text Field'
+ },
+ {
+ id: 'myField2',
+ type: 'text',
+ label: 'Another Text Field'
+ }
+ ]
+ });
+
+ // Provide the focus handler to start initial focus in "customField" field.
+ dialogDefinition.onFocus = function() {
+ var urlField = this.getContentElement( 'tab1', 'customField' );
+ urlField.select();
+ };
+ }
+ });
+
+ var config = {
+ extraPlugins: 'dialog',
+ toolbar: [ [ 'MyButton' ] ]
+ };
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Using CKEditor Dialog API
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to use the
+ <a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.dialog">CKEditor Dialog API</a>
+ to customize CKEditor dialog windows without changing the original editor code.
+ The following customizations are being done in the example below:
+ </p>
+ <p>
+ For details on how to create this setup check the source code of this sample page.
+ </p>
+ </div>
+ <p>A custom dialog is added to the editors using the <code>pluginsLoaded</code> event, from an external <a target="_blank" href="assets/my_dialog.js">dialog definition file</a>:</p>
+ <ol>
+ <li><strong>Creating a custom dialog window</strong> &ndash; "My Dialog" dialog window opened with the "My Dialog" toolbar button.</li>
+ <li><strong>Creating a custom button</strong> &ndash; Add button to open the dialog with "My Dialog" toolbar button.</li>
+ </ol>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+ // Replace the <textarea id="editor1"> with an CKEditor instance.
+ CKEDITOR.replace( 'editor1', config );
+ </script>
+ <p>The below editor modify the dialog definition of the above added dialog using the <code>dialogDefinition</code> event:</p>
+ <ol>
+ <li><strong>Adding dialog tab</strong> &ndash; Add new tab "My Tab" to dialog window.</li>
+ <li><strong>Removing a dialog window tab</strong> &ndash; Remove "Second Tab" page from the dialog window.</li>
+ <li><strong>Adding dialog window fields</strong> &ndash; Add "My Custom Field" to the dialog window.</li>
+ <li><strong>Removing dialog window field</strong> &ndash; Remove "Select Field" selection field from the dialog window.</li>
+ <li><strong>Setting default values for dialog window fields</strong> &ndash; Set default value of "Text Field" text field. </li>
+ <li><strong>Setup initial focus for dialog window</strong> &ndash; Put initial focus on "My Custom Field" text field. </li>
+ </ol>
+ <textarea cols="80" id="editor2" name="editor2" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+
+ // Replace the <textarea id="editor1"> with an CKEditor instance.
+ CKEDITOR.replace( 'editor2', config );
+
+ </script>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/enterkey/enterkey.html b/js/ckeditor/samples/plugins/enterkey/enterkey.html
new file mode 100644
index 0000000..61fbc72
--- /dev/null
+++ b/js/ckeditor/samples/plugins/enterkey/enterkey.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>ENTER Key Configuration &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <link href="../../../samples/sample.css" rel="stylesheet">
+ <meta name="ckeditor-sample-name" content="Using the &quot;Enter&quot; key in CKEditor">
+ <meta name="ckeditor-sample-group" content="Advanced Samples">
+ <meta name="ckeditor-sample-description" content="Configuring the behavior of &lt;em&gt;Enter&lt;/em&gt; and &lt;em&gt;Shift+Enter&lt;/em&gt; keys.">
+ <script>
+
+ var editor;
+
+ function changeEnter() {
+ // If we already have an editor, let's destroy it first.
+ if ( editor )
+ editor.destroy( true );
+
+ // Create the editor again, with the appropriate settings.
+ editor = CKEDITOR.replace( 'editor1', {
+ extraPlugins: 'enterkey',
+ enterMode: Number( document.getElementById( 'xEnter' ).value ),
+ shiftEnterMode: Number( document.getElementById( 'xShiftEnter' ).value )
+ });
+ }
+
+ window.onload = changeEnter;
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; ENTER Key Configuration
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to configure the <em>Enter</em> and <em>Shift+Enter</em> keys
+ to perform actions specified in the
+ <a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode"><code>enterMode</code></a>
+ and <a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-shiftEnterMode"><code>shiftEnterMode</code></a>
+ parameters, respectively.
+ You can choose from the following options:
+ </p>
+ <ul class="samples">
+ <li><strong><code>ENTER_P</code></strong> &ndash; new <code>&lt;p&gt;</code> paragraphs are created;</li>
+ <li><strong><code>ENTER_BR</code></strong> &ndash; lines are broken with <code>&lt;br&gt;</code> elements;</li>
+ <li><strong><code>ENTER_DIV</code></strong> &ndash; new <code>&lt;div&gt;</code> blocks are created.</li>
+ </ul>
+ <p>
+ The sample code below shows how to configure CKEditor to create a <code>&lt;div&gt;</code> block when <em>Enter</em> key is pressed.
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ <strong>enterMode: CKEDITOR.ENTER_DIV</strong>
+});</pre>
+ <p>
+ Note that <code><em>textarea_id</em></code> in the code above is the <code>id</code> attribute of
+ the <code>&lt;textarea&gt;</code> element to be replaced.
+ </p>
+ </div>
+ <div style="float: left; margin-right: 20px">
+ When <em>Enter</em> is pressed:<br>
+ <select id="xEnter" onchange="changeEnter();">
+ <option selected="selected" value="1">Create a new &lt;P&gt; (recommended)</option>
+ <option value="3">Create a new &lt;DIV&gt;</option>
+ <option value="2">Break the line with a &lt;BR&gt;</option>
+ </select>
+ </div>
+ <div style="float: left">
+ When <em>Shift+Enter</em> is pressed:<br>
+ <select id="xShiftEnter" onchange="changeEnter();">
+ <option value="1">Create a new &lt;P&gt;</option>
+ <option value="3">Create a new &lt;DIV&gt;</option>
+ <option selected="selected" value="2">Break the line with a &lt;BR&gt; (recommended)</option>
+ </select>
+ </div>
+ <br style="clear: both">
+ <form action="../../../samples/sample_posteddata.php" method="post">
+ <p>
+ <br>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.</textarea>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla
new file mode 100644
index 0000000..27e68cc
--- /dev/null
+++ b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla
Binary files differ
diff --git a/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf
new file mode 100644
index 0000000..dbe17b6
--- /dev/null
+++ b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf
Binary files differ
diff --git a/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js
new file mode 100644
index 0000000..95fdf0a
--- /dev/null
+++ b/js/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js
@@ -0,0 +1,18 @@
+var swfobject=function(){function u(){if(!s){try{var a=d.getElementsByTagName("body")[0].appendChild(d.createElement("span"));a.parentNode.removeChild(a)}catch(b){return}s=!0;for(var a=x.length,c=0;c<a;c++)x[c]()}}function L(a){s?a():x[x.length]=a}function M(a){if(typeof m.addEventListener!=i)m.addEventListener("load",a,!1);else if(typeof d.addEventListener!=i)d.addEventListener("load",a,!1);else if(typeof m.attachEvent!=i)U(m,"onload",a);else if("function"==typeof m.onload){var b=m.onload;m.onload=
+function(){b();a()}}else m.onload=a}function V(){var a=d.getElementsByTagName("body")[0],b=d.createElement(r);b.setAttribute("type",y);var c=a.appendChild(b);if(c){var f=0;(function(){if(typeof c.GetVariable!=i){var g=c.GetVariable("$version");g&&(g=g.split(" ")[1].split(","),e.pv=[parseInt(g[0],10),parseInt(g[1],10),parseInt(g[2],10)])}else if(10>f){f++;setTimeout(arguments.callee,10);return}a.removeChild(b);c=null;D()})()}else D()}function D(){var a=p.length;if(0<a)for(var b=0;b<a;b++){var c=p[b].id,
+f=p[b].callbackFn,g={success:!1,id:c};if(0<e.pv[0]){var d=n(c);if(d)if(z(p[b].swfVersion)&&!(e.wk&&312>e.wk))t(c,!0),f&&(g.success=!0,g.ref=E(c),f(g));else if(p[b].expressInstall&&F()){g={};g.data=p[b].expressInstall;g.width=d.getAttribute("width")||"0";g.height=d.getAttribute("height")||"0";d.getAttribute("class")&&(g.styleclass=d.getAttribute("class"));d.getAttribute("align")&&(g.align=d.getAttribute("align"));for(var h={},d=d.getElementsByTagName("param"),j=d.length,k=0;k<j;k++)"movie"!=d[k].getAttribute("name").toLowerCase()&&
+(h[d[k].getAttribute("name")]=d[k].getAttribute("value"));G(g,h,c,f)}else W(d),f&&f(g)}else if(t(c,!0),f){if((c=E(c))&&typeof c.SetVariable!=i)g.success=!0,g.ref=c;f(g)}}}function E(a){var b=null;if((a=n(a))&&"OBJECT"==a.nodeName)typeof a.SetVariable!=i?b=a:(a=a.getElementsByTagName(r)[0])&&(b=a);return b}function F(){return!A&&z("6.0.65")&&(e.win||e.mac)&&!(e.wk&&312>e.wk)}function G(a,b,c,f){A=!0;H=f||null;N={success:!1,id:c};var g=n(c);if(g){"OBJECT"==g.nodeName?(w=I(g),B=null):(w=g,B=c);a.id=
+O;if(typeof a.width==i||!/%$/.test(a.width)&&310>parseInt(a.width,10))a.width="310";if(typeof a.height==i||!/%$/.test(a.height)&&137>parseInt(a.height,10))a.height="137";d.title=d.title.slice(0,47)+" - Flash Player Installation";f=e.ie&&e.win?"ActiveX":"PlugIn";f="MMredirectURL="+m.location.toString().replace(/&/g,"%26")+"&MMplayerType="+f+"&MMdoctitle="+d.title;b.flashvars=typeof b.flashvars!=i?b.flashvars+("&"+f):f;e.ie&&(e.win&&4!=g.readyState)&&(f=d.createElement("div"),c+="SWFObjectNew",f.setAttribute("id",
+c),g.parentNode.insertBefore(f,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}());J(a,b,c)}}function W(a){if(e.ie&&e.win&&4!=a.readyState){var b=d.createElement("div");a.parentNode.insertBefore(b,a);b.parentNode.replaceChild(I(a),b);a.style.display="none";(function(){4==a.readyState?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)})()}else a.parentNode.replaceChild(I(a),a)}function I(a){var b=d.createElement("div");if(e.win&&
+e.ie)b.innerHTML=a.innerHTML;else if(a=a.getElementsByTagName(r)[0])if(a=a.childNodes)for(var c=a.length,f=0;f<c;f++)!(1==a[f].nodeType&&"PARAM"==a[f].nodeName)&&8!=a[f].nodeType&&b.appendChild(a[f].cloneNode(!0));return b}function J(a,b,c){var f,g=n(c);if(e.wk&&312>e.wk)return f;if(g)if(typeof a.id==i&&(a.id=c),e.ie&&e.win){var o="",h;for(h in a)a[h]!=Object.prototype[h]&&("data"==h.toLowerCase()?b.movie=a[h]:"styleclass"==h.toLowerCase()?o+=' class="'+a[h]+'"':"classid"!=h.toLowerCase()&&(o+=" "+
+h+'="'+a[h]+'"'));h="";for(var j in b)b[j]!=Object.prototype[j]&&(h+='<param name="'+j+'" value="'+b[j]+'" />');g.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+o+">"+h+"</object>";C[C.length]=a.id;f=n(a.id)}else{j=d.createElement(r);j.setAttribute("type",y);for(var k in a)a[k]!=Object.prototype[k]&&("styleclass"==k.toLowerCase()?j.setAttribute("class",a[k]):"classid"!=k.toLowerCase()&&j.setAttribute(k,a[k]));for(o in b)b[o]!=Object.prototype[o]&&"movie"!=o.toLowerCase()&&
+(a=j,h=o,k=b[o],c=d.createElement("param"),c.setAttribute("name",h),c.setAttribute("value",k),a.appendChild(c));g.parentNode.replaceChild(j,g);f=j}return f}function P(a){var b=n(a);b&&"OBJECT"==b.nodeName&&(e.ie&&e.win?(b.style.display="none",function(){if(4==b.readyState){var c=n(a);if(c){for(var f in c)"function"==typeof c[f]&&(c[f]=null);c.parentNode.removeChild(c)}}else setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function n(a){var b=null;try{b=d.getElementById(a)}catch(c){}return b}
+function U(a,b,c){a.attachEvent(b,c);v[v.length]=[a,b,c]}function z(a){var b=e.pv,a=a.split(".");a[0]=parseInt(a[0],10);a[1]=parseInt(a[1],10)||0;a[2]=parseInt(a[2],10)||0;return b[0]>a[0]||b[0]==a[0]&&b[1]>a[1]||b[0]==a[0]&&b[1]==a[1]&&b[2]>=a[2]?!0:!1}function Q(a,b,c,f){if(!e.ie||!e.mac){var g=d.getElementsByTagName("head")[0];if(g){c=c&&"string"==typeof c?c:"screen";f&&(K=l=null);if(!l||K!=c)f=d.createElement("style"),f.setAttribute("type","text/css"),f.setAttribute("media",c),l=g.appendChild(f),
+e.ie&&(e.win&&typeof d.styleSheets!=i&&0<d.styleSheets.length)&&(l=d.styleSheets[d.styleSheets.length-1]),K=c;e.ie&&e.win?l&&typeof l.addRule==r&&l.addRule(a,b):l&&typeof d.createTextNode!=i&&l.appendChild(d.createTextNode(a+" {"+b+"}"))}}}function t(a,b){if(R){var c=b?"visible":"hidden";s&&n(a)?n(a).style.visibility=c:Q("#"+a,"visibility:"+c)}}function S(a){return null!=/[\\\"<>\.;]/.exec(a)&&typeof encodeURIComponent!=i?encodeURIComponent(a):a}var i="undefined",r="object",y="application/x-shockwave-flash",
+O="SWFObjectExprInst",m=window,d=document,q=navigator,T=!1,x=[function(){T?V():D()}],p=[],C=[],v=[],w,B,H,N,s=!1,A=!1,l,K,R=!0,e=function(){var a=typeof d.getElementById!=i&&typeof d.getElementsByTagName!=i&&typeof d.createElement!=i,b=q.userAgent.toLowerCase(),c=q.platform.toLowerCase(),f=c?/win/.test(c):/win/.test(b),c=c?/mac/.test(c):/mac/.test(b),b=/webkit/.test(b)?parseFloat(b.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,g=!+"\v1",e=[0,0,0],h=null;if(typeof q.plugins!=i&&typeof q.plugins["Shockwave Flash"]==
+r){if((h=q.plugins["Shockwave Flash"].description)&&!(typeof q.mimeTypes!=i&&q.mimeTypes[y]&&!q.mimeTypes[y].enabledPlugin))T=!0,g=!1,h=h.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),e[0]=parseInt(h.replace(/^(.*)\..*$/,"$1"),10),e[1]=parseInt(h.replace(/^.*\.(.*)\s.*$/,"$1"),10),e[2]=/[a-zA-Z]/.test(h)?parseInt(h.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}else if(typeof m.ActiveXObject!=i)try{var j=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(j&&(h=j.GetVariable("$version")))g=!0,h=h.split(" ")[1].split(","),
+e=[parseInt(h[0],10),parseInt(h[1],10),parseInt(h[2],10)]}catch(k){}return{w3:a,pv:e,wk:b,ie:g,win:f,mac:c}}();(function(){e.w3&&((typeof d.readyState!=i&&"complete"==d.readyState||typeof d.readyState==i&&(d.getElementsByTagName("body")[0]||d.body))&&u(),s||(typeof d.addEventListener!=i&&d.addEventListener("DOMContentLoaded",u,!1),e.ie&&e.win&&(d.attachEvent("onreadystatechange",function(){"complete"==d.readyState&&(d.detachEvent("onreadystatechange",arguments.callee),u())}),m==top&&function(){if(!s){try{d.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,
+0);return}u()}}()),e.wk&&function(){s||(/loaded|complete/.test(d.readyState)?u():setTimeout(arguments.callee,0))}(),M(u)))})();(function(){e.ie&&e.win&&window.attachEvent("onunload",function(){for(var a=v.length,b=0;b<a;b++)v[b][0].detachEvent(v[b][1],v[b][2]);a=C.length;for(b=0;b<a;b++)P(C[b]);for(var c in e)e[c]=null;e=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})})();return{registerObject:function(a,b,c,f){if(e.w3&&a&&b){var d={};d.id=a;d.swfVersion=b;d.expressInstall=c;d.callbackFn=
+f;p[p.length]=d;t(a,!1)}else f&&f({success:!1,id:a})},getObjectById:function(a){if(e.w3)return E(a)},embedSWF:function(a,b,c,d,g,o,h,j,k,m){var n={success:!1,id:b};e.w3&&!(e.wk&&312>e.wk)&&a&&b&&c&&d&&g?(t(b,!1),L(function(){c+="";d+="";var e={};if(k&&typeof k===r)for(var l in k)e[l]=k[l];e.data=a;e.width=c;e.height=d;l={};if(j&&typeof j===r)for(var p in j)l[p]=j[p];if(h&&typeof h===r)for(var q in h)l.flashvars=typeof l.flashvars!=i?l.flashvars+("&"+q+"="+h[q]):q+"="+h[q];if(z(g))p=J(e,l,b),e.id==
+b&&t(b,!0),n.success=!0,n.ref=p;else{if(o&&F()){e.data=o;G(e,l,b,m);return}t(b,!0)}m&&m(n)})):m&&m(n)},switchOffAutoHideShow:function(){R=!1},ua:e,getFlashPlayerVersion:function(){return{major:e.pv[0],minor:e.pv[1],release:e.pv[2]}},hasFlashPlayerVersion:z,createSWF:function(a,b,c){if(e.w3)return J(a,b,c)},showExpressInstall:function(a,b,c,d){e.w3&&F()&&G(a,b,c,d)},removeSWF:function(a){e.w3&&P(a)},createCSS:function(a,b,c,d){e.w3&&Q(a,b,c,d)},addDomLoadEvent:L,addLoadEvent:M,getQueryParamValue:function(a){var b=
+d.location.search||d.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(null==a)return S(b);for(var b=b.split("&"),c=0;c<b.length;c++)if(b[c].substring(0,b[c].indexOf("="))==a)return S(b[c].substring(b[c].indexOf("=")+1))}return""},expressInstallCallback:function(){if(A){var a=n(O);a&&w&&(a.parentNode.replaceChild(w,a),B&&(t(B,!0),e.ie&&e.win&&(w.style.display="block")),H&&H(N));A=!1}}}}(); \ No newline at end of file
diff --git a/js/ckeditor/samples/plugins/htmlwriter/outputforflash.html b/js/ckeditor/samples/plugins/htmlwriter/outputforflash.html
new file mode 100644
index 0000000..ddef541
--- /dev/null
+++ b/js/ckeditor/samples/plugins/htmlwriter/outputforflash.html
@@ -0,0 +1,280 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Output for Flash &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <script src="../../../samples/sample.js"></script>
+ <script src="assets/outputforflash/swfobject.js"></script>
+ <link href="../../../samples/sample.css" rel="stylesheet">
+ <meta name="ckeditor-sample-required-plugins" content="sourcearea">
+ <meta name="ckeditor-sample-name" content="Output for Flash">
+ <meta name="ckeditor-sample-group" content="Advanced Samples">
+ <meta name="ckeditor-sample-description" content="Configuring CKEditor to produce HTML code that can be used with Adobe Flash.">
+ <style>
+
+ .alert
+ {
+ background: #ffa84c;
+ padding: 10px 15px;
+ font-weight: bold;
+ display: block;
+ margin-bottom: 20px;
+ }
+
+ </style>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Producing Flash Compliant HTML Output
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to configure CKEditor to output
+ HTML code that can be used with
+ <a class="samples" href="http://www.adobe.com/livedocs/flash/9.0/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&amp;file=00000922.html">
+ Adobe Flash</a>.
+ The code will contain a subset of standard HTML elements like <code>&lt;b&gt;</code>,
+ <code>&lt;i&gt;</code>, and <code>&lt;p&gt;</code> as well as HTML attributes.
+ </p>
+ <p>
+ To add a CKEditor instance outputting Flash compliant HTML code, load the editor using a standard
+ JavaScript call, and define CKEditor features to use HTML elements and attributes.
+ </p>
+ <p>
+ For details on how to create this setup check the source code of this sample page.
+ </p>
+ </div>
+ <p>
+ To see how it works, create some content in the editing area of CKEditor on the left
+ and send it to the Flash object on the right side of the page by using the
+ <strong>Send to Flash</strong> button.
+ </p>
+ <table style="width: 100%; border-spacing: 0; border-collapse:collapse;">
+ <tr>
+ <td style="width: 100%">
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;&lt;b&gt;&lt;font size=&quot;18&quot; style=&quot;font-size:18px;&quot;&gt;Flash and HTML&lt;/font&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;It is possible to have &lt;a href=&quot;http://ckeditor.com&quot;&gt;CKEditor&lt;/a&gt; creating content that will be later loaded inside &lt;b&gt;Flash&lt;/b&gt; objects and animations.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Flash has a few limitations when dealing with HTML:&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It has limited support on tags.&lt;/li&gt;&lt;li&gt;There is no margin between block elements, like paragraphs.&lt;/li&gt;&lt;/ul&gt;</textarea>
+ <script>
+
+ if ( document.location.protocol == 'file:' )
+ alert( 'Warning: This samples does not work when loaded from local filesystem' +
+ 'due to security restrictions implemented in Flash.' +
+ '\n\nPlease load the sample from a web server instead.' );
+
+ var editor = CKEDITOR.replace( 'editor1', {
+ /*
+ * Ensure that htmlwriter plugin, which is required for this sample, is loaded.
+ */
+ extraPlugins: 'htmlwriter',
+
+ height: 290,
+ width: '100%',
+ toolbar: [
+ [ 'Source', '-', 'Bold', 'Italic', 'Underline', '-', 'BulletedList', '-', 'Link', 'Unlink' ],
+ [ 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ],
+ '/',
+ [ 'Font', 'FontSize' ],
+ [ 'TextColor', '-', 'About' ]
+ ],
+
+ /*
+ * Style sheet for the contents
+ */
+ contentsCss: 'body {color:#000; background-color#FFF; font-family: Arial; font-size:80%;} p, ol, ul {margin-top: 0px; margin-bottom: 0px;}',
+
+ /*
+ * Quirks doctype
+ */
+ docType: '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
+
+ /*
+ * Core styles.
+ */
+ coreStyles_bold: { element: 'b' },
+ coreStyles_italic: { element: 'i' },
+ coreStyles_underline: { element: 'u' },
+
+ /*
+ * Font face.
+ */
+
+ // Define the way font elements will be applied to the document. The "font"
+ // element will be used.
+ font_style: {
+ element: 'font',
+ attributes: { 'face': '#(family)' }
+ },
+
+ /*
+ * Font sizes.
+ */
+
+ // The CSS part of the font sizes isn't used by Flash, it is there to get the
+ // font rendered correctly in CKEditor.
+ fontSize_sizes: '8px/8;9px/9;10px/10;11px/11;12px/12;14px/14;16px/16;18px/18;20px/20;22px/22;24px/24;26px/26;28px/28;36px/36;48px/48;72px/72',
+ fontSize_style: {
+ element: 'font',
+ attributes: { 'size': '#(size)' },
+ styles: { 'font-size': '#(size)px' }
+ } ,
+
+ /*
+ * Font colors.
+ */
+ colorButton_enableMore: true,
+
+ colorButton_foreStyle: {
+ element: 'font',
+ attributes: { 'color': '#(color)' }
+ },
+
+ colorButton_backStyle: {
+ element: 'font',
+ styles: { 'background-color': '#(color)' }
+ },
+
+ on: { 'instanceReady': configureFlashOutput }
+ });
+
+ /*
+ * Adjust the behavior of the dataProcessor to match the
+ * requirements of Flash
+ */
+ function configureFlashOutput( ev ) {
+ var editor = ev.editor,
+ dataProcessor = editor.dataProcessor,
+ htmlFilter = dataProcessor && dataProcessor.htmlFilter;
+
+ // Out self closing tags the HTML4 way, like <br>.
+ dataProcessor.writer.selfClosingEnd = '>';
+
+ // Make output formatting match Flash expectations
+ var dtd = CKEDITOR.dtd;
+ for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) {
+ dataProcessor.writer.setRules( e, {
+ indent: false,
+ breakBeforeOpen: false,
+ breakAfterOpen: false,
+ breakBeforeClose: false,
+ breakAfterClose: false
+ });
+ }
+ dataProcessor.writer.setRules( 'br', {
+ indent: false,
+ breakBeforeOpen: false,
+ breakAfterOpen: false,
+ breakBeforeClose: false,
+ breakAfterClose: false
+ });
+
+ // Output properties as attributes, not styles.
+ htmlFilter.addRules( {
+ elements: {
+ $: function( element ) {
+ var style, match, width, height, align;
+
+ // Output dimensions of images as width and height
+ if ( element.name == 'img' ) {
+ style = element.attributes.style;
+
+ if ( style ) {
+ // Get the width from the style.
+ match = ( /(?:^|\s)width\s*:\s*(\d+)px/i ).exec( style );
+ width = match && match[1];
+
+ // Get the height from the style.
+ match = ( /(?:^|\s)height\s*:\s*(\d+)px/i ).exec( style );
+ height = match && match[1];
+
+ if ( width ) {
+ element.attributes.style = element.attributes.style.replace( /(?:^|\s)width\s*:\s*(\d+)px;?/i , '' );
+ element.attributes.width = width;
+ }
+
+ if ( height ) {
+ element.attributes.style = element.attributes.style.replace( /(?:^|\s)height\s*:\s*(\d+)px;?/i , '' );
+ element.attributes.height = height;
+ }
+ }
+ }
+
+ // Output alignment of paragraphs using align
+ if ( element.name == 'p' ) {
+ style = element.attributes.style;
+
+ if ( style ) {
+ // Get the align from the style.
+ match = ( /(?:^|\s)text-align\s*:\s*(\w*);?/i ).exec( style );
+ align = match && match[1];
+
+ if ( align ) {
+ element.attributes.style = element.attributes.style.replace( /(?:^|\s)text-align\s*:\s*(\w*);?/i , '' );
+ element.attributes.align = align;
+ }
+ }
+ }
+
+ if ( element.attributes.style === '' )
+ delete element.attributes.style;
+
+ return element;
+ }
+ }
+ });
+ }
+
+ function sendToFlash() {
+ var html = CKEDITOR.instances.editor1.getData() ;
+
+ // Quick fix for link color.
+ html = html.replace( /<a /g, '<font color="#0000FF"><u><a ' )
+ html = html.replace( /<\/a>/g, '</a></u></font>' )
+
+ var flash = document.getElementById( 'ckFlashContainer' ) ;
+ flash.setData( html ) ;
+ }
+
+ CKEDITOR.domReady( function() {
+ if ( !swfobject.hasFlashPlayerVersion( '8' ) ) {
+ CKEDITOR.dom.element.createFromHtml( '<span class="alert">' +
+ 'At least Adobe Flash Player 8 is required to run this sample. ' +
+ 'You can download it from <a href="http://get.adobe.com/flashplayer">Adobe\'s website</a>.' +
+ '</span>' ).insertBefore( editor.element );
+ }
+
+ swfobject.embedSWF(
+ 'assets/outputforflash/outputforflash.swf',
+ 'ckFlashContainer',
+ '550',
+ '400',
+ '8',
+ { wmode: 'transparent' }
+ );
+ });
+
+ </script>
+ <p>
+ <input type="button" value="Send to Flash" onclick="sendToFlash();">
+ </p>
+ </td>
+ <td style="vertical-align: top; padding-left: 20px">
+ <div id="ckFlashContainer"></div>
+ </td>
+ </tr>
+ </table>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/htmlwriter/outputhtml.html b/js/ckeditor/samples/plugins/htmlwriter/outputhtml.html
new file mode 100644
index 0000000..587988a
--- /dev/null
+++ b/js/ckeditor/samples/plugins/htmlwriter/outputhtml.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>HTML Compliant Output &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <script src="../../../samples/sample.js"></script>
+ <link href="../../../samples/sample.css" rel="stylesheet">
+ <meta name="ckeditor-sample-required-plugins" content="sourcearea">
+ <meta name="ckeditor-sample-name" content="Output HTML">
+ <meta name="ckeditor-sample-group" content="Advanced Samples">
+ <meta name="ckeditor-sample-description" content="Configuring CKEditor to produce legacy HTML 4 code.">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Producing HTML Compliant Output
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to configure CKEditor to output valid
+ <a class="samples" href="http://www.w3.org/TR/html401/">HTML 4.01</a> code.
+ Traditional HTML elements like <code>&lt;b&gt;</code>,
+ <code>&lt;i&gt;</code>, and <code>&lt;font&gt;</code> are used in place of
+ <code>&lt;strong&gt;</code>, <code>&lt;em&gt;</code>, and CSS styles.
+ </p>
+ <p>
+ To add a CKEditor instance outputting legacy HTML 4.01 code, load the editor using a standard
+ JavaScript call, and define CKEditor features to use the HTML compliant elements and attributes.
+ </p>
+ <p>
+ A snippet of the configuration code can be seen below; check the source of this page for
+ full definition:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ coreStyles_bold: { element: 'b' },
+ coreStyles_italic: { element: 'i' },
+
+ fontSize_style: {
+ element: 'font',
+ attributes: { 'size': '#(size)' }
+ }
+
+ ...
+});</pre>
+ </div>
+ <form action="../../../samples/sample_posteddata.php" method="post">
+ <p>
+ <label for="editor1">
+ Editor 1:
+ </label>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;b&gt;sample text&lt;/b&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor1', {
+ /*
+ * Ensure that htmlwriter plugin, which is required for this sample, is loaded.
+ */
+ extraPlugins: 'htmlwriter',
+
+ /*
+ * Style sheet for the contents
+ */
+ contentsCss: 'body {color:#000; background-color#:FFF;}',
+
+ /*
+ * Simple HTML5 doctype
+ */
+ docType: '<!DOCTYPE HTML>',
+
+ /*
+ * Allowed content rules which beside limiting allowed HTML
+ * will also take care of transforming styles to attributes
+ * (currently only for img - see transformation rules defined below).
+ *
+ * Read more: http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter
+ */
+ allowedContent:
+ 'h1 h2 h3 p pre[align]; ' +
+ 'blockquote code kbd samp var del ins cite q b i u strike ul ol li hr table tbody tr td th caption; ' +
+ 'img[!src,alt,align,width,height]; font[!face]; font[!family]; font[!color]; font[!size]; font{!background-color}; a[!href]; a[!name]',
+
+ /*
+ * Core styles.
+ */
+ coreStyles_bold: { element: 'b' },
+ coreStyles_italic: { element: 'i' },
+ coreStyles_underline: { element: 'u' },
+ coreStyles_strike: { element: 'strike' },
+
+ /*
+ * Font face.
+ */
+
+ // Define the way font elements will be applied to the document.
+ // The "font" element will be used.
+ font_style: {
+ element: 'font',
+ attributes: { 'face': '#(family)' }
+ },
+
+ /*
+ * Font sizes.
+ */
+ fontSize_sizes: 'xx-small/1;x-small/2;small/3;medium/4;large/5;x-large/6;xx-large/7',
+ fontSize_style: {
+ element: 'font',
+ attributes: { 'size': '#(size)' }
+ },
+
+ /*
+ * Font colors.
+ */
+
+ colorButton_foreStyle: {
+ element: 'font',
+ attributes: { 'color': '#(color)' }
+ },
+
+ colorButton_backStyle: {
+ element: 'font',
+ styles: { 'background-color': '#(color)' }
+ },
+
+ /*
+ * Styles combo.
+ */
+ stylesSet: [
+ { name: 'Computer Code', element: 'code' },
+ { name: 'Keyboard Phrase', element: 'kbd' },
+ { name: 'Sample Text', element: 'samp' },
+ { name: 'Variable', element: 'var' },
+ { name: 'Deleted Text', element: 'del' },
+ { name: 'Inserted Text', element: 'ins' },
+ { name: 'Cited Work', element: 'cite' },
+ { name: 'Inline Quotation', element: 'q' }
+ ],
+
+ on: {
+ pluginsLoaded: configureTransformations,
+ loaded: configureHtmlWriter
+ }
+ });
+
+ /*
+ * Add missing content transformations.
+ */
+ function configureTransformations( evt ) {
+ var editor = evt.editor;
+
+ editor.dataProcessor.htmlFilter.addRules( {
+ attributes: {
+ style: function( value, element ) {
+ // Return #RGB for background and border colors
+ return CKEDITOR.tools.convertRgbToHex( value );
+ }
+ }
+ } );
+
+ // Default automatic content transformations do not yet take care of
+ // align attributes on blocks, so we need to add our own transformation rules.
+ function alignToAttribute( element ) {
+ if ( element.styles[ 'text-align' ] ) {
+ element.attributes.align = element.styles[ 'text-align' ];
+ delete element.styles[ 'text-align' ];
+ }
+ }
+ editor.filter.addTransformations( [
+ [ { element: 'p', right: alignToAttribute } ],
+ [ { element: 'h1', right: alignToAttribute } ],
+ [ { element: 'h2', right: alignToAttribute } ],
+ [ { element: 'h3', right: alignToAttribute } ],
+ [ { element: 'pre', right: alignToAttribute } ]
+ ] );
+ }
+
+ /*
+ * Adjust the behavior of htmlWriter to make it output HTML like FCKeditor.
+ */
+ function configureHtmlWriter( evt ) {
+ var editor = evt.editor,
+ dataProcessor = editor.dataProcessor;
+
+ // Out self closing tags the HTML4 way, like <br>.
+ dataProcessor.writer.selfClosingEnd = '>';
+
+ // Make output formatting behave similar to FCKeditor.
+ var dtd = CKEDITOR.dtd;
+ for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) {
+ dataProcessor.writer.setRules( e, {
+ indent: true,
+ breakBeforeOpen: true,
+ breakAfterOpen: false,
+ breakBeforeClose: !dtd[ e ][ '#' ],
+ breakAfterClose: true
+ });
+ }
+ }
+
+ </script>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/magicline/magicline.html b/js/ckeditor/samples/plugins/magicline/magicline.html
new file mode 100644
index 0000000..996c3b9
--- /dev/null
+++ b/js/ckeditor/samples/plugins/magicline/magicline.html
@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Using Magicline plugin &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <link rel="stylesheet" href="../../../samples/sample.css">
+ <meta name="ckeditor-sample-name" content="Magicline plugin">
+ <meta name="ckeditor-sample-group" content="Plugins">
+ <meta name="ckeditor-sample-description" content="Using the Magicline plugin to access difficult focus spaces.">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Using Magicline plugin
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows the advantages of <strong>Magicline</strong> plugin
+ which is to enhance the editing process. Thanks to this plugin,
+ a number of difficult focus spaces which are inaccessible due to
+ browser issues can now be focused.
+ </p>
+ <p>
+ <strong>Magicline</strong> plugin shows a red line with a handler
+ which, when clicked, inserts a paragraph and allows typing. To see this,
+ focus an editor and move your mouse above the focus space you want
+ to access. The plugin is enabled by default so no additional
+ configuration is necessary.
+ </p>
+ </div>
+ <div>
+ <label for="editor1">
+ Editor 1:
+ </label>
+ <div class="description">
+ <p>
+ This editor uses a default <strong>Magicline</strong> setup.
+ </p>
+ </div>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">
+ &lt;table border=&quot;1&quot; cellpadding=&quot;1&quot; cellspacing=&quot;1&quot; style=&quot;width: 100%; &quot;&gt;
+ &lt;tbody&gt;
+ &lt;tr&gt;
+ &lt;td&gt;This table&lt;/td&gt;
+ &lt;td&gt;is the&lt;/td&gt;
+ &lt;td&gt;very first&lt;/td&gt;
+ &lt;td&gt;element of the document.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;We are still&lt;/td&gt;
+ &lt;td&gt;able to acces&lt;/td&gt;
+ &lt;td&gt;the space before it.&lt;/td&gt;
+ &lt;td&gt;
+ &lt;table border=&quot;1&quot; cellpadding=&quot;1&quot; cellspacing=&quot;1&quot; style=&quot;width: 100%; &quot;&gt;
+ &lt;tbody&gt;
+ &lt;tr&gt;
+ &lt;td&gt;This table is inside of a cell of another table.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;We can type&amp;nbsp;either before or after it though.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/tbody&gt;
+ &lt;/table&gt;
+ &lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/tbody&gt;
+ &lt;/table&gt;
+
+ &lt;p&gt;Two succesive horizontal lines (&lt;tt&gt;HR&lt;/tt&gt; tags). We can access the space in between:&lt;/p&gt;
+
+ &lt;hr /&gt;
+ &lt;hr /&gt;
+ &lt;ol&gt;
+ &lt;li&gt;This numbered list...&lt;/li&gt;
+ &lt;li&gt;...is a neighbour of a horizontal line...&lt;/li&gt;
+ &lt;li&gt;...and another list.&lt;/li&gt;
+ &lt;/ol&gt;
+
+ &lt;ul&gt;
+ &lt;li&gt;We can type between the lists...&lt;/li&gt;
+ &lt;li&gt;...thanks to &lt;strong&gt;Magicline&lt;/strong&gt;.&lt;/li&gt;
+ &lt;/ul&gt;
+
+ &lt;p&gt;Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.&lt;/p&gt;
+
+ &lt;p&gt;Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.&lt;/p&gt;
+
+ &lt;p&gt;Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.&lt;/p&gt;
+
+ &lt;div style=&quot;border: 2px dashed green; background: #ddd; text-align: center;&quot;&gt;
+ &lt;p&gt;This text is wrapped in a&amp;nbsp;&lt;tt&gt;DIV&lt;/tt&gt;&amp;nbsp;element. We can type after this element though.&lt;/p&gt;
+ &lt;/div&gt;
+ </textarea>
+ <script>
+
+ // This call can be placed at any point after the
+ // <textarea>, or inside a <head><script> in a
+ // window.onload event handler.
+
+ CKEDITOR.replace( 'editor1', {
+ extraPlugins: 'magicline', // Ensure that magicline plugin, which is required for this sample, is loaded.
+ allowedContent: true // Switch off the ACF, so very complex content created to
+ // show magicline's power isn't filtered.
+ } );
+
+ </script>
+ </div>
+ <br>
+ <div>
+ <label for="editor2">
+ Editor 2:
+ </label>
+ <div class="description">
+ <p>
+ This editor is using a blue line.
+ </p>
+<pre class="samples">
+CKEDITOR.replace( 'editor2', {
+ magicline_color: 'blue'
+});</pre>
+ </div>
+ <textarea cols="80" id="editor2" name="editor2" rows="10">
+ &lt;table border=&quot;1&quot; cellpadding=&quot;1&quot; cellspacing=&quot;1&quot; style=&quot;width: 100%; &quot;&gt;
+ &lt;tbody&gt;
+ &lt;tr&gt;
+ &lt;td&gt;This table&lt;/td&gt;
+ &lt;td&gt;is the&lt;/td&gt;
+ &lt;td&gt;very first&lt;/td&gt;
+ &lt;td&gt;element of the document.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;We are still&lt;/td&gt;
+ &lt;td&gt;able to acces&lt;/td&gt;
+ &lt;td&gt;the space before it.&lt;/td&gt;
+ &lt;td&gt;
+ &lt;table border=&quot;1&quot; cellpadding=&quot;1&quot; cellspacing=&quot;1&quot; style=&quot;width: 100%; &quot;&gt;
+ &lt;tbody&gt;
+ &lt;tr&gt;
+ &lt;td&gt;This table is inside of a cell of another table.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;tr&gt;
+ &lt;td&gt;We can type&amp;nbsp;either before or after it though.&lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/tbody&gt;
+ &lt;/table&gt;
+ &lt;/td&gt;
+ &lt;/tr&gt;
+ &lt;/tbody&gt;
+ &lt;/table&gt;
+
+ &lt;p&gt;Two succesive horizontal lines (&lt;tt&gt;HR&lt;/tt&gt; tags). We can access the space in between:&lt;/p&gt;
+
+ &lt;hr /&gt;
+ &lt;hr /&gt;
+ &lt;ol&gt;
+ &lt;li&gt;This numbered list...&lt;/li&gt;
+ &lt;li&gt;...is a neighbour of a horizontal line...&lt;/li&gt;
+ &lt;li&gt;...and another list.&lt;/li&gt;
+ &lt;/ol&gt;
+
+ &lt;ul&gt;
+ &lt;li&gt;We can type between the lists...&lt;/li&gt;
+ &lt;li&gt;...thanks to &lt;strong&gt;Magicline&lt;/strong&gt;.&lt;/li&gt;
+ &lt;/ul&gt;
+
+ &lt;p&gt;Lorem ipsum dolor sit amet dui. Morbi vel turpis. Nullam et leo. Etiam rutrum, urna tellus dui vel tincidunt mattis egestas, justo fringilla vel, massa. Phasellus.&lt;/p&gt;
+
+ &lt;p&gt;Quisque iaculis, dui lectus varius vitae, tortor. Proin lacus. Pellentesque ac lacus. Aenean nonummy commodo nec, pede. Etiam blandit risus elit.&lt;/p&gt;
+
+ &lt;p&gt;Ut pretium. Vestibulum rutrum in, adipiscing elit. Sed in quam in purus sem vitae pede. Pellentesque bibendum, urna sem vel risus. Vivamus posuere metus. Aliquam gravida iaculis nisl. Nam enim. Aliquam erat ac lacus tellus ac felis.&lt;/p&gt;
+
+ &lt;div style=&quot;border: 2px dashed green; background: #ddd; text-align: center;&quot;&gt;
+ &lt;p&gt;This text is wrapped in a&amp;nbsp;&lt;tt&gt;DIV&lt;/tt&gt;&amp;nbsp;element. We can type after this element though.&lt;/p&gt;
+ &lt;/div&gt;
+ </textarea>
+ <script>
+
+ // This call can be placed at any point after the
+ // <textarea>, or inside a <head><script> in a
+ // window.onload event handler.
+
+ CKEDITOR.replace( 'editor2', {
+ extraPlugins: 'magicline', // Ensure that magicline plugin, which is required for this sample, is loaded.
+ magicline_color: 'blue', // Blue line
+ allowedContent: true // Switch off the ACF, so very complex content created to
+ // show magicline's power isn't filtered.
+ });
+
+ </script>
+ </div>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/toolbar/toolbar.html b/js/ckeditor/samples/plugins/toolbar/toolbar.html
new file mode 100644
index 0000000..79d230b
--- /dev/null
+++ b/js/ckeditor/samples/plugins/toolbar/toolbar.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Toolbar Configuration &mdash; CKEditor Sample</title>
+ <meta name="ckeditor-sample-name" content="Toolbar Configurations">
+ <meta name="ckeditor-sample-group" content="Advanced Samples">
+ <meta name="ckeditor-sample-description" content="Configuring CKEditor to display full or custom toolbar layout.">
+ <script src="../../../ckeditor.js"></script>
+ <link href="../../../samples/sample.css" rel="stylesheet">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Toolbar Configuration
+ </h1>
+ <div class="description">
+ <p>
+ This sample page demonstrates editor with loaded <a href="#fullToolbar">full toolbar</a> (all registered buttons) and, if
+ current editor's configuration modifies default settings, also editor with <a href="#currentToolbar">modified toolbar</a>.
+ </p>
+
+ <p>Since CKEditor 4 there are two ways to configure toolbar buttons.</p>
+
+ <h2 class="samples">By <a href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-toolbar">config.toolbar</a></h2>
+
+ <p>
+ You can explicitly define which buttons are displayed in which groups and in which order.
+ This is the more precise setting, but less flexible. If newly added plugin adds its
+ own button you'll have to add it manually to your <code>config.toolbar</code> setting as well.
+ </p>
+
+ <p>To add a CKEditor instance with custom toolbar setting, insert the following JavaScript call to your code:</p>
+
+ <pre class="samples">
+CKEDITOR.replace( <em>'textarea_id'</em>, {
+ <strong>toolbar:</strong> [
+ { name: 'document', items: [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ] }, // Defines toolbar group with name (used to create voice label) and items in 3 subgroups.
+ [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ], // Defines toolbar group without name.
+ '/', // Line break - next group will be placed in new line.
+ { name: 'basicstyles', items: [ 'Bold', 'Italic' ] }
+ ]
+});</pre>
+
+ <h2 class="samples">By <a href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-toolbarGroups">config.toolbarGroups</a></h2>
+
+ <p>
+ You can define which groups of buttons (like e.g. <code>basicstyles</code>, <code>clipboard</code>
+ and <code>forms</code>) are displayed and in which order. Registered buttons are associated
+ with toolbar groups by <code>toolbar</code> property in their definition.
+ This setting's advantage is that you don't have to modify toolbar configuration
+ when adding/removing plugins which register their own buttons.
+ </p>
+
+ <p>To add a CKEditor instance with custom toolbar groups setting, insert the following JavaScript call to your code:</p>
+
+ <pre class="samples">
+CKEDITOR.replace( <em>'textarea_id'</em>, {
+ <strong>toolbarGroups:</strong> [
+ { name: 'document', groups: [ 'mode', 'document' ] }, // Displays document group with its two subgroups.
+ { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, // Group's name will be used to create voice label.
+ '/', // Line break - next group will be placed in new line.
+ { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+ { name: 'links' }
+ ]
+
+ // NOTE: Remember to leave 'toolbar' property with the default value (null).
+});</pre>
+ </div>
+
+ <div id="currentToolbar" style="display: none">
+ <h2 class="samples">Current toolbar configuration</h2>
+ <p>Below you can see editor with current toolbar definition.</p>
+ <textarea cols="80" id="editorCurrent" name="editorCurrent" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <pre id="editorCurrentCfg" class="samples"></pre>
+ </div>
+
+ <div id="fullToolbar">
+ <h2 class="samples">Full toolbar configuration</h2>
+ <p>Below you can see editor with full toolbar, generated automatically by the editor.</p>
+ <p>
+ <strong>Note</strong>: To create editor instance with full toolbar you don't have to set anything.
+ Just leave <code>toolbar</code> and <code>toolbarGroups</code> with the default, <code>null</code> values.
+ </p>
+ <textarea cols="80" id="editorFull" name="editorFull" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <pre id="editorFullCfg" class="samples"></pre>
+ </div>
+
+ <script>
+
+(function() {
+ 'use strict';
+
+ var buttonsNames;
+
+ CKEDITOR.config.extraPlugins = 'toolbar';
+
+ CKEDITOR.on( 'instanceReady', function( evt ) {
+ var editor = evt.editor,
+ editorCurrent = editor.name == 'editorCurrent',
+ defaultToolbar = !( editor.config.toolbar || editor.config.toolbarGroups || editor.config.removeButtons ),
+ pre = CKEDITOR.document.getById( editor.name + 'Cfg' ),
+ output = '';
+
+ if ( editorCurrent ) {
+ // If default toolbar configuration has been modified, show "current toolbar" section.
+ if ( !defaultToolbar )
+ CKEDITOR.document.getById( 'currentToolbar' ).show();
+ else
+ return;
+ }
+
+ if ( !buttonsNames )
+ buttonsNames = createButtonsNamesHash( editor.ui.items );
+
+ // Toolbar isn't set explicitly, so it was created automatically from toolbarGroups.
+ if ( !editor.config.toolbar ) {
+ output +=
+ '// Toolbar configuration generated automatically by the editor based on config.toolbarGroups.\n' +
+ dumpToolbarConfiguration( editor ) +
+ '\n\n' +
+ '// Toolbar groups configuration.\n' +
+ dumpToolbarConfiguration( editor, true )
+ }
+ // Toolbar groups doesn't count in this case - print only toolbar.
+ else {
+ output += '// Toolbar configuration.\n' +
+ dumpToolbarConfiguration( editor );
+ }
+
+ // Recreate to avoid old IE from loosing whitespaces on filling <pre> content.
+ var preOutput = pre.getOuterHtml().replace( /(?=<\/)/, output );
+ CKEDITOR.dom.element.createFromHtml( preOutput ).replace( pre );
+ } );
+
+ CKEDITOR.replace( 'editorCurrent', { height: 100 } );
+ CKEDITOR.replace( 'editorFull', {
+ // Reset toolbar settings, so full toolbar will be generated automatically.
+ toolbar: null,
+ toolbarGroups: null,
+ removeButtons: null,
+ height: 100
+ } );
+
+ function dumpToolbarConfiguration( editor, printGroups ) {
+ var output = [],
+ toolbar = editor.toolbar;
+
+ for ( var i = 0; i < toolbar.length; ++i ) {
+ var group = dumpToolbarGroup( toolbar[ i ], printGroups );
+ if ( group )
+ output.push( group );
+ }
+
+ return 'config.toolbar' + ( printGroups ? 'Groups' : '' ) + ' = [\n\t' + output.join( ',\n\t' ) + '\n];';
+ }
+
+ function dumpToolbarGroup( group, printGroups ) {
+ var output = [];
+
+ if ( typeof group == 'string' )
+ return '\'' + group + '\'';
+ if ( CKEDITOR.tools.isArray( group ) )
+ return dumpToolbarItems( group );
+ // Skip group when printing entire toolbar configuration and there are no items in this group.
+ if ( !printGroups && !group.items )
+ return;
+
+ if ( group.name )
+ output.push( 'name: \'' + group.name + '\'' );
+
+ if ( group.groups )
+ output.push( 'groups: ' + dumpToolbarItems( group.groups ) );
+
+ if ( !printGroups )
+ output.push( 'items: ' + dumpToolbarItems( group.items ) );
+
+ return '{ ' + output.join( ', ' ) + ' }';
+ }
+
+ function dumpToolbarItems( items ) {
+ if ( typeof items == 'string' )
+ return '\'' + items + '\'';
+
+ var names = [],
+ i, item;
+
+ for ( var i = 0; i < items.length; ++i ) {
+ item = items[ i ];
+ if ( typeof item == 'string' )
+ names.push( item );
+ else {
+ if ( item.type == CKEDITOR.UI_SEPARATOR )
+ names.push( '-' );
+ else
+ names.push( buttonsNames[ item.name ] );
+ }
+ }
+
+ return '[ \'' + names.join( '\', \'' ) + '\' ]';
+ }
+
+ // Creates { 'lowercased': 'LowerCased' } buttons names hash.
+ function createButtonsNamesHash( items ) {
+ var hash = {},
+ name;
+
+ for ( name in items ) {
+ hash[ items[ name ].name ] = name;
+ }
+
+ return hash;
+ }
+
+})();
+ </script>
+
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/plugins/wysiwygarea/fullpage.html b/js/ckeditor/samples/plugins/wysiwygarea/fullpage.html
new file mode 100644
index 0000000..66b3f12
--- /dev/null
+++ b/js/ckeditor/samples/plugins/wysiwygarea/fullpage.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Full Page Editing &mdash; CKEditor Sample</title>
+ <script src="../../../ckeditor.js"></script>
+ <script src="../../../samples/sample.js"></script>
+ <link rel="stylesheet" href="../../../samples/sample.css">
+ <meta name="ckeditor-sample-required-plugins" content="sourcearea">
+ <meta name="ckeditor-sample-name" content="Full page support">
+ <meta name="ckeditor-sample-group" content="Plugins">
+ <meta name="ckeditor-sample-description" content="CKEditor inserted with a JavaScript call and used to edit the whole page from &lt;html&gt; to &lt;/html&gt;.">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="../../../samples/index.html">CKEditor Samples</a> &raquo; Full Page Editing
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to configure CKEditor to edit entire HTML pages, from the
+ <code>&lt;html&gt;</code> tag to the <code>&lt;/html&gt;</code> tag.
+ </p>
+ <p>
+ The CKEditor instance below is inserted with a JavaScript call using the following code:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ <strong>fullPage: true</strong>,
+ <strong>allowedContent: true</strong>
+});
+</pre>
+ <p>
+ Note that <code><em>textarea_id</em></code> in the code above is the <code>id</code> attribute of
+ the <code>&lt;textarea&gt;</code> element to be replaced.
+ </p>
+ <p>
+ The <code><em>allowedContent</em></code> in the code above is set to <code>true</code> to disable content filtering.
+ Setting this option is not obligatory, but in full page mode there is a strong chance that one may want be able to freely enter any HTML content in source mode without any limitations.
+ </p>
+ </div>
+ <form action="../../../samples/sample_posteddata.php" method="post">
+ <label for="editor1">
+ CKEditor output the entire page including content outside of
+ <code>&lt;body&gt;</code> element, so content like meta and title can be changed:
+ </label>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">
+ &lt;h1&gt;&lt;img align=&quot;right&quot; alt=&quot;Saturn V carrying Apollo 11&quot; src=&quot;../../../samples/assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor1', {
+ fullPage: true,
+ allowedContent: true,
+ extraPlugins: 'wysiwygarea'
+ });
+
+ </script>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/readonly.html b/js/ckeditor/samples/readonly.html
new file mode 100644
index 0000000..bbd9f69
--- /dev/null
+++ b/js/ckeditor/samples/readonly.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Using the CKEditor Read-Only API &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+ <script>
+
+ var editor;
+
+ // The instanceReady event is fired, when an instance of CKEditor has finished
+ // its initialization.
+ CKEDITOR.on( 'instanceReady', function( ev ) {
+ editor = ev.editor;
+
+ // Show this "on" button.
+ document.getElementById( 'readOnlyOn' ).style.display = '';
+
+ // Event fired when the readOnly property changes.
+ editor.on( 'readOnly', function() {
+ document.getElementById( 'readOnlyOn' ).style.display = this.readOnly ? 'none' : '';
+ document.getElementById( 'readOnlyOff' ).style.display = this.readOnly ? '' : 'none';
+ });
+ });
+
+ function toggleReadOnly( isReadOnly ) {
+ // Change the read-only state of the editor.
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setReadOnly
+ editor.setReadOnly( isReadOnly );
+ }
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Using the CKEditor Read-Only API
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to use the
+ <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-setReadOnly">setReadOnly</a></code>
+ API to put editor into the read-only state that makes it impossible for users to change the editor contents.
+ </p>
+ <p>
+ For details on how to create this setup check the source code of this sample page.
+ </p>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <p>
+ <textarea class="ckeditor" id="editor1" name="editor1" cols="100" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ </p>
+ <p>
+ <input id="readOnlyOn" onclick="toggleReadOnly();" type="button" value="Make it read-only" style="display:none">
+ <input id="readOnlyOff" onclick="toggleReadOnly( false );" type="button" value="Make it editable again" style="display:none">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/replacebyclass.html b/js/ckeditor/samples/replacebyclass.html
new file mode 100644
index 0000000..1547f33
--- /dev/null
+++ b/js/ckeditor/samples/replacebyclass.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Replace Textareas by Class Name &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Replace Textarea Elements by Class Name
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to automatically replace all <code>&lt;textarea&gt;</code> elements
+ of a given class with a CKEditor instance.
+ </p>
+ <p>
+ To replace a <code>&lt;textarea&gt;</code> element, simply assign it the <code>ckeditor</code>
+ class, as in the code below:
+ </p>
+<pre class="samples">
+&lt;textarea <strong>class="ckeditor</strong>" name="editor1"&gt;&lt;/textarea&gt;
+</pre>
+ <p>
+ Note that other <code>&lt;textarea&gt;</code> attributes (like <code>id</code> or <code>name</code>) need to be adjusted to your document.
+ </p>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <p>
+ <label for="editor1">
+ Editor 1:
+ </label>
+ <textarea class="ckeditor" cols="80" id="editor1" name="editor1" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/replacebycode.html b/js/ckeditor/samples/replacebycode.html
new file mode 100644
index 0000000..e25e915
--- /dev/null
+++ b/js/ckeditor/samples/replacebycode.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Replace Textarea by Code &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Replace Textarea Elements Using JavaScript Code
+ </h1>
+ <form action="sample_posteddata.php" method="post">
+ <div class="description">
+ <p>
+ This editor is using an <code>&lt;iframe&gt;</code> element-based editing area, provided by the <strong>Wysiwygarea</strong> plugin.
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>' )
+</pre>
+ </div>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">
+ &lt;h1&gt;&lt;img alt=&quot;Saturn V carrying Apollo 11&quot; class=&quot;right&quot; src=&quot;assets/sample.jpg&quot;/&gt; Apollo 11&lt;/h1&gt; &lt;p&gt;&lt;b&gt;Apollo 11&lt;/b&gt; was the spaceflight that landed the first humans, Americans &lt;a href=&quot;http://en.wikipedia.org/wiki/Neil_Armstrong&quot; title=&quot;Neil Armstrong&quot;&gt;Neil Armstrong&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Buzz_Aldrin&quot; title=&quot;Buzz Aldrin&quot;&gt;Buzz Aldrin&lt;/a&gt;, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.&lt;/p&gt; &lt;p&gt;Armstrong spent about &lt;s&gt;three and a half&lt;/s&gt; two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5&amp;nbsp;kg) of lunar material for return to Earth. A third member of the mission, &lt;a href=&quot;http://en.wikipedia.org/wiki/Michael_Collins_(astronaut)&quot; title=&quot;Michael Collins (astronaut)&quot;&gt;Michael Collins&lt;/a&gt;, piloted the &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_Command/Service_Module&quot; title=&quot;Apollo Command/Service Module&quot;&gt;command&lt;/a&gt; spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.&lt;/p&gt; &lt;h2&gt;Broadcasting and &lt;em&gt;quotes&lt;/em&gt; &lt;a id=&quot;quotes&quot; name=&quot;quotes&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;One small step for [a] man, one giant leap for mankind.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Apollo 11 effectively ended the &lt;a href=&quot;http://en.wikipedia.org/wiki/Space_Race&quot; title=&quot;Space Race&quot;&gt;Space Race&lt;/a&gt; and fulfilled a national goal proposed in 1961 by the late U.S. President &lt;a href=&quot;http://en.wikipedia.org/wiki/John_F._Kennedy&quot; title=&quot;John F. Kennedy&quot;&gt;John F. Kennedy&lt;/a&gt; in a speech before the United States Congress:&lt;/p&gt; &lt;blockquote&gt;&lt;p&gt;[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;Technical details &lt;a id=&quot;tech-details&quot; name=&quot;tech-details&quot;&gt;&lt;/a&gt;&lt;/h2&gt; &lt;table align=&quot;right&quot; border=&quot;1&quot; bordercolor=&quot;#ccc&quot; cellpadding=&quot;5&quot; cellspacing=&quot;0&quot; style=&quot;border-collapse:collapse;margin:10px 0 10px 15px;&quot;&gt; &lt;caption&gt;&lt;strong&gt;Mission crew&lt;/strong&gt;&lt;/caption&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope=&quot;col&quot;&gt;Position&lt;/th&gt; &lt;th scope=&quot;col&quot;&gt;Astronaut&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;Commander&lt;/td&gt; &lt;td&gt;Neil A. Armstrong&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Command Module Pilot&lt;/td&gt; &lt;td&gt;Michael Collins&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;Lunar Module Pilot&lt;/td&gt; &lt;td&gt;Edwin &amp;quot;Buzz&amp;quot; E. Aldrin, Jr.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Launched by a &lt;strong&gt;Saturn V&lt;/strong&gt; rocket from &lt;a href=&quot;http://en.wikipedia.org/wiki/Kennedy_Space_Center&quot; title=&quot;Kennedy Space Center&quot;&gt;Kennedy Space Center&lt;/a&gt; in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of &lt;a href=&quot;http://en.wikipedia.org/wiki/NASA&quot; title=&quot;NASA&quot;&gt;NASA&lt;/a&gt;&amp;#39;s Apollo program. The Apollo spacecraft had three parts:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&lt;strong&gt;Command Module&lt;/strong&gt; with a cabin for the three astronauts which was the only part which landed back on Earth&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Service Module&lt;/strong&gt; which supported the Command Module with propulsion, electrical power, oxygen and water&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Lunar Module&lt;/strong&gt; for landing on the Moon.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;After being sent to the Moon by the Saturn V&amp;#39;s upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Mare_Tranquillitatis&quot; title=&quot;Mare Tranquillitatis&quot;&gt;Sea of Tranquility&lt;/a&gt;. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Pacific_Ocean&quot; title=&quot;Pacific Ocean&quot;&gt;Pacific Ocean&lt;/a&gt; on July 24.&lt;/p&gt; &lt;hr/&gt; &lt;p style=&quot;text-align: right;&quot;&gt;&lt;small&gt;Source: &lt;a href=&quot;http://en.wikipedia.org/wiki/Apollo_11&quot;&gt;Wikipedia.org&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
+ </textarea>
+ <script>
+
+ // This call can be placed at any point after the
+ // <textarea>, or inside a <head><script> in a
+ // window.onload event handler.
+
+ // Replace the <textarea id="editor"> with an CKEditor
+ // instance, using default configurations.
+
+ CKEDITOR.replace( 'editor1' );
+
+ </script>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/sample.css b/js/ckeditor/samples/sample.css
new file mode 100644
index 0000000..4d138a8
--- /dev/null
+++ b/js/ckeditor/samples/sample.css
@@ -0,0 +1,365 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+
+html, body, h1, h2, h3, h4, h5, h6, div, span, blockquote, p, address, form, fieldset, img, ul, ol, dl, dt, dd, li, hr, table, td, th, strong, em, sup, sub, dfn, ins, del, q, cite, var, samp, code, kbd, tt, pre
+{
+ line-height: 1.5;
+}
+
+body
+{
+ padding: 10px 30px;
+}
+
+input, textarea, select, option, optgroup, button, td, th
+{
+ font-size: 100%;
+}
+
+pre
+{
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ -webkit-tab-size: 4;
+ tab-size: 4;
+}
+
+pre, code, kbd, samp, tt
+{
+ font-family: monospace,monospace;
+ font-size: 1em;
+}
+
+body {
+ width: 960px;
+ margin: 0 auto;
+}
+
+code
+{
+ background: #f3f3f3;
+ border: 1px solid #ddd;
+ padding: 1px 4px;
+
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+abbr
+{
+ border-bottom: 1px dotted #555;
+ cursor: pointer;
+}
+
+.new, .beta
+{
+ text-transform: uppercase;
+ font-size: 10px;
+ font-weight: bold;
+ padding: 1px 4px;
+ margin: 0 0 0 5px;
+ color: #fff;
+ float: right;
+
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.new
+{
+ background: #FF7E00;
+ border: 1px solid #DA8028;
+ text-shadow: 0 1px 0 #C97626;
+
+ -moz-box-shadow: 0 2px 3px 0 #FFA54E inset;
+ -webkit-box-shadow: 0 2px 3px 0 #FFA54E inset;
+ box-shadow: 0 2px 3px 0 #FFA54E inset;
+}
+
+.beta
+{
+ background: #18C0DF;
+ border: 1px solid #19AAD8;
+ text-shadow: 0 1px 0 #048CAD;
+ font-style: italic;
+
+ -moz-box-shadow: 0 2px 3px 0 #50D4FD inset;
+ -webkit-box-shadow: 0 2px 3px 0 #50D4FD inset;
+ box-shadow: 0 2px 3px 0 #50D4FD inset;
+}
+
+h1.samples
+{
+ color: #0782C1;
+ font-size: 200%;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+
+h1.samples a
+{
+ color: #0782C1;
+ text-decoration: none;
+ border-bottom: 1px dotted #0782C1;
+}
+
+.samples a:hover
+{
+ border-bottom: 1px dotted #0782C1;
+}
+
+h2.samples
+{
+ color: #000000;
+ font-size: 130%;
+ margin: 15px 0 0 0;
+ padding: 0;
+}
+
+p, blockquote, address, form, pre, dl, h1.samples, h2.samples
+{
+ margin-bottom: 15px;
+}
+
+ul.samples
+{
+ margin-bottom: 15px;
+}
+
+.clear
+{
+ clear: both;
+}
+
+fieldset
+{
+ margin: 0;
+ padding: 10px;
+}
+
+body, input, textarea
+{
+ color: #333333;
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+body
+{
+ font-size: 75%;
+}
+
+a.samples
+{
+ color: #189DE1;
+ text-decoration: none;
+}
+
+form
+{
+ margin: 0;
+ padding: 0;
+}
+
+pre.samples
+{
+ background-color: #F7F7F7;
+ border: 1px solid #D7D7D7;
+ overflow: auto;
+ padding: 0.25em;
+ white-space: pre-wrap; /* CSS 2.1 */
+ word-wrap: break-word; /* IE7 */
+}
+
+#footer
+{
+ clear: both;
+ padding-top: 10px;
+}
+
+#footer hr
+{
+ margin: 10px 0 15px 0;
+ height: 1px;
+ border: solid 1px gray;
+ border-bottom: none;
+}
+
+#footer p
+{
+ margin: 0 10px 10px 10px;
+ float: left;
+}
+
+#footer #copy
+{
+ float: right;
+}
+
+#outputSample
+{
+ width: 100%;
+ table-layout: fixed;
+}
+
+#outputSample thead th
+{
+ color: #dddddd;
+ background-color: #999999;
+ padding: 4px;
+ white-space: nowrap;
+}
+
+#outputSample tbody th
+{
+ vertical-align: top;
+ text-align: left;
+}
+
+#outputSample pre
+{
+ margin: 0;
+ padding: 0;
+}
+
+.description
+{
+ border: 1px dotted #B7B7B7;
+ margin-bottom: 10px;
+ padding: 10px 10px 0;
+ overflow: hidden;
+}
+
+label
+{
+ display: block;
+ margin-bottom: 6px;
+}
+
+/**
+ * CKEditor editables are automatically set with the "cke_editable" class
+ * plus cke_editable_(inline|themed) depending on the editor type.
+ */
+
+/* Style a bit the inline editables. */
+.cke_editable.cke_editable_inline
+{
+ cursor: pointer;
+}
+
+/* Once an editable element gets focused, the "cke_focus" class is
+ added to it, so we can style it differently. */
+.cke_editable.cke_editable_inline.cke_focus
+{
+ box-shadow: inset 0px 0px 20px 3px #ddd, inset 0 0 1px #000;
+ outline: none;
+ background: #eee;
+ cursor: text;
+}
+
+/* Avoid pre-formatted overflows inline editable. */
+.cke_editable_inline pre
+{
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/**
+ * Samples index styles.
+ */
+
+.twoColumns,
+.twoColumnsLeft,
+.twoColumnsRight
+{
+ overflow: hidden;
+}
+
+.twoColumnsLeft,
+.twoColumnsRight
+{
+ width: 45%;
+}
+
+.twoColumnsLeft
+{
+ float: left;
+}
+
+.twoColumnsRight
+{
+ float: right;
+}
+
+dl.samples
+{
+ padding: 0 0 0 40px;
+}
+dl.samples > dt
+{
+ display: list-item;
+ list-style-type: disc;
+ list-style-position: outside;
+ margin: 0 0 3px;
+}
+dl.samples > dd
+{
+ margin: 0 0 3px;
+}
+.warning
+{
+ color: #ff0000;
+ background-color: #FFCCBA;
+ border: 2px dotted #ff0000;
+ padding: 15px 10px;
+ margin: 10px 0;
+}
+
+/* Used on inline samples */
+
+blockquote
+{
+ font-style: italic;
+ font-family: Georgia, Times, "Times New Roman", serif;
+ padding: 2px 0;
+ border-style: solid;
+ border-color: #ccc;
+ border-width: 0;
+}
+
+.cke_contents_ltr blockquote
+{
+ padding-left: 20px;
+ padding-right: 8px;
+ border-left-width: 5px;
+}
+
+.cke_contents_rtl blockquote
+{
+ padding-left: 8px;
+ padding-right: 20px;
+ border-right-width: 5px;
+}
+
+img.right {
+ border: 1px solid #ccc;
+ float: right;
+ margin-left: 15px;
+ padding: 5px;
+}
+
+img.left {
+ border: 1px solid #ccc;
+ float: left;
+ margin-right: 15px;
+ padding: 5px;
+}
+
+.marker
+{
+ background-color: Yellow;
+}
diff --git a/js/ckeditor/samples/sample.js b/js/ckeditor/samples/sample.js
new file mode 100644
index 0000000..2bdcd98
--- /dev/null
+++ b/js/ckeditor/samples/sample.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+
+// Tool scripts for the sample pages.
+// This file can be ignored and is not required to make use of CKEditor.
+
+( function() {
+ CKEDITOR.on( 'instanceReady', function( ev ) {
+ // Check for sample compliance.
+ var editor = ev.editor,
+ meta = CKEDITOR.document.$.getElementsByName( 'ckeditor-sample-required-plugins' ),
+ requires = meta.length ? CKEDITOR.dom.element.get( meta[ 0 ] ).getAttribute( 'content' ).split( ',' ) : [],
+ missing = [],
+ i;
+
+ if ( requires.length ) {
+ for ( i = 0; i < requires.length; i++ ) {
+ if ( !editor.plugins[ requires[ i ] ] )
+ missing.push( '<code>' + requires[ i ] + '</code>' );
+ }
+
+ if ( missing.length ) {
+ var warn = CKEDITOR.dom.element.createFromHtml(
+ '<div class="warning">' +
+ '<span>To fully experience this demo, the ' + missing.join( ', ' ) + ' plugin' + ( missing.length > 1 ? 's are' : ' is' ) + ' required.</span>' +
+ '</div>'
+ );
+ warn.insertBefore( editor.container );
+ }
+ }
+
+ // Set icons.
+ var doc = new CKEDITOR.dom.document( document ),
+ icons = doc.find( '.button_icon' );
+
+ for ( i = 0; i < icons.count(); i++ ) {
+ var icon = icons.getItem( i ),
+ name = icon.getAttribute( 'data-icon' ),
+ style = CKEDITOR.skin.getIconStyle( name, ( CKEDITOR.lang.dir == 'rtl' ) );
+
+ icon.addClass( 'cke_button_icon' );
+ icon.addClass( 'cke_button__' + name + '_icon' );
+ icon.setAttribute( 'style', style );
+ icon.setStyle( 'float', 'none' );
+
+ }
+ } );
+} )();
diff --git a/js/ckeditor/samples/sample_posteddata.php b/js/ckeditor/samples/sample_posteddata.php
new file mode 100644
index 0000000..7775f07
--- /dev/null
+++ b/js/ckeditor/samples/sample_posteddata.php
@@ -0,0 +1,16 @@
+<?php /* <body><pre>
+
+-------------------------------------------------------------------------------------------
+ CKEditor - Posted Data
+
+ We are sorry, but your Web server does not support the PHP language used in this script.
+
+ Please note that CKEditor can be used with any other server-side language than just PHP.
+ To save the content created with CKEditor you need to read the POST data on the server
+ side and write it to a file or the database.
+
+ Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ For licensing, see LICENSE.md or <a href="http://ckeditor.com/license">http://ckeditor.com/license</a>
+-------------------------------------------------------------------------------------------
+
+</pre><div style="display:none"></body> */ include "assets/posteddata.php"; ?>
diff --git a/js/ckeditor/samples/tabindex.html b/js/ckeditor/samples/tabindex.html
new file mode 100644
index 0000000..5d1ab63
--- /dev/null
+++ b/js/ckeditor/samples/tabindex.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>TAB Key-Based Navigation &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link href="sample.css" rel="stylesheet">
+ <style>
+
+ .cke_focused,
+ .cke_editable.cke_focused
+ {
+ outline: 3px dotted blue !important;
+ *border: 3px dotted blue !important; /* For IE7 */
+ }
+
+ </style>
+ <script>
+
+ CKEDITOR.on( 'instanceReady', function( evt ) {
+ var editor = evt.editor;
+ editor.setData( 'This editor has it\'s tabIndex set to <strong>' + editor.tabIndex + '</strong>' );
+
+ // Apply focus class name.
+ editor.on( 'focus', function() {
+ editor.container.addClass( 'cke_focused' );
+ });
+ editor.on( 'blur', function() {
+ editor.container.removeClass( 'cke_focused' );
+ });
+
+ // Put startup focus on the first editor in tab order.
+ if ( editor.tabIndex == 1 )
+ editor.focus();
+ });
+
+ </script>
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; TAB Key-Based Navigation
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how tab key navigation among editor instances is
+ affected by the <code>tabIndex</code> attribute from
+ the original page element. Use TAB key to move between the editors.
+ </p>
+ </div>
+ <p>
+ <textarea class="ckeditor" cols="80" id="editor4" rows="10" tabindex="1"></textarea>
+ </p>
+ <div class="ckeditor" contenteditable="true" id="editor1" tabindex="4"></div>
+ <p>
+ <textarea class="ckeditor" cols="80" id="editor2" rows="10" tabindex="2"></textarea>
+ </p>
+ <p>
+ <textarea class="ckeditor" cols="80" id="editor3" rows="10" tabindex="3"></textarea>
+ </p>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/uicolor.html b/js/ckeditor/samples/uicolor.html
new file mode 100644
index 0000000..d7c5dea
--- /dev/null
+++ b/js/ckeditor/samples/uicolor.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>UI Color Picker &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; UI Color
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to automatically replace <code>&lt;textarea&gt;</code> elements
+ with a CKEditor instance with an option to change the color of its user interface.<br>
+ <strong>Note:</strong>The UI skin color feature depends on the CKEditor skin
+ compatibility. The Moono and Kama skins are examples of skins that work with it.
+ </p>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <p>
+ This editor instance has a UI color value defined in configuration to change the skin color,
+ To specify the color of the user interface, set the <code>uiColor</code> property:
+ </p>
+ <pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ <strong>uiColor: '#14B8C4'</strong>
+});</pre>
+ <p>
+ Note that <code><em>textarea_id</em></code> in the code above is the <code>id</code> attribute of
+ the <code>&lt;textarea&gt;</code> element to be replaced.
+ </p>
+ <p>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+
+ // Replace the <textarea id="editor"> with an CKEditor
+ // instance, using default configurations.
+ CKEDITOR.replace( 'editor1', {
+ uiColor: '#14B8C4',
+ toolbar: [
+ [ 'Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink' ],
+ [ 'FontSize', 'TextColor', 'BGColor' ]
+ ]
+ });
+
+ </script>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/uilanguages.html b/js/ckeditor/samples/uilanguages.html
new file mode 100644
index 0000000..4e73dcc
--- /dev/null
+++ b/js/ckeditor/samples/uilanguages.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>User Interface Globalization &mdash; CKEditor Sample</title>
+ <script src="../ckeditor.js"></script>
+ <script src="assets/uilanguages/languages.js"></script>
+ <link rel="stylesheet" href="sample.css">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; User Interface Languages
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to automatically replace <code>&lt;textarea&gt;</code> elements
+ with a CKEditor instance with an option to change the language of its user interface.
+ </p>
+ <p>
+ It pulls the language list from CKEditor <code>_languages.js</code> file that contains the list of supported languages and creates
+ a drop-down list that lets the user change the UI language.
+ </p>
+ <p>
+ By default, CKEditor automatically localizes the editor to the language of the user.
+ The UI language can be controlled with two configuration options:
+ <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-language">language</a></code> and
+ <code><a class="samples" href="http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-defaultLanguage">
+ defaultLanguage</a></code>. The <code>defaultLanguage</code> setting specifies the
+ default CKEditor language to be used when a localization suitable for user's settings is not available.
+ </p>
+ <p>
+ To specify the user interface language that will be used no matter what language is
+ specified in user's browser or operating system, set the <code>language</code> property:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ // Load the German interface.
+ <strong>language: 'de'</strong>
+});</pre>
+ <p>
+ Note that <code><em>textarea_id</em></code> in the code above is the <code>id</code> attribute of
+ the <code>&lt;textarea&gt;</code> element to be replaced.
+ </p>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <p>
+ Available languages (<span id="count"> </span> languages!):<br>
+ <script>
+
+ document.write( '<select disabled="disabled" id="languages" onchange="createEditor( this.value );">' );
+
+ // Get the language list from the _languages.js file.
+ for ( var i = 0 ; i < window.CKEDITOR_LANGS.length ; i++ ) {
+ document.write(
+ '<option value="' + window.CKEDITOR_LANGS[i].code + '">' +
+ window.CKEDITOR_LANGS[i].name +
+ '</option>' );
+ }
+
+ document.write( '</select>' );
+
+ </script>
+ <br>
+ <span style="color: #888888">
+ (You may see strange characters if your system does not support the selected language)
+ </span>
+ </p>
+ <p>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;strong&gt;sample text&lt;/strong&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+
+ // Set the number of languages.
+ document.getElementById( 'count' ).innerHTML = window.CKEDITOR_LANGS.length;
+
+ var editor;
+
+ function createEditor( languageCode ) {
+ if ( editor )
+ editor.destroy();
+
+ // Replace the <textarea id="editor"> with an CKEditor
+ // instance, using default configurations.
+ editor = CKEDITOR.replace( 'editor1', {
+ language: languageCode,
+
+ on: {
+ instanceReady: function() {
+ // Wait for the editor to be ready to set
+ // the language combo.
+ var languages = document.getElementById( 'languages' );
+ languages.value = this.langCode;
+ languages.disabled = false;
+ }
+ }
+ });
+ }
+
+ // At page startup, load the default language:
+ createEditor( '' );
+
+ </script>
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/samples/xhtmlstyle.html b/js/ckeditor/samples/xhtmlstyle.html
new file mode 100644
index 0000000..b53b431
--- /dev/null
+++ b/js/ckeditor/samples/xhtmlstyle.html
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+-->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>XHTML Compliant Output &mdash; CKEditor Sample</title>
+ <meta name="ckeditor-sample-required-plugins" content="sourcearea">
+ <script src="../ckeditor.js"></script>
+ <script src="../samples/sample.js"></script>
+ <link href="sample.css" rel="stylesheet">
+</head>
+<body>
+ <h1 class="samples">
+ <a href="index.html">CKEditor Samples</a> &raquo; Producing XHTML Compliant Output
+ </h1>
+ <div class="description">
+ <p>
+ This sample shows how to configure CKEditor to output valid
+ <a class="samples" href="http://www.w3.org/TR/xhtml11/">XHTML 1.1</a> code.
+ Deprecated elements (<code>&lt;font&gt;</code>, <code>&lt;u&gt;</code>) or attributes
+ (<code>size</code>, <code>face</code>) will be replaced with XHTML compliant code.
+ </p>
+ <p>
+ To add a CKEditor instance outputting valid XHTML code, load the editor using a standard
+ JavaScript call and define CKEditor features to use the XHTML compliant elements and styles.
+ </p>
+ <p>
+ A snippet of the configuration code can be seen below; check the source of this page for
+ full definition:
+ </p>
+<pre class="samples">
+CKEDITOR.replace( '<em>textarea_id</em>', {
+ contentsCss: 'assets/outputxhtml.css',
+
+ coreStyles_bold: {
+ element: 'span',
+ attributes: { 'class': 'Bold' }
+ },
+ coreStyles_italic: {
+ element: 'span',
+ attributes: { 'class': 'Italic' }
+ },
+
+ ...
+});</pre>
+ </div>
+ <form action="sample_posteddata.php" method="post">
+ <p>
+ <label for="editor1">
+ Editor 1:
+ </label>
+ <textarea cols="80" id="editor1" name="editor1" rows="10">&lt;p&gt;This is some &lt;span class="Bold"&gt;sample text&lt;/span&gt;. You are using &lt;a href="http://ckeditor.com/"&gt;CKEditor&lt;/a&gt;.&lt;/p&gt;</textarea>
+ <script>
+
+ CKEDITOR.replace( 'editor1', {
+ /*
+ * Style sheet for the contents
+ */
+ contentsCss: 'assets/outputxhtml/outputxhtml.css',
+
+ /*
+ * Special allowed content rules for spans used by
+ * font face, size, and color buttons.
+ *
+ * Note: all rules have been written separately so
+ * it was possible to specify required classes.
+ */
+ extraAllowedContent: 'span(!FontColor1);span(!FontColor2);span(!FontColor3);' +
+ 'span(!FontColor1BG);span(!FontColor2BG);span(!FontColor3BG);' +
+ 'span(!FontComic);span(!FontCourier);span(!FontTimes);' +
+ 'span(!FontSmaller);span(!FontLarger);span(!FontSmall);span(!FontBig);span(!FontDouble)',
+
+ /*
+ * Core styles.
+ */
+ coreStyles_bold: {
+ element: 'span',
+ attributes: { 'class': 'Bold' }
+ },
+ coreStyles_italic: {
+ element: 'span',
+ attributes: { 'class': 'Italic' }
+ },
+ coreStyles_underline: {
+ element: 'span',
+ attributes: { 'class': 'Underline' }
+ },
+ coreStyles_strike: {
+ element: 'span',
+ attributes: { 'class': 'StrikeThrough' },
+ overrides: 'strike'
+ },
+ coreStyles_subscript: {
+ element: 'span',
+ attributes: { 'class': 'Subscript' },
+ overrides: 'sub'
+ },
+ coreStyles_superscript: {
+ element: 'span',
+ attributes: { 'class': 'Superscript' },
+ overrides: 'sup'
+ },
+
+ /*
+ * Font face.
+ */
+
+ // List of fonts available in the toolbar combo. Each font definition is
+ // separated by a semi-colon (;). We are using class names here, so each font
+ // is defined by {Combo Label}/{Class Name}.
+ font_names: 'Comic Sans MS/FontComic;Courier New/FontCourier;Times New Roman/FontTimes',
+
+ // Define the way font elements will be applied to the document. The "span"
+ // element will be used. When a font is selected, the font name defined in the
+ // above list is passed to this definition with the name "Font", being it
+ // injected in the "class" attribute.
+ // We must also instruct the editor to replace span elements that are used to
+ // set the font (Overrides).
+ font_style: {
+ element: 'span',
+ attributes: { 'class': '#(family)' },
+ overrides: [
+ {
+ element: 'span',
+ attributes: {
+ 'class': /^Font(?:Comic|Courier|Times)$/
+ }
+ }
+ ]
+ },
+
+ /*
+ * Font sizes.
+ */
+ fontSize_sizes: 'Smaller/FontSmaller;Larger/FontLarger;8pt/FontSmall;14pt/FontBig;Double Size/FontDouble',
+ fontSize_style: {
+ element: 'span',
+ attributes: { 'class': '#(size)' },
+ overrides: [
+ {
+ element: 'span',
+ attributes: {
+ 'class': /^Font(?:Smaller|Larger|Small|Big|Double)$/
+ }
+ }
+ ]
+ } ,
+
+ /*
+ * Font colors.
+ */
+ colorButton_enableMore: false,
+
+ colorButton_colors: 'FontColor1/FF9900,FontColor2/0066CC,FontColor3/F00',
+ colorButton_foreStyle: {
+ element: 'span',
+ attributes: { 'class': '#(color)' },
+ overrides: [
+ {
+ element: 'span',
+ attributes: {
+ 'class': /^FontColor(?:1|2|3)$/
+ }
+ }
+ ]
+ },
+
+ colorButton_backStyle: {
+ element: 'span',
+ attributes: { 'class': '#(color)BG' },
+ overrides: [
+ {
+ element: 'span',
+ attributes: {
+ 'class': /^FontColor(?:1|2|3)BG$/
+ }
+ }
+ ]
+ },
+
+ /*
+ * Indentation.
+ */
+ indentClasses: [ 'Indent1', 'Indent2', 'Indent3' ],
+
+ /*
+ * Paragraph justification.
+ */
+ justifyClasses: [ 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyFull' ],
+
+ /*
+ * Styles combo.
+ */
+ stylesSet: [
+ { name: 'Strong Emphasis', element: 'strong' },
+ { name: 'Emphasis', element: 'em' },
+
+ { name: 'Computer Code', element: 'code' },
+ { name: 'Keyboard Phrase', element: 'kbd' },
+ { name: 'Sample Text', element: 'samp' },
+ { name: 'Variable', element: 'var' },
+
+ { name: 'Deleted Text', element: 'del' },
+ { name: 'Inserted Text', element: 'ins' },
+
+ { name: 'Cited Work', element: 'cite' },
+ { name: 'Inline Quotation', element: 'q' }
+ ]
+ });
+
+ </script>
+ </p>
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
+ <div id="footer">
+ <hr>
+ <p>
+ CKEditor - The text editor for the Internet - <a class="samples" href="http://ckeditor.com/">http://ckeditor.com</a>
+ </p>
+ <p id="copy">
+ Copyright &copy; 2003-2015, <a class="samples" href="http://cksource.com/">CKSource</a> - Frederico
+ Knabben. All rights reserved.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/js/ckeditor/skins/moono/dialog.css b/js/ckeditor/skins/moono/dialog.css
new file mode 100644
index 0000000..8fdd5ab
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/dialog_ie.css b/js/ckeditor/skins/moono/dialog_ie.css
new file mode 100644
index 0000000..48c1b4b
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog_ie.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/dialog_ie7.css b/js/ckeditor/skins/moono/dialog_ie7.css
new file mode 100644
index 0000000..bdc5a38
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog_ie7.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_title{zoom:1}.cke_dialog_footer{border-top:1px solid #bfbfbf}.cke_dialog_footer_buttons{position:static}.cke_dialog_footer_buttons a.cke_dialog_ui_button{vertical-align:top}.cke_dialog .cke_resizer_ltr{padding-left:4px}.cke_dialog .cke_resizer_rtl{padding-right:4px}.cke_dialog_ui_input_text,.cke_dialog_ui_input_password,.cke_dialog_ui_input_textarea,.cke_dialog_ui_input_select{padding:0!important}.cke_dialog_ui_checkbox_input,.cke_dialog_ui_ratio_input,.cke_btn_reset,.cke_btn_locked,.cke_btn_unlocked{border:1px solid transparent!important} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/dialog_ie8.css b/js/ckeditor/skins/moono/dialog_ie8.css
new file mode 100644
index 0000000..89b1664
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog_ie8.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{display:block} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/dialog_iequirks.css b/js/ckeditor/skins/moono/dialog_iequirks.css
new file mode 100644
index 0000000..a168d84
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog_iequirks.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_footer{filter:""} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/dialog_opera.css b/js/ckeditor/skins/moono/dialog_opera.css
new file mode 100644
index 0000000..2f5072d
--- /dev/null
+++ b/js/ckeditor/skins/moono/dialog_opera.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fff5f5f5',endColorstr='#ffcfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fffafafa',endColorstr='#ffededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffffff',endColorstr='#ffe4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fff2f2f2',endColorstr='#ffcccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}a.cke_dialog_ui_button_ok span,a.cke_dialog_ui_button_cancel span{color:inherit;font-size:12px;font-weight:bold;text-shadow:0 1px 0 #fff;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff9ad717',endColorstr='#ff69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background-position:0 -32px;background-image:url(images/mini.png);width:16px;height:16px;background-repeat:no-repeat;border:1px none;font-size:1px}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;background-position:0 0;background-image:url(images/mini.png);width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_unlocked{background-position:0 -16px;background-image:url(images/mini.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_dialog_footer{display:block;height:38px}.cke_ltr .cke_dialog_footer>*{float:right}.cke_rtl .cke_dialog_footer>*{float:left} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor.css b/js/ckeditor/skins/moono/editor.css
new file mode 100644
index 0000000..e3c946a
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor_gecko.css b/js/ckeditor/skins/moono/editor_gecko.css
new file mode 100644
index 0000000..98fa259
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor_gecko.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_bottom{padding-bottom:3px}.cke_combo_text{margin-bottom:-1px;margin-top:1px}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor_ie.css b/js/ckeditor/skins/moono/editor_ie.css
new file mode 100644
index 0000000..fa4cf00
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor_ie.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor_ie7.css b/js/ckeditor/skins/moono/editor_ie7.css
new file mode 100644
index 0000000..fe43ce5
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor_ie7.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon{display:inline-block;vertical-align:top}.cke_toolbox{display:inline-block;padding-bottom:5px;height:100%}.cke_rtl .cke_toolbox{padding-bottom:0}.cke_toolbar{margin-bottom:5px}.cke_rtl .cke_toolbar{margin-bottom:0}.cke_toolgroup{height:26px}.cke_toolgroup,.cke_combo{position:relative}a.cke_button{float:none;vertical-align:top}.cke_toolbar_separator{display:inline-block;float:none;vertical-align:top;background-color:#c0c0c0}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_rtl .cke_button_arrow{padding-top:8px;margin-right:2px}.cke_rtl .cke_combo_inlinelabel{display:table-cell;vertical-align:middle}.cke_menubutton{display:block;height:24px}.cke_menubutton_inner{display:block;position:relative}.cke_menubutton_icon{height:16px;width:16px}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:inline-block}.cke_menubutton_label{width:auto;vertical-align:top;line-height:24px;height:24px;margin:0 10px 0 0}.cke_menuarrow{width:5px;height:6px;padding:0;position:absolute;right:8px;top:10px;background-position:0 0}.cke_rtl .cke_menubutton_icon{position:absolute;right:0;top:0}.cke_rtl .cke_menubutton_label{float:right;clear:both;margin:0 24px 0 10px}.cke_hc .cke_rtl .cke_menubutton_label{margin-right:0}.cke_rtl .cke_menuarrow{left:8px;right:auto;background-position:0 -24px}.cke_hc .cke_menuarrow{top:5px;padding:0 5px}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{position:relative}.cke_wysiwyg_div{padding-top:0!important;padding-bottom:0!important}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor_ie8.css b/js/ckeditor/skins/moono/editor_ie8.css
new file mode 100644
index 0000000..e5174c7
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor_ie8.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/editor_iequirks.css b/js/ckeditor/skins/moono/editor_iequirks.css
new file mode 100644
index 0000000..4048b69
--- /dev/null
+++ b/js/ckeditor/skins/moono/editor_iequirks.css
@@ -0,0 +1,5 @@
+/*
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or http://ckeditor.com/license
+*/
+.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_reset_all table{table-layout:auto}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_top,.cke_contents,.cke_bottom{width:100%}.cke_button_arrow{font-size:0}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon{display:inline-block;vertical-align:top}.cke_rtl .cke_button_icon{float:none}.cke_resizer{width:10px}.cke_source{white-space:normal}.cke_bottom{position:static}.cke_colorbox{font-size:0}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file
diff --git a/js/ckeditor/skins/moono/icons.png b/js/ckeditor/skins/moono/icons.png
new file mode 100644
index 0000000..ee02970
--- /dev/null
+++ b/js/ckeditor/skins/moono/icons.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/icons_hidpi.png b/js/ckeditor/skins/moono/icons_hidpi.png
new file mode 100644
index 0000000..0466c2b
--- /dev/null
+++ b/js/ckeditor/skins/moono/icons_hidpi.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/arrow.png b/js/ckeditor/skins/moono/images/arrow.png
new file mode 100644
index 0000000..d72b5f3
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/arrow.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/close.png b/js/ckeditor/skins/moono/images/close.png
new file mode 100644
index 0000000..6a04ab5
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/close.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/hidpi/close.png b/js/ckeditor/skins/moono/images/hidpi/close.png
new file mode 100644
index 0000000..e406c2c
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/hidpi/close.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/hidpi/lock-open.png b/js/ckeditor/skins/moono/images/hidpi/lock-open.png
new file mode 100644
index 0000000..edbd12f
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/hidpi/lock-open.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/hidpi/lock.png b/js/ckeditor/skins/moono/images/hidpi/lock.png
new file mode 100644
index 0000000..1b87bbb
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/hidpi/lock.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/hidpi/refresh.png b/js/ckeditor/skins/moono/images/hidpi/refresh.png
new file mode 100644
index 0000000..c6c2b86
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/hidpi/refresh.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/lock-open.png b/js/ckeditor/skins/moono/images/lock-open.png
new file mode 100644
index 0000000..0476987
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/lock-open.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/lock.png b/js/ckeditor/skins/moono/images/lock.png
new file mode 100644
index 0000000..c5a1440
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/lock.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/mini.png b/js/ckeditor/skins/moono/images/mini.png
new file mode 100644
index 0000000..3e65bd5
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/mini.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/images/refresh.png b/js/ckeditor/skins/moono/images/refresh.png
new file mode 100644
index 0000000..1ff63c3
--- /dev/null
+++ b/js/ckeditor/skins/moono/images/refresh.png
Binary files differ
diff --git a/js/ckeditor/skins/moono/readme.md b/js/ckeditor/skins/moono/readme.md
new file mode 100644
index 0000000..e3abd58
--- /dev/null
+++ b/js/ckeditor/skins/moono/readme.md
@@ -0,0 +1,51 @@
+"Moono" Skin
+====================
+
+This skin has been chosen for the **default skin** of CKEditor 4.x, elected from the CKEditor
+[skin contest](http://ckeditor.com/blog/new_ckeditor_4_skin) and further shaped by
+the CKEditor team. "Moono" is maintained by the core developers.
+
+For more information about skins, please check the [CKEditor Skin SDK](http://docs.cksource.com/CKEditor_4.x/Skin_SDK)
+documentation.
+
+Features
+-------------------
+"Moono" is a monochromatic skin, which offers a modern look coupled with gradients and transparency.
+It comes with the following features:
+
+- Chameleon feature with brightness,
+- high-contrast compatibility,
+- graphics source provided in SVG.
+
+Directory Structure
+-------------------
+
+CSS parts:
+- **editor.css**: the main CSS file. It's simply loading several other files, for easier maintenance,
+- **mainui.css**: the file contains styles of entire editor outline structures,
+- **toolbar.css**: the file contains styles of the editor toolbar space (top),
+- **richcombo.css**: the file contains styles of the rich combo ui elements on toolbar,
+- **panel.css**: the file contains styles of the rich combo drop-down, it's not loaded
+until the first panel open up,
+- **elementspath.css**: the file contains styles of the editor elements path bar (bottom),
+- **menu.css**: the file contains styles of all editor menus including context menu and button drop-down,
+it's not loaded until the first menu open up,
+- **dialog.css**: the CSS files for the dialog UI, it's not loaded until the first dialog open,
+- **reset.css**: the file defines the basis of style resets among all editor UI spaces,
+- **preset.css**: the file defines the default styles of some UI elements reflecting the skin preference,
+- **editor_XYZ.css** and **dialog_XYZ.css**: browser specific CSS hacks.
+
+Other parts:
+- **skin.js**: the only JavaScript part of the skin that registers the skin, its browser specific files and its icons and defines the Chameleon feature,
+- **icons/**: contains all skin defined icons,
+- **images/**: contains a fill general used images,
+- **dev/**: contains SVG source of the skin icons.
+
+License
+-------
+
+Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+
+Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html).
+
+See LICENSE.md for more information.
diff --git a/js/ckeditor/styles.js b/js/ckeditor/styles.js
new file mode 100644
index 0000000..38bb680
--- /dev/null
+++ b/js/ckeditor/styles.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
+ */
+
+// This file contains style definitions that can be used by CKEditor plugins.
+//
+// The most common use for it is the "stylescombo" plugin, which shows a combo
+// in the editor toolbar, containing all styles. Other plugins instead, like
+// the div plugin, use a subset of the styles on their feature.
+//
+// If you don't have plugins that depend on this file, you can simply ignore it.
+// Otherwise it is strongly recommended to customize this file to match your
+// website requirements and design properly.
+
+CKEDITOR.stylesSet.add( 'default', [
+ /* Block Styles */
+
+ // These styles are already available in the "Format" combo ("format" plugin),
+ // so they are not needed here by default. You may enable them to avoid
+ // placing the "Format" combo in the toolbar, maintaining the same features.
+ /*
+ { name: 'Paragraph', element: 'p' },
+ { name: 'Heading 1', element: 'h1' },
+ { name: 'Heading 2', element: 'h2' },
+ { name: 'Heading 3', element: 'h3' },
+ { name: 'Heading 4', element: 'h4' },
+ { name: 'Heading 5', element: 'h5' },
+ { name: 'Heading 6', element: 'h6' },
+ { name: 'Preformatted Text',element: 'pre' },
+ { name: 'Address', element: 'address' },
+ */
+
+ { name: 'Italic Title', element: 'h2', styles: { 'font-style': 'italic' } },
+ { name: 'Subtitle', element: 'h3', styles: { 'color': '#aaa', 'font-style': 'italic' } },
+ {
+ name: 'Special Container',
+ element: 'div',
+ styles: {
+ padding: '5px 10px',
+ background: '#eee',
+ border: '1px solid #ccc'
+ }
+ },
+
+ /* Inline Styles */
+
+ // These are core styles available as toolbar buttons. You may opt enabling
+ // some of them in the Styles combo, removing them from the toolbar.
+ // (This requires the "stylescombo" plugin)
+ /*
+ { name: 'Strong', element: 'strong', overrides: 'b' },
+ { name: 'Emphasis', element: 'em' , overrides: 'i' },
+ { name: 'Underline', element: 'u' },
+ { name: 'Strikethrough', element: 'strike' },
+ { name: 'Subscript', element: 'sub' },
+ { name: 'Superscript', element: 'sup' },
+ */
+
+ { name: 'Marker', element: 'span', attributes: { 'class': 'marker' } },
+
+ { name: 'Big', element: 'big' },
+ { name: 'Small', element: 'small' },
+ { name: 'Typewriter', element: 'tt' },
+
+ { name: 'Computer Code', element: 'code' },
+ { name: 'Keyboard Phrase', element: 'kbd' },
+ { name: 'Sample Text', element: 'samp' },
+ { name: 'Variable', element: 'var' },
+
+ { name: 'Deleted Text', element: 'del' },
+ { name: 'Inserted Text', element: 'ins' },
+
+ { name: 'Cited Work', element: 'cite' },
+ { name: 'Inline Quotation', element: 'q' },
+
+ { name: 'Language: RTL', element: 'span', attributes: { 'dir': 'rtl' } },
+ { name: 'Language: LTR', element: 'span', attributes: { 'dir': 'ltr' } },
+
+ /* Object Styles */
+
+ {
+ name: 'Styled image (left)',
+ element: 'img',
+ attributes: { 'class': 'left' }
+ },
+
+ {
+ name: 'Styled image (right)',
+ element: 'img',
+ attributes: { 'class': 'right' }
+ },
+
+ {
+ name: 'Compact table',
+ element: 'table',
+ attributes: {
+ cellpadding: '5',
+ cellspacing: '0',
+ border: '1',
+ bordercolor: '#ccc'
+ },
+ styles: {
+ 'border-collapse': 'collapse'
+ }
+ },
+
+ { name: 'Borderless Table', element: 'table', styles: { 'border-style': 'hidden', 'background-color': '#E6E6FA' } },
+ { name: 'Square Bulleted List', element: 'ul', styles: { 'list-style-type': 'square' } }
+] );
+
diff --git a/js/details.js b/js/details.js
new file mode 100644
index 0000000..6ee1472
--- /dev/null
+++ b/js/details.js
@@ -0,0 +1,46 @@
+Event.observe(window,'load',detailsInit);
+
+function detailsInit() {
+
+ // set current task
+ var title = document.getElementsByTagName('title')[0];
+ title = title.textContent || title.text; //IE uses .text
+ var arr = /(#)(\d+)/.exec(title);
+ if( arr != null){
+ sessionStorage.setItem('current_task', arr[2]);
+
+ // make sure the page is not in edit mode, 'details' is id of description textarea
+ if (!document.getElementById('details')) {
+ Event.observe(document,'keydown',keyboardNavigation);
+ }
+ }
+}
+function keyboardNavigation(e) {
+ var src = Event.element(e);
+ if (/input|select|textarea/.test(src.nodeName.toLowerCase())) {
+ // don't do anything if key is pressed in input, select or textarea
+ return;
+ }
+ if ((useAltForKeyboardNavigation && !e.altKey) ||
+ e.ctrlKey || e.shiftKey) {
+ return;
+ }
+ switch (e.keyCode) {
+ case 85: // "u" get back to task list
+ window.location = $('indexlink').href;
+ Event.stop(e);
+ break;
+ case 80: // "p" move to previous task
+ if ($('prev')) {
+ window.location = $('prev').href;
+ Event.stop(e);
+ }
+ break;
+ case 78: // "n" move to next task
+ if ($('next')) {
+ window.location = $('next').href;
+ Event.stop(e);
+ }
+ break;
+ }
+}
diff --git a/js/functions.js b/js/functions.js
new file mode 100644
index 0000000..6df60e2
--- /dev/null
+++ b/js/functions.js
@@ -0,0 +1,576 @@
+// Set up the task list onclick handler
+addEvent(window,'load',setUpTasklistTable);
+function Disable(formid)
+{
+ document.formid.buSubmit.disabled = true;
+ document.formid.submit();
+}
+
+function showstuff(boxid, type){
+ if (!type) type = 'block';
+ $(boxid).style.display= type;
+ $(boxid).style.visibility='visible';
+}
+
+function hidestuff(boxid){
+ $(boxid).style.display='none';
+}
+
+function hidestuff_e(e, boxid){
+ e = e || window.event;
+ if (Event.element(e).getAttribute('id') !== 'lastsearchlink' ||
+ (Event.element(e).getAttribute('id') === 'lastsearchlink' && $('lastsearchlink').className == 'inactive')) {
+ if (!Position.within($(boxid), Event.pointerX(e), Event.pointerY(e))) {
+ //Event.stop(e);
+ if (boxid === 'mysearches') {
+ activelink('lastsearchlink');
+ }
+ $(boxid).style.visibility='hidden';
+ $(boxid).style.display='none';
+ document.onmouseup = null;
+ }
+ }
+}
+
+function showhidestuff(boxid) {
+ if (boxid === 'mysearches') {
+ activelink('lastsearchlink');
+ }
+ switch ($(boxid).style.visibility) {
+ case '':
+ $(boxid).style.visibility='visible';
+ break;
+ case 'hidden':
+ $(boxid).style.visibility='visible';
+ break;
+ case 'visible':
+ $(boxid).style.visibility='hidden';
+ break;
+ }
+ switch ($(boxid).style.display) {
+ case '':
+ $(boxid).style.display='block';
+ document.onmouseup = function(e) { hidestuff_e(e, boxid); };
+ break;
+ case 'none':
+ $(boxid).style.display='block';
+ document.onmouseup = function(e) { hidestuff_e(e, boxid); };
+ break;
+ case 'block':
+ $(boxid).style.display='none';
+ document.onmouseup = null;
+ break;
+ case 'inline':
+ $(boxid).style.display='none';
+ document.onmouseup = null;
+ break;
+ }
+}
+function setUpTasklistTable() {
+ if (!$('tasklist_table')) {
+ // No tasklist on the page
+ return;
+ }
+ var table = $('tasklist_table');
+ // deactivated 201508: when users click on cells with property, IMHO it should go to property or filter list by property, not open task details view.
+ //addEvent(table,'click',tasklistTableClick);
+}
+function tasklistTableClick(e) {
+ var src = eventGetSrc(e);
+ if (src.nodeName != 'TD') {
+ return;
+ }
+ if (src.hasChildNodes()) {
+ var checkBoxes = src.getElementsByTagName('input');
+ if (checkBoxes.length > 0) {
+ // User clicked the cell where the task select checkbox is
+ if (checkBoxes[0].checked) {
+ checkBoxes[0].checked = false;
+ } else {
+ checkBoxes[0].checked = true;
+ }
+ return;
+ }
+ }
+ var row = src.parentNode;
+ var aElements = row.getElementsByTagName('A');
+ if (aElements.length > 0) {
+ window.location = aElements[0].href;
+ } else {
+ // If both the task id and the task summary columns are non-visible
+ // just use the good old way to get to the task
+ window.location = '?do=details&task_id=' + row.id.substr(4);
+ }
+}
+
+function eventGetSrc(e) {
+ if (e.target) {
+ return e.target;
+ } else if (window.event) {
+ return window.event.srcElement;
+ } else {
+ return;
+ }
+}
+
+function ToggleSelected(id) {
+ var inputs = $(id).getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ if(inputs[i].type == 'checkbox'){
+ inputs[i].checked = !(inputs[i].checked);
+ }
+ }
+}
+
+function addUploadFields(id) {
+ if (!id) {
+ id = 'uploadfilebox';
+ }
+ var el = $(id);
+ var span = el.getElementsByTagName('span')[0];
+ if ('none' == span.style.display) {
+ // Show the file upload box
+ span.style.display = 'inline';
+ // Switch the buttns
+ $(id + '_attachafile').style.display = 'none';
+ $(id + '_attachanotherfile').style.display = 'inline';
+
+ } else {
+ // Copy the first file upload box and clear it's value
+ var newBox = span.cloneNode(true);
+ newBox.getElementsByTagName('input')[0].value = '';
+ el.appendChild(newBox);
+ }
+}
+
+function addLinkField(id) {
+ if(!id) {
+ id = 'addlinkbox';
+ }
+ var el = $(id);
+ var span = el.getElementsByTagName('span')[0];
+ if('none' == span.style.display) {
+
+ span.style.display = 'inline';
+
+ $(id + '_addalink').style.display = 'none';
+ $(id + '_addanotherlink').style.display = 'inline';
+ } else {
+
+ var newBox = span.cloneNode(true);
+ newBox.getElementsByTagName('input')[0].value = '';
+ el.appendChild(newBox);
+ }
+}
+
+function checkok(url, message, form) {
+
+ var myAjax = new Ajax.Request(url, {method: 'get', onComplete:function(originalRequest)
+ {
+ if(originalRequest.responseText == 'ok' || confirm(message)) {
+ $(form).submit();
+ }
+ }});
+ return false;
+}
+
+function removeUploadField(element, id) {
+ if (!id) {
+ id = 'uploadfilebox';
+ }
+ var el = $(id);
+ var span = el.getElementsByTagName('span');
+ if (1 == span.length) {
+ // Clear and hide the box
+ span[0].style.display='none';
+ span[0].getElementsByTagName('input')[0].value = '';
+ // Switch the buttons
+ $(id + '_attachafile').style.display = 'inline';
+ $(id + '_attachanotherfile').style.display = 'none';
+ } else {
+ el.removeChild(element.parentNode);
+ }
+}
+
+function removeLinkField(element, id) {
+ if(!id) {
+ id = 'addlinkbox';
+ }
+ var el = $(id);
+ var span = el.getElementsByTagName('span');
+ if (1 == span.length) {
+ span[0].style.display='none';
+ span[0].getElementsByTagName('input')[0].value = '';
+
+ $(id + '_addalink').style.display = 'inline';
+ $(id + '_addanotherlink').style.display = 'none';
+ } else {
+ el.removeChild(element.parentNode);
+ }
+}
+
+function updateDualSelectValue(id)
+{
+ var rt = $('r'+id);
+ var val = $('v'+id);
+ val.value = '';
+
+ var i;
+ for (i=0; i < rt.options.length; i++) {
+ val.value += (i > 0 ? ' ' : '') + rt.options[i].value;
+ }
+}
+function dualSelect(from, to, id) {
+ if (typeof(from) == 'string') {
+ from = $(from+id);
+ }
+ if (typeof(to) == 'string') {
+ var to_el = $(to+id);
+ // if (!to_el) alert("no element with id '" + (to+id) + "'");
+ to = to_el;
+ }
+
+ var i;
+ var len = from.options.length;
+ for(i=0;i<len;++i) {
+ if (!from.options[i].selected) continue;
+ if (to && to.options)
+ to.appendChild(from.options[i]);
+ else
+ from.removeChild(from.options[i]);
+ // make the option that is slid down selected (if any)
+ if (len > 1)
+ from.options[i == len - 1 ? len - 2 : i].selected = true;
+ break;
+ }
+
+ updateDualSelectValue(id);
+}
+
+function selectMove(id, step) {
+ var sel = $('r'+id);
+
+ var i = 0;
+
+ while (i < sel.options.length) {
+ if (sel.options[i].selected) {
+ if (i+step < 0 || i+step >= sel.options.length) {
+ return;
+ }
+ if (i + step == sel.options.length - 1)
+ sel.appendChild(sel.options[i]);
+ else if (step < 0)
+ sel.insertBefore(sel.options[i], sel.options[i+step]);
+ else
+ sel.insertBefore(sel.options[i], sel.options[i+step+1]);
+ updateDualSelectValue(id);
+ return;
+ }
+ i++;
+ }
+}
+var Cookie = {
+ getVar: function(name) {
+ var cookie = document.cookie;
+ if (cookie.length > 0) {
+ cookie += ';';
+ }
+ re = new RegExp(name + '\=(.*?);' );
+ if (cookie.match(re)) {
+ return RegExp.$1;
+ } else {
+ return '';
+ }
+ },
+ setVar: function(name,value,expire,path) {
+ document.cookie = name + '=' + value;
+ },
+ removeVar: function(name) {
+ var date = new Date(12);
+ document.cookie = name + '=;expires=' + date.toUTCString();
+ }
+};
+
+function deletesearch(id, url) {
+ $('rs' + id).getElementsByTagName('i')[0].className='fa fa-spinner fa-spin fa-lg';
+ url = url + 'js/callbacks/deletesearches.php';
+ var myAjax = new Ajax.Request(url, {
+ method: 'post',
+ parameters: { 'id': id, 'csrftoken': document.getElementById('deletesearchtoken').value },
+ onSuccess:function()
+ {
+ var oNodeToRemove = $('rs' + id);
+ oNodeToRemove.parentNode.removeChild(oNodeToRemove);
+ var table = $('mysearchestable');
+ if(table.rows.length > 0) {
+ table.getElementsByTagName('tr')[table.rows.length-1].style.borderBottom = '0';
+ } else {
+ showstuff('nosearches');
+ }
+ }
+ });
+}
+function savesearch(query, baseurl, savetext, csrftoken) {
+ url = baseurl + 'js/callbacks/savesearches.php';
+ if($('save_search').value != '') {
+ var old_text = $('lblsaveas').firstChild.nodeValue;
+ $('lblsaveas').firstChild.nodeValue = savetext;
+ var myAjax = new Ajax.Request(url, {
+ method: 'post',
+ parameters: query + '&search_name=' + encodeURIComponent($('save_search').value) + '&csrftoken=' + csrftoken,
+ onComplete:function()
+ {
+ $('lblsaveas').firstChild.nodeValue=old_text;
+ var myAjax2 = new Ajax.Updater('mysearches', baseurl + 'js/callbacks/getsearches.php', { method: 'get'});
+ }
+ });
+ }
+}
+function activelink(id) {
+ if($(id).className == 'active') {
+ $(id).className = 'inactive';
+ } else {
+ $(id).className = 'active';
+ }
+}
+var useAltForKeyboardNavigation = false; // Set this to true if you don't want to kill
+ // Firefox's find as you type
+
+function emptyElement(el) {
+ while(el.firstChild) {
+ emptyElement(el.firstChild);
+ var oNodeToRemove = el.firstChild;
+ oNodeToRemove.parentNode.removeChild(oNodeToRemove);
+ }
+}
+function showPreview(textfield, baseurl, field)
+{
+ var preview = $(field);
+ emptyElement(preview);
+
+ var img = document.createElement('i');
+ //img.src = baseurl + 'themes/CleanFS/ajax_load.gif';
+ img.className='fa fa-spinner fa-spin fa-lg'; // fontawesome animated fonticon
+ img.id = 'temp_img';
+ img.alt = 'Loading...';
+ preview.appendChild(img);
+
+ var text = $(textfield).value;
+ text = encodeURIComponent(text);
+ var url = baseurl + 'js/callbacks/getpreview.php';
+ var myAjax = new Ajax.Updater(field, url, {parameters:'text=' + text, method: 'post'});
+
+ if (text == '') {
+ hidestuff(field);
+ } else {
+ showstuff(field);
+ }
+}
+function checkname(value){
+ var re=/^[A-Za-z0-9_\.\-]*$/;
+
+ if (re.test(value)==false)
+ {
+ $('username').style.color ='red';
+ $('buSubmit').style.visibility = 'hidden';
+ // TODO an available translation array
+ // maybe provided by something like js/translations.php?lang=de
+ $('errormessage').innerHTML = 'invalid username';
+ }
+ // Otherwise check if username already exists
+ else
+ {
+ new Ajax.Request('js/callbacks/searchnames.php?name='+value, {onSuccess: function(t){ allow(t.responseText); } });
+ }
+}
+function allow(booler){
+ if(booler.indexOf('false') > -1) {
+ $('username').style.color ='red';
+ $('buSubmit').style.visibility = 'hidden';
+ // text after 'false|'
+ $('errormessage').innerHTML = booler.substring(6,booler.length);
+ }
+ else {
+ $('username').style.color ='green';
+ $('buSubmit').style.visibility = 'visible';
+ $('errormessage').innerHTML = '';
+ }
+}
+function getHistory(task_id, baseurl, field, details)
+{
+ var url = baseurl + 'js/callbacks/gethistory.php?task_id=' + task_id;
+ if (details) {
+ url += '&details=' + details;
+ }
+ var myAjax = new Ajax.Updater(field, url, { method: 'get'});
+}
+
+/********* Permissions popup ***********/
+
+function createClosure(obj, method) {
+ return (function() { obj[method](); });
+}
+
+function Perms(id) {
+ this.div = $(id);
+}
+
+Perms.prototype.timeout = null;
+Perms.prototype.div = null;
+
+Perms.prototype.clearTimeout = function() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
+}
+
+Perms.prototype.do_later = function(action) {
+ this.clearTimeout();
+ closure = createClosure(this, action);
+ this.timeout = setTimeout(closure, 400);
+}
+
+Perms.prototype.show = function() {
+ this.clearTimeout();
+ this.div.style.display = 'block';
+ this.div.style.visibility = 'visible';
+}
+
+Perms.prototype.hide = function() {
+ this.clearTimeout();
+ this.div.style.display = 'none';
+}
+
+// Replaces the currently selected text with the passed text.
+function replaceText(text, textarea)
+{
+ textarea = document.getElementById( textarea );
+ // Attempt to create a text range (IE).
+ if (typeof(textarea.caretPos) != "undefined" && textarea.createTextRange)
+ {
+ var caretPos = textarea.caretPos;
+
+ caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
+ caretPos.select();
+ }
+ // Mozilla text range replace.
+ else if (typeof(textarea.selectionStart) != "undefined")
+ {
+ var begin = textarea.value.substr(0, textarea.selectionStart);
+ var end = textarea.value.substr(textarea.selectionEnd);
+ var scrollPos = textarea.scrollTop;
+
+ textarea.value = begin + text + end;
+
+ if (textarea.setSelectionRange)
+ {
+ textarea.focus();
+ textarea.setSelectionRange(begin.length + text.length, begin.length + text.length);
+ }
+ textarea.scrollTop = scrollPos;
+ }
+ else if (document.selection) {
+ textarea.focus();
+ sel=document.selection.createRange();
+ sel.text=text;
+ }
+ // Just put it on the end.
+ else
+ {
+ textarea.value += text;
+ textarea.focus(textarea.value.length - 1);
+ }
+}
+
+
+// Surrounds the selected text with text1 and text2.
+function surroundText(text1, text2, textarea)
+{
+ textarea = document.getElementById( textarea );
+ // Can a text range be created?
+ if (typeof(textarea.caretPos) != "undefined" && textarea.createTextRange)
+ {
+ var caretPos = textarea.caretPos;
+
+ caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text1 + caretPos.text + text2 + ' ' : text1 + caretPos.text + text2;
+ caretPos.select();
+ }
+ // Mozilla text range wrap.
+ else if (typeof(textarea.selectionStart) != "undefined")
+ {
+ var begin = textarea.value.substr(0, textarea.selectionStart);
+ var selection = textarea.value.substr(textarea.selectionStart, textarea.selectionEnd - textarea.selectionStart);
+ var end = textarea.value.substr(textarea.selectionEnd);
+ var newCursorPos = textarea.selectionStart;
+ var scrollPos = textarea.scrollTop;
+
+ textarea.value = begin + text1 + selection + text2 + end;
+
+ if (textarea.setSelectionRange)
+ {
+ if (selection.length == 0)
+ textarea.setSelectionRange(newCursorPos + text1.length, newCursorPos + text1.length);
+ else
+ textarea.setSelectionRange(newCursorPos, newCursorPos + text1.length + selection.length + text2.length);
+ textarea.focus();
+ }
+ textarea.scrollTop = scrollPos;
+ }
+ else if(document.selection) {
+ textarea.focus();
+ var sampleText = 'TEXT';
+ var currentRange = document.selection.createRange();
+ var selection = currentRange.text;
+ var replaced = true;
+ if(!selection) {
+ replaced=false;
+ selection = sampleText;
+ }
+ if(selection.charAt(selection.length-1)==" "){
+ selection=selection.substring(0,selection.length-1);
+ currentRange.text = text1 + selection + text2 + " ";
+ }
+ else
+ {
+ currentRange.text = text1 + selection + text2;
+ }
+ if(!replaced){
+ // If putting in sample text (i.e. insert) adjust range start and end
+ currentRange.moveStart('character',-text.length-text2.length);
+ currentRange.moveEnd('character',-text2.length);
+ }
+ currentRange.select();
+ }
+ // Just put them on the end, then.
+ else
+ {
+ textarea.value += text1 + text2;
+ textarea.focus(textarea.value.length - 1);
+ }
+}
+
+function stopBubble(e) {
+ if (!e) { var e = window.event; }
+ e.cancelBubble = true;
+ if (e.stopPropagation) { e.stopPropagation(); }
+}
+
+//login box toggling
+login_box_status = false;
+var toggleLoginBox = function(el){
+ //this would turn the box on
+ if(login_box_status == false){
+ el.addClassName('active');
+ showstuff('loginbox');
+ //this would turn the box off
+ } else {
+ el.removeClassName('active');
+ hidestuff('loginbox');
+ }
+ //toggle functionality
+ if(login_box_status == true) login_box_status = false;
+ else login_box_status = true;
+ //return false to stop event bubbling
+ return false;
+}
diff --git a/js/index.js b/js/index.js
new file mode 100644
index 0000000..ae0586b
--- /dev/null
+++ b/js/index.js
@@ -0,0 +1,91 @@
+Event.observe(window,'load',tasklistInit);
+Event.observe(window,'load',searchInit);
+
+function tasklistInit() {
+ Caret.init();
+}
+function searchInit() {
+ if (navigator.appVersion.match(/\bMSIE 6\.0\b/) && $('searchthisproject') && $('reset')) {
+ Event.observe('searchthisproject','click',function() {$('reset').remove();});
+ }
+}
+var Caret = {
+ init: function () {
+ var task = sessionStorage.getItem('current_task') || 'top';
+ if (task == 'bottom' || task == 'top') {
+ var tab = $('tasklist_table');
+ var rows = tab ? tab.getElementsByTagName('tbody')[0].getElementsByTagName('tr') : [];
+ Caret.currentRow = (task == 'top' || rows.length == 0) ? rows[0] : rows[rows.length-1];
+ }
+ else {
+ Caret.currentRow = $('task'+task);
+ }
+ if (Caret.currentRow) {
+ Element.addClassName(Caret.currentRow,'current_row');
+ Event.observe(document,'keydown',Caret.keypress);
+ }
+ },
+ keypress: function (e) {
+ var src = Event.element(e);
+ if (/input|select|textarea/.test(src.nodeName.toLowerCase())) {
+ // don't do anything if key is pressed in input, select or textarea
+ return;
+ }
+ if ((useAltForKeyboardNavigation && !e.altKey) ||
+ (!useAltForKeyboardNavigation && e.altKey) ||
+ e.ctrlKey || e.shiftKey) {
+ return;
+ }
+ switch (e.keyCode) {
+ case 74: // user pressed "j" move down
+ Element.removeClassName(Caret.currentRow,'current_row');
+ Caret.nextRow();
+ Element.addClassName(Caret.currentRow,'current_row');
+ Event.stop(e);
+ break;
+ case 75: // user pressed "k" move up
+ Element.removeClassName(Caret.currentRow,'current_row');
+ Caret.previousRow();
+ Element.addClassName(Caret.currentRow,'current_row');
+ Event.stop(e);
+ break;
+ case 79: // user pressed "o" open task
+ window.location = Caret.currentRow.getElementsByTagName('a')[0].href; // FIXME ambiguous in future: if first a is not a link to the task, e.g. a column with link to task opener
+ Event.stop(e);
+ break;
+ }
+ },
+ nextRow: function () {
+ var row = Caret.currentRow;
+ while ((row = row.nextSibling)) {
+ if ('tr' == row.nodeName.toLowerCase()) {
+ Caret.currentRow = row;
+ return;
+ }
+ }
+ // we've reached the bottom of the list
+ if ($('next')) {
+ //Cookie.setVar('current_task','top'); // doesn't work well on multitab multiproject usage
+ sessionStorage.setItem('current_task','top');
+ window.location = $('next').href;
+ return;
+ }
+ },
+ previousRow: function () {
+ var row = Caret.currentRow;
+ while ((row = row.previousSibling)) {
+ if ('tr' == row.nodeName.toLowerCase()) {
+ Caret.currentRow = row;
+ return;
+ }
+ }
+ // we've reached the top of the list
+ if ($('previous')) {
+ //Cookie.setVar('current_task','bottom'); // doesn't work well on multitab multiproject usage
+ sessionStorage.setItem('current_task','bottom');
+ window.location = $('previous').href;
+ return;
+ }
+
+ }
+};
diff --git a/js/jit/jit.js b/js/jit/jit.js
new file mode 100644
index 0000000..d417d79
--- /dev/null
+++ b/js/jit/jit.js
@@ -0,0 +1,16841 @@
+/*
+ Copyright (c) 2010, Nicolas Garcia Belmonte
+ 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.
+ > * Neither the name of the organization nor the
+ > names of its contributors may be used to endorse or promote products
+ > derived from this software without specific prior written permission.
+ >
+ > THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``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 NICOLAS GARCIA BELMONTE 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.
+ */
+ (function () {
+
+/*
+ File: Core.js
+
+ */
+
+/*
+ Object: $jit
+
+ Defines the namespace for all library Classes and Objects.
+ This variable is the *only* global variable defined in the Toolkit.
+ There are also other interesting properties attached to this variable described below.
+ */
+window.$jit = function(w) {
+ w = w || window;
+ for(var k in $jit) {
+ if($jit[k].$extend) {
+ w[k] = $jit[k];
+ }
+ }
+};
+
+$jit.version = '2.0.0b';
+/*
+ Object: $jit.id
+
+ Works just like *document.getElementById*
+
+ Example:
+ (start code js)
+ var element = $jit.id('elementId');
+ (end code)
+
+*/
+
+/*
+ Object: $jit.util
+
+ Contains utility functions.
+
+ Some of the utility functions and the Class system were based in the MooTools Framework
+ <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
+ MIT license <http://mootools.net/license.txt>.
+
+ These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
+ I'd suggest you to use the functions from those libraries instead of using these, since their functions
+ are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
+
+ */
+var $ = function(d) {
+ return document.getElementById(d);
+};
+
+$.empty = function() {
+};
+
+/*
+ Method: extend
+
+ Augment an object by appending another object's properties.
+
+ Parameters:
+
+ original - (object) The object to be extended.
+ extended - (object) An object which properties are going to be appended to the original object.
+
+ Example:
+ (start code js)
+ $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
+ (end code)
+*/
+$.extend = function(original, extended) {
+ for ( var key in (extended || {}))
+ original[key] = extended[key];
+ return original;
+};
+
+$.lambda = function(value) {
+ return (typeof value == 'function') ? value : function() {
+ return value;
+ };
+};
+
+$.time = Date.now || function() {
+ return +new Date;
+};
+
+/*
+ Method: splat
+
+ Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
+
+ Parameters:
+
+ obj - (mixed) The object to be wrapped in an array.
+
+ Example:
+ (start code js)
+ $jit.util.splat(3); //[3]
+ $jit.util.splat([3]); //[3]
+ (end code)
+*/
+$.splat = function(obj) {
+ var type = $.type(obj);
+ return type ? ((type != 'array') ? [ obj ] : obj) : [];
+};
+
+$.type = function(elem) {
+ var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
+ if(type != 'object') return type;
+ if(elem && elem.$$family) return elem.$$family;
+ return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
+};
+$.type.s = Object.prototype.toString;
+
+/*
+ Method: each
+
+ Iterates through an iterable applying *f*.
+
+ Parameters:
+
+ iterable - (array) The original array.
+ fn - (function) The function to apply to the array elements.
+
+ Example:
+ (start code js)
+ $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
+ (end code)
+*/
+$.each = function(iterable, fn) {
+ var type = $.type(iterable);
+ if (type == 'object') {
+ for ( var key in iterable)
+ fn(iterable[key], key);
+ } else {
+ for ( var i = 0, l = iterable.length; i < l; i++)
+ fn(iterable[i], i);
+ }
+};
+
+$.indexOf = function(array, item) {
+ if(Array.indexOf) return array.indexOf(item);
+ for(var i=0,l=array.length; i<l; i++) {
+ if(array[i] === item) return i;
+ }
+ return -1;
+};
+
+/*
+ Method: map
+
+ Maps or collects an array by applying *f*.
+
+ Parameters:
+
+ array - (array) The original array.
+ f - (function) The function to apply to the array elements.
+
+ Example:
+ (start code js)
+ $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
+ (end code)
+*/
+$.map = function(array, f) {
+ var ans = [];
+ $.each(array, function(elem, i) {
+ ans.push(f(elem, i));
+ });
+ return ans;
+};
+
+/*
+ Method: reduce
+
+ Iteratively applies the binary function *f* storing the result in an accumulator.
+
+ Parameters:
+
+ array - (array) The original array.
+ f - (function) The function to apply to the array elements.
+ opt - (optional|mixed) The starting value for the acumulator.
+
+ Example:
+ (start code js)
+ $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
+ (end code)
+*/
+$.reduce = function(array, f, opt) {
+ var l = array.length;
+ if(l==0) return opt;
+ var acum = arguments.length == 3? opt : array[--l];
+ while(l--) {
+ acum = f(acum, array[l]);
+ }
+ return acum;
+};
+
+/*
+ Method: merge
+
+ Merges n-objects and their sub-objects creating a new, fresh object.
+
+ Parameters:
+
+ An arbitrary number of objects.
+
+ Example:
+ (start code js)
+ $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
+ (end code)
+*/
+$.merge = function() {
+ var mix = {};
+ for ( var i = 0, l = arguments.length; i < l; i++) {
+ var object = arguments[i];
+ if ($.type(object) != 'object')
+ continue;
+ for ( var key in object) {
+ var op = object[key], mp = mix[key];
+ mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
+ .merge(mp, op) : $.unlink(op);
+ }
+ }
+ return mix;
+};
+
+$.unlink = function(object) {
+ var unlinked;
+ switch ($.type(object)) {
+ case 'object':
+ unlinked = {};
+ for ( var p in object)
+ unlinked[p] = $.unlink(object[p]);
+ break;
+ case 'array':
+ unlinked = [];
+ for ( var i = 0, l = object.length; i < l; i++)
+ unlinked[i] = $.unlink(object[i]);
+ break;
+ default:
+ return object;
+ }
+ return unlinked;
+};
+
+$.zip = function() {
+ if(arguments.length === 0) return [];
+ for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
+ for(var i=0, row=[]; i<l; i++) {
+ row.push(arguments[i][j]);
+ }
+ ans.push(row);
+ }
+ return ans;
+};
+
+/*
+ Method: rgbToHex
+
+ Converts an RGB array into a Hex string.
+
+ Parameters:
+
+ srcArray - (array) An array with R, G and B values
+
+ Example:
+ (start code js)
+ $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
+ (end code)
+*/
+$.rgbToHex = function(srcArray, array) {
+ if (srcArray.length < 3)
+ return null;
+ if (srcArray.length == 4 && srcArray[3] == 0 && !array)
+ return 'transparent';
+ var hex = [];
+ for ( var i = 0; i < 3; i++) {
+ var bit = (srcArray[i] - 0).toString(16);
+ hex.push(bit.length == 1 ? '0' + bit : bit);
+ }
+ return array ? hex : '#' + hex.join('');
+};
+
+/*
+ Method: hexToRgb
+
+ Converts an Hex color string into an RGB array.
+
+ Parameters:
+
+ hex - (string) A color hex string.
+
+ Example:
+ (start code js)
+ $jit.util.hexToRgb('#fff'); //[255, 255, 255]
+ (end code)
+*/
+$.hexToRgb = function(hex) {
+ if (hex.length != 7) {
+ hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ hex.shift();
+ if (hex.length != 3)
+ return null;
+ var rgb = [];
+ for ( var i = 0; i < 3; i++) {
+ var value = hex[i];
+ if (value.length == 1)
+ value += value;
+ rgb.push(parseInt(value, 16));
+ }
+ return rgb;
+ } else {
+ hex = parseInt(hex.slice(1), 16);
+ return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
+ }
+};
+
+$.destroy = function(elem) {
+ $.clean(elem);
+ if (elem.parentNode)
+ elem.parentNode.removeChild(elem);
+ if (elem.clearAttributes)
+ elem.clearAttributes();
+};
+
+$.clean = function(elem) {
+ for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
+ $.destroy(ch[i]);
+ }
+};
+
+/*
+ Method: addEvent
+
+ Cross-browser add event listener.
+
+ Parameters:
+
+ obj - (obj) The Element to attach the listener to.
+ type - (string) The listener type. For example 'click', or 'mousemove'.
+ fn - (function) The callback function to be used when the event is fired.
+
+ Example:
+ (start code js)
+ $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
+ (end code)
+*/
+$.addEvent = function(obj, type, fn) {
+ if (obj.addEventListener)
+ obj.addEventListener(type, fn, false);
+ else
+ obj.attachEvent('on' + type, fn);
+};
+
+$.addEvents = function(obj, typeObj) {
+ for(var type in typeObj) {
+ $.addEvent(obj, type, typeObj[type]);
+ }
+};
+
+$.hasClass = function(obj, klass) {
+ return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
+};
+
+$.addClass = function(obj, klass) {
+ if (!$.hasClass(obj, klass))
+ obj.className = (obj.className + " " + klass);
+};
+
+$.removeClass = function(obj, klass) {
+ obj.className = obj.className.replace(new RegExp(
+ '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
+};
+
+$.getPos = function(elem) {
+ var offset = getOffsets(elem);
+ var scroll = getScrolls(elem);
+ return {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ function getOffsets(elem) {
+ var position = {
+ x: 0,
+ y: 0
+ };
+ while (elem && !isBody(elem)) {
+ position.x += elem.offsetLeft;
+ position.y += elem.offsetTop;
+ elem = elem.offsetParent;
+ }
+ return position;
+ }
+
+ function getScrolls(elem) {
+ var position = {
+ x: 0,
+ y: 0
+ };
+ while (elem && !isBody(elem)) {
+ position.x += elem.scrollLeft;
+ position.y += elem.scrollTop;
+ elem = elem.parentNode;
+ }
+ return position;
+ }
+
+ function isBody(element) {
+ return (/^(?:body|html)$/i).test(element.tagName);
+ }
+};
+
+$.event = {
+ get: function(e, win) {
+ win = win || window;
+ return e || win.event;
+ },
+ getWheel: function(e) {
+ return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
+ },
+ isRightClick: function(e) {
+ return (e.which == 3 || e.button == 2);
+ },
+ getPos: function(e, win) {
+ // get mouse position
+ win = win || window;
+ e = e || win.event;
+ var doc = win.document;
+ doc = doc.documentElement || doc.body;
+ //TODO(nico): make touch event handling better
+ if(e.touches && e.touches.length) {
+ e = e.touches[0];
+ }
+ var page = {
+ x: e.pageX || (e.clientX + doc.scrollLeft),
+ y: e.pageY || (e.clientY + doc.scrollTop)
+ };
+ return page;
+ },
+ stop: function(e) {
+ if (e.stopPropagation) e.stopPropagation();
+ e.cancelBubble = true;
+ if (e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ }
+};
+
+$jit.util = $jit.id = $;
+
+var Class = function(properties) {
+ properties = properties || {};
+ var klass = function() {
+ for ( var key in this) {
+ if (typeof this[key] != 'function')
+ this[key] = $.unlink(this[key]);
+ }
+ this.constructor = klass;
+ if (Class.prototyping)
+ return this;
+ var instance = this.initialize ? this.initialize.apply(this, arguments)
+ : this;
+ //typize
+ this.$$family = 'class';
+ return instance;
+ };
+
+ for ( var mutator in Class.Mutators) {
+ if (!properties[mutator])
+ continue;
+ properties = Class.Mutators[mutator](properties, properties[mutator]);
+ delete properties[mutator];
+ }
+
+ $.extend(klass, this);
+ klass.constructor = Class;
+ klass.prototype = properties;
+ return klass;
+};
+
+Class.Mutators = {
+
+ Implements: function(self, klasses) {
+ $.each($.splat(klasses), function(klass) {
+ Class.prototyping = klass;
+ var instance = (typeof klass == 'function') ? new klass : klass;
+ for ( var prop in instance) {
+ if (!(prop in self)) {
+ self[prop] = instance[prop];
+ }
+ }
+ delete Class.prototyping;
+ });
+ return self;
+ }
+
+};
+
+$.extend(Class, {
+
+ inherit: function(object, properties) {
+ for ( var key in properties) {
+ var override = properties[key];
+ var previous = object[key];
+ var type = $.type(override);
+ if (previous && type == 'function') {
+ if (override != previous) {
+ Class.override(object, key, override);
+ }
+ } else if (type == 'object') {
+ object[key] = $.merge(previous, override);
+ } else {
+ object[key] = override;
+ }
+ }
+ return object;
+ },
+
+ override: function(object, name, method) {
+ var parent = Class.prototyping;
+ if (parent && object[name] != parent[name])
+ parent = null;
+ var override = function() {
+ var previous = this.parent;
+ this.parent = parent ? parent[name] : object[name];
+ var value = method.apply(this, arguments);
+ this.parent = previous;
+ return value;
+ };
+ object[name] = override;
+ }
+
+});
+
+Class.prototype.implement = function() {
+ var proto = this.prototype;
+ $.each(Array.prototype.slice.call(arguments || []), function(properties) {
+ Class.inherit(proto, properties);
+ });
+ return this;
+};
+
+$jit.Class = Class;
+
+/*
+ Object: $jit.json
+
+ Provides JSON utility functions.
+
+ Most of these functions are JSON-tree traversal and manipulation functions.
+*/
+$jit.json = {
+ /*
+ Method: prune
+
+ Clears all tree nodes having depth greater than maxLevel.
+
+ Parameters:
+
+ tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
+ maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
+
+ */
+ prune: function(tree, maxLevel) {
+ this.each(tree, function(elem, i) {
+ if (i == maxLevel && elem.children) {
+ delete elem.children;
+ elem.children = [];
+ }
+ });
+ },
+ /*
+ Method: getParent
+
+ Returns the parent node of the node having _id_ as id.
+
+ Parameters:
+
+ tree - (object) A JSON tree object. See also <Loader.loadJSON>.
+ id - (string) The _id_ of the child node whose parent will be returned.
+
+ Returns:
+
+ A tree JSON node if any, or false otherwise.
+
+ */
+ getParent: function(tree, id) {
+ if (tree.id == id)
+ return false;
+ var ch = tree.children;
+ if (ch && ch.length > 0) {
+ for ( var i = 0; i < ch.length; i++) {
+ if (ch[i].id == id)
+ return tree;
+ else {
+ var ans = this.getParent(ch[i], id);
+ if (ans)
+ return ans;
+ }
+ }
+ }
+ return false;
+ },
+ /*
+ Method: getSubtree
+
+ Returns the subtree that matches the given id.
+
+ Parameters:
+
+ tree - (object) A JSON tree object. See also <Loader.loadJSON>.
+ id - (string) A node *unique* identifier.
+
+ Returns:
+
+ A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
+
+ */
+ getSubtree: function(tree, id) {
+ if (tree.id == id)
+ return tree;
+ for ( var i = 0, ch = tree.children; i < ch.length; i++) {
+ var t = this.getSubtree(ch[i], id);
+ if (t != null)
+ return t;
+ }
+ return null;
+ },
+ /*
+ Method: eachLevel
+
+ Iterates on tree nodes with relative depth less or equal than a specified level.
+
+ Parameters:
+
+ tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
+ initLevel - (number) An integer specifying the initial relative level. Usually zero.
+ toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
+ action - (function) A function that receives a node and an integer specifying the actual level of the node.
+
+ Example:
+ (start code js)
+ $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
+ alert(node.name + ' ' + depth);
+ });
+ (end code)
+ */
+ eachLevel: function(tree, initLevel, toLevel, action) {
+ if (initLevel <= toLevel) {
+ action(tree, initLevel);
+ if(!tree.children) return;
+ for ( var i = 0, ch = tree.children; i < ch.length; i++) {
+ this.eachLevel(ch[i], initLevel + 1, toLevel, action);
+ }
+ }
+ },
+ /*
+ Method: each
+
+ A JSON tree iterator.
+
+ Parameters:
+
+ tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
+ action - (function) A function that receives a node.
+
+ Example:
+ (start code js)
+ $jit.json.each(tree, function(node) {
+ alert(node.name);
+ });
+ (end code)
+
+ */
+ each: function(tree, action) {
+ this.eachLevel(tree, 0, Number.MAX_VALUE, action);
+ }
+};
+
+
+/*
+ An object containing multiple type of transformations.
+*/
+
+$jit.Trans = {
+ $extend: true,
+
+ linear: function(p){
+ return p;
+ }
+};
+
+var Trans = $jit.Trans;
+
+(function(){
+
+ var makeTrans = function(transition, params){
+ params = $.splat(params);
+ return $.extend(transition, {
+ easeIn: function(pos){
+ return transition(pos, params);
+ },
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
+ 2 * (1 - pos), params)) / 2;
+ }
+ });
+ };
+
+ var transitions = {
+
+ Pow: function(p, x){
+ return Math.pow(p, x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.sin((1 - p) * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for ( var a = 0, b = 1; 1; a += b, b /= 2) {
+ if (p >= (7 - 4 * a) / 11) {
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p)
+ * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+ }
+
+ };
+
+ $.each(transitions, function(val, key){
+ Trans[key] = makeTrans(val);
+ });
+
+ $.each( [
+ 'Quad', 'Cubic', 'Quart', 'Quint'
+ ], function(elem, i){
+ Trans[elem] = makeTrans(function(p){
+ return Math.pow(p, [
+ i + 2
+ ]);
+ });
+ });
+
+})();
+
+/*
+ A Class that can perform animations for generic objects.
+
+ If you are looking for animation transitions please take a look at the <Trans> object.
+
+ Used by:
+
+ <Graph.Plot>
+
+ Based on:
+
+ The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
+
+*/
+
+var Animation = new Class( {
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ setOptions: function(options){
+ var opt = {
+ duration: 2500,
+ fps: 40,
+ transition: Trans.Quart.easeInOut,
+ compute: $.empty,
+ complete: $.empty,
+ link: 'ignore'
+ };
+ this.opt = $.merge(opt, options || {});
+ return this;
+ },
+
+ step: function(){
+ var time = $.time(), opt = this.opt;
+ if (time < this.time + opt.duration) {
+ var delta = opt.transition((time - this.time) / opt.duration);
+ opt.compute(delta);
+ } else {
+ this.timer = clearInterval(this.timer);
+ opt.compute(1);
+ opt.complete();
+ }
+ },
+
+ start: function(){
+ if (!this.check())
+ return this;
+ this.time = 0;
+ this.startTimer();
+ return this;
+ },
+
+ startTimer: function(){
+ var that = this, fps = this.opt.fps;
+ if (this.timer)
+ return false;
+ this.time = $.time() - this.time;
+ this.timer = setInterval((function(){
+ that.step();
+ }), Math.round(1000 / fps));
+ return true;
+ },
+
+ pause: function(){
+ this.stopTimer();
+ return this;
+ },
+
+ resume: function(){
+ this.startTimer();
+ return this;
+ },
+
+ stopTimer: function(){
+ if (!this.timer)
+ return false;
+ this.time = $.time() - this.time;
+ this.timer = clearInterval(this.timer);
+ return true;
+ },
+
+ check: function(){
+ if (!this.timer)
+ return true;
+ if (this.opt.link == 'cancel') {
+ this.stopTimer();
+ return true;
+ }
+ return false;
+ }
+});
+
+
+var Options = function() {
+ var args = arguments;
+ for(var i=0, l=args.length, ans={}; i<l; i++) {
+ var opt = Options[args[i]];
+ if(opt.$extend) {
+ $.extend(ans, opt);
+ } else {
+ ans[args[i]] = opt;
+ }
+ }
+ return ans;
+};
+
+/*
+ * File: Options.AreaChart.js
+ *
+*/
+
+/*
+ Object: Options.AreaChart
+
+ <AreaChart> options.
+ Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
+
+ Syntax:
+
+ (start code js)
+
+ Options.AreaChart = {
+ animate: true,
+ labelOffset: 3,
+ type: 'stacked',
+ selectOnHover: true,
+ showAggregates: true,
+ showLabels: true,
+ filterOnClick: false,
+ restoreOnRightClick: false
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+
+ var areaChart = new $jit.AreaChart({
+ animate: true,
+ type: 'stacked:gradient',
+ selectOnHover: true,
+ filterOnClick: true,
+ restoreOnRightClick: true
+ });
+
+ (end code)
+
+ Parameters:
+
+ animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
+ labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
+ type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
+ selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
+ showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
+ showLabels - (boolean) Default's *true*. Display the name of the slots.
+ filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
+ restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
+
+*/
+
+Options.AreaChart = {
+ $extend: true,
+
+ animate: true,
+ labelOffset: 3, // label offset
+ type: 'stacked', // gradient
+ Tips: {
+ enable: false,
+ onShow: $.empty,
+ onHide: $.empty
+ },
+ Events: {
+ enable: false,
+ onClick: $.empty
+ },
+ selectOnHover: true,
+ showAggregates: true,
+ showLabels: true,
+ filterOnClick: false,
+ restoreOnRightClick: false
+};
+
+/*
+ * File: Options.Margin.js
+ *
+*/
+
+/*
+ Object: Options.Margin
+
+ Canvas drawing margins.
+
+ Syntax:
+
+ (start code js)
+
+ Options.Margin = {
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+
+ var viz = new $jit.Viz({
+ Margin: {
+ right: 10,
+ bottom: 20
+ }
+ });
+
+ (end code)
+
+ Parameters:
+
+ top - (number) Default's *0*. Top margin.
+ left - (number) Default's *0*. Left margin.
+ right - (number) Default's *0*. Right margin.
+ bottom - (number) Default's *0*. Bottom margin.
+
+*/
+
+Options.Margin = {
+ $extend: false,
+
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0
+};
+
+/*
+ * File: Options.Canvas.js
+ *
+*/
+
+/*
+ Object: Options.Canvas
+
+ These are Canvas general options, like where to append it in the DOM, its dimensions, background,
+ and other more advanced options.
+
+ Syntax:
+
+ (start code js)
+
+ Options.Canvas = {
+ injectInto: 'id',
+ width: false,
+ height: false,
+ useCanvas: false,
+ withLabels: true,
+ background: false
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ injectInto: 'someContainerId',
+ width: 500,
+ height: 700
+ });
+ (end code)
+
+ Parameters:
+
+ injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
+ width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
+ height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
+ useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
+ withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
+ background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
+*/
+
+Options.Canvas = {
+ $extend: true,
+
+ injectInto: 'id',
+ width: false,
+ height: false,
+ useCanvas: false,
+ withLabels: true,
+ background: false
+};
+
+/*
+ * File: Options.Tree.js
+ *
+*/
+
+/*
+ Object: Options.Tree
+
+ Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
+
+ Syntax:
+
+ (start code js)
+ Options.Tree = {
+ orientation: "left",
+ subtreeOffset: 8,
+ siblingOffset: 5,
+ indent:10,
+ multitree: false,
+ align:"center"
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var st = new $jit.ST({
+ orientation: 'left',
+ subtreeOffset: 1,
+ siblingOFfset: 5,
+ multitree: true
+ });
+ (end code)
+
+ Parameters:
+
+ subtreeOffset - (number) Default's 8. Separation offset between subtrees.
+ siblingOffset - (number) Default's 5. Separation offset between siblings.
+ orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
+ align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
+ indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
+ multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
+
+*/
+Options.Tree = {
+ $extend: true,
+
+ orientation: "left",
+ subtreeOffset: 8,
+ siblingOffset: 5,
+ indent:10,
+ multitree: false,
+ align:"center"
+};
+
+
+/*
+ * File: Options.Node.js
+ *
+*/
+
+/*
+ Object: Options.Node
+
+ Provides Node rendering options for Tree and Graph based visualizations.
+
+ Syntax:
+
+ (start code js)
+ Options.Node = {
+ overridable: false,
+ type: 'circle',
+ color: '#ccb',
+ alpha: 1,
+ dim: 3,
+ height: 20,
+ width: 90,
+ autoHeight: false,
+ autoWidth: false,
+ lineWidth: 1,
+ transform: true,
+ align: "center",
+ angularWidth:1,
+ span:1,
+ CanvasStyles: {}
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Node: {
+ overridable: true,
+ width: 30,
+ autoHeight: true,
+ type: 'rectangle'
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
+ type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
+ color - (string) Default's *#ccb*. Node color.
+ alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
+ dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
+ height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
+ width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
+ autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
+ autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
+ lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
+ transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
+ align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
+ angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
+ span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
+ CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
+
+*/
+Options.Node = {
+ $extend: false,
+
+ overridable: false,
+ type: 'circle',
+ color: '#ccb',
+ alpha: 1,
+ dim: 3,
+ height: 20,
+ width: 90,
+ autoHeight: false,
+ autoWidth: false,
+ lineWidth: 1,
+ transform: true,
+ align: "center",
+ angularWidth:1,
+ span:1,
+ //Raw canvas styles to be
+ //applied to the context instance
+ //before plotting a node
+ CanvasStyles: {}
+};
+
+
+/*
+ * File: Options.Edge.js
+ *
+*/
+
+/*
+ Object: Options.Edge
+
+ Provides Edge rendering options for Tree and Graph based visualizations.
+
+ Syntax:
+
+ (start code js)
+ Options.Edge = {
+ overridable: false,
+ type: 'line',
+ color: '#ccb',
+ lineWidth: 1,
+ dim:15,
+ alpha: 1,
+ CanvasStyles: {}
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Edge: {
+ overridable: true,
+ type: 'line',
+ color: '#fff',
+ CanvasStyles: {
+ shadowColor: '#ccc',
+ shadowBlur: 10
+ }
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
+ type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
+ color - (string) Default's '#ccb'. Edge color.
+ lineWidth - (number) Default's *1*. Line/Edge width.
+ alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
+ dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
+ epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
+ CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
+
+ See also:
+
+ If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
+*/
+Options.Edge = {
+ $extend: false,
+
+ overridable: false,
+ type: 'line',
+ color: '#ccb',
+ lineWidth: 1,
+ dim:15,
+ alpha: 1,
+ epsilon: 7,
+
+ //Raw canvas styles to be
+ //applied to the context instance
+ //before plotting an edge
+ CanvasStyles: {}
+};
+
+
+/*
+ * File: Options.Fx.js
+ *
+*/
+
+/*
+ Object: Options.Fx
+
+ Provides animation options like duration of the animations, frames per second and animation transitions.
+
+ Syntax:
+
+ (start code js)
+ Options.Fx = {
+ fps:40,
+ duration: 2500,
+ transition: $jit.Trans.Quart.easeInOut,
+ clearCanvas: true
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ duration: 1000,
+ fps: 35,
+ transition: $jit.Trans.linear
+ });
+ (end code)
+
+ Parameters:
+
+ clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
+ duration - (number) Default's *2500*. Duration of the animation in milliseconds.
+ fps - (number) Default's *40*. Frames per second.
+ transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
+
+ Object: $jit.Trans
+
+ This object is used for specifying different animation transitions in all visualizations.
+
+ There are many different type of animation transitions.
+
+ linear:
+
+ Displays a linear transition
+
+ >Trans.linear
+
+ (see Linear.png)
+
+ Quad:
+
+ Displays a Quadratic transition.
+
+ >Trans.Quad.easeIn
+ >Trans.Quad.easeOut
+ >Trans.Quad.easeInOut
+
+ (see Quad.png)
+
+ Cubic:
+
+ Displays a Cubic transition.
+
+ >Trans.Cubic.easeIn
+ >Trans.Cubic.easeOut
+ >Trans.Cubic.easeInOut
+
+ (see Cubic.png)
+
+ Quart:
+
+ Displays a Quartetic transition.
+
+ >Trans.Quart.easeIn
+ >Trans.Quart.easeOut
+ >Trans.Quart.easeInOut
+
+ (see Quart.png)
+
+ Quint:
+
+ Displays a Quintic transition.
+
+ >Trans.Quint.easeIn
+ >Trans.Quint.easeOut
+ >Trans.Quint.easeInOut
+
+ (see Quint.png)
+
+ Expo:
+
+ Displays an Exponential transition.
+
+ >Trans.Expo.easeIn
+ >Trans.Expo.easeOut
+ >Trans.Expo.easeInOut
+
+ (see Expo.png)
+
+ Circ:
+
+ Displays a Circular transition.
+
+ >Trans.Circ.easeIn
+ >Trans.Circ.easeOut
+ >Trans.Circ.easeInOut
+
+ (see Circ.png)
+
+ Sine:
+
+ Displays a Sineousidal transition.
+
+ >Trans.Sine.easeIn
+ >Trans.Sine.easeOut
+ >Trans.Sine.easeInOut
+
+ (see Sine.png)
+
+ Back:
+
+ >Trans.Back.easeIn
+ >Trans.Back.easeOut
+ >Trans.Back.easeInOut
+
+ (see Back.png)
+
+ Bounce:
+
+ Bouncy transition.
+
+ >Trans.Bounce.easeIn
+ >Trans.Bounce.easeOut
+ >Trans.Bounce.easeInOut
+
+ (see Bounce.png)
+
+ Elastic:
+
+ Elastic curve.
+
+ >Trans.Elastic.easeIn
+ >Trans.Elastic.easeOut
+ >Trans.Elastic.easeInOut
+
+ (see Elastic.png)
+
+ Based on:
+
+ Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
+
+
+*/
+Options.Fx = {
+ $extend: true,
+
+ fps:40,
+ duration: 2500,
+ transition: $jit.Trans.Quart.easeInOut,
+ clearCanvas: true
+};
+
+/*
+ * File: Options.Label.js
+ *
+*/
+/*
+ Object: Options.Label
+
+ Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
+
+ Syntax:
+
+ (start code js)
+ Options.Label = {
+ overridable: false,
+ type: 'HTML', //'SVG', 'Native'
+ style: ' ',
+ size: 10,
+ family: 'sans-serif',
+ textAlign: 'center',
+ textBaseline: 'alphabetic',
+ color: '#fff'
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Label: {
+ type: 'Native',
+ size: 11,
+ color: '#ccc'
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
+ type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
+ style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+ size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+ family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+ color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
+*/
+Options.Label = {
+ $extend: false,
+
+ overridable: false,
+ type: 'HTML', //'SVG', 'Native'
+ style: ' ',
+ size: 10,
+ family: 'sans-serif',
+ textAlign: 'center',
+ textBaseline: 'alphabetic',
+ color: '#fff'
+};
+
+
+/*
+ * File: Options.Tips.js
+ *
+ */
+
+/*
+ Object: Options.Tips
+
+ Tips options
+
+ Syntax:
+
+ (start code js)
+ Options.Tips = {
+ enable: false,
+ type: 'auto',
+ offsetX: 20,
+ offsetY: 20,
+ onShow: $.empty,
+ onHide: $.empty
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Tips: {
+ enable: true,
+ type: 'Native',
+ offsetX: 10,
+ offsetY: 10,
+ onShow: function(tip, node) {
+ tip.innerHTML = node.name;
+ }
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class.
+ type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
+ offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
+ offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
+ onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
+ onHide() - This callack is used when hiding a tooltip.
+
+*/
+Options.Tips = {
+ $extend: false,
+
+ enable: false,
+ type: 'auto',
+ offsetX: 20,
+ offsetY: 20,
+ force: false,
+ onShow: $.empty,
+ onHide: $.empty
+};
+
+
+/*
+ * File: Options.NodeStyles.js
+ *
+ */
+
+/*
+ Object: Options.NodeStyles
+
+ Apply different styles when a node is hovered or selected.
+
+ Syntax:
+
+ (start code js)
+ Options.NodeStyles = {
+ enable: false,
+ type: 'auto',
+ stylesHover: false,
+ stylesClick: false
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ NodeStyles: {
+ enable: true,
+ type: 'Native',
+ stylesHover: {
+ dim: 30,
+ color: '#fcc'
+ },
+ duration: 600
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ enable - (boolean) Default's *false*. Whether to enable this option.
+ type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
+ stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
+ stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
+*/
+
+Options.NodeStyles = {
+ $extend: false,
+
+ enable: false,
+ type: 'auto',
+ stylesHover: false,
+ stylesClick: false
+};
+
+
+/*
+ * File: Options.Events.js
+ *
+*/
+
+/*
+ Object: Options.Events
+
+ Configuration for adding mouse/touch event handlers to Nodes.
+
+ Syntax:
+
+ (start code js)
+ Options.Events = {
+ enable: false,
+ enableForEdges: false,
+ type: 'auto',
+ onClick: $.empty,
+ onRightClick: $.empty,
+ onMouseMove: $.empty,
+ onMouseEnter: $.empty,
+ onMouseLeave: $.empty,
+ onDragStart: $.empty,
+ onDragMove: $.empty,
+ onDragCancel: $.empty,
+ onDragEnd: $.empty,
+ onTouchStart: $.empty,
+ onTouchMove: $.empty,
+ onTouchEnd: $.empty,
+ onTouchCancel: $.empty,
+ onMouseWheel: $.empty
+ };
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Events: {
+ enable: true,
+ onClick: function(node, eventInfo, e) {
+ viz.doSomething();
+ },
+ onMouseEnter: function(node, eventInfo, e) {
+ viz.canvas.getElement().style.cursor = 'pointer';
+ },
+ onMouseLeave: function(node, eventInfo, e) {
+ viz.canvas.getElement().style.cursor = '';
+ }
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ enable - (boolean) Default's *false*. Whether to enable the Event system.
+ enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
+ type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
+ onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
+ onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
+ onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
+ onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
+ onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
+ onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
+*/
+
+Options.Events = {
+ $extend: false,
+
+ enable: false,
+ enableForEdges: false,
+ type: 'auto',
+ onClick: $.empty,
+ onRightClick: $.empty,
+ onMouseMove: $.empty,
+ onMouseEnter: $.empty,
+ onMouseLeave: $.empty,
+ onDragStart: $.empty,
+ onDragMove: $.empty,
+ onDragCancel: $.empty,
+ onDragEnd: $.empty,
+ onTouchStart: $.empty,
+ onTouchMove: $.empty,
+ onTouchEnd: $.empty,
+ onMouseWheel: $.empty
+};
+
+/*
+ * File: Options.Navigation.js
+ *
+*/
+
+/*
+ Object: Options.Navigation
+
+ Panning and zooming options for Graph/Tree based visualizations. These options are implemented
+ by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
+
+ Syntax:
+
+ (start code js)
+
+ Options.Navigation = {
+ enable: false,
+ type: 'auto',
+ panning: false, //true, 'avoid nodes'
+ zooming: false
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ Navigation: {
+ enable: true,
+ panning: 'avoid nodes',
+ zooming: 20
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
+ panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
+ zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
+
+*/
+
+Options.Navigation = {
+ $extend: false,
+
+ enable: false,
+ type: 'auto',
+ panning: false, //true | 'avoid nodes'
+ zooming: false
+};
+
+/*
+ * File: Options.Controller.js
+ *
+*/
+
+/*
+ Object: Options.Controller
+
+ Provides controller methods. Controller methods are callback functions that get called at different stages
+ of the animation, computing or plotting of the visualization.
+
+ Implemented by:
+
+ All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
+
+ Syntax:
+
+ (start code js)
+
+ Options.Controller = {
+ onBeforeCompute: $.empty,
+ onAfterCompute: $.empty,
+ onCreateLabel: $.empty,
+ onPlaceLabel: $.empty,
+ onComplete: $.empty,
+ onBeforePlotLine:$.empty,
+ onAfterPlotLine: $.empty,
+ onBeforePlotNode:$.empty,
+ onAfterPlotNode: $.empty,
+ request: false
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz({
+ onBeforePlotNode: function(node) {
+ if(node.selected) {
+ node.setData('color', '#ffc');
+ } else {
+ node.removeData('color');
+ }
+ },
+ onBeforePlotLine: function(adj) {
+ if(adj.nodeFrom.selected && adj.nodeTo.selected) {
+ adj.setData('color', '#ffc');
+ } else {
+ adj.removeData('color');
+ }
+ },
+ onAfterCompute: function() {
+ alert("computed!");
+ }
+ });
+ (end code)
+
+ Parameters:
+
+ onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
+ onAfterCompute() - This method is triggered after all animations or computations ended.
+ onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
+ onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
+ onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
+ onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
+ onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
+ onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
+
+ *Used in <ST>, <TM.Base> and <Icicle> visualizations*
+
+ request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.
+
+ */
+Options.Controller = {
+ $extend: true,
+
+ onBeforeCompute: $.empty,
+ onAfterCompute: $.empty,
+ onCreateLabel: $.empty,
+ onPlaceLabel: $.empty,
+ onComplete: $.empty,
+ onBeforePlotLine:$.empty,
+ onAfterPlotLine: $.empty,
+ onBeforePlotNode:$.empty,
+ onAfterPlotNode: $.empty,
+ request: false
+};
+
+
+/*
+ * File: Extras.js
+ *
+ * Provides Extras such as Tips and Style Effects.
+ *
+ * Description:
+ *
+ * Provides the <Tips> and <NodeStyles> classes and functions.
+ *
+ */
+
+/*
+ * Manager for mouse events (clicking and mouse moving).
+ *
+ * This class is used for registering objects implementing onClick
+ * and onMousemove methods. These methods are called when clicking or
+ * moving the mouse around the Canvas.
+ * For now, <Tips> and <NodeStyles> are classes implementing these methods.
+ *
+ */
+var ExtrasInitializer = {
+ initialize: function(className, viz) {
+ this.viz = viz;
+ this.canvas = viz.canvas;
+ this.config = viz.config[className];
+ this.nodeTypes = viz.fx.nodeTypes;
+ var type = this.config.type;
+ this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
+ this.labelContainer = this.dom && viz.labels.getLabelContainer();
+ this.isEnabled() && this.initializePost();
+ },
+ initializePost: $.empty,
+ setAsProperty: $.lambda(false),
+ isEnabled: function() {
+ return this.config.enable;
+ },
+ isLabel: function(e, win) {
+ e = $.event.get(e, win);
+ var labelContainer = this.labelContainer,
+ target = e.target || e.srcElement;
+ if(target && target.parentNode == labelContainer)
+ return target;
+ return false;
+ }
+};
+
+var EventsInterface = {
+ onMouseUp: $.empty,
+ onMouseDown: $.empty,
+ onMouseMove: $.empty,
+ onMouseOver: $.empty,
+ onMouseOut: $.empty,
+ onMouseWheel: $.empty,
+ onTouchStart: $.empty,
+ onTouchMove: $.empty,
+ onTouchEnd: $.empty,
+ onTouchCancel: $.empty
+};
+
+var MouseEventsManager = new Class({
+ initialize: function(viz) {
+ this.viz = viz;
+ this.canvas = viz.canvas;
+ this.node = false;
+ this.edge = false;
+ this.registeredObjects = [];
+ this.attachEvents();
+ },
+
+ attachEvents: function() {
+ var htmlCanvas = this.canvas.getElement(),
+ that = this;
+ htmlCanvas.oncontextmenu = $.lambda(false);
+ $.addEvents(htmlCanvas, {
+ 'mouseup': function(e, win) {
+ var event = $.event.get(e, win);
+ that.handleEvent('MouseUp', e, win,
+ that.makeEventObject(e, win),
+ $.event.isRightClick(event));
+ },
+ 'mousedown': function(e, win) {
+ var event = $.event.get(e, win);
+ that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
+ $.event.isRightClick(event));
+ },
+ 'mousemove': function(e, win) {
+ that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
+ },
+ 'mouseover': function(e, win) {
+ that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
+ },
+ 'mouseout': function(e, win) {
+ that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
+ },
+ 'touchstart': function(e, win) {
+ that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
+ },
+ 'touchmove': function(e, win) {
+ that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
+ },
+ 'touchend': function(e, win) {
+ that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
+ }
+ });
+ //attach mousewheel event
+ var handleMouseWheel = function(e, win) {
+ var event = $.event.get(e, win);
+ var wheel = $.event.getWheel(event);
+ that.handleEvent('MouseWheel', e, win, wheel);
+ };
+ //TODO(nico): this is a horrible check for non-gecko browsers!
+ if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
+ $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
+ } else {
+ htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
+ }
+ },
+
+ register: function(obj) {
+ this.registeredObjects.push(obj);
+ },
+
+ handleEvent: function() {
+ var args = Array.prototype.slice.call(arguments),
+ type = args.shift();
+ for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
+ regs[i]['on' + type].apply(regs[i], args);
+ }
+ },
+
+ makeEventObject: function(e, win) {
+ var that = this,
+ graph = this.viz.graph,
+ fx = this.viz.fx,
+ ntypes = fx.nodeTypes,
+ etypes = fx.edgeTypes;
+ return {
+ pos: false,
+ node: false,
+ edge: false,
+ contains: false,
+ getNodeCalled: false,
+ getEdgeCalled: false,
+ getPos: function() {
+ //TODO(nico): check why this can't be cache anymore when using edge detection
+ //if(this.pos) return this.pos;
+ var canvas = that.viz.canvas,
+ s = canvas.getSize(),
+ p = canvas.getPos(),
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ pos = $.event.getPos(e, win);
+ this.pos = {
+ x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
+ y: (pos.y - p.y - s.height/2 - oy) * 1/sy
+ };
+ return this.pos;
+ },
+ getNode: function() {
+ if(this.getNodeCalled) return this.node;
+ this.getNodeCalled = true;
+ for(var id in graph.nodes) {
+ var n = graph.nodes[id],
+ geom = n && ntypes[n.getData('type')],
+ contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
+ if(contains) {
+ this.contains = contains;
+ return that.node = this.node = n;
+ }
+ }
+ return that.node = this.node = false;
+ },
+ getEdge: function() {
+ if(this.getEdgeCalled) return this.edge;
+ this.getEdgeCalled = true;
+ var hashset = {};
+ for(var id in graph.edges) {
+ var edgeFrom = graph.edges[id];
+ hashset[id] = true;
+ for(var edgeId in edgeFrom) {
+ if(edgeId in hashset) continue;
+ var e = edgeFrom[edgeId],
+ geom = e && etypes[e.getData('type')],
+ contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
+ if(contains) {
+ this.contains = contains;
+ return that.edge = this.edge = e;
+ }
+ }
+ }
+ return that.edge = this.edge = false;
+ },
+ getContains: function() {
+ if(this.getNodeCalled) return this.contains;
+ this.getNode();
+ return this.contains;
+ }
+ };
+ }
+});
+
+/*
+ * Provides the initialization function for <NodeStyles> and <Tips> implemented
+ * by all main visualizations.
+ *
+ */
+var Extras = {
+ initializeExtras: function() {
+ var mem = new MouseEventsManager(this), that = this;
+ $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
+ var obj = new Extras.Classes[k](k, that);
+ if(obj.isEnabled()) {
+ mem.register(obj);
+ }
+ if(obj.setAsProperty()) {
+ that[k.toLowerCase()] = obj;
+ }
+ });
+ }
+};
+
+Extras.Classes = {};
+/*
+ Class: Events
+
+ This class defines an Event API to be accessed by the user.
+ The methods implemented are the ones defined in the <Options.Events> object.
+*/
+
+Extras.Classes.Events = new Class({
+ Implements: [ExtrasInitializer, EventsInterface],
+
+ initializePost: function() {
+ this.fx = this.viz.fx;
+ this.ntypes = this.viz.fx.nodeTypes;
+ this.etypes = this.viz.fx.edgeTypes;
+
+ this.hovered = false;
+ this.pressed = false;
+ this.touched = false;
+
+ this.touchMoved = false;
+ this.moved = false;
+
+ },
+
+ setAsProperty: $.lambda(true),
+
+ onMouseUp: function(e, win, event, isRightClick) {
+ var evt = $.event.get(e, win);
+ if(!this.moved) {
+ if(isRightClick) {
+ this.config.onRightClick(this.hovered, event, evt);
+ } else {
+ this.config.onClick(this.pressed, event, evt);
+ }
+ }
+ if(this.pressed) {
+ if(this.moved) {
+ this.config.onDragEnd(this.pressed, event, evt);
+ } else {
+ this.config.onDragCancel(this.pressed, event, evt);
+ }
+ this.pressed = this.moved = false;
+ }
+ },
+
+ onMouseOut: function(e, win, event) {
+ //mouseout a label
+ var evt = $.event.get(e, win), label;
+ if(this.dom && (label = this.isLabel(e, win))) {
+ this.config.onMouseLeave(this.viz.graph.getNode(label.id),
+ event, evt);
+ this.hovered = false;
+ return;
+ }
+ //mouseout canvas
+ var rt = evt.relatedTarget,
+ canvasWidget = this.canvas.getElement();
+ while(rt && rt.parentNode) {
+ if(canvasWidget == rt.parentNode) return;
+ rt = rt.parentNode;
+ }
+ if(this.hovered) {
+ this.config.onMouseLeave(this.hovered,
+ event, evt);
+ this.hovered = false;
+ }
+ },
+
+ onMouseOver: function(e, win, event) {
+ //mouseover a label
+ var evt = $.event.get(e, win), label;
+ if(this.dom && (label = this.isLabel(e, win))) {
+ this.hovered = this.viz.graph.getNode(label.id);
+ this.config.onMouseEnter(this.hovered,
+ event, evt);
+ }
+ },
+
+ onMouseMove: function(e, win, event) {
+ var label, evt = $.event.get(e, win);
+ if(this.pressed) {
+ this.moved = true;
+ this.config.onDragMove(this.pressed, event, evt);
+ return;
+ }
+ if(this.dom) {
+ this.config.onMouseMove(this.hovered,
+ event, evt);
+ } else {
+ if(this.hovered) {
+ var hn = this.hovered;
+ var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
+ var contains = geom && geom.contains
+ && geom.contains.call(this.fx, hn, event.getPos());
+ if(contains) {
+ this.config.onMouseMove(hn, event, evt);
+ return;
+ } else {
+ this.config.onMouseLeave(hn, event, evt);
+ this.hovered = false;
+ }
+ }
+ if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
+ this.config.onMouseEnter(this.hovered, event, evt);
+ } else {
+ this.config.onMouseMove(false, event, evt);
+ }
+ }
+ },
+
+ onMouseWheel: function(e, win, delta) {
+ this.config.onMouseWheel(delta, $.event.get(e, win));
+ },
+
+ onMouseDown: function(e, win, event) {
+ var evt = $.event.get(e, win);
+ this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
+ this.config.onDragStart(this.pressed, event, evt);
+ },
+
+ onTouchStart: function(e, win, event) {
+ var evt = $.event.get(e, win);
+ this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
+ this.config.onTouchStart(this.touched, event, evt);
+ },
+
+ onTouchMove: function(e, win, event) {
+ var evt = $.event.get(e, win);
+ if(this.touched) {
+ this.touchMoved = true;
+ this.config.onTouchMove(this.touched, event, evt);
+ }
+ },
+
+ onTouchEnd: function(e, win, event) {
+ var evt = $.event.get(e, win);
+ if(this.touched) {
+ if(this.touchMoved) {
+ this.config.onTouchEnd(this.touched, event, evt);
+ } else {
+ this.config.onTouchCancel(this.touched, event, evt);
+ }
+ this.touched = this.touchMoved = false;
+ }
+ }
+});
+
+/*
+ Class: Tips
+
+ A class containing tip related functions. This class is used internally.
+
+ Used by:
+
+ <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
+
+ See also:
+
+ <Options.Tips>
+*/
+
+Extras.Classes.Tips = new Class({
+ Implements: [ExtrasInitializer, EventsInterface],
+
+ initializePost: function() {
+ //add DOM tooltip
+ if(document.body) {
+ var tip = $('_tooltip') || document.createElement('div');
+ tip.id = '_tooltip';
+ tip.className = 'tip';
+ $.extend(tip.style, {
+ position: 'absolute',
+ display: 'none',
+ zIndex: 13000
+ });
+ document.body.appendChild(tip);
+ this.tip = tip;
+ this.node = false;
+ }
+ },
+
+ setAsProperty: $.lambda(true),
+
+ onMouseOut: function(e, win) {
+ //mouseout a label
+ if(this.dom && this.isLabel(e, win)) {
+ this.hide(true);
+ return;
+ }
+ //mouseout canvas
+ var rt = e.relatedTarget,
+ canvasWidget = this.canvas.getElement();
+ while(rt && rt.parentNode) {
+ if(canvasWidget == rt.parentNode) return;
+ rt = rt.parentNode;
+ }
+ this.hide(false);
+ },
+
+ onMouseOver: function(e, win) {
+ //mouseover a label
+ var label;
+ if(this.dom && (label = this.isLabel(e, win))) {
+ this.node = this.viz.graph.getNode(label.id);
+ this.config.onShow(this.tip, this.node, label);
+ }
+ },
+
+ onMouseMove: function(e, win, opt) {
+ if(this.dom && this.isLabel(e, win)) {
+ this.setTooltipPosition($.event.getPos(e, win));
+ }
+ if(!this.dom) {
+ var node = opt.getNode();
+ if(!node) {
+ this.hide(true);
+ return;
+ }
+ if(this.config.force || !this.node || this.node.id != node.id) {
+ this.node = node;
+ this.config.onShow(this.tip, node, opt.getContains());
+ }
+ this.setTooltipPosition($.event.getPos(e, win));
+ }
+ },
+
+ setTooltipPosition: function(pos) {
+ var tip = this.tip,
+ style = tip.style,
+ cont = this.config;
+ style.display = '';
+ //get window dimensions
+ var win = {
+ 'height': document.body.clientHeight,
+ 'width': document.body.clientWidth
+ };
+ //get tooltip dimensions
+ var obj = {
+ 'width': tip.offsetWidth,
+ 'height': tip.offsetHeight
+ };
+ //set tooltip position
+ var x = cont.offsetX, y = cont.offsetY;
+ style.top = ((pos.y + y + obj.height > win.height)?
+ (pos.y - obj.height - y) : pos.y + y) + 'px';
+ style.left = ((pos.x + obj.width + x > win.width)?
+ (pos.x - obj.width - x) : pos.x + x) + 'px';
+ },
+
+ hide: function(triggerCallback) {
+ this.tip.style.display = 'none';
+ triggerCallback && this.config.onHide();
+ }
+});
+
+/*
+ Class: NodeStyles
+
+ Change node styles when clicking or hovering a node. This class is used internally.
+
+ Used by:
+
+ <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
+
+ See also:
+
+ <Options.NodeStyles>
+*/
+Extras.Classes.NodeStyles = new Class({
+ Implements: [ExtrasInitializer, EventsInterface],
+
+ initializePost: function() {
+ this.fx = this.viz.fx;
+ this.types = this.viz.fx.nodeTypes;
+ this.nStyles = this.config;
+ this.nodeStylesOnHover = this.nStyles.stylesHover;
+ this.nodeStylesOnClick = this.nStyles.stylesClick;
+ this.hoveredNode = false;
+ this.fx.nodeFxAnimation = new Animation();
+
+ this.down = false;
+ this.move = false;
+ },
+
+ onMouseOut: function(e, win) {
+ this.down = this.move = false;
+ if(!this.hoveredNode) return;
+ //mouseout a label
+ if(this.dom && this.isLabel(e, win)) {
+ this.toggleStylesOnHover(this.hoveredNode, false);
+ }
+ //mouseout canvas
+ var rt = e.relatedTarget,
+ canvasWidget = this.canvas.getElement();
+ while(rt && rt.parentNode) {
+ if(canvasWidget == rt.parentNode) return;
+ rt = rt.parentNode;
+ }
+ this.toggleStylesOnHover(this.hoveredNode, false);
+ this.hoveredNode = false;
+ },
+
+ onMouseOver: function(e, win) {
+ //mouseover a label
+ var label;
+ if(this.dom && (label = this.isLabel(e, win))) {
+ var node = this.viz.graph.getNode(label.id);
+ if(node.selected) return;
+ this.hoveredNode = node;
+ this.toggleStylesOnHover(this.hoveredNode, true);
+ }
+ },
+
+ onMouseDown: function(e, win, event, isRightClick) {
+ if(isRightClick) return;
+ var label;
+ if(this.dom && (label = this.isLabel(e, win))) {
+ this.down = this.viz.graph.getNode(label.id);
+ } else if(!this.dom) {
+ this.down = event.getNode();
+ }
+ this.move = false;
+ },
+
+ onMouseUp: function(e, win, event, isRightClick) {
+ if(isRightClick) return;
+ if(!this.move) {
+ this.onClick(event.getNode());
+ }
+ this.down = this.move = false;
+ },
+
+ getRestoredStyles: function(node, type) {
+ var restoredStyles = {},
+ nStyles = this['nodeStylesOn' + type];
+ for(var prop in nStyles) {
+ restoredStyles[prop] = node.styles['$' + prop];
+ }
+ return restoredStyles;
+ },
+
+ toggleStylesOnHover: function(node, set) {
+ if(this.nodeStylesOnHover) {
+ this.toggleStylesOn('Hover', node, set);
+ }
+ },
+
+ toggleStylesOnClick: function(node, set) {
+ if(this.nodeStylesOnClick) {
+ this.toggleStylesOn('Click', node, set);
+ }
+ },
+
+ toggleStylesOn: function(type, node, set) {
+ var viz = this.viz;
+ var nStyles = this.nStyles;
+ if(set) {
+ var that = this;
+ if(!node.styles) {
+ node.styles = $.merge(node.data, {});
+ }
+ for(var s in this['nodeStylesOn' + type]) {
+ var $s = '$' + s;
+ if(!($s in node.styles)) {
+ node.styles[$s] = node.getData(s);
+ }
+ }
+ viz.fx.nodeFx($.extend({
+ 'elements': {
+ 'id': node.id,
+ 'properties': that['nodeStylesOn' + type]
+ },
+ transition: Trans.Quart.easeOut,
+ duration:300,
+ fps:40
+ }, this.config));
+ } else {
+ var restoredStyles = this.getRestoredStyles(node, type);
+ viz.fx.nodeFx($.extend({
+ 'elements': {
+ 'id': node.id,
+ 'properties': restoredStyles
+ },
+ transition: Trans.Quart.easeOut,
+ duration:300,
+ fps:40
+ }, this.config));
+ }
+ },
+
+ onClick: function(node) {
+ if(!node) return;
+ var nStyles = this.nodeStylesOnClick;
+ if(!nStyles) return;
+ //if the node is selected then unselect it
+ if(node.selected) {
+ this.toggleStylesOnClick(node, false);
+ delete node.selected;
+ } else {
+ //unselect all selected nodes...
+ this.viz.graph.eachNode(function(n) {
+ if(n.selected) {
+ for(var s in nStyles) {
+ n.setData(s, n.styles['$' + s], 'end');
+ }
+ delete n.selected;
+ }
+ });
+ //select clicked node
+ this.toggleStylesOnClick(node, true);
+ node.selected = true;
+ delete node.hovered;
+ this.hoveredNode = false;
+ }
+ },
+
+ onMouseMove: function(e, win, event) {
+ //if mouse button is down and moving set move=true
+ if(this.down) this.move = true;
+ //already handled by mouseover/out
+ if(this.dom && this.isLabel(e, win)) return;
+ var nStyles = this.nodeStylesOnHover;
+ if(!nStyles) return;
+
+ if(!this.dom) {
+ if(this.hoveredNode) {
+ var geom = this.types[this.hoveredNode.getData('type')];
+ var contains = geom && geom.contains && geom.contains.call(this.fx,
+ this.hoveredNode, event.getPos());
+ if(contains) return;
+ }
+ var node = event.getNode();
+ //if no node is being hovered then just exit
+ if(!this.hoveredNode && !node) return;
+ //if the node is hovered then exit
+ if(node.hovered) return;
+ //select hovered node
+ if(node && !node.selected) {
+ //check if an animation is running and exit it
+ this.fx.nodeFxAnimation.stopTimer();
+ //unselect all hovered nodes...
+ this.viz.graph.eachNode(function(n) {
+ if(n.hovered && !n.selected) {
+ for(var s in nStyles) {
+ n.setData(s, n.styles['$' + s], 'end');
+ }
+ delete n.hovered;
+ }
+ });
+ //select hovered node
+ node.hovered = true;
+ this.hoveredNode = node;
+ this.toggleStylesOnHover(node, true);
+ } else if(this.hoveredNode && !this.hoveredNode.selected) {
+ //check if an animation is running and exit it
+ this.fx.nodeFxAnimation.stopTimer();
+ //unselect hovered node
+ this.toggleStylesOnHover(this.hoveredNode, false);
+ delete this.hoveredNode.hovered;
+ this.hoveredNode = false;
+ }
+ }
+ }
+});
+
+Extras.Classes.Navigation = new Class({
+ Implements: [ExtrasInitializer, EventsInterface],
+
+ initializePost: function() {
+ this.pos = false;
+ this.pressed = false;
+ },
+
+ onMouseWheel: function(e, win, scroll) {
+ if(!this.config.zooming) return;
+ $.event.stop($.event.get(e, win));
+ var val = this.config.zooming / 1000,
+ ans = 1 + scroll * val;
+ this.canvas.scale(ans, ans);
+ },
+
+ onMouseDown: function(e, win, eventInfo) {
+ if(!this.config.panning) return;
+ if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
+ this.pressed = true;
+ this.pos = eventInfo.getPos();
+ var canvas = this.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY;
+ this.pos.x *= sx;
+ this.pos.x += ox;
+ this.pos.y *= sy;
+ this.pos.y += oy;
+ },
+
+ onMouseMove: function(e, win, eventInfo) {
+ if(!this.config.panning) return;
+ if(!this.pressed) return;
+ if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
+ var thispos = this.pos,
+ currentPos = eventInfo.getPos(),
+ canvas = this.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY;
+ currentPos.x *= sx;
+ currentPos.y *= sy;
+ currentPos.x += ox;
+ currentPos.y += oy;
+ var x = currentPos.x - thispos.x,
+ y = currentPos.y - thispos.y;
+ this.pos = currentPos;
+ this.canvas.translate(x * 1/sx, y * 1/sy);
+ },
+
+ onMouseUp: function(e, win, eventInfo, isRightClick) {
+ if(!this.config.panning) return;
+ this.pressed = false;
+ }
+});
+
+
+/*
+ * File: Canvas.js
+ *
+ */
+
+/*
+ Class: Canvas
+
+ A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
+ know more about <Canvas> options take a look at <Options.Canvas>.
+
+ A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
+ across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
+
+ Example:
+
+ Suppose we have this HTML
+
+ (start code xml)
+ <div id="infovis"></div>
+ (end code)
+
+ Now we create a new Visualization
+
+ (start code js)
+ var viz = new $jit.Viz({
+ //Where to inject the canvas. Any div container will do.
+ 'injectInto':'infovis',
+ //width and height for canvas.
+ //Default's to the container offsetWidth and Height.
+ 'width': 900,
+ 'height':500
+ });
+ (end code)
+
+ The generated HTML will look like this
+
+ (start code xml)
+ <div id="infovis">
+ <div id="infovis-canvaswidget" style="position:relative;">
+ <canvas id="infovis-canvas" width=900 height=500
+ style="position:absolute; top:0; left:0; width:900px; height:500px;" />
+ <div id="infovis-label"
+ style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
+ </div>
+ </div>
+ </div>
+ (end code)
+
+ As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
+ of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
+ */
+
+var Canvas;
+(function() {
+ //check for native canvas support
+ var canvasType = typeof HTMLCanvasElement,
+ supportsCanvas = (canvasType == 'object' || canvasType == 'function');
+ //create element function
+ function $E(tag, props) {
+ var elem = document.createElement(tag);
+ for(var p in props) {
+ if(typeof props[p] == "object") {
+ $.extend(elem[p], props[p]);
+ } else {
+ elem[p] = props[p];
+ }
+ }
+ if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
+ elem = G_vmlCanvasManager.initElement(document.body.appendChild(elem));
+ }
+ return elem;
+ }
+ //canvas widget which we will call just Canvas
+ $jit.Canvas = Canvas = new Class({
+ canvases: [],
+ pos: false,
+ element: false,
+ labelContainer: false,
+ translateOffsetX: 0,
+ translateOffsetY: 0,
+ scaleOffsetX: 1,
+ scaleOffsetY: 1,
+
+ initialize: function(viz, opt) {
+ this.viz = viz;
+ this.opt = opt;
+ var id = $.type(opt.injectInto) == 'string'?
+ opt.injectInto:opt.injectInto.id,
+ idLabel = id + "-label",
+ wrapper = $(id),
+ width = opt.width || wrapper.offsetWidth,
+ height = opt.height || wrapper.offsetHeight;
+ this.id = id;
+ //canvas options
+ var canvasOptions = {
+ injectInto: id,
+ width: width,
+ height: height
+ };
+ //create main wrapper
+ this.element = $E('div', {
+ 'id': id + '-canvaswidget',
+ 'style': {
+ 'position': 'relative',
+ 'width': width + 'px',
+ 'height': height + 'px'
+ }
+ });
+ //create label container
+ this.labelContainer = this.createLabelContainer(opt.Label.type,
+ idLabel, canvasOptions);
+ //create primary canvas
+ this.canvases.push(new Canvas.Base({
+ config: $.extend({idSuffix: '-canvas'}, canvasOptions),
+ plot: function(base) {
+ viz.fx.plot();
+ },
+ resize: function() {
+ viz.refresh();
+ }
+ }));
+ //create secondary canvas
+ var back = opt.background;
+ if(back) {
+ var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
+ this.canvases.push(new Canvas.Base(backCanvas));
+ }
+ //insert canvases
+ var len = this.canvases.length;
+ while(len--) {
+ this.element.appendChild(this.canvases[len].canvas);
+ if(len > 0) {
+ this.canvases[len].plot();
+ }
+ }
+ this.element.appendChild(this.labelContainer);
+ wrapper.appendChild(this.element);
+ //Update canvas position when the page is scrolled.
+ var timer = null, that = this;
+ $.addEvent(window, 'scroll', function() {
+ clearTimeout(timer);
+ timer = setTimeout(function() {
+ that.getPos(true); //update canvas position
+ }, 500);
+ });
+ },
+ /*
+ Method: getCtx
+
+ Returns the main canvas context object
+
+ Example:
+
+ (start code js)
+ var ctx = canvas.getCtx();
+ //Now I can use the native canvas context
+ //and for example change some canvas styles
+ ctx.globalAlpha = 1;
+ (end code)
+ */
+ getCtx: function(i) {
+ return this.canvases[i || 0].getCtx();
+ },
+ /*
+ Method: getConfig
+
+ Returns the current Configuration for this Canvas Widget.
+
+ Example:
+
+ (start code js)
+ var config = canvas.getConfig();
+ (end code)
+ */
+ getConfig: function() {
+ return this.opt;
+ },
+ /*
+ Method: getElement
+
+ Returns the main Canvas DOM wrapper
+
+ Example:
+
+ (start code js)
+ var wrapper = canvas.getElement();
+ //Returns <div id="infovis-canvaswidget" ... >...</div> as element
+ (end code)
+ */
+ getElement: function() {
+ return this.element;
+ },
+ /*
+ Method: getSize
+
+ Returns canvas dimensions.
+
+ Returns:
+
+ An object with *width* and *height* properties.
+
+ Example:
+ (start code js)
+ canvas.getSize(); //returns { width: 900, height: 500 }
+ (end code)
+ */
+ getSize: function(i) {
+ return this.canvases[i || 0].getSize();
+ },
+ /*
+ Method: resize
+
+ Resizes the canvas.
+
+ Parameters:
+
+ width - New canvas width.
+ height - New canvas height.
+
+ Example:
+
+ (start code js)
+ canvas.resize(width, height);
+ (end code)
+
+ */
+ resize: function(width, height) {
+ this.getPos(true);
+ this.translateOffsetX = this.translateOffsetY = 0;
+ this.scaleOffsetX = this.scaleOffsetY = 1;
+ for(var i=0, l=this.canvases.length; i<l; i++) {
+ this.canvases[i].resize(width, height);
+ }
+ var style = this.element.style;
+ style.width = width + 'px';
+ style.height = height + 'px';
+ if(this.labelContainer)
+ this.labelContainer.style.width = width + 'px';
+ },
+ /*
+ Method: translate
+
+ Applies a translation to the canvas.
+
+ Parameters:
+
+ x - (number) x offset.
+ y - (number) y offset.
+ disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
+
+ Example:
+
+ (start code js)
+ canvas.translate(30, 30);
+ (end code)
+
+ */
+ translate: function(x, y, disablePlot) {
+ this.translateOffsetX += x*this.scaleOffsetX;
+ this.translateOffsetY += y*this.scaleOffsetY;
+ for(var i=0, l=this.canvases.length; i<l; i++) {
+ this.canvases[i].translate(x, y, disablePlot);
+ }
+ },
+ /*
+ Method: scale
+
+ Scales the canvas.
+
+ Parameters:
+
+ x - (number) scale value.
+ y - (number) scale value.
+ disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
+
+ Example:
+
+ (start code js)
+ canvas.scale(0.5, 0.5);
+ (end code)
+
+ */
+ scale: function(x, y, disablePlot) {
+ var px = this.scaleOffsetX * x,
+ py = this.scaleOffsetY * y;
+ var dx = this.translateOffsetX * (x -1) / px,
+ dy = this.translateOffsetY * (y -1) / py;
+ this.scaleOffsetX = px;
+ this.scaleOffsetY = py;
+ for(var i=0, l=this.canvases.length; i<l; i++) {
+ this.canvases[i].scale(x, y, true);
+ }
+ this.translate(dx, dy, false);
+ },
+ /*
+ Method: getPos
+
+ Returns the canvas position as an *x, y* object.
+
+ Parameters:
+
+ force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
+
+ Returns:
+
+ An object with *x* and *y* properties.
+
+ Example:
+ (start code js)
+ canvas.getPos(true); //returns { x: 900, y: 500 }
+ (end code)
+ */
+ getPos: function(force){
+ if(force || !this.pos) {
+ return this.pos = $.getPos(this.getElement());
+ }
+ return this.pos;
+ },
+ /*
+ Method: clear
+
+ Clears the canvas.
+ */
+ clear: function(i){
+ this.canvases[i||0].clear();
+ },
+
+ path: function(type, action){
+ var ctx = this.canvases[0].getCtx();
+ ctx.beginPath();
+ action(ctx);
+ ctx[type]();
+ ctx.closePath();
+ },
+
+ createLabelContainer: function(type, idLabel, dim) {
+ var NS = 'http://www.w3.org/2000/svg';
+ if(type == 'HTML' || type == 'Native') {
+ return $E('div', {
+ 'id': idLabel,
+ 'style': {
+ 'overflow': 'visible',
+ 'position': 'absolute',
+ 'top': 0,
+ 'left': 0,
+ 'width': dim.width + 'px',
+ 'height': 0
+ }
+ });
+ } else if(type == 'SVG') {
+ var svgContainer = document.createElementNS(NS, 'svg:svg');
+ svgContainer.setAttribute("width", dim.width);
+ svgContainer.setAttribute('height', dim.height);
+ var style = svgContainer.style;
+ style.position = 'absolute';
+ style.left = style.top = '0px';
+ var labelContainer = document.createElementNS(NS, 'svg:g');
+ labelContainer.setAttribute('width', dim.width);
+ labelContainer.setAttribute('height', dim.height);
+ labelContainer.setAttribute('x', 0);
+ labelContainer.setAttribute('y', 0);
+ labelContainer.setAttribute('id', idLabel);
+ svgContainer.appendChild(labelContainer);
+ return svgContainer;
+ }
+ }
+ });
+ //base canvas wrapper
+ Canvas.Base = new Class({
+ translateOffsetX: 0,
+ translateOffsetY: 0,
+ scaleOffsetX: 1,
+ scaleOffsetY: 1,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ this.opt = viz.config;
+ this.size = false;
+ this.createCanvas();
+ this.translateToCenter();
+ },
+ createCanvas: function() {
+ var opt = this.opt,
+ width = opt.width,
+ height = opt.height;
+ this.canvas = $E('canvas', {
+ 'id': opt.injectInto + opt.idSuffix,
+ 'width': width,
+ 'height': height,
+ 'style': {
+ 'position': 'absolute',
+ 'top': 0,
+ 'left': 0,
+ 'width': width + 'px',
+ 'height': height + 'px'
+ }
+ });
+ },
+ getCtx: function() {
+ if(!this.ctx)
+ return this.ctx = this.canvas.getContext('2d');
+ return this.ctx;
+ },
+ getSize: function() {
+ if(this.size) return this.size;
+ var canvas = this.canvas;
+ return this.size = {
+ width: canvas.width,
+ height: canvas.height
+ };
+ },
+ translateToCenter: function(ps) {
+ var size = this.getSize(),
+ width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
+ height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
+ var ctx = this.getCtx();
+ ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
+ ctx.translate(width/2, height/2);
+ },
+ resize: function(width, height) {
+ var size = this.getSize(),
+ canvas = this.canvas,
+ styles = canvas.style;
+ this.size = false;
+ canvas.width = width;
+ canvas.height = height;
+ styles.width = width + "px";
+ styles.height = height + "px";
+ //small ExCanvas fix
+ if(!supportsCanvas) {
+ this.translateToCenter(size);
+ } else {
+ this.translateToCenter();
+ }
+ this.translateOffsetX =
+ this.translateOffsetY = 0;
+ this.scaleOffsetX =
+ this.scaleOffsetY = 1;
+ this.clear();
+ this.viz.resize(width, height, this);
+ },
+ translate: function(x, y, disablePlot) {
+ var sx = this.scaleOffsetX,
+ sy = this.scaleOffsetY;
+ this.translateOffsetX += x*sx;
+ this.translateOffsetY += y*sy;
+ this.getCtx().translate(x, y);
+ !disablePlot && this.plot();
+ },
+ scale: function(x, y, disablePlot) {
+ this.scaleOffsetX *= x;
+ this.scaleOffsetY *= y;
+ this.getCtx().scale(x, y);
+ !disablePlot && this.plot();
+ },
+ clear: function(){
+ var size = this.getSize(),
+ ox = this.translateOffsetX,
+ oy = this.translateOffsetY,
+ sx = this.scaleOffsetX,
+ sy = this.scaleOffsetY;
+ this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
+ (-size.height / 2 - oy) * 1/sy,
+ size.width * 1/sx, size.height * 1/sy);
+ },
+ plot: function() {
+ this.clear();
+ this.viz.plot(this);
+ }
+ });
+ //background canvases
+ //TODO(nico): document this!
+ Canvas.Background = {};
+ Canvas.Background.Circles = new Class({
+ initialize: function(viz, options) {
+ this.viz = viz;
+ this.config = $.merge({
+ idSuffix: '-bkcanvas',
+ levelDistance: 100,
+ numberOfCircles: 6,
+ CanvasStyles: {},
+ offset: 0
+ }, options);
+ },
+ resize: function(width, height, base) {
+ this.plot(base);
+ },
+ plot: function(base) {
+ var canvas = base.canvas,
+ ctx = base.getCtx(),
+ conf = this.config,
+ styles = conf.CanvasStyles;
+ //set canvas styles
+ for(var s in styles) ctx[s] = styles[s];
+ var n = conf.numberOfCircles,
+ rho = conf.levelDistance;
+ for(var i=1; i<=n; i++) {
+ ctx.beginPath();
+ ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ //TODO(nico): print labels too!
+ }
+ });
+})();
+
+
+/*
+ * File: Polar.js
+ *
+ * Defines the <Polar> class.
+ *
+ * Description:
+ *
+ * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+ *
+ * See also:
+ *
+ * <http://en.wikipedia.org/wiki/Polar_coordinates>
+ *
+*/
+
+/*
+ Class: Polar
+
+ A multi purpose polar representation.
+
+ Description:
+
+ The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+
+ See also:
+
+ <http://en.wikipedia.org/wiki/Polar_coordinates>
+
+ Parameters:
+
+ theta - An angle.
+ rho - The norm.
+*/
+
+var Polar = function(theta, rho) {
+ this.theta = theta;
+ this.rho = rho;
+};
+
+$jit.Polar = Polar;
+
+Polar.prototype = {
+ /*
+ Method: getc
+
+ Returns a complex number.
+
+ Parameters:
+
+ simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
+
+ Returns:
+
+ A complex number.
+ */
+ getc: function(simple) {
+ return this.toComplex(simple);
+ },
+
+ /*
+ Method: getp
+
+ Returns a <Polar> representation.
+
+ Returns:
+
+ A variable in polar coordinates.
+ */
+ getp: function() {
+ return this;
+ },
+
+
+ /*
+ Method: set
+
+ Sets a number.
+
+ Parameters:
+
+ v - A <Complex> or <Polar> instance.
+
+ */
+ set: function(v) {
+ v = v.getp();
+ this.theta = v.theta; this.rho = v.rho;
+ },
+
+ /*
+ Method: setc
+
+ Sets a <Complex> number.
+
+ Parameters:
+
+ x - A <Complex> number real part.
+ y - A <Complex> number imaginary part.
+
+ */
+ setc: function(x, y) {
+ this.rho = Math.sqrt(x * x + y * y);
+ this.theta = Math.atan2(y, x);
+ if(this.theta < 0) this.theta += Math.PI * 2;
+ },
+
+ /*
+ Method: setp
+
+ Sets a polar number.
+
+ Parameters:
+
+ theta - A <Polar> number angle property.
+ rho - A <Polar> number rho property.
+
+ */
+ setp: function(theta, rho) {
+ this.theta = theta;
+ this.rho = rho;
+ },
+
+ /*
+ Method: clone
+
+ Returns a copy of the current object.
+
+ Returns:
+
+ A copy of the real object.
+ */
+ clone: function() {
+ return new Polar(this.theta, this.rho);
+ },
+
+ /*
+ Method: toComplex
+
+ Translates from polar to cartesian coordinates and returns a new <Complex> instance.
+
+ Parameters:
+
+ simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
+
+ Returns:
+
+ A new <Complex> instance.
+ */
+ toComplex: function(simple) {
+ var x = Math.cos(this.theta) * this.rho;
+ var y = Math.sin(this.theta) * this.rho;
+ if(simple) return { 'x': x, 'y': y};
+ return new Complex(x, y);
+ },
+
+ /*
+ Method: add
+
+ Adds two <Polar> instances.
+
+ Parameters:
+
+ polar - A <Polar> number.
+
+ Returns:
+
+ A new Polar instance.
+ */
+ add: function(polar) {
+ return new Polar(this.theta + polar.theta, this.rho + polar.rho);
+ },
+
+ /*
+ Method: scale
+
+ Scales a polar norm.
+
+ Parameters:
+
+ number - A scale factor.
+
+ Returns:
+
+ A new Polar instance.
+ */
+ scale: function(number) {
+ return new Polar(this.theta, this.rho * number);
+ },
+
+ /*
+ Method: equals
+
+ Comparison method.
+
+ Returns *true* if the theta and rho properties are equal.
+
+ Parameters:
+
+ c - A <Polar> number.
+
+ Returns:
+
+ *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
+ */
+ equals: function(c) {
+ return this.theta == c.theta && this.rho == c.rho;
+ },
+
+ /*
+ Method: $add
+
+ Adds two <Polar> instances affecting the current object.
+
+ Paramters:
+
+ polar - A <Polar> instance.
+
+ Returns:
+
+ The changed object.
+ */
+ $add: function(polar) {
+ this.theta = this.theta + polar.theta; this.rho += polar.rho;
+ return this;
+ },
+
+ /*
+ Method: $madd
+
+ Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
+
+ Parameters:
+
+ polar - A <Polar> instance.
+
+ Returns:
+
+ The changed object.
+ */
+ $madd: function(polar) {
+ this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
+ return this;
+ },
+
+
+ /*
+ Method: $scale
+
+ Scales a polar instance affecting the object.
+
+ Parameters:
+
+ number - A scaling factor.
+
+ Returns:
+
+ The changed object.
+ */
+ $scale: function(number) {
+ this.rho *= number;
+ return this;
+ },
+
+ /*
+ Method: interpolate
+
+ Calculates a polar interpolation between two points at a given delta moment.
+
+ Parameters:
+
+ elem - A <Polar> instance.
+ delta - A delta factor ranging [0, 1].
+
+ Returns:
+
+ A new <Polar> instance representing an interpolation between _this_ and _elem_
+ */
+ interpolate: function(elem, delta) {
+ var pi = Math.PI, pi2 = pi * 2;
+ var ch = function(t) {
+ var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
+ return a;
+ };
+ var tt = this.theta, et = elem.theta;
+ var sum, diff = Math.abs(tt - et);
+ if(diff == pi) {
+ if(tt > et) {
+ sum = ch((et + ((tt - pi2) - et) * delta)) ;
+ } else {
+ sum = ch((et - pi2 + (tt - (et)) * delta));
+ }
+ } else if(diff >= pi) {
+ if(tt > et) {
+ sum = ch((et + ((tt - pi2) - et) * delta)) ;
+ } else {
+ sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
+ }
+ } else {
+ sum = ch((et + (tt - et) * delta)) ;
+ }
+ var r = (this.rho - elem.rho) * delta + elem.rho;
+ return {
+ 'theta': sum,
+ 'rho': r
+ };
+ }
+};
+
+
+var $P = function(a, b) { return new Polar(a, b); };
+
+Polar.KER = $P(0, 0);
+
+
+
+/*
+ * File: Complex.js
+ *
+ * Defines the <Complex> class.
+ *
+ * Description:
+ *
+ * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+ *
+ * See also:
+ *
+ * <http://en.wikipedia.org/wiki/Complex_number>
+ *
+*/
+
+/*
+ Class: Complex
+
+ A multi-purpose Complex Class with common methods.
+
+ Description:
+
+ The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
+
+ See also:
+
+ <http://en.wikipedia.org/wiki/Complex_number>
+
+ Parameters:
+
+ x - _optional_ A Complex number real part.
+ y - _optional_ A Complex number imaginary part.
+
+*/
+
+var Complex = function(x, y) {
+ this.x = x;
+ this.y = y;
+};
+
+$jit.Complex = Complex;
+
+Complex.prototype = {
+ /*
+ Method: getc
+
+ Returns a complex number.
+
+ Returns:
+
+ A complex number.
+ */
+ getc: function() {
+ return this;
+ },
+
+ /*
+ Method: getp
+
+ Returns a <Polar> representation of this number.
+
+ Parameters:
+
+ simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
+
+ Returns:
+
+ A variable in <Polar> coordinates.
+ */
+ getp: function(simple) {
+ return this.toPolar(simple);
+ },
+
+
+ /*
+ Method: set
+
+ Sets a number.
+
+ Parameters:
+
+ c - A <Complex> or <Polar> instance.
+
+ */
+ set: function(c) {
+ c = c.getc(true);
+ this.x = c.x;
+ this.y = c.y;
+ },
+
+ /*
+ Method: setc
+
+ Sets a complex number.
+
+ Parameters:
+
+ x - A <Complex> number Real part.
+ y - A <Complex> number Imaginary part.
+
+ */
+ setc: function(x, y) {
+ this.x = x;
+ this.y = y;
+ },
+
+ /*
+ Method: setp
+
+ Sets a polar number.
+
+ Parameters:
+
+ theta - A <Polar> number theta property.
+ rho - A <Polar> number rho property.
+
+ */
+ setp: function(theta, rho) {
+ this.x = Math.cos(theta) * rho;
+ this.y = Math.sin(theta) * rho;
+ },
+
+ /*
+ Method: clone
+
+ Returns a copy of the current object.
+
+ Returns:
+
+ A copy of the real object.
+ */
+ clone: function() {
+ return new Complex(this.x, this.y);
+ },
+
+ /*
+ Method: toPolar
+
+ Transforms cartesian to polar coordinates.
+
+ Parameters:
+
+ simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
+
+ Returns:
+
+ A new <Polar> instance.
+ */
+
+ toPolar: function(simple) {
+ var rho = this.norm();
+ var atan = Math.atan2(this.y, this.x);
+ if(atan < 0) atan += Math.PI * 2;
+ if(simple) return { 'theta': atan, 'rho': rho };
+ return new Polar(atan, rho);
+ },
+ /*
+ Method: norm
+
+ Calculates a <Complex> number norm.
+
+ Returns:
+
+ A real number representing the complex norm.
+ */
+ norm: function () {
+ return Math.sqrt(this.squaredNorm());
+ },
+
+ /*
+ Method: squaredNorm
+
+ Calculates a <Complex> number squared norm.
+
+ Returns:
+
+ A real number representing the complex squared norm.
+ */
+ squaredNorm: function () {
+ return this.x*this.x + this.y*this.y;
+ },
+
+ /*
+ Method: add
+
+ Returns the result of adding two complex numbers.
+
+ Does not alter the original object.
+
+ Parameters:
+
+ pos - A <Complex> instance.
+
+ Returns:
+
+ The result of adding two complex numbers.
+ */
+ add: function(pos) {
+ return new Complex(this.x + pos.x, this.y + pos.y);
+ },
+
+ /*
+ Method: prod
+
+ Returns the result of multiplying two <Complex> numbers.
+
+ Does not alter the original object.
+
+ Parameters:
+
+ pos - A <Complex> instance.
+
+ Returns:
+
+ The result of multiplying two complex numbers.
+ */
+ prod: function(pos) {
+ return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
+ },
+
+ /*
+ Method: conjugate
+
+ Returns the conjugate of this <Complex> number.
+
+ Does not alter the original object.
+
+ Returns:
+
+ The conjugate of this <Complex> number.
+ */
+ conjugate: function() {
+ return new Complex(this.x, -this.y);
+ },
+
+
+ /*
+ Method: scale
+
+ Returns the result of scaling a <Complex> instance.
+
+ Does not alter the original object.
+
+ Parameters:
+
+ factor - A scale factor.
+
+ Returns:
+
+ The result of scaling this complex to a factor.
+ */
+ scale: function(factor) {
+ return new Complex(this.x * factor, this.y * factor);
+ },
+
+ /*
+ Method: equals
+
+ Comparison method.
+
+ Returns *true* if both real and imaginary parts are equal.
+
+ Parameters:
+
+ c - A <Complex> instance.
+
+ Returns:
+
+ A boolean instance indicating if both <Complex> numbers are equal.
+ */
+ equals: function(c) {
+ return this.x == c.x && this.y == c.y;
+ },
+
+ /*
+ Method: $add
+
+ Returns the result of adding two <Complex> numbers.
+
+ Alters the original object.
+
+ Parameters:
+
+ pos - A <Complex> instance.
+
+ Returns:
+
+ The result of adding two complex numbers.
+ */
+ $add: function(pos) {
+ this.x += pos.x; this.y += pos.y;
+ return this;
+ },
+
+ /*
+ Method: $prod
+
+ Returns the result of multiplying two <Complex> numbers.
+
+ Alters the original object.
+
+ Parameters:
+
+ pos - A <Complex> instance.
+
+ Returns:
+
+ The result of multiplying two complex numbers.
+ */
+ $prod:function(pos) {
+ var x = this.x, y = this.y;
+ this.x = x*pos.x - y*pos.y;
+ this.y = y*pos.x + x*pos.y;
+ return this;
+ },
+
+ /*
+ Method: $conjugate
+
+ Returns the conjugate for this <Complex>.
+
+ Alters the original object.
+
+ Returns:
+
+ The conjugate for this complex.
+ */
+ $conjugate: function() {
+ this.y = -this.y;
+ return this;
+ },
+
+ /*
+ Method: $scale
+
+ Returns the result of scaling a <Complex> instance.
+
+ Alters the original object.
+
+ Parameters:
+
+ factor - A scale factor.
+
+ Returns:
+
+ The result of scaling this complex to a factor.
+ */
+ $scale: function(factor) {
+ this.x *= factor; this.y *= factor;
+ return this;
+ },
+
+ /*
+ Method: $div
+
+ Returns the division of two <Complex> numbers.
+
+ Alters the original object.
+
+ Parameters:
+
+ pos - A <Complex> number.
+
+ Returns:
+
+ The result of scaling this complex to a factor.
+ */
+ $div: function(pos) {
+ var x = this.x, y = this.y;
+ var sq = pos.squaredNorm();
+ this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
+ return this.$scale(1 / sq);
+ }
+};
+
+var $C = function(a, b) { return new Complex(a, b); };
+
+Complex.KER = $C(0, 0);
+
+
+
+/*
+ * File: Graph.js
+ *
+*/
+
+/*
+ Class: Graph
+
+ A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
+
+ An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
+
+ Example:
+
+ (start code js)
+ //create new visualization
+ var viz = new $jit.Viz(options);
+ //load JSON data
+ viz.loadJSON(json);
+ //access model
+ viz.graph; //<Graph> instance
+ (end code)
+
+ Implements:
+
+ The following <Graph.Util> methods are implemented in <Graph>
+
+ - <Graph.Util.getNode>
+ - <Graph.Util.eachNode>
+ - <Graph.Util.computeLevels>
+ - <Graph.Util.eachBFS>
+ - <Graph.Util.clean>
+ - <Graph.Util.getClosestNodeToPos>
+ - <Graph.Util.getClosestNodeToOrigin>
+
+*/
+
+$jit.Graph = new Class({
+
+ initialize: function(opt, Node, Edge, Label) {
+ var innerOptions = {
+ 'complex': false,
+ 'Node': {}
+ };
+ this.Node = Node;
+ this.Edge = Edge;
+ this.Label = Label;
+ this.opt = $.merge(innerOptions, opt || {});
+ this.nodes = {};
+ this.edges = {};
+
+ //add nodeList methods
+ var that = this;
+ this.nodeList = {};
+ for(var p in Accessors) {
+ that.nodeList[p] = (function(p) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ that.eachNode(function(n) {
+ n[p].apply(n, args);
+ });
+ };
+ })(p);
+ }
+
+ },
+
+/*
+ Method: getNode
+
+ Returns a <Graph.Node> by *id*.
+
+ Parameters:
+
+ id - (string) A <Graph.Node> id.
+
+ Example:
+
+ (start code js)
+ var node = graph.getNode('nodeId');
+ (end code)
+*/
+ getNode: function(id) {
+ if(this.hasNode(id)) return this.nodes[id];
+ return false;
+ },
+
+ /*
+ Method: getByName
+
+ Returns a <Graph.Node> by *name*.
+
+ Parameters:
+
+ name - (string) A <Graph.Node> name.
+
+ Example:
+
+ (start code js)
+ var node = graph.getByName('someName');
+ (end code)
+ */
+ getByName: function(name) {
+ for(var id in this.nodes) {
+ var n = this.nodes[id];
+ if(n.name == name) return n;
+ }
+ return false;
+ },
+
+/*
+ Method: getAdjacence
+
+ Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
+
+ Parameters:
+
+ id - (string) A <Graph.Node> id.
+ id2 - (string) A <Graph.Node> id.
+*/
+ getAdjacence: function (id, id2) {
+ if(id in this.edges) {
+ return this.edges[id][id2];
+ }
+ return false;
+ },
+
+ /*
+ Method: addNode
+
+ Adds a node.
+
+ Parameters:
+
+ obj - An object with the properties described below
+
+ id - (string) A node id
+ name - (string) A node's name
+ data - (object) A node's data hash
+
+ See also:
+ <Graph.Node>
+
+ */
+ addNode: function(obj) {
+ if(!this.nodes[obj.id]) {
+ var edges = this.edges[obj.id] = {};
+ this.nodes[obj.id] = new Graph.Node($.extend({
+ 'id': obj.id,
+ 'name': obj.name,
+ 'data': $.merge(obj.data || {}, {}),
+ 'adjacencies': edges
+ }, this.opt.Node),
+ this.opt.complex,
+ this.Node,
+ this.Edge,
+ this.Label);
+ }
+ return this.nodes[obj.id];
+ },
+
+ /*
+ Method: addAdjacence
+
+ Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
+
+ Parameters:
+
+ obj - (object) A <Graph.Node> object.
+ obj2 - (object) Another <Graph.Node> object.
+ data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
+
+ See also:
+
+ <Graph.Node>, <Graph.Adjacence>
+ */
+ addAdjacence: function (obj, obj2, data) {
+ if(!this.hasNode(obj.id)) { this.addNode(obj); }
+ if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
+ obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
+ if(!obj.adjacentTo(obj2)) {
+ var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
+ var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
+ adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
+ return adjsObj[obj2.id];
+ }
+ return this.edges[obj.id][obj2.id];
+ },
+
+ /*
+ Method: removeNode
+
+ Removes a <Graph.Node> matching the specified *id*.
+
+ Parameters:
+
+ id - (string) A node's id.
+
+ */
+ removeNode: function(id) {
+ if(this.hasNode(id)) {
+ delete this.nodes[id];
+ var adjs = this.edges[id];
+ for(var to in adjs) {
+ delete this.edges[to][id];
+ }
+ delete this.edges[id];
+ }
+ },
+
+/*
+ Method: removeAdjacence
+
+ Removes a <Graph.Adjacence> matching *id1* and *id2*.
+
+ Parameters:
+
+ id1 - (string) A <Graph.Node> id.
+ id2 - (string) A <Graph.Node> id.
+*/
+ removeAdjacence: function(id1, id2) {
+ delete this.edges[id1][id2];
+ delete this.edges[id2][id1];
+ },
+
+ /*
+ Method: hasNode
+
+ Returns a boolean indicating if the node belongs to the <Graph> or not.
+
+ Parameters:
+
+ id - (string) Node id.
+ */
+ hasNode: function(id) {
+ return id in this.nodes;
+ },
+
+ /*
+ Method: empty
+
+ Empties the Graph
+
+ */
+ empty: function() { this.nodes = {}; this.edges = {};}
+
+});
+
+var Graph = $jit.Graph;
+
+/*
+ Object: Accessors
+
+ Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
+
+ */
+var Accessors;
+
+(function () {
+ var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
+ var data;
+ type = type || 'current';
+ prefix = "$" + (prefix ? prefix + "-" : "");
+
+ if(type == 'current') {
+ data = this.data;
+ } else if(type == 'start') {
+ data = this.startData;
+ } else if(type == 'end') {
+ data = this.endData;
+ }
+
+ var dollar = prefix + prop;
+
+ if(force) {
+ return data[dollar];
+ }
+
+ if(!this.Config.overridable)
+ return prefixConfig[prop] || 0;
+
+ return (dollar in data) ?
+ data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
+ }
+
+ var setDataInternal = function(prefix, prop, value, type) {
+ type = type || 'current';
+ prefix = '$' + (prefix ? prefix + '-' : '');
+
+ var data;
+
+ if(type == 'current') {
+ data = this.data;
+ } else if(type == 'start') {
+ data = this.startData;
+ } else if(type == 'end') {
+ data = this.endData;
+ }
+
+ data[prefix + prop] = value;
+ }
+
+ var removeDataInternal = function(prefix, properties) {
+ prefix = '$' + (prefix ? prefix + '-' : '');
+ var that = this;
+ $.each(properties, function(prop) {
+ var pref = prefix + prop;
+ delete that.data[pref];
+ delete that.endData[pref];
+ delete that.startData[pref];
+ });
+ }
+
+ Accessors = {
+ /*
+ Method: getData
+
+ Returns the specified data value property.
+ This is useful for querying special/reserved <Graph.Node> data properties
+ (i.e dollar prefixed properties).
+
+ Parameters:
+
+ prop - (string) The name of the property. The dollar sign is not needed. For
+ example *getData(width)* will return *data.$width*.
+ type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
+ data properties also. These properties are used when making animations.
+ force - (boolean) Whether to obtain the true value of the property (equivalent to
+ *data.$prop*) or to check for *node.overridable = true* first.
+
+ Returns:
+
+ The value of the dollar prefixed property or the global Node/Edge property
+ value if *overridable=false*
+
+ Example:
+ (start code js)
+ node.getData('width'); //will return node.data.$width if Node.overridable=true;
+ (end code)
+ */
+ getData: function(prop, type, force) {
+ return getDataInternal.call(this, "", prop, type, force, this.Config);
+ },
+
+
+ /*
+ Method: setData
+
+ Sets the current data property with some specific value.
+ This method is only useful for reserved (dollar prefixed) properties.
+
+ Parameters:
+
+ prop - (string) The name of the property. The dollar sign is not necessary. For
+ example *setData(width)* will set *data.$width*.
+ value - (mixed) The value to store.
+ type - (string) The type of the data property to store. Default's "current" but
+ can also be "start" or "end".
+
+ Example:
+
+ (start code js)
+ node.setData('width', 30);
+ (end code)
+
+ If we were to make an animation of a node/edge width then we could do
+
+ (start code js)
+ var node = viz.getNode('nodeId');
+ //set start and end values
+ node.setData('width', 10, 'start');
+ node.setData('width', 30, 'end');
+ //will animate nodes width property
+ viz.fx.animate({
+ modes: ['node-property:width'],
+ duration: 1000
+ });
+ (end code)
+ */
+ setData: function(prop, value, type) {
+ setDataInternal.call(this, "", prop, value, type);
+ },
+
+ /*
+ Method: setDataset
+
+ Convenience method to set multiple data values at once.
+
+ Parameters:
+
+ types - (array|string) A set of 'current', 'end' or 'start' values.
+ obj - (object) A hash containing the names and values of the properties to be altered.
+
+ Example:
+ (start code js)
+ node.setDataset(['current', 'end'], {
+ 'width': [100, 5],
+ 'color': ['#fff', '#ccc']
+ });
+ //...or also
+ node.setDataset('end', {
+ 'width': 5,
+ 'color': '#ccc'
+ });
+ (end code)
+
+ See also:
+
+ <Accessors.setData>
+
+ */
+ setDataset: function(types, obj) {
+ types = $.splat(types);
+ for(var attr in obj) {
+ for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+ this.setData(attr, val[i], types[i]);
+ }
+ }
+ },
+
+ /*
+ Method: removeData
+
+ Remove data properties.
+
+ Parameters:
+
+ One or more property names as arguments. The dollar sign is not needed.
+
+ Example:
+ (start code js)
+ node.removeData('width'); //now the default width value is returned
+ (end code)
+ */
+ removeData: function() {
+ removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
+ },
+
+ /*
+ Method: getCanvasStyle
+
+ Returns the specified canvas style data value property. This is useful for
+ querying special/reserved <Graph.Node> canvas style data properties (i.e.
+ dollar prefixed properties that match with $canvas-<name of canvas style>).
+
+ Parameters:
+
+ prop - (string) The name of the property. The dollar sign is not needed. For
+ example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
+ type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
+ data properties also.
+
+ Example:
+ (start code js)
+ node.getCanvasStyle('shadowBlur');
+ (end code)
+
+ See also:
+
+ <Accessors.getData>
+ */
+ getCanvasStyle: function(prop, type, force) {
+ return getDataInternal.call(
+ this, 'canvas', prop, type, force, this.Config.CanvasStyles);
+ },
+
+ /*
+ Method: setCanvasStyle
+
+ Sets the canvas style data property with some specific value.
+ This method is only useful for reserved (dollar prefixed) properties.
+
+ Parameters:
+
+ prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+ value - (mixed) The value to set to the property.
+ type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+
+ Example:
+
+ (start code js)
+ node.setCanvasStyle('shadowBlur', 30);
+ (end code)
+
+ If we were to make an animation of a node/edge shadowBlur canvas style then we could do
+
+ (start code js)
+ var node = viz.getNode('nodeId');
+ //set start and end values
+ node.setCanvasStyle('shadowBlur', 10, 'start');
+ node.setCanvasStyle('shadowBlur', 30, 'end');
+ //will animate nodes canvas style property for nodes
+ viz.fx.animate({
+ modes: ['node-style:shadowBlur'],
+ duration: 1000
+ });
+ (end code)
+
+ See also:
+
+ <Accessors.setData>.
+ */
+ setCanvasStyle: function(prop, value, type) {
+ setDataInternal.call(this, 'canvas', prop, value, type);
+ },
+
+ /*
+ Method: setCanvasStyles
+
+ Convenience method to set multiple styles at once.
+
+ Parameters:
+
+ types - (array|string) A set of 'current', 'end' or 'start' values.
+ obj - (object) A hash containing the names and values of the properties to be altered.
+
+ See also:
+
+ <Accessors.setDataset>.
+ */
+ setCanvasStyles: function(types, obj) {
+ types = $.splat(types);
+ for(var attr in obj) {
+ for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+ this.setCanvasStyle(attr, val[i], types[i]);
+ }
+ }
+ },
+
+ /*
+ Method: removeCanvasStyle
+
+ Remove canvas style properties from data.
+
+ Parameters:
+
+ A variable number of canvas style strings.
+
+ See also:
+
+ <Accessors.removeData>.
+ */
+ removeCanvasStyle: function() {
+ removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
+ },
+
+ /*
+ Method: getLabelData
+
+ Returns the specified label data value property. This is useful for
+ querying special/reserved <Graph.Node> label options (i.e.
+ dollar prefixed properties that match with $label-<name of label style>).
+
+ Parameters:
+
+ prop - (string) The name of the property. The dollar sign prefix is not needed. For
+ example *getLabelData(size)* will return *data[$label-size]*.
+ type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
+ data properties also.
+
+ See also:
+
+ <Accessors.getData>.
+ */
+ getLabelData: function(prop, type, force) {
+ return getDataInternal.call(
+ this, 'label', prop, type, force, this.Label);
+ },
+
+ /*
+ Method: setLabelData
+
+ Sets the current label data with some specific value.
+ This method is only useful for reserved (dollar prefixed) properties.
+
+ Parameters:
+
+ prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
+ value - (mixed) The value to set to the property.
+ type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
+
+ Example:
+
+ (start code js)
+ node.setLabelData('size', 30);
+ (end code)
+
+ If we were to make an animation of a node label size then we could do
+
+ (start code js)
+ var node = viz.getNode('nodeId');
+ //set start and end values
+ node.setLabelData('size', 10, 'start');
+ node.setLabelData('size', 30, 'end');
+ //will animate nodes label size
+ viz.fx.animate({
+ modes: ['label-property:size'],
+ duration: 1000
+ });
+ (end code)
+
+ See also:
+
+ <Accessors.setData>.
+ */
+ setLabelData: function(prop, value, type) {
+ setDataInternal.call(this, 'label', prop, value, type);
+ },
+
+ /*
+ Method: setLabelDataset
+
+ Convenience function to set multiple label data at once.
+
+ Parameters:
+
+ types - (array|string) A set of 'current', 'end' or 'start' values.
+ obj - (object) A hash containing the names and values of the properties to be altered.
+
+ See also:
+
+ <Accessors.setDataset>.
+ */
+ setLabelDataset: function(types, obj) {
+ types = $.splat(types);
+ for(var attr in obj) {
+ for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
+ this.setLabelData(attr, val[i], types[i]);
+ }
+ }
+ },
+
+ /*
+ Method: removeLabelData
+
+ Remove label properties from data.
+
+ Parameters:
+
+ A variable number of label property strings.
+
+ See also:
+
+ <Accessors.removeData>.
+ */
+ removeLabelData: function() {
+ removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
+ }
+ };
+})();
+
+/*
+ Class: Graph.Node
+
+ A <Graph> node.
+
+ Implements:
+
+ <Accessors> methods.
+
+ The following <Graph.Util> methods are implemented by <Graph.Node>
+
+ - <Graph.Util.eachAdjacency>
+ - <Graph.Util.eachLevel>
+ - <Graph.Util.eachSubgraph>
+ - <Graph.Util.eachSubnode>
+ - <Graph.Util.anySubnode>
+ - <Graph.Util.getSubnodes>
+ - <Graph.Util.getParents>
+ - <Graph.Util.isDescendantOf>
+*/
+Graph.Node = new Class({
+
+ initialize: function(opt, complex, Node, Edge, Label) {
+ var innerOptions = {
+ 'id': '',
+ 'name': '',
+ 'data': {},
+ 'startData': {},
+ 'endData': {},
+ 'adjacencies': {},
+
+ 'selected': false,
+ 'drawn': false,
+ 'exist': false,
+
+ 'angleSpan': {
+ 'begin': 0,
+ 'end' : 0
+ },
+
+ 'pos': (complex && $C(0, 0)) || $P(0, 0),
+ 'startPos': (complex && $C(0, 0)) || $P(0, 0),
+ 'endPos': (complex && $C(0, 0)) || $P(0, 0)
+ };
+
+ $.extend(this, $.extend(innerOptions, opt));
+ this.Config = this.Node = Node;
+ this.Edge = Edge;
+ this.Label = Label;
+ },
+
+ /*
+ Method: adjacentTo
+
+ Indicates if the node is adjacent to the node specified by id
+
+ Parameters:
+
+ id - (string) A node id.
+
+ Example:
+ (start code js)
+ node.adjacentTo('nodeId') == true;
+ (end code)
+ */
+ adjacentTo: function(node) {
+ return node.id in this.adjacencies;
+ },
+
+ /*
+ Method: getAdjacency
+
+ Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
+
+ Parameters:
+
+ id - (string) A node id.
+ */
+ getAdjacency: function(id) {
+ return this.adjacencies[id];
+ },
+
+ /*
+ Method: getPos
+
+ Returns the position of the node.
+
+ Parameters:
+
+ type - (string) Default's *current*. Possible values are "start", "end" or "current".
+
+ Returns:
+
+ A <Complex> or <Polar> instance.
+
+ Example:
+ (start code js)
+ var pos = node.getPos('end');
+ (end code)
+ */
+ getPos: function(type) {
+ type = type || "current";
+ if(type == "current") {
+ return this.pos;
+ } else if(type == "end") {
+ return this.endPos;
+ } else if(type == "start") {
+ return this.startPos;
+ }
+ },
+ /*
+ Method: setPos
+
+ Sets the node's position.
+
+ Parameters:
+
+ value - (object) A <Complex> or <Polar> instance.
+ type - (string) Default's *current*. Possible values are "start", "end" or "current".
+
+ Example:
+ (start code js)
+ node.setPos(new $jit.Complex(0, 0), 'end');
+ (end code)
+ */
+ setPos: function(value, type) {
+ type = type || "current";
+ var pos;
+ if(type == "current") {
+ pos = this.pos;
+ } else if(type == "end") {
+ pos = this.endPos;
+ } else if(type == "start") {
+ pos = this.startPos;
+ }
+ pos.set(value);
+ }
+});
+
+Graph.Node.implement(Accessors);
+
+/*
+ Class: Graph.Adjacence
+
+ A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
+
+ Implements:
+
+ <Accessors> methods.
+
+ See also:
+
+ <Graph>, <Graph.Node>
+
+ Properties:
+
+ nodeFrom - A <Graph.Node> connected by this edge.
+ nodeTo - Another <Graph.Node> connected by this edge.
+ data - Node data property containing a hash (i.e {}) with custom options.
+*/
+Graph.Adjacence = new Class({
+
+ initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
+ this.nodeFrom = nodeFrom;
+ this.nodeTo = nodeTo;
+ this.data = data || {};
+ this.startData = {};
+ this.endData = {};
+ this.Config = this.Edge = Edge;
+ this.Label = Label;
+ }
+});
+
+Graph.Adjacence.implement(Accessors);
+
+/*
+ Object: Graph.Util
+
+ <Graph> traversal and processing utility object.
+
+ Note:
+
+ For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
+*/
+Graph.Util = {
+ /*
+ filter
+
+ For internal use only. Provides a filtering function based on flags.
+ */
+ filter: function(param) {
+ if(!param || !($.type(param) == 'string')) return function() { return true; };
+ var props = param.split(" ");
+ return function(elem) {
+ for(var i=0; i<props.length; i++) {
+ if(elem[props[i]]) {
+ return false;
+ }
+ }
+ return true;
+ };
+ },
+ /*
+ Method: getNode
+
+ Returns a <Graph.Node> by *id*.
+
+ Also implemented by:
+
+ <Graph>
+
+ Parameters:
+
+ graph - (object) A <Graph> instance.
+ id - (string) A <Graph.Node> id.
+
+ Example:
+
+ (start code js)
+ $jit.Graph.Util.getNode(graph, 'nodeid');
+ //or...
+ graph.getNode('nodeid');
+ (end code)
+ */
+ getNode: function(graph, id) {
+ return graph.nodes[id];
+ },
+
+ /*
+ Method: eachNode
+
+ Iterates over <Graph> nodes performing an *action*.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Parameters:
+
+ graph - (object) A <Graph> instance.
+ action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.eachNode(graph, function(node) {
+ alert(node.name);
+ });
+ //or...
+ graph.eachNode(function(node) {
+ alert(node.name);
+ });
+ (end code)
+ */
+ eachNode: function(graph, action, flags) {
+ var filter = this.filter(flags);
+ for(var i in graph.nodes) {
+ if(filter(graph.nodes[i])) action(graph.nodes[i]);
+ }
+ },
+
+ /*
+ Method: eachAdjacency
+
+ Iterates over <Graph.Node> adjacencies applying the *action* function.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.eachAdjacency(node, function(adj) {
+ alert(adj.nodeTo.name);
+ });
+ //or...
+ node.eachAdjacency(function(adj) {
+ alert(adj.nodeTo.name);
+ });
+ (end code)
+ */
+ eachAdjacency: function(node, action, flags) {
+ var adj = node.adjacencies, filter = this.filter(flags);
+ for(var id in adj) {
+ var a = adj[id];
+ if(filter(a)) {
+ if(a.nodeFrom != node) {
+ var tmp = a.nodeFrom;
+ a.nodeFrom = a.nodeTo;
+ a.nodeTo = tmp;
+ }
+ action(a, id);
+ }
+ }
+ },
+
+ /*
+ Method: computeLevels
+
+ Performs a BFS traversal setting the correct depth for each node.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Note:
+
+ The depth of each node can then be accessed by
+ >node._depth
+
+ Parameters:
+
+ graph - (object) A <Graph>.
+ id - (string) A starting node id for the BFS traversal.
+ startDepth - (optional|number) A minimum depth value. Default's 0.
+
+ */
+ computeLevels: function(graph, id, startDepth, flags) {
+ startDepth = startDepth || 0;
+ var filter = this.filter(flags);
+ this.eachNode(graph, function(elem) {
+ elem._flag = false;
+ elem._depth = -1;
+ }, flags);
+ var root = graph.getNode(id);
+ root._depth = startDepth;
+ var queue = [root];
+ while(queue.length != 0) {
+ var node = queue.pop();
+ node._flag = true;
+ this.eachAdjacency(node, function(adj) {
+ var n = adj.nodeTo;
+ if(n._flag == false && filter(n)) {
+ if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
+ queue.unshift(n);
+ }
+ }, flags);
+ }
+ },
+
+ /*
+ Method: eachBFS
+
+ Performs a BFS traversal applying *action* to each <Graph.Node>.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Parameters:
+
+ graph - (object) A <Graph>.
+ id - (string) A starting node id for the BFS traversal.
+ action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
+ alert(node.name);
+ });
+ //or...
+ graph.eachBFS('mynodeid', function(node) {
+ alert(node.name);
+ });
+ (end code)
+ */
+ eachBFS: function(graph, id, action, flags) {
+ var filter = this.filter(flags);
+ this.clean(graph);
+ var queue = [graph.getNode(id)];
+ while(queue.length != 0) {
+ var node = queue.pop();
+ node._flag = true;
+ action(node, node._depth);
+ this.eachAdjacency(node, function(adj) {
+ var n = adj.nodeTo;
+ if(n._flag == false && filter(n)) {
+ n._flag = true;
+ queue.unshift(n);
+ }
+ }, flags);
+ }
+ },
+
+ /*
+ Method: eachLevel
+
+ Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ levelBegin - (number) A relative level value.
+ levelEnd - (number) A relative level value.
+ action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+ */
+ eachLevel: function(node, levelBegin, levelEnd, action, flags) {
+ var d = node._depth, filter = this.filter(flags), that = this;
+ levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
+ (function loopLevel(node, levelBegin, levelEnd) {
+ var d = node._depth;
+ if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
+ if(d < levelEnd) {
+ that.eachAdjacency(node, function(adj) {
+ var n = adj.nodeTo;
+ if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
+ });
+ }
+ })(node, levelBegin + d, levelEnd + d);
+ },
+
+ /*
+ Method: eachSubgraph
+
+ Iterates over a node's children recursively.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+ action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.eachSubgraph(node, function(node) {
+ alert(node.name);
+ });
+ //or...
+ node.eachSubgraph(function(node) {
+ alert(node.name);
+ });
+ (end code)
+ */
+ eachSubgraph: function(node, action, flags) {
+ this.eachLevel(node, 0, false, action, flags);
+ },
+
+ /*
+ Method: eachSubnode
+
+ Iterates over a node's children (without deeper recursion).
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+ action - (function) A callback function having a <Graph.Node> as first formal parameter.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.eachSubnode(node, function(node) {
+ alert(node.name);
+ });
+ //or...
+ node.eachSubnode(function(node) {
+ alert(node.name);
+ });
+ (end code)
+ */
+ eachSubnode: function(node, action, flags) {
+ this.eachLevel(node, 1, 1, action, flags);
+ },
+
+ /*
+ Method: anySubnode
+
+ Returns *true* if any subnode matches the given condition.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+ cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
+ //or...
+ node.anySubnode(function(node) { return node.name == 'mynodename'; });
+ (end code)
+ */
+ anySubnode: function(node, cond, flags) {
+ var flag = false;
+ cond = cond || $.lambda(true);
+ var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
+ this.eachSubnode(node, function(elem) {
+ if(c(elem)) flag = true;
+ }, flags);
+ return flag;
+ },
+
+ /*
+ Method: getSubnodes
+
+ Collects all subnodes for a specified node.
+ The *level* parameter filters nodes having relative depth of *level* from the root node.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+ level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
+
+ Returns:
+ An array of nodes.
+
+ */
+ getSubnodes: function(node, level, flags) {
+ var ans = [], that = this;
+ level = level || 0;
+ var levelStart, levelEnd;
+ if($.type(level) == 'array') {
+ levelStart = level[0];
+ levelEnd = level[1];
+ } else {
+ levelStart = level;
+ levelEnd = Number.MAX_VALUE - node._depth;
+ }
+ this.eachLevel(node, levelStart, levelEnd, function(n) {
+ ans.push(n);
+ }, flags);
+ return ans;
+ },
+
+
+ /*
+ Method: getParents
+
+ Returns an Array of <Graph.Nodes> which are parents of the given node.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+
+ Returns:
+ An Array of <Graph.Nodes>.
+
+ Example:
+ (start code js)
+ var pars = $jit.Graph.Util.getParents(node);
+ //or...
+ var pars = node.getParents();
+
+ if(pars.length > 0) {
+ //do stuff with parents
+ }
+ (end code)
+ */
+ getParents: function(node) {
+ var ans = [];
+ this.eachAdjacency(node, function(adj) {
+ var n = adj.nodeTo;
+ if(n._depth < node._depth) ans.push(n);
+ });
+ return ans;
+ },
+
+ /*
+ Method: isDescendantOf
+
+ Returns a boolean indicating if some node is descendant of the node with the given id.
+
+ Also implemented by:
+
+ <Graph.Node>.
+
+
+ Parameters:
+ node - (object) A <Graph.Node>.
+ id - (string) A <Graph.Node> id.
+
+ Example:
+ (start code js)
+ $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
+ //or...
+ node.isDescendantOf('nodeid');//true|false
+ (end code)
+ */
+ isDescendantOf: function(node, id) {
+ if(node.id == id) return true;
+ var pars = this.getParents(node), ans = false;
+ for ( var i = 0; !ans && i < pars.length; i++) {
+ ans = ans || this.isDescendantOf(pars[i], id);
+ }
+ return ans;
+ },
+
+ /*
+ Method: clean
+
+ Cleans flags from nodes.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Parameters:
+ graph - A <Graph> instance.
+ */
+ clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
+
+ /*
+ Method: getClosestNodeToOrigin
+
+ Returns the closest node to the center of canvas.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Parameters:
+
+ graph - (object) A <Graph> instance.
+ prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+
+ */
+ getClosestNodeToOrigin: function(graph, prop, flags) {
+ return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
+ },
+
+ /*
+ Method: getClosestNodeToPos
+
+ Returns the closest node to the given position.
+
+ Also implemented by:
+
+ <Graph>.
+
+ Parameters:
+
+ graph - (object) A <Graph> instance.
+ pos - (object) A <Complex> or <Polar> instance.
+ prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
+
+ */
+ getClosestNodeToPos: function(graph, pos, prop, flags) {
+ var node = null;
+ prop = prop || 'current';
+ pos = pos && pos.getc(true) || Complex.KER;
+ var distance = function(a, b) {
+ var d1 = a.x - b.x, d2 = a.y - b.y;
+ return d1 * d1 + d2 * d2;
+ };
+ this.eachNode(graph, function(elem) {
+ node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
+ node.getPos(prop).getc(true), pos)) ? elem : node;
+ }, flags);
+ return node;
+ }
+};
+
+//Append graph methods to <Graph>
+$.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
+ Graph.prototype[m] = function() {
+ return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+ };
+});
+
+//Append node methods to <Graph.Node>
+$.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
+ Graph.Node.prototype[m] = function() {
+ return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
+ };
+});
+
+/*
+ * File: Graph.Op.js
+ *
+*/
+
+/*
+ Object: Graph.Op
+
+ Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
+ morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
+
+*/
+Graph.Op = {
+
+ options: {
+ type: 'nothing',
+ duration: 2000,
+ hideLabels: true,
+ fps:30
+ },
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+
+ /*
+ Method: removeNode
+
+ Removes one or more <Graph.Nodes> from the visualization.
+ It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
+
+ Parameters:
+
+ node - (string|array) The node's id. Can also be an array having many ids.
+ opt - (object) Animation options. It's an object with optional properties described below
+ type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
+ duration - Described in <Options.Fx>.
+ fps - Described in <Options.Fx>.
+ transition - Described in <Options.Fx>.
+ hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.op.removeNode('nodeId', {
+ type: 'fade:seq',
+ duration: 1000,
+ hideLabels: false,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ //or also
+ viz.op.removeNode(['someId', 'otherId'], {
+ type: 'fade:con',
+ duration: 1500
+ });
+ (end code)
+ */
+
+ removeNode: function(node, opt) {
+ var viz = this.viz;
+ var options = $.merge(this.options, viz.controller, opt);
+ var n = $.splat(node);
+ var i, that, nodeObj;
+ switch(options.type) {
+ case 'nothing':
+ for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
+ break;
+
+ case 'replot':
+ this.removeNode(n, { type: 'nothing' });
+ viz.labels.clearLabels();
+ viz.refresh(true);
+ break;
+
+ case 'fade:seq': case 'fade':
+ that = this;
+ //set alpha to 0 for nodes to remove.
+ for(i=0; i<n.length; i++) {
+ nodeObj = viz.graph.getNode(n[i]);
+ nodeObj.setData('alpha', 0, 'end');
+ }
+ viz.fx.animate($.merge(options, {
+ modes: ['node-property:alpha'],
+ onComplete: function() {
+ that.removeNode(n, { type: 'nothing' });
+ viz.labels.clearLabels();
+ viz.reposition();
+ viz.fx.animate($.merge(options, {
+ modes: ['linear']
+ }));
+ }
+ }));
+ break;
+
+ case 'fade:con':
+ that = this;
+ //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
+ for(i=0; i<n.length; i++) {
+ nodeObj = viz.graph.getNode(n[i]);
+ nodeObj.setData('alpha', 0, 'end');
+ nodeObj.ignore = true;
+ }
+ viz.reposition();
+ viz.fx.animate($.merge(options, {
+ modes: ['node-property:alpha', 'linear'],
+ onComplete: function() {
+ that.removeNode(n, { type: 'nothing' });
+ }
+ }));
+ break;
+
+ case 'iter':
+ that = this;
+ viz.fx.sequence({
+ condition: function() { return n.length != 0; },
+ step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
+ onComplete: function() { options.onComplete(); },
+ duration: Math.ceil(options.duration / n.length)
+ });
+ break;
+
+ default: this.doError();
+ }
+ },
+
+ /*
+ Method: removeEdge
+
+ Removes one or more <Graph.Adjacences> from the visualization.
+ It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
+
+ Parameters:
+
+ vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
+ opt - (object) Animation options. It's an object with optional properties described below
+ type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
+ duration - Described in <Options.Fx>.
+ fps - Described in <Options.Fx>.
+ transition - Described in <Options.Fx>.
+ hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.op.removeEdge(['nodeId', 'otherId'], {
+ type: 'fade:seq',
+ duration: 1000,
+ hideLabels: false,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ //or also
+ viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
+ type: 'fade:con',
+ duration: 1500
+ });
+ (end code)
+
+ */
+ removeEdge: function(vertex, opt) {
+ var viz = this.viz;
+ var options = $.merge(this.options, viz.controller, opt);
+ var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
+ var i, that, adj;
+ switch(options.type) {
+ case 'nothing':
+ for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
+ break;
+
+ case 'replot':
+ this.removeEdge(v, { type: 'nothing' });
+ viz.refresh(true);
+ break;
+
+ case 'fade:seq': case 'fade':
+ that = this;
+ //set alpha to 0 for edges to remove.
+ for(i=0; i<v.length; i++) {
+ adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
+ if(adj) {
+ adj.setData('alpha', 0,'end');
+ }
+ }
+ viz.fx.animate($.merge(options, {
+ modes: ['edge-property:alpha'],
+ onComplete: function() {
+ that.removeEdge(v, { type: 'nothing' });
+ viz.reposition();
+ viz.fx.animate($.merge(options, {
+ modes: ['linear']
+ }));
+ }
+ }));
+ break;
+
+ case 'fade:con':
+ that = this;
+ //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
+ for(i=0; i<v.length; i++) {
+ adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
+ if(adj) {
+ adj.setData('alpha',0 ,'end');
+ adj.ignore = true;
+ }
+ }
+ viz.reposition();
+ viz.fx.animate($.merge(options, {
+ modes: ['edge-property:alpha', 'linear'],
+ onComplete: function() {
+ that.removeEdge(v, { type: 'nothing' });
+ }
+ }));
+ break;
+
+ case 'iter':
+ that = this;
+ viz.fx.sequence({
+ condition: function() { return v.length != 0; },
+ step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
+ onComplete: function() { options.onComplete(); },
+ duration: Math.ceil(options.duration / v.length)
+ });
+ break;
+
+ default: this.doError();
+ }
+ },
+
+ /*
+ Method: sum
+
+ Adds a new graph to the visualization.
+ The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
+ The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
+
+ Parameters:
+
+ json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
+ opt - (object) Animation options. It's an object with optional properties described below
+ type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
+ duration - Described in <Options.Fx>.
+ fps - Described in <Options.Fx>.
+ transition - Described in <Options.Fx>.
+ hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+
+ Example:
+ (start code js)
+ //...json contains a tree or graph structure...
+
+ var viz = new $jit.Viz(options);
+ viz.op.sum(json, {
+ type: 'fade:seq',
+ duration: 1000,
+ hideLabels: false,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ //or also
+ viz.op.sum(json, {
+ type: 'fade:con',
+ duration: 1500
+ });
+ (end code)
+
+ */
+ sum: function(json, opt) {
+ var viz = this.viz;
+ var options = $.merge(this.options, viz.controller, opt), root = viz.root;
+ var graph;
+ viz.root = opt.id || viz.root;
+ switch(options.type) {
+ case 'nothing':
+ graph = viz.construct(json);
+ graph.eachNode(function(elem) {
+ elem.eachAdjacency(function(adj) {
+ viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
+ });
+ });
+ break;
+
+ case 'replot':
+ viz.refresh(true);
+ this.sum(json, { type: 'nothing' });
+ viz.refresh(true);
+ break;
+
+ case 'fade:seq': case 'fade': case 'fade:con':
+ that = this;
+ graph = viz.construct(json);
+
+ //set alpha to 0 for nodes to add.
+ var fadeEdges = this.preprocessSum(graph);
+ var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
+ viz.reposition();
+ if(options.type != 'fade:con') {
+ viz.fx.animate($.merge(options, {
+ modes: ['linear'],
+ onComplete: function() {
+ viz.fx.animate($.merge(options, {
+ modes: modes,
+ onComplete: function() {
+ options.onComplete();
+ }
+ }));
+ }
+ }));
+ } else {
+ viz.graph.eachNode(function(elem) {
+ if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
+ elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
+ }
+ });
+ viz.fx.animate($.merge(options, {
+ modes: ['linear'].concat(modes)
+ }));
+ }
+ break;
+
+ default: this.doError();
+ }
+ },
+
+ /*
+ Method: morph
+
+ This method will transform the current visualized graph into the new JSON representation passed in the method.
+ The JSON object must at least have the root node in common with the current visualized graph.
+
+ Parameters:
+
+ json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
+ opt - (object) Animation options. It's an object with optional properties described below
+ type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
+ duration - Described in <Options.Fx>.
+ fps - Described in <Options.Fx>.
+ transition - Described in <Options.Fx>.
+ hideLabels - (boolean) Default's *true*. Hide labels during the animation.
+ id - (string) The shared <Graph.Node> id between both graphs.
+
+ extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
+ *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
+ For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
+ properties as values, just like specified in <Graph.Plot.animate>.
+
+ Example:
+ (start code js)
+ //...json contains a tree or graph structure...
+
+ var viz = new $jit.Viz(options);
+ viz.op.morph(json, {
+ type: 'fade',
+ duration: 1000,
+ hideLabels: false,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ //or also
+ viz.op.morph(json, {
+ type: 'fade',
+ duration: 1500
+ });
+ //if the json data contains dollar prefixed params
+ //like $width or $height these too can be animated
+ viz.op.morph(json, {
+ type: 'fade',
+ duration: 1500
+ }, {
+ 'node-property': ['width', 'height']
+ });
+ (end code)
+
+ */
+ morph: function(json, opt, extraModes) {
+ var viz = this.viz;
+ var options = $.merge(this.options, viz.controller, opt), root = viz.root;
+ var graph;
+ //TODO(nico) this hack makes morphing work with the Hypertree.
+ //Need to check if it has been solved and this can be removed.
+ viz.root = opt.id || viz.root;
+ switch(options.type) {
+ case 'nothing':
+ graph = viz.construct(json);
+ graph.eachNode(function(elem) {
+ var nodeExists = viz.graph.hasNode(elem.id);
+ elem.eachAdjacency(function(adj) {
+ var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+ viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
+ //Update data properties if the node existed
+ if(adjExists) {
+ var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+ for(var prop in (adj.data || {})) {
+ addedAdj.data[prop] = adj.data[prop];
+ }
+ }
+ });
+ //Update data properties if the node existed
+ if(nodeExists) {
+ var addedNode = viz.graph.getNode(elem.id);
+ for(var prop in (elem.data || {})) {
+ addedNode.data[prop] = elem.data[prop];
+ }
+ }
+ });
+ viz.graph.eachNode(function(elem) {
+ elem.eachAdjacency(function(adj) {
+ if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
+ viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+ }
+ });
+ if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
+ });
+
+ break;
+
+ case 'replot':
+ viz.labels.clearLabels(true);
+ this.morph(json, { type: 'nothing' });
+ viz.refresh(true);
+ viz.refresh(true);
+ break;
+
+ case 'fade:seq': case 'fade': case 'fade:con':
+ that = this;
+ graph = viz.construct(json);
+ //preprocessing for nodes to delete.
+ //get node property modes to interpolate
+ var nodeModes = extraModes && ('node-property' in extraModes)
+ && $.map($.splat(extraModes['node-property']),
+ function(n) { return '$' + n; });
+ viz.graph.eachNode(function(elem) {
+ var graphNode = graph.getNode(elem.id);
+ if(!graphNode) {
+ elem.setData('alpha', 1);
+ elem.setData('alpha', 1, 'start');
+ elem.setData('alpha', 0, 'end');
+ elem.ignore = true;
+ } else {
+ //Update node data information
+ var graphNodeData = graphNode.data;
+ for(var prop in graphNodeData) {
+ if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
+ elem.endData[prop] = graphNodeData[prop];
+ } else {
+ elem.data[prop] = graphNodeData[prop];
+ }
+ }
+ }
+ });
+ viz.graph.eachNode(function(elem) {
+ if(elem.ignore) return;
+ elem.eachAdjacency(function(adj) {
+ if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
+ var nodeFrom = graph.getNode(adj.nodeFrom.id);
+ var nodeTo = graph.getNode(adj.nodeTo.id);
+ if(!nodeFrom.adjacentTo(nodeTo)) {
+ var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
+ fadeEdges = true;
+ adj.setData('alpha', 1);
+ adj.setData('alpha', 1, 'start');
+ adj.setData('alpha', 0, 'end');
+ }
+ });
+ });
+ //preprocessing for adding nodes.
+ var fadeEdges = this.preprocessSum(graph);
+
+ var modes = !fadeEdges? ['node-property:alpha'] :
+ ['node-property:alpha',
+ 'edge-property:alpha'];
+ //Append extra node-property animations (if any)
+ modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
+ (':' + $.splat(extraModes['node-property']).join(':')) : '');
+ //Append extra edge-property animations (if any)
+ modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
+ (':' + $.splat(extraModes['edge-property']).join(':')) : '');
+ //Add label-property animations (if any)
+ if(extraModes && ('label-property' in extraModes)) {
+ modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
+ }
+ viz.reposition();
+ viz.graph.eachNode(function(elem) {
+ if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
+ elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
+ }
+ });
+ viz.fx.animate($.merge(options, {
+ modes: ['polar'].concat(modes),
+ onComplete: function() {
+ viz.graph.eachNode(function(elem) {
+ if(elem.ignore) viz.graph.removeNode(elem.id);
+ });
+ viz.graph.eachNode(function(elem) {
+ elem.eachAdjacency(function(adj) {
+ if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
+ });
+ });
+ options.onComplete();
+ }
+ }));
+ break;
+
+ default:;
+ }
+ },
+
+
+ /*
+ Method: contract
+
+ Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ opt - (object) An object containing options described below
+ type - (string) Whether to 'replot' or 'animate' the contraction.
+
+ There are also a number of Animation options. For more information see <Options.Fx>.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.op.contract(node, {
+ type: 'animate',
+ duration: 1000,
+ hideLabels: true,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ (end code)
+
+ */
+ contract: function(node, opt) {
+ var viz = this.viz;
+ if(node.collapsed || !node.anySubnode($.lambda(true))) return;
+ opt = $.merge(this.options, viz.config, opt || {}, {
+ 'modes': ['node-property:alpha:span', 'linear']
+ });
+ node.collapsed = true;
+ (function subn(n) {
+ n.eachSubnode(function(ch) {
+ ch.ignore = true;
+ ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
+ subn(ch);
+ });
+ })(node);
+ if(opt.type == 'animate') {
+ viz.compute('end');
+ if(viz.rotated) {
+ viz.rotate(viz.rotated, 'none', {
+ 'property':'end'
+ });
+ }
+ (function subn(n) {
+ n.eachSubnode(function(ch) {
+ ch.setPos(node.getPos('end'), 'end');
+ subn(ch);
+ });
+ })(node);
+ viz.fx.animate(opt);
+ } else if(opt.type == 'replot'){
+ viz.refresh();
+ }
+ },
+
+ /*
+ Method: expand
+
+ Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ opt - (object) An object containing options described below
+ type - (string) Whether to 'replot' or 'animate'.
+
+ There are also a number of Animation options. For more information see <Options.Fx>.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.op.expand(node, {
+ type: 'animate',
+ duration: 1000,
+ hideLabels: true,
+ transition: $jit.Trans.Quart.easeOut
+ });
+ (end code)
+
+ */
+ expand: function(node, opt) {
+ if(!('collapsed' in node)) return;
+ var viz = this.viz;
+ opt = $.merge(this.options, viz.config, opt || {}, {
+ 'modes': ['node-property:alpha:span', 'linear']
+ });
+ delete node.collapsed;
+ (function subn(n) {
+ n.eachSubnode(function(ch) {
+ delete ch.ignore;
+ ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
+ subn(ch);
+ });
+ })(node);
+ if(opt.type == 'animate') {
+ viz.compute('end');
+ if(viz.rotated) {
+ viz.rotate(viz.rotated, 'none', {
+ 'property':'end'
+ });
+ }
+ viz.fx.animate(opt);
+ } else if(opt.type == 'replot'){
+ viz.refresh();
+ }
+ },
+
+ preprocessSum: function(graph) {
+ var viz = this.viz;
+ graph.eachNode(function(elem) {
+ if(!viz.graph.hasNode(elem.id)) {
+ viz.graph.addNode(elem);
+ var n = viz.graph.getNode(elem.id);
+ n.setData('alpha', 0);
+ n.setData('alpha', 0, 'start');
+ n.setData('alpha', 1, 'end');
+ }
+ });
+ var fadeEdges = false;
+ graph.eachNode(function(elem) {
+ elem.eachAdjacency(function(adj) {
+ var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
+ var nodeTo = viz.graph.getNode(adj.nodeTo.id);
+ if(!nodeFrom.adjacentTo(nodeTo)) {
+ var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
+ if(nodeFrom.startAlpha == nodeFrom.endAlpha
+ && nodeTo.startAlpha == nodeTo.endAlpha) {
+ fadeEdges = true;
+ adj.setData('alpha', 0);
+ adj.setData('alpha', 0, 'start');
+ adj.setData('alpha', 1, 'end');
+ }
+ }
+ });
+ });
+ return fadeEdges;
+ }
+};
+
+
+
+/*
+ File: Helpers.js
+
+ Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
+ Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
+ position is over the rendered shape.
+
+ Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
+ *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
+
+ Example:
+ (start code js)
+ //implement a new node type
+ $jit.Viz.Plot.NodeTypes.implement({
+ 'customNodeType': {
+ 'render': function(node, canvas) {
+ this.nodeHelper.circle.render ...
+ },
+ 'contains': function(node, pos) {
+ this.nodeHelper.circle.contains ...
+ }
+ }
+ });
+ //implement an edge type
+ $jit.Viz.Plot.EdgeTypes.implement({
+ 'customNodeType': {
+ 'render': function(node, canvas) {
+ this.edgeHelper.circle.render ...
+ },
+ //optional
+ 'contains': function(node, pos) {
+ this.edgeHelper.circle.contains ...
+ }
+ }
+ });
+ (end code)
+
+*/
+
+/*
+ Object: NodeHelper
+
+ Contains rendering and other type of primitives for simple shapes.
+ */
+var NodeHelper = {
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false)
+ },
+ /*
+ Object: NodeHelper.circle
+ */
+ 'circle': {
+ /*
+ Method: render
+
+ Renders a circle into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the circle.
+ radius - (number) The radius of the circle to be rendered.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, radius, canvas){
+ var ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
+ ctx.closePath();
+ ctx[type]();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ radius - (number) The radius of the rendered circle.
+
+ Example:
+ (start code js)
+ NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
+ (end code)
+ */
+ 'contains': function(npos, pos, radius){
+ var diffx = npos.x - pos.x,
+ diffy = npos.y - pos.y,
+ diff = diffx * diffx + diffy * diffy;
+ return diff <= radius * radius;
+ }
+ },
+ /*
+ Object: NodeHelper.ellipse
+ */
+ 'ellipse': {
+ /*
+ Method: render
+
+ Renders an ellipse into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the ellipse.
+ width - (number) The width of the ellipse.
+ height - (number) The height of the ellipse.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, width, height, canvas){
+ var ctx = canvas.getCtx();
+ height /= 2;
+ width /= 2;
+ ctx.save();
+ ctx.scale(width / height, height / width);
+ ctx.beginPath();
+ ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
+ Math.PI * 2, true);
+ ctx.closePath();
+ ctx[type]();
+ ctx.restore();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ width - (number) The width of the rendered ellipse.
+ height - (number) The height of the rendered ellipse.
+
+ Example:
+ (start code js)
+ NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
+ (end code)
+ */
+ 'contains': function(npos, pos, width, height){
+ // TODO(nico): be more precise...
+ width /= 2;
+ height /= 2;
+ var dist = (width + height) / 2,
+ diffx = npos.x - pos.x,
+ diffy = npos.y - pos.y,
+ diff = diffx * diffx + diffy * diffy;
+ return diff <= dist * dist;
+ }
+ },
+ /*
+ Object: NodeHelper.square
+ */
+ 'square': {
+ /*
+ Method: render
+
+ Renders a square into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the square.
+ dim - (number) The radius (or half-diameter) of the square.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, dim, canvas){
+ canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ dim - (number) The radius (or half-diameter) of the square.
+
+ Example:
+ (start code js)
+ NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': function(npos, pos, dim){
+ return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
+ }
+ },
+ /*
+ Object: NodeHelper.rectangle
+ */
+ 'rectangle': {
+ /*
+ Method: render
+
+ Renders a rectangle into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the rectangle.
+ width - (number) The width of the rectangle.
+ height - (number) The height of the rectangle.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, width, height, canvas){
+ canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
+ width, height);
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ width - (number) The width of the rendered rectangle.
+ height - (number) The height of the rendered rectangle.
+
+ Example:
+ (start code js)
+ NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
+ (end code)
+ */
+ 'contains': function(npos, pos, width, height){
+ return Math.abs(pos.x - npos.x) <= width / 2
+ && Math.abs(pos.y - npos.y) <= height / 2;
+ }
+ },
+ /*
+ Object: NodeHelper.triangle
+ */
+ 'triangle': {
+ /*
+ Method: render
+
+ Renders a triangle into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the triangle.
+ dim - (number) The dimension of the triangle.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, dim, canvas){
+ var ctx = canvas.getCtx(),
+ c1x = pos.x,
+ c1y = pos.y - dim,
+ c2x = c1x - dim,
+ c2y = pos.y + dim,
+ c3x = c1x + dim,
+ c3y = c2y;
+ ctx.beginPath();
+ ctx.moveTo(c1x, c1y);
+ ctx.lineTo(c2x, c2y);
+ ctx.lineTo(c3x, c3y);
+ ctx.closePath();
+ ctx[type]();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ dim - (number) The dimension of the shape.
+
+ Example:
+ (start code js)
+ NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': function(npos, pos, dim) {
+ return NodeHelper.circle.contains(npos, pos, dim);
+ }
+ },
+ /*
+ Object: NodeHelper.star
+ */
+ 'star': {
+ /*
+ Method: render
+
+ Renders a star into the canvas.
+
+ Parameters:
+
+ type - (string) Possible options are 'fill' or 'stroke'.
+ pos - (object) An *x*, *y* object with the position of the center of the star.
+ dim - (number) The dimension of the star.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
+ (end code)
+ */
+ 'render': function(type, pos, dim, canvas){
+ var ctx = canvas.getCtx(),
+ pi5 = Math.PI / 5;
+ ctx.save();
+ ctx.translate(pos.x, pos.y);
+ ctx.beginPath();
+ ctx.moveTo(dim, 0);
+ for (var i = 0; i < 9; i++) {
+ ctx.rotate(pi5);
+ if (i % 2 == 0) {
+ ctx.lineTo((dim / 0.525731) * 0.200811, 0);
+ } else {
+ ctx.lineTo(dim, 0);
+ }
+ }
+ ctx.closePath();
+ ctx[type]();
+ ctx.restore();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ npos - (object) An *x*, *y* object with the <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ dim - (number) The dimension of the shape.
+
+ Example:
+ (start code js)
+ NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': function(npos, pos, dim) {
+ return NodeHelper.circle.contains(npos, pos, dim);
+ }
+ }
+};
+
+/*
+ Object: EdgeHelper
+
+ Contains rendering primitives for simple edge shapes.
+*/
+var EdgeHelper = {
+ /*
+ Object: EdgeHelper.line
+ */
+ 'line': {
+ /*
+ Method: render
+
+ Renders a line into the canvas.
+
+ Parameters:
+
+ from - (object) An *x*, *y* object with the starting position of the line.
+ to - (object) An *x*, *y* object with the ending position of the line.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
+ (end code)
+ */
+ 'render': function(from, to, canvas){
+ var ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.moveTo(from.x, from.y);
+ ctx.lineTo(to.x, to.y);
+ ctx.stroke();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
+ posTo - (object) An *x*, *y* object with a <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ epsilon - (number) The dimension of the shape.
+
+ Example:
+ (start code js)
+ EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': function(posFrom, posTo, pos, epsilon) {
+ var min = Math.min,
+ max = Math.max,
+ minPosX = min(posFrom.x, posTo.x),
+ maxPosX = max(posFrom.x, posTo.x),
+ minPosY = min(posFrom.y, posTo.y),
+ maxPosY = max(posFrom.y, posTo.y);
+
+ if(pos.x >= minPosX && pos.x <= maxPosX
+ && pos.y >= minPosY && pos.y <= maxPosY) {
+ if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
+ return true;
+ }
+ var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
+ return Math.abs(dist - pos.y) <= epsilon;
+ }
+ return false;
+ }
+ },
+ /*
+ Object: EdgeHelper.arrow
+ */
+ 'arrow': {
+ /*
+ Method: render
+
+ Renders an arrow into the canvas.
+
+ Parameters:
+
+ from - (object) An *x*, *y* object with the starting position of the arrow.
+ to - (object) An *x*, *y* object with the ending position of the arrow.
+ dim - (number) The dimension of the arrow.
+ swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
+ (end code)
+ */
+ 'render': function(from, to, dim, swap, canvas){
+ var ctx = canvas.getCtx();
+ // invert edge direction
+ if (swap) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+ var vect = new Complex(to.x - from.x, to.y - from.y);
+ vect.$scale(dim / vect.norm());
+ var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
+ normal = new Complex(-vect.y / 2, vect.x / 2),
+ v1 = intermediatePoint.add(normal),
+ v2 = intermediatePoint.$add(normal.$scale(-1));
+
+ ctx.beginPath();
+ ctx.moveTo(from.x, from.y);
+ ctx.lineTo(to.x, to.y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(v1.x, v1.y);
+ ctx.lineTo(v2.x, v2.y);
+ ctx.lineTo(to.x, to.y);
+ ctx.closePath();
+ ctx.fill();
+ },
+ /*
+ Method: contains
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
+ posTo - (object) An *x*, *y* object with a <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ epsilon - (number) The dimension of the shape.
+
+ Example:
+ (start code js)
+ EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': function(posFrom, posTo, pos, epsilon) {
+ return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
+ }
+ },
+ /*
+ Object: EdgeHelper.hyperline
+ */
+ 'hyperline': {
+ /*
+ Method: render
+
+ Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
+
+ Parameters:
+
+ from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
+ to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
+ r - (number) The scaling factor.
+ canvas - (object) A <Canvas> instance.
+
+ Example:
+ (start code js)
+ EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
+ (end code)
+ */
+ 'render': function(from, to, r, canvas){
+ var ctx = canvas.getCtx();
+ var centerOfCircle = computeArcThroughTwoPoints(from, to);
+ if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
+ || centerOfCircle.ratio < 0) {
+ ctx.beginPath();
+ ctx.moveTo(from.x * r, from.y * r);
+ ctx.lineTo(to.x * r, to.y * r);
+ ctx.stroke();
+ } else {
+ var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
+ - centerOfCircle.x);
+ var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
+ - centerOfCircle.x);
+ var sense = sense(angleBegin, angleEnd);
+ ctx.beginPath();
+ ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
+ * r, angleBegin, angleEnd, sense);
+ ctx.stroke();
+ }
+ /*
+ Calculates the arc parameters through two points.
+
+ More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
+
+ Parameters:
+
+ p1 - A <Complex> instance.
+ p2 - A <Complex> instance.
+ scale - The Disk's diameter.
+
+ Returns:
+
+ An object containing some arc properties.
+ */
+ function computeArcThroughTwoPoints(p1, p2){
+ var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
+ var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
+ // Fall back to a straight line
+ if (aDen == 0)
+ return {
+ x: 0,
+ y: 0,
+ ratio: -1
+ };
+
+ var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
+ var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
+ var x = -a / 2;
+ var y = -b / 2;
+ var squaredRatio = (a * a + b * b) / 4 - 1;
+ // Fall back to a straight line
+ if (squaredRatio < 0)
+ return {
+ x: 0,
+ y: 0,
+ ratio: -1
+ };
+ var ratio = Math.sqrt(squaredRatio);
+ var out = {
+ x: x,
+ y: y,
+ ratio: ratio > 1000? -1 : ratio,
+ a: a,
+ b: b
+ };
+
+ return out;
+ }
+ /*
+ Sets angle direction to clockwise (true) or counterclockwise (false).
+
+ Parameters:
+
+ angleBegin - Starting angle for drawing the arc.
+ angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
+
+ Returns:
+
+ A Boolean instance describing the sense for drawing the HyperLine.
+ */
+ function sense(angleBegin, angleEnd){
+ return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
+ : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
+ }
+ },
+ /*
+ Method: contains
+
+ Not Implemented
+
+ Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
+
+ Parameters:
+
+ posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
+ posTo - (object) An *x*, *y* object with a <Graph.Node> position.
+ pos - (object) An *x*, *y* object with the position to check.
+ epsilon - (number) The dimension of the shape.
+
+ Example:
+ (start code js)
+ EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
+ (end code)
+ */
+ 'contains': $.lambda(false)
+ }
+};
+
+
+/*
+ * File: Graph.Plot.js
+ */
+
+/*
+ Object: Graph.Plot
+
+ <Graph> rendering and animation methods.
+
+ Properties:
+
+ nodeHelper - <NodeHelper> object.
+ edgeHelper - <EdgeHelper> object.
+*/
+Graph.Plot = {
+ //Default intializer
+ initialize: function(viz, klass){
+ this.viz = viz;
+ this.config = viz.config;
+ this.node = viz.config.Node;
+ this.edge = viz.config.Edge;
+ this.animation = new Animation;
+ this.nodeTypes = new klass.Plot.NodeTypes;
+ this.edgeTypes = new klass.Plot.EdgeTypes;
+ this.labels = viz.labels;
+ },
+
+ //Add helpers
+ nodeHelper: NodeHelper,
+ edgeHelper: EdgeHelper,
+
+ Interpolator: {
+ //node/edge property parsers
+ 'map': {
+ 'border': 'color',
+ 'color': 'color',
+ 'width': 'number',
+ 'height': 'number',
+ 'dim': 'number',
+ 'alpha': 'number',
+ 'lineWidth': 'number',
+ 'angularWidth':'number',
+ 'span':'number',
+ 'valueArray':'array-number',
+ 'dimArray':'array-number'
+ //'colorArray':'array-color'
+ },
+
+ //canvas specific parsers
+ 'canvas': {
+ 'globalAlpha': 'number',
+ 'fillStyle': 'color',
+ 'strokeStyle': 'color',
+ 'lineWidth': 'number',
+ 'shadowBlur': 'number',
+ 'shadowColor': 'color',
+ 'shadowOffsetX': 'number',
+ 'shadowOffsetY': 'number',
+ 'miterLimit': 'number'
+ },
+
+ //label parsers
+ 'label': {
+ 'size': 'number',
+ 'color': 'color'
+ },
+
+ //Number interpolator
+ 'compute': function(from, to, delta) {
+ return from + (to - from) * delta;
+ },
+
+ //Position interpolators
+ 'moebius': function(elem, props, delta, vector) {
+ var v = vector.scale(-delta);
+ if(v.norm() < 1) {
+ var x = v.x, y = v.y;
+ var ans = elem.startPos
+ .getc().moebiusTransformation(v);
+ elem.pos.setc(ans.x, ans.y);
+ v.x = x; v.y = y;
+ }
+ },
+
+ 'linear': function(elem, props, delta) {
+ var from = elem.startPos.getc(true);
+ var to = elem.endPos.getc(true);
+ elem.pos.setc(this.compute(from.x, to.x, delta),
+ this.compute(from.y, to.y, delta));
+ },
+
+ 'polar': function(elem, props, delta) {
+ var from = elem.startPos.getp(true);
+ var to = elem.endPos.getp();
+ var ans = to.interpolate(from, delta);
+ elem.pos.setp(ans.theta, ans.rho);
+ },
+
+ //Graph's Node/Edge interpolators
+ 'number': function(elem, prop, delta, getter, setter) {
+ var from = elem[getter](prop, 'start');
+ var to = elem[getter](prop, 'end');
+ elem[setter](prop, this.compute(from, to, delta));
+ },
+
+ 'color': function(elem, prop, delta, getter, setter) {
+ var from = $.hexToRgb(elem[getter](prop, 'start'));
+ var to = $.hexToRgb(elem[getter](prop, 'end'));
+ var comp = this.compute;
+ var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
+ parseInt(comp(from[1], to[1], delta)),
+ parseInt(comp(from[2], to[2], delta))]);
+
+ elem[setter](prop, val);
+ },
+
+ 'array-number': function(elem, prop, delta, getter, setter) {
+ var from = elem[getter](prop, 'start'),
+ to = elem[getter](prop, 'end'),
+ cur = [];
+ for(var i=0, l=from.length; i<l; i++) {
+ var fromi = from[i], toi = to[i];
+ if(fromi.length) {
+ for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
+ curi.push(this.compute(fromi[j], toi[j], delta));
+ }
+ cur.push(curi);
+ } else {
+ cur.push(this.compute(fromi, toi, delta));
+ }
+ }
+ elem[setter](prop, cur);
+ },
+
+ 'node': function(elem, props, delta, map, getter, setter) {
+ map = this[map];
+ if(props) {
+ var len = props.length;
+ for(var i=0; i<len; i++) {
+ var pi = props[i];
+ this[map[pi]](elem, pi, delta, getter, setter);
+ }
+ } else {
+ for(var pi in map) {
+ this[map[pi]](elem, pi, delta, getter, setter);
+ }
+ }
+ },
+
+ 'edge': function(elem, props, delta, mapKey, getter, setter) {
+ var adjs = elem.adjacencies;
+ for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
+ },
+
+ 'node-property': function(elem, props, delta) {
+ this['node'](elem, props, delta, 'map', 'getData', 'setData');
+ },
+
+ 'edge-property': function(elem, props, delta) {
+ this['edge'](elem, props, delta, 'map', 'getData', 'setData');
+ },
+
+ 'label-property': function(elem, props, delta) {
+ this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
+ },
+
+ 'node-style': function(elem, props, delta) {
+ this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
+ },
+
+ 'edge-style': function(elem, props, delta) {
+ this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
+ }
+ },
+
+
+ /*
+ sequence
+
+ Iteratively performs an action while refreshing the state of the visualization.
+
+ Parameters:
+
+ options - (object) An object containing some sequence options described below
+ condition - (function) A function returning a boolean instance in order to stop iterations.
+ step - (function) A function to execute on each step of the iteration.
+ onComplete - (function) A function to execute when the sequence finishes.
+ duration - (number) Duration (in milliseconds) of each step.
+
+ Example:
+ (start code js)
+ var rg = new $jit.RGraph(options);
+ var i = 0;
+ rg.fx.sequence({
+ condition: function() {
+ return i == 10;
+ },
+ step: function() {
+ alert(i++);
+ },
+ onComplete: function() {
+ alert('done!');
+ }
+ });
+ (end code)
+
+ */
+ sequence: function(options) {
+ var that = this;
+ options = $.merge({
+ condition: $.lambda(false),
+ step: $.empty,
+ onComplete: $.empty,
+ duration: 200
+ }, options || {});
+
+ var interval = setInterval(function() {
+ if(options.condition()) {
+ options.step();
+ } else {
+ clearInterval(interval);
+ options.onComplete();
+ }
+ that.viz.refresh(true);
+ }, options.duration);
+ },
+
+ /*
+ prepare
+
+ Prepare graph position and other attribute values before performing an Animation.
+ This method is used internally by the Toolkit.
+
+ See also:
+
+ <Animation>, <Graph.Plot.animate>
+
+ */
+ prepare: function(modes) {
+ var graph = this.viz.graph,
+ accessors = {
+ 'node-property': {
+ 'getter': 'getData',
+ 'setter': 'setData'
+ },
+ 'edge-property': {
+ 'getter': 'getData',
+ 'setter': 'setData'
+ },
+ 'node-style': {
+ 'getter': 'getCanvasStyle',
+ 'setter': 'setCanvasStyle'
+ },
+ 'edge-style': {
+ 'getter': 'getCanvasStyle',
+ 'setter': 'setCanvasStyle'
+ }
+ };
+
+ //parse modes
+ var m = {};
+ if($.type(modes) == 'array') {
+ for(var i=0, len=modes.length; i < len; i++) {
+ var elems = modes[i].split(':');
+ m[elems.shift()] = elems;
+ }
+ } else {
+ for(var p in modes) {
+ if(p == 'position') {
+ m[modes.position] = [];
+ } else {
+ m[p] = $.splat(modes[p]);
+ }
+ }
+ }
+
+ graph.eachNode(function(node) {
+ node.startPos.set(node.pos);
+ $.each(['node-property', 'node-style'], function(p) {
+ if(p in m) {
+ var prop = m[p];
+ for(var i=0, l=prop.length; i < l; i++) {
+ node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
+ }
+ }
+ });
+ $.each(['edge-property', 'edge-style'], function(p) {
+ if(p in m) {
+ var prop = m[p];
+ node.eachAdjacency(function(adj) {
+ for(var i=0, l=prop.length; i < l; i++) {
+ adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
+ }
+ });
+ }
+ });
+ });
+ return m;
+ },
+
+ /*
+ Method: animate
+
+ Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
+
+ Parameters:
+
+ opt - (object) Animation options. The object properties are described below
+ duration - (optional) Described in <Options.Fx>.
+ fps - (optional) Described in <Options.Fx>.
+ hideLabels - (optional|boolean) Whether to hide labels during the animation.
+ modes - (required|object) An object with animation modes (described below).
+
+ Animation modes:
+
+ Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
+ They are represented by an object that has as keys main categories of properties to animate and as values a list
+ of these specific properties. The properties are described below
+
+ position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
+ node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
+ edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
+ label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
+ node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
+ edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ //...tweak some Data, CanvasStyles or LabelData properties...
+ viz.fx.animate({
+ modes: {
+ 'position': 'linear',
+ 'node-property': ['width', 'height'],
+ 'node-style': 'shadowColor',
+ 'label-property': 'size'
+ },
+ hideLabels: false
+ });
+ //...can also be written like this...
+ viz.fx.animate({
+ modes: ['linear',
+ 'node-property:width:height',
+ 'node-style:shadowColor',
+ 'label-property:size'],
+ hideLabels: false
+ });
+ (end code)
+ */
+ animate: function(opt, versor) {
+ opt = $.merge(this.viz.config, opt || {});
+ var that = this,
+ viz = this.viz,
+ graph = viz.graph,
+ interp = this.Interpolator,
+ animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
+ //prepare graph values
+ var m = this.prepare(opt.modes);
+
+ //animate
+ if(opt.hideLabels) this.labels.hideLabels(true);
+ animation.setOptions($.merge(opt, {
+ $animating: false,
+ compute: function(delta) {
+ graph.eachNode(function(node) {
+ for(var p in m) {
+ interp[p](node, m[p], delta, versor);
+ }
+ });
+ that.plot(opt, this.$animating, delta);
+ this.$animating = true;
+ },
+ complete: function() {
+ if(opt.hideLabels) that.labels.hideLabels(false);
+ that.plot(opt);
+ opt.onComplete();
+ opt.onAfterCompute();
+ }
+ })).start();
+ },
+
+ /*
+ nodeFx
+
+ Apply animation to node properties like color, width, height, dim, etc.
+
+ Parameters:
+
+ options - Animation options. This object properties is described below
+ elements - The Elements to be transformed. This is an object that has a properties
+
+ (start code js)
+ 'elements': {
+ //can also be an array of ids
+ 'id': 'id-of-node-to-transform',
+ //properties to be modified. All properties are optional.
+ 'properties': {
+ 'color': '#ccc', //some color
+ 'width': 10, //some width
+ 'height': 10, //some height
+ 'dim': 20, //some dim
+ 'lineWidth': 10 //some line width
+ }
+ }
+ (end code)
+
+ - _reposition_ Whether to recalculate positions and add a motion animation.
+ This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
+
+ - _onComplete_ A method that is called when the animation completes.
+
+ ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
+
+ Example:
+ (start code js)
+ var rg = new RGraph(canvas, config); //can be also Hypertree or ST
+ rg.fx.nodeFx({
+ 'elements': {
+ 'id':'mynodeid',
+ 'properties': {
+ 'color':'#ccf'
+ },
+ 'transition': Trans.Quart.easeOut
+ }
+ });
+ (end code)
+ */
+ nodeFx: function(opt) {
+ var viz = this.viz,
+ graph = viz.graph,
+ animation = this.nodeFxAnimation,
+ options = $.merge(this.viz.config, {
+ 'elements': {
+ 'id': false,
+ 'properties': {}
+ },
+ 'reposition': false
+ });
+ opt = $.merge(options, opt || {}, {
+ onBeforeCompute: $.empty,
+ onAfterCompute: $.empty
+ });
+ //check if an animation is running
+ animation.stopTimer();
+ var props = opt.elements.properties;
+ //set end values for nodes
+ if(!opt.elements.id) {
+ graph.eachNode(function(n) {
+ for(var prop in props) {
+ n.setData(prop, props[prop], 'end');
+ }
+ });
+ } else {
+ var ids = $.splat(opt.elements.id);
+ $.each(ids, function(id) {
+ var n = graph.getNode(id);
+ if(n) {
+ for(var prop in props) {
+ n.setData(prop, props[prop], 'end');
+ }
+ }
+ });
+ }
+ //get keys
+ var propnames = [];
+ for(var prop in props) propnames.push(prop);
+ //add node properties modes
+ var modes = ['node-property:' + propnames.join(':')];
+ //set new node positions
+ if(opt.reposition) {
+ modes.push('linear');
+ viz.compute('end');
+ }
+ //animate
+ this.animate($.merge(opt, {
+ modes: modes,
+ type: 'nodefx'
+ }));
+ },
+
+
+ /*
+ Method: plot
+
+ Plots a <Graph>.
+
+ Parameters:
+
+ opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.fx.plot();
+ (end code)
+
+ */
+ plot: function(opt, animating) {
+ var viz = this.viz,
+ aGraph = viz.graph,
+ canvas = viz.canvas,
+ id = viz.root,
+ that = this,
+ ctx = canvas.getCtx(),
+ min = Math.min,
+ opt = opt || this.viz.controller;
+ opt.clearCanvas && canvas.clear();
+
+ var root = aGraph.getNode(id);
+ if(!root) return;
+
+ var T = !!root.visited;
+ aGraph.eachNode(function(node) {
+ var nodeAlpha = node.getData('alpha');
+ node.eachAdjacency(function(adj) {
+ var nodeTo = adj.nodeTo;
+ if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
+ !animating && opt.onBeforePlotLine(adj);
+ ctx.save();
+ ctx.globalAlpha = min(nodeAlpha,
+ nodeTo.getData('alpha'),
+ adj.getData('alpha'));
+ that.plotLine(adj, canvas, animating);
+ ctx.restore();
+ !animating && opt.onAfterPlotLine(adj);
+ }
+ });
+ ctx.save();
+ if(node.drawn) {
+ !animating && opt.onBeforePlotNode(node);
+ that.plotNode(node, canvas, animating);
+ !animating && opt.onAfterPlotNode(node);
+ }
+ if(!that.labelsHidden && opt.withLabels) {
+ if(node.drawn && nodeAlpha >= 0.95) {
+ that.labels.plotLabel(canvas, node, opt);
+ } else {
+ that.labels.hideLabel(node, false);
+ }
+ }
+ ctx.restore();
+ node.visited = !T;
+ });
+ },
+
+ /*
+ Plots a Subtree.
+ */
+ plotTree: function(node, opt, animating) {
+ var that = this,
+ viz = this.viz,
+ canvas = viz.canvas,
+ config = this.config,
+ ctx = canvas.getCtx();
+ var nodeAlpha = node.getData('alpha');
+ node.eachSubnode(function(elem) {
+ if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
+ var adj = node.getAdjacency(elem.id);
+ !animating && opt.onBeforePlotLine(adj);
+ ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
+ that.plotLine(adj, canvas, animating);
+ !animating && opt.onAfterPlotLine(adj);
+ that.plotTree(elem, opt, animating);
+ }
+ });
+ if(node.drawn) {
+ !animating && opt.onBeforePlotNode(node);
+ this.plotNode(node, canvas, animating);
+ !animating && opt.onAfterPlotNode(node);
+ if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
+ this.labels.plotLabel(canvas, node, opt);
+ else
+ this.labels.hideLabel(node, false);
+ } else {
+ this.labels.hideLabel(node, true);
+ }
+ },
+
+ /*
+ Method: plotNode
+
+ Plots a <Graph.Node>.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ canvas - (object) A <Canvas> element.
+
+ */
+ plotNode: function(node, canvas, animating) {
+ var f = node.getData('type'),
+ ctxObj = this.node.CanvasStyles;
+ if(f != 'none') {
+ var width = node.getData('lineWidth'),
+ color = node.getData('color'),
+ alpha = node.getData('alpha'),
+ ctx = canvas.getCtx();
+
+ ctx.lineWidth = width;
+ ctx.fillStyle = ctx.strokeStyle = color;
+ ctx.globalAlpha = alpha;
+
+ for(var s in ctxObj) {
+ ctx[s] = node.getCanvasStyle(s);
+ }
+
+ this.nodeTypes[f].render.call(this, node, canvas, animating);
+ }
+ },
+
+ /*
+ Method: plotLine
+
+ Plots a <Graph.Adjacence>.
+
+ Parameters:
+
+ adj - (object) A <Graph.Adjacence>.
+ canvas - (object) A <Canvas> instance.
+
+ */
+ plotLine: function(adj, canvas, animating) {
+ var f = adj.getData('type'),
+ ctxObj = this.edge.CanvasStyles;
+ if(f != 'none') {
+ var width = adj.getData('lineWidth'),
+ color = adj.getData('color'),
+ ctx = canvas.getCtx();
+
+ ctx.lineWidth = width;
+ ctx.fillStyle = ctx.strokeStyle = color;
+
+ for(var s in ctxObj) {
+ ctx[s] = adj.getCanvasStyle(s);
+ }
+
+ this.edgeTypes[f].render.call(this, adj, canvas, animating);
+ }
+ }
+
+};
+
+
+
+/*
+ * File: Graph.Label.js
+ *
+*/
+
+/*
+ Object: Graph.Label
+
+ An interface for plotting/hiding/showing labels.
+
+ Description:
+
+ This is a generic interface for plotting/hiding/showing labels.
+ The <Graph.Label> interface is implemented in multiple ways to provide
+ different label types.
+
+ For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
+ HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
+ The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
+
+ All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
+*/
+
+Graph.Label = {};
+
+/*
+ Class: Graph.Label.Native
+
+ Implements labels natively, using the Canvas text API.
+*/
+Graph.Label.Native = new Class({
+ /*
+ Method: plotLabel
+
+ Plots a label for a given node.
+
+ Parameters:
+
+ canvas - (object) A <Canvas> instance.
+ node - (object) A <Graph.Node>.
+ controller - (object) A configuration object.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ var node = viz.graph.getNode('nodeId');
+ viz.labels.plotLabel(viz.canvas, node, viz.config);
+ (end code)
+ */
+ plotLabel: function(canvas, node, controller) {
+ var ctx = canvas.getCtx();
+ var pos = node.pos.getc(true);
+
+ ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
+ ctx.textAlign = node.getLabelData('textAlign');
+ ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
+ ctx.textBaseline = node.getLabelData('textBaseline');
+
+ this.renderLabel(canvas, node, controller);
+ },
+
+ /*
+ renderLabel
+
+ Does the actual rendering of the label in the canvas. The default
+ implementation renders the label close to the position of the node, this
+ method should be overriden to position the labels differently.
+
+ Parameters:
+
+ canvas - A <Canvas> instance.
+ node - A <Graph.Node>.
+ controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
+ */
+ renderLabel: function(canvas, node, controller) {
+ var ctx = canvas.getCtx();
+ var pos = node.pos.getc(true);
+ ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
+ },
+
+ hideLabel: $.empty,
+ hideLabels: $.empty
+});
+
+/*
+ Class: Graph.Label.DOM
+
+ Abstract Class implementing some DOM label methods.
+
+ Implemented by:
+
+ <Graph.Label.HTML> and <Graph.Label.SVG>.
+
+*/
+Graph.Label.DOM = new Class({
+ //A flag value indicating if node labels are being displayed or not.
+ labelsHidden: false,
+ //Label container
+ labelContainer: false,
+ //Label elements hash.
+ labels: {},
+
+ /*
+ Method: getLabelContainer
+
+ Lazy fetcher for the label container.
+
+ Returns:
+
+ The label container DOM element.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ var labelContainer = viz.labels.getLabelContainer();
+ alert(labelContainer.innerHTML);
+ (end code)
+ */
+ getLabelContainer: function() {
+ return this.labelContainer ?
+ this.labelContainer :
+ this.labelContainer = document.getElementById(this.viz.config.labelContainer);
+ },
+
+ /*
+ Method: getLabel
+
+ Lazy fetcher for the label element.
+
+ Parameters:
+
+ id - (string) The label id (which is also a <Graph.Node> id).
+
+ Returns:
+
+ The label element.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ var label = viz.labels.getLabel('someid');
+ alert(label.innerHTML);
+ (end code)
+
+ */
+ getLabel: function(id) {
+ return (id in this.labels && this.labels[id] != null) ?
+ this.labels[id] :
+ this.labels[id] = document.getElementById(id);
+ },
+
+ /*
+ Method: hideLabels
+
+ Hides all labels (by hiding the label container).
+
+ Parameters:
+
+ hide - (boolean) A boolean value indicating if the label container must be hidden or not.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ rg.labels.hideLabels(true);
+ (end code)
+
+ */
+ hideLabels: function (hide) {
+ var container = this.getLabelContainer();
+ if(hide)
+ container.style.display = 'none';
+ else
+ container.style.display = '';
+ this.labelsHidden = hide;
+ },
+
+ /*
+ Method: clearLabels
+
+ Clears the label container.
+
+ Useful when using a new visualization with the same canvas element/widget.
+
+ Parameters:
+
+ force - (boolean) Forces deletion of all labels.
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.labels.clearLabels();
+ (end code)
+ */
+ clearLabels: function(force) {
+ for(var id in this.labels) {
+ if (force || !this.viz.graph.hasNode(id)) {
+ this.disposeLabel(id);
+ delete this.labels[id];
+ }
+ }
+ },
+
+ /*
+ Method: disposeLabel
+
+ Removes a label.
+
+ Parameters:
+
+ id - (string) A label id (which generally is also a <Graph.Node> id).
+
+ Example:
+ (start code js)
+ var viz = new $jit.Viz(options);
+ viz.labels.disposeLabel('labelid');
+ (end code)
+ */
+ disposeLabel: function(id) {
+ var elem = this.getLabel(id);
+ if(elem && elem.parentNode) {
+ elem.parentNode.removeChild(elem);
+ }
+ },
+
+ /*
+ Method: hideLabel
+
+ Hides the corresponding <Graph.Node> label.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
+ show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
+
+ Example:
+ (start code js)
+ var rg = new $jit.Viz(options);
+ viz.labels.hideLabel(viz.graph.getNode('someid'), false);
+ (end code)
+ */
+ hideLabel: function(node, show) {
+ node = $.splat(node);
+ var st = show ? "" : "none", lab, that = this;
+ $.each(node, function(n) {
+ var lab = that.getLabel(n.id);
+ if (lab) {
+ lab.style.display = st;
+ }
+ });
+ },
+
+ /*
+ fitsInCanvas
+
+ Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
+
+ Parameters:
+
+ pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
+ canvas - A <Canvas> instance.
+
+ Returns:
+
+ A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
+
+ */
+ fitsInCanvas: function(pos, canvas) {
+ var size = canvas.getSize();
+ if(pos.x >= size.width || pos.x < 0
+ || pos.y >= size.height || pos.y < 0) return false;
+ return true;
+ }
+});
+
+/*
+ Class: Graph.Label.HTML
+
+ Implements HTML labels.
+
+ Extends:
+
+ All <Graph.Label.DOM> methods.
+
+*/
+Graph.Label.HTML = new Class({
+ Implements: Graph.Label.DOM,
+
+ /*
+ Method: plotLabel
+
+ Plots a label for a given node.
+
+ Parameters:
+
+ canvas - (object) A <Canvas> instance.
+ node - (object) A <Graph.Node>.
+ controller - (object) A configuration object.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ var node = viz.graph.getNode('nodeId');
+ viz.labels.plotLabel(viz.canvas, node, viz.config);
+ (end code)
+
+
+ */
+ plotLabel: function(canvas, node, controller) {
+ var id = node.id, tag = this.getLabel(id);
+
+ if(!tag && !(tag = document.getElementById(id))) {
+ tag = document.createElement('div');
+ var container = this.getLabelContainer();
+ tag.id = id;
+ tag.className = 'node';
+ tag.style.position = 'absolute';
+ controller.onCreateLabel(tag, node);
+ container.appendChild(tag);
+ this.labels[node.id] = tag;
+ }
+
+ this.placeLabel(tag, node, controller);
+ }
+});
+
+/*
+ Class: Graph.Label.SVG
+
+ Implements SVG labels.
+
+ Extends:
+
+ All <Graph.Label.DOM> methods.
+*/
+Graph.Label.SVG = new Class({
+ Implements: Graph.Label.DOM,
+
+ /*
+ Method: plotLabel
+
+ Plots a label for a given node.
+
+ Parameters:
+
+ canvas - (object) A <Canvas> instance.
+ node - (object) A <Graph.Node>.
+ controller - (object) A configuration object.
+
+ Example:
+
+ (start code js)
+ var viz = new $jit.Viz(options);
+ var node = viz.graph.getNode('nodeId');
+ viz.labels.plotLabel(viz.canvas, node, viz.config);
+ (end code)
+
+
+ */
+ plotLabel: function(canvas, node, controller) {
+ var id = node.id, tag = this.getLabel(id);
+ if(!tag && !(tag = document.getElementById(id))) {
+ var ns = 'http://www.w3.org/2000/svg';
+ tag = document.createElementNS(ns, 'svg:text');
+ var tspan = document.createElementNS(ns, 'svg:tspan');
+ tag.appendChild(tspan);
+ var container = this.getLabelContainer();
+ tag.setAttribute('id', id);
+ tag.setAttribute('class', 'node');
+ container.appendChild(tag);
+ controller.onCreateLabel(tag, node);
+ this.labels[node.id] = tag;
+ }
+ this.placeLabel(tag, node, controller);
+ }
+});
+
+
+
+Graph.Geom = new Class({
+
+ initialize: function(viz) {
+ this.viz = viz;
+ this.config = viz.config;
+ this.node = viz.config.Node;
+ this.edge = viz.config.Edge;
+ },
+ /*
+ Applies a translation to the tree.
+
+ Parameters:
+
+ pos - A <Complex> number specifying translation vector.
+ prop - A <Graph.Node> position property ('pos', 'start' or 'end').
+
+ Example:
+
+ (start code js)
+ st.geom.translate(new Complex(300, 100), 'end');
+ (end code)
+ */
+ translate: function(pos, prop) {
+ prop = $.splat(prop);
+ this.viz.graph.eachNode(function(elem) {
+ $.each(prop, function(p) { elem.getPos(p).$add(pos); });
+ });
+ },
+ /*
+ Hides levels of the tree until it properly fits in canvas.
+ */
+ setRightLevelToShow: function(node, canvas, callback) {
+ var level = this.getRightLevelToShow(node, canvas),
+ fx = this.viz.labels,
+ opt = $.merge({
+ execShow:true,
+ execHide:true,
+ onHide: $.empty,
+ onShow: $.empty
+ }, callback || {});
+ node.eachLevel(0, this.config.levelsToShow, function(n) {
+ var d = n._depth - node._depth;
+ if(d > level) {
+ opt.onHide(n);
+ if(opt.execHide) {
+ n.drawn = false;
+ n.exist = false;
+ fx.hideLabel(n, false);
+ }
+ } else {
+ opt.onShow(n);
+ if(opt.execShow) {
+ n.exist = true;
+ }
+ }
+ });
+ node.drawn= true;
+ },
+ /*
+ Returns the right level to show for the current tree in order to fit in canvas.
+ */
+ getRightLevelToShow: function(node, canvas) {
+ var config = this.config;
+ var level = config.levelsToShow;
+ var constrained = config.constrained;
+ if(!constrained) return level;
+ while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
+ return level;
+ }
+});
+
+/*
+ * File: Loader.js
+ *
+ */
+
+/*
+ Object: Loader
+
+ Provides methods for loading and serving JSON data.
+*/
+var Loader = {
+ construct: function(json) {
+ var isGraph = ($.type(json) == 'array');
+ var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
+ if(!isGraph)
+ //make tree
+ (function (ans, json) {
+ ans.addNode(json);
+ if(json.children) {
+ for(var i=0, ch = json.children; i<ch.length; i++) {
+ ans.addAdjacence(json, ch[i]);
+ arguments.callee(ans, ch[i]);
+ }
+ }
+ })(ans, json);
+ else
+ //make graph
+ (function (ans, json) {
+ var getNode = function(id) {
+ for(var i=0, l=json.length; i<l; i++) {
+ if(json[i].id == id) {
+ return json[i];
+ }
+ }
+ // The node was not defined in the JSON
+ // Let's create it
+ var newNode = {
+ "id" : id,
+ "name" : id
+ };
+ return ans.addNode(newNode);
+ };
+
+ for(var i=0, l=json.length; i<l; i++) {
+ ans.addNode(json[i]);
+ var adj = json[i].adjacencies;
+ if (adj) {
+ for(var j=0, lj=adj.length; j<lj; j++) {
+ var node = adj[j], data = {};
+ if(typeof adj[j] != 'string') {
+ data = $.merge(node.data, {});
+ node = node.nodeTo;
+ }
+ ans.addAdjacence(json[i], getNode(node), data);
+ }
+ }
+ }
+ })(ans, json);
+
+ return ans;
+ },
+
+ /*
+ Method: loadJSON
+
+ Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
+
+ A JSON tree or graph structure consists of nodes, each having as properties
+
+ id - (string) A unique identifier for the node
+ name - (string) A node's name
+ data - (object) The data optional property contains a hash (i.e {})
+ where you can store all the information you want about this node.
+
+ For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
+
+ Example:
+
+ (start code js)
+ var json = {
+ "id": "aUniqueIdentifier",
+ "name": "usually a nodes name",
+ "data": {
+ "some key": "some value",
+ "some other key": "some other value"
+ },
+ "children": [ *other nodes or empty* ]
+ };
+ (end code)
+
+ JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
+ For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
+
+ There are two types of *Graph* structures, *simple* and *extended* graph structures.
+
+ For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
+ id of the node connected to the main node.
+
+ Example:
+
+ (start code js)
+ var json = [
+ {
+ "id": "aUniqueIdentifier",
+ "name": "usually a nodes name",
+ "data": {
+ "some key": "some value",
+ "some other key": "some other value"
+ },
+ "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
+ },
+
+ 'other nodes go here...'
+ ];
+ (end code)
+
+ For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
+
+ nodeTo - (string) The other node connected by this adjacency.
+ data - (object) A data property, where we can store custom key/value information.
+
+ Example:
+
+ (start code js)
+ var json = [
+ {
+ "id": "aUniqueIdentifier",
+ "name": "usually a nodes name",
+ "data": {
+ "some key": "some value",
+ "some other key": "some other value"
+ },
+ "adjacencies": [
+ {
+ nodeTo:"aNodeId",
+ data: {} //put whatever you want here
+ },
+ 'other adjacencies go here...'
+ },
+
+ 'other nodes go here...'
+ ];
+ (end code)
+
+ About the data property:
+
+ As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
+ You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
+ have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
+
+ For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
+ <Options.Node> will override the general value for that option with that particular value. For this to work
+ however, you do have to set *overridable = true* in <Options.Node>.
+
+ The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
+ if <Options.Edge> has *overridable = true*.
+
+ When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
+ since this is the value which will be taken into account when creating the layout.
+ The same thing goes for the *$color* parameter.
+
+ In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
+ *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
+ canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
+ to the *shadowBlur* property.
+
+ These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
+ by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
+
+ Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
+ information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
+
+ loadJSON Parameters:
+
+ json - A JSON Tree or Graph structure.
+ i - For Graph structures only. Sets the indexed node as root for the visualization.
+
+ */
+ loadJSON: function(json, i) {
+ this.json = json;
+ //if they're canvas labels erase them.
+ if(this.labels && this.labels.clearLabels) {
+ this.labels.clearLabels(true);
+ }
+ this.graph = this.construct(json);
+ if($.type(json) != 'array'){
+ this.root = json.id;
+ } else {
+ this.root = json[i? i : 0].id;
+ }
+ },
+
+ /*
+ Method: toJSON
+
+ Returns a JSON tree/graph structure from the visualization's <Graph>.
+ See <Loader.loadJSON> for the graph formats available.
+
+ See also:
+
+ <Loader.loadJSON>
+
+ Parameters:
+
+ type - (string) Default's "tree". The type of the JSON structure to be returned.
+ Possible options are "tree" or "graph".
+ */
+ toJSON: function(type) {
+ type = type || "tree";
+ if(type == 'tree') {
+ var ans = {};
+ var rootNode = this.graph.getNode(this.root);
+ var ans = (function recTree(node) {
+ var ans = {};
+ ans.id = node.id;
+ ans.name = node.name;
+ ans.data = node.data;
+ var ch =[];
+ node.eachSubnode(function(n) {
+ ch.push(recTree(n));
+ });
+ ans.children = ch;
+ return ans;
+ })(rootNode);
+ return ans;
+ } else {
+ var ans = [];
+ var T = !!this.graph.getNode(this.root).visited;
+ this.graph.eachNode(function(node) {
+ var ansNode = {};
+ ansNode.id = node.id;
+ ansNode.name = node.name;
+ ansNode.data = node.data;
+ var adjs = [];
+ node.eachAdjacency(function(adj) {
+ var nodeTo = adj.nodeTo;
+ if(!!nodeTo.visited === T) {
+ var ansAdj = {};
+ ansAdj.nodeTo = nodeTo.id;
+ ansAdj.data = adj.data;
+ adjs.push(ansAdj);
+ }
+ });
+ ansNode.adjacencies = adjs;
+ ans.push(ansNode);
+ node.visited = !T;
+ });
+ return ans;
+ }
+ }
+};
+
+
+
+/*
+ * File: Layouts.js
+ *
+ * Implements base Tree and Graph layouts.
+ *
+ * Description:
+ *
+ * Implements base Tree and Graph layouts like Radial, Tree, etc.
+ *
+ */
+
+/*
+ * Object: Layouts
+ *
+ * Parent object for common layouts.
+ *
+ */
+var Layouts = $jit.Layouts = {};
+
+
+//Some util shared layout functions are defined here.
+var NodeDim = {
+ label: null,
+
+ compute: function(graph, prop, opt) {
+ this.initializeLabel(opt);
+ var label = this.label, style = label.style;
+ graph.eachNode(function(n) {
+ var autoWidth = n.getData('autoWidth'),
+ autoHeight = n.getData('autoHeight');
+ if(autoWidth || autoHeight) {
+ //delete dimensions since these are
+ //going to be overridden now.
+ delete n.data.$width;
+ delete n.data.$height;
+ delete n.data.$dim;
+
+ var width = n.getData('width'),
+ height = n.getData('height');
+ //reset label dimensions
+ style.width = autoWidth? 'auto' : width + 'px';
+ style.height = autoHeight? 'auto' : height + 'px';
+
+ //TODO(nico) should let the user choose what to insert here.
+ label.innerHTML = n.name;
+
+ var offsetWidth = label.offsetWidth,
+ offsetHeight = label.offsetHeight;
+ var type = n.getData('type');
+ if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
+ n.setData('width', offsetWidth);
+ n.setData('height', offsetHeight);
+ } else {
+ var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
+ n.setData('width', dim);
+ n.setData('height', dim);
+ n.setData('dim', dim);
+ }
+ }
+ });
+ },
+
+ initializeLabel: function(opt) {
+ if(!this.label) {
+ this.label = document.createElement('div');
+ document.body.appendChild(this.label);
+ }
+ this.setLabelStyles(opt);
+ },
+
+ setLabelStyles: function(opt) {
+ $.extend(this.label.style, {
+ 'visibility': 'hidden',
+ 'position': 'absolute',
+ 'width': 'auto',
+ 'height': 'auto'
+ });
+ this.label.className = 'jit-autoadjust-label';
+ }
+};
+
+
+/*
+ * Class: Layouts.Tree
+ *
+ * Implements a Tree Layout.
+ *
+ * Implemented By:
+ *
+ * <ST>
+ *
+ * Inspired by:
+ *
+ * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
+ *
+ */
+Layouts.Tree = (function() {
+ //Layout functions
+ var slice = Array.prototype.slice;
+
+ /*
+ Calculates the max width and height nodes for a tree level
+ */
+ function getBoundaries(graph, config, level, orn, prop) {
+ var dim = config.Node;
+ var multitree = config.multitree;
+ if (dim.overridable) {
+ var w = -1, h = -1;
+ graph.eachNode(function(n) {
+ if (n._depth == level
+ && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
+ var dw = n.getData('width', prop);
+ var dh = n.getData('height', prop);
+ w = (w < dw) ? dw : w;
+ h = (h < dh) ? dh : h;
+ }
+ });
+ return {
+ 'width' : w < 0 ? dim.width : w,
+ 'height' : h < 0 ? dim.height : h
+ };
+ } else {
+ return dim;
+ }
+ }
+
+
+ function movetree(node, prop, val, orn) {
+ var p = (orn == "left" || orn == "right") ? "y" : "x";
+ node.getPos(prop)[p] += val;
+ }
+
+
+ function moveextent(extent, val) {
+ var ans = [];
+ $.each(extent, function(elem) {
+ elem = slice.call(elem);
+ elem[0] += val;
+ elem[1] += val;
+ ans.push(elem);
+ });
+ return ans;
+ }
+
+
+ function merge(ps, qs) {
+ if (ps.length == 0)
+ return qs;
+ if (qs.length == 0)
+ return ps;
+ var p = ps.shift(), q = qs.shift();
+ return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
+ }
+
+
+ function mergelist(ls, def) {
+ def = def || [];
+ if (ls.length == 0)
+ return def;
+ var ps = ls.pop();
+ return mergelist(ls, merge(ps, def));
+ }
+
+
+ function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
+ if (ext1.length <= i || ext2.length <= i)
+ return 0;
+
+ var p = ext1[i][1], q = ext2[i][0];
+ return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
+ + subtreeOffset, p - q + siblingOffset);
+ }
+
+
+ function fitlistl(es, subtreeOffset, siblingOffset) {
+ function $fitlistl(acc, es, i) {
+ if (es.length <= i)
+ return [];
+ var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
+ return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
+ }
+ ;
+ return $fitlistl( [], es, 0);
+ }
+
+
+ function fitlistr(es, subtreeOffset, siblingOffset) {
+ function $fitlistr(acc, es, i) {
+ if (es.length <= i)
+ return [];
+ var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
+ return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
+ }
+ ;
+ es = slice.call(es);
+ var ans = $fitlistr( [], es.reverse(), 0);
+ return ans.reverse();
+ }
+
+
+ function fitlist(es, subtreeOffset, siblingOffset, align) {
+ var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
+ subtreeOffset, siblingOffset);
+
+ if (align == "left")
+ esr = esl;
+ else if (align == "right")
+ esl = esr;
+
+ for ( var i = 0, ans = []; i < esl.length; i++) {
+ ans[i] = (esl[i] + esr[i]) / 2;
+ }
+ return ans;
+ }
+
+
+ function design(graph, node, prop, config, orn) {
+ var multitree = config.multitree;
+ var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
+ var ind = +(orn == "left" || orn == "right");
+ var p = auxp[ind], notp = auxp[1 - ind];
+
+ var cnode = config.Node;
+ var s = auxs[ind], nots = auxs[1 - ind];
+
+ var siblingOffset = config.siblingOffset;
+ var subtreeOffset = config.subtreeOffset;
+ var align = config.align;
+
+ function $design(node, maxsize, acum) {
+ var sval = node.getData(s, prop);
+ var notsval = maxsize
+ || (node.getData(nots, prop));
+
+ var trees = [], extents = [], chmaxsize = false;
+ var chacum = notsval + config.levelDistance;
+ node.eachSubnode(function(n) {
+ if (n.exist
+ && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
+
+ if (!chmaxsize)
+ chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
+
+ var s = $design(n, chmaxsize[nots], acum + chacum);
+ trees.push(s.tree);
+ extents.push(s.extent);
+ }
+ });
+ var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
+ for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
+ movetree(trees[i], prop, positions[i], orn);
+ pextents.push(moveextent(extents[i], positions[i]));
+ }
+ var resultextent = [ [ -sval / 2, sval / 2 ] ]
+ .concat(mergelist(pextents));
+ node.getPos(prop)[p] = 0;
+
+ if (orn == "top" || orn == "left") {
+ node.getPos(prop)[notp] = acum;
+ } else {
+ node.getPos(prop)[notp] = -acum;
+ }
+
+ return {
+ tree : node,
+ extent : resultextent
+ };
+ }
+
+ $design(node, false, 0);
+ }
+
+
+ return new Class({
+ /*
+ Method: compute
+
+ Computes nodes' positions.
+
+ */
+ compute : function(property, computeLevels) {
+ var prop = property || 'start';
+ var node = this.graph.getNode(this.root);
+ $.extend(node, {
+ 'drawn' : true,
+ 'exist' : true,
+ 'selected' : true
+ });
+ NodeDim.compute(this.graph, prop, this.config);
+ if (!!computeLevels || !("_depth" in node)) {
+ this.graph.computeLevels(this.root, 0, "ignore");
+ }
+
+ this.computePositions(node, prop);
+ },
+
+ computePositions : function(node, prop) {
+ var config = this.config;
+ var multitree = config.multitree;
+ var align = config.align;
+ var indent = align !== 'center' && config.indent;
+ var orn = config.orientation;
+ var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
+ var that = this;
+ $.each(orns, function(orn) {
+ //calculate layout
+ design(that.graph, node, prop, that.config, orn, prop);
+ var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
+ //absolutize
+ (function red(node) {
+ node.eachSubnode(function(n) {
+ if (n.exist
+ && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
+
+ n.getPos(prop)[i] += node.getPos(prop)[i];
+ if (indent) {
+ n.getPos(prop)[i] += align == 'left' ? indent : -indent;
+ }
+ red(n);
+ }
+ });
+ })(node);
+ });
+ }
+ });
+
+})();
+
+/*
+ * File: Spacetree.js
+ */
+
+/*
+ Class: ST
+
+ A Tree layout with advanced contraction and expansion animations.
+
+ Inspired by:
+
+ SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
+ <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
+
+ Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
+
+ Note:
+
+ This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Tree>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
+ levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
+ levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
+ Node.type - Described in <Options.Node>. Default's set to *rectangle*.
+ offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
+ offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
+ duration - Described in <Options.Fx>. It's default value has been changed to *700*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <ST.Op> instance.
+ fx - Access a <ST.Plot> instance.
+ labels - Access a <ST.Label> interface implementation.
+
+ */
+
+$jit.ST= (function() {
+ // Define some private methods first...
+ // Nodes in path
+ var nodesInPath = [];
+ // Nodes to contract
+ function getNodesToHide(node) {
+ node = node || this.clickedNode;
+ if(!this.config.constrained) {
+ return [];
+ }
+ var Geom = this.geom;
+ var graph = this.graph;
+ var canvas = this.canvas;
+ var level = node._depth, nodeArray = [];
+ graph.eachNode(function(n) {
+ if(n.exist && !n.selected) {
+ if(n.isDescendantOf(node.id)) {
+ if(n._depth <= level) nodeArray.push(n);
+ } else {
+ nodeArray.push(n);
+ }
+ }
+ });
+ var leafLevel = Geom.getRightLevelToShow(node, canvas);
+ node.eachLevel(leafLevel, leafLevel, function(n) {
+ if(n.exist && !n.selected) nodeArray.push(n);
+ });
+
+ for (var i = 0; i < nodesInPath.length; i++) {
+ var n = this.graph.getNode(nodesInPath[i]);
+ if(!n.isDescendantOf(node.id)) {
+ nodeArray.push(n);
+ }
+ }
+ return nodeArray;
+ };
+ // Nodes to expand
+ function getNodesToShow(node) {
+ var nodeArray = [], config = this.config;
+ node = node || this.clickedNode;
+ this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
+ if(config.multitree && !('$orn' in n.data)
+ && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
+ nodeArray.push(n);
+ } else if(n.drawn && !n.anySubnode("drawn")) {
+ nodeArray.push(n);
+ }
+ });
+ return nodeArray;
+ };
+ // Now define the actual class.
+ return new Class({
+
+ Implements: [Loader, Extras, Layouts.Tree],
+
+ initialize: function(controller) {
+ var $ST = $jit.ST;
+
+ var config= {
+ levelsToShow: 2,
+ levelDistance: 30,
+ constrained: true,
+ Node: {
+ type: 'rectangle'
+ },
+ duration: 700,
+ offsetX: 0,
+ offsetY: 0
+ };
+
+ this.controller = this.config = $.merge(
+ Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
+ "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
+
+ var canvasConfig = this.config;
+ if(canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': true
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
+ this.labels = new $ST.Label[canvasConfig.Label.type](this);
+ this.fx = new $ST.Plot(this, $ST);
+ this.op = new $ST.Op(this);
+ this.group = new $ST.Group(this);
+ this.geom = new $ST.Geom(this);
+ this.clickedNode= null;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+ Method: plot
+
+ Plots the <ST>. This is a shortcut to *fx.plot*.
+
+ */
+ plot: function() { this.fx.plot(this.controller); },
+
+
+ /*
+ Method: switchPosition
+
+ Switches the tree orientation.
+
+ Parameters:
+
+ pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
+ method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
+ onComplete - (optional|object) This callback is called once the "switching" animation is complete.
+
+ Example:
+
+ (start code js)
+ st.switchPosition("right", "animate", {
+ onComplete: function() {
+ alert('completed!');
+ }
+ });
+ (end code)
+ */
+ switchPosition: function(pos, method, onComplete) {
+ var Geom = this.geom, Plot = this.fx, that = this;
+ if(!Plot.busy) {
+ Plot.busy = true;
+ this.contract({
+ onComplete: function() {
+ Geom.switchOrientation(pos);
+ that.compute('end', false);
+ Plot.busy = false;
+ if(method == 'animate') {
+ that.onClick(that.clickedNode.id, onComplete);
+ } else if(method == 'replot') {
+ that.select(that.clickedNode.id, onComplete);
+ }
+ }
+ }, pos);
+ }
+ },
+
+ /*
+ Method: switchAlignment
+
+ Switches the tree alignment.
+
+ Parameters:
+
+ align - (string) The new tree alignment. Possible values are "left", "center" and "right".
+ method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
+ onComplete - (optional|object) This callback is called once the "switching" animation is complete.
+
+ Example:
+
+ (start code js)
+ st.switchAlignment("right", "animate", {
+ onComplete: function() {
+ alert('completed!');
+ }
+ });
+ (end code)
+ */
+ switchAlignment: function(align, method, onComplete) {
+ this.config.align = align;
+ if(method == 'animate') {
+ this.select(this.clickedNode.id, onComplete);
+ } else if(method == 'replot') {
+ this.onClick(this.clickedNode.id, onComplete);
+ }
+ },
+
+ /*
+ Method: addNodeInPath
+
+ Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
+
+
+ Parameters:
+
+ id - (string) A <Graph.Node> id.
+
+ Example:
+
+ (start code js)
+ st.addNodeInPath("nodeId");
+ (end code)
+ */
+ addNodeInPath: function(id) {
+ nodesInPath.push(id);
+ this.select((this.clickedNode && this.clickedNode.id) || this.root);
+ },
+
+ /*
+ Method: clearNodesInPath
+
+ Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
+
+ See also:
+
+ <ST.addNodeInPath>
+
+ Example:
+
+ (start code js)
+ st.clearNodesInPath();
+ (end code)
+ */
+ clearNodesInPath: function(id) {
+ nodesInPath.length = 0;
+ this.select((this.clickedNode && this.clickedNode.id) || this.root);
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+
+ */
+ refresh: function() {
+ this.reposition();
+ this.select((this.clickedNode && this.clickedNode.id) || this.root);
+ },
+
+ reposition: function() {
+ this.graph.computeLevels(this.root, 0, "ignore");
+ this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
+ this.graph.eachNode(function(n) {
+ if(n.exist) n.drawn = true;
+ });
+ this.compute('end');
+ },
+
+ requestNodes: function(node, onComplete) {
+ var handler = $.merge(this.controller, onComplete),
+ lev = this.config.levelsToShow;
+ if(handler.request) {
+ var leaves = [], d = node._depth;
+ node.eachLevel(0, lev, function(n) {
+ if(n.drawn &&
+ !n.anySubnode()) {
+ leaves.push(n);
+ n._level = lev - (n._depth - d);
+ }
+ });
+ this.group.requestNodes(leaves, handler);
+ }
+ else
+ handler.onComplete();
+ },
+
+ contract: function(onComplete, switched) {
+ var orn = this.config.orientation;
+ var Geom = this.geom, Group = this.group;
+ if(switched) Geom.switchOrientation(switched);
+ var nodes = getNodesToHide.call(this);
+ if(switched) Geom.switchOrientation(orn);
+ Group.contract(nodes, $.merge(this.controller, onComplete));
+ },
+
+ move: function(node, onComplete) {
+ this.compute('end', false);
+ var move = onComplete.Move, offset = {
+ 'x': move.offsetX,
+ 'y': move.offsetY
+ };
+ if(move.enable) {
+ this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
+ }
+ this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
+ },
+
+ expand: function (node, onComplete) {
+ var nodeArray = getNodesToShow.call(this, node);
+ this.group.expand(nodeArray, $.merge(this.controller, onComplete));
+ },
+
+ selectPath: function(node) {
+ var that = this;
+ this.graph.eachNode(function(n) { n.selected = false; });
+ function path(node) {
+ if(node == null || node.selected) return;
+ node.selected = true;
+ $.each(that.group.getSiblings([node])[node.id],
+ function(n) {
+ n.exist = true;
+ n.drawn = true;
+ });
+ var parents = node.getParents();
+ parents = (parents.length > 0)? parents[0] : null;
+ path(parents);
+ };
+ for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
+ path(this.graph.getNode(ns[i]));
+ }
+ },
+
+ /*
+ Method: setRoot
+
+ Switches the current root node. Changes the topology of the Tree.
+
+ Parameters:
+ id - (string) The id of the node to be set as root.
+ method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
+ onComplete - (optional|object) An action to perform after the animation (if any).
+
+ Example:
+
+ (start code js)
+ st.setRoot('nodeId', 'animate', {
+ onComplete: function() {
+ alert('complete!');
+ }
+ });
+ (end code)
+ */
+ setRoot: function(id, method, onComplete) {
+ if(this.busy) return;
+ this.busy = true;
+ var that = this, canvas = this.canvas;
+ var rootNode = this.graph.getNode(this.root);
+ var clickedNode = this.graph.getNode(id);
+ function $setRoot() {
+ if(this.config.multitree && clickedNode.data.$orn) {
+ var orn = clickedNode.data.$orn;
+ var opp = {
+ 'left': 'right',
+ 'right': 'left',
+ 'top': 'bottom',
+ 'bottom': 'top'
+ }[orn];
+ rootNode.data.$orn = opp;
+ (function tag(rootNode) {
+ rootNode.eachSubnode(function(n) {
+ if(n.id != id) {
+ n.data.$orn = opp;
+ tag(n);
+ }
+ });
+ })(rootNode);
+ delete clickedNode.data.$orn;
+ }
+ this.root = id;
+ this.clickedNode = clickedNode;
+ this.graph.computeLevels(this.root, 0, "ignore");
+ this.geom.setRightLevelToShow(clickedNode, canvas, {
+ execHide: false,
+ onShow: function(node) {
+ if(!node.drawn) {
+ node.drawn = true;
+ node.setData('alpha', 1, 'end');
+ node.setData('alpha', 0);
+ node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
+ }
+ }
+ });
+ this.compute('end');
+ this.busy = true;
+ this.fx.animate({
+ modes: ['linear', 'node-property:alpha'],
+ onComplete: function() {
+ that.busy = false;
+ that.onClick(id, {
+ onComplete: function() {
+ onComplete && onComplete.onComplete();
+ }
+ });
+ }
+ });
+ }
+
+ // delete previous orientations (if any)
+ delete rootNode.data.$orns;
+
+ if(method == 'animate') {
+ $setRoot.call(this);
+ that.selectPath(clickedNode);
+ } else if(method == 'replot') {
+ $setRoot.call(this);
+ this.select(this.root);
+ }
+ },
+
+ /*
+ Method: addSubtree
+
+ Adds a subtree.
+
+ Parameters:
+ subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
+ method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
+ onComplete - (optional|object) An action to perform after the animation (if any).
+
+ Example:
+
+ (start code js)
+ st.addSubtree(json, 'animate', {
+ onComplete: function() {
+ alert('complete!');
+ }
+ });
+ (end code)
+ */
+ addSubtree: function(subtree, method, onComplete) {
+ if(method == 'replot') {
+ this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
+ } else if (method == 'animate') {
+ this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
+ }
+ },
+
+ /*
+ Method: removeSubtree
+
+ Removes a subtree.
+
+ Parameters:
+ id - (string) The _id_ of the subtree to be removed.
+ removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
+ method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
+ onComplete - (optional|object) An action to perform after the animation (if any).
+
+ Example:
+
+ (start code js)
+ st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
+ onComplete: function() {
+ alert('complete!');
+ }
+ });
+ (end code)
+
+ */
+ removeSubtree: function(id, removeRoot, method, onComplete) {
+ var node = this.graph.getNode(id), subids = [];
+ node.eachLevel(+!removeRoot, false, function(n) {
+ subids.push(n.id);
+ });
+ if(method == 'replot') {
+ this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
+ } else if (method == 'animate') {
+ this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
+ }
+ },
+
+ /*
+ Method: select
+
+ Selects a node in the <ST> without performing an animation. Useful when selecting
+ nodes which are currently hidden or deep inside the tree.
+
+ Parameters:
+ id - (string) The id of the node to select.
+ onComplete - (optional|object) an onComplete callback.
+
+ Example:
+ (start code js)
+ st.select('mynodeid', {
+ onComplete: function() {
+ alert('complete!');
+ }
+ });
+ (end code)
+ */
+ select: function(id, onComplete) {
+ var group = this.group, geom = this.geom;
+ var node= this.graph.getNode(id), canvas = this.canvas;
+ var root = this.graph.getNode(this.root);
+ var complete = $.merge(this.controller, onComplete);
+ var that = this;
+
+ complete.onBeforeCompute(node);
+ this.selectPath(node);
+ this.clickedNode= node;
+ this.requestNodes(node, {
+ onComplete: function(){
+ group.hide(group.prepare(getNodesToHide.call(that)), complete);
+ geom.setRightLevelToShow(node, canvas);
+ that.compute("current");
+ that.graph.eachNode(function(n) {
+ var pos = n.pos.getc(true);
+ n.startPos.setc(pos.x, pos.y);
+ n.endPos.setc(pos.x, pos.y);
+ n.visited = false;
+ });
+ var offset = { x: complete.offsetX, y: complete.offsetY };
+ that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
+ group.show(getNodesToShow.call(that));
+ that.plot();
+ complete.onAfterCompute(that.clickedNode);
+ complete.onComplete();
+ }
+ });
+ },
+
+ /*
+ Method: onClick
+
+ Animates the <ST> to center the node specified by *id*.
+
+ Parameters:
+
+ id - (string) A node id.
+ options - (optional|object) A group of options and callbacks described below.
+ onComplete - (object) An object callback called when the animation finishes.
+ Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
+
+ Example:
+
+ (start code js)
+ st.onClick('mynodeid', {
+ Move: {
+ enable: true,
+ offsetX: 30,
+ offsetY: 5
+ },
+ onComplete: function() {
+ alert('yay!');
+ }
+ });
+ (end code)
+
+ */
+ onClick: function (id, options) {
+ var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
+ var innerController = {
+ Move: {
+ enable: true,
+ offsetX: config.offsetX || 0,
+ offsetY: config.offsetY || 0
+ },
+ setRightLevelToShowConfig: false,
+ onBeforeRequest: $.empty,
+ onBeforeContract: $.empty,
+ onBeforeMove: $.empty,
+ onBeforeExpand: $.empty
+ };
+ var complete = $.merge(this.controller, innerController, options);
+
+ if(!this.busy) {
+ this.busy = true;
+ var node = this.graph.getNode(id);
+ this.selectPath(node, this.clickedNode);
+ this.clickedNode = node;
+ complete.onBeforeCompute(node);
+ complete.onBeforeRequest(node);
+ this.requestNodes(node, {
+ onComplete: function() {
+ complete.onBeforeContract(node);
+ that.contract({
+ onComplete: function() {
+ Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
+ complete.onBeforeMove(node);
+ that.move(node, {
+ Move: complete.Move,
+ onComplete: function() {
+ complete.onBeforeExpand(node);
+ that.expand(node, {
+ onComplete: function() {
+ that.busy = false;
+ complete.onAfterCompute(id);
+ complete.onComplete();
+ }
+ }); // expand
+ }
+ }); // move
+ }
+ });// contract
+ }
+ });// request
+ }
+ }
+ });
+
+})();
+
+$jit.ST.$extend = true;
+
+/*
+ Class: ST.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+*/
+$jit.ST.Op = new Class({
+
+ Implements: Graph.Op
+
+});
+
+/*
+
+ Performs operations on group of nodes.
+
+*/
+$jit.ST.Group = new Class({
+
+ initialize: function(viz) {
+ this.viz = viz;
+ this.canvas = viz.canvas;
+ this.config = viz.config;
+ this.animation = new Animation;
+ this.nodes = null;
+ },
+
+ /*
+
+ Calls the request method on the controller to request a subtree for each node.
+ */
+ requestNodes: function(nodes, controller) {
+ var counter = 0, len = nodes.length, nodeSelected = {};
+ var complete = function() { controller.onComplete(); };
+ var viz = this.viz;
+ if(len == 0) complete();
+ for(var i=0; i<len; i++) {
+ nodeSelected[nodes[i].id] = nodes[i];
+ controller.request(nodes[i].id, nodes[i]._level, {
+ onComplete: function(nodeId, data) {
+ if(data && data.children) {
+ data.id = nodeId;
+ viz.op.sum(data, { type: 'nothing' });
+ }
+ if(++counter == len) {
+ viz.graph.computeLevels(viz.root, 0);
+ complete();
+ }
+ }
+ });
+ }
+ },
+
+ /*
+
+ Collapses group of nodes.
+ */
+ contract: function(nodes, controller) {
+ var viz = this.viz;
+ var that = this;
+
+ nodes = this.prepare(nodes);
+ this.animation.setOptions($.merge(controller, {
+ $animating: false,
+ compute: function(delta) {
+ if(delta == 1) delta = 0.99;
+ that.plotStep(1 - delta, controller, this.$animating);
+ this.$animating = 'contract';
+ },
+
+ complete: function() {
+ that.hide(nodes, controller);
+ }
+ })).start();
+ },
+
+ hide: function(nodes, controller) {
+ var viz = this.viz;
+ for(var i=0; i<nodes.length; i++) {
+ // TODO nodes are requested on demand, but not
+ // deleted when hidden. Would that be a good feature?
+ // Currently that feature is buggy, so I'll turn it off
+ // Actually this feature is buggy because trimming should take
+ // place onAfterCompute and not right after collapsing nodes.
+ if (true || !controller || !controller.request) {
+ nodes[i].eachLevel(1, false, function(elem){
+ if (elem.exist) {
+ $.extend(elem, {
+ 'drawn': false,
+ 'exist': false
+ });
+ }
+ });
+ } else {
+ var ids = [];
+ nodes[i].eachLevel(1, false, function(n) {
+ ids.push(n.id);
+ });
+ viz.op.removeNode(ids, { 'type': 'nothing' });
+ viz.labels.clearLabels();
+ }
+ }
+ controller.onComplete();
+ },
+
+
+ /*
+ Expands group of nodes.
+ */
+ expand: function(nodes, controller) {
+ var that = this;
+ this.show(nodes);
+ this.animation.setOptions($.merge(controller, {
+ $animating: false,
+ compute: function(delta) {
+ that.plotStep(delta, controller, this.$animating);
+ this.$animating = 'expand';
+ },
+
+ complete: function() {
+ that.plotStep(undefined, controller, false);
+ controller.onComplete();
+ }
+ })).start();
+
+ },
+
+ show: function(nodes) {
+ var config = this.config;
+ this.prepare(nodes);
+ $.each(nodes, function(n) {
+ // check for root nodes if multitree
+ if(config.multitree && !('$orn' in n.data)) {
+ delete n.data.$orns;
+ var orns = ' ';
+ n.eachSubnode(function(ch) {
+ if(('$orn' in ch.data)
+ && orns.indexOf(ch.data.$orn) < 0
+ && ch.exist && !ch.drawn) {
+ orns += ch.data.$orn + ' ';
+ }
+ });
+ n.data.$orns = orns;
+ }
+ n.eachLevel(0, config.levelsToShow, function(n) {
+ if(n.exist) n.drawn = true;
+ });
+ });
+ },
+
+ prepare: function(nodes) {
+ this.nodes = this.getNodesWithChildren(nodes);
+ return this.nodes;
+ },
+
+ /*
+ Filters an array of nodes leaving only nodes with children.
+ */
+ getNodesWithChildren: function(nodes) {
+ var ans = [], config = this.config, root = this.viz.root;
+ nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
+ for(var i=0; i<nodes.length; i++) {
+ if(nodes[i].anySubnode("exist")) {
+ for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
+ if(!config.multitree || '$orn' in nodes[j].data) {
+ desc = desc || nodes[i].isDescendantOf(nodes[j].id);
+ }
+ }
+ if(!desc) ans.push(nodes[i]);
+ }
+ }
+ return ans;
+ },
+
+ plotStep: function(delta, controller, animating) {
+ var viz = this.viz,
+ config = this.config,
+ canvas = viz.canvas,
+ ctx = canvas.getCtx(),
+ nodes = this.nodes;
+ var i, node;
+ // hide nodes that are meant to be collapsed/expanded
+ var nds = {};
+ for(i=0; i<nodes.length; i++) {
+ node = nodes[i];
+ nds[node.id] = [];
+ var root = config.multitree && !('$orn' in node.data);
+ var orns = root && node.data.$orns;
+ node.eachSubgraph(function(n) {
+ // TODO(nico): Cleanup
+ // special check for root node subnodes when
+ // multitree is checked.
+ if(root && orns && orns.indexOf(n.data.$orn) > 0
+ && n.drawn) {
+ n.drawn = false;
+ nds[node.id].push(n);
+ } else if((!root || !orns) && n.drawn) {
+ n.drawn = false;
+ nds[node.id].push(n);
+ }
+ });
+ node.drawn = true;
+ }
+ // plot the whole (non-scaled) tree
+ if(nodes.length > 0) viz.fx.plot();
+ // show nodes that were previously hidden
+ for(i in nds) {
+ $.each(nds[i], function(n) { n.drawn = true; });
+ }
+ // plot each scaled subtree
+ for(i=0; i<nodes.length; i++) {
+ node = nodes[i];
+ ctx.save();
+ viz.fx.plotSubtree(node, controller, delta, animating);
+ ctx.restore();
+ }
+ },
+
+ getSiblings: function(nodes) {
+ var siblings = {};
+ $.each(nodes, function(n) {
+ var par = n.getParents();
+ if (par.length == 0) {
+ siblings[n.id] = [n];
+ } else {
+ var ans = [];
+ par[0].eachSubnode(function(sn) {
+ ans.push(sn);
+ });
+ siblings[n.id] = ans;
+ }
+ });
+ return siblings;
+ }
+});
+
+/*
+ ST.Geom
+
+ Performs low level geometrical computations.
+
+ Access:
+
+ This instance can be accessed with the _geom_ parameter of the st instance created.
+
+ Example:
+
+ (start code js)
+ var st = new ST(canvas, config);
+ st.geom.translate //or can also call any other <ST.Geom> method
+ (end code)
+
+*/
+
+$jit.ST.Geom = new Class({
+ Implements: Graph.Geom,
+ /*
+ Changes the tree current orientation to the one specified.
+
+ You should usually use <ST.switchPosition> instead.
+ */
+ switchOrientation: function(orn) {
+ this.config.orientation = orn;
+ },
+
+ /*
+ Makes a value dispatch according to the current layout
+ Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
+ */
+ dispatch: function() {
+ // TODO(nico) should store Array.prototype.slice.call somewhere.
+ var args = Array.prototype.slice.call(arguments);
+ var s = args.shift(), len = args.length;
+ var val = function(a) { return typeof a == 'function'? a() : a; };
+ if(len == 2) {
+ return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
+ } else if(len == 4) {
+ switch(s) {
+ case "top": return val(args[0]);
+ case "right": return val(args[1]);
+ case "bottom": return val(args[2]);
+ case "left": return val(args[3]);
+ }
+ }
+ return undefined;
+ },
+
+ /*
+ Returns label height or with, depending on the tree current orientation.
+ */
+ getSize: function(n, invert) {
+ var data = n.data, config = this.config;
+ var siblingOffset = config.siblingOffset;
+ var s = (config.multitree
+ && ('$orn' in data)
+ && data.$orn) || config.orientation;
+ var w = n.getData('width') + siblingOffset;
+ var h = n.getData('height') + siblingOffset;
+ if(!invert)
+ return this.dispatch(s, h, w);
+ else
+ return this.dispatch(s, w, h);
+ },
+
+ /*
+ Calculates a subtree base size. This is an utility function used by _getBaseSize_
+ */
+ getTreeBaseSize: function(node, level, leaf) {
+ var size = this.getSize(node, true), baseHeight = 0, that = this;
+ if(leaf(level, node)) return size;
+ if(level === 0) return 0;
+ node.eachSubnode(function(elem) {
+ baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
+ });
+ return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
+ },
+
+
+ /*
+ getEdge
+
+ Returns a Complex instance with the begin or end position of the edge to be plotted.
+
+ Parameters:
+
+ node - A <Graph.Node> that is connected to this edge.
+ type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
+
+ Returns:
+
+ A <Complex> number specifying the begin or end position.
+ */
+ getEdge: function(node, type, s) {
+ var $C = function(a, b) {
+ return function(){
+ return node.pos.add(new Complex(a, b));
+ };
+ };
+ var dim = this.node;
+ var w = node.getData('width');
+ var h = node.getData('height');
+
+ if(type == 'begin') {
+ if(dim.align == "center") {
+ return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
+ $C(0, -h/2),$C(w/2, 0));
+ } else if(dim.align == "left") {
+ return this.dispatch(s, $C(0, h), $C(0, 0),
+ $C(0, 0), $C(w, 0));
+ } else if(dim.align == "right") {
+ return this.dispatch(s, $C(0, 0), $C(-w, 0),
+ $C(0, -h),$C(0, 0));
+ } else throw "align: not implemented";
+
+
+ } else if(type == 'end') {
+ if(dim.align == "center") {
+ return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
+ $C(0, h/2), $C(-w/2, 0));
+ } else if(dim.align == "left") {
+ return this.dispatch(s, $C(0, 0), $C(w, 0),
+ $C(0, h), $C(0, 0));
+ } else if(dim.align == "right") {
+ return this.dispatch(s, $C(0, -h),$C(0, 0),
+ $C(0, 0), $C(-w, 0));
+ } else throw "align: not implemented";
+ }
+ },
+
+ /*
+ Adjusts the tree position due to canvas scaling or translation.
+ */
+ getScaledTreePosition: function(node, scale) {
+ var dim = this.node;
+ var w = node.getData('width');
+ var h = node.getData('height');
+ var s = (this.config.multitree
+ && ('$orn' in node.data)
+ && node.data.$orn) || this.config.orientation;
+
+ var $C = function(a, b) {
+ return function(){
+ return node.pos.add(new Complex(a, b)).$scale(1 - scale);
+ };
+ };
+ if(dim.align == "left") {
+ return this.dispatch(s, $C(0, h), $C(0, 0),
+ $C(0, 0), $C(w, 0));
+ } else if(dim.align == "center") {
+ return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
+ $C(0, -h / 2),$C(w / 2, 0));
+ } else if(dim.align == "right") {
+ return this.dispatch(s, $C(0, 0), $C(-w, 0),
+ $C(0, -h),$C(0, 0));
+ } else throw "align: not implemented";
+ },
+
+ /*
+ treeFitsInCanvas
+
+ Returns a Boolean if the current subtree fits in canvas.
+
+ Parameters:
+
+ node - A <Graph.Node> which is the current root of the subtree.
+ canvas - The <Canvas> object.
+ level - The depth of the subtree to be considered.
+ */
+ treeFitsInCanvas: function(node, canvas, level) {
+ var csize = canvas.getSize();
+ var s = (this.config.multitree
+ && ('$orn' in node.data)
+ && node.data.$orn) || this.config.orientation;
+
+ var size = this.dispatch(s, csize.width, csize.height);
+ var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
+ return level === 0 || !node.anySubnode();
+ });
+ return (baseSize < size);
+ }
+});
+
+/*
+ Class: ST.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+*/
+$jit.ST.Plot = new Class({
+
+ Implements: Graph.Plot,
+
+ /*
+ Plots a subtree from the spacetree.
+ */
+ plotSubtree: function(node, opt, scale, animating) {
+ var viz = this.viz, canvas = viz.canvas, config = viz.config;
+ scale = Math.min(Math.max(0.001, scale), 1);
+ if(scale >= 0) {
+ node.drawn = false;
+ var ctx = canvas.getCtx();
+ var diff = viz.geom.getScaledTreePosition(node, scale);
+ ctx.translate(diff.x, diff.y);
+ ctx.scale(scale, scale);
+ }
+ this.plotTree(node, $.merge(opt, {
+ 'withLabels': true,
+ 'hideLabels': !!scale,
+ 'plotSubtree': function(n, ch) {
+ var root = config.multitree && !('$orn' in node.data);
+ var orns = root && node.getData('orns');
+ return !root || orns.indexOf(elem.getData('orn')) > -1;
+ }
+ }), animating);
+ if(scale >= 0) node.drawn = true;
+ },
+
+ /*
+ Method: getAlignedPos
+
+ Returns a *x, y* object with the position of the top/left corner of a <ST> node.
+
+ Parameters:
+
+ pos - (object) A <Graph.Node> position.
+ width - (number) The width of the node.
+ height - (number) The height of the node.
+
+ */
+ getAlignedPos: function(pos, width, height) {
+ var nconfig = this.node;
+ var square, orn;
+ if(nconfig.align == "center") {
+ square = {
+ x: pos.x - width / 2,
+ y: pos.y - height / 2
+ };
+ } else if (nconfig.align == "left") {
+ orn = this.config.orientation;
+ if(orn == "bottom" || orn == "top") {
+ square = {
+ x: pos.x - width / 2,
+ y: pos.y
+ };
+ } else {
+ square = {
+ x: pos.x,
+ y: pos.y - height / 2
+ };
+ }
+ } else if(nconfig.align == "right") {
+ orn = this.config.orientation;
+ if(orn == "bottom" || orn == "top") {
+ square = {
+ x: pos.x - width / 2,
+ y: pos.y - height
+ };
+ } else {
+ square = {
+ x: pos.x - width,
+ y: pos.y - height / 2
+ };
+ }
+ } else throw "align: not implemented";
+
+ return square;
+ },
+
+ getOrientation: function(adj) {
+ var config = this.config;
+ var orn = config.orientation;
+
+ if(config.multitree) {
+ var nodeFrom = adj.nodeFrom;
+ var nodeTo = adj.nodeTo;
+ orn = (('$orn' in nodeFrom.data)
+ && nodeFrom.data.$orn)
+ || (('$orn' in nodeTo.data)
+ && nodeTo.data.$orn);
+ }
+
+ return orn;
+ }
+});
+
+/*
+ Class: ST.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+ */
+$jit.ST.Label = {};
+
+/*
+ ST.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+*/
+$jit.ST.Label.Native = new Class({
+ Implements: Graph.Label.Native,
+
+ renderLabel: function(canvas, node, controller) {
+ var ctx = canvas.getCtx();
+ var coord = node.pos.getc(true);
+ ctx.fillText(node.name, coord.x, coord.y);
+ }
+});
+
+$jit.ST.Label.DOM = new Class({
+ Implements: Graph.Label.DOM,
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true),
+ config = this.viz.config,
+ dim = config.Node,
+ canvas = this.viz.canvas,
+ w = node.getData('width'),
+ h = node.getData('height'),
+ radius = canvas.getSize(),
+ labelPos, orn;
+
+ var ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ posx = pos.x * sx + ox,
+ posy = pos.y * sy + oy;
+
+ if(dim.align == "center") {
+ labelPos= {
+ x: Math.round(posx - w / 2 + radius.width/2),
+ y: Math.round(posy - h / 2 + radius.height/2)
+ };
+ } else if (dim.align == "left") {
+ orn = config.orientation;
+ if(orn == "bottom" || orn == "top") {
+ labelPos= {
+ x: Math.round(posx - w / 2 + radius.width/2),
+ y: Math.round(posy + radius.height/2)
+ };
+ } else {
+ labelPos= {
+ x: Math.round(posx + radius.width/2),
+ y: Math.round(posy - h / 2 + radius.height/2)
+ };
+ }
+ } else if(dim.align == "right") {
+ orn = config.orientation;
+ if(orn == "bottom" || orn == "top") {
+ labelPos= {
+ x: Math.round(posx - w / 2 + radius.width/2),
+ y: Math.round(posy - h + radius.height/2)
+ };
+ } else {
+ labelPos= {
+ x: Math.round(posx - w + radius.width/2),
+ y: Math.round(posy - h / 2 + radius.height/2)
+ };
+ }
+ } else throw "align: not implemented";
+
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
+ controller.onPlaceLabel(tag, node);
+ }
+});
+
+/*
+ ST.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+*/
+$jit.ST.Label.SVG = new Class({
+ Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
+
+ initialize: function(viz) {
+ this.viz = viz;
+ }
+});
+
+/*
+ ST.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+*/
+$jit.ST.Label.HTML = new Class({
+ Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
+
+ initialize: function(viz) {
+ this.viz = viz;
+ }
+});
+
+
+/*
+ Class: ST.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ ST.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+*/
+$jit.ST.Plot.NodeTypes = new Class({
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false)
+ },
+ 'circle': {
+ 'render': function(node, canvas) {
+ var dim = node.getData('dim'),
+ pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ dim2 = dim/2;
+ this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ dim2 = dim/2;
+ this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
+ }
+ },
+ 'square': {
+ 'render': function(node, canvas) {
+ var dim = node.getData('dim'),
+ dim2 = dim/2,
+ pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
+ this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ dim2 = dim/2;
+ this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
+ }
+ },
+ 'ellipse': {
+ 'render': function(node, canvas) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ pos = this.getAlignedPos(node.pos.getc(true), width, height);
+ this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
+ },
+ 'contains': function(node, pos) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ npos = this.getAlignedPos(node.pos.getc(true), width, height);
+ this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
+ }
+ },
+ 'rectangle': {
+ 'render': function(node, canvas) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ pos = this.getAlignedPos(node.pos.getc(true), width, height);
+ this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
+ },
+ 'contains': function(node, pos) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ npos = this.getAlignedPos(node.pos.getc(true), width, height);
+ this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
+ }
+ }
+});
+
+/*
+ Class: ST.Plot.EdgeTypes
+
+ This class contains a list of <Graph.Adjacence> built-in types.
+ Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
+
+ You can add your custom edge types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ ST.Plot.EdgeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(adj, canvas) {
+ //print your custom edge to canvas
+ },
+ //optional
+ 'contains': function(adj, pos) {
+ //return true if pos is inside the arc or false otherwise
+ }
+ }
+ });
+ (end code)
+
+*/
+$jit.ST.Plot.EdgeTypes = new Class({
+ 'none': $.empty,
+ 'line': {
+ 'render': function(adj, canvas) {
+ var orn = this.getOrientation(adj),
+ nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
+ this.edgeHelper.line.render(from, to, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var orn = this.getOrientation(adj),
+ nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
+ return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'arrow': {
+ 'render': function(adj, canvas) {
+ var orn = this.getOrientation(adj),
+ node = adj.nodeFrom,
+ child = adj.nodeTo,
+ dim = adj.getData('dim'),
+ from = this.viz.geom.getEdge(node, 'begin', orn),
+ to = this.viz.geom.getEdge(child, 'end', orn),
+ direction = adj.data.$direction,
+ inv = (direction && direction.length>1 && direction[0] != node.id);
+ this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var orn = this.getOrientation(adj),
+ nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
+ return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'quadratic:begin': {
+ 'render': function(adj, canvas) {
+ var orn = this.getOrientation(adj);
+ var nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
+ dim = adj.getData('dim'),
+ ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.moveTo(begin.x, begin.y);
+ switch(orn) {
+ case "left":
+ ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
+ break;
+ case "right":
+ ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
+ break;
+ case "top":
+ ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
+ break;
+ case "bottom":
+ ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
+ break;
+ }
+ ctx.stroke();
+ }
+ },
+ 'quadratic:end': {
+ 'render': function(adj, canvas) {
+ var orn = this.getOrientation(adj);
+ var nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
+ dim = adj.getData('dim'),
+ ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.moveTo(begin.x, begin.y);
+ switch(orn) {
+ case "left":
+ ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
+ break;
+ case "right":
+ ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
+ break;
+ case "top":
+ ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
+ break;
+ case "bottom":
+ ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
+ break;
+ }
+ ctx.stroke();
+ }
+ },
+ 'bezier': {
+ 'render': function(adj, canvas) {
+ var orn = this.getOrientation(adj),
+ nodeFrom = adj.nodeFrom,
+ nodeTo = adj.nodeTo,
+ rel = nodeFrom._depth < nodeTo._depth,
+ begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
+ end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
+ dim = adj.getData('dim'),
+ ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.moveTo(begin.x, begin.y);
+ switch(orn) {
+ case "left":
+ ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
+ break;
+ case "right":
+ ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
+ break;
+ case "top":
+ ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
+ break;
+ case "bottom":
+ ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
+ break;
+ }
+ ctx.stroke();
+ }
+ }
+});
+
+
+
+/*
+ * File: AreaChart.js
+ *
+*/
+
+$jit.ST.Plot.NodeTypes.implement({
+ 'areachart-stacked' : {
+ 'render' : function(node, canvas) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ stringArray = node.getData('stringArray'),
+ dimArray = node.getData('dimArray'),
+ valArray = node.getData('valueArray'),
+ valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
+ valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
+ colorArray = node.getData('colorArray'),
+ colorLength = colorArray.length,
+ config = node.getData('config'),
+ gradient = node.getData('gradient'),
+ showLabels = config.showLabels,
+ aggregates = config.showAggregates,
+ label = config.Label,
+ prev = node.getData('prev');
+
+ var ctx = canvas.getCtx(), border = node.getData('border');
+ if (colorArray && dimArray && stringArray) {
+ for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
+ ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
+ ctx.save();
+ if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
+ var h1 = acumLeft + dimArray[i][0],
+ h2 = acumRight + dimArray[i][1],
+ alpha = Math.atan((h2 - h1) / width),
+ delta = 55;
+ var linear = ctx.createLinearGradient(x + width/2,
+ y - (h1 + h2)/2,
+ x + width/2 + delta * Math.sin(alpha),
+ y - (h1 + h2)/2 + delta * Math.cos(alpha));
+ var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
+ function(v) { return (v * 0.85) >> 0; }));
+ linear.addColorStop(0, colorArray[i % colorLength]);
+ linear.addColorStop(1, color);
+ ctx.fillStyle = linear;
+ }
+ ctx.beginPath();
+ ctx.moveTo(x, y - acumLeft);
+ ctx.lineTo(x + width, y - acumRight);
+ ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
+ ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
+ ctx.lineTo(x, y - acumLeft);
+ ctx.fill();
+ ctx.restore();
+ if(border) {
+ var strong = border.name == stringArray[i];
+ var perc = strong? 0.7 : 0.8;
+ var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
+ function(v) { return (v * perc) >> 0; }));
+ ctx.strokeStyle = color;
+ ctx.lineWidth = strong? 4 : 1;
+ ctx.save();
+ ctx.beginPath();
+ if(border.index === 0) {
+ ctx.moveTo(x, y - acumLeft);
+ ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
+ } else {
+ ctx.moveTo(x + width, y - acumRight);
+ ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
+ }
+ ctx.stroke();
+ ctx.restore();
+ }
+ acumLeft += (dimArray[i][0] || 0);
+ acumRight += (dimArray[i][1] || 0);
+
+ if(dimArray[i][0] > 0)
+ valAcum += (valArray[i][0] || 0);
+ }
+ if(prev && label.type == 'Native') {
+ ctx.save();
+ ctx.beginPath();
+ ctx.fillStyle = ctx.strokeStyle = label.color;
+ ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ if(aggregates(node.name, valLeft, valRight, node)) {
+ ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
+ }
+ if(showLabels(node.name, valLeft, valRight, node)) {
+ ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
+ }
+ ctx.restore();
+ }
+ }
+ },
+ 'contains': function(node, mpos) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ dimArray = node.getData('dimArray'),
+ rx = mpos.x - x;
+ //bounding box check
+ if(mpos.x < x || mpos.x > x + width
+ || mpos.y > y || mpos.y < y - height) {
+ return false;
+ }
+ //deep check
+ for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
+ var dimi = dimArray[i];
+ lAcum -= dimi[0];
+ rAcum -= dimi[1];
+ var intersec = lAcum + (rAcum - lAcum) * rx / width;
+ if(mpos.y >= intersec) {
+ var index = +(rx > width/2);
+ return {
+ 'name': node.getData('stringArray')[i],
+ 'color': node.getData('colorArray')[i],
+ 'value': node.getData('valueArray')[i][index],
+ 'index': index
+ };
+ }
+ }
+ return false;
+ }
+ }
+});
+
+/*
+ Class: AreaChart
+
+ A visualization that displays stacked area charts.
+
+ Constructor Options:
+
+ See <Options.AreaChart>.
+
+*/
+$jit.AreaChart = new Class({
+ st: null,
+ colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
+ selected: {},
+ busy: false,
+
+ initialize: function(opt) {
+ this.controller = this.config =
+ $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
+ Label: { type: 'Native' }
+ }, opt);
+ //set functions for showLabels and showAggregates
+ var showLabels = this.config.showLabels,
+ typeLabels = $.type(showLabels),
+ showAggregates = this.config.showAggregates,
+ typeAggregates = $.type(showAggregates);
+ this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
+ this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
+
+ this.initializeViz();
+ },
+
+ initializeViz: function() {
+ var config = this.config,
+ that = this,
+ nodeType = config.type.split(":")[0],
+ nodeLabels = {};
+
+ var st = new $jit.ST({
+ injectInto: config.injectInto,
+ orientation: "bottom",
+ levelDistance: 0,
+ siblingOffset: 0,
+ subtreeOffset: 0,
+ withLabels: config.Label.type != 'Native',
+ useCanvas: config.useCanvas,
+ Label: {
+ type: config.Label.type
+ },
+ Node: {
+ overridable: true,
+ type: 'areachart-' + nodeType,
+ align: 'left',
+ width: 1,
+ height: 1
+ },
+ Edge: {
+ type: 'none'
+ },
+ Tips: {
+ enable: config.Tips.enable,
+ type: 'Native',
+ force: true,
+ onShow: function(tip, node, contains) {
+ var elem = contains;
+ config.Tips.onShow(tip, elem, node);
+ }
+ },
+ Events: {
+ enable: true,
+ type: 'Native',
+ onClick: function(node, eventInfo, evt) {
+ if(!config.filterOnClick && !config.Events.enable) return;
+ var elem = eventInfo.getContains();
+ if(elem) config.filterOnClick && that.filter(elem.name);
+ config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
+ },
+ onRightClick: function(node, eventInfo, evt) {
+ if(!config.restoreOnRightClick) return;
+ that.restore();
+ },
+ onMouseMove: function(node, eventInfo, evt) {
+ if(!config.selectOnHover) return;
+ if(node) {
+ var elem = eventInfo.getContains();
+ that.select(node.id, elem.name, elem.index);
+ } else {
+ that.select(false, false, false);
+ }
+ }
+ },
+ onCreateLabel: function(domElement, node) {
+ var labelConf = config.Label,
+ valueArray = node.getData('valueArray'),
+ acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
+ acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
+ if(node.getData('prev')) {
+ var nlbs = {
+ wrapper: document.createElement('div'),
+ aggregate: document.createElement('div'),
+ label: document.createElement('div')
+ };
+ var wrapper = nlbs.wrapper,
+ label = nlbs.label,
+ aggregate = nlbs.aggregate,
+ wrapperStyle = wrapper.style,
+ labelStyle = label.style,
+ aggregateStyle = aggregate.style;
+ //store node labels
+ nodeLabels[node.id] = nlbs;
+ //append labels
+ wrapper.appendChild(label);
+ wrapper.appendChild(aggregate);
+ if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
+ label.style.display = 'none';
+ }
+ if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
+ aggregate.style.display = 'none';
+ }
+ wrapperStyle.position = 'relative';
+ wrapperStyle.overflow = 'visible';
+ wrapperStyle.fontSize = labelConf.size + 'px';
+ wrapperStyle.fontFamily = labelConf.family;
+ wrapperStyle.color = labelConf.color;
+ wrapperStyle.textAlign = 'center';
+ aggregateStyle.position = labelStyle.position = 'absolute';
+
+ domElement.style.width = node.getData('width') + 'px';
+ domElement.style.height = node.getData('height') + 'px';
+ label.innerHTML = node.name;
+
+ domElement.appendChild(wrapper);
+ }
+ },
+ onPlaceLabel: function(domElement, node) {
+ if(!node.getData('prev')) return;
+ var labels = nodeLabels[node.id],
+ wrapperStyle = labels.wrapper.style,
+ labelStyle = labels.label.style,
+ aggregateStyle = labels.aggregate.style,
+ width = node.getData('width'),
+ height = node.getData('height'),
+ dimArray = node.getData('dimArray'),
+ valArray = node.getData('valueArray'),
+ acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
+ acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
+ font = parseInt(wrapperStyle.fontSize, 10),
+ domStyle = domElement.style;
+
+ if(dimArray && valArray) {
+ if(config.showLabels(node.name, acumLeft, acumRight, node)) {
+ labelStyle.display = '';
+ } else {
+ labelStyle.display = 'none';
+ }
+ if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
+ aggregateStyle.display = '';
+ } else {
+ aggregateStyle.display = 'none';
+ }
+ wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
+ aggregateStyle.left = labelStyle.left = -width/2 + 'px';
+ for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
+ if(dimArray[i][0] > 0) {
+ acum+= valArray[i][0];
+ leftAcum+= dimArray[i][0];
+ }
+ }
+ aggregateStyle.top = (-font - config.labelOffset) + 'px';
+ labelStyle.top = (config.labelOffset + leftAcum) + 'px';
+ domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
+ domElement.style.height = wrapperStyle.height = leftAcum + 'px';
+ labels.aggregate.innerHTML = acum;
+ }
+ }
+ });
+
+ var size = st.canvas.getSize(),
+ margin = config.Margin;
+ st.config.offsetY = -size.height/2 + margin.bottom
+ + (config.showLabels && (config.labelOffset + config.Label.size));
+ st.config.offsetX = (margin.right - margin.left)/2;
+ this.st = st;
+ this.canvas = this.st.canvas;
+ },
+
+ /*
+ Method: loadJSON
+
+ Loads JSON data into the visualization.
+
+ Parameters:
+
+ json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
+
+ Example:
+ (start code js)
+ var areaChart = new $jit.AreaChart(options);
+ areaChart.loadJSON(json);
+ (end code)
+ */
+ loadJSON: function(json) {
+ var prefix = $.time(),
+ ch = [],
+ st = this.st,
+ name = $.splat(json.label),
+ color = $.splat(json.color || this.colors),
+ config = this.config,
+ gradient = !!config.type.split(":")[1],
+ animate = config.animate;
+
+ for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
+ var val = values[i], prev = values[i-1], next = values[i+1];
+ var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
+ var valArray = $.zip(valLeft, valRight);
+ var acumLeft = 0, acumRight = 0;
+ ch.push({
+ 'id': prefix + val.label,
+ 'name': val.label,
+ 'data': {
+ 'value': valArray,
+ '$valueArray': valArray,
+ '$colorArray': color,
+ '$stringArray': name,
+ '$next': next.label,
+ '$prev': prev? prev.label:false,
+ '$config': config,
+ '$gradient': gradient
+ },
+ 'children': []
+ });
+ }
+ var root = {
+ 'id': prefix + '$root',
+ 'name': '',
+ 'data': {
+ '$type': 'none',
+ '$width': 1,
+ '$height': 1
+ },
+ 'children': ch
+ };
+ st.loadJSON(root);
+
+ this.normalizeDims();
+ st.compute();
+ st.select(st.root);
+ if(animate) {
+ st.fx.animate({
+ modes: ['node-property:height:dimArray'],
+ duration:1500
+ });
+ }
+ },
+
+ /*
+ Method: updateJSON
+
+ Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
+
+ Parameters:
+
+ json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
+ onComplete - (object) A callback object to be called when the animation transition when updating the data end.
+
+ Example:
+
+ (start code js)
+ areaChart.updateJSON(json, {
+ onComplete: function() {
+ alert('update complete!');
+ }
+ });
+ (end code)
+ */
+ updateJSON: function(json, onComplete) {
+ if(this.busy) return;
+ this.busy = true;
+
+ var st = this.st,
+ graph = st.graph,
+ labels = json.label && $.splat(json.label),
+ values = json.values,
+ animate = this.config.animate,
+ that = this;
+ $.each(values, function(v) {
+ var n = graph.getByName(v.label);
+ if(n) {
+ v.values = $.splat(v.values);
+ var stringArray = n.getData('stringArray'),
+ valArray = n.getData('valueArray');
+ $.each(valArray, function(a, i) {
+ a[0] = v.values[i];
+ if(labels) stringArray[i] = labels[i];
+ });
+ n.setData('valueArray', valArray);
+ var prev = n.getData('prev'),
+ next = n.getData('next'),
+ nextNode = graph.getByName(next);
+ if(prev) {
+ var p = graph.getByName(prev);
+ if(p) {
+ var valArray = p.getData('valueArray');
+ $.each(valArray, function(a, i) {
+ a[1] = v.values[i];
+ });
+ }
+ }
+ if(!nextNode) {
+ var valArray = n.getData('valueArray');
+ $.each(valArray, function(a, i) {
+ a[1] = v.values[i];
+ });
+ }
+ }
+ });
+ this.normalizeDims();
+ st.compute();
+ st.select(st.root);
+ if(animate) {
+ st.fx.animate({
+ modes: ['node-property:height:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ onComplete && onComplete.onComplete();
+ }
+ });
+ }
+ },
+
+/*
+ Method: filter
+
+ Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
+
+ Parameters:
+
+ Variable strings arguments with the name of the stacks.
+
+ Example:
+
+ (start code js)
+ areaChart.filter('label A', 'label C');
+ (end code)
+
+ See also:
+
+ <AreaChart.restore>.
+ */
+ filter: function() {
+ if(this.busy) return;
+ this.busy = true;
+ if(this.config.Tips.enable) this.st.tips.hide();
+ this.select(false, false, false);
+ var args = Array.prototype.slice.call(arguments);
+ var rt = this.st.graph.getNode(this.st.root);
+ var that = this;
+ rt.eachAdjacency(function(adj) {
+ var n = adj.nodeTo,
+ dimArray = n.getData('dimArray'),
+ stringArray = n.getData('stringArray');
+ n.setData('dimArray', $.map(dimArray, function(d, i) {
+ return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
+ }), 'end');
+ });
+ this.st.fx.animate({
+ modes: ['node-property:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ }
+ });
+ },
+
+ /*
+ Method: restore
+
+ Sets all stacks that could have been filtered visible.
+
+ Example:
+
+ (start code js)
+ areaChart.restore();
+ (end code)
+
+ See also:
+
+ <AreaChart.filter>.
+ */
+ restore: function() {
+ if(this.busy) return;
+ this.busy = true;
+ if(this.config.Tips.enable) this.st.tips.hide();
+ this.select(false, false, false);
+ this.normalizeDims();
+ var that = this;
+ this.st.fx.animate({
+ modes: ['node-property:height:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ }
+ });
+ },
+ //adds the little brown bar when hovering the node
+ select: function(id, name, index) {
+ if(!this.config.selectOnHover) return;
+ var s = this.selected;
+ if(s.id != id || s.name != name
+ || s.index != index) {
+ s.id = id;
+ s.name = name;
+ s.index = index;
+ this.st.graph.eachNode(function(n) {
+ n.setData('border', false);
+ });
+ if(id) {
+ var n = this.st.graph.getNode(id);
+ n.setData('border', s);
+ var link = index === 0? 'prev':'next';
+ link = n.getData(link);
+ if(link) {
+ n = this.st.graph.getByName(link);
+ if(n) {
+ n.setData('border', {
+ name: name,
+ index: 1-index
+ });
+ }
+ }
+ }
+ this.st.plot();
+ }
+ },
+
+ /*
+ Method: getLegend
+
+ Returns an object containing as keys the legend names and as values hex strings with color values.
+
+ Example:
+
+ (start code js)
+ var legend = areaChart.getLegend();
+ (end code)
+ */
+ getLegend: function() {
+ var legend = {};
+ var n;
+ this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
+ n = adj.nodeTo;
+ });
+ var colors = n.getData('colorArray'),
+ len = colors.length;
+ $.each(n.getData('stringArray'), function(s, i) {
+ legend[s] = colors[i % len];
+ });
+ return legend;
+ },
+
+ /*
+ Method: getMaxValue
+
+ Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
+
+ Example:
+
+ (start code js)
+ var ans = areaChart.getMaxValue();
+ (end code)
+
+ In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
+
+ Example:
+
+ (start code js)
+ //will return 100 for all AreaChart instances,
+ //displaying all of them with the same scale
+ $jit.AreaChart.implement({
+ 'getMaxValue': function() {
+ return 100;
+ }
+ });
+ (end code)
+
+*/
+ getMaxValue: function() {
+ var maxValue = 0;
+ this.st.graph.eachNode(function(n) {
+ var valArray = n.getData('valueArray'),
+ acumLeft = 0, acumRight = 0;
+ $.each(valArray, function(v) {
+ acumLeft += +v[0];
+ acumRight += +v[1];
+ });
+ var acum = acumRight>acumLeft? acumRight:acumLeft;
+ maxValue = maxValue>acum? maxValue:acum;
+ });
+ return maxValue;
+ },
+
+ normalizeDims: function() {
+ //number of elements
+ var root = this.st.graph.getNode(this.st.root), l=0;
+ root.eachAdjacency(function() {
+ l++;
+ });
+ var maxValue = this.getMaxValue() || 1,
+ size = this.st.canvas.getSize(),
+ config = this.config,
+ margin = config.Margin,
+ labelOffset = config.labelOffset + config.Label.size,
+ fixedDim = (size.width - (margin.left + margin.right)) / l,
+ animate = config.animate,
+ height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
+ - (config.showLabels && labelOffset);
+ this.st.graph.eachNode(function(n) {
+ var acumLeft = 0, acumRight = 0, animateValue = [];
+ $.each(n.getData('valueArray'), function(v) {
+ acumLeft += +v[0];
+ acumRight += +v[1];
+ animateValue.push([0, 0]);
+ });
+ var acum = acumRight>acumLeft? acumRight:acumLeft;
+ n.setData('width', fixedDim);
+ if(animate) {
+ n.setData('height', acum * height / maxValue, 'end');
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return [n[0] * height / maxValue, n[1] * height / maxValue];
+ }), 'end');
+ var dimArray = n.getData('dimArray');
+ if(!dimArray) {
+ n.setData('dimArray', animateValue);
+ }
+ } else {
+ n.setData('height', acum * height / maxValue);
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return [n[0] * height / maxValue, n[1] * height / maxValue];
+ }));
+ }
+ });
+ }
+});
+
+/*
+ * File: Options.BarChart.js
+ *
+*/
+
+/*
+ Object: Options.BarChart
+
+ <BarChart> options.
+ Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
+
+ Syntax:
+
+ (start code js)
+
+ Options.BarChart = {
+ animate: true,
+ labelOffset: 3,
+ barsOffset: 0,
+ type: 'stacked',
+ hoveredColor: '#9fd4ff',
+ orientation: 'horizontal',
+ showAggregates: true,
+ showLabels: true
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+
+ var barChart = new $jit.BarChart({
+ animate: true,
+ barsOffset: 10,
+ type: 'stacked:gradient'
+ });
+
+ (end code)
+
+ Parameters:
+
+ animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
+ offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
+ labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
+ barsOffset - (number) Default's *0*. Separation between bars.
+ type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
+ hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
+ orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
+ showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
+ showLabels - (boolean) Default's *true*. Display the name of the slots.
+
+*/
+
+Options.BarChart = {
+ $extend: true,
+
+ animate: true,
+ type: 'stacked', //stacked, grouped, : gradient
+ labelOffset: 3, //label offset
+ barsOffset: 0, //distance between bars
+ hoveredColor: '#9fd4ff',
+ orientation: 'horizontal',
+ showAggregates: true,
+ showLabels: true,
+ Tips: {
+ enable: false,
+ onShow: $.empty,
+ onHide: $.empty
+ },
+ Events: {
+ enable: false,
+ onClick: $.empty
+ }
+};
+
+/*
+ * File: BarChart.js
+ *
+*/
+
+$jit.ST.Plot.NodeTypes.implement({
+ 'barchart-stacked' : {
+ 'render' : function(node, canvas) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ dimArray = node.getData('dimArray'),
+ valueArray = node.getData('valueArray'),
+ colorArray = node.getData('colorArray'),
+ colorLength = colorArray.length,
+ stringArray = node.getData('stringArray');
+
+ var ctx = canvas.getCtx(),
+ opt = {},
+ border = node.getData('border'),
+ gradient = node.getData('gradient'),
+ config = node.getData('config'),
+ horz = config.orientation == 'horizontal',
+ aggregates = config.showAggregates,
+ showLabels = config.showLabels,
+ label = config.Label;
+
+ if (colorArray && dimArray && stringArray) {
+ for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
+ ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
+ if(gradient) {
+ var linear;
+ if(horz) {
+ linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
+ x + acum + dimArray[i]/2, y + height);
+ } else {
+ linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
+ x + width, y - acum- dimArray[i]/2);
+ }
+ var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
+ function(v) { return (v * 0.5) >> 0; }));
+ linear.addColorStop(0, color);
+ linear.addColorStop(0.5, colorArray[i % colorLength]);
+ linear.addColorStop(1, color);
+ ctx.fillStyle = linear;
+ }
+ if(horz) {
+ ctx.fillRect(x + acum, y, dimArray[i], height);
+ } else {
+ ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
+ }
+ if(border && border.name == stringArray[i]) {
+ opt.acum = acum;
+ opt.dimValue = dimArray[i];
+ }
+ acum += (dimArray[i] || 0);
+ valAcum += (valueArray[i] || 0);
+ }
+ if(border) {
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = border.color;
+ if(horz) {
+ ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
+ } else {
+ ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
+ }
+ ctx.restore();
+ }
+ if(label.type == 'Native') {
+ ctx.save();
+ ctx.fillStyle = ctx.strokeStyle = label.color;
+ ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
+ ctx.textBaseline = 'middle';
+ if(aggregates(node.name, valAcum)) {
+ if(horz) {
+ ctx.textAlign = 'right';
+ ctx.fillText(valAcum, x + acum - config.labelOffset, y + height/2);
+ } else {
+ ctx.textAlign = 'center';
+ ctx.fillText(valAcum, x + width/2, y - height - label.size/2 - config.labelOffset);
+ }
+ }
+ if(showLabels(node.name, valAcum, node)) {
+ if(horz) {
+ ctx.textAlign = 'center';
+ ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
+ ctx.rotate(Math.PI / 2);
+ ctx.fillText(node.name, 0, 0);
+ } else {
+ ctx.textAlign = 'center';
+ ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
+ }
+ }
+ ctx.restore();
+ }
+ }
+ },
+ 'contains': function(node, mpos) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ dimArray = node.getData('dimArray'),
+ config = node.getData('config'),
+ rx = mpos.x - x,
+ horz = config.orientation == 'horizontal';
+ //bounding box check
+ if(horz) {
+ if(mpos.x < x || mpos.x > x + width
+ || mpos.y > y + height || mpos.y < y) {
+ return false;
+ }
+ } else {
+ if(mpos.x < x || mpos.x > x + width
+ || mpos.y > y || mpos.y < y - height) {
+ return false;
+ }
+ }
+ //deep check
+ for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
+ var dimi = dimArray[i];
+ if(horz) {
+ acum += dimi;
+ var intersec = acum;
+ if(mpos.x <= intersec) {
+ return {
+ 'name': node.getData('stringArray')[i],
+ 'color': node.getData('colorArray')[i],
+ 'value': node.getData('valueArray')[i],
+ 'label': node.name
+ };
+ }
+ } else {
+ acum -= dimi;
+ var intersec = acum;
+ if(mpos.y >= intersec) {
+ return {
+ 'name': node.getData('stringArray')[i],
+ 'color': node.getData('colorArray')[i],
+ 'value': node.getData('valueArray')[i],
+ 'label': node.name
+ };
+ }
+ }
+ }
+ return false;
+ }
+ },
+ 'barchart-grouped' : {
+ 'render' : function(node, canvas) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ dimArray = node.getData('dimArray'),
+ valueArray = node.getData('valueArray'),
+ valueLength = valueArray.length,
+ colorArray = node.getData('colorArray'),
+ colorLength = colorArray.length,
+ stringArray = node.getData('stringArray');
+
+ var ctx = canvas.getCtx(),
+ opt = {},
+ border = node.getData('border'),
+ gradient = node.getData('gradient'),
+ config = node.getData('config'),
+ horz = config.orientation == 'horizontal',
+ aggregates = config.showAggregates,
+ showLabels = config.showLabels,
+ label = config.Label,
+ fixedDim = (horz? height : width) / valueLength;
+
+ if (colorArray && dimArray && stringArray) {
+ for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
+ ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
+ if(gradient) {
+ var linear;
+ if(horz) {
+ linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
+ x + dimArray[i]/2, y + fixedDim * (i + 1));
+ } else {
+ linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
+ x + fixedDim * (i + 1), y - dimArray[i]/2);
+ }
+ var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
+ function(v) { return (v * 0.5) >> 0; }));
+ linear.addColorStop(0, color);
+ linear.addColorStop(0.5, colorArray[i % colorLength]);
+ linear.addColorStop(1, color);
+ ctx.fillStyle = linear;
+ }
+ if(horz) {
+ ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
+ } else {
+ ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
+ }
+ if(border && border.name == stringArray[i]) {
+ opt.acum = fixedDim * i;
+ opt.dimValue = dimArray[i];
+ }
+ acum += (dimArray[i] || 0);
+ valAcum += (valueArray[i] || 0);
+ }
+ if(border) {
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = border.color;
+ if(horz) {
+ ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
+ } else {
+ ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
+ }
+ ctx.restore();
+ }
+ if(label.type == 'Native') {
+ ctx.save();
+ ctx.fillStyle = ctx.strokeStyle = label.color;
+ ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
+ ctx.textBaseline = 'middle';
+ if(aggregates(node.name, valAcum)) {
+ if(horz) {
+ ctx.textAlign = 'right';
+ ctx.fillText(valAcum, x + Math.max.apply(null, dimArray) - config.labelOffset, y + height/2);
+ } else {
+ ctx.textAlign = 'center';
+ ctx.fillText(valAcum, x + width/2, y - Math.max.apply(null, dimArray) - label.size/2 - config.labelOffset);
+ }
+ }
+ if(showLabels(node.name, valAcum, node)) {
+ if(horz) {
+ ctx.textAlign = 'center';
+ ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
+ ctx.rotate(Math.PI / 2);
+ ctx.fillText(node.name, 0, 0);
+ } else {
+ ctx.textAlign = 'center';
+ ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
+ }
+ }
+ ctx.restore();
+ }
+ }
+ },
+ 'contains': function(node, mpos) {
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ algnPos = this.getAlignedPos(pos, width, height),
+ x = algnPos.x, y = algnPos.y,
+ dimArray = node.getData('dimArray'),
+ len = dimArray.length,
+ config = node.getData('config'),
+ rx = mpos.x - x,
+ horz = config.orientation == 'horizontal',
+ fixedDim = (horz? height : width) / len;
+ //bounding box check
+ if(horz) {
+ if(mpos.x < x || mpos.x > x + width
+ || mpos.y > y + height || mpos.y < y) {
+ return false;
+ }
+ } else {
+ if(mpos.x < x || mpos.x > x + width
+ || mpos.y > y || mpos.y < y - height) {
+ return false;
+ }
+ }
+ //deep check
+ for(var i=0, l=dimArray.length; i<l; i++) {
+ var dimi = dimArray[i];
+ if(horz) {
+ var limit = y + fixedDim * i;
+ if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
+ return {
+ 'name': node.getData('stringArray')[i],
+ 'color': node.getData('colorArray')[i],
+ 'value': node.getData('valueArray')[i],
+ 'label': node.name
+ };
+ }
+ } else {
+ var limit = x + fixedDim * i;
+ if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
+ return {
+ 'name': node.getData('stringArray')[i],
+ 'color': node.getData('colorArray')[i],
+ 'value': node.getData('valueArray')[i],
+ 'label': node.name
+ };
+ }
+ }
+ }
+ return false;
+ }
+ }
+});
+
+/*
+ Class: BarChart
+
+ A visualization that displays stacked bar charts.
+
+ Constructor Options:
+
+ See <Options.BarChart>.
+
+*/
+$jit.BarChart = new Class({
+ st: null,
+ colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
+ selected: {},
+ busy: false,
+
+ initialize: function(opt) {
+ this.controller = this.config =
+ $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
+ Label: { type: 'Native' }
+ }, opt);
+ //set functions for showLabels and showAggregates
+ var showLabels = this.config.showLabels,
+ typeLabels = $.type(showLabels),
+ showAggregates = this.config.showAggregates,
+ typeAggregates = $.type(showAggregates);
+ this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
+ this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
+
+ this.initializeViz();
+ },
+
+ initializeViz: function() {
+ var config = this.config, that = this;
+ var nodeType = config.type.split(":")[0],
+ horz = config.orientation == 'horizontal',
+ nodeLabels = {};
+
+ var st = new $jit.ST({
+ injectInto: config.injectInto,
+ orientation: horz? 'left' : 'bottom',
+ levelDistance: 0,
+ siblingOffset: config.barsOffset,
+ subtreeOffset: 0,
+ withLabels: config.Label.type != 'Native',
+ useCanvas: config.useCanvas,
+ Label: {
+ type: config.Label.type
+ },
+ Node: {
+ overridable: true,
+ type: 'barchart-' + nodeType,
+ align: 'left',
+ width: 1,
+ height: 1
+ },
+ Edge: {
+ type: 'none'
+ },
+ Tips: {
+ enable: config.Tips.enable,
+ type: 'Native',
+ force: true,
+ onShow: function(tip, node, contains) {
+ var elem = contains;
+ config.Tips.onShow(tip, elem, node);
+ }
+ },
+ Events: {
+ enable: true,
+ type: 'Native',
+ onClick: function(node, eventInfo, evt) {
+ if(!config.Events.enable) return;
+ var elem = eventInfo.getContains();
+ config.Events.onClick(elem, eventInfo, evt);
+ },
+ onMouseMove: function(node, eventInfo, evt) {
+ if(!config.hoveredColor) return;
+ if(node) {
+ var elem = eventInfo.getContains();
+ that.select(node.id, elem.name, elem.index);
+ } else {
+ that.select(false, false, false);
+ }
+ }
+ },
+ onCreateLabel: function(domElement, node) {
+ var labelConf = config.Label,
+ valueArray = node.getData('valueArray'),
+ acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0);
+ var nlbs = {
+ wrapper: document.createElement('div'),
+ aggregate: document.createElement('div'),
+ label: document.createElement('div')
+ };
+ var wrapper = nlbs.wrapper,
+ label = nlbs.label,
+ aggregate = nlbs.aggregate,
+ wrapperStyle = wrapper.style,
+ labelStyle = label.style,
+ aggregateStyle = aggregate.style;
+ //store node labels
+ nodeLabels[node.id] = nlbs;
+ //append labels
+ wrapper.appendChild(label);
+ wrapper.appendChild(aggregate);
+ if(!config.showLabels(node.name, acum, node)) {
+ labelStyle.display = 'none';
+ }
+ if(!config.showAggregates(node.name, acum, node)) {
+ aggregateStyle.display = 'none';
+ }
+ wrapperStyle.position = 'relative';
+ wrapperStyle.overflow = 'visible';
+ wrapperStyle.fontSize = labelConf.size + 'px';
+ wrapperStyle.fontFamily = labelConf.family;
+ wrapperStyle.color = labelConf.color;
+ wrapperStyle.textAlign = 'center';
+ aggregateStyle.position = labelStyle.position = 'absolute';
+
+ domElement.style.width = node.getData('width') + 'px';
+ domElement.style.height = node.getData('height') + 'px';
+ aggregateStyle.left = labelStyle.left = '0px';
+
+ label.innerHTML = node.name;
+
+ domElement.appendChild(wrapper);
+ },
+ onPlaceLabel: function(domElement, node) {
+ if(!nodeLabels[node.id]) return;
+ var labels = nodeLabels[node.id],
+ wrapperStyle = labels.wrapper.style,
+ labelStyle = labels.label.style,
+ aggregateStyle = labels.aggregate.style,
+ grouped = config.type.split(':')[0] == 'grouped',
+ horz = config.orientation == 'horizontal',
+ dimArray = node.getData('dimArray'),
+ valArray = node.getData('valueArray'),
+ width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
+ height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
+ font = parseInt(wrapperStyle.fontSize, 10),
+ domStyle = domElement.style;
+
+
+ if(dimArray && valArray) {
+ wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
+ for(var i=0, l=valArray.length, acum=0; i<l; i++) {
+ if(dimArray[i] > 0) {
+ acum+= valArray[i];
+ }
+ }
+ if(config.showLabels(node.name, acum, node)) {
+ labelStyle.display = '';
+ } else {
+ labelStyle.display = 'none';
+ }
+ if(config.showAggregates(node.name, acum, node)) {
+ aggregateStyle.display = '';
+ } else {
+ aggregateStyle.display = 'none';
+ }
+ if(config.orientation == 'horizontal') {
+ aggregateStyle.textAlign = 'right';
+ labelStyle.textAlign = 'left';
+ labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
+ aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
+ domElement.style.height = wrapperStyle.height = height + 'px';
+ } else {
+ aggregateStyle.top = (-font - config.labelOffset) + 'px';
+ labelStyle.top = (config.labelOffset + height) + 'px';
+ domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
+ domElement.style.height = wrapperStyle.height = height + 'px';
+ }
+ labels.aggregate.innerHTML = acum;
+ }
+ }
+ });
+
+ var size = st.canvas.getSize(),
+ margin = config.Margin;
+ if(horz) {
+ st.config.offsetX = size.width/2 - margin.left
+ - (config.showLabels && (config.labelOffset + config.Label.size));
+ st.config.offsetY = (margin.bottom - margin.top)/2;
+ } else {
+ st.config.offsetY = -size.height/2 + margin.bottom
+ + (config.showLabels && (config.labelOffset + config.Label.size));
+ st.config.offsetX = (margin.right - margin.left)/2;
+ }
+ this.st = st;
+ this.canvas = this.st.canvas;
+ },
+
+ /*
+ Method: loadJSON
+
+ Loads JSON data into the visualization.
+
+ Parameters:
+
+ json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
+
+ Example:
+ (start code js)
+ var barChart = new $jit.BarChart(options);
+ barChart.loadJSON(json);
+ (end code)
+ */
+ loadJSON: function(json) {
+ if(this.busy) return;
+ this.busy = true;
+
+ var prefix = $.time(),
+ ch = [],
+ st = this.st,
+ name = $.splat(json.label),
+ color = $.splat(json.color || this.colors),
+ config = this.config,
+ gradient = !!config.type.split(":")[1],
+ animate = config.animate,
+ horz = config.orientation == 'horizontal',
+ that = this;
+
+ for(var i=0, values=json.values, l=values.length; i<l; i++) {
+ var val = values[i]
+ var valArray = $.splat(values[i].values);
+ var acum = 0;
+ ch.push({
+ 'id': prefix + val.label,
+ 'name': val.label,
+ 'data': {
+ 'value': valArray,
+ '$valueArray': valArray,
+ '$colorArray': color,
+ '$stringArray': name,
+ '$gradient': gradient,
+ '$config': config
+ },
+ 'children': []
+ });
+ }
+ var root = {
+ 'id': prefix + '$root',
+ 'name': '',
+ 'data': {
+ '$type': 'none',
+ '$width': 1,
+ '$height': 1
+ },
+ 'children': ch
+ };
+ st.loadJSON(root);
+
+ this.normalizeDims();
+ st.compute();
+ st.select(st.root);
+ if(animate) {
+ if(horz) {
+ st.fx.animate({
+ modes: ['node-property:width:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ }
+ });
+ } else {
+ st.fx.animate({
+ modes: ['node-property:height:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ }
+ });
+ }
+ } else {
+ this.busy = false;
+ }
+ },
+
+ /*
+ Method: updateJSON
+
+ Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
+
+ Parameters:
+
+ json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
+ onComplete - (object) A callback object to be called when the animation transition when updating the data end.
+
+ Example:
+
+ (start code js)
+ barChart.updateJSON(json, {
+ onComplete: function() {
+ alert('update complete!');
+ }
+ });
+ (end code)
+ */
+ updateJSON: function(json, onComplete) {
+ if(this.busy) return;
+ this.busy = true;
+
+ var st = this.st;
+ var graph = st.graph;
+ var values = json.values;
+ var animate = this.config.animate;
+ var that = this;
+ var horz = this.config.orientation == 'horizontal';
+ $.each(values, function(v) {
+ var n = graph.getByName(v.label);
+ if(n) {
+ n.setData('valueArray', $.splat(v.values));
+ if(json.label) {
+ n.setData('stringArray', $.splat(json.label));
+ }
+ }
+ });
+ this.normalizeDims();
+ st.compute();
+ st.select(st.root);
+ if(animate) {
+ if(horz) {
+ st.fx.animate({
+ modes: ['node-property:width:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ onComplete && onComplete.onComplete();
+ }
+ });
+ } else {
+ st.fx.animate({
+ modes: ['node-property:height:dimArray'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ onComplete && onComplete.onComplete();
+ }
+ });
+ }
+ }
+ },
+
+ //adds the little brown bar when hovering the node
+ select: function(id, name) {
+ if(!this.config.hoveredColor) return;
+ var s = this.selected;
+ if(s.id != id || s.name != name) {
+ s.id = id;
+ s.name = name;
+ s.color = this.config.hoveredColor;
+ this.st.graph.eachNode(function(n) {
+ if(id == n.id) {
+ n.setData('border', s);
+ } else {
+ n.setData('border', false);
+ }
+ });
+ this.st.plot();
+ }
+ },
+
+ /*
+ Method: getLegend
+
+ Returns an object containing as keys the legend names and as values hex strings with color values.
+
+ Example:
+
+ (start code js)
+ var legend = barChart.getLegend();
+ (end code)
+ */
+ getLegend: function() {
+ var legend = {};
+ var n;
+ this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
+ n = adj.nodeTo;
+ });
+ var colors = n.getData('colorArray'),
+ len = colors.length;
+ $.each(n.getData('stringArray'), function(s, i) {
+ legend[s] = colors[i % len];
+ });
+ return legend;
+ },
+
+ /*
+ Method: getMaxValue
+
+ Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
+
+ Example:
+
+ (start code js)
+ var ans = barChart.getMaxValue();
+ (end code)
+
+ In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
+
+ Example:
+
+ (start code js)
+ //will return 100 for all BarChart instances,
+ //displaying all of them with the same scale
+ $jit.BarChart.implement({
+ 'getMaxValue': function() {
+ return 100;
+ }
+ });
+ (end code)
+
+ */
+ getMaxValue: function() {
+ var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
+ this.st.graph.eachNode(function(n) {
+ var valArray = n.getData('valueArray'),
+ acum = 0;
+ if(!valArray) return;
+ if(stacked) {
+ $.each(valArray, function(v) {
+ acum += +v;
+ });
+ } else {
+ acum = Math.max.apply(null, valArray);
+ }
+ maxValue = maxValue>acum? maxValue:acum;
+ });
+ return maxValue;
+ },
+
+ setBarType: function(type) {
+ this.config.type = type;
+ this.st.config.Node.type = 'barchart-' + type.split(':')[0];
+ },
+
+ normalizeDims: function() {
+ //number of elements
+ var root = this.st.graph.getNode(this.st.root), l=0;
+ root.eachAdjacency(function() {
+ l++;
+ });
+ var maxValue = this.getMaxValue() || 1,
+ size = this.st.canvas.getSize(),
+ config = this.config,
+ margin = config.Margin,
+ marginWidth = margin.left + margin.right,
+ marginHeight = margin.top + margin.bottom,
+ horz = config.orientation == 'horizontal',
+ fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (l -1) * config.barsOffset) / l,
+ animate = config.animate,
+ height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
+ - (!horz && config.showAggregates && (config.Label.size + config.labelOffset))
+ - (config.showLabels && (config.Label.size + config.labelOffset)),
+ dim1 = horz? 'height':'width',
+ dim2 = horz? 'width':'height';
+ this.st.graph.eachNode(function(n) {
+ var acum = 0, animateValue = [];
+ $.each(n.getData('valueArray'), function(v) {
+ acum += +v;
+ animateValue.push(0);
+ });
+ n.setData(dim1, fixedDim);
+ if(animate) {
+ n.setData(dim2, acum * height / maxValue, 'end');
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return n * height / maxValue;
+ }), 'end');
+ var dimArray = n.getData('dimArray');
+ if(!dimArray) {
+ n.setData('dimArray', animateValue);
+ }
+ } else {
+ n.setData(dim2, acum * height / maxValue);
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return n * height / maxValue;
+ }));
+ }
+ });
+ }
+});
+
+/*
+ * File: Options.PieChart.js
+ *
+*/
+/*
+ Object: Options.PieChart
+
+ <PieChart> options.
+ Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
+
+ Syntax:
+
+ (start code js)
+
+ Options.PieChart = {
+ animate: true,
+ offset: 25,
+ sliceOffset:0,
+ labelOffset: 3,
+ type: 'stacked',
+ hoveredColor: '#9fd4ff',
+ showLabels: true,
+ resizeLabels: false,
+ updateHeights: false
+ };
+
+ (end code)
+
+ Example:
+
+ (start code js)
+
+ var pie = new $jit.PieChart({
+ animate: true,
+ sliceOffset: 5,
+ type: 'stacked:gradient'
+ });
+
+ (end code)
+
+ Parameters:
+
+ animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
+ offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
+ sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
+ labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
+ type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
+ hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
+ showLabels - (boolean) Default's *true*. Display the name of the slots.
+ resizeLabels - (boolean|number) Default's *false*. Resize the pie labels according to their stacked values. Set a number for *resizeLabels* to set a font size minimum.
+ updateHeights - (boolean) Default's *false*. Only for mono-valued (most common) pie charts. Resize the height of the pie slices according to their current values.
+
+*/
+Options.PieChart = {
+ $extend: true,
+
+ animate: true,
+ offset: 25, // page offset
+ sliceOffset:0,
+ labelOffset: 3, // label offset
+ type: 'stacked', // gradient
+ hoveredColor: '#9fd4ff',
+ Events: {
+ enable: false,
+ onClick: $.empty
+ },
+ Tips: {
+ enable: false,
+ onShow: $.empty,
+ onHide: $.empty
+ },
+ showLabels: true,
+ resizeLabels: false,
+
+ //only valid for mono-valued datasets
+ updateHeights: false
+};
+
+/*
+ * Class: Layouts.Radial
+ *
+ * Implements a Radial Layout.
+ *
+ * Implemented By:
+ *
+ * <RGraph>, <Hypertree>
+ *
+ */
+Layouts.Radial = new Class({
+
+ /*
+ * Method: compute
+ *
+ * Computes nodes' positions.
+ *
+ * Parameters:
+ *
+ * property - _optional_ A <Graph.Node> position property to store the new
+ * positions. Possible values are 'pos', 'end' or 'start'.
+ *
+ */
+ compute : function(property) {
+ var prop = $.splat(property || [ 'current', 'start', 'end' ]);
+ NodeDim.compute(this.graph, prop, this.config);
+ this.graph.computeLevels(this.root, 0, "ignore");
+ var lengthFunc = this.createLevelDistanceFunc();
+ this.computeAngularWidths(prop);
+ this.computePositions(prop, lengthFunc);
+ },
+
+ /*
+ * computePositions
+ *
+ * Performs the main algorithm for computing node positions.
+ */
+ computePositions : function(property, getLength) {
+ var propArray = property;
+ var graph = this.graph;
+ var root = graph.getNode(this.root);
+ var parent = this.parent;
+ var config = this.config;
+
+ for ( var i=0, l=propArray.length; i < l; i++) {
+ var pi = propArray[i];
+ root.setPos($P(0, 0), pi);
+ root.setData('span', Math.PI * 2, pi);
+ }
+
+ root.angleSpan = {
+ begin : 0,
+ end : 2 * Math.PI
+ };
+
+ graph.eachBFS(this.root, function(elem) {
+ var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
+ var angleInit = elem.angleSpan.begin;
+ var len = getLength(elem);
+ //Calculate the sum of all angular widths
+ var totalAngularWidths = 0, subnodes = [], maxDim = {};
+ elem.eachSubnode(function(sib) {
+ totalAngularWidths += sib._treeAngularWidth;
+ //get max dim
+ for ( var i=0, l=propArray.length; i < l; i++) {
+ var pi = propArray[i], dim = sib.getData('dim', pi);
+ maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
+ }
+ subnodes.push(sib);
+ }, "ignore");
+ //Maintain children order
+ //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
+ if (parent && parent.id == elem.id && subnodes.length > 0
+ && subnodes[0].dist) {
+ subnodes.sort(function(a, b) {
+ return (a.dist >= b.dist) - (a.dist <= b.dist);
+ });
+ }
+ //Calculate nodes positions.
+ for (var k = 0, ls=subnodes.length; k < ls; k++) {
+ var child = subnodes[k];
+ if (!child._flag) {
+ var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
+ var theta = angleInit + angleProportion / 2;
+
+ for ( var i=0, l=propArray.length; i < l; i++) {
+ var pi = propArray[i];
+ child.setPos($P(theta, len), pi);
+ child.setData('span', angleProportion, pi);
+ child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
+ }
+
+ child.angleSpan = {
+ begin : angleInit,
+ end : angleInit + angleProportion
+ };
+ angleInit += angleProportion;
+ }
+ }
+ }, "ignore");
+ },
+
+ /*
+ * Method: setAngularWidthForNodes
+ *
+ * Sets nodes angular widths.
+ */
+ setAngularWidthForNodes : function(prop) {
+ this.graph.eachBFS(this.root, function(elem, i) {
+ var diamValue = elem.getData('angularWidth', prop[0]) || 5;
+ elem._angularWidth = diamValue / i;
+ }, "ignore");
+ },
+
+ /*
+ * Method: setSubtreesAngularWidth
+ *
+ * Sets subtrees angular widths.
+ */
+ setSubtreesAngularWidth : function() {
+ var that = this;
+ this.graph.eachNode(function(elem) {
+ that.setSubtreeAngularWidth(elem);
+ }, "ignore");
+ },
+
+ /*
+ * Method: setSubtreeAngularWidth
+ *
+ * Sets the angular width for a subtree.
+ */
+ setSubtreeAngularWidth : function(elem) {
+ var that = this, nodeAW = elem._angularWidth, sumAW = 0;
+ elem.eachSubnode(function(child) {
+ that.setSubtreeAngularWidth(child);
+ sumAW += child._treeAngularWidth;
+ }, "ignore");
+ elem._treeAngularWidth = Math.max(nodeAW, sumAW);
+ },
+
+ /*
+ * Method: computeAngularWidths
+ *
+ * Computes nodes and subtrees angular widths.
+ */
+ computeAngularWidths : function(prop) {
+ this.setAngularWidthForNodes(prop);
+ this.setSubtreesAngularWidth();
+ }
+
+});
+
+
+/*
+ * File: Sunburst.js
+ */
+
+/*
+ Class: Sunburst
+
+ A radial space filling tree visualization.
+
+ Inspired by:
+
+ Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
+
+ Note:
+
+ This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
+ levelDistance - (number) Default's *100*. The distance between levels of the tree.
+ Node.type - Described in <Options.Node>. Default's to *multipie*.
+ Node.height - Described in <Options.Node>. Default's *0*.
+ Edge.type - Described in <Options.Edge>. Default's *none*.
+ Label.textAlign - Described in <Options.Label>. Default's *start*.
+ Label.textBaseline - Described in <Options.Label>. Default's *middle*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <Sunburst.Op> instance.
+ fx - Access a <Sunburst.Plot> instance.
+ labels - Access a <Sunburst.Label> interface implementation.
+
+*/
+
+$jit.Sunburst = new Class({
+
+ Implements: [ Loader, Extras, Layouts.Radial ],
+
+ initialize: function(controller) {
+ var $Sunburst = $jit.Sunburst;
+
+ var config = {
+ interpolation: 'linear',
+ levelDistance: 100,
+ Node: {
+ 'type': 'multipie',
+ 'height':0
+ },
+ Edge: {
+ 'type': 'none'
+ },
+ Label: {
+ textAlign: 'start',
+ textBaseline: 'middle'
+ }
+ };
+
+ this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+ "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
+
+ var canvasConfig = this.config;
+ if(canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': false,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node,
+ this.config.Edge);
+ this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
+ this.fx = new $Sunburst.Plot(this, $Sunburst);
+ this.op = new $Sunburst.Op(this);
+ this.json = null;
+ this.root = null;
+ this.rotated = null;
+ this.busy = false;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+
+ createLevelDistanceFunc
+
+ Returns the levelDistance function used for calculating a node distance
+ to its origin. This function returns a function that is computed
+ per level and not per node, such that all nodes with the same depth will have the
+ same distance to the origin. The resulting function gets the
+ parent node as parameter and returns a float.
+
+ */
+ createLevelDistanceFunc: function() {
+ var ld = this.config.levelDistance;
+ return function(elem) {
+ return (elem._depth + 1) * ld;
+ };
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+
+ */
+ refresh: function() {
+ this.compute();
+ this.plot();
+ },
+
+ /*
+ reposition
+
+ An alias for computing new positions to _endPos_
+
+ See also:
+
+ <Sunburst.compute>
+
+ */
+ reposition: function() {
+ this.compute('end');
+ },
+
+ /*
+ Method: rotate
+
+ Rotates the graph so that the selected node is horizontal on the right.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
+ opt - (object) Configuration options merged with this visualization configuration options.
+
+ See also:
+
+ <Sunburst.rotateAngle>
+
+ */
+ rotate: function(node, method, opt) {
+ var theta = node.getPos(opt.property || 'current').getp(true).theta;
+ this.rotated = node;
+ this.rotateAngle(-theta, method, opt);
+ },
+
+ /*
+ Method: rotateAngle
+
+ Rotates the graph of an angle theta.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+ method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
+ opt - (object) Configuration options merged with this visualization configuration options.
+
+ See also:
+
+ <Sunburst.rotate>
+
+ */
+ rotateAngle: function(theta, method, opt) {
+ var that = this;
+ var options = $.merge(this.config, opt || {}, {
+ modes: [ 'polar' ]
+ });
+ var prop = opt.property || (method === "animate" ? 'end' : 'current');
+ if(method === 'animate') {
+ this.fx.animation.pause();
+ }
+ this.graph.eachNode(function(n) {
+ var p = n.getPos(prop);
+ p.theta += theta;
+ if (p.theta < 0) {
+ p.theta += Math.PI * 2;
+ }
+ });
+ if (method == 'animate') {
+ this.fx.animate(options);
+ } else if (method == 'replot') {
+ this.fx.plot();
+ this.busy = false;
+ }
+ },
+
+ /*
+ Method: plot
+
+ Plots the Sunburst. This is a shortcut to *fx.plot*.
+ */
+ plot: function() {
+ this.fx.plot();
+ }
+});
+
+$jit.Sunburst.$extend = true;
+
+(function(Sunburst) {
+
+ /*
+ Class: Sunburst.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+ Sunburst.Op = new Class( {
+
+ Implements: Graph.Op
+
+ });
+
+ /*
+ Class: Sunburst.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+ Sunburst.Plot = new Class( {
+
+ Implements: Graph.Plot
+
+ });
+
+ /*
+ Class: Sunburst.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+ */
+ Sunburst.Label = {};
+
+ /*
+ Sunburst.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+ */
+ Sunburst.Label.Native = new Class( {
+ Implements: Graph.Label.Native,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ this.label = viz.config.Label;
+ this.config = viz.config;
+ },
+
+ renderLabel: function(canvas, node, controller) {
+ var span = node.getData('span');
+ if(span < Math.PI /2 && Math.tan(span) *
+ this.config.levelDistance * node._depth < 10) {
+ return;
+ }
+ var ctx = canvas.getCtx();
+ var measure = ctx.measureText(node.name);
+ if (node.id == this.viz.root) {
+ var x = -measure.width / 2, y = 0, thetap = 0;
+ var ld = 0;
+ } else {
+ var indent = 5;
+ var ld = controller.levelDistance - indent;
+ var clone = node.pos.clone();
+ clone.rho += indent;
+ var p = clone.getp(true);
+ var ct = clone.getc(true);
+ var x = ct.x, y = ct.y;
+ // get angle in degrees
+ var pi = Math.PI;
+ var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
+ var thetap = cond ? p.theta + pi : p.theta;
+ if (cond) {
+ x -= Math.abs(Math.cos(p.theta) * measure.width);
+ y += Math.sin(p.theta) * measure.width;
+ } else if (node.id == this.viz.root) {
+ x -= measure.width / 2;
+ }
+ }
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate(thetap);
+ ctx.fillText(node.name, 0, 0);
+ ctx.restore();
+ }
+ });
+
+ /*
+ Sunburst.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+
+ */
+ Sunburst.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
+ var radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x + radius.width / 2),
+ y: Math.round(pos.y + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+
+ var bb = tag.getBBox();
+ if (bb) {
+ // center the label
+ var x = tag.getAttribute('x');
+ var y = tag.getAttribute('y');
+ // get polar coordinates
+ var p = node.pos.getp(true);
+ // get angle in degrees
+ var pi = Math.PI;
+ var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
+ if (cond) {
+ tag.setAttribute('x', x - bb.width);
+ tag.setAttribute('y', y - bb.height);
+ } else if (node.id == viz.root) {
+ tag.setAttribute('x', x - bb.width / 2);
+ }
+
+ var thetap = cond ? p.theta + pi : p.theta;
+ if(node._depth)
+ tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
+ + ' ' + y + ')');
+ }
+
+ controller.onPlaceLabel(tag, node);
+}
+ });
+
+ /*
+ Sunburst.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+ */
+ Sunburst.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.clone(),
+ canvas = this.viz.canvas,
+ height = node.getData('height'),
+ ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
+ radius = canvas.getSize();
+ pos.rho += ldist;
+ pos = pos.getc(true);
+
+ var labelPos = {
+ x: Math.round(pos.x + radius.width / 2),
+ y: Math.round(pos.y + radius.height / 2)
+ };
+
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ Class: Sunburst.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ Sunburst.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ Sunburst.Plot.NodeTypes = new Class( {
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false),
+ 'anglecontains': function(node, pos) {
+ var span = node.getData('span') / 2, theta = node.pos.theta;
+ var begin = theta - span, end = theta + span;
+ if (begin < 0)
+ begin += Math.PI * 2;
+ var atan = Math.atan2(pos.y, pos.x);
+ if (atan < 0)
+ atan += Math.PI * 2;
+ if (begin > end) {
+ return (atan > begin && atan <= Math.PI * 2) || atan < end;
+ } else {
+ return atan > begin && atan < end;
+ }
+ }
+ },
+
+ 'pie': {
+ 'render': function(node, canvas) {
+ var span = node.getData('span') / 2, theta = node.pos.theta;
+ var begin = theta - span, end = theta + span;
+ var polarNode = node.pos.getp(true);
+ var polar = new Polar(polarNode.rho, begin);
+ var p1coord = polar.getc(true);
+ polar.theta = end;
+ var p2coord = polar.getc(true);
+
+ var ctx = canvas.getCtx();
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(p1coord.x, p1coord.y);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(p2coord.x, p2coord.y);
+ ctx.moveTo(0, 0);
+ ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
+ false);
+ ctx.fill();
+ },
+ 'contains': function(node, pos) {
+ if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
+ var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
+ var ld = this.config.levelDistance, d = node._depth;
+ return (rho <= ld * d);
+ }
+ return false;
+ }
+ },
+ 'multipie': {
+ 'render': function(node, canvas) {
+ var height = node.getData('height');
+ var ldist = height? height : this.config.levelDistance;
+ var span = node.getData('span') / 2, theta = node.pos.theta;
+ var begin = theta - span, end = theta + span;
+ var polarNode = node.pos.getp(true);
+
+ var polar = new Polar(polarNode.rho, begin);
+ var p1coord = polar.getc(true);
+
+ polar.theta = end;
+ var p2coord = polar.getc(true);
+
+ polar.rho += ldist;
+ var p3coord = polar.getc(true);
+
+ polar.theta = begin;
+ var p4coord = polar.getc(true);
+
+ var ctx = canvas.getCtx();
+ ctx.moveTo(0, 0);
+ ctx.beginPath();
+ ctx.arc(0, 0, polarNode.rho, begin, end, false);
+ ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
+ ctx.moveTo(p1coord.x, p1coord.y);
+ ctx.lineTo(p4coord.x, p4coord.y);
+ ctx.moveTo(p2coord.x, p2coord.y);
+ ctx.lineTo(p3coord.x, p3coord.y);
+ ctx.fill();
+
+ if (node.collapsed) {
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.moveTo(0, 0);
+ ctx.beginPath();
+ ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
+ true);
+ ctx.stroke();
+ ctx.restore();
+ }
+ },
+ 'contains': function(node, pos) {
+ if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
+ var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
+ var height = node.getData('height');
+ var ldist = height? height : this.config.levelDistance;
+ var ld = this.config.levelDistance, d = node._depth;
+ return (rho >= ld * d) && (rho <= (ld * d + ldist));
+ }
+ return false;
+ }
+ },
+
+ 'gradient-multipie': {
+ 'render': function(node, canvas) {
+ var ctx = canvas.getCtx();
+ var height = node.getData('height');
+ var ldist = height? height : this.config.levelDistance;
+ var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
+ 0, 0, node.getPos().rho + ldist);
+
+ var colorArray = $.hexToRgb(node.getData('color')), ans = [];
+ $.each(colorArray, function(i) {
+ ans.push(parseInt(i * 0.5, 10));
+ });
+ var endColor = $.rgbToHex(ans);
+ radialGradient.addColorStop(0, endColor);
+ radialGradient.addColorStop(1, node.getData('color'));
+ ctx.fillStyle = radialGradient;
+ this.nodeTypes['multipie'].render.call(this, node, canvas);
+ },
+ 'contains': function(node, pos) {
+ return this.nodeTypes['multipie'].contains.call(this, node, pos);
+ }
+ },
+
+ 'gradient-pie': {
+ 'render': function(node, canvas) {
+ var ctx = canvas.getCtx();
+ var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
+ .getPos().rho);
+
+ var colorArray = $.hexToRgb(node.getData('color')), ans = [];
+ $.each(colorArray, function(i) {
+ ans.push(parseInt(i * 0.5, 10));
+ });
+ var endColor = $.rgbToHex(ans);
+ radialGradient.addColorStop(1, endColor);
+ radialGradient.addColorStop(0, node.getData('color'));
+ ctx.fillStyle = radialGradient;
+ this.nodeTypes['pie'].render.call(this, node, canvas);
+ },
+ 'contains': function(node, pos) {
+ return this.nodeTypes['pie'].contains.call(this, node, pos);
+ }
+ }
+ });
+
+ /*
+ Class: Sunburst.Plot.EdgeTypes
+
+ This class contains a list of <Graph.Adjacence> built-in types.
+ Edge types implemented are 'none', 'line' and 'arrow'.
+
+ You can add your custom edge types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ Sunburst.Plot.EdgeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(adj, canvas) {
+ //print your custom edge to canvas
+ },
+ //optional
+ 'contains': function(adj, pos) {
+ //return true if pos is inside the arc or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ Sunburst.Plot.EdgeTypes = new Class({
+ 'none': $.empty,
+ 'line': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ this.edgeHelper.line.render(from, to, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'arrow': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ dim = adj.getData('dim'),
+ direction = adj.data.$direction,
+ inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
+ this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'hyperline': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(),
+ to = adj.nodeTo.pos.getc(),
+ dim = Math.max(from.norm(), to.norm());
+ this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
+ },
+ 'contains': $.lambda(false) //TODO(nico): Implement this!
+ }
+ });
+
+})($jit.Sunburst);
+
+
+/*
+ * File: PieChart.js
+ *
+*/
+
+$jit.Sunburst.Plot.NodeTypes.implement({
+ 'piechart-stacked' : {
+ 'render' : function(node, canvas) {
+ var pos = node.pos.getp(true),
+ dimArray = node.getData('dimArray'),
+ valueArray = node.getData('valueArray'),
+ colorArray = node.getData('colorArray'),
+ colorLength = colorArray.length,
+ stringArray = node.getData('stringArray'),
+ span = node.getData('span') / 2,
+ theta = node.pos.theta,
+ begin = theta - span,
+ end = theta + span,
+ polar = new Polar;
+
+ var ctx = canvas.getCtx(),
+ opt = {},
+ gradient = node.getData('gradient'),
+ border = node.getData('border'),
+ config = node.getData('config'),
+ showLabels = config.showLabels,
+ resizeLabels = config.resizeLabels,
+ label = config.Label;
+
+ var xpos = config.sliceOffset * Math.cos((begin + end) /2);
+ var ypos = config.sliceOffset * Math.sin((begin + end) /2);
+
+ if (colorArray && dimArray && stringArray) {
+ for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
+ var dimi = dimArray[i], colori = colorArray[i % colorLength];
+ if(dimi <= 0) continue;
+ ctx.fillStyle = ctx.strokeStyle = colori;
+ if(gradient && dimi) {
+ var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
+ xpos, ypos, acum + dimi + config.sliceOffset);
+ var colorRgb = $.hexToRgb(colori),
+ ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
+ endColor = $.rgbToHex(ans);
+
+ radialGradient.addColorStop(0, colori);
+ radialGradient.addColorStop(0.5, colori);
+ radialGradient.addColorStop(1, endColor);
+ ctx.fillStyle = radialGradient;
+ }
+
+ polar.rho = acum + config.sliceOffset;
+ polar.theta = begin;
+ var p1coord = polar.getc(true);
+ polar.theta = end;
+ var p2coord = polar.getc(true);
+ polar.rho += dimi;
+ var p3coord = polar.getc(true);
+ polar.theta = begin;
+ var p4coord = polar.getc(true);
+
+ ctx.beginPath();
+ //fixing FF arc method + fill
+ ctx.arc(xpos, ypos, acum + .01, begin, end, false);
+ ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
+ ctx.fill();
+ if(border && border.name == stringArray[i]) {
+ opt.acum = acum;
+ opt.dimValue = dimArray[i];
+ opt.begin = begin;
+ opt.end = end;
+ }
+ acum += (dimi || 0);
+ valAcum += (valueArray[i] || 0);
+ }
+ if(border) {
+ ctx.save();
+ ctx.globalCompositeOperation = "source-over";
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = border.color;
+ var s = begin < end? 1 : -1;
+ ctx.beginPath();
+ //fixing FF arc method + fill
+ ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
+ ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.restore();
+ }
+ if(showLabels && label.type == 'Native') {
+ ctx.save();
+ ctx.fillStyle = ctx.strokeStyle = label.color;
+ var scale = resizeLabels? node.getData('normalizedDim') : 1,
+ fontSize = (label.size * scale) >> 0;
+ fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
+
+ ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
+ ctx.textBaseline = 'middle';
+ ctx.textAlign = 'center';
+
+ polar.rho = acum + config.labelOffset + config.sliceOffset;
+ polar.theta = node.pos.theta;
+ var cart = polar.getc(true);
+
+ ctx.fillText(node.name, cart.x, cart.y);
+ ctx.restore();
+ }
+ }
+ },
+ 'contains': function(node, pos) {
+ if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
+ var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
+ var ld = this.config.levelDistance, d = node._depth;
+ var config = node.getData('config');
+ if(rho <=ld * d + config.sliceOffset) {
+ var dimArray = node.getData('dimArray');
+ for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
+ var dimi = dimArray[i];
+ if(rho >= acum && rho <= acum + dimi) {
+ return {
+ name: node.getData('stringArray')[i],
+ color: node.getData('colorArray')[i],
+ value: node.getData('valueArray')[i],
+ label: node.name
+ };
+ }
+ acum += dimi;
+ }
+ }
+ return false;
+
+ }
+ return false;
+ }
+ }
+});
+
+/*
+ Class: PieChart
+
+ A visualization that displays stacked bar charts.
+
+ Constructor Options:
+
+ See <Options.PieChart>.
+
+*/
+$jit.PieChart = new Class({
+ sb: null,
+ colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
+ selected: {},
+ busy: false,
+
+ initialize: function(opt) {
+ this.controller = this.config =
+ $.merge(Options("Canvas", "PieChart", "Label"), {
+ Label: { type: 'Native' }
+ }, opt);
+ this.initializeViz();
+ },
+
+ initializeViz: function() {
+ var config = this.config, that = this;
+ var nodeType = config.type.split(":")[0];
+ var sb = new $jit.Sunburst({
+ injectInto: config.injectInto,
+ useCanvas: config.useCanvas,
+ withLabels: config.Label.type != 'Native',
+ Label: {
+ type: config.Label.type
+ },
+ Node: {
+ overridable: true,
+ type: 'piechart-' + nodeType,
+ width: 1,
+ height: 1
+ },
+ Edge: {
+ type: 'none'
+ },
+ Tips: {
+ enable: config.Tips.enable,
+ type: 'Native',
+ force: true,
+ onShow: function(tip, node, contains) {
+ var elem = contains;
+ config.Tips.onShow(tip, elem, node);
+ }
+ },
+ Events: {
+ enable: true,
+ type: 'Native',
+ onClick: function(node, eventInfo, evt) {
+ if(!config.Events.enable) return;
+ var elem = eventInfo.getContains();
+ config.Events.onClick(elem, eventInfo, evt);
+ },
+ onMouseMove: function(node, eventInfo, evt) {
+ if(!config.hoveredColor) return;
+ if(node) {
+ var elem = eventInfo.getContains();
+ that.select(node.id, elem.name, elem.index);
+ } else {
+ that.select(false, false, false);
+ }
+ }
+ },
+ onCreateLabel: function(domElement, node) {
+ var labelConf = config.Label;
+ if(config.showLabels) {
+ var style = domElement.style;
+ style.fontSize = labelConf.size + 'px';
+ style.fontFamily = labelConf.family;
+ style.color = labelConf.color;
+ style.textAlign = 'center';
+ domElement.innerHTML = node.name;
+ }
+ },
+ onPlaceLabel: function(domElement, node) {
+ if(!config.showLabels) return;
+ var pos = node.pos.getp(true),
+ dimArray = node.getData('dimArray'),
+ span = node.getData('span') / 2,
+ theta = node.pos.theta,
+ begin = theta - span,
+ end = theta + span,
+ polar = new Polar;
+
+ var showLabels = config.showLabels,
+ resizeLabels = config.resizeLabels,
+ label = config.Label;
+
+ if (dimArray) {
+ for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
+ acum += dimArray[i];
+ }
+ var scale = resizeLabels? node.getData('normalizedDim') : 1,
+ fontSize = (label.size * scale) >> 0;
+ fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
+ domElement.style.fontSize = fontSize + 'px';
+ polar.rho = acum + config.labelOffset + config.sliceOffset;
+ polar.theta = (begin + end) / 2;
+ var pos = polar.getc(true);
+ var radius = that.canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x + radius.width / 2),
+ y: Math.round(pos.y + radius.height / 2)
+ };
+ domElement.style.left = labelPos.x + 'px';
+ domElement.style.top = labelPos.y + 'px';
+ }
+ }
+ });
+
+ var size = sb.canvas.getSize(),
+ min = Math.min;
+ sb.config.levelDistance = min(size.width, size.height)/2
+ - config.offset - config.sliceOffset;
+ this.sb = sb;
+ this.canvas = this.sb.canvas;
+ this.canvas.getCtx().globalCompositeOperation = 'lighter';
+ },
+
+ /*
+ Method: loadJSON
+
+ Loads JSON data into the visualization.
+
+ Parameters:
+
+ json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
+
+ Example:
+ (start code js)
+ var pieChart = new $jit.PieChart(options);
+ pieChart.loadJSON(json);
+ (end code)
+ */
+ loadJSON: function(json) {
+ var prefix = $.time(),
+ ch = [],
+ sb = this.sb,
+ name = $.splat(json.label),
+ nameLength = name.length,
+ color = $.splat(json.color || this.colors),
+ colorLength = color.length,
+ config = this.config,
+ gradient = !!config.type.split(":")[1],
+ animate = config.animate,
+ mono = nameLength == 1;
+
+ for(var i=0, values=json.values, l=values.length; i<l; i++) {
+ var val = values[i];
+ var valArray = $.splat(val.values);
+ ch.push({
+ 'id': prefix + val.label,
+ 'name': val.label,
+ 'data': {
+ 'value': valArray,
+ '$valueArray': valArray,
+ '$colorArray': mono? $.splat(color[i % colorLength]) : color,
+ '$stringArray': name,
+ '$gradient': gradient,
+ '$config': config,
+ '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
+ },
+ 'children': []
+ });
+ }
+ var root = {
+ 'id': prefix + '$root',
+ 'name': '',
+ 'data': {
+ '$type': 'none',
+ '$width': 1,
+ '$height': 1
+ },
+ 'children': ch
+ };
+ sb.loadJSON(root);
+
+ this.normalizeDims();
+ sb.refresh();
+ if(animate) {
+ sb.fx.animate({
+ modes: ['node-property:dimArray'],
+ duration:1500
+ });
+ }
+ },
+
+ /*
+ Method: updateJSON
+
+ Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
+
+ Parameters:
+
+ json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
+ onComplete - (object) A callback object to be called when the animation transition when updating the data end.
+
+ Example:
+
+ (start code js)
+ pieChart.updateJSON(json, {
+ onComplete: function() {
+ alert('update complete!');
+ }
+ });
+ (end code)
+ */
+ updateJSON: function(json, onComplete) {
+ if(this.busy) return;
+ this.busy = true;
+
+ var sb = this.sb;
+ var graph = sb.graph;
+ var values = json.values;
+ var animate = this.config.animate;
+ var that = this;
+ $.each(values, function(v) {
+ var n = graph.getByName(v.label),
+ vals = $.splat(v.values);
+ if(n) {
+ n.setData('valueArray', vals);
+ n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
+ if(json.label) {
+ n.setData('stringArray', $.splat(json.label));
+ }
+ }
+ });
+ this.normalizeDims();
+ if(animate) {
+ sb.compute('end');
+ sb.fx.animate({
+ modes: ['node-property:dimArray:span', 'linear'],
+ duration:1500,
+ onComplete: function() {
+ that.busy = false;
+ onComplete && onComplete.onComplete();
+ }
+ });
+ } else {
+ sb.refresh();
+ }
+ },
+
+ //adds the little brown bar when hovering the node
+ select: function(id, name) {
+ if(!this.config.hoveredColor) return;
+ var s = this.selected;
+ if(s.id != id || s.name != name) {
+ s.id = id;
+ s.name = name;
+ s.color = this.config.hoveredColor;
+ this.sb.graph.eachNode(function(n) {
+ if(id == n.id) {
+ n.setData('border', s);
+ } else {
+ n.setData('border', false);
+ }
+ });
+ this.sb.plot();
+ }
+ },
+
+ /*
+ Method: getLegend
+
+ Returns an object containing as keys the legend names and as values hex strings with color values.
+
+ Example:
+
+ (start code js)
+ var legend = pieChart.getLegend();
+ (end code)
+ */
+ getLegend: function() {
+ var legend = {};
+ var n;
+ this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
+ n = adj.nodeTo;
+ });
+ var colors = n.getData('colorArray'),
+ len = colors.length;
+ $.each(n.getData('stringArray'), function(s, i) {
+ legend[s] = colors[i % len];
+ });
+ return legend;
+ },
+
+ /*
+ Method: getMaxValue
+
+ Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
+
+ Example:
+
+ (start code js)
+ var ans = pieChart.getMaxValue();
+ (end code)
+
+ In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
+
+ Example:
+
+ (start code js)
+ //will return 100 for all PieChart instances,
+ //displaying all of them with the same scale
+ $jit.PieChart.implement({
+ 'getMaxValue': function() {
+ return 100;
+ }
+ });
+ (end code)
+
+ */
+ getMaxValue: function() {
+ var maxValue = 0;
+ this.sb.graph.eachNode(function(n) {
+ var valArray = n.getData('valueArray'),
+ acum = 0;
+ $.each(valArray, function(v) {
+ acum += +v;
+ });
+ maxValue = maxValue>acum? maxValue:acum;
+ });
+ return maxValue;
+ },
+
+ normalizeDims: function() {
+ //number of elements
+ var root = this.sb.graph.getNode(this.sb.root), l=0;
+ root.eachAdjacency(function() {
+ l++;
+ });
+ var maxValue = this.getMaxValue() || 1,
+ config = this.config,
+ animate = config.animate,
+ rho = this.sb.config.levelDistance;
+ this.sb.graph.eachNode(function(n) {
+ var acum = 0, animateValue = [];
+ $.each(n.getData('valueArray'), function(v) {
+ acum += +v;
+ animateValue.push(1);
+ });
+ var stat = (animateValue.length == 1) && !config.updateHeights;
+ if(animate) {
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return stat? rho: (n * rho / maxValue);
+ }), 'end');
+ var dimArray = n.getData('dimArray');
+ if(!dimArray) {
+ n.setData('dimArray', animateValue);
+ }
+ } else {
+ n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
+ return stat? rho : (n * rho / maxValue);
+ }));
+ }
+ n.setData('normalizedDim', acum / maxValue);
+ });
+ }
+});
+
+/*
+ * Class: Layouts.TM
+ *
+ * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
+ *
+ * Implemented By:
+ *
+ * <TM>
+ *
+ */
+Layouts.TM = {};
+
+Layouts.TM.SliceAndDice = new Class({
+ compute: function(prop) {
+ var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
+ this.controller.onBeforeCompute(root);
+ var size = this.canvas.getSize(),
+ config = this.config,
+ width = size.width,
+ height = size.height;
+ this.graph.computeLevels(this.root, 0, "ignore");
+ //set root position and dimensions
+ root.getPos(prop).setc(-width/2, -height/2);
+ root.setData('width', width, prop);
+ root.setData('height', height + config.titleHeight, prop);
+ this.computePositions(root, root, this.layout.orientation, prop);
+ this.controller.onAfterCompute(root);
+ },
+
+ computePositions: function(par, ch, orn, prop) {
+ //compute children areas
+ var totalArea = 0;
+ par.eachSubnode(function(n) {
+ totalArea += n.getData('area', prop);
+ });
+
+ var config = this.config,
+ offst = config.offset,
+ width = par.getData('width', prop),
+ height = par.getData('height', prop) - config.titleHeight,
+ fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
+
+ var otherSize, size, dim, pos, pos2, posth, pos2th;
+ var horizontal = (orn == "h");
+ if(horizontal) {
+ orn = 'v';
+ otherSize = height;
+ size = width * fact;
+ dim = 'height';
+ pos = 'y';
+ pos2 = 'x';
+ posth = config.titleHeight;
+ pos2th = 0;
+ } else {
+ orn = 'h';
+ otherSize = height * fact;
+ size = width;
+ dim = 'width';
+ pos = 'x';
+ pos2 = 'y';
+ posth = 0;
+ pos2th = config.titleHeight;
+ }
+ var cpos = ch.getPos(prop);
+ ch.setData('width', size, prop);
+ ch.setData('height', otherSize, prop);
+ var offsetSize = 0, tm = this;
+ ch.eachSubnode(function(n) {
+ var p = n.getPos(prop);
+ p[pos] = offsetSize + cpos[pos] + posth;
+ p[pos2] = cpos[pos2] + pos2th;
+ tm.computePositions(ch, n, orn, prop);
+ offsetSize += n.getData(dim, prop);
+ });
+ }
+
+});
+
+Layouts.TM.Area = {
+ /*
+ Method: compute
+
+ Called by loadJSON to calculate recursively all node positions and lay out the tree.
+
+ Parameters:
+
+ json - A JSON tree. See also <Loader.loadJSON>.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ compute: function(prop) {
+ prop = prop || "current";
+ var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
+ this.controller.onBeforeCompute(root);
+ var config = this.config,
+ size = this.canvas.getSize(),
+ width = size.width,
+ height = size.height,
+ offst = config.offset,
+ offwdth = width - offst,
+ offhght = height - offst;
+ this.graph.computeLevels(this.root, 0, "ignore");
+ //set root position and dimensions
+ root.getPos(prop).setc(-width/2, -height/2);
+ root.setData('width', width, prop);
+ root.setData('height', height, prop);
+ //create a coordinates object
+ var coord = {
+ 'top': -height/2 + config.titleHeight,
+ 'left': -width/2,
+ 'width': offwdth,
+ 'height': offhght - config.titleHeight
+ };
+ this.computePositions(root, coord, prop);
+ this.controller.onAfterCompute(root);
+ },
+
+ /*
+ Method: computeDim
+
+ Computes dimensions and positions of a group of nodes
+ according to a custom layout row condition.
+
+ Parameters:
+
+ tail - An array of nodes.
+ initElem - An array of nodes (containing the initial node to be laid).
+ w - A fixed dimension where nodes will be layed out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ comp - A custom comparison function
+ */
+ computeDim: function(tail, initElem, w, coord, comp, prop) {
+ if(tail.length + initElem.length == 1) {
+ var l = (tail.length == 1)? tail : initElem;
+ this.layoutLast(l, w, coord, prop);
+ return;
+ }
+ if(tail.length >= 2 && initElem.length == 0) {
+ initElem = [tail.shift()];
+ }
+ if(tail.length == 0) {
+ if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
+ return;
+ }
+ var c = tail[0];
+ if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
+ this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
+ } else {
+ var newCoords = this.layoutRow(initElem, w, coord, prop);
+ this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
+ }
+ },
+
+
+ /*
+ Method: worstAspectRatio
+
+ Calculates the worst aspect ratio of a group of rectangles.
+
+ See also:
+
+ <http://en.wikipedia.org/wiki/Aspect_ratio>
+
+ Parameters:
+
+ ch - An array of nodes.
+ w - The fixed dimension where rectangles are being laid out.
+
+ Returns:
+
+ The worst aspect ratio.
+
+
+ */
+ worstAspectRatio: function(ch, w) {
+ if(!ch || ch.length == 0) return Number.MAX_VALUE;
+ var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
+ for(var i=0, l=ch.length; i<l; i++) {
+ var area = ch[i]._area;
+ areaSum += area;
+ minArea = minArea < area? minArea : area;
+ maxArea = maxArea > area? maxArea : area;
+ }
+ var sqw = w * w, sqAreaSum = areaSum * areaSum;
+ return Math.max(sqw * maxArea / sqAreaSum,
+ sqAreaSum / (sqw * minArea));
+ },
+
+ /*
+ Method: avgAspectRatio
+
+ Calculates the average aspect ratio of a group of rectangles.
+
+ See also:
+
+ <http://en.wikipedia.org/wiki/Aspect_ratio>
+
+ Parameters:
+
+ ch - An array of nodes.
+ w - The fixed dimension where rectangles are being laid out.
+
+ Returns:
+
+ The average aspect ratio.
+
+
+ */
+ avgAspectRatio: function(ch, w) {
+ if(!ch || ch.length == 0) return Number.MAX_VALUE;
+ var arSum = 0;
+ for(var i=0, l=ch.length; i<l; i++) {
+ var area = ch[i]._area;
+ var h = area / w;
+ arSum += w > h? w / h : h / w;
+ }
+ return arSum / l;
+ },
+
+ /*
+ layoutLast
+
+ Performs the layout of the last computed sibling.
+
+ Parameters:
+
+ ch - An array of nodes.
+ w - A fixed dimension where nodes will be layed out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ layoutLast: function(ch, w, coord, prop) {
+ var child = ch[0];
+ child.getPos(prop).setc(coord.left, coord.top);
+ child.setData('width', coord.width, prop);
+ child.setData('height', coord.height, prop);
+ }
+};
+
+
+Layouts.TM.Squarified = new Class({
+ Implements: Layouts.TM.Area,
+
+ computePositions: function(node, coord, prop) {
+ var config = this.config;
+
+ if (coord.width >= coord.height)
+ this.layout.orientation = 'h';
+ else
+ this.layout.orientation = 'v';
+
+ var ch = node.getSubnodes([1, 1], "ignore");
+ if(ch.length > 0) {
+ this.processChildrenLayout(node, ch, coord, prop);
+ for(var i=0, l=ch.length; i<l; i++) {
+ var chi = ch[i];
+ var offst = config.offset,
+ height = chi.getData('height', prop) - offst - config.titleHeight,
+ width = chi.getData('width', prop) - offst;
+ var chipos = chi.getPos(prop);
+ coord = {
+ 'width': width,
+ 'height': height,
+ 'top': chipos.y + config.titleHeight,
+ 'left': chipos.x
+ };
+ this.computePositions(chi, coord, prop);
+ }
+ }
+ },
+
+ /*
+ Method: processChildrenLayout
+
+ Computes children real areas and other useful parameters for performing the Squarified algorithm.
+
+ Parameters:
+
+ par - The parent node of the json subtree.
+ ch - An Array of nodes
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ processChildrenLayout: function(par, ch, coord, prop) {
+ //compute children real areas
+ var parentArea = coord.width * coord.height;
+ var i, l=ch.length, totalChArea=0, chArea = [];
+ for(i=0; i<l; i++) {
+ chArea[i] = parseFloat(ch[i].getData('area', prop));
+ totalChArea += chArea[i];
+ }
+ for(i=0; i<l; i++) {
+ ch[i]._area = parentArea * chArea[i] / totalChArea;
+ }
+ var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
+ ch.sort(function(a, b) {
+ var diff = b._area - a._area;
+ return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
+ });
+ var initElem = [ch[0]];
+ var tail = ch.slice(1);
+ this.squarify(tail, initElem, minimumSideValue, coord, prop);
+ },
+
+ /*
+ Method: squarify
+
+ Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
+
+ Parameters:
+
+ tail - An array of nodes.
+ initElem - An array of nodes, containing the initial node to be laid out.
+ w - A fixed dimension where nodes will be laid out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ squarify: function(tail, initElem, w, coord, prop) {
+ this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
+ },
+
+ /*
+ Method: layoutRow
+
+ Performs the layout of an array of nodes.
+
+ Parameters:
+
+ ch - An array of nodes.
+ w - A fixed dimension where nodes will be laid out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ layoutRow: function(ch, w, coord, prop) {
+ if(this.layout.horizontal()) {
+ return this.layoutV(ch, w, coord, prop);
+ } else {
+ return this.layoutH(ch, w, coord, prop);
+ }
+ },
+
+ layoutV: function(ch, w, coord, prop) {
+ var totalArea = 0, rnd = function(x) { return x; };
+ $.each(ch, function(elem) { totalArea += elem._area; });
+ var width = rnd(totalArea / w), top = 0;
+ for(var i=0, l=ch.length; i<l; i++) {
+ var h = rnd(ch[i]._area / width);
+ var chi = ch[i];
+ chi.getPos(prop).setc(coord.left, coord.top + top);
+ chi.setData('width', width, prop);
+ chi.setData('height', h, prop);
+ top += h;
+ }
+ var ans = {
+ 'height': coord.height,
+ 'width': coord.width - width,
+ 'top': coord.top,
+ 'left': coord.left + width
+ };
+ //take minimum side value.
+ ans.dim = Math.min(ans.width, ans.height);
+ if(ans.dim != ans.height) this.layout.change();
+ return ans;
+ },
+
+ layoutH: function(ch, w, coord, prop) {
+ var totalArea = 0;
+ $.each(ch, function(elem) { totalArea += elem._area; });
+ var height = totalArea / w,
+ top = coord.top,
+ left = 0;
+
+ for(var i=0, l=ch.length; i<l; i++) {
+ var chi = ch[i];
+ var w = chi._area / height;
+ chi.getPos(prop).setc(coord.left + left, top);
+ chi.setData('width', w, prop);
+ chi.setData('height', height, prop);
+ left += w;
+ }
+ var ans = {
+ 'height': coord.height - height,
+ 'width': coord.width,
+ 'top': coord.top + height,
+ 'left': coord.left
+ };
+ ans.dim = Math.min(ans.width, ans.height);
+ if(ans.dim != ans.width) this.layout.change();
+ return ans;
+ }
+});
+
+Layouts.TM.Strip = new Class({
+ Implements: Layouts.TM.Area,
+
+ /*
+ Method: compute
+
+ Called by loadJSON to calculate recursively all node positions and lay out the tree.
+
+ Parameters:
+
+ json - A JSON subtree. See also <Loader.loadJSON>.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ computePositions: function(node, coord, prop) {
+ var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
+ if(ch.length > 0) {
+ this.processChildrenLayout(node, ch, coord, prop);
+ for(var i=0, l=ch.length; i<l; i++) {
+ var chi = ch[i];
+ var offst = config.offset,
+ height = chi.getData('height', prop) - offst - config.titleHeight,
+ width = chi.getData('width', prop) - offst;
+ var chipos = chi.getPos(prop);
+ coord = {
+ 'width': width,
+ 'height': height,
+ 'top': chipos.y + config.titleHeight,
+ 'left': chipos.x
+ };
+ this.computePositions(chi, coord, prop);
+ }
+ }
+ },
+
+ /*
+ Method: processChildrenLayout
+
+ Computes children real areas and other useful parameters for performing the Strip algorithm.
+
+ Parameters:
+
+ par - The parent node of the json subtree.
+ ch - An Array of nodes
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ processChildrenLayout: function(par, ch, coord, prop) {
+ //compute children real areas
+ var parentArea = coord.width * coord.height;
+ var i, l=ch.length, totalChArea=0, chArea = [];
+ for(i=0; i<l; i++) {
+ chArea[i] = +ch[i].getData('area', prop);
+ totalChArea += chArea[i];
+ }
+ for(i=0; i<l; i++) {
+ ch[i]._area = parentArea * chArea[i] / totalChArea;
+ }
+ var side = this.layout.horizontal()? coord.width : coord.height;
+ var initElem = [ch[0]];
+ var tail = ch.slice(1);
+ this.stripify(tail, initElem, side, coord, prop);
+ },
+
+ /*
+ Method: stripify
+
+ Performs an heuristic method to calculate div elements sizes in order to have
+ a good compromise between aspect ratio and order.
+
+ Parameters:
+
+ tail - An array of nodes.
+ initElem - An array of nodes.
+ w - A fixed dimension where nodes will be layed out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ stripify: function(tail, initElem, w, coord, prop) {
+ this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
+ },
+
+ /*
+ Method: layoutRow
+
+ Performs the layout of an array of nodes.
+
+ Parameters:
+
+ ch - An array of nodes.
+ w - A fixed dimension where nodes will be laid out.
+ coord - A coordinates object specifying width, height, left and top style properties.
+ */
+ layoutRow: function(ch, w, coord, prop) {
+ if(this.layout.horizontal()) {
+ return this.layoutH(ch, w, coord, prop);
+ } else {
+ return this.layoutV(ch, w, coord, prop);
+ }
+ },
+
+ layoutV: function(ch, w, coord, prop) {
+ var totalArea = 0;
+ $.each(ch, function(elem) { totalArea += elem._area; });
+ var width = totalArea / w, top = 0;
+ for(var i=0, l=ch.length; i<l; i++) {
+ var chi = ch[i];
+ var h = chi._area / width;
+ chi.getPos(prop).setc(coord.left,
+ coord.top + (w - h - top));
+ chi.setData('width', width, prop);
+ chi.setData('height', h, prop);
+ top += h;
+ }
+
+ return {
+ 'height': coord.height,
+ 'width': coord.width - width,
+ 'top': coord.top,
+ 'left': coord.left + width,
+ 'dim': w
+ };
+ },
+
+ layoutH: function(ch, w, coord, prop) {
+ var totalArea = 0;
+ $.each(ch, function(elem) { totalArea += elem._area; });
+ var height = totalArea / w,
+ top = coord.height - height,
+ left = 0;
+
+ for(var i=0, l=ch.length; i<l; i++) {
+ var chi = ch[i];
+ var s = chi._area / height;
+ chi.getPos(prop).setc(coord.left + left, coord.top + top);
+ chi.setData('width', s, prop);
+ chi.setData('height', height, prop);
+ left += s;
+ }
+ return {
+ 'height': coord.height - height,
+ 'width': coord.width,
+ 'top': coord.top,
+ 'left': coord.left,
+ 'dim': w
+ };
+ }
+ });
+
+/*
+ * Class: Layouts.Icicle
+ *
+ * Implements the icicle tree layout.
+ *
+ * Implemented By:
+ *
+ * <Icicle>
+ *
+ */
+
+Layouts.Icicle = new Class({
+ /*
+ * Method: compute
+ *
+ * Called by loadJSON to calculate all node positions.
+ *
+ * Parameters:
+ *
+ * posType - The nodes' position to compute. Either "start", "end" or
+ * "current". Defaults to "current".
+ */
+ compute: function(posType) {
+ posType = posType || "current";
+
+ var root = this.graph.getNode(this.root),
+ config = this.config,
+ size = this.canvas.getSize(),
+ width = size.width,
+ height = size.height,
+ offset = config.offset,
+ levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
+
+ this.controller.onBeforeCompute(root);
+
+ Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
+
+ var treeDepth = 0;
+
+ Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
+
+ var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
+ var maxDepth = Math.min(treeDepth, levelsToShow-1);
+ var initialDepth = startNode._depth;
+ if(this.layout.horizontal()) {
+ this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
+ } else {
+ this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
+ }
+ },
+
+ computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
+ root.getPos(posType).setc(x, y);
+ root.setData('width', width, posType);
+ root.setData('height', height, posType);
+
+ var nodeLength, prevNodeLength = 0, totalDim = 0;
+ var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
+
+ if(!children.length)
+ return;
+
+ $.each(children, function(e) { totalDim += e.getData('dim'); });
+
+ for(var i=0, l=children.length; i < l; i++) {
+ if(this.layout.horizontal()) {
+ nodeLength = height * children[i].getData('dim') / totalDim;
+ this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
+ y += nodeLength;
+ } else {
+ nodeLength = width * children[i].getData('dim') / totalDim;
+ this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
+ x += nodeLength;
+ }
+ }
+ }
+});
+
+
+
+/*
+ * File: Icicle.js
+ *
+*/
+
+/*
+ Class: Icicle
+
+ Icicle space filling visualization.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
+ offset - (number) Default's *2*. Boxes offset.
+ constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
+ levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
+ animate - (boolean) Default's *false*. Whether to animate transitions.
+ Node.type - Described in <Options.Node>. Default's *rectangle*.
+ Label.type - Described in <Options.Label>. Default's *Native*.
+ duration - Described in <Options.Fx>. Default's *700*.
+ fps - Described in <Options.Fx>. Default's *45*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <Icicle.Op> instance.
+ fx - Access a <Icicle.Plot> instance.
+ labels - Access a <Icicle.Label> interface implementation.
+
+*/
+
+$jit.Icicle = new Class({
+ Implements: [ Loader, Extras, Layouts.Icicle ],
+
+ layout: {
+ orientation: "h",
+ vertical: function(){
+ return this.orientation == "v";
+ },
+ horizontal: function(){
+ return this.orientation == "h";
+ },
+ change: function(){
+ this.orientation = this.vertical()? "h" : "v";
+ }
+ },
+
+ initialize: function(controller) {
+ var config = {
+ animate: false,
+ orientation: "h",
+ offset: 2,
+ levelsToShow: Number.MAX_VALUE,
+ constrained: false,
+ Node: {
+ type: 'rectangle',
+ overridable: true
+ },
+ Edge: {
+ type: 'none'
+ },
+ Label: {
+ type: 'Native'
+ },
+ duration: 700,
+ fps: 45
+ };
+
+ var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
+ "Events", "Navigation", "Controller", "Label");
+ this.controller = this.config = $.merge(opts, config, controller);
+ this.layout.orientation = this.config.orientation;
+
+ var canvasConfig = this.config;
+ if (canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': true,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+
+ this.graph = new Graph(
+ this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
+
+ this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
+ this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
+ this.op = new $jit.Icicle.Op(this);
+ this.group = new $jit.Icicle.Group(this);
+ this.clickedNode = null;
+
+ this.initializeExtras();
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+ */
+ refresh: function(){
+ var labelType = this.config.Label.type;
+ if(labelType != 'Native') {
+ var that = this;
+ this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
+ }
+ this.compute();
+ this.plot();
+ },
+
+ /*
+ Method: plot
+
+ Plots the Icicle visualization. This is a shortcut to *fx.plot*.
+
+ */
+ plot: function(){
+ this.fx.plot(this.config);
+ },
+
+ /*
+ Method: enter
+
+ Sets the node as root.
+
+ Parameters:
+
+ node - (object) A <Graph.Node>.
+
+ */
+ enter: function (node) {
+ if (this.busy)
+ return;
+ this.busy = true;
+
+ var that = this,
+ config = this.config;
+
+ var callback = {
+ onComplete: function() {
+ //compute positions of newly inserted nodes
+ if(config.request)
+ that.compute();
+
+ if(config.animate) {
+ that.graph.nodeList.setDataset(['current', 'end'], {
+ 'alpha': [1, 0] //fade nodes
+ });
+
+ Graph.Util.eachSubgraph(node, function(n) {
+ n.setData('alpha', 1, 'end');
+ }, "ignore");
+
+ that.fx.animate({
+ duration: 500,
+ modes:['node-property:alpha'],
+ onComplete: function() {
+ that.clickedNode = node;
+ that.compute('end');
+
+ that.fx.animate({
+ modes:['linear', 'node-property:width:height'],
+ duration: 1000,
+ onComplete: function() {
+ that.busy = false;
+ that.clickedNode = node;
+ }
+ });
+ }
+ });
+ } else {
+ that.clickedNode = node;
+ that.busy = false;
+ that.refresh();
+ }
+ }
+ };
+
+ if(config.request) {
+ this.requestNodes(clickedNode, callback);
+ } else {
+ callback.onComplete();
+ }
+ },
+
+ /*
+ Method: out
+
+ Sets the parent node of the current selected node as root.
+
+ */
+ out: function(){
+ if(this.busy)
+ return;
+
+ var that = this,
+ GUtil = Graph.Util,
+ config = this.config,
+ graph = this.graph,
+ parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
+ parent = parents[0],
+ clickedNode = parent,
+ previousClickedNode = this.clickedNode;
+
+ this.busy = true;
+ this.events.hoveredNode = false;
+
+ if(!parent) {
+ this.busy = false;
+ return;
+ }
+
+ //final plot callback
+ callback = {
+ onComplete: function() {
+ that.clickedNode = parent;
+ if(config.request) {
+ that.requestNodes(parent, {
+ onComplete: function() {
+ that.compute();
+ that.plot();
+ that.busy = false;
+ }
+ });
+ } else {
+ that.compute();
+ that.plot();
+ that.busy = false;
+ }
+ }
+ };
+
+ //animate node positions
+ if(config.animate) {
+ this.clickedNode = clickedNode;
+ this.compute('end');
+ //animate the visible subtree only
+ this.clickedNode = previousClickedNode;
+ this.fx.animate({
+ modes:['linear', 'node-property:width:height'],
+ duration: 1000,
+ onComplete: function() {
+ //animate the parent subtree
+ that.clickedNode = clickedNode;
+ //change nodes alpha
+ graph.nodeList.setDataset(['current', 'end'], {
+ 'alpha': [0, 1]
+ });
+ GUtil.eachSubgraph(previousClickedNode, function(node) {
+ node.setData('alpha', 1);
+ }, "ignore");
+ that.fx.animate({
+ duration: 500,
+ modes:['node-property:alpha'],
+ onComplete: function() {
+ callback.onComplete();
+ }
+ });
+ }
+ });
+ } else {
+ callback.onComplete();
+ }
+ },
+ requestNodes: function(node, onComplete){
+ var handler = $.merge(this.controller, onComplete),
+ levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
+
+ if (handler.request) {
+ var leaves = [], d = node._depth;
+ Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
+ if (n.drawn && !Graph.Util.anySubnode(n)) {
+ leaves.push(n);
+ n._level = n._depth - d;
+ if (this.config.constrained)
+ n._level = levelsToShow - n._level;
+
+ }
+ });
+ this.group.requestNodes(leaves, handler);
+ } else {
+ handler.onComplete();
+ }
+ }
+});
+
+/*
+ Class: Icicle.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+$jit.Icicle.Op = new Class({
+
+ Implements: Graph.Op
+
+});
+
+/*
+ * Performs operations on group of nodes.
+ */
+$jit.Icicle.Group = new Class({
+
+ initialize: function(viz){
+ this.viz = viz;
+ this.canvas = viz.canvas;
+ this.config = viz.config;
+ },
+
+ /*
+ * Calls the request method on the controller to request a subtree for each node.
+ */
+ requestNodes: function(nodes, controller){
+ var counter = 0, len = nodes.length, nodeSelected = {};
+ var complete = function(){
+ controller.onComplete();
+ };
+ var viz = this.viz;
+ if (len == 0)
+ complete();
+ for(var i = 0; i < len; i++) {
+ nodeSelected[nodes[i].id] = nodes[i];
+ controller.request(nodes[i].id, nodes[i]._level, {
+ onComplete: function(nodeId, data){
+ if (data && data.children) {
+ data.id = nodeId;
+ viz.op.sum(data, {
+ type: 'nothing'
+ });
+ }
+ if (++counter == len) {
+ Graph.Util.computeLevels(viz.graph, viz.root, 0);
+ complete();
+ }
+ }
+ });
+ }
+ }
+});
+
+/*
+ Class: Icicle.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+$jit.Icicle.Plot = new Class({
+ Implements: Graph.Plot,
+
+ plot: function(opt, animating){
+ opt = opt || this.viz.controller;
+ var viz = this.viz,
+ graph = viz.graph,
+ root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
+ initialDepth = root._depth;
+
+ viz.canvas.clear();
+ this.plotTree(root, $.merge(opt, {
+ 'withLabels': true,
+ 'hideLabels': false,
+ 'plotSubtree': function(root, node) {
+ return !viz.config.constrained ||
+ (node._depth - initialDepth < viz.config.levelsToShow);
+ }
+ }), animating);
+ }
+});
+
+/*
+ Class: Icicle.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+ */
+$jit.Icicle.Label = {};
+
+/*
+ Icicle.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+
+ */
+$jit.Icicle.Label.Native = new Class({
+ Implements: Graph.Label.Native,
+
+ renderLabel: function(canvas, node, controller) {
+ var ctx = canvas.getCtx(),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ size = node.getLabelData('size'),
+ m = ctx.measureText(node.name);
+
+ // Guess as much as possible if the label will fit in the node
+ if(height < (size * 1.5) || width < m.width)
+ return;
+
+ var pos = node.pos.getc(true);
+ ctx.fillText(node.name,
+ pos.x + width / 2,
+ pos.y + height / 2);
+ }
+});
+
+/*
+ Icicle.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+*/
+$jit.Icicle.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz){
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true), canvas = this.viz.canvas;
+ var radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x + radius.width / 2),
+ y: Math.round(pos.y + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+
+ controller.onPlaceLabel(tag, node);
+ }
+});
+
+/*
+ Icicle.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+ */
+$jit.Icicle.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz){
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true), canvas = this.viz.canvas;
+ var radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x + radius.width / 2),
+ y: Math.round(pos.y + radius.height / 2)
+ };
+
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = '';
+
+ controller.onPlaceLabel(tag, node);
+ }
+});
+
+/*
+ Class: Icicle.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'rectangle'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ Icicle.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+$jit.Icicle.Plot.NodeTypes = new Class( {
+ 'none': {
+ 'render': $.empty
+ },
+
+ 'rectangle': {
+ 'render': function(node, canvas, animating) {
+ var config = this.viz.config;
+ var offset = config.offset;
+ var width = node.getData('width');
+ var height = node.getData('height');
+ var border = node.getData('border');
+ var pos = node.pos.getc(true);
+ var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
+ var ctx = canvas.getCtx();
+
+ if(width - offset < 2 || height - offset < 2) return;
+
+ if(config.cushion) {
+ var color = node.getData('color');
+ var lg = ctx.createRadialGradient(posx + (width - offset)/2,
+ posy + (height - offset)/2, 1,
+ posx + (width-offset)/2, posy + (height-offset)/2,
+ width < height? height : width);
+ var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
+ function(r) { return r * 0.3 >> 0; }));
+ lg.addColorStop(0, color);
+ lg.addColorStop(1, colorGrad);
+ ctx.fillStyle = lg;
+ }
+
+ if (border) {
+ ctx.strokeStyle = border;
+ ctx.lineWidth = 3;
+ }
+
+ ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
+ border && ctx.strokeRect(pos.x, pos.y, width, height);
+ },
+
+ 'contains': function(node, pos) {
+ if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
+ }
+ }
+});
+
+$jit.Icicle.Plot.EdgeTypes = new Class( {
+ 'none': $.empty
+});
+
+
+
+/*
+ * File: Layouts.ForceDirected.js
+ *
+*/
+
+/*
+ * Class: Layouts.ForceDirected
+ *
+ * Implements a Force Directed Layout.
+ *
+ * Implemented By:
+ *
+ * <ForceDirected>
+ *
+ * Credits:
+ *
+ * Marcus Cobden <http://marcuscobden.co.uk>
+ *
+ */
+Layouts.ForceDirected = new Class({
+
+ getOptions: function(random) {
+ var s = this.canvas.getSize();
+ var w = s.width, h = s.height;
+ //count nodes
+ var count = 0;
+ this.graph.eachNode(function(n) {
+ count++;
+ });
+ var k2 = w * h / count, k = Math.sqrt(k2);
+ var l = this.config.levelDistance;
+
+ return {
+ width: w,
+ height: h,
+ tstart: w * 0.1,
+ nodef: function(x) { return k2 / (x || 1); },
+ edgef: function(x) { return /* x * x / k; */ k * (x - l); }
+ };
+ },
+
+ compute: function(property, incremental) {
+ var prop = $.splat(property || ['current', 'start', 'end']);
+ var opt = this.getOptions();
+ NodeDim.compute(this.graph, prop, this.config);
+ this.graph.computeLevels(this.root, 0, "ignore");
+ this.graph.eachNode(function(n) {
+ $.each(prop, function(p) {
+ var pos = n.getPos(p);
+ if(pos.equals(Complex.KER)) {
+ pos.x = opt.width/5 * (Math.random() - 0.5);
+ pos.y = opt.height/5 * (Math.random() - 0.5);
+ }
+ //initialize disp vector
+ n.disp = {};
+ $.each(prop, function(p) {
+ n.disp[p] = $C(0, 0);
+ });
+ });
+ });
+ this.computePositions(prop, opt, incremental);
+ },
+
+ computePositions: function(property, opt, incremental) {
+ var times = this.config.iterations, i = 0, that = this;
+ if(incremental) {
+ (function iter() {
+ for(var total=incremental.iter, j=0; j<total; j++) {
+ opt.t = opt.tstart * (1 - i++/(times -1));
+ that.computePositionStep(property, opt);
+ if(i >= times) {
+ incremental.onComplete();
+ return;
+ }
+ }
+ incremental.onStep(Math.round(i / (times -1) * 100));
+ setTimeout(iter, 1);
+ })();
+ } else {
+ for(; i < times; i++) {
+ opt.t = opt.tstart * (1 - i/(times -1));
+ this.computePositionStep(property, opt);
+ }
+ }
+ },
+
+ computePositionStep: function(property, opt) {
+ var graph = this.graph;
+ var min = Math.min, max = Math.max;
+ var dpos = $C(0, 0);
+ //calculate repulsive forces
+ graph.eachNode(function(v) {
+ //initialize disp
+ $.each(property, function(p) {
+ v.disp[p].x = 0; v.disp[p].y = 0;
+ });
+ graph.eachNode(function(u) {
+ if(u.id != v.id) {
+ $.each(property, function(p) {
+ var vp = v.getPos(p), up = u.getPos(p);
+ dpos.x = vp.x - up.x;
+ dpos.y = vp.y - up.y;
+ var norm = dpos.norm() || 1;
+ v.disp[p].$add(dpos
+ .$scale(opt.nodef(norm) / norm));
+ });
+ }
+ });
+ });
+ //calculate attractive forces
+ var T = !!graph.getNode(this.root).visited;
+ graph.eachNode(function(node) {
+ node.eachAdjacency(function(adj) {
+ var nodeTo = adj.nodeTo;
+ if(!!nodeTo.visited === T) {
+ $.each(property, function(p) {
+ var vp = node.getPos(p), up = nodeTo.getPos(p);
+ dpos.x = vp.x - up.x;
+ dpos.y = vp.y - up.y;
+ var norm = dpos.norm() || 1;
+ node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
+ nodeTo.disp[p].$add(dpos.$scale(-1));
+ });
+ }
+ });
+ node.visited = !T;
+ });
+ //arrange positions to fit the canvas
+ var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
+ graph.eachNode(function(u) {
+ $.each(property, function(p) {
+ var disp = u.disp[p];
+ var norm = disp.norm() || 1;
+ var p = u.getPos(p);
+ p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
+ disp.y * min(Math.abs(disp.y), t) / norm));
+ p.x = min(w2, max(-w2, p.x));
+ p.y = min(h2, max(-h2, p.y));
+ });
+ });
+ }
+});
+
+/*
+ * File: ForceDirected.js
+ */
+
+/*
+ Class: ForceDirected
+
+ A visualization that lays graphs using a Force-Directed layout algorithm.
+
+ Inspired by:
+
+ Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are two parameters
+
+ levelDistance - (number) Default's *50*. The natural length desired for the edges.
+ iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <ForceDirected.Op> instance.
+ fx - Access a <ForceDirected.Plot> instance.
+ labels - Access a <ForceDirected.Label> interface implementation.
+
+*/
+
+$jit.ForceDirected = new Class( {
+
+ Implements: [ Loader, Extras, Layouts.ForceDirected ],
+
+ initialize: function(controller) {
+ var $ForceDirected = $jit.ForceDirected;
+
+ var config = {
+ iterations: 50,
+ levelDistance: 50
+ };
+
+ this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+ "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
+
+ var canvasConfig = this.config;
+ if(canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': true,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node,
+ this.config.Edge);
+ this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
+ this.fx = new $ForceDirected.Plot(this, $ForceDirected);
+ this.op = new $ForceDirected.Op(this);
+ this.json = null;
+ this.busy = false;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+ */
+ refresh: function() {
+ this.compute();
+ this.plot();
+ },
+
+ reposition: function() {
+ this.compute('end');
+ },
+
+/*
+ Method: computeIncremental
+
+ Performs the Force Directed algorithm incrementally.
+
+ Description:
+
+ ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
+ This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
+ avoiding browser messages such as "This script is taking too long to complete".
+
+ Parameters:
+
+ opt - (object) The object properties are described below
+
+ iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
+ of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
+
+ property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
+ You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
+ computations for final animation positions then you can just choose 'end'.
+
+ onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
+ parameter a percentage value.
+
+ onComplete - A callback function called when the algorithm completed.
+
+ Example:
+
+ In this example I calculate the end positions and then animate the graph to those positions
+
+ (start code js)
+ var fd = new $jit.ForceDirected(...);
+ fd.computeIncremental({
+ iter: 20,
+ property: 'end',
+ onStep: function(perc) {
+ Log.write("loading " + perc + "%");
+ },
+ onComplete: function() {
+ Log.write("done");
+ fd.animate();
+ }
+ });
+ (end code)
+
+ In this example I calculate all positions and (re)plot the graph
+
+ (start code js)
+ var fd = new ForceDirected(...);
+ fd.computeIncremental({
+ iter: 20,
+ property: ['end', 'start', 'current'],
+ onStep: function(perc) {
+ Log.write("loading " + perc + "%");
+ },
+ onComplete: function() {
+ Log.write("done");
+ fd.plot();
+ }
+ });
+ (end code)
+
+ */
+ computeIncremental: function(opt) {
+ opt = $.merge( {
+ iter: 20,
+ property: 'end',
+ onStep: $.empty,
+ onComplete: $.empty
+ }, opt || {});
+
+ this.config.onBeforeCompute(this.graph.getNode(this.root));
+ this.compute(opt.property, opt);
+ },
+
+ /*
+ Method: plot
+
+ Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
+ */
+ plot: function() {
+ this.fx.plot();
+ },
+
+ /*
+ Method: animate
+
+ Animates the graph from the current positions to the 'end' node positions.
+ */
+ animate: function(opt) {
+ this.fx.animate($.merge( {
+ modes: [ 'linear' ]
+ }, opt || {}));
+ }
+});
+
+$jit.ForceDirected.$extend = true;
+
+(function(ForceDirected) {
+
+ /*
+ Class: ForceDirected.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+ ForceDirected.Op = new Class( {
+
+ Implements: Graph.Op
+
+ });
+
+ /*
+ Class: ForceDirected.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+ ForceDirected.Plot = new Class( {
+
+ Implements: Graph.Plot
+
+ });
+
+ /*
+ Class: ForceDirected.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+ */
+ ForceDirected.Label = {};
+
+ /*
+ ForceDirected.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+
+ */
+ ForceDirected.Label.Native = new Class( {
+ Implements: Graph.Label.Native
+ });
+
+ /*
+ ForceDirected.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+
+ */
+ ForceDirected.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Label>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ ForceDirected.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+ */
+ ForceDirected.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ Class: ForceDirected.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ ForceDirected.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ ForceDirected.Plot.NodeTypes = new Class({
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false)
+ },
+ 'circle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.circle.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.circle.contains(npos, pos, dim);
+ }
+ },
+ 'ellipse': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
+ },
+ // TODO(nico): be more precise...
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ return this.nodeHelper.ellipse.contains(npos, pos, width, height);
+ }
+ },
+ 'square': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.square.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.square.contains(npos, pos, dim);
+ }
+ },
+ 'rectangle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ return this.nodeHelper.rectangle.contains(npos, pos, width, height);
+ }
+ },
+ 'triangle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.triangle.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos) {
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.triangle.contains(npos, pos, dim);
+ }
+ },
+ 'star': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.star.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos) {
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.star.contains(npos, pos, dim);
+ }
+ }
+ });
+
+ /*
+ Class: ForceDirected.Plot.EdgeTypes
+
+ This class contains a list of <Graph.Adjacence> built-in types.
+ Edge types implemented are 'none', 'line' and 'arrow'.
+
+ You can add your custom edge types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ ForceDirected.Plot.EdgeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(adj, canvas) {
+ //print your custom edge to canvas
+ },
+ //optional
+ 'contains': function(adj, pos) {
+ //return true if pos is inside the arc or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ ForceDirected.Plot.EdgeTypes = new Class({
+ 'none': $.empty,
+ 'line': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ this.edgeHelper.line.render(from, to, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'arrow': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ dim = adj.getData('dim'),
+ direction = adj.data.$direction,
+ inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
+ this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
+ }
+ }
+ });
+
+})($jit.ForceDirected);
+
+
+/*
+ * File: Treemap.js
+ *
+*/
+
+$jit.TM = {};
+
+var TM = $jit.TM;
+
+$jit.TM.$extend = true;
+
+/*
+ Class: TM.Base
+
+ Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
+ titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
+ offset - (number) Default's *2*. Boxes offset.
+ constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
+ levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
+ animate - (boolean) Default's *false*. Whether to animate transitions.
+ Node.type - Described in <Options.Node>. Default's *rectangle*.
+ duration - Described in <Options.Fx>. Default's *700*.
+ fps - Described in <Options.Fx>. Default's *45*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <TM.Op> instance.
+ fx - Access a <TM.Plot> instance.
+ labels - Access a <TM.Label> interface implementation.
+
+ Inspired by:
+
+ Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
+
+ Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
+
+ Note:
+
+ This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
+
+*/
+TM.Base = {
+ layout: {
+ orientation: "h",
+ vertical: function(){
+ return this.orientation == "v";
+ },
+ horizontal: function(){
+ return this.orientation == "h";
+ },
+ change: function(){
+ this.orientation = this.vertical()? "h" : "v";
+ }
+ },
+
+ initialize: function(controller){
+ var config = {
+ orientation: "h",
+ titleHeight: 13,
+ offset: 2,
+ levelsToShow: 0,
+ constrained: false,
+ animate: false,
+ Node: {
+ type: 'rectangle',
+ overridable: true,
+ //we all know why this is not zero,
+ //right, Firefox?
+ width: 3,
+ height: 3,
+ color: '#444'
+ },
+ Label: {
+ textAlign: 'center',
+ textBaseline: 'top'
+ },
+ Edge: {
+ type: 'none'
+ },
+ duration: 700,
+ fps: 45
+ };
+
+ this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+ "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
+ this.layout.orientation = this.config.orientation;
+
+ var canvasConfig = this.config;
+ if (canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': true,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node,
+ this.config.Edge);
+ this.labels = new TM.Label[canvasConfig.Label.type](this);
+ this.fx = new TM.Plot(this);
+ this.op = new TM.Op(this);
+ this.group = new TM.Group(this);
+ this.geom = new TM.Geom(this);
+ this.clickedNode = null;
+ this.busy = false;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+ */
+ refresh: function(){
+ if(this.busy) return;
+ this.busy = true;
+ var that = this;
+ if(this.config.animate) {
+ this.compute('end');
+ this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
+ && this.clickedNode.id || this.root));
+ this.fx.animate($.merge(this.config, {
+ modes: ['linear', 'node-property:width:height'],
+ onComplete: function() {
+ that.busy = false;
+ }
+ }));
+ } else {
+ var labelType = this.config.Label.type;
+ if(labelType != 'Native') {
+ var that = this;
+ this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
+ }
+ this.busy = false;
+ this.compute();
+ this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
+ && this.clickedNode.id || this.root));
+ this.plot();
+ }
+ },
+
+ /*
+ Method: plot
+
+ Plots the TreeMap. This is a shortcut to *fx.plot*.
+
+ */
+ plot: function(){
+ this.fx.plot();
+ },
+
+ /*
+ Method: leaf
+
+ Returns whether the node is a leaf.
+
+ Parameters:
+
+ n - (object) A <Graph.Node>.
+
+ */
+ leaf: function(n){
+ return n.getSubnodes([
+ 1, 1
+ ], "ignore").length == 0;
+ },
+
+ /*
+ Method: enter
+
+ Sets the node as root.
+
+ Parameters:
+
+ n - (object) A <Graph.Node>.
+
+ */
+ enter: function(n){
+ if(this.busy) return;
+ this.busy = true;
+
+ var that = this,
+ config = this.config,
+ graph = this.graph,
+ clickedNode = n,
+ previousClickedNode = this.clickedNode;
+
+ var callback = {
+ onComplete: function() {
+ //ensure that nodes are shown for that level
+ if(config.levelsToShow > 0) {
+ that.geom.setRightLevelToShow(n);
+ }
+ //compute positions of newly inserted nodes
+ if(config.levelsToShow > 0 || config.request) that.compute();
+ if(config.animate) {
+ //fade nodes
+ graph.nodeList.setData('alpha', 0, 'end');
+ n.eachSubgraph(function(n) {
+ n.setData('alpha', 1, 'end');
+ }, "ignore");
+ that.fx.animate({
+ duration: 500,
+ modes:['node-property:alpha'],
+ onComplete: function() {
+ //compute end positions
+ that.clickedNode = clickedNode;
+ that.compute('end');
+ //animate positions
+ //TODO(nico) commenting this line didn't seem to throw errors...
+ that.clickedNode = previousClickedNode;
+ that.fx.animate({
+ modes:['linear', 'node-property:width:height'],
+ duration: 1000,
+ onComplete: function() {
+ that.busy = false;
+ //TODO(nico) check comment above
+ that.clickedNode = clickedNode;
+ }
+ });
+ }
+ });
+ } else {
+ that.busy = false;
+ that.clickedNode = n;
+ that.refresh();
+ }
+ }
+ };
+ if(config.request) {
+ this.requestNodes(clickedNode, callback);
+ } else {
+ callback.onComplete();
+ }
+ },
+
+ /*
+ Method: out
+
+ Sets the parent node of the current selected node as root.
+
+ */
+ out: function(){
+ if(this.busy) return;
+ this.busy = true;
+ this.events.hoveredNode = false;
+ var that = this,
+ config = this.config,
+ graph = this.graph,
+ parents = graph.getNode(this.clickedNode
+ && this.clickedNode.id || this.root).getParents(),
+ parent = parents[0],
+ clickedNode = parent,
+ previousClickedNode = this.clickedNode;
+
+ //if no parents return
+ if(!parent) {
+ this.busy = false;
+ return;
+ }
+ //final plot callback
+ callback = {
+ onComplete: function() {
+ that.clickedNode = parent;
+ if(config.request) {
+ that.requestNodes(parent, {
+ onComplete: function() {
+ that.compute();
+ that.plot();
+ that.busy = false;
+ }
+ });
+ } else {
+ that.compute();
+ that.plot();
+ that.busy = false;
+ }
+ }
+ };
+ //prune tree
+ if (config.levelsToShow > 0)
+ this.geom.setRightLevelToShow(parent);
+ //animate node positions
+ if(config.animate) {
+ this.clickedNode = clickedNode;
+ this.compute('end');
+ //animate the visible subtree only
+ this.clickedNode = previousClickedNode;
+ this.fx.animate({
+ modes:['linear', 'node-property:width:height'],
+ duration: 1000,
+ onComplete: function() {
+ //animate the parent subtree
+ that.clickedNode = clickedNode;
+ //change nodes alpha
+ graph.eachNode(function(n) {
+ n.setDataset(['current', 'end'], {
+ 'alpha': [0, 1]
+ });
+ }, "ignore");
+ previousClickedNode.eachSubgraph(function(node) {
+ node.setData('alpha', 1);
+ }, "ignore");
+ that.fx.animate({
+ duration: 500,
+ modes:['node-property:alpha'],
+ onComplete: function() {
+ callback.onComplete();
+ }
+ });
+ }
+ });
+ } else {
+ callback.onComplete();
+ }
+ },
+
+ requestNodes: function(node, onComplete){
+ var handler = $.merge(this.controller, onComplete),
+ lev = this.config.levelsToShow;
+ if (handler.request) {
+ var leaves = [], d = node._depth;
+ node.eachLevel(0, lev, function(n){
+ var nodeLevel = lev - (n._depth - d);
+ if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
+ leaves.push(n);
+ n._level = nodeLevel;
+ }
+ });
+ this.group.requestNodes(leaves, handler);
+ } else {
+ handler.onComplete();
+ }
+ }
+};
+
+/*
+ Class: TM.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+TM.Op = new Class({
+ Implements: Graph.Op,
+
+ initialize: function(viz){
+ this.viz = viz;
+ }
+});
+
+//extend level methods of Graph.Geom
+TM.Geom = new Class({
+ Implements: Graph.Geom,
+
+ getRightLevelToShow: function() {
+ return this.viz.config.levelsToShow;
+ },
+
+ setRightLevelToShow: function(node) {
+ var level = this.getRightLevelToShow(),
+ fx = this.viz.labels;
+ node.eachLevel(0, level+1, function(n) {
+ var d = n._depth - node._depth;
+ if(d > level) {
+ n.drawn = false;
+ n.exist = false;
+ n.ignore = true;
+ fx.hideLabel(n, false);
+ } else {
+ n.drawn = true;
+ n.exist = true;
+ delete n.ignore;
+ }
+ });
+ node.drawn = true;
+ delete node.ignore;
+ }
+});
+
+/*
+
+Performs operations on group of nodes.
+
+*/
+TM.Group = new Class( {
+
+ initialize: function(viz){
+ this.viz = viz;
+ this.canvas = viz.canvas;
+ this.config = viz.config;
+ },
+
+ /*
+
+ Calls the request method on the controller to request a subtree for each node.
+ */
+ requestNodes: function(nodes, controller){
+ var counter = 0, len = nodes.length, nodeSelected = {};
+ var complete = function(){
+ controller.onComplete();
+ };
+ var viz = this.viz;
+ if (len == 0)
+ complete();
+ for ( var i = 0; i < len; i++) {
+ nodeSelected[nodes[i].id] = nodes[i];
+ controller.request(nodes[i].id, nodes[i]._level, {
+ onComplete: function(nodeId, data){
+ if (data && data.children) {
+ data.id = nodeId;
+ viz.op.sum(data, {
+ type: 'nothing'
+ });
+ }
+ if (++counter == len) {
+ viz.graph.computeLevels(viz.root, 0);
+ complete();
+ }
+ }
+ });
+ }
+ }
+});
+
+/*
+ Class: TM.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+TM.Plot = new Class({
+
+ Implements: Graph.Plot,
+
+ initialize: function(viz){
+ this.viz = viz;
+ this.config = viz.config;
+ this.node = this.config.Node;
+ this.edge = this.config.Edge;
+ this.animation = new Animation;
+ this.nodeTypes = new TM.Plot.NodeTypes;
+ this.edgeTypes = new TM.Plot.EdgeTypes;
+ this.labels = viz.labels;
+ },
+
+ plot: function(opt, animating){
+ var viz = this.viz,
+ graph = viz.graph;
+ viz.canvas.clear();
+ this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
+ 'withLabels': true,
+ 'hideLabels': false,
+ 'plotSubtree': function(n, ch){
+ return n.anySubnode("exist");
+ }
+ }), animating);
+ }
+});
+
+/*
+ Class: TM.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+*/
+TM.Label = {};
+
+/*
+ TM.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+*/
+TM.Label.Native = new Class({
+ Implements: Graph.Label.Native,
+
+ initialize: function(viz) {
+ this.config = viz.config;
+ this.leaf = viz.leaf;
+ },
+
+ renderLabel: function(canvas, node, controller){
+ if(!this.leaf(node) && !this.config.titleHeight) return;
+ var pos = node.pos.getc(true),
+ ctx = canvas.getCtx(),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ x = pos.x + width/2,
+ y = pos.y;
+
+ ctx.fillText(node.name, x, y, width);
+ }
+});
+
+/*
+ TM.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+*/
+TM.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz){
+ this.viz = viz;
+ this.leaf = viz.leaf;
+ this.config = viz.config;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+
+ if(!this.leaf(node) && !this.config.titleHeight) {
+ tag.style.display = 'none';
+ }
+ controller.onPlaceLabel(tag, node);
+ }
+});
+
+/*
+ TM.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+*/
+TM.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz){
+ this.viz = viz;
+ this.leaf = viz.leaf;
+ this.config = viz.config;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.width = node.getData('width') * sx + 'px';
+ style.height = node.getData('height') * sy + 'px';
+ style.zIndex = node._depth * 100;
+ style.display = '';
+
+ if(!this.leaf(node) && !this.config.titleHeight) {
+ tag.style.display = 'none';
+ }
+ controller.onPlaceLabel(tag, node);
+ }
+});
+
+/*
+ Class: TM.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'rectangle'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ TM.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+*/
+TM.Plot.NodeTypes = new Class( {
+ 'none': {
+ 'render': $.empty
+ },
+
+ 'rectangle': {
+ 'render': function(node, canvas, animating){
+ var leaf = this.viz.leaf(node),
+ config = this.config,
+ offst = config.offset,
+ titleHeight = config.titleHeight,
+ pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height'),
+ border = node.getData('border'),
+ ctx = canvas.getCtx(),
+ posx = pos.x + offst / 2,
+ posy = pos.y + offst / 2;
+ if(width <= offst || height <= offst) return;
+ if (leaf) {
+ if(config.cushion) {
+ var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
+ posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
+ var color = node.getData('color');
+ var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
+ function(r) { return r * 0.2 >> 0; }));
+ lg.addColorStop(0, color);
+ lg.addColorStop(1, colorGrad);
+ ctx.fillStyle = lg;
+ }
+ ctx.fillRect(posx, posy, width - offst, height - offst);
+ if(border) {
+ ctx.save();
+ ctx.strokeStyle = border;
+ ctx.strokeRect(posx, posy, width - offst, height - offst);
+ ctx.restore();
+ }
+ } else if(titleHeight > 0){
+ ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
+ titleHeight - offst);
+ if(border) {
+ ctx.save();
+ ctx.strokeStyle = border;
+ ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
+ height - offst);
+ ctx.restore();
+ }
+ }
+ },
+ 'contains': function(node, pos) {
+ if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ leaf = this.viz.leaf(node),
+ height = leaf? node.getData('height') : this.config.titleHeight;
+ return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
+ }
+ }
+});
+
+TM.Plot.EdgeTypes = new Class( {
+ 'none': $.empty
+});
+
+/*
+ Class: TM.SliceAndDice
+
+ A slice and dice TreeMap visualization.
+
+ Implements:
+
+ All <TM.Base> methods and properties.
+*/
+TM.SliceAndDice = new Class( {
+ Implements: [
+ Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
+ ]
+});
+
+/*
+ Class: TM.Squarified
+
+ A squarified TreeMap visualization.
+
+ Implements:
+
+ All <TM.Base> methods and properties.
+*/
+TM.Squarified = new Class( {
+ Implements: [
+ Loader, Extras, TM.Base, Layouts.TM.Squarified
+ ]
+});
+
+/*
+ Class: TM.Strip
+
+ A strip TreeMap visualization.
+
+ Implements:
+
+ All <TM.Base> methods and properties.
+*/
+TM.Strip = new Class( {
+ Implements: [
+ Loader, Extras, TM.Base, Layouts.TM.Strip
+ ]
+});
+
+
+/*
+ * File: RGraph.js
+ *
+ */
+
+/*
+ Class: RGraph
+
+ A radial graph visualization with advanced animations.
+
+ Inspired by:
+
+ Animated Exploration of Dynamic Graphs with Radial Layout (Ka-Ping Yee, Danyel Fisher, Rachna Dhamija, Marti Hearst) <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
+
+ Note:
+
+ This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
+ levelDistance - (number) Default's *100*. The distance between levels of the tree.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <RGraph.Op> instance.
+ fx - Access a <RGraph.Plot> instance.
+ labels - Access a <RGraph.Label> interface implementation.
+*/
+
+$jit.RGraph = new Class( {
+
+ Implements: [
+ Loader, Extras, Layouts.Radial
+ ],
+
+ initialize: function(controller){
+ var $RGraph = $jit.RGraph;
+
+ var config = {
+ interpolation: 'linear',
+ levelDistance: 100
+ };
+
+ this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+ "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
+
+ var canvasConfig = this.config;
+ if(canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': false,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node,
+ this.config.Edge);
+ this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
+ this.fx = new $RGraph.Plot(this, $RGraph);
+ this.op = new $RGraph.Op(this);
+ this.json = null;
+ this.root = null;
+ this.busy = false;
+ this.parent = false;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+
+ createLevelDistanceFunc
+
+ Returns the levelDistance function used for calculating a node distance
+ to its origin. This function returns a function that is computed
+ per level and not per node, such that all nodes with the same depth will have the
+ same distance to the origin. The resulting function gets the
+ parent node as parameter and returns a float.
+
+ */
+ createLevelDistanceFunc: function(){
+ var ld = this.config.levelDistance;
+ return function(elem){
+ return (elem._depth + 1) * ld;
+ };
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+
+ */
+ refresh: function(){
+ this.compute();
+ this.plot();
+ },
+
+ reposition: function(){
+ this.compute('end');
+ },
+
+ /*
+ Method: plot
+
+ Plots the RGraph. This is a shortcut to *fx.plot*.
+ */
+ plot: function(){
+ this.fx.plot();
+ },
+ /*
+ getNodeAndParentAngle
+
+ Returns the _parent_ of the given node, also calculating its angle span.
+ */
+ getNodeAndParentAngle: function(id){
+ var theta = false;
+ var n = this.graph.getNode(id);
+ var ps = n.getParents();
+ var p = (ps.length > 0)? ps[0] : false;
+ if (p) {
+ var posParent = p.pos.getc(), posChild = n.pos.getc();
+ var newPos = posParent.add(posChild.scale(-1));
+ theta = Math.atan2(newPos.y, newPos.x);
+ if (theta < 0)
+ theta += 2 * Math.PI;
+ }
+ return {
+ parent: p,
+ theta: theta
+ };
+ },
+ /*
+ tagChildren
+
+ Enumerates the children in order to maintain child ordering (second constraint of the paper).
+ */
+ tagChildren: function(par, id){
+ if (par.angleSpan) {
+ var adjs = [];
+ par.eachAdjacency(function(elem){
+ adjs.push(elem.nodeTo);
+ }, "ignore");
+ var len = adjs.length;
+ for ( var i = 0; i < len && id != adjs[i].id; i++)
+ ;
+ for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
+ adjs[j].dist = k++;
+ }
+ }
+ },
+ /*
+ Method: onClick
+
+ Animates the <RGraph> to center the node specified by *id*.
+
+ Parameters:
+
+ id - A <Graph.Node> id.
+ opt - (optional|object) An object containing some extra properties described below
+ hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
+
+ Example:
+
+ (start code js)
+ rgraph.onClick('someid');
+ //or also...
+ rgraph.onClick('someid', {
+ hideLabels: false
+ });
+ (end code)
+
+ */
+ onClick: function(id, opt){
+ if (this.root != id && !this.busy) {
+ this.busy = true;
+ this.root = id;
+ that = this;
+ this.controller.onBeforeCompute(this.graph.getNode(id));
+ var obj = this.getNodeAndParentAngle(id);
+
+ // second constraint
+ this.tagChildren(obj.parent, id);
+ this.parent = obj.parent;
+ this.compute('end');
+
+ // first constraint
+ var thetaDiff = obj.theta - obj.parent.endPos.theta;
+ this.graph.eachNode(function(elem){
+ elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
+ });
+
+ var mode = this.config.interpolation;
+ opt = $.merge( {
+ onComplete: $.empty
+ }, opt || {});
+
+ this.fx.animate($.merge( {
+ hideLabels: true,
+ modes: [
+ mode
+ ]
+ }, opt, {
+ onComplete: function(){
+ that.busy = false;
+ opt.onComplete();
+ }
+ }));
+ }
+ }
+});
+
+$jit.RGraph.$extend = true;
+
+(function(RGraph){
+
+ /*
+ Class: RGraph.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+ RGraph.Op = new Class( {
+
+ Implements: Graph.Op
+
+ });
+
+ /*
+ Class: RGraph.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+ RGraph.Plot = new Class( {
+
+ Implements: Graph.Plot
+
+ });
+
+ /*
+ Object: RGraph.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+ */
+ RGraph.Label = {};
+
+ /*
+ RGraph.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+
+ */
+ RGraph.Label.Native = new Class( {
+ Implements: Graph.Label.Native
+ });
+
+ /*
+ RGraph.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+
+ */
+ RGraph.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz){
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ RGraph.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+ */
+ RGraph.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz){
+ this.viz = viz;
+ },
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller){
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize();
+ var labelPos = {
+ x: Math.round(pos.x * sx + ox + radius.width / 2),
+ y: Math.round(pos.y * sy + oy + radius.height / 2)
+ };
+
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ Class: RGraph.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ RGraph.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ RGraph.Plot.NodeTypes = new Class({
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false)
+ },
+ 'circle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.circle.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.circle.contains(npos, pos, dim);
+ }
+ },
+ 'ellipse': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
+ },
+ // TODO(nico): be more precise...
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ return this.nodeHelper.ellipse.contains(npos, pos, width, height);
+ }
+ },
+ 'square': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.square.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.square.contains(npos, pos, dim);
+ }
+ },
+ 'rectangle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
+ },
+ 'contains': function(node, pos){
+ var npos = node.pos.getc(true),
+ width = node.getData('width'),
+ height = node.getData('height');
+ return this.nodeHelper.rectangle.contains(npos, pos, width, height);
+ }
+ },
+ 'triangle': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.triangle.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos) {
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.triangle.contains(npos, pos, dim);
+ }
+ },
+ 'star': {
+ 'render': function(node, canvas){
+ var pos = node.pos.getc(true),
+ dim = node.getData('dim');
+ this.nodeHelper.star.render('fill', pos, dim, canvas);
+ },
+ 'contains': function(node, pos) {
+ var npos = node.pos.getc(true),
+ dim = node.getData('dim');
+ return this.nodeHelper.star.contains(npos, pos, dim);
+ }
+ }
+ });
+
+ /*
+ Class: RGraph.Plot.EdgeTypes
+
+ This class contains a list of <Graph.Adjacence> built-in types.
+ Edge types implemented are 'none', 'line' and 'arrow'.
+
+ You can add your custom edge types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ RGraph.Plot.EdgeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(adj, canvas) {
+ //print your custom edge to canvas
+ },
+ //optional
+ 'contains': function(adj, pos) {
+ //return true if pos is inside the arc or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ RGraph.Plot.EdgeTypes = new Class({
+ 'none': $.empty,
+ 'line': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ this.edgeHelper.line.render(from, to, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
+ }
+ },
+ 'arrow': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ dim = adj.getData('dim'),
+ direction = adj.data.$direction,
+ inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
+ this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true);
+ return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
+ }
+ }
+ });
+
+})($jit.RGraph);
+
+
+/*
+ * File: Hypertree.js
+ *
+*/
+
+/*
+ Complex
+
+ A multi-purpose Complex Class with common methods. Extended for the Hypertree.
+
+*/
+/*
+ moebiusTransformation
+
+ Calculates a moebius transformation for this point / complex.
+ For more information go to:
+ http://en.wikipedia.org/wiki/Moebius_transformation.
+
+ Parameters:
+
+ c - An initialized Complex instance representing a translation Vector.
+*/
+
+Complex.prototype.moebiusTransformation = function(c) {
+ var num = this.add(c);
+ var den = c.$conjugate().$prod(this);
+ den.x++;
+ return num.$div(den);
+};
+
+/*
+ moebiusTransformation
+
+ Calculates a moebius transformation for the hyperbolic tree.
+
+ <http://en.wikipedia.org/wiki/Moebius_transformation>
+
+ Parameters:
+
+ graph - A <Graph> instance.
+ pos - A <Complex>.
+ prop - A property array.
+ theta - Rotation angle.
+ startPos - _optional_ start position.
+*/
+Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
+ this.eachNode(graph, function(elem) {
+ for ( var i = 0; i < prop.length; i++) {
+ var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
+ elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
+ }
+ }, flags);
+};
+
+/*
+ Class: Hypertree
+
+ A Hyperbolic Tree/Graph visualization.
+
+ Inspired by:
+
+ A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
+ <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
+
+ Note:
+
+ This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the Hypertree described in the paper.
+
+ Implements:
+
+ All <Loader> methods
+
+ Constructor Options:
+
+ Inherits options from
+
+ - <Options.Canvas>
+ - <Options.Controller>
+ - <Options.Node>
+ - <Options.Edge>
+ - <Options.Label>
+ - <Options.Events>
+ - <Options.Tips>
+ - <Options.NodeStyles>
+ - <Options.Navigation>
+
+ Additionally, there are other parameters and some default values changed
+
+ radius - (string|number) Default's *auto*. The radius of the disc to plot the <Hypertree> in. 'auto' will take the smaller value from the width and height canvas dimensions. You can also set this to a custom value, for example *250*.
+ offset - (number) Default's *0*. A number in the range [0, 1) that will be substracted to each node position to make a more compact <Hypertree>. This will avoid placing nodes too far from each other when a there's a selected node.
+ fps - Described in <Options.Fx>. It's default value has been changed to *35*.
+ duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
+ Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
+
+ Instance Properties:
+
+ canvas - Access a <Canvas> instance.
+ graph - Access a <Graph> instance.
+ op - Access a <Hypertree.Op> instance.
+ fx - Access a <Hypertree.Plot> instance.
+ labels - Access a <Hypertree.Label> interface implementation.
+
+*/
+
+$jit.Hypertree = new Class( {
+
+ Implements: [ Loader, Extras, Layouts.Radial ],
+
+ initialize: function(controller) {
+ var $Hypertree = $jit.Hypertree;
+
+ var config = {
+ radius: "auto",
+ offset: 0,
+ Edge: {
+ type: 'hyperline'
+ },
+ duration: 1500,
+ fps: 35
+ };
+ this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
+ "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
+
+ var canvasConfig = this.config;
+ if(canvasConfig.useCanvas) {
+ this.canvas = canvasConfig.useCanvas;
+ this.config.labelContainer = this.canvas.id + '-label';
+ } else {
+ if(canvasConfig.background) {
+ canvasConfig.background = $.merge({
+ type: 'Circles'
+ }, canvasConfig.background);
+ }
+ this.canvas = new Canvas(this, canvasConfig);
+ this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ }
+
+ this.graphOptions = {
+ 'complex': false,
+ 'Node': {
+ 'selected': false,
+ 'exist': true,
+ 'drawn': true
+ }
+ };
+ this.graph = new Graph(this.graphOptions, this.config.Node,
+ this.config.Edge);
+ this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
+ this.fx = new $Hypertree.Plot(this, $Hypertree);
+ this.op = new $Hypertree.Op(this);
+ this.json = null;
+ this.root = null;
+ this.busy = false;
+ // initialize extras
+ this.initializeExtras();
+ },
+
+ /*
+
+ createLevelDistanceFunc
+
+ Returns the levelDistance function used for calculating a node distance
+ to its origin. This function returns a function that is computed
+ per level and not per node, such that all nodes with the same depth will have the
+ same distance to the origin. The resulting function gets the
+ parent node as parameter and returns a float.
+
+ */
+ createLevelDistanceFunc: function() {
+ // get max viz. length.
+ var r = this.getRadius();
+ // get max depth.
+ var depth = 0, max = Math.max, config = this.config;
+ this.graph.eachNode(function(node) {
+ depth = max(node._depth, depth);
+ }, "ignore");
+ depth++;
+ // node distance generator
+ var genDistFunc = function(a) {
+ return function(node) {
+ node.scale = r;
+ var d = node._depth + 1;
+ var acum = 0, pow = Math.pow;
+ while (d) {
+ acum += pow(a, d--);
+ }
+ return acum - config.offset;
+ };
+ };
+ // estimate better edge length.
+ for ( var i = 0.51; i <= 1; i += 0.01) {
+ var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
+ if (valSeries >= 2) { return genDistFunc(i - 0.01); }
+ }
+ return genDistFunc(0.75);
+ },
+
+ /*
+ Method: getRadius
+
+ Returns the current radius of the visualization. If *config.radius* is *auto* then it
+ calculates the radius by taking the smaller size of the <Canvas> widget.
+
+ See also:
+
+ <Canvas.getSize>
+
+ */
+ getRadius: function() {
+ var rad = this.config.radius;
+ if (rad !== "auto") { return rad; }
+ var s = this.canvas.getSize();
+ return Math.min(s.width, s.height) / 2;
+ },
+
+ /*
+ Method: refresh
+
+ Computes positions and plots the tree.
+
+ Parameters:
+
+ reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
+
+ */
+ refresh: function(reposition) {
+ if (reposition) {
+ this.reposition();
+ this.graph.eachNode(function(node) {
+ node.startPos.rho = node.pos.rho = node.endPos.rho;
+ node.startPos.theta = node.pos.theta = node.endPos.theta;
+ });
+ } else {
+ this.compute();
+ }
+ this.plot();
+ },
+
+ /*
+ reposition
+
+ Computes nodes' positions and restores the tree to its previous position.
+
+ For calculating nodes' positions the root must be placed on its origin. This method does this
+ and then attemps to restore the hypertree to its previous position.
+
+ */
+ reposition: function() {
+ this.compute('end');
+ var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
+ Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
+ 'end', "ignore");
+ this.graph.eachNode(function(node) {
+ if (node.ignore) {
+ node.endPos.rho = node.pos.rho;
+ node.endPos.theta = node.pos.theta;
+ }
+ });
+ },
+
+ /*
+ Method: plot
+
+ Plots the <Hypertree>. This is a shortcut to *fx.plot*.
+
+ */
+ plot: function() {
+ this.fx.plot();
+ },
+
+ /*
+ Method: onClick
+
+ Animates the <Hypertree> to center the node specified by *id*.
+
+ Parameters:
+
+ id - A <Graph.Node> id.
+ opt - (optional|object) An object containing some extra properties described below
+ hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
+
+ Example:
+
+ (start code js)
+ ht.onClick('someid');
+ //or also...
+ ht.onClick('someid', {
+ hideLabels: false
+ });
+ (end code)
+
+ */
+ onClick: function(id, opt) {
+ var pos = this.graph.getNode(id).pos.getc(true);
+ this.move(pos, opt);
+ },
+
+ /*
+ Method: move
+
+ Translates the tree to the given position.
+
+ Parameters:
+
+ pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
+ opt - This object has been defined in <Hypertree.onClick>
+
+ Example:
+
+ (start code js)
+ ht.move({ x: 0, y: 0.7 }, {
+ hideLabels: false
+ });
+ (end code)
+
+ */
+ move: function(pos, opt) {
+ var versor = $C(pos.x, pos.y);
+ if (this.busy === false && versor.norm() < 1) {
+ this.busy = true;
+ var root = this.graph.getClosestNodeToPos(versor), that = this;
+ this.graph.computeLevels(root.id, 0);
+ this.controller.onBeforeCompute(root);
+ opt = $.merge( {
+ onComplete: $.empty
+ }, opt || {});
+ this.fx.animate($.merge( {
+ modes: [ 'moebius' ],
+ hideLabels: true
+ }, opt, {
+ onComplete: function() {
+ that.busy = false;
+ opt.onComplete();
+ }
+ }), versor);
+ }
+ }
+});
+
+$jit.Hypertree.$extend = true;
+
+(function(Hypertree) {
+
+ /*
+ Class: Hypertree.Op
+
+ Custom extension of <Graph.Op>.
+
+ Extends:
+
+ All <Graph.Op> methods
+
+ See also:
+
+ <Graph.Op>
+
+ */
+ Hypertree.Op = new Class( {
+
+ Implements: Graph.Op
+
+ });
+
+ /*
+ Class: Hypertree.Plot
+
+ Custom extension of <Graph.Plot>.
+
+ Extends:
+
+ All <Graph.Plot> methods
+
+ See also:
+
+ <Graph.Plot>
+
+ */
+ Hypertree.Plot = new Class( {
+
+ Implements: Graph.Plot
+
+ });
+
+ /*
+ Object: Hypertree.Label
+
+ Custom extension of <Graph.Label>.
+ Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
+
+ Extends:
+
+ All <Graph.Label> methods and subclasses.
+
+ See also:
+
+ <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
+
+ */
+ Hypertree.Label = {};
+
+ /*
+ Hypertree.Label.Native
+
+ Custom extension of <Graph.Label.Native>.
+
+ Extends:
+
+ All <Graph.Label.Native> methods
+
+ See also:
+
+ <Graph.Label.Native>
+
+ */
+ Hypertree.Label.Native = new Class( {
+ Implements: Graph.Label.Native,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+
+ renderLabel: function(canvas, node, controller) {
+ var ctx = canvas.getCtx();
+ var coord = node.pos.getc(true);
+ var s = this.viz.getRadius();
+ ctx.fillText(node.name, coord.x * s, coord.y * s);
+ }
+ });
+
+ /*
+ Hypertree.Label.SVG
+
+ Custom extension of <Graph.Label.SVG>.
+
+ Extends:
+
+ All <Graph.Label.SVG> methods
+
+ See also:
+
+ <Graph.Label.SVG>
+
+ */
+ Hypertree.Label.SVG = new Class( {
+ Implements: Graph.Label.SVG,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize(),
+ r = this.viz.getRadius();
+ var labelPos = {
+ x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
+ y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
+ };
+ tag.setAttribute('x', labelPos.x);
+ tag.setAttribute('y', labelPos.y);
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ Hypertree.Label.HTML
+
+ Custom extension of <Graph.Label.HTML>.
+
+ Extends:
+
+ All <Graph.Label.HTML> methods.
+
+ See also:
+
+ <Graph.Label.HTML>
+
+ */
+ Hypertree.Label.HTML = new Class( {
+ Implements: Graph.Label.HTML,
+
+ initialize: function(viz) {
+ this.viz = viz;
+ },
+ /*
+ placeLabel
+
+ Overrides abstract method placeLabel in <Graph.Plot>.
+
+ Parameters:
+
+ tag - A DOM label element.
+ node - A <Graph.Node>.
+ controller - A configuration/controller object passed to the visualization.
+
+ */
+ placeLabel: function(tag, node, controller) {
+ var pos = node.pos.getc(true),
+ canvas = this.viz.canvas,
+ ox = canvas.translateOffsetX,
+ oy = canvas.translateOffsetY,
+ sx = canvas.scaleOffsetX,
+ sy = canvas.scaleOffsetY,
+ radius = canvas.getSize(),
+ r = this.viz.getRadius();
+ var labelPos = {
+ x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
+ y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
+ };
+ var style = tag.style;
+ style.left = labelPos.x + 'px';
+ style.top = labelPos.y + 'px';
+ style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
+
+ controller.onPlaceLabel(tag, node);
+ }
+ });
+
+ /*
+ Class: Hypertree.Plot.NodeTypes
+
+ This class contains a list of <Graph.Node> built-in types.
+ Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
+
+ You can add your custom node types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ Hypertree.Plot.NodeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(node, canvas) {
+ //print your custom node to canvas
+ },
+ //optional
+ 'contains': function(node, pos) {
+ //return true if pos is inside the node or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ Hypertree.Plot.NodeTypes = new Class({
+ 'none': {
+ 'render': $.empty,
+ 'contains': $.lambda(false)
+ },
+ 'circle': {
+ 'render': function(node, canvas) {
+ var nconfig = this.node,
+ dim = node.getData('dim'),
+ p = node.pos.getc();
+ dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
+ p.$scale(node.scale);
+ if (dim > 0.2) {
+ this.nodeHelper.circle.render('fill', p, dim, canvas);
+ }
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.circle.contains(npos, pos, dim);
+ }
+ },
+ 'ellipse': {
+ 'render': function(node, canvas) {
+ var pos = node.pos.getc().$scale(node.scale),
+ width = node.getData('width'),
+ height = node.getData('height');
+ this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
+ },
+ 'contains': function(node, pos) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.circle.contains(npos, pos, width, height);
+ }
+ },
+ 'square': {
+ 'render': function(node, canvas) {
+ var nconfig = this.node,
+ dim = node.getData('dim'),
+ p = node.pos.getc();
+ dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
+ p.$scale(node.scale);
+ if (dim > 0.2) {
+ this.nodeHelper.square.render('fill', p, dim, canvas);
+ }
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.square.contains(npos, pos, dim);
+ }
+ },
+ 'rectangle': {
+ 'render': function(node, canvas) {
+ var nconfig = this.node,
+ width = node.getData('width'),
+ height = node.getData('height'),
+ pos = node.pos.getc();
+ width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
+ height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
+ pos.$scale(node.scale);
+ if (width > 0.2 && height > 0.2) {
+ this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
+ }
+ },
+ 'contains': function(node, pos) {
+ var width = node.getData('width'),
+ height = node.getData('height'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.square.contains(npos, pos, width, height);
+ }
+ },
+ 'triangle': {
+ 'render': function(node, canvas) {
+ var nconfig = this.node,
+ dim = node.getData('dim'),
+ p = node.pos.getc();
+ dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
+ p.$scale(node.scale);
+ if (dim > 0.2) {
+ this.nodeHelper.triangle.render('fill', p, dim, canvas);
+ }
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.triangle.contains(npos, pos, dim);
+ }
+ },
+ 'star': {
+ 'render': function(node, canvas) {
+ var nconfig = this.node,
+ dim = node.getData('dim'),
+ p = node.pos.getc();
+ dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
+ p.$scale(node.scale);
+ if (dim > 0.2) {
+ this.nodeHelper.star.render('fill', p, dim, canvas);
+ }
+ },
+ 'contains': function(node, pos) {
+ var dim = node.getData('dim'),
+ npos = node.pos.getc().$scale(node.scale);
+ return this.nodeHelper.star.contains(npos, pos, dim);
+ }
+ }
+ });
+
+ /*
+ Class: Hypertree.Plot.EdgeTypes
+
+ This class contains a list of <Graph.Adjacence> built-in types.
+ Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
+
+ You can add your custom edge types, customizing your visualization to the extreme.
+
+ Example:
+
+ (start code js)
+ Hypertree.Plot.EdgeTypes.implement({
+ 'mySpecialType': {
+ 'render': function(adj, canvas) {
+ //print your custom edge to canvas
+ },
+ //optional
+ 'contains': function(adj, pos) {
+ //return true if pos is inside the arc or false otherwise
+ }
+ }
+ });
+ (end code)
+
+ */
+ Hypertree.Plot.EdgeTypes = new Class({
+ 'none': $.empty,
+ 'line': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ r = adj.nodeFrom.scale;
+ this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ r = adj.nodeFrom.scale;
+ this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
+ }
+ },
+ 'arrow': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ r = adj.nodeFrom.scale,
+ dim = adj.getData('dim'),
+ direction = adj.data.$direction,
+ inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
+ this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
+ },
+ 'contains': function(adj, pos) {
+ var from = adj.nodeFrom.pos.getc(true),
+ to = adj.nodeTo.pos.getc(true),
+ r = adj.nodeFrom.scale;
+ this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
+ }
+ },
+ 'hyperline': {
+ 'render': function(adj, canvas) {
+ var from = adj.nodeFrom.pos.getc(),
+ to = adj.nodeTo.pos.getc(),
+ dim = this.viz.getRadius();
+ this.edgeHelper.hyperline.render(from, to, dim, canvas);
+ },
+ 'contains': $.lambda(false)
+ }
+ });
+
+})($jit.Hypertree);
+
+
+
+
+ })(); \ No newline at end of file
diff --git a/js/jscalendar/calendar-blue.css b/js/jscalendar/calendar-blue.css
new file mode 100644
index 0000000..ca33cde
--- /dev/null
+++ b/js/jscalendar/calendar-blue.css
@@ -0,0 +1,232 @@
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #556;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #eef;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+}
+
+.calendar .nav {
+ background: #778 url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ background: #fff;
+ color: #000;
+ padding: 2px;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+ background: #778;
+ color: #fff;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: #bdf;
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #556;
+ padding: 2px;
+ text-align: center;
+ color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #a66;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background-color: #aaf;
+ color: #000;
+ border: 1px solid #04f;
+ padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background-color: #77c;
+ padding: 2px 0px 0px 2px;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ color: #456;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #fbb;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #bdf;
+}
+
+.calendar tbody .rowhilite td {
+ background: #def;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #eef;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: #def;
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ background: #cde;
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+ font-weight: bold;
+ border: 1px solid #000;
+ padding: 1px 3px 1px 1px;
+ background: #fff;
+ color: #000;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #a66;
+}
+
+.calendar tbody td.today { /* Cell showing selected date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #556;
+ color: #fff;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #fff;
+ color: #445;
+ border-top: 1px solid #556;
+ padding: 1px;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #aaf;
+ border: 1px solid #04f;
+ color: #000;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #77c;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border: 1px solid #655;
+ background: #def;
+ color: #000;
+ font-size: 90%;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .hilite {
+ background: #acf;
+}
+
+.calendar .combo .active {
+ border-top: 1px solid #46a;
+ border-bottom: 1px solid #46a;
+ background: #eef;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #f4f0e8;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #667;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-blue2.css b/js/jscalendar/calendar-blue2.css
new file mode 100644
index 0000000..47128ec
--- /dev/null
+++ b/js/jscalendar/calendar-blue2.css
@@ -0,0 +1,236 @@
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #206A9B;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #F1F8FC;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+}
+
+.calendar .nav {
+ background: #007ED1 url(menuarrow2.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ background: #000;
+ color: #fff;
+ padding: 2px;
+}
+
+.calendar thead tr { /* Row <TR> containing navigation buttons */
+ background: #007ED1;
+ color: #fff;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: #C7E1F3;
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #206A9B;
+ padding: 2px;
+ text-align: center;
+ color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #a66;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background-color: #34ABFA;
+ color: #000;
+ border: 1px solid #016DC5;
+ padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background-color: #006AA9;
+ border: 1px solid #008AFF;
+ padding: 2px 0px 0px 2px;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ color: #456;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #fbb;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #C7E1F3;
+}
+
+.calendar tbody .rowhilite td {
+ background: #def;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #F1F8FC;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: #def;
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #8FC4E8;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ background: #cde;
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+ font-weight: bold;
+ border: 1px solid #000;
+ padding: 1px 3px 1px 1px;
+ background: #fff;
+ color: #000;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #a66;
+}
+
+.calendar tbody td.today { /* Cell showing selected date */
+ font-weight: bold;
+ color: #D50000;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #206A9B;
+ color: #fff;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #000;
+ color: #fff;
+ border-top: 1px solid #206A9B;
+ padding: 1px;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #B8DAF0;
+ border: 1px solid #178AEB;
+ color: #000;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #006AA9;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border: 1px solid #655;
+ background: #def;
+ color: #000;
+ font-size: 90%;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .hilite {
+ background: #34ABFA;
+ border-top: 1px solid #46a;
+ border-bottom: 1px solid #46a;
+ font-weight: bold;
+}
+
+.calendar .combo .active {
+ border-top: 1px solid #46a;
+ border-bottom: 1px solid #46a;
+ background: #F1F8FC;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #E3F0F9;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #F1F8FC;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #267DB7;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: red;
+ background-color: #000;
+ color: #A5FF00;
+}
diff --git a/js/jscalendar/calendar-brown.css b/js/jscalendar/calendar-brown.css
new file mode 100644
index 0000000..c42da5e
--- /dev/null
+++ b/js/jscalendar/calendar-brown.css
@@ -0,0 +1,225 @@
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #655;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #ffd;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+}
+
+.calendar .nav {
+ background: #edc url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ background: #654;
+ color: #fed;
+ padding: 2px;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+ background: #edc;
+ color: #000;
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #655;
+ padding: 2px;
+ text-align: center;
+ color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background-color: #faa;
+ color: #000;
+ border: 1px solid #f40;
+ padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background-color: #c77;
+ padding: 2px 0px 0px 2px;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: #fed;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #fbb;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #fed;
+}
+
+.calendar tbody .rowhilite td {
+ background: #ddf;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #efe;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: #ffe;
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ background: #ddc;
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+ font-weight: bold;
+ border: 1px solid #000;
+ padding: 1px 3px 1px 1px;
+ background: #fea;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { font-weight: bold; }
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #988;
+ color: #000;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ border-top: 1px solid #655;
+ background: #dcb;
+ color: #840;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #faa;
+ border: 1px solid #f40;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #c77;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border: 1px solid #655;
+ background: #ffe;
+ color: #000;
+ font-size: 90%;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .hilite {
+ background: #fc8;
+}
+
+.calendar .combo .active {
+ border-top: 1px solid #a64;
+ border-bottom: 1px solid #a64;
+ background: #fee;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #a88;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #fed;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #988;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #866;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-green.css b/js/jscalendar/calendar-green.css
new file mode 100644
index 0000000..2e1867a
--- /dev/null
+++ b/js/jscalendar/calendar-green.css
@@ -0,0 +1,229 @@
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #565;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #efe;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+ background: #676;
+ color: #fff;
+ font-size: 90%;
+}
+
+.calendar .nav {
+ background: #676 url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ padding: 2px;
+ background: #250;
+ color: #efa;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #565;
+ padding: 2px;
+ text-align: center;
+ color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #a66;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background-color: #afa;
+ color: #000;
+ border: 1px solid #084;
+ padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background-color: #7c7;
+ padding: 2px 0px 0px 2px;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: #dfb;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ color: #564;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #fbb;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #8a8;
+ background: #dfb;
+}
+
+.calendar tbody .rowhilite td {
+ background: #dfd;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #efe;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: #efd;
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ background: #dec;
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+ font-weight: bold;
+ border: 1px solid #000;
+ padding: 1px 3px 1px 1px;
+ background: #f8fff8;
+ color: #000;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #a66;
+}
+
+.calendar tbody td.today { font-weight: bold; color: #0a0; }
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #565;
+ color: #fff;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ padding: 2px;
+ background: #250;
+ color: #efa;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #afa;
+ border: 1px solid #084;
+ color: #000;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #7c7;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border: 1px solid #565;
+ background: #efd;
+ color: #000;
+ font-size: 90%;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .hilite {
+ background: #af8;
+}
+
+.calendar .combo .active {
+ border-top: 1px solid #6a4;
+ border-bottom: 1px solid #6a4;
+ background: #efe;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #8a8;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #dfb;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #898;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #686;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-setup.js b/js/jscalendar/calendar-setup.js
new file mode 100644
index 0000000..2519335
--- /dev/null
+++ b/js/jscalendar/calendar-setup.js
@@ -0,0 +1,203 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+
+// $Id$
+
+/**
+ * This function "patches" an input field (or other element) to use a calendar
+ * widget for date selection.
+ *
+ * The "params" is a single object that can have the following properties:
+ *
+ * prop. name | description
+ * -------------------------------------------------------------------------------------------------
+ * inputField | the ID of an input field to store the date
+ * displayArea | the ID of a DIV or other element to show the date
+ * button | ID of a button or other element that will trigger the calendar
+ * eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
+ * ifFormat | date format that will be stored in the input field
+ * daFormat | the date format that will be used to display the date in displayArea
+ * singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
+ * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc.
+ * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation
+ * range | array with 2 elements. Default: [1900, 2999] -- the range of years available
+ * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
+ * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
+ * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
+ * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
+ * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
+ * onClose | function that gets called when the calendar is closed. [default]
+ * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
+ * date | the date that the calendar will be initially displayed to
+ * showsTime | default: false; if true the calendar will include a time selector
+ * timeFormat | the time format; can be "12" or "24", default is "12"
+ * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
+ * step | configures the step of the years in drop-down boxes; default: 2
+ * position | configures the calendar absolute position; default: null
+ * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible
+ * showOthers | if "true" (but default: "false") it will show days from other months too
+ *
+ * None of them is required, they all have default values. However, if you
+ * pass none of "inputField", "displayArea" or "button" you'll get a warning
+ * saying "nothing to setup".
+ */
+Calendar.setup = function (params) {
+ function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
+
+ param_default("inputField", null);
+ param_default("displayArea", null);
+ param_default("button", null);
+ param_default("eventName", "click");
+ param_default("ifFormat", "%Y/%m/%d");
+ param_default("daFormat", "%Y/%m/%d");
+ param_default("singleClick", true);
+ param_default("disableFunc", null);
+ param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
+ param_default("dateTooltipFunc", null);
+ param_default("dateText", null);
+ param_default("firstDay", null);
+ param_default("align", "Br");
+ param_default("range", [1900, 2999]);
+ param_default("weekNumbers", true);
+ param_default("flat", null);
+ param_default("flatCallback", null);
+ param_default("onSelect", null);
+ param_default("onClose", null);
+ param_default("onUpdate", null);
+ param_default("date", null);
+ param_default("showsTime", false);
+ param_default("timeFormat", "24");
+ param_default("electric", true);
+ param_default("step", 2);
+ param_default("position", null);
+ param_default("cache", false);
+ param_default("showOthers", false);
+ param_default("multiple", null);
+
+ var tmp = ["inputField", "displayArea", "button"];
+ for (var i in tmp) {
+ if (typeof params[tmp[i]] == "string") {
+ params[tmp[i]] = document.getElementById(params[tmp[i]]);
+ }
+ }
+ if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
+ alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
+ return false;
+ }
+
+ function onSelect(cal) {
+ var p = cal.params;
+ var update = (cal.dateClicked || p.electric);
+ if (update && p.inputField) {
+ p.inputField.value = cal.date.print(p.ifFormat);
+ if (typeof p.inputField.onchange == "function")
+ p.inputField.onchange();
+ }
+ if (update && p.displayArea)
+ p.displayArea.innerHTML = cal.date.print(p.daFormat);
+ if (update && typeof p.onUpdate == "function")
+ p.onUpdate(cal);
+ if (update && p.flat) {
+ if (typeof p.flatCallback == "function")
+ p.flatCallback(cal);
+ }
+ if (update && p.singleClick && cal.dateClicked)
+ cal.callCloseHandler();
+ };
+
+ if (params.flat != null) {
+ if (typeof params.flat == "string")
+ params.flat = document.getElementById(params.flat);
+ if (!params.flat) {
+ alert("Calendar.setup:\n Flat specified but can't find parent.");
+ return false;
+ }
+ var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
+ cal.setDateToolTipHandler(params.dateTooltipFunc);
+ cal.showsOtherMonths = params.showOthers;
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.params = params;
+ cal.weekNumbers = params.weekNumbers;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ if (params.ifFormat) {
+ cal.setDateFormat(params.ifFormat);
+ }
+ if (params.inputField && typeof params.inputField.value == "string") {
+ cal.parseDate(params.inputField.value);
+ }
+ cal.create(params.flat);
+ cal.show();
+ return false;
+ }
+
+ var triggerEl = params.button || params.displayArea || params.inputField;
+ triggerEl["on" + params.eventName] = function() {
+ var dateEl = params.inputField || params.displayArea;
+ var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
+ var mustCreate = false;
+ var cal = window.calendar;
+ if (dateEl)
+ params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
+ if (!(cal && params.cache)) {
+ window.calendar = cal = new Calendar(params.firstDay,
+ params.date,
+ params.onSelect || onSelect,
+ params.onClose || function(cal) { cal.hide(); });
+ cal.setDateToolTipHandler(params.dateTooltipFunc);
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.weekNumbers = params.weekNumbers;
+ mustCreate = true;
+ } else {
+ if (params.date)
+ cal.setDate(params.date);
+ cal.hide();
+ }
+ if (params.multiple) {
+ cal.multiple = {};
+ for (var i = params.multiple.length; --i >= 0;) {
+ var d = params.multiple[i];
+ var ds = d.print("%Y%m%d");
+ cal.multiple[ds] = d;
+ }
+ }
+ cal.showsOtherMonths = params.showOthers;
+ cal.yearStep = params.step;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ cal.refresh();
+ if (!params.position)
+ cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
+ else
+ cal.showAt(params.position[0], params.position[1]);
+ return false;
+ };
+
+ return cal;
+};
diff --git a/js/jscalendar/calendar-setup_stripped.js b/js/jscalendar/calendar-setup_stripped.js
new file mode 100644
index 0000000..5285df9
--- /dev/null
+++ b/js/jscalendar/calendar-setup_stripped.js
@@ -0,0 +1,22 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+ Calendar.setup=function(params){function param_default(pname,def){if(typeof params[pname]=="undefined"){params[pname]=def;}};param_default("inputField",null);param_default("displayArea",null);param_default("button",null);param_default("eventName","click");param_default("ifFormat","%Y-%m-%d");param_default("daFormat","%Y-%m-%d");param_default("singleClick",true);param_default("disableFunc",null);param_default("dateStatusFunc",params["disableFunc"]);param_default("dateText",null);param_default("firstDay",null);param_default("align","Br");param_default("range",[1900,2999]);param_default("weekNumbers",true);param_default("flat",null);param_default("flatCallback",null);param_default("onSelect",null);param_default("onClose",null);param_default("onUpdate",null);param_default("date",null);param_default("showsTime",false);param_default("timeFormat","24");param_default("electric",true);param_default("step",2);param_default("position",null);param_default("cache",false);param_default("showOthers",false);param_default("multiple",null);var tmp=["inputField","displayArea","button"];for(var i in tmp){if(typeof params[tmp[i]]=="string"){params[tmp[i]]=document.getElementById(params[tmp[i]]);}}if(!(params.flat||params.multiple||params.inputField||params.displayArea||params.button)){alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");return false;}function onSelect(cal){var p=cal.params;var update=(cal.dateClicked||p.electric);if(update&&p.inputField){p.inputField.value=cal.date.print(p.ifFormat);if(typeof p.inputField.onchange=="function")p.inputField.onchange();}if(update&&p.displayArea)p.displayArea.innerHTML=cal.date.print(p.daFormat);if(update&&typeof p.onUpdate=="function")p.onUpdate(cal);if(update&&p.flat){if(typeof p.flatCallback=="function")p.flatCallback(cal);}if(update&&p.singleClick&&cal.dateClicked)cal.callCloseHandler();};if(params.flat!=null){if(typeof params.flat=="string")params.flat=document.getElementById(params.flat);if(!params.flat){alert("Calendar.setup:\n Flat specified but can't find parent.");return false;}var cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect);cal.showsOtherMonths=params.showOthers;cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.params=params;cal.weekNumbers=params.weekNumbers;cal.setRange(params.range[0],params.range[1]);cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;if(params.ifFormat){cal.setDateFormat(params.ifFormat);}if(params.inputField&&typeof params.inputField.value=="string"){cal.parseDate(params.inputField.value);}cal.create(params.flat);cal.show();return false;}var triggerEl=params.button||params.displayArea||params.inputField;triggerEl["on"+params.eventName]=function(){var dateEl=params.inputField||params.displayArea;var dateFmt=params.inputField?params.ifFormat:params.daFormat;var mustCreate=false;var cal=window.calendar;if(dateEl)params.date=Date.parseDate(dateEl.value||dateEl.innerHTML,dateFmt);if(!(cal&&params.cache)){window.calendar=cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect,params.onClose||function(cal){cal.hide();});cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.weekNumbers=params.weekNumbers;mustCreate=true;}else{if(params.date)cal.setDate(params.date);cal.hide();}if(params.multiple){cal.multiple={};for(var i=params.multiple.length;--i>=0;){var d=params.multiple[i];var ds=d.print("%Y%m%d");cal.multiple[ds]=d;}}cal.showsOtherMonths=params.showOthers;cal.yearStep=params.step;cal.setRange(params.range[0],params.range[1]);cal.params=params;cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;cal.setDateFormat(dateFmt);if(mustCreate)cal.create();cal.refresh();if(!params.position)cal.showAtElement(params.button||params.displayArea||params.inputField,params.align);else cal.showAt(params.position[0],params.position[1]);return false;};return cal;};
+ Calendar._FD = 0; \ No newline at end of file
diff --git a/js/jscalendar/calendar-system.css b/js/jscalendar/calendar-system.css
new file mode 100644
index 0000000..9f18b95
--- /dev/null
+++ b/js/jscalendar/calendar-system.css
@@ -0,0 +1,225 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border: 1px solid;
+ border-color: #fff #000 #000 #fff;
+ cursor: default;
+ background: Window;
+ color: WindowText;
+}
+
+.calendar table {
+ border: 1px solid #ccc;
+ background: Window;
+ color: WindowText;
+ margin:auto;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ border: 1px solid;
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+ background: ButtonFace;
+ display:table-cell;
+ cursor:pointer;
+}
+
+.calendar .nav {
+ background: ButtonFace url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+/* background: ActiveCaption;
+ color: CaptionText;
+*/
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid ButtonShadow;
+ text-align: center;
+ background: ButtonFace;
+ color: ButtonText;
+ cursor:pointer;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #999;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+ cursor:pointer;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid ButtonShadow;
+ background: ButtonFace;
+ color: ButtonText;
+}
+
+.calendar tbody .rowhilite td {
+/* think it is a bit annoying
+ background: Highlight;
+ color: HighlightText;
+*/
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+ background: ButtonFace;
+ color: ButtonText;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #666;
+ background-color:#ddd;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ background-color: #78bf34;
+ color:#000;
+}
+
+.calendar tbody td.disabled { color: GrayText; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: ButtonFace;
+ padding: 1px;
+ border: 1px solid;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+ color: ButtonText;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #e4e0d8;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border: 1px solid;
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+ background: Menu;
+ color: MenuText;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ padding: 0px;
+ border: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: Highlight;
+ color: HighlightText;
+}
+
+.calendar td.time {
+ border-top: 1px solid ButtonShadow;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: ButtonFace;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: Menu;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-tas.css b/js/jscalendar/calendar-tas.css
new file mode 100644
index 0000000..c2f8721
--- /dev/null
+++ b/js/jscalendar/calendar-tas.css
@@ -0,0 +1,239 @@
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #655;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #ffd;
+ font-family: tahoma,verdana,sans-serif;
+ filter:
+progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF);
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+ color:#363636;
+}
+
+.calendar .nav {
+ background: #edc url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ background: #654;
+ color: #363636;
+ padding: 2px;
+ filter:
+progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#dddccc);
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+ /*background: #3B86A0;*/
+ color: #363636;
+ font-weight: bold;
+filter:
+progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#3b86a0);
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #655;
+ padding: 2px;
+ text-align: center;
+ color: #363636;
+ filter:
+progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF);
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background-color: #ffcc86;
+ color: #000;
+ border: 1px solid #b59345;
+ padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background-color: #c77;
+ padding: 2px 0px 0px 2px;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: #fed;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #fed;
+}
+
+.calendar tbody .rowhilite td {
+ background: #ddf;
+
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #efe;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: #ffe;
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ background: #ddc;
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+ font-weight: bold;
+ border: 1px solid #000;
+ padding: 1px 3px 1px 1px;
+ background: #fea;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { font-weight: bold; }
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #988;
+ color: #000;
+
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ border-top: 1px solid #655;
+ background: #dcb;
+ color: #363636;
+ font-weight: bold;
+ filter:
+progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#FFFFFF,EndColorStr=#DDDCCC);
+}
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #faa;
+ border: 1px solid #f40;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #c77;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border: 1px solid #655;
+ background: #ffe;
+ color: #000;
+ font-size: smaller;
+ z-index: 100;
+}
+
+.combo .label,
+.combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.combo .label-IEfix {
+ width: 4em;
+}
+
+.combo .hilite {
+ background: #fc8;
+}
+
+.combo .active {
+ border-top: 1px solid #a64;
+ border-bottom: 1px solid #a64;
+ background: #fee;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #a88;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #fed;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #988;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #866;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-win2k-1.css b/js/jscalendar/calendar-win2k-1.css
new file mode 100644
index 0000000..8c5d026
--- /dev/null
+++ b/js/jscalendar/calendar-win2k-1.css
@@ -0,0 +1,271 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4d0c8;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4d0c8;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #848078;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #f4f0e8;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #e4e0d8;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #c4c0b8;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #f4f0e8;
+}
+
+.calendar tbody .rowhilite td {
+ background: #e4e0d8;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #d4d0c8;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ padding: 2px 2px 0px 2px;
+ background: #e4e0d8;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #f4f0e8;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #848078;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #e4e0d8;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #e4e0d8;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ background: #c4c0b8;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: #048;
+ color: #fea;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #f4f0e8;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #766;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-win2k-2.css b/js/jscalendar/calendar-win2k-2.css
new file mode 100644
index 0000000..6f37b7d
--- /dev/null
+++ b/js/jscalendar/calendar-win2k-2.css
@@ -0,0 +1,271 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #f4e8f0;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #e4d8e0;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #c4b8c0;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #f4e8f0;
+}
+
+.calendar tbody .rowhilite td {
+ background: #e4d8e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #d4c8d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ padding: 2px 2px 0px 2px;
+ background: #e4d8e0;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #f4e8f0;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #e4d8e0;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #e4d8e0;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ background: #d4c8d0;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: #408;
+ color: #fea;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #f4f0e8;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #766;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-win2k-cold-1.css b/js/jscalendar/calendar-win2k-cold-1.css
new file mode 100644
index 0000000..fa5c093
--- /dev/null
+++ b/js/jscalendar/calendar-win2k-cold-1.css
@@ -0,0 +1,265 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #c8d0d4;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #c8d0d4;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #788084;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #e8f0f4;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #d8e0e4;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #b8c0c4;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #e8f4f0;
+}
+
+.calendar tbody .rowhilite td {
+ background: #d8e4e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #c8d4d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border: 1px solid;
+ border-color: #fff #000 #000 #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border: 1px solid;
+ border-color: #000 #fff #fff #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ padding: 2px 2px 0px 2px;
+ border: 1px solid;
+ border-color: #000 #fff #fff #000;
+ background: #d8e0e4;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #e8f0f4;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #788084;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #d8e0e4;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #d8e0e4;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ background: #c8d0d4;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: #048;
+ color: #aef;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #e8f0f4;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #667;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar-win2k-cold-2.css b/js/jscalendar/calendar-win2k-cold-2.css
new file mode 100644
index 0000000..8e930c8
--- /dev/null
+++ b/js/jscalendar/calendar-win2k-cold-2.css
@@ -0,0 +1,271 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #c8d4d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #c8d4d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #788480;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #e8f4f0;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #d8e4e0;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #b8c4c0;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #e8f4f0;
+}
+
+.calendar tbody .rowhilite td {
+ background: #d8e4e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #c8d4d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ padding: 2px 2px 0px 2px;
+ background: #d8e4e0;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #e8f4f0;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #788480;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #d8e4e0;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #d8e4e0;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ background: #c8d4d0;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: #048;
+ color: #aef;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #e8f0f4;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #667;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/js/jscalendar/calendar.js b/js/jscalendar/calendar.js
new file mode 100644
index 0000000..27f253a
--- /dev/null
+++ b/js/jscalendar/calendar.js
@@ -0,0 +1,1807 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id$
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.getDateToolTip = null;
+ this.getDateText = null;
+ this.timeout = null;
+ this.onSelected = onSelected || null;
+ this.onClose = onClose || null;
+ this.dragging = false;
+ this.hidden = false;
+ this.minYear = 1970;
+ this.maxYear = 2050;
+ this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+ this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+ this.isPopup = true;
+ this.weekNumbers = true;
+ this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc.
+ this.showsOtherMonths = false;
+ this.dateStr = dateStr;
+ this.ar_days = null;
+ this.showsTime = false;
+ this.time24 = true;
+ this.yearStep = 2;
+ this.hiliteToday = true;
+ this.multiple = null;
+ // HTML elements
+ this.table = null;
+ this.element = null;
+ this.tbody = null;
+ this.firstdayname = null;
+ // Combo boxes
+ this.monthsCombo = null;
+ this.yearsCombo = null;
+ this.hilitedMonth = null;
+ this.activeMonth = null;
+ this.hilitedYear = null;
+ this.activeYear = null;
+ // Information
+ this.dateClicked = false;
+
+ // one-time initializations
+ if (typeof Calendar._SDN == "undefined") {
+ // table of short day names
+ if (typeof Calendar._SDN_len == "undefined")
+ Calendar._SDN_len = 3;
+ var ar = new Array();
+ for (var i = 8; i > 0;) {
+ ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+ }
+ Calendar._SDN = ar;
+ // table of short month names
+ if (typeof Calendar._SMN_len == "undefined")
+ Calendar._SMN_len = 3;
+ ar = new Array();
+ for (var i = 12; i > 0;) {
+ ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+ }
+ Calendar._SMN = ar;
+ }
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+ !/opera/i.test(navigator.userAgent) );
+
+Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+// library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+ var SL = 0, ST = 0;
+ var is_div = /^div$/i.test(el.tagName);
+ if (is_div && el.scrollLeft)
+ SL = el.scrollLeft;
+ if (is_div && el.scrollTop)
+ ST = el.scrollTop;
+ var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+ if (el.offsetParent) {
+ var tmp = this.getAbsolutePos(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+ var related = evt.relatedTarget;
+ if (!related) {
+ var type = evt.type;
+ if (type == "mouseover") {
+ related = evt.fromElement;
+ } else if (type == "mouseout") {
+ related = evt.toElement;
+ }
+ }
+ while (related) {
+ if (related == el) {
+ return true;
+ }
+ related = related.parentNode;
+ }
+ return false;
+};
+
+Calendar.removeClass = function(el, className) {
+ if (!(el && el.className)) {
+ return;
+ }
+ var cls = el.className.split(" ");
+ var ar = new Array();
+ for (var i = cls.length; i > 0;) {
+ if (cls[--i] != className) {
+ ar[ar.length] = cls[i];
+ }
+ }
+ el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+ Calendar.removeClass(el, className);
+ el.className += " " + className;
+};
+
+// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
+Calendar.getElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
+ while (f.nodeType != 1 || /^div$/i.test(f.tagName))
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.getTargetElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.target;
+ while (f.nodeType != 1)
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.stopEvent = function(ev) {
+ ev || (ev = window.event);
+ if (Calendar.is_ie) {
+ ev.cancelBubble = true;
+ ev.returnValue = false;
+ } else {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+ if (el.attachEvent) { // IE
+ el.attachEvent("on" + evname, func);
+ } else if (el.addEventListener) { // Gecko / W3C
+ el.addEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = func;
+ }
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+ if (el.detachEvent) { // IE
+ el.detachEvent("on" + evname, func);
+ } else if (el.removeEventListener) { // Gecko / W3C
+ el.removeEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = null;
+ }
+};
+
+Calendar.createElement = function(type, parent) {
+ var el = null;
+ if (document.createElementNS) {
+ // use the XHTML namespace; IE won't normally get here unless
+ // _they_ "fix" the DOM2 implementation.
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ } else {
+ el = document.createElement(type);
+ }
+ if (typeof parent != "undefined") {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+ with (Calendar) {
+ addEvent(el, "mouseover", dayMouseOver);
+ addEvent(el, "mousedown", dayMouseDown);
+ addEvent(el, "mouseout", dayMouseOut);
+ if (is_ie) {
+ addEvent(el, "dblclick", dayMouseDblClick);
+ el.setAttribute("unselectable", true);
+ }
+ }
+};
+
+Calendar.findMonth = function(el) {
+ if (typeof el.month != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.month != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.findYear = function(el) {
+ if (typeof el.year != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.year != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.showMonthsCombo = function () {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var mc = cal.monthsCombo;
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ if (cal.activeMonth) {
+ Calendar.removeClass(cal.activeMonth, "active");
+ }
+ var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+ Calendar.addClass(mon, "active");
+ cal.activeMonth = mon;
+ var s = mc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var mcw = mc.offsetWidth;
+ if (typeof mcw == "undefined")
+ // Konqueror brain-dead techniques
+ mcw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var yc = cal.yearsCombo;
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ if (cal.activeYear) {
+ Calendar.removeClass(cal.activeYear, "active");
+ }
+ cal.activeYear = null;
+ var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+ var yr = yc.firstChild;
+ var show = false;
+ for (var i = 12; i > 0; --i) {
+ if (Y >= cal.minYear && Y <= cal.maxYear) {
+ yr.innerHTML = Y;
+ yr.year = Y;
+ yr.style.display = "block";
+ show = true;
+ } else {
+ yr.style.display = "none";
+ }
+ yr = yr.nextSibling;
+ Y += fwd ? cal.yearStep : -cal.yearStep;
+ }
+ if (show) {
+ var s = yc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var ycw = yc.offsetWidth;
+ if (typeof ycw == "undefined")
+ // Konqueror brain-dead techniques
+ ycw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+ }
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ if (cal.timeout) {
+ clearTimeout(cal.timeout);
+ }
+ var el = cal.activeDiv;
+ if (!el) {
+ return false;
+ }
+ var target = Calendar.getTargetElement(ev);
+ ev || (ev = window.event);
+ Calendar.removeClass(el, "active");
+ if (target == el || target.parentNode == el) {
+ Calendar.cellClick(el, ev);
+ }
+ var mon = Calendar.findMonth(target);
+ var date = null;
+ if (mon) {
+ date = new Date(cal.date);
+ if (mon.month != date.getMonth()) {
+ date.setMonth(mon.month);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ } else {
+ var year = Calendar.findYear(target);
+ if (year) {
+ date = new Date(cal.date);
+ if (year.year != date.getFullYear()) {
+ date.setFullYear(year.year);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ }
+ }
+ with (Calendar) {
+ removeEvent(document, "mouseup", tableMouseUp);
+ removeEvent(document, "mouseover", tableMouseOver);
+ removeEvent(document, "mousemove", tableMouseOver);
+ cal._hideCombos();
+ _C = null;
+ return stopEvent(ev);
+ }
+};
+
+Calendar.tableMouseOver = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return;
+ }
+ var el = cal.activeDiv;
+ var target = Calendar.getTargetElement(ev);
+ if (target == el || target.parentNode == el) {
+ Calendar.addClass(el, "hilite active");
+ Calendar.addClass(el.parentNode, "rowhilite");
+ } else {
+ if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+ Calendar.removeClass(el, "active");
+ Calendar.removeClass(el, "hilite");
+ Calendar.removeClass(el.parentNode, "rowhilite");
+ }
+ ev || (ev = window.event);
+ if (el.navtype == 50 && target != el) {
+ var pos = Calendar.getAbsolutePos(el);
+ var w = el.offsetWidth;
+ var x = ev.clientX;
+ var dx;
+ var decrease = true;
+ if (x > pos.x + w) {
+ dx = x - pos.x - w;
+ decrease = false;
+ } else
+ dx = pos.x - x;
+
+ if (dx < 0) dx = 0;
+ var range = el._range;
+ var current = el._current;
+ var count = Math.floor(dx / 10) % range.length;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ while (count-- > 0)
+ if (decrease) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+
+ cal.onUpdateTime();
+ }
+ var mon = Calendar.findMonth(target);
+ if (mon) {
+ if (mon.month != cal.date.getMonth()) {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ Calendar.addClass(mon, "hilite");
+ cal.hilitedMonth = mon;
+ } else if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ } else {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ var year = Calendar.findYear(target);
+ if (year) {
+ if (year.year != cal.date.getFullYear()) {
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ Calendar.addClass(year, "hilite");
+ cal.hilitedYear = year;
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+ if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+ return Calendar.stopEvent(ev);
+ }
+};
+
+Calendar.calDragIt = function (ev) {
+ var cal = Calendar._C;
+ if (!(cal && cal.dragging)) {
+ return false;
+ }
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posX = ev.pageX;
+ posY = ev.pageY;
+ }
+ cal.hideShowCovered();
+ var st = cal.element.style;
+ st.left = (posX - cal.xOffs) + "px";
+ st.top = (posY - cal.yOffs) + "px";
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ cal.dragging = false;
+ with (Calendar) {
+ removeEvent(document, "mousemove", calDragIt);
+ removeEvent(document, "mouseup", calDragEnd);
+ tableMouseUp(ev);
+ }
+ cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (el.disabled) {
+ return false;
+ }
+ var cal = el.calendar;
+ cal.activeDiv = el;
+ Calendar._C = cal;
+ if (el.navtype != 300) with (Calendar) {
+ if (el.navtype == 50) {
+ el._current = el.innerHTML;
+ addEvent(document, "mousemove", tableMouseOver);
+ } else
+ addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
+ addClass(el, "hilite active");
+ addEvent(document, "mouseup", tableMouseUp);
+ } else if (cal.isPopup) {
+ cal._dragStart(ev);
+ }
+ if (el.navtype == -1 || el.navtype == 1) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+ } else if (el.navtype == -2 || el.navtype == 2) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+ } else {
+ cal.timeout = null;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+ Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+ if (Calendar.is_ie) {
+ document.selection.empty();
+ }
+};
+
+Calendar.dayMouseOver = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+ return false;
+ }
+ if (el.ttip) {
+ if (el.ttip.substr(0, 1) == "_") {
+ el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+ }
+ el.calendar.tooltips.innerHTML = el.ttip;
+ }
+ if (el.navtype != 300) {
+ Calendar.addClass(el, "hilite");
+ if (el.caldate) {
+ Calendar.addClass(el.parentNode, "rowhilite");
+ var cal = el.calendar;
+ if (cal && cal.getDateToolTip) {
+ var d = el.caldate;
+ window.status = d;
+ el.title = cal.getDateToolTip(d, d.getFullYear(), d.getMonth(), d.getDate());
+ }
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+ with (Calendar) {
+ var el = getElement(ev);
+ if (isRelated(el, ev) || _C || el.disabled)
+ return false;
+ removeClass(el, "hilite");
+ if (el.caldate)
+ removeClass(el.parentNode, "rowhilite");
+ if (el.calendar)
+ el.calendar.tooltips.innerHTML = _TT["SEL_DATE"];
+ // return stopEvent(ev);
+ }
+};
+
+/**
+ * A generic "click" handler :) handles all types of buttons defined in this
+ * calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+ var cal = el.calendar;
+ var closing = false;
+ var newdate = false;
+ var date = null;
+ if (typeof el.navtype == "undefined") {
+ if (cal.currentDateEl) {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ }
+ cal.date.setDateOnly(el.caldate);
+ date = cal.date;
+ var other_month = !(cal.dateClicked = !el.otherMonth);
+ if (!other_month && !cal.currentDateEl && cal.multiple)
+ cal._toggleMultipleDate(new Date(date));
+ else
+ newdate = !el.disabled;
+ // a date was clicked
+ if (other_month)
+ cal._init(cal.firstDayOfWeek, date);
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = new Date(cal.date);
+ if (el.navtype == 0)
+ date.setDateOnly(new Date()); // TODAY
+ // unless "today" was clicked, we assume no date was clicked so
+ // the selected handler will know not to close the calenar when
+ // in single-click mode.
+ // cal.dateClicked = (el.navtype == 0);
+ cal.dateClicked = false;
+ var year = date.getFullYear();
+ var mon = date.getMonth();
+ function setMonth(m) {
+ var day = date.getDate();
+ var max = date.getMonthDays(m);
+ if (day > max) {
+ date.setDate(max);
+ }
+ date.setMonth(m);
+ };
+ switch (el.navtype) {
+ case 400:
+ Calendar.removeClass(el, "hilite");
+ var text = Calendar._TT["ABOUT"];
+ if (typeof text != "undefined") {
+ text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+ } else {
+ // FIXME: this should be removed as soon as lang files get updated!
+ text = "Help and about box text is not translated into this language.\n" +
+ "If you know this language and you feel generous please update\n" +
+ "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+ "and send it back to <mihai_bazon@yahoo.com> to get it into the distribution ;-)\n\n" +
+ "Thank you!\n" +
+ "http://dynarch.com/mishoo/calendar.epl\n";
+ }
+ alert(text);
+ return;
+ case -2:
+ if (year > cal.minYear) {
+ date.setFullYear(year - 1);
+ }
+ break;
+ case -1:
+ if (mon > 0) {
+ setMonth(mon - 1);
+ } else if (year-- > cal.minYear) {
+ date.setFullYear(year);
+ setMonth(11);
+ }
+ break;
+ case 1:
+ if (mon < 11) {
+ setMonth(mon + 1);
+ } else if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ setMonth(0);
+ }
+ break;
+ case 2:
+ if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ }
+ break;
+ case 100:
+ cal.setFirstDayOfWeek(el.fdow);
+ return;
+ case 50:
+ var range = el._range;
+ var current = el.innerHTML;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ if (ev && ev.shiftKey) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+ cal.onUpdateTime();
+ return;
+ case 0:
+ // TODAY will bring us here
+ if ((typeof cal.getDateStatus == "function") &&
+ cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ } else if (el.navtype == 0)
+ newdate = closing = true;
+ }
+ if (newdate) {
+ ev && cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ ev && cal.callCloseHandler();
+ }
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ * This function creates the calendar inside the given parent. If _par is
+ * null than it creates a popup calendar inside the BODY element. If _par is
+ * an element, be it BODY, then it creates a non-popup calendar (still
+ * hidden). Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+ var parent = null;
+ if (! _par) {
+ // default parent is the document body, in which case we create
+ // a popup calendar.
+ parent = document.getElementsByTagName("body")[0];
+ this.isPopup = true;
+ } else {
+ parent = _par;
+ this.isPopup = false;
+ }
+ this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+ var table = Calendar.createElement("table");
+ this.table = table;
+ table.cellSpacing = 0;
+ table.cellPadding = 0;
+ table.calendar = this;
+ Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+ var div = Calendar.createElement("div");
+ this.element = div;
+ div.className = "calendar";
+ if (this.isPopup) {
+ div.style.position = "absolute";
+ div.style.display = "none";
+ }
+ div.appendChild(table);
+
+ var thead = Calendar.createElement("thead", table);
+ var cell = null;
+ var row = null;
+
+ var cal = this;
+ var hh = function (text, cs, navtype) {
+ cell = Calendar.createElement("td", row);
+ cell.colSpan = cs;
+ cell.className = "button";
+ if (navtype != 0 && Math.abs(navtype) <= 2)
+ cell.className += " nav";
+ Calendar._add_evs(cell);
+ cell.calendar = cal;
+ cell.navtype = navtype;
+ cell.innerHTML = "<div unselectable='on'>" + text + "</div>";
+ return cell;
+ };
+
+ row = Calendar.createElement("tr", thead);
+ var title_length = 6;
+ (this.isPopup) && --title_length;
+ (this.weekNumbers) && ++title_length;
+
+ hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+ this.title = hh("", title_length, 300);
+ this.title.className = "title";
+ if (this.isPopup) {
+ this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ this.title.style.cursor = "move";
+ hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("&#x00ab;", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("&#x2039;", 1, -1);
+ this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+ this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+ this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+ this._nav_nm = hh("&#x203a;", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("&#x00bb;", 1, 2);
+ this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+ // day names
+ row = Calendar.createElement("tr", thead);
+ row.className = "daynames";
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.className = "name wn";
+ cell.innerHTML = Calendar._TT["WK"];
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ if (!i) {
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+ this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+ this._displayWeekdays();
+
+ var tbody = Calendar.createElement("tbody", table);
+ this.tbody = tbody;
+
+ for (i = 6; i > 0; --i) {
+ row = Calendar.createElement("tr", tbody);
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+
+ if (this.showsTime) {
+ row = Calendar.createElement("tr", tbody);
+ row.className = "time";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ cell.innerHTML = Calendar._TT["TIME"] || "&nbsp;";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = this.weekNumbers ? 4 : 3;
+
+ (function(){
+ function makeTimePart(className, init, range_start, range_end) {
+ var part = Calendar.createElement("span", cell);
+ part.className = className;
+ part.innerHTML = init;
+ part.calendar = cal;
+ part.ttip = Calendar._TT["TIME_PART"];
+ part.navtype = 50;
+ part._range = [];
+ if (typeof range_start != "number")
+ part._range = range_start;
+ else {
+ for (var i = range_start; i <= range_end; ++i) {
+ var txt;
+ if (i < 10 && range_end >= 10) txt = '0' + i;
+ else txt = '' + i;
+ part._range[part._range.length] = txt;
+ }
+ }
+ Calendar._add_evs(part);
+ return part;
+ };
+ var hrs = cal.date.getHours();
+ var mins = cal.date.getMinutes();
+ var t12 = !cal.time24;
+ var pm = (hrs > 12);
+ if (t12 && pm) hrs -= 12;
+ var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+ var span = Calendar.createElement("span", cell);
+ span.innerHTML = ":";
+ span.className = "colon";
+ var M = makeTimePart("minute", mins, 0, 59);
+ var AP = null;
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ if (t12)
+ AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+ else
+ cell.innerHTML = "&nbsp;";
+
+ cal.onSetTime = function() {
+ var pm, hrs = this.date.getHours(),
+ mins = this.date.getMinutes();
+ if (t12) {
+ pm = (hrs >= 12);
+ if (pm) hrs -= 12;
+ if (hrs == 0) hrs = 12;
+ AP.innerHTML = pm ? "pm" : "am";
+ }
+ H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.innerHTML, 10);
+ if (t12) {
+ if (/pm/i.test(AP.innerHTML) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.innerHTML) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.innerHTML, 10));
+ date.setFullYear(y);
+ date.setMonth(m);
+ date.setDate(d);
+ this.dateClicked = false;
+ this.callHandler();
+ };
+ })();
+ } else {
+ this.onSetTime = this.onUpdateTime = function() {};
+ }
+
+ var tfoot = Calendar.createElement("tfoot", table);
+
+ row = Calendar.createElement("tr", tfoot);
+ row.className = "footrow";
+
+ cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+ cell.className = "ttip";
+ if (this.isPopup) {
+ cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ cell.style.cursor = "move";
+ }
+ this.tooltips = cell;
+
+ div = Calendar.createElement("div", this.element);
+ this.monthsCombo = div;
+ div.className = "combo";
+ for (i = 0; i < Calendar._MN.length; ++i) {
+ var mn = Calendar.createElement("div");
+ mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+ mn.month = i;
+ mn.innerHTML = Calendar._SMN[i];
+ div.appendChild(mn);
+ }
+
+ div = Calendar.createElement("div", this.element);
+ this.yearsCombo = div;
+ div.className = "combo";
+ for (i = 12; i > 0; --i) {
+ var yr = Calendar.createElement("div");
+ yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+ div.appendChild(yr);
+ }
+
+ this._init(this.firstDayOfWeek, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ var cal = window._dynarch_popupCalendar;
+ if (!cal || cal.multiple)
+ return false;
+ (Calendar.is_ie) && (ev = window.event);
+ var act = (Calendar.is_ie || ev.type == "keypress"),
+ K = ev.keyCode;
+ if (ev.ctrlKey) {
+ switch (K) {
+ case 37: // KEY left
+ act && Calendar.cellClick(cal._nav_pm);
+ break;
+ case 38: // KEY up
+ act && Calendar.cellClick(cal._nav_py);
+ break;
+ case 39: // KEY right
+ act && Calendar.cellClick(cal._nav_nm);
+ break;
+ case 40: // KEY down
+ act && Calendar.cellClick(cal._nav_ny);
+ break;
+ default:
+ return false;
+ }
+ } else switch (K) {
+ case 32: // KEY space (now)
+ Calendar.cellClick(cal._nav_now);
+ break;
+ case 27: // KEY esc
+ act && cal.callCloseHandler();
+ break;
+ case 37: // KEY left
+ case 38: // KEY up
+ case 39: // KEY right
+ case 40: // KEY down
+ if (act) {
+ var prev, x, y, ne, el, step;
+ prev = K == 37 || K == 38;
+ step = (K == 37 || K == 39) ? 1 : 7;
+ function setVars() {
+ el = cal.currentDateEl;
+ var p = el.pos;
+ x = p & 15;
+ y = p >> 4;
+ ne = cal.ar_days[y][x];
+ };setVars();
+ function prevMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() - step);
+ cal.setDate(date);
+ };
+ function nextMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() + step);
+ cal.setDate(date);
+ };
+ while (1) {
+ switch (K) {
+ case 37: // KEY left
+ if (--x >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 6;
+ K = 38;
+ continue;
+ }
+ break;
+ case 38: // KEY up
+ if (--y >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ prevMonth();
+ setVars();
+ }
+ break;
+ case 39: // KEY right
+ if (++x < 7)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 0;
+ K = 40;
+ continue;
+ }
+ break;
+ case 40: // KEY down
+ if (++y < cal.ar_days.length)
+ ne = cal.ar_days[y][x];
+ else {
+ nextMonth();
+ setVars();
+ }
+ break;
+ }
+ break;
+ }
+ if (ne) {
+ if (!ne.disabled)
+ Calendar.cellClick(ne);
+ else if (prev)
+ prevMonth();
+ else
+ nextMonth();
+ }
+ }
+ break;
+ case 13: // KEY enter
+ if (act)
+ Calendar.cellClick(cal.currentDateEl, ev);
+ break;
+ default:
+ return false;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+/**
+ * (RE)Initializes the calendar to the given date and firstDayOfWeek
+ */
+Calendar.prototype._init = function (firstDayOfWeek, date) {
+ var today = new Date(),
+ TY = today.getFullYear(),
+ TM = today.getMonth(),
+ TD = today.getDate();
+ this.table.style.visibility = "hidden";
+ var year = date.getFullYear();
+ if (year < this.minYear) {
+ year = this.minYear;
+ date.setFullYear(year);
+ } else if (year > this.maxYear) {
+ year = this.maxYear;
+ date.setFullYear(year);
+ }
+ this.firstDayOfWeek = firstDayOfWeek;
+ this.date = new Date(date);
+ var month = date.getMonth();
+ var mday = date.getDate();
+ var no_days = date.getMonthDays();
+
+ // calendar voodoo for computing the first day that would actually be
+ // displayed in the calendar, even if it's from the previous month.
+ // WARNING: this is magic. ;-)
+ date.setDate(1);
+ var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
+ if (day1 < 0)
+ day1 += 7;
+ date.setDate(-day1);
+ date.setDate(date.getDate() + 1);
+
+ var row = this.tbody.firstChild;
+ var MN = Calendar._SMN[month];
+ var ar_days = this.ar_days = new Array();
+ var weekend = Calendar._TT["WEEKEND"];
+ var dates = this.multiple ? (this.datesCells = {}) : null;
+ for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.innerHTML = date.getWeekNumber();
+ cell = cell.nextSibling;
+ }
+ row.className = "daysrow";
+ var hasdays = false, iday, dpos = ar_days[i] = [];
+ for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
+ iday = date.getDate();
+ var wday = date.getDay();
+ cell.className = "day";
+ cell.pos = i << 4 | j;
+ dpos[j] = cell;
+ var current_month = (date.getMonth() == month);
+ if (!current_month) {
+ if (this.showsOtherMonths) {
+ cell.className += " othermonth";
+ cell.otherMonth = true;
+ } else {
+ cell.className = "emptycell";
+ cell.innerHTML = "&nbsp;";
+ cell.disabled = true;
+ continue;
+ }
+ } else {
+ cell.otherMonth = false;
+ hasdays = true;
+ }
+ cell.disabled = false;
+ cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
+ if (dates)
+ dates[date.print("%Y%m%d")] = cell;
+ if (this.getDateStatus) {
+ var status = this.getDateStatus(date, year, month, iday);
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ cell.caldate = new Date(date);
+ cell.ttip = "_";
+ if (!this.multiple && current_month
+ && iday == mday && this.hiliteToday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (date.getFullYear() == TY &&
+ date.getMonth() == TM &&
+ iday == TD) {
+ cell.className += " today";
+ cell.ttip += Calendar._TT["PART_TODAY"];
+ }
+ if (weekend.indexOf(wday.toString()) != -1)
+ cell.className += cell.otherMonth ? " oweekend" : " weekend";
+ }
+ }
+ if (!(hasdays || this.showsOtherMonths))
+ row.className = "emptyrow";
+ }
+ this.title.innerHTML = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ this.table.style.visibility = "visible";
+ this._initMultipleDates();
+ // PROFILE
+ // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+Calendar.prototype._initMultipleDates = function() {
+ if (this.multiple) {
+ for (var i in this.multiple) {
+ var cell = this.datesCells[i];
+ var d = this.multiple[i];
+ if (!d)
+ continue;
+ if (cell)
+ cell.className += " selected";
+ }
+ }
+};
+
+Calendar.prototype._toggleMultipleDate = function(date) {
+ if (this.multiple) {
+ var ds = date.print("%Y%m%d");
+ var cell = this.datesCells[ds];
+ if (cell) {
+ var d = this.multiple[ds];
+ if (!d) {
+ Calendar.addClass(cell, "selected");
+ this.multiple[ds] = date;
+ } else {
+ Calendar.removeClass(cell, "selected");
+ delete this.multiple[ds];
+ }
+ }
+ }
+};
+
+Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
+ this.getDateToolTip = unaryFunction;
+};
+
+/**
+ * Calls _init function above for going to a certain date (but only if the
+ * date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+ if (!date.equalsTo(this.date)) {
+ this._init(this.firstDayOfWeek, date);
+ }
+};
+
+/**
+ * Refreshes the calendar. Useful if the "disabledHandler" function is
+ * dynamic, meaning that the list of disabled date can change at runtime.
+ * Just * call this function if you think that the list of disabled dates
+ * should * change.
+ */
+Calendar.prototype.refresh = function () {
+ this._init(this.firstDayOfWeek, this.date);
+};
+
+/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
+Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
+ this._init(firstDayOfWeek, this.date);
+ this._displayWeekdays();
+};
+
+/**
+ * Allows customization of what dates are enabled. The "unaryFunction"
+ * parameter must be a function object that receives the date (as a JS Date
+ * object) and returns a boolean value. If the returned value is true then
+ * the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+ this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+ this.minYear = a;
+ this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+ if (this.onSelected) {
+ this.onSelected(this, this.date.print(this.dateFormat));
+ }
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+ if (this.onClose) {
+ this.onClose(this);
+ }
+ this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+ var el = this.element.parentNode;
+ el.removeChild(this.element);
+ Calendar._C = null;
+ window._dynarch_popupCalendar = null;
+};
+
+/**
+ * Moves the calendar element to a different section in the DOM tree (changes
+ * its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+ var el = this.element;
+ el.parentNode.removeChild(el);
+ new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown. If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+ var calendar = window._dynarch_popupCalendar;
+ if (!calendar) {
+ return false;
+ }
+ var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+ for (; el != null && el != calendar.element; el = el.parentNode);
+ if (el == null) {
+ // calls closeHandler which should hide the calendar.
+ window._dynarch_popupCalendar.callCloseHandler();
+ return Calendar.stopEvent(ev);
+ }
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+ var rows = this.table.getElementsByTagName("tr");
+ for (var i = rows.length; i > 0;) {
+ var row = rows[--i];
+ Calendar.removeClass(row, "rowhilite");
+ var cells = row.getElementsByTagName("td");
+ for (var j = cells.length; j > 0;) {
+ var cell = cells[--j];
+ Calendar.removeClass(cell, "hilite");
+ Calendar.removeClass(cell, "active");
+ }
+ }
+ this.element.style.display = "block";
+ this.hidden = false;
+ if (this.isPopup) {
+ window._dynarch_popupCalendar = this;
+ Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.hideShowCovered();
+};
+
+/**
+ * Hides the calendar. Also removes any "hilite" from the class of any TD
+ * element.
+ */
+Calendar.prototype.hide = function () {
+ if (this.isPopup) {
+ Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.element.style.display = "none";
+ this.hidden = true;
+ this.hideShowCovered();
+};
+
+/**
+ * Shows the calendar at a given absolute position (beware that, depending on
+ * the calendar element style -- position property -- this might be relative
+ * to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+ var s = this.element.style;
+ s.left = x + "px";
+ s.top = y + "px";
+ this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+ var self = this;
+ var p = Calendar.getAbsolutePos(el);
+ if (!opts || typeof opts != "string") {
+ this.showAt(p.x, p.y + el.offsetHeight);
+ return true;
+ }
+ function fixPosition(box) {
+ if (box.x < 0)
+ box.x = 0;
+ if (box.y < 0)
+ box.y = 0;
+ var cp = document.createElement("div");
+ var s = cp.style;
+ s.position = "absolute";
+ s.right = s.bottom = s.width = s.height = "0px";
+ document.body.appendChild(cp);
+ var br = Calendar.getAbsolutePos(cp);
+ document.body.removeChild(cp);
+ if (Calendar.is_ie) {
+ br.y += document.body.scrollTop;
+ br.x += document.body.scrollLeft;
+ } else {
+ br.y += window.scrollY;
+ br.x += window.scrollX;
+ }
+ var tmp = box.x + box.width - br.x;
+ if (tmp > 0) box.x -= tmp;
+ tmp = box.y + box.height - br.y;
+ if (tmp > 0) box.y -= tmp;
+ };
+ this.element.style.display = "block";
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ var w = self.element.offsetWidth;
+ var h = self.element.offsetHeight;
+ self.element.style.display = "none";
+ var valign = opts.substr(0, 1);
+ var halign = "l";
+ if (opts.length > 1) {
+ halign = opts.substr(1, 1);
+ }
+ // vertical alignment
+ switch (valign) {
+ case "T": p.y -= h; break;
+ case "B": p.y += el.offsetHeight; break;
+ case "C": p.y += (el.offsetHeight - h) / 2; break;
+ case "t": p.y += el.offsetHeight - h; break;
+ case "b": break; // already there
+ }
+ // horizontal alignment
+ switch (halign) {
+ case "L": p.x -= w; break;
+ case "R": p.x += el.offsetWidth; break;
+ case "C": p.x += (el.offsetWidth - w) / 2; break;
+ case "l": p.x += el.offsetWidth - w; break;
+ case "r": break; // already there
+ }
+ p.width = w;
+ p.height = h + 40;
+ self.monthsCombo.style.display = "none";
+ fixPosition(p);
+ self.showAt(p.x, p.y);
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+ this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+ this.ttDateFormat = str;
+};
+
+/**
+ * Tries to identify the date represented in a string. If successful it also
+ * calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function(str, fmt) {
+ if (!fmt)
+ fmt = this.dateFormat;
+ this.setDate(Date.parseDate(str, fmt));
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ if (!Calendar.is_ie && !Calendar.is_opera)
+ return;
+ function getVisib(obj){
+ var value = obj.style.visibility;
+ if (!value) {
+ if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+ if (!Calendar.is_khtml)
+ value = document.defaultView.
+ getComputedStyle(obj, "").getPropertyValue("visibility");
+ else
+ value = '';
+ } else if (obj.currentStyle) { // IE
+ value = obj.currentStyle.visibility;
+ } else
+ value = '';
+ }
+ return value;
+ };
+
+ var tags = new Array("applet", "iframe", "select");
+ var el = this.element;
+
+ var p = Calendar.getAbsolutePos(el);
+ var EX1 = p.x;
+ var EX2 = el.offsetWidth + EX1;
+ var EY1 = p.y;
+ var EY2 = el.offsetHeight + EY1;
+
+ for (var k = tags.length; k > 0; ) {
+ var ar = document.getElementsByTagName(tags[--k]);
+ var cc = null;
+
+ for (var i = ar.length; i > 0;) {
+ cc = ar[--i];
+
+ p = Calendar.getAbsolutePos(cc);
+ var CX1 = p.x;
+ var CX2 = cc.offsetWidth + CX1;
+ var CY1 = p.y;
+ var CY2 = cc.offsetHeight + CY1;
+
+ if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = cc.__msh_save_visibility;
+ } else {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = "hidden";
+ }
+ }
+ }
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+ var fdow = this.firstDayOfWeek;
+ var cell = this.firstdayname;
+ var weekend = Calendar._TT["WEEKEND"];
+ for (var i = 0; i < 7; ++i) {
+ cell.className = "day name";
+ var realday = (i + fdow) % 7;
+ if (i) {
+ cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
+ cell.navtype = 100;
+ cell.calendar = this;
+ cell.fdow = realday;
+ Calendar._add_evs(cell);
+ }
+ if (weekend.indexOf(realday.toString()) != -1) {
+ Calendar.addClass(cell, "weekend");
+ }
+ cell.innerHTML = Calendar._SDN[(i + fdow) % 7];
+ cell = cell.nextSibling;
+ }
+};
+
+/** Internal function. Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+ this.monthsCombo.style.display = "none";
+ this.yearsCombo.style.display = "none";
+};
+
+/** Internal function. Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+ if (this.dragging) {
+ return;
+ }
+ this.dragging = true;
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posY = ev.clientY + window.scrollY;
+ posX = ev.clientX + window.scrollX;
+ }
+ var st = this.element.style;
+ this.xOffs = posX - parseInt(st.left);
+ this.yOffs = posY - parseInt(st.top);
+ with (Calendar) {
+ addEvent(document, "mousemove", calDragIt);
+ addEvent(document, "mouseup", calDragEnd);
+ }
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR = 60 * Date.MINUTE;
+Date.DAY = 24 * Date.HOUR;
+Date.WEEK = 7 * Date.DAY;
+
+Date.parseDate = function(str, fmt) {
+ var today = new Date();
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ var b = fmt.match(/%./g);
+ var i = 0, j = 0;
+ var hr = 0;
+ var min = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (!a[i])
+ continue;
+ switch (b[i]) {
+ case "%d":
+ case "%e":
+ d = parseInt(a[i], 10);
+ break;
+
+ case "%m":
+ m = parseInt(a[i], 10) - 1;
+ break;
+
+ case "%Y":
+ case "%y":
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ break;
+
+ case "%b":
+ case "%B":
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+ }
+ break;
+
+ case "%H":
+ case "%I":
+ case "%k":
+ case "%l":
+ hr = parseInt(a[i], 10);
+ break;
+
+ case "%P":
+ case "%p":
+ if (/pm/i.test(a[i]) && hr < 12)
+ hr += 12;
+ else if (/am/i.test(a[i]) && hr >= 12)
+ hr -= 12;
+ break;
+
+ case "%M":
+ min = parseInt(a[i], 10);
+ break;
+ }
+ }
+ if (isNaN(y)) y = today.getFullYear();
+ if (isNaN(m)) m = today.getMonth();
+ if (isNaN(d)) d = today.getDate();
+ if (isNaN(hr)) hr = today.getHours();
+ if (isNaN(min)) min = today.getMinutes();
+ if (y != 0 && m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ y = 0; m = -1; d = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
+ var t = -1;
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+ }
+ if (t != -1) {
+ if (m != -1) {
+ d = m+1;
+ }
+ m = t;
+ }
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
+ m = a[i]-1;
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ } else if (d == 0) {
+ d = a[i];
+ }
+ }
+ if (y == 0)
+ y = today.getFullYear();
+ if (m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ return today;
+};
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+ var year = this.getFullYear();
+ if (typeof month == "undefined") {
+ month = this.getMonth();
+ }
+ if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+ return 29;
+ } else {
+ return Date._MD[month];
+ }
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+ var time = now - then;
+ return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+ var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var DoW = d.getDay();
+ d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
+ var ms = d.valueOf(); // GMT
+ d.setMonth(0);
+ d.setDate(4); // Thu in Week 1
+ return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
+};
+
+/** Checks date and time equality */
+Date.prototype.equalsTo = function(date) {
+ return ((this.getFullYear() == date.getFullYear()) &&
+ (this.getMonth() == date.getMonth()) &&
+ (this.getDate() == date.getDate()) &&
+ (this.getHours() == date.getHours()) &&
+ (this.getMinutes() == date.getMinutes()));
+};
+
+/** Set only the year, month, date parts (keep existing time) */
+Date.prototype.setDateOnly = function(date) {
+ var tmp = new Date(date);
+ this.setDate(1);
+ this.setFullYear(tmp.getFullYear());
+ this.setMonth(tmp.getMonth());
+ this.setDate(tmp.getDate());
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+ var m = this.getMonth();
+ var d = this.getDate();
+ var y = this.getFullYear();
+ var wn = this.getWeekNumber();
+ var w = this.getDay();
+ var s = {};
+ var hr = this.getHours();
+ var pm = (hr >= 12);
+ var ir = (pm) ? (hr - 12) : hr;
+ var dy = this.getDayOfYear();
+ if (ir == 0)
+ ir = 12;
+ var min = this.getMinutes();
+ var sec = this.getSeconds();
+ s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+ s["%A"] = Calendar._DN[w]; // full weekday name
+ s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+ s["%B"] = Calendar._MN[m]; // full month name
+ // FIXME: %c : preferred date and time representation for the current locale
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+ s["%e"] = d; // the day of the month (range 1 to 31)
+ // FIXME: %D : american date style: %m/%d/%y
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+ s["%n"] = "\n"; // a newline character
+ s["%p"] = pm ? "PM" : "AM";
+ s["%P"] = pm ? "pm" : "am";
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
+ // FIXME: %R : the time in 24-hour notation %H:%M
+ s["%s"] = Math.floor(this.getTime() / 1000);
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+ s["%t"] = "\t"; // a tab character
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
+ // FIXME: %x : preferred date representation for the current locale without the time
+ // FIXME: %X : preferred time representation for the current locale without the date
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+ s["%Y"] = y; // year with the century
+ s["%%"] = "%"; // a literal '%' character
+
+ var re = /%./g;
+ if (!Calendar.is_ie5 && !Calendar.is_khtml)
+ return str.replace(re, function (par) { return s[par] || par; });
+
+ var a = str.match(re);
+ for (var i = 0; i < a.length; i++) {
+ var tmp = s[a[i]];
+ if (tmp) {
+ re = new RegExp(a[i], 'g');
+ str = str.replace(re, tmp);
+ }
+ }
+
+ return str;
+};
+
+Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
+Date.prototype.setFullYear = function(y) {
+ var d = new Date(this);
+ d.__msh_oldSetFullYear(y);
+ if (d.getMonth() != this.getMonth())
+ this.setDate(28);
+ this.__msh_oldSetFullYear(y);
+};
+
+// END: DATE OBJECT PATCHES
+
+
+// global object that remembers the calendar
+window._dynarch_popupCalendar = null;
diff --git a/js/jscalendar/calendar.php b/js/jscalendar/calendar.php
new file mode 100644
index 0000000..5b9120d
--- /dev/null
+++ b/js/jscalendar/calendar.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * File: calendar.php | (c) dynarch.com 2004
+ * Distributed as part of "The Coolest DHTML Calendar"
+ * under the same terms.
+ * -----------------------------------------------------------------
+ * This file implements a simple PHP wrapper for the calendar. It
+ * allows you to easily include all the calendar files and setup the
+ * calendar by instantiating and calling a PHP object.
+ */
+
+define('NEWLINE', "\n");
+
+class DHTML_Calendar {
+ var $calendar_lib_path;
+
+ var $calendar_file;
+ var $calendar_lang_file;
+ var $calendar_setup_file;
+ var $calendar_theme_file;
+ var $calendar_options;
+
+ function DHTML_Calendar($calendar_lib_path = '/calendar/',
+ $lang = 'en',
+ $theme = 'calendar-win2k-1',
+ $stripped = true) {
+ if ($stripped) {
+ $this->calendar_file = 'calendar_stripped.js';
+ $this->calendar_setup_file = 'calendar-setup_stripped.js';
+ } else {
+ $this->calendar_file = 'calendar.js';
+ $this->calendar_setup_file = 'calendar-setup.js';
+ }
+ $this->calendar_lang_file = 'lang/calendar-' . $lang . '.js';
+ $this->calendar_theme_file = $theme.'.css';
+ $this->calendar_lib_path = preg_replace('/\/+$/', '/', $calendar_lib_path);
+ $this->calendar_options = array('ifFormat' => '%Y/%m/%d',
+ 'daFormat' => '%Y/%m/%d');
+ }
+
+ function set_option($name, $value) {
+ $this->calendar_options[$name] = $value;
+ }
+
+ function load_files() {
+ echo $this->get_load_files_code();
+ }
+
+ function get_load_files_code() {
+ $code = ( '<link rel="stylesheet" type="text/css" media="all" href="' .
+ $this->calendar_lib_path . $this->calendar_theme_file .
+ '" />' . NEWLINE );
+ $code .= ( '<script type="text/javascript" src="' .
+ $this->calendar_lib_path . $this->calendar_file .
+ '"></script>' . NEWLINE );
+ $code .= ( '<script type="text/javascript" src="' .
+ $this->calendar_lib_path . $this->calendar_lang_file .
+ '"></script>' . NEWLINE );
+ $code .= ( '<script type="text/javascript" src="' .
+ $this->calendar_lib_path . $this->calendar_setup_file .
+ '"></script>' );
+ return $code;
+ }
+
+ function _make_calendar($other_options = array()) {
+ $js_options = $this->_make_js_hash(array_merge($this->calendar_options, $other_options));
+ $code = ( '<script type="text/javascript">Calendar.setup({' .
+ $js_options .
+ '});</script>' );
+ return $code;
+ }
+
+ function make_input_field($cal_options = array(), $field_attributes = array()) {
+ $id = $this->_gen_id();
+ $attrstr = $this->_make_html_attr(array_merge($field_attributes,
+ array('id' => $this->_field_id($id),
+ 'type' => 'text')));
+ echo '<input ' . $attrstr .'/>';
+ echo '<a href="#" id="'. $this->_trigger_id($id) . '">' .
+ '<img align="middle" border="0" src="' . $this->calendar_lib_path . 'img.gif" alt="" /></a>';
+
+ $options = array_merge($cal_options,
+ array('inputField' => $this->_field_id($id),
+ 'button' => $this->_trigger_id($id)));
+ echo $this->_make_calendar($options);
+ }
+
+ /// PRIVATE SECTION
+
+ function _field_id($id) { return 'f-calendar-field-' . $id; }
+ function _trigger_id($id) { return 'f-calendar-trigger-' . $id; }
+ function _gen_id() { static $id = 0; return ++$id; }
+
+ function _make_js_hash($array) {
+ $jstr = '';
+ reset($array);
+ while (list($key, $val) = each($array)) {
+ if (is_bool($val))
+ $val = $val ? 'true' : 'false';
+ else if (!is_numeric($val))
+ $val = '"'.$val.'"';
+ if ($jstr) $jstr .= ',';
+ $jstr .= '"' . $key . '":' . $val;
+ }
+ return $jstr;
+ }
+
+ function _make_html_attr($array) {
+ $attrstr = '';
+ reset($array);
+ while (list($key, $val) = each($array)) {
+ $attrstr .= $key . '="' . $val . '" ';
+ }
+ return $attrstr;
+ }
+};
+
+?> \ No newline at end of file
diff --git a/js/jscalendar/calendar_stripped.js b/js/jscalendar/calendar_stripped.js
new file mode 100644
index 0000000..4fe03f1
--- /dev/null
+++ b/js/jscalendar/calendar_stripped.js
@@ -0,0 +1,14 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+ Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mihai_bazon@yahoo.com> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setFirstDayOfWeek(el.fdow);return;case 50:var range=el._range;var current=el.innerHTML;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="<div unselectable='on'>"+text+"</div>";return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("&#x00d7;",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("&#x00ab;",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("&#x2039;",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("&#x203a;",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("&#x00bb;",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||"&nbsp;";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.innerHTML=init;part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.innerHTML=":";span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML="&nbsp;";cal.onSetTime=function(){var pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.innerHTML=Calendar._SMN[i];div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++y<cal.ar_days.length)ne=cal.ar_days[y][x];else{nextMonth();setVars();}break;}break;}if(ne){if(!ne.disabled)Calendar.cellClick(ne);else if(prev)prevMonth();else nextMonth();}}break;case 13:if(act)Calendar.cellClick(cal.currentDateEl,ev);break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date(),TY=today.getFullYear(),TM=today.getMonth(),TD=today.getDate();this.table.style.visibility="hidden";var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML="&nbsp;";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&&current_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window._dynarch_popupCalendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window._dynarch_popupCalendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=this.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(this.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};Calendar.prototype._displayWeekdays=function(){var fdow=this.firstDayOfWeek;var cell=this.firstdayname;var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<7;++i){cell.className="day name";var realday=(i+fdow)%7;if(i){cell.ttip=Calendar._TT["DAY_FIRST"].replace("%s",Calendar._DN[realday]);cell.navtype=100;cell.calendar=this;cell.fdow=realday;Calendar._add_evs(cell);}if(weekend.indexOf(realday.toString())!=-1){Calendar.addClass(cell,"weekend");}cell.innerHTML=Calendar._SDN[(i+fdow)%7];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.parseDate=function(str,fmt){var today=new Date();var y=0;var m=-1;var d=0;var a=str.split(/\W+/);var b=fmt.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(!a[i])continue;switch(b[i]){case "%d":case "%e":d=parseInt(a[i],10);break;case "%m":m=parseInt(a[i],10)-1;break;case "%Y":case "%y":y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i<a.length;i++){var tmp=s[a[i]];if(tmp){re=new RegExp(a[i],'g');str=str.replace(re,tmp);}}return str;};Date.prototype.__msh_oldSetFullYear=Date.prototype.setFullYear;Date.prototype.setFullYear=function(y){var d=new Date(this);d.__msh_oldSetFullYear(y);if(d.getMonth()!=this.getMonth())this.setDate(28);this.__msh_oldSetFullYear(y);};window._dynarch_popupCalendar=null; \ No newline at end of file
diff --git a/js/jscalendar/img.gif b/js/jscalendar/img.gif
new file mode 100644
index 0000000..cd2c4a5
--- /dev/null
+++ b/js/jscalendar/img.gif
Binary files differ
diff --git a/js/jscalendar/lang/calendar-af.js b/js/jscalendar/lang/calendar-af.js
new file mode 100644
index 0000000..aeda581
--- /dev/null
+++ b/js/jscalendar/lang/calendar-af.js
@@ -0,0 +1,39 @@
+// ** I18N Afrikaans
+Calendar._DN = new Array
+("Sondag",
+ "Maandag",
+ "Dinsdag",
+ "Woensdag",
+ "Donderdag",
+ "Vrydag",
+ "Saterdag",
+ "Sondag");
+Calendar._MN = new Array
+("Januarie",
+ "Februarie",
+ "Maart",
+ "April",
+ "Mei",
+ "Junie",
+ "Julie",
+ "Augustus",
+ "September",
+ "Oktober",
+ "November",
+ "Desember");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["TOGGLE"] = "Verander eerste dag van die week";
+Calendar._TT["PREV_YEAR"] = "Vorige jaar (hou vir keuselys)";
+Calendar._TT["PREV_MONTH"] = "Vorige maand (hou vir keuselys)";
+Calendar._TT["GO_TODAY"] = "Gaan na vandag";
+Calendar._TT["NEXT_MONTH"] = "Volgende maand (hou vir keuselys)";
+Calendar._TT["NEXT_YEAR"] = "Volgende jaar (hou vir keuselys)";
+Calendar._TT["SEL_DATE"] = "Kies datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te skuif";
+Calendar._TT["PART_TODAY"] = " (vandag)";
+Calendar._TT["MON_FIRST"] = "Vertoon Maandag eerste";
+Calendar._TT["SUN_FIRST"] = "Display Sunday first";
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
diff --git a/js/jscalendar/lang/calendar-al.js b/js/jscalendar/lang/calendar-al.js
new file mode 100644
index 0000000..4f701cf
--- /dev/null
+++ b/js/jscalendar/lang/calendar-al.js
@@ -0,0 +1,101 @@
+// Calendar ALBANIAN language
+//author Rigels Gordani rige@hotmail.com
+
+// ditet
+Calendar._DN = new Array
+("E Diele",
+"E Hene",
+"E Marte",
+"E Merkure",
+"E Enjte",
+"E Premte",
+"E Shtune",
+"E Diele");
+
+//ditet shkurt
+Calendar._SDN = new Array
+("Die",
+"Hen",
+"Mar",
+"Mer",
+"Enj",
+"Pre",
+"Sht",
+"Die");
+
+// muajt
+Calendar._MN = new Array
+("Janar",
+"Shkurt",
+"Mars",
+"Prill",
+"Maj",
+"Qeshor",
+"Korrik",
+"Gusht",
+"Shtator",
+"Tetor",
+"Nentor",
+"Dhjetor");
+
+// muajte shkurt
+Calendar._SMN = new Array
+("Jan",
+"Shk",
+"Mar",
+"Pri",
+"Maj",
+"Qes",
+"Kor",
+"Gus",
+"Sht",
+"Tet",
+"Nen",
+"Dhj");
+
+// ndihmesa
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Per kalendarin";
+
+Calendar._TT["ABOUT"] =
+"Zgjedhes i ores/dates ne DHTML \n" +
+"\n\n" +"Zgjedhja e Dates:\n" +
+"- Perdor butonat \xab, \xbb per te zgjedhur vitin\n" +
+"- Perdor butonat" + String.fromCharCode(0x2039) + ", " +
+String.fromCharCode(0x203a) +
+" per te zgjedhur muajin\n" +
+"- Mbani shtypur butonin e mousit per nje zgjedje me te shpejte.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Zgjedhja e kohes:\n" +
+"- Kliko tek ndonje nga pjeset e ores per ta rritur ate\n" +
+"- ose kliko me Shift per ta zvogeluar ate\n" +
+"- ose cliko dhe terhiq per zgjedhje me te shpejte.";
+
+Calendar._TT["PREV_YEAR"] = "Viti i shkuar (prit per menune)";
+Calendar._TT["PREV_MONTH"] = "Muaji i shkuar (prit per menune)";
+Calendar._TT["GO_TODAY"] = "Sot";
+Calendar._TT["NEXT_MONTH"] = "Muaji i ardhshem (prit per menune)";
+Calendar._TT["NEXT_YEAR"] = "Viti i ardhshem (prit per menune)";
+Calendar._TT["SEL_DATE"] = "Zgjidh daten";
+Calendar._TT["DRAG_TO_MOVE"] = "Terhiqe per te levizur";
+Calendar._TT["PART_TODAY"] = " (sot)";
+
+// "%s" eshte dita e pare e javes
+// %s do te zevendesohet me emrin e dite
+Calendar._TT["DAY_FIRST"] = "Trego te %s te paren";
+
+
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Mbyll";
+Calendar._TT["TODAY"] = "Sot";
+Calendar._TT["TIME_PART"] = "Kliko me (Shift-)ose terhiqe per te ndryshuar
+vleren";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "Java";
+Calendar._TT["TIME"] = "Koha:";
+
diff --git a/js/jscalendar/lang/calendar-bg.js b/js/jscalendar/lang/calendar-bg.js
new file mode 100644
index 0000000..664eb70
--- /dev/null
+++ b/js/jscalendar/lang/calendar-bg.js
@@ -0,0 +1,131 @@
+// ** I18N
+
+// Calendar BG language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Translator: Valentin Sheiretsky, <valio@valio.eu.org>
+// Translator: Doncho N. Gunchev, <gunchev@gmail.com> 2006-11-20
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("ÐеделÑ",
+ "Понеделник",
+ "Вторник",
+ "СрÑда",
+ "Четвъртък",
+ "Петък",
+ "Събота",
+ "ÐеделÑ");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Ðед",
+ "Пон",
+ "Вто",
+ "СрÑ",
+ "Чет",
+ "Пет",
+ "Съб",
+ "Ðед");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Януари",
+ "Февруари",
+ "Март",
+ "Ðприл",
+ "Май",
+ "Юни",
+ "Юли",
+ "ÐвгуÑÑ‚",
+ "Септември",
+ "Октомври",
+ "Ðоември",
+ "Декември");
+
+// short month names
+Calendar._SMN = new Array
+("Яну",
+ "Фев",
+ "Мар",
+ "Ðпр",
+ "Май",
+ "Юни",
+ "Юли",
+ "Ðвг",
+ "Сеп",
+ "Окт",
+ "Ðое",
+ "Дек");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° календара";
+
+
+
+Calendar._TT["ABOUT"] =
+"DHTML Дата/Ð§Ð°Ñ Ð¡ÐµÐ»ÐµÐºÑ‚Ð¾Ñ€\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"За поÑледна верÑÐ¸Ñ Ð¿Ð¾Ñетете: http://www.dynarch.com/projects/calendar/\n" +
+"РазпроÑтранÑва Ñе под GNU LGPL. Вижте http://gnu.org/licenses/lgpl.html за повече информациÑ." +
+"\n\n" +
+"Избор на дата:\n" +
+"- Ползвайте бутони \xab, \xbb за да изберете година\n" +
+"- Ползвайте бутони " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " за да изберете меÑец\n" +
+"- Задръжте бутона на мишката на нÑкой от горните бутони за по-бърз избор.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Избор на време:\n" +
+"- ÐатиÑнете Ñ Ð¼Ð¸ÑˆÐºÐ°Ñ‚Ð° на нÑкой от елементите на чаÑа за да го увеличите\n" +
+"- или натиÑнете Ñ Ð¼Ð¸ÑˆÐºÐ°Ñ‚Ð° държейки Shift за да го намалите\n" +
+"- или натиÑнете и влачете (лÑво-дÑÑно) за по-бърз избор.";
+
+Calendar._TT["PREV_YEAR"] = "Предна година (задръжте за меню)";
+Calendar._TT["PREV_MONTH"] = "Преден меÑец (задръжте за меню)";
+Calendar._TT["GO_TODAY"] = "Изберете днеÑ";
+Calendar._TT["NEXT_MONTH"] = "Следващ меÑец (задръжте за меню)";
+Calendar._TT["NEXT_YEAR"] = "Следваща година (задръжте за меню)";
+Calendar._TT["SEL_DATE"] = "Изберете дата";
+Calendar._TT["DRAG_TO_MOVE"] = "ПремеÑтване";
+Calendar._TT["PART_TODAY"] = " (днеÑ)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "%s като първи ден";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Затворете";
+Calendar._TT["TODAY"] = "ДнеÑ";
+Calendar._TT["TIME_PART"] = "ÐатиÑнете (ÑÑŠÑ Shift) или влачете";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%A - %e %B %Y";
+
+Calendar._TT["WK"] = "Седм";
+Calendar._TT["TIME"] = "ЧаÑ:";
diff --git a/js/jscalendar/lang/calendar-big5-utf8.js b/js/jscalendar/lang/calendar-big5-utf8.js
new file mode 100644
index 0000000..14e0d5d
--- /dev/null
+++ b/js/jscalendar/lang/calendar-big5-utf8.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar big5-utf8 language
+// Author: Gary Fu, <gary@garyfu.idv.tw>
+// Encoding: utf8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("星期日",
+ "星期一",
+ "星期二",
+ "星期三",
+ "星期四",
+ "星期五",
+ "星期六",
+ "星期日");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("æ—¥",
+ "一",
+ "二",
+ "三",
+ "å››",
+ "五",
+ "å…­",
+ "æ—¥");
+
+// full month names
+Calendar._MN = new Array
+("一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "ä¹æœˆ",
+ "å月",
+ "å一月",
+ "å二月");
+
+// short month names
+Calendar._SMN = new Array
+("一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "ä¹æœˆ",
+ "å月",
+ "å一月",
+ "å二月");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "關於";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"日期é¸æ“‡æ–¹æ³•:\n" +
+"- 使用 \xab, \xbb 按鈕å¯é¸æ“‡å¹´ä»½\n" +
+"- 使用 " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 按鈕å¯é¸æ“‡æœˆä»½\n" +
+"- 按ä½ä¸Šé¢çš„按鈕å¯ä»¥åŠ å¿«é¸å–";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"時間é¸æ“‡æ–¹æ³•:\n" +
+"- 點擊任何的時間部份å¯å¢žåŠ å…¶å€¼\n" +
+"- åŒæ™‚按Shiftéµå†é»žæ“Šå¯æ¸›å°‘其值\n" +
+"- 點擊並拖曳å¯åŠ å¿«æ”¹è®Šçš„值";
+
+Calendar._TT["PREV_YEAR"] = "上一年 (按ä½é¸å–®)";
+Calendar._TT["PREV_MONTH"] = "下一年 (按ä½é¸å–®)";
+Calendar._TT["GO_TODAY"] = "到今日";
+Calendar._TT["NEXT_MONTH"] = "上一月 (按ä½é¸å–®)";
+Calendar._TT["NEXT_YEAR"] = "下一月 (按ä½é¸å–®)";
+Calendar._TT["SEL_DATE"] = "é¸æ“‡æ—¥æœŸ";
+Calendar._TT["DRAG_TO_MOVE"] = "拖曳";
+Calendar._TT["PART_TODAY"] = " (今日)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "å°‡ %s 顯示在å‰";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "關閉";
+Calendar._TT["TODAY"] = "今日";
+Calendar._TT["TIME_PART"] = "點擊or拖曳å¯æ”¹è®Šæ™‚é–“(åŒæ™‚按Shift為減)";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "週";
+Calendar._TT["TIME"] = "Time:";
diff --git a/js/jscalendar/lang/calendar-big5.js b/js/jscalendar/lang/calendar-big5.js
new file mode 100644
index 0000000..a589358
--- /dev/null
+++ b/js/jscalendar/lang/calendar-big5.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar big5 language
+// Author: Gary Fu, <gary@garyfu.idv.tw>
+// Encoding: big5
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("¬P´Á¤é",
+ "¬P´Á¤@",
+ "¬P´Á¤G",
+ "¬P´Á¤T",
+ "¬P´Á¥|",
+ "¬P´Á¤­",
+ "¬P´Á¤»",
+ "¬P´Á¤é");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("¤é",
+ "¤@",
+ "¤G",
+ "¤T",
+ "¥|",
+ "¤­",
+ "¤»",
+ "¤é");
+
+// full month names
+Calendar._MN = new Array
+("¤@¤ë",
+ "¤G¤ë",
+ "¤T¤ë",
+ "¥|¤ë",
+ "¤­¤ë",
+ "¤»¤ë",
+ "¤C¤ë",
+ "¤K¤ë",
+ "¤E¤ë",
+ "¤Q¤ë",
+ "¤Q¤@¤ë",
+ "¤Q¤G¤ë");
+
+// short month names
+Calendar._SMN = new Array
+("¤@¤ë",
+ "¤G¤ë",
+ "¤T¤ë",
+ "¥|¤ë",
+ "¤­¤ë",
+ "¤»¤ë",
+ "¤C¤ë",
+ "¤K¤ë",
+ "¤E¤ë",
+ "¤Q¤ë",
+ "¤Q¤@¤ë",
+ "¤Q¤G¤ë");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Ãö©ó";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"¤é´Á¿ï¾Ü¤èªk:\n" +
+"- ¨Ï¥Î \xab, \xbb «ö¶s¥i¿ï¾Ü¦~¥÷\n" +
+"- ¨Ï¥Î " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " «ö¶s¥i¿ï¾Ü¤ë¥÷\n" +
+"- «ö¦í¤W­±ªº«ö¶s¥i¥H¥[§Ö¿ï¨ú";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"®É¶¡¿ï¾Ü¤èªk:\n" +
+"- ÂIÀ»¥ô¦óªº®É¶¡³¡¥÷¥i¼W¥[¨ä­È\n" +
+"- ¦P®É«öShiftÁä¦AÂIÀ»¥i´î¤Ö¨ä­È\n" +
+"- ÂIÀ»¨Ã©ì¦²¥i¥[§Ö§ïÅܪº­È";
+
+Calendar._TT["PREV_YEAR"] = "¤W¤@¦~ («ö¦í¿ï³æ)";
+Calendar._TT["PREV_MONTH"] = "¤U¤@¦~ («ö¦í¿ï³æ)";
+Calendar._TT["GO_TODAY"] = "¨ì¤µ¤é";
+Calendar._TT["NEXT_MONTH"] = "¤W¤@¤ë («ö¦í¿ï³æ)";
+Calendar._TT["NEXT_YEAR"] = "¤U¤@¤ë («ö¦í¿ï³æ)";
+Calendar._TT["SEL_DATE"] = "¿ï¾Ü¤é´Á";
+Calendar._TT["DRAG_TO_MOVE"] = "©ì¦²";
+Calendar._TT["PART_TODAY"] = " (¤µ¤é)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "±N %s Åã¥Ü¦b«e";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Ãö³¬";
+Calendar._TT["TODAY"] = "¤µ¤é";
+Calendar._TT["TIME_PART"] = "ÂIÀ»or©ì¦²¥i§ïÅܮɶ¡(¦P®É«öShift¬°´î)";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "¶g";
+Calendar._TT["TIME"] = "Time:";
diff --git a/js/jscalendar/lang/calendar-br.js b/js/jscalendar/lang/calendar-br.js
new file mode 100644
index 0000000..bfb0747
--- /dev/null
+++ b/js/jscalendar/lang/calendar-br.js
@@ -0,0 +1,108 @@
+// ** I18N
+
+// Calendar pt-BR language
+// Author: Fernando Dourado, <fernando.dourado@ig.com.br>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Domingo",
+ "Segunda",
+ "Terça",
+ "Quarta",
+ "Quinta",
+ "Sexta",
+ "Sabádo",
+ "Domingo");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+// [No changes using default values]
+
+// full month names
+Calendar._MN = new Array
+("Janeiro",
+ "Fevereiro",
+ "Março",
+ "Abril",
+ "Maio",
+ "Junho",
+ "Julho",
+ "Agosto",
+ "Setembro",
+ "Outubro",
+ "Novembro",
+ "Dezembro");
+
+// short month names
+// [No changes using default values]
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Sobre o calendário";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Translate to portuguese Brazil (pt-BR) by Fernando Dourado (fernando.dourado@ig.com.br)\n" +
+"Tradução para o português Brasil (pt-BR) por Fernando Dourado (fernando.dourado@ig.com.br)" +
+"\n\n" +
+"Selecionar data:\n" +
+"- Use as teclas \xab, \xbb para selecionar o ano\n" +
+"- Use as teclas " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mês\n" +
+"- Clique e segure com o mouse em qualquer botão para selecionar rapidamente.";
+
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selecionar hora:\n" +
+"- Clique em qualquer uma das partes da hora para aumentar\n" +
+"- ou Shift-clique para diminuir\n" +
+"- ou clique e arraste para selecionar rapidamente.";
+
+Calendar._TT["PREV_YEAR"] = "Ano anterior (clique e segure para menu)";
+Calendar._TT["PREV_MONTH"] = "Mês anterior (clique e segure para menu)";
+Calendar._TT["GO_TODAY"] = "Ir para a data atual";
+Calendar._TT["NEXT_MONTH"] = "Próximo mês (clique e segure para menu)";
+Calendar._TT["NEXT_YEAR"] = "Próximo ano (clique e segure para menu)";
+Calendar._TT["SEL_DATE"] = "Selecione uma data";
+Calendar._TT["DRAG_TO_MOVE"] = "Clique e segure para mover";
+Calendar._TT["PART_TODAY"] = " (hoje)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Exibir %s primeiro";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Fechar";
+Calendar._TT["TODAY"] = "Hoje";
+Calendar._TT["TIME_PART"] = "(Shift-)Clique ou arraste para mudar o valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%d de %B de %Y";
+
+Calendar._TT["WK"] = "sem";
+Calendar._TT["TIME"] = "Hora:";
+
diff --git a/js/jscalendar/lang/calendar-ca.js b/js/jscalendar/lang/calendar-ca.js
new file mode 100644
index 0000000..a2121bc
--- /dev/null
+++ b/js/jscalendar/lang/calendar-ca.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar CA language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Diumenge",
+ "Dilluns",
+ "Dimarts",
+ "Dimecres",
+ "Dijous",
+ "Divendres",
+ "Dissabte",
+ "Diumenge");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Diu",
+ "Dil",
+ "Dmt",
+ "Dmc",
+ "Dij",
+ "Div",
+ "Dis",
+ "Diu");
+
+// full month names
+Calendar._MN = new Array
+("Gener",
+ "Febrer",
+ "Març",
+ "Abril",
+ "Maig",
+ "Juny",
+ "Juliol",
+ "Agost",
+ "Setembre",
+ "Octubre",
+ "Novembre",
+ "Desembre");
+
+// short month names
+Calendar._SMN = new Array
+("Gen",
+ "Feb",
+ "Mar",
+ "Abr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Set",
+ "Oct",
+ "Nov",
+ "Des");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Sobre el calendari";
+
+Calendar._TT["ABOUT"] =
+"DHTML Selector de Data/Hora\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Sel.lecció de Dates:\n" +
+"- Fes servir els botons \xab, \xbb per sel.leccionar l'any\n" +
+"- Fes servir els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per se.lecciconar el mes\n" +
+"- Manté el ratolí apretat en qualsevol dels anteriors per sel.lecció ràpida.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- claca en qualsevol de les parts de la hora per augmentar-les\n" +
+"- o Shift-click per decrementar-la\n" +
+"- or click and arrastra per sel.lecció ràpida.";
+
+Calendar._TT["PREV_YEAR"] = "Any anterior (Mantenir per menu)";
+Calendar._TT["PREV_MONTH"] = "Mes anterior (Mantenir per menu)";
+Calendar._TT["GO_TODAY"] = "Anar a avui";
+Calendar._TT["NEXT_MONTH"] = "Mes següent (Mantenir per menu)";
+Calendar._TT["NEXT_YEAR"] = "Any següent (Mantenir per menu)";
+Calendar._TT["SEL_DATE"] = "Sel.leccionar data";
+Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar per moure";
+Calendar._TT["PART_TODAY"] = " (avui)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Mostra %s primer";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Tanca";
+Calendar._TT["TODAY"] = "Avui";
+Calendar._TT["TIME_PART"] = "(Shift-)Click a arrastra per canviar el valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "st";
+Calendar._TT["TIME"] = "Hora:";
diff --git a/js/jscalendar/lang/calendar-cs.js b/js/jscalendar/lang/calendar-cs.js
new file mode 100644
index 0000000..09e49ba
--- /dev/null
+++ b/js/jscalendar/lang/calendar-cs.js
@@ -0,0 +1,69 @@
+/*
+ calendar-cs.js
+ language: Czech
+ encoding: utf-8
+ author: Lubos Jerabek (xnet@seznam.cz)
+ Jan Uhlir (espinosa@centrum.cz)
+*/
+
+// ** I18N
+Calendar._DN = new Array('Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota','Neděle');
+Calendar._SDN = new Array('Ne','Po','Út','St','Čt','Pá','So','Ne');
+Calendar._MN = new Array('Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec');
+Calendar._SMN = new Array('Led','Úno','Bře','Dub','Kvě','Črv','Čvc','Srp','Zář','Říj','Lis','Pro');
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O komponentě kalendář";
+Calendar._TT["TOGGLE"] = "Změna prvního dne v týdnu";
+Calendar._TT["PREV_YEAR"] = "Předchozí rok (přidrž pro menu)";
+Calendar._TT["PREV_MONTH"] = "Předchozí měsíc (přidrž pro menu)";
+Calendar._TT["GO_TODAY"] = "Dnešní datum";
+Calendar._TT["NEXT_MONTH"] = "Další měsíc (přidrž pro menu)";
+Calendar._TT["NEXT_YEAR"] = "Další rok (přidrž pro menu)";
+Calendar._TT["SEL_DATE"] = "Vyber datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Chyť a táhni, pro přesun";
+Calendar._TT["PART_TODAY"] = " (dnes)";
+Calendar._TT["MON_FIRST"] = "Ukaž jako první Pondělí";
+//Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Výběr datumu:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Použijte tlaÄítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k výbÄ›ru mÄ›síce\n" +
+"- Podržte tlaÄítko myÅ¡i na jakémkoliv z tÄ›ch tlaÄítek pro rychlejší výbÄ›r.";
+
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"VýbÄ›r Äasu:\n" +
+"- KliknÄ›te na jakoukoliv z Äástí výbÄ›ru Äasu pro zvýšení.\n" +
+"- nebo Shift-click pro snížení\n" +
+"- nebo klikněte a táhněte pro rychlejší výběr.";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Zobraz %s první";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Zavřít";
+Calendar._TT["TODAY"] = "Dnes";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro změnu hodnoty";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "ÄŒas:";
+
+// First day of the week. 0 means display Sunday first, 1 means display
+// Monday first, etc.
+Calendar._FD = 1;
diff --git a/js/jscalendar/lang/calendar-da.js b/js/jscalendar/lang/calendar-da.js
new file mode 100644
index 0000000..2d87263
--- /dev/null
+++ b/js/jscalendar/lang/calendar-da.js
@@ -0,0 +1,125 @@
+// ** I18N
+
+// Calendar DA language
+// Author: Michael Thingmand Henriksen, <michael (a) thingmand dot dk>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Søndag",
+"Mandag",
+"Tirsdag",
+"Onsdag",
+"Torsdag",
+"Fredag",
+"Lørdag",
+"Søndag");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Søn",
+"Man",
+"Tir",
+"Ons",
+"Tor",
+"Fre",
+"Lør",
+"Søn");
+
+// full month names
+Calendar._MN = new Array
+("Januar",
+"Februar",
+"Marts",
+"April",
+"Maj",
+"Juni",
+"Juli",
+"August",
+"September",
+"Oktober",
+"November",
+"December");
+
+Calendar._FD = 1;
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+"Feb",
+"Mar",
+"Apr",
+"Maj",
+"Jun",
+"Jul",
+"Aug",
+"Sep",
+"Okt",
+"Nov",
+"Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Om Kalenderen";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For den seneste version besøg: http://www.dynarch.com/projects/calendar/\n"; +
+"Distribueret under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detajler." +
+"\n\n" +
+"Valg af dato:\n" +
+"- Brug \xab, \xbb knapperne for at vælge år\n" +
+"- Brug " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knapperne for at vælge måned\n" +
+"- Hold knappen på musen nede på knapperne ovenfor for hurtigere valg.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Valg af tid:\n" +
+"- Klik på en vilkårlig del for større værdi\n" +
+"- eller Shift-klik for for mindre værdi\n" +
+"- eller klik og træk for hurtigere valg.";
+
+Calendar._TT["PREV_YEAR"] = "Ét år tilbage (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Én måned tilbage (hold for menu)";
+Calendar._TT["GO_TODAY"] = "GÃ¥ til i dag";
+Calendar._TT["NEXT_MONTH"] = "Én måned frem (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Ét år frem (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Vælg dag";
+Calendar._TT["DRAG_TO_MOVE"] = "Træk vinduet";
+Calendar._TT["PART_TODAY"] = " (i dag)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Vis %s først";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Luk";
+Calendar._TT["TODAY"] = "I dag";
+Calendar._TT["TIME_PART"] = "(Shift-)klik eller træk for at ændre værdi";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "Uge";
+Calendar._TT["TIME"] = "Tid:";
diff --git a/js/jscalendar/lang/calendar-de.js b/js/jscalendar/lang/calendar-de.js
new file mode 100644
index 0000000..c5dfd55
--- /dev/null
+++ b/js/jscalendar/lang/calendar-de.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar DE language
+// Author: Jack (tR), <jack@jtr.de>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sonntag",
+ "Montag",
+ "Dienstag",
+ "Mittwoch",
+ "Donnerstag",
+ "Freitag",
+ "Samstag",
+ "Sonntag");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("So",
+ "Mo",
+ "Di",
+ "Mi",
+ "Do",
+ "Fr",
+ "Sa",
+ "So");
+
+// First day of the week. 0 means Sunday, 1 means Monday
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Januar",
+ "Februar",
+ "M\u00e4rz",
+ "April",
+ "Mai",
+ "Juni",
+ "Juli",
+ "August",
+ "September",
+ "Oktober",
+ "November",
+ "Dezember");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "M\u00e4r",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dez");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "\u00DCber dieses Kalendarmodul";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Datum ausw\u00e4hlen:\n" +
+"- Benutzen Sie die \xab, \xbb Buttons um das Jahr zu w\u00e4hlen\n" +
+"- Benutzen Sie die " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " Buttons um den Monat zu w\u00e4hlen\n" +
+"- F\u00fcr eine Schnellauswahl halten Sie die Maustaste \u00fcber diesen Buttons fest.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Zeit ausw\u00e4hlen:\n" +
+"- Klicken Sie auf die Teile der Uhrzeit, um diese zu erh\u00F6hen\n" +
+"- oder klicken Sie mit festgehaltener Shift-Taste um diese zu verringern\n" +
+"- oder klicken und festhalten f\u00fcr Schnellauswahl.";
+
+Calendar._TT["TOGGLE"] = "Ersten Tag der Woche w\u00e4hlen";
+Calendar._TT["PREV_YEAR"] = "Voriges Jahr (Festhalten f\u00fcr Schnellauswahl)";
+Calendar._TT["PREV_MONTH"] = "Voriger Monat (Festhalten f\u00fcr Schnellauswahl)";
+Calendar._TT["GO_TODAY"] = "Heute ausw\u00e4hlen";
+Calendar._TT["NEXT_MONTH"] = "N\u00e4chst. Monat (Festhalten f\u00fcr Schnellauswahl)";
+Calendar._TT["NEXT_YEAR"] = "N\u00e4chst. Jahr (Festhalten f\u00fcr Schnellauswahl)";
+Calendar._TT["SEL_DATE"] = "Datum ausw\u00e4hlen";
+Calendar._TT["DRAG_TO_MOVE"] = "Zum Bewegen festhalten";
+Calendar._TT["PART_TODAY"] = " (Heute)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Woche beginnt mit %s ";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Schlie\u00dfen";
+Calendar._TT["TODAY"] = "Heute";
+Calendar._TT["TIME_PART"] = "(Shift-)Klick oder Festhalten und Ziehen um den Wert zu \u00e4ndern";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Zeit:";
diff --git a/js/jscalendar/lang/calendar-du.js b/js/jscalendar/lang/calendar-du.js
new file mode 100644
index 0000000..2200448
--- /dev/null
+++ b/js/jscalendar/lang/calendar-du.js
@@ -0,0 +1,45 @@
+// ** I18N
+Calendar._DN = new Array
+("Zondag",
+ "Maandag",
+ "Dinsdag",
+ "Woensdag",
+ "Donderdag",
+ "Vrijdag",
+ "Zaterdag",
+ "Zondag");
+Calendar._MN = new Array
+("Januari",
+ "Februari",
+ "Maart",
+ "April",
+ "Mei",
+ "Juni",
+ "Juli",
+ "Augustus",
+ "September",
+ "Oktober",
+ "November",
+ "December");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["TOGGLE"] = "Toggle startdag van de week";
+Calendar._TT["PREV_YEAR"] = "Vorig jaar (indrukken voor menu)";
+Calendar._TT["PREV_MONTH"] = "Vorige month (indrukken voor menu)";
+Calendar._TT["GO_TODAY"] = "Naar Vandaag";
+Calendar._TT["NEXT_MONTH"] = "Volgende Maand (indrukken voor menu)";
+Calendar._TT["NEXT_YEAR"] = "Volgend jaar (indrukken voor menu)";
+Calendar._TT["SEL_DATE"] = "Selecteer datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te verplaatsen";
+Calendar._TT["PART_TODAY"] = " (vandaag)";
+Calendar._TT["MON_FIRST"] = "Toon Maandag eerst";
+Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst";
+Calendar._TT["CLOSE"] = "Sluiten";
+Calendar._TT["TODAY"] = "Vandaag";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd";
+Calendar._TT["TT_DATE_FORMAT"] = "D, M d";
+
+Calendar._TT["WK"] = "wk";
diff --git a/js/jscalendar/lang/calendar-el.js b/js/jscalendar/lang/calendar-el.js
new file mode 100644
index 0000000..6b5e814
--- /dev/null
+++ b/js/jscalendar/lang/calendar-el.js
@@ -0,0 +1,100 @@
+Calendar._DN = new Array
+("ΚυÏιακή",
+ "ΔευτέÏα",
+ "ΤÏίτη",
+ "ΤετάÏτη",
+ "Πέμπτη",
+ "ΠαÏασκευή",
+ "Σάββατο",
+ "ΚυÏιακή");
+
+Calendar._SDN = new Array
+("Κυ",
+ "Δε",
+ "TÏ",
+ "Τε",
+ "Πε",
+ "Πα",
+ "Σα",
+ "Κυ");
+
+Calendar._FD = 1;
+
+Calendar._MN = new Array
+("ΙανουάÏιος",
+ "ΦεβÏουάÏιος",
+ "ΜάÏτιος",
+ "ΑπÏίλιος",
+ "Μάϊος",
+ "ΙοÏνιος",
+ "ΙοÏλιος",
+ "ΑÏγουστος",
+ "ΣεπτέμβÏιος",
+ "ΟκτώβÏιος",
+ "ÎοέμβÏιος",
+ "ΔεκέμβÏιος");
+
+Calendar._SMN = new Array
+("Ιαν",
+ "Φεβ",
+ "ΜαÏ",
+ "ΑπÏ",
+ "Μαι",
+ "Ιουν",
+ "Ιουλ",
+ "Αυγ",
+ "Σεπ",
+ "Οκτ",
+ "Îοε",
+ "Δεκ");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Σχετικά με το ημεÏολόγιο";
+
+Calendar._TT["ABOUT"] =
+"Επιλογέας ημεÏομηνίας/ÏŽÏας σε DHTML\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Για τελευταία έκδοση: http://www.dynarch.com/projects/calendar/\n" +
+"Διανέμεται υπό την GNU LGPL. Βλ. http://gnu.org/licenses/lgpl.html για λεπτομέÏειες." +
+"\n\n" +
+"Επιλογή ημεÏομηνίας:\n" +
+"- ΧÏησιμοποιήστε τα κουμπιά \xab, \xbb για επιλογή έτους\n" +
+"- ΧÏησιμοποιήστε τα κουμπιά " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " για επιλογή μήνα\n" +
+"- ΚÏατήστε το πλήκτÏο του Ï€Î¿Î½Ï„Î¹ÎºÎ¿Ï Ï€Î±Ï„Î·Î¼Î­Î½Î¿ στα παÏαπάνω κουμπιά για ταχÏτεÏη επιλογή.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Επιλογή ÏŽÏας:\n" +
+"- Κάντε κλικ σε ένα από τα μέÏη της ÏŽÏας για αÏξηση\n" +
+"- ή Shift-κλικ για μείωση\n" +
+"- ή κλικ και σÏÏσιμο για ταχÏτεÏη επιλογή.";
+Calendar._TT["TOGGLE"] = "ΜπάÏα Ï€Ïώτης ημέÏας της εβδομάδας";
+Calendar._TT["PREV_YEAR"] = "ΠÏοηγ. έτος (παÏατεταμένα για μενοÏ)";
+Calendar._TT["PREV_MONTH"] = "ΠÏοηγ. μήνας (παÏατεταμένα για μενοÏ)";
+Calendar._TT["GO_TODAY"] = "ΣήμεÏα";
+Calendar._TT["NEXT_MONTH"] = "Επόμενος μήνας (παÏατεταμένα για μενοÏ)";
+Calendar._TT["NEXT_YEAR"] = "Επόμενο έτος (παÏατεταμένα για μενοÏ)";
+Calendar._TT["SEL_DATE"] = "Επιλέξτε ημεÏομηνία";
+Calendar._TT["DRAG_TO_MOVE"] = "ΣÏÏτε για να μετακινήσετε";
+Calendar._TT["PART_TODAY"] = " (σήμεÏα)";
+Calendar._TT["MON_FIRST"] = "Εμφάνιση ΔευτέÏας Ï€Ïώτα";
+Calendar._TT["SUN_FIRST"] = "Εμφάνιση ΚυÏιακής Ï€Ïώτα";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Εμφάνιση %s Ï€Ïώτα";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Κλείσιμο";
+Calendar._TT["TODAY"] = "ΣήμεÏα";
+Calendar._TT["TIME_PART"] = "(Shift-)κλικ ή μετακίνηση για αλλαγή";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y";
+Calendar._TT["TT_DATE_FORMAT"] = "D, d M";
+
+Calendar._TT["WK"] = "εβδ";
+
diff --git a/js/jscalendar/lang/calendar-en.js b/js/jscalendar/lang/calendar-en.js
new file mode 100644
index 0000000..0dbde79
--- /dev/null
+++ b/js/jscalendar/lang/calendar-en.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/js/jscalendar/lang/calendar-es.js b/js/jscalendar/lang/calendar-es.js
new file mode 100644
index 0000000..947610c
--- /dev/null
+++ b/js/jscalendar/lang/calendar-es.js
@@ -0,0 +1,129 @@
+// ** I18N
+
+// Calendar ES (spanish) language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Updater: Servilio Afre Puentes <servilios@yahoo.com>
+// Updated: 2019-03-05
+// Encoding: utf-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Domingo",
+ "Lunes",
+ "Martes",
+ "Miércoles",
+ "Jueves",
+ "Viernes",
+ "Sábado",
+ "Domingo");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Dom",
+ "Lun",
+ "Mar",
+ "Mié",
+ "Jue",
+ "Vie",
+ "Sáb",
+ "Dom");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Enero",
+ "Febrero",
+ "Marzo",
+ "Abril",
+ "Mayo",
+ "Junio",
+ "Julio",
+ "Agosto",
+ "Septiembre",
+ "Octubre",
+ "Noviembre",
+ "Diciembre");
+
+// short month names
+Calendar._SMN = new Array
+("Ene",
+ "Feb",
+ "Mar",
+ "Abr",
+ "May",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dic");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Acerca del calendario";
+
+Calendar._TT["ABOUT"] =
+"Selector DHTML de Fecha/Hora\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Para conseguir la última versión visite: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuido bajo licencia GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para más detalles." +
+"\n\n" +
+"Selección de fecha:\n" +
+"- Use los botones \xab, \xbb para seleccionar el año\n" +
+"- Use los botones " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" +
+"- Mantenga pulsado el ratón en cualquiera de estos botones para una selección rápida.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selección de hora:\n" +
+"- Pulse en cualquiera de las partes de la hora para incrementarla\n" +
+"- o pulse las mayúsculas mientras hace clic para decrementarla\n" +
+"- o haga clic y arrastre el ratón para una selección más rápida.";
+
+Calendar._TT["PREV_YEAR"] = "Año anterior (mantener para menú)";
+Calendar._TT["PREV_MONTH"] = "Mes anterior (mantener para menú)";
+Calendar._TT["GO_TODAY"] = "Ir a hoy";
+Calendar._TT["NEXT_MONTH"] = "Mes siguiente (mantener para menú)";
+Calendar._TT["NEXT_YEAR"] = "Año siguiente (mantener para menú)";
+Calendar._TT["SEL_DATE"] = "Seleccionar fecha";
+Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover";
+Calendar._TT["PART_TODAY"] = " (hoy)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Hacer %s primer día de la semana";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Cerrar";
+Calendar._TT["TODAY"] = "Hoy";
+Calendar._TT["TIME_PART"] = "(Mayúscula-)Clic o arrastre para cambiar valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y";
+
+Calendar._TT["WK"] = "sem";
+Calendar._TT["TIME"] = "Hora:";
diff --git a/js/jscalendar/lang/calendar-fi.js b/js/jscalendar/lang/calendar-fi.js
new file mode 100644
index 0000000..d1163b2
--- /dev/null
+++ b/js/jscalendar/lang/calendar-fi.js
@@ -0,0 +1,102 @@
+// ** I18N
+
+// Calendar FI language (Finnish, Suomi)
+// Author: Jarno Käyhkö, <gambler@phnet.fi>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// full day names
+Calendar._DN = new Array
+("Sunnuntai",
+ "Maanantai",
+ "Tiistai",
+ "Keskiviikko",
+ "Torstai",
+ "Perjantai",
+ "Lauantai",
+ "Sunnuntai");
+
+// short day names
+Calendar._SDN = new Array
+("Su",
+ "Ma",
+ "Ti",
+ "Ke",
+ "To",
+ "Pe",
+ "La",
+ "Su");
+
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Tammikuu",
+ "Helmikuu",
+ "Maaliskuu",
+ "Huhtikuu",
+ "Toukokuu",
+ "Kesäkuu",
+ "Heinäkuu",
+ "Elokuu",
+ "Syyskuu",
+ "Lokakuu",
+ "Marraskuu",
+ "Joulukuu");
+
+// short month names
+Calendar._SMN = new Array
+("Tam",
+ "Hel",
+ "Maa",
+ "Huh",
+ "Tou",
+ "Kes",
+ "Hei",
+ "Elo",
+ "Syy",
+ "Lok",
+ "Mar",
+ "Jou");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Tietoja kalenterista";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Uusin versio osoitteessa: http://www.dynarch.com/projects/calendar/\n" +
+"Julkaistu GNU LGPL lisenssin alaisuudessa. Lisätietoja osoitteessa http://gnu.org/licenses/lgpl.html" +
+"\n\n" +
+"Päivämäärä valinta:\n" +
+"- Käytä \xab, \xbb painikkeita valitaksesi vuosi\n" +
+"- Käytä " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " painikkeita valitaksesi kuukausi\n" +
+"- Pitämällä hiiren painiketta minkä tahansa yllä olevan painikkeen kohdalla, saat näkyviin valikon nopeampaan siirtymiseen.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Ajan valinta:\n" +
+"- Klikkaa kellonajan numeroita lisätäksesi aikaa\n" +
+"- tai pitämällä Shift-näppäintä pohjassa saat aikaa taaksepäin\n" +
+"- tai klikkaa ja pidä hiiren painike pohjassa sekä liikuta hiirtä muuttaaksesi aikaa nopeasti eteen- ja taaksepäin.";
+
+Calendar._TT["PREV_YEAR"] = "Edell. vuosi (paina hetki, näet valikon)";
+Calendar._TT["PREV_MONTH"] = "Edell. kuukausi (paina hetki, näet valikon)";
+Calendar._TT["GO_TODAY"] = "Siirry tähän päivään";
+Calendar._TT["NEXT_MONTH"] = "Seur. kuukausi (paina hetki, näet valikon)";
+Calendar._TT["NEXT_YEAR"] = "Seur. vuosi (paina hetki, näet valikon)";
+Calendar._TT["SEL_DATE"] = "Valitse päivämäärä";
+Calendar._TT["DRAG_TO_MOVE"] = "Siirrä kalenterin paikkaa";
+Calendar._TT["PART_TODAY"] = " (tänään)";
+Calendar._TT["MON_FIRST"] = "Näytä maanantai ensimmäisenä";
+Calendar._TT["SUN_FIRST"] = "Näytä sunnuntai ensimmäisenä";
+Calendar._TT["CLOSE"] = "Sulje";
+Calendar._TT["TODAY"] = "Tänään";
+Calendar._TT["TIME_PART"] = "(Shift-) Klikkaa tai liikuta muuttaaksesi aikaa";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%d.%m.%Y";
+
+Calendar._TT["WK"] = "Vko";
+Calendar._TT["WEEKEND"] = "0,6";
+Calendar._TT["DAY_FIRST"] = "Näytä %s ensimmäisenä";
diff --git a/js/jscalendar/lang/calendar-fr.js b/js/jscalendar/lang/calendar-fr.js
new file mode 100644
index 0000000..9bc7e08
--- /dev/null
+++ b/js/jscalendar/lang/calendar-fr.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar FR language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// Translator: David Duret, <pilgrim@mala-template.net> from previous french version
+
+// full day names
+Calendar._DN = new Array
+("Dimanche",
+ "Lundi",
+ "Mardi",
+ "Mercredi",
+ "Jeudi",
+ "Vendredi",
+ "Samedi",
+ "Dimanche");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Dim",
+ "Lun",
+ "Mar",
+ "Mar",
+ "Jeu",
+ "Ven",
+ "Sam",
+ "Dim");
+
+// full month names
+Calendar._MN = new Array
+("Janvier",
+ "Février",
+ "Mars",
+ "Avril",
+ "Mai",
+ "Juin",
+ "Juillet",
+ "Août",
+ "Septembre",
+ "Octobre",
+ "Novembre",
+ "Décembre");
+
+Calendar._FD = 1;
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Fev",
+ "Mar",
+ "Avr",
+ "Mai",
+ "Juin",
+ "Juil",
+ "Aout",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "A propos du calendrier";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Heure Selecteur\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Pour la derniere version visitez : http://www.dynarch.com/projects/calendar/\n" +
+"Distribué par GNU LGPL. Voir http://gnu.org/licenses/lgpl.html pour les details." +
+"\n\n" +
+"Selection de la date :\n" +
+"- Utiliser les bouttons \xab, \xbb pour selectionner l\'annee\n" +
+"- Utiliser les bouttons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pour selectionner les mois\n" +
+"- Garder la souris sur n'importe quels boutons pour une selection plus rapide";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selection de l\'heure :\n" +
+"- Cliquer sur heures ou minutes pour incrementer\n" +
+"- ou Maj-clic pour decrementer\n" +
+"- ou clic et glisser-deplacer pour une selection plus rapide";
+
+Calendar._TT["PREV_YEAR"] = "Année préc. (maintenir pour menu)";
+Calendar._TT["PREV_MONTH"] = "Mois préc. (maintenir pour menu)";
+Calendar._TT["GO_TODAY"] = "Atteindre la date du jour";
+Calendar._TT["NEXT_MONTH"] = "Mois suiv. (maintenir pour menu)";
+Calendar._TT["NEXT_YEAR"] = "Année suiv. (maintenir pour menu)";
+Calendar._TT["SEL_DATE"] = "Sélectionner une date";
+Calendar._TT["DRAG_TO_MOVE"] = "Déplacer";
+Calendar._TT["PART_TODAY"] = " (Aujourd'hui)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Afficher %s en premier";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Fermer";
+Calendar._TT["TODAY"] = "Aujourd'hui";
+Calendar._TT["TIME_PART"] = "(Maj-)Clic ou glisser pour modifier la valeur";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "Sem.";
+Calendar._TT["TIME"] = "Heure :";
diff --git a/js/jscalendar/lang/calendar-he.js b/js/jscalendar/lang/calendar-he.js
new file mode 100644
index 0000000..f4c4311
--- /dev/null
+++ b/js/jscalendar/lang/calendar-he.js
@@ -0,0 +1,122 @@
+// ** I18N
+
+// Calendar HE language
+// Author: Idan Sofer, <idan@idanso.dyndns.org>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("ר×שון",
+ "שני",
+ "שלישי",
+ "רביעי",
+ "חמישי",
+ "שישי",
+ "שבת",
+ "ר×שון");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("×",
+ "ב",
+ "×’",
+ "ד",
+ "×”",
+ "ו",
+ "ש",
+ "×");
+
+// full month names
+Calendar._MN = new Array
+("ינו×ר",
+ "פברו×ר",
+ "מרץ",
+ "×פריל",
+ "מ××™",
+ "יוני",
+ "יולי",
+ "×וגוסט",
+ "ספטמבר",
+ "×וקטובר",
+ "נובמבר",
+ "דצמבר");
+
+// short month names
+Calendar._SMN = new Array
+("×™× ×",
+ "פבר",
+ "מרץ",
+ "×פר",
+ "מ××™",
+ "יונ",
+ "יול",
+ "×וג",
+ "ספט",
+ "×וק",
+ "נוב",
+ "דצמ");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "×ודות השנתון";
+
+Calendar._TT["ABOUT"] =
+"בחרן ת×ריך/שעה DHTML\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"×”×’×™×¨×¡× ×”×חרונה זמינה ב: http://www.dynarch.com/projects/calendar/\n" +
+"מופץ תחת זיכיון ×” GNU LGPL. עיין ב http://gnu.org/licenses/lgpl.html ×œ×¤×¨×˜×™× × ×•×¡×¤×™×." +
+"\n\n" + "בחירת ת×ריך:\n" +
+"- השתמש ×‘×›×¤×ª×•×¨×™× \xab, \xbb לבחירת שנה\n" +
+"- השתמש ×‘×›×¤×ª×•×¨×™× " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " לבחירת חודש\n" +
+"- החזק העכבר לחוץ מעל ×”×›×¤×ª×•×¨×™× ×”×ž×•×–×›×¨×™× ×œ×¢×™×œ לבחירה מהירה יותר.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"בחירת זמן:\n" +
+"- לחץ על כל ×חד מחלקי הזמן כדי להוסיף\n" +
+"- ×ו shift בשילוב ×¢× ×œ×—×™×¦×” כדי להחסיר\n" +
+"- ×ו לחץ וגרור לפעולה מהירה יותר.";
+
+Calendar._TT["PREV_YEAR"] = "שנה קודמת - החזק לקבלת תפריט";
+Calendar._TT["PREV_MONTH"] = "חודש ×§×•×“× - החזק לקבלת תפריט";
+Calendar._TT["GO_TODAY"] = "עבור להיו×";
+Calendar._TT["NEXT_MONTH"] = "חודש ×”×‘× - החזק לתפריט";
+Calendar._TT["NEXT_YEAR"] = "שנה הב××” - החזק לתפריט";
+Calendar._TT["SEL_DATE"] = "בחר ת×ריך";
+Calendar._TT["DRAG_TO_MOVE"] = "גרור להזזה";
+Calendar._TT["PART_TODAY"] = " )היו×(";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "הצג %s קוד×";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "6";
+
+Calendar._TT["CLOSE"] = "סגור";
+Calendar._TT["TODAY"] = "היו×";
+Calendar._TT["TIME_PART"] = "(שיפט-)לחץ וגרור כדי לשנות ערך";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "שעה::";
diff --git a/js/jscalendar/lang/calendar-hr.js b/js/jscalendar/lang/calendar-hr.js
new file mode 100644
index 0000000..bcc026b
--- /dev/null
+++ b/js/jscalendar/lang/calendar-hr.js
@@ -0,0 +1,53 @@
+/* Croatian language file for the DHTML Calendar version 0.9.2
+* Author Krunoslav Zubrinic <krunoslav.zubrinic@vip.hr>, June 2003.
+* Feel free to use this script under the terms of the GNU Lesser General
+* Public License, as long as you do not remove or alter this notice.
+*/
+Calendar._DN = new Array
+("Nedjelja",
+ "Ponedjeljak",
+ "Utorak",
+ "Srijeda",
+ "ÄŒetvrtak",
+ "Petak",
+ "Subota",
+ "Nedjelja");
+Calendar._MN = new Array
+("SijeÄanj",
+ "VeljaÄa",
+ "Ožujak",
+ "Travanj",
+ "Svibanj",
+ "Lipanj",
+ "Srpanj",
+ "Kolovoz",
+ "Rujan",
+ "Listopad",
+ "Studeni",
+ "Prosinac");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["TOGGLE"] = "Promjeni dan s kojim poÄinje tjedan";
+Calendar._TT["PREV_YEAR"] = "Prethodna godina (dugi pritisak za meni)";
+Calendar._TT["PREV_MONTH"] = "Prethodni mjesec (dugi pritisak za meni)";
+Calendar._TT["GO_TODAY"] = "Idi na tekući dan";
+Calendar._TT["NEXT_MONTH"] = "Slijedeći mjesec (dugi pritisak za meni)";
+Calendar._TT["NEXT_YEAR"] = "Slijedeća godina (dugi pritisak za meni)";
+Calendar._TT["SEL_DATE"] = "Izaberite datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Pritisni i povuci za promjenu pozicije";
+Calendar._TT["PART_TODAY"] = " (today)";
+Calendar._TT["MON_FIRST"] = "Prikaži ponedjeljak kao prvi dan";
+Calendar._TT["SUN_FIRST"] = "Prikaži nedjelju kao prvi dan";
+Calendar._TT["CLOSE"] = "Zatvori";
+Calendar._TT["TODAY"] = "Danas";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y";
+Calendar._TT["TT_DATE_FORMAT"] = "DD, dd.mm.y";
+
+Calendar._TT["WK"] = "Tje";
+
+// First day of the week. 0 means display Sunday first, 1 means display
+// Monday first, etc.
+Calendar._FD = 1;
diff --git a/js/jscalendar/lang/calendar-hu.js b/js/jscalendar/lang/calendar-hu.js
new file mode 100644
index 0000000..7176260
--- /dev/null
+++ b/js/jscalendar/lang/calendar-hu.js
@@ -0,0 +1,126 @@
+// ** I18N
+
+// Calendar HU language
+// Author: ???
+// Modifier: KARASZI Istvan, <jscalendar@spam.raszi.hu>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Vasárnap",
+ "Hétfõ",
+ "Kedd",
+ "Szerda",
+ "Csütörtök",
+ "Péntek",
+ "Szombat",
+ "Vasárnap");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("v",
+ "h",
+ "k",
+ "sze",
+ "cs",
+ "p",
+ "szo",
+ "v");
+
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("január",
+ "február",
+ "március",
+ "április",
+ "május",
+ "június",
+ "július",
+ "augusztus",
+ "szeptember",
+ "október",
+ "november",
+ "december");
+
+// short month names
+Calendar._SMN = new Array
+("jan",
+ "feb",
+ "már",
+ "ápr",
+ "máj",
+ "jún",
+ "júl",
+ "aug",
+ "sze",
+ "okt",
+ "nov",
+ "dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "A kalendáriumról";
+
+Calendar._TT["ABOUT"] =
+"DHTML dátum/idõ kiválasztó\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"a legfrissebb verzió megtalálható: http://www.dynarch.com/projects/calendar/\n" +
+"GNU LGPL alatt terjesztve. Lásd a http://gnu.org/licenses/lgpl.html oldalt a részletekhez." +
+"\n\n" +
+"Dátum választás:\n" +
+"- használja a \xab, \xbb gombokat az év kiválasztásához\n" +
+"- használja a " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gombokat a hónap kiválasztásához\n" +
+"- tartsa lenyomva az egérgombot a gyors választáshoz.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Idõ választás:\n" +
+"- kattintva növelheti az idõt\n" +
+"- shift-tel kattintva csökkentheti\n" +
+"- lenyomva tartva és húzva gyorsabban kiválaszthatja.";
+
+Calendar._TT["PREV_YEAR"] = "Elõzõ év (tartsa nyomva a menühöz)";
+Calendar._TT["PREV_MONTH"] = "Elõzõ hónap (tartsa nyomva a menühöz)";
+Calendar._TT["GO_TODAY"] = "Mai napra ugrás";
+Calendar._TT["NEXT_MONTH"] = "Köv. hónap (tartsa nyomva a menühöz)";
+Calendar._TT["NEXT_YEAR"] = "Köv. év (tartsa nyomva a menühöz)";
+Calendar._TT["SEL_DATE"] = "Válasszon dátumot";
+Calendar._TT["DRAG_TO_MOVE"] = "Húzza a mozgatáshoz";
+Calendar._TT["PART_TODAY"] = " (ma)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "%s legyen a hét elsõ napja";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Bezár";
+Calendar._TT["TODAY"] = "Ma";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikk vagy húzás az érték változtatásához";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%b %e, %a";
+
+Calendar._TT["WK"] = "hét";
+Calendar._TT["TIME"] = "idõ:";
diff --git a/js/jscalendar/lang/calendar-it.js b/js/jscalendar/lang/calendar-it.js
new file mode 100644
index 0000000..1064d4d
--- /dev/null
+++ b/js/jscalendar/lang/calendar-it.js
@@ -0,0 +1,126 @@
+// ** I18N
+
+// Calendar IT language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Translator: Fabio Di Bernardini, <altraqua@email.it>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Domenica",
+ "Lunedì",
+ "Martedì",
+ "Mercoledì",
+ "Giovedì",
+ "Venerdì",
+ "Sabato",
+ "Domenica");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Dom",
+ "Lun",
+ "Mar",
+ "Mer",
+ "Gio",
+ "Ven",
+ "Sab",
+ "Dom");
+
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Gennaio",
+ "Febbraio",
+ "Marzo",
+ "Aprile",
+ "Maggio",
+ "Giugno",
+ "Luglio",
+ "Augosto",
+ "Settembre",
+ "Ottobre",
+ "Novembre",
+ "Dicembre");
+
+// short month names
+Calendar._SMN = new Array
+("Gen",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mag",
+ "Giu",
+ "Lug",
+ "Ago",
+ "Set",
+ "Ott",
+ "Nov",
+ "Dic");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Informazioni sul calendario";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Per gli aggiornamenti: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuito sotto licenza GNU LGPL. Vedi http://gnu.org/licenses/lgpl.html per i dettagli." +
+"\n\n" +
+"Selezione data:\n" +
+"- Usa \xab, \xbb per selezionare l'anno\n" +
+"- Usa " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per i mesi\n" +
+"- Tieni premuto a lungo il mouse per accedere alle funzioni di selezione veloce.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selezione orario:\n" +
+"- Clicca sul numero per incrementarlo\n" +
+"- o Shift+click per decrementarlo\n" +
+"- o click e sinistra o destra per variarlo.";
+
+Calendar._TT["PREV_YEAR"] = "Anno prec.(clicca a lungo per il menù)";
+Calendar._TT["PREV_MONTH"] = "Mese prec. (clicca a lungo per il menù)";
+Calendar._TT["GO_TODAY"] = "Oggi";
+Calendar._TT["NEXT_MONTH"] = "Pross. mese (clicca a lungo per il menù)";
+Calendar._TT["NEXT_YEAR"] = "Pross. anno (clicca a lungo per il menù)";
+Calendar._TT["SEL_DATE"] = "Seleziona data";
+Calendar._TT["DRAG_TO_MOVE"] = "Trascina per spostarlo";
+Calendar._TT["PART_TODAY"] = " (oggi)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Mostra prima %s";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Chiudi";
+Calendar._TT["TODAY"] = "Oggi";
+Calendar._TT["TIME_PART"] = "(Shift-)Click o trascina per cambiare il valore";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a:%b:%e";
+
+Calendar._TT["WK"] = "set";
+Calendar._TT["TIME"] = "Ora:";
diff --git a/js/jscalendar/lang/calendar-ja.js b/js/jscalendar/lang/calendar-ja.js
new file mode 100644
index 0000000..892c912
--- /dev/null
+++ b/js/jscalendar/lang/calendar-ja.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar JA language (Japanese)
+// Author: KAWASHIMA Takahiro
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("日曜日",
+ "月曜日",
+ "ç«æ›œæ—¥",
+ "水曜日",
+ "木曜日",
+ "金曜日",
+ "土曜日",
+ "日曜日");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("æ—¥",
+ "月",
+ "ç«",
+ "æ°´",
+ "木",
+ "金",
+ "土",
+ "æ—¥");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月");
+
+// short month names
+Calendar._SMN = new Array
+("1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "ã“ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã¤ã„ã¦";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"日付é¸æŠž:\n" +
+"- å¹´ã®é¸æŠžã¯ \xab, \xbb ボタン\n" +
+"- 月ã®é¸æŠžã¯ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ボタン\n" +
+"- 速ãé¸æŠžã™ã‚‹ã«ã¯ä¸Šè¨˜ãƒœã‚¿ãƒ³ã‚’長押ã—";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"時刻é¸æŠž:\n" +
+"- 時刻を進ã‚ã‚‹ã«ã¯æ™‚刻をクリック\n" +
+"- 戻ã™ã«ã¯Shiftクリック\n" +
+"- 速ãé¸æŠžã™ã‚‹ã«ã¯ã‚¯ãƒªãƒƒã‚¯ã—ã¦ãƒ‰ãƒ©ãƒƒã‚°";
+
+Calendar._TT["PREV_YEAR"] = "å‰ã®å¹´ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)";
+Calendar._TT["PREV_MONTH"] = "å‰ã®æœˆ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)";
+Calendar._TT["GO_TODAY"] = "今日ã«ç§»å‹•";
+Calendar._TT["NEXT_MONTH"] = "次ã®æœˆ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)";
+Calendar._TT["NEXT_YEAR"] = "次ã®å¹´ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)";
+Calendar._TT["SEL_DATE"] = "日付é¸æŠž";
+Calendar._TT["DRAG_TO_MOVE"] = "ドラッグã§ç§»å‹•";
+Calendar._TT["PART_TODAY"] = " (今日)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "%sを最åˆã«è¡¨ç¤º";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "é–‰ã˜ã‚‹";
+Calendar._TT["TODAY"] = "今日";
+Calendar._TT["TIME_PART"] = "変更ã¯(Shift-)クリックã¾ãŸã¯ãƒ‰ãƒ©ãƒƒã‚°";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y/%m/%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%Yå¹´%b%eæ—¥(%a)";
+
+Calendar._TT["WK"] = "週";
+Calendar._TT["TIME"] = "時刻:";
diff --git a/js/jscalendar/lang/calendar-jp.js b/js/jscalendar/lang/calendar-jp.js
new file mode 100644
index 0000000..3bca7eb
--- /dev/null
+++ b/js/jscalendar/lang/calendar-jp.js
@@ -0,0 +1,45 @@
+// ** I18N
+Calendar._DN = new Array
+("“ú",
+ "ŒŽ",
+ "‰Î",
+ "…",
+ "–Ø",
+ "‹à",
+ "“y",
+ "“ú");
+Calendar._MN = new Array
+("1ŒŽ",
+ "2ŒŽ",
+ "3ŒŽ",
+ "4ŒŽ",
+ "5ŒŽ",
+ "6ŒŽ",
+ "7ŒŽ",
+ "8ŒŽ",
+ "9ŒŽ",
+ "10ŒŽ",
+ "11ŒŽ",
+ "12ŒŽ");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["TOGGLE"] = "T‚Ìʼn‚Ì—j“ú‚ðØ‚è‘Ö‚¦";
+Calendar._TT["PREV_YEAR"] = "‘O”N";
+Calendar._TT["PREV_MONTH"] = "‘OŒŽ";
+Calendar._TT["GO_TODAY"] = "¡“ú";
+Calendar._TT["NEXT_MONTH"] = "—‚ŒŽ";
+Calendar._TT["NEXT_YEAR"] = "—‚”N";
+Calendar._TT["SEL_DATE"] = "“ú•t‘I‘ð";
+Calendar._TT["DRAG_TO_MOVE"] = "ƒEƒBƒ“ƒhƒE‚̈ړ®";
+Calendar._TT["PART_TODAY"] = " (¡“ú)";
+Calendar._TT["MON_FIRST"] = "ŒŽ—j“ú‚ð擪‚É";
+Calendar._TT["SUN_FIRST"] = "“ú—j“ú‚ð擪‚É";
+Calendar._TT["CLOSE"] = "•Â‚¶‚é";
+Calendar._TT["TODAY"] = "¡“ú";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd";
+Calendar._TT["TT_DATE_FORMAT"] = "%mŒŽ %d“ú (%a)";
+
+Calendar._TT["WK"] = "T";
diff --git a/js/jscalendar/lang/calendar-ko.js b/js/jscalendar/lang/calendar-ko.js
new file mode 100644
index 0000000..100b978
--- /dev/null
+++ b/js/jscalendar/lang/calendar-ko.js
@@ -0,0 +1,119 @@
+// ** I18N
+
+// Calendar KO language
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Translation: Yourim Yi <yyi@yourim.net>
+// Encoding: UTF-8
+// lang : ko
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("ì¼ìš”ì¼",
+ "월요ì¼",
+ "화요ì¼",
+ "수요ì¼",
+ "목요ì¼",
+ "금요ì¼",
+ "토요ì¼",
+ "ì¼ìš”ì¼");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("ì¼",
+ "ì›”",
+ "í™”",
+ "수",
+ "목",
+ "금",
+ "토",
+ "ì¼");
+
+// full month names
+Calendar._MN = new Array
+("1ì›”",
+ "2ì›”",
+ "3ì›”",
+ "4ì›”",
+ "5ì›”",
+ "6ì›”",
+ "7ì›”",
+ "8ì›”",
+ "9ì›”",
+ "10ì›”",
+ "11ì›”",
+ "12ì›”");
+
+// short month names
+Calendar._SMN = new Array
+("1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "11",
+ "12");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "calendar ì— ëŒ€í•´ì„œ";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"\n"+
+"최신 ë²„ì „ì„ ë°›ìœ¼ì‹œë ¤ë©´ http://www.dynarch.com/projects/calendar/ ì— ë°©ë¬¸í•˜ì„¸ìš”\n" +
+"\n"+
+"GNU LGPL ë¼ì´ì„¼ìŠ¤ë¡œ ë°°í¬ë©ë‹ˆë‹¤. \n"+
+"ë¼ì´ì„¼ìŠ¤ì— 대한 ìžì„¸í•œ ë‚´ìš©ì€ http://gnu.org/licenses/lgpl.html ì„ ì½ìœ¼ì„¸ìš”." +
+"\n\n" +
+"날짜 ì„ íƒ:\n" +
+"- ì—°ë„를 ì„ íƒí•˜ë ¤ë©´ \xab, \xbb ë²„íŠ¼ì„ ì‚¬ìš©í•©ë‹ˆë‹¤\n" +
+"- ë‹¬ì„ ì„ íƒí•˜ë ¤ë©´ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ë²„íŠ¼ì„ ëˆ„ë¥´ì„¸ìš”\n" +
+"- ê³„ì† ëˆ„ë¥´ê³  있으면 위 ê°’ë“¤ì„ ë¹ ë¥´ê²Œ ì„ íƒí•˜ì‹¤ 수 있습니다.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"시간 ì„ íƒ:\n" +
+"- 마우스로 누르면 ì‹œê°„ì´ ì¦ê°€í•©ë‹ˆë‹¤\n" +
+"- Shift 키와 함께 누르면 ê°ì†Œí•©ë‹ˆë‹¤\n" +
+"- 누른 ìƒíƒœì—ì„œ 마우스를 움ì§ì´ë©´ 좀 ë” ë¹ ë¥´ê²Œ ê°’ì´ ë³€í•©ë‹ˆë‹¤.\n";
+
+Calendar._TT["PREV_YEAR"] = "지난 í•´ (길게 누르면 목ë¡)";
+Calendar._TT["PREV_MONTH"] = "지난 달 (길게 누르면 목ë¡)";
+Calendar._TT["GO_TODAY"] = "오늘 날짜로";
+Calendar._TT["NEXT_MONTH"] = "ë‹¤ìŒ ë‹¬ (길게 누르면 목ë¡)";
+Calendar._TT["NEXT_YEAR"] = "ë‹¤ìŒ í•´ (길게 누르면 목ë¡)";
+Calendar._TT["SEL_DATE"] = "날짜를 ì„ íƒí•˜ì„¸ìš”";
+Calendar._TT["DRAG_TO_MOVE"] = "마우스 드래그로 ì´ë™ 하세요";
+Calendar._TT["PART_TODAY"] = " (오늘)";
+Calendar._TT["MON_FIRST"] = "월요ì¼ì„ í•œ ì£¼ì˜ ì‹œìž‘ ìš”ì¼ë¡œ";
+Calendar._TT["SUN_FIRST"] = "ì¼ìš”ì¼ì„ í•œ ì£¼ì˜ ì‹œìž‘ ìš”ì¼ë¡œ";
+Calendar._TT["CLOSE"] = "닫기";
+Calendar._TT["TODAY"] = "오늘";
+Calendar._TT["TIME_PART"] = "(Shift-)í´ë¦­ ë˜ëŠ” 드래그 하세요";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%b/%e [%a]";
+
+Calendar._TT["WK"] = "주";
diff --git a/js/jscalendar/lang/calendar-lt.js b/js/jscalendar/lang/calendar-lt.js
new file mode 100644
index 0000000..d39653b
--- /dev/null
+++ b/js/jscalendar/lang/calendar-lt.js
@@ -0,0 +1,114 @@
+// ** I18N
+
+// Calendar LT language
+// Author: Martynas Majeris, <martynas@solmetra.lt>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sekmadienis",
+ "Pirmadienis",
+ "Antradienis",
+ "TreÄiadienis",
+ "Ketvirtadienis",
+ "Pentadienis",
+ "Šeštadienis",
+ "Sekmadienis");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sek",
+ "Pir",
+ "Ant",
+ "Tre",
+ "Ket",
+ "Pen",
+ "Šeš",
+ "Sek");
+
+// full month names
+Calendar._MN = new Array
+("Sausis",
+ "Vasaris",
+ "Kovas",
+ "Balandis",
+ "Gegužė",
+ "Birželis",
+ "Liepa",
+ "Rugpjūtis",
+ "RugsÄ—jis",
+ "Spalis",
+ "Lapkritis",
+ "Gruodis");
+
+// short month names
+Calendar._SMN = new Array
+("Sau",
+ "Vas",
+ "Kov",
+ "Bal",
+ "Geg",
+ "Bir",
+ "Lie",
+ "Rgp",
+ "Rgs",
+ "Spa",
+ "Lap",
+ "Gru");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Apie kalendorių";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"NaujausiÄ… versijÄ… rasite: http://www.dynarch.com/projects/calendar/\n" +
+"Platinamas pagal GNU LGPL licencijÄ…. Aplankykite http://gnu.org/licenses/lgpl.html" +
+"\n\n" +
+"Datos pasirinkimas:\n" +
+"- Metų pasirinkimas: \xab, \xbb\n" +
+"- MÄ—nesio pasirinkimas: " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" +
+"- Nuspauskite ir laikykite pelės klavišą greitesniam pasirinkimui.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Laiko pasirinkimas:\n" +
+"- Spustelkite ant valandų arba minuÄių - skaiÄius padidÄ—s vienetu.\n" +
+"- Jei spausite kartu su Shift, skaiÄius sumažės.\n" +
+"- Greitam pasirinkimui spustelkite ir pajudinkite pelÄ™.";
+
+Calendar._TT["PREV_YEAR"] = "Ankstesni metai (laikykite, jei norite meniu)";
+Calendar._TT["PREV_MONTH"] = "Ankstesnis mÄ—nuo (laikykite, jei norite meniu)";
+Calendar._TT["GO_TODAY"] = "Pasirinkti Å¡iandienÄ…";
+Calendar._TT["NEXT_MONTH"] = "Kitas mÄ—nuo (laikykite, jei norite meniu)";
+Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikykite, jei norite meniu)";
+Calendar._TT["SEL_DATE"] = "Pasirinkite datÄ…";
+Calendar._TT["DRAG_TO_MOVE"] = "Tempkite";
+Calendar._TT["PART_TODAY"] = " (Å¡iandien)";
+Calendar._TT["MON_FIRST"] = "Pirma savaitÄ—s diena - pirmadienis";
+Calendar._TT["SUN_FIRST"] = "Pirma savaitÄ—s diena - sekmadienis";
+Calendar._TT["CLOSE"] = "Uždaryti";
+Calendar._TT["TODAY"] = "Å iandien";
+Calendar._TT["TIME_PART"] = "Spustelkite arba tempkite jei norite pakeisti";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %Y-%m-%d";
+
+Calendar._TT["WK"] = "sav";
diff --git a/js/jscalendar/lang/calendar-lv.js b/js/jscalendar/lang/calendar-lv.js
new file mode 100644
index 0000000..407699d
--- /dev/null
+++ b/js/jscalendar/lang/calendar-lv.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar LV language
+// Author: Juris Valdovskis, <juris@dc.lv>
+// Encoding: cp1257
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Svçtdiena",
+ "Pirmdiena",
+ "Otrdiena",
+ "Treðdiena",
+ "Ceturdiena",
+ "Piektdiena",
+ "Sestdiena",
+ "Svçtdiena");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sv",
+ "Pr",
+ "Ot",
+ "Tr",
+ "Ce",
+ "Pk",
+ "Se",
+ "Sv");
+
+// full month names
+Calendar._MN = new Array
+("Janvâris",
+ "Februâris",
+ "Marts",
+ "Aprîlis",
+ "Maijs",
+ "Jûnijs",
+ "Jûlijs",
+ "Augusts",
+ "Septembris",
+ "Oktobris",
+ "Novembris",
+ "Decembris");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mai",
+ "Jûn",
+ "Jûl",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Par kalendâru";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Datuma izvçle:\n" +
+"- Izmanto \xab, \xbb pogas, lai izvçlçtos gadu\n" +
+"- Izmanto " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "pogas, lai izvçlçtos mçnesi\n" +
+"- Turi nospiestu peles pogu uz jebkuru no augstâk minçtajâm pogâm, lai paâtrinâtu izvçli.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Laika izvçle:\n" +
+"- Uzklikðíini uz jebkuru no laika daïâm, lai palielinâtu to\n" +
+"- vai Shift-klikðíis, lai samazinâtu to\n" +
+"- vai noklikðíini un velc uz attiecîgo virzienu lai mainîtu âtrâk.";
+
+Calendar._TT["PREV_YEAR"] = "Iepr. gads (turi izvçlnei)";
+Calendar._TT["PREV_MONTH"] = "Iepr. mçnesis (turi izvçlnei)";
+Calendar._TT["GO_TODAY"] = "Ðodien";
+Calendar._TT["NEXT_MONTH"] = "Nâkoðais mçnesis (turi izvçlnei)";
+Calendar._TT["NEXT_YEAR"] = "Nâkoðais gads (turi izvçlnei)";
+Calendar._TT["SEL_DATE"] = "Izvçlies datumu";
+Calendar._TT["DRAG_TO_MOVE"] = "Velc, lai pârvietotu";
+Calendar._TT["PART_TODAY"] = " (ðodien)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Attçlot %s kâ pirmo";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "1,7";
+
+Calendar._TT["CLOSE"] = "Aizvçrt";
+Calendar._TT["TODAY"] = "Ðodien";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikðíis vai pârvieto, lai mainîtu";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Laiks:";
diff --git a/js/jscalendar/lang/calendar-mk.js b/js/jscalendar/lang/calendar-mk.js
new file mode 100644
index 0000000..bef512b
--- /dev/null
+++ b/js/jscalendar/lang/calendar-mk.js
@@ -0,0 +1,119 @@
+// Calendar MK Macedonian language
+// Distributed under the same terms as the calendar itself.
+
+// full day names
+Calendar._DN = new Array
+("недела",
+ "понеделник",
+ "вторник",
+ "Ñреда",
+ "четврток",
+ "петок",
+ "Ñабота",
+ "недела");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("нед",
+ "пон",
+ "вт",
+ "Ñре",
+ "чет",
+ "пет",
+ "Ñаб",
+ "нед");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("јануари",
+ "февруари",
+ "март",
+ "април",
+ "мај",
+ "јуни",
+ "јули",
+ "авгуÑÑ‚",
+ "Ñептември",
+ "октомври",
+ "ноември",
+ "декември");
+
+// short month names
+Calendar._SMN = new Array
+("јан",
+ "фев",
+ "мар",
+ "апр",
+ "мај",
+ "јун",
+ "јул",
+ "авг",
+ "Ñепт",
+ "окт",
+ "ноем",
+ "декем");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "денеÑка";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%A - %e %B %Y";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time";
diff --git a/js/jscalendar/lang/calendar-nl.js b/js/jscalendar/lang/calendar-nl.js
new file mode 100644
index 0000000..dd71f0f
--- /dev/null
+++ b/js/jscalendar/lang/calendar-nl.js
@@ -0,0 +1,75 @@
+// ** I18N
+Calendar._DN = new Array
+("Zondag",
+ "Maandag",
+ "Dinsdag",
+ "Woensdag",
+ "Donderdag",
+ "Vrijdag",
+ "Zaterdag",
+ "Zondag");
+
+Calendar._SDN_len = 2;
+
+Calendar._FD = 1;
+
+Calendar._MN = new Array
+("Januari",
+ "Februari",
+ "Maart",
+ "April",
+ "Mei",
+ "Juni",
+ "Juli",
+ "Augustus",
+ "September",
+ "Oktober",
+ "November",
+ "December");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Info";
+
+Calendar._TT["ABOUT"] =
+"DHTML Datum/Tijd Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" +
+"Ga voor de meest recente versie naar: http://www.dynarch.com/projects/calendar/\n" +
+"Verspreid onder de GNU LGPL. Zie http://gnu.org/licenses/lgpl.html voor details." +
+"\n\n" +
+"Datum selectie:\n" +
+"- Gebruik de \xab \xbb knoppen om een jaar te selecteren\n" +
+"- Gebruik de " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knoppen om een maand te selecteren\n" +
+"- Houd de muis ingedrukt op de genoemde knoppen voor een snellere selectie.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Tijd selectie:\n" +
+"- Klik op een willekeurig onderdeel van het tijd gedeelte om het te verhogen\n" +
+"- of Shift-klik om het te verlagen\n" +
+"- of klik en sleep voor een snellere selectie.";
+
+//Calendar._TT["TOGGLE"] = "Selecteer de eerste week-dag";
+Calendar._TT["PREV_YEAR"] = "Vorig jaar (ingedrukt voor menu)";
+Calendar._TT["PREV_MONTH"] = "Vorige maand (ingedrukt voor menu)";
+Calendar._TT["GO_TODAY"] = "Ga naar Vandaag";
+Calendar._TT["NEXT_MONTH"] = "Volgende maand (ingedrukt voor menu)";
+Calendar._TT["NEXT_YEAR"] = "Volgend jaar (ingedrukt voor menu)";
+Calendar._TT["SEL_DATE"] = "Selecteer datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Klik en sleep om te verplaatsen";
+Calendar._TT["PART_TODAY"] = " (vandaag)";
+//Calendar._TT["MON_FIRST"] = "Toon Maandag eerst";
+//Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst";
+
+Calendar._TT["DAY_FIRST"] = "Toon %s eerst";
+
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Sluiten";
+Calendar._TT["TODAY"] = "(vandaag)";
+Calendar._TT["TIME_PART"] = "(Shift-)Klik of sleep om de waarde te veranderen";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b %Y";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Tijd:";
diff --git a/js/jscalendar/lang/calendar-no.js b/js/jscalendar/lang/calendar-no.js
new file mode 100644
index 0000000..9ef42d8
--- /dev/null
+++ b/js/jscalendar/lang/calendar-no.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar NO language
+// Author: Daniel Holmen, <daniel.holmen@ciber.no>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Søndag",
+ "Mandag",
+ "Tirsdag",
+ "Onsdag",
+ "Torsdag",
+ "Fredag",
+ "Lørdag",
+ "Søndag");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Søn",
+ "Man",
+ "Tir",
+ "Ons",
+ "Tor",
+ "Fre",
+ "Lør",
+ "Søn");
+
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Januar",
+ "Februar",
+ "Mars",
+ "April",
+ "Mai",
+ "Juni",
+ "Juli",
+ "August",
+ "September",
+ "Oktober",
+ "November",
+ "Desember");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Des");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Om kalenderen";
+
+Calendar._TT["ABOUT"] =
+"DHTML Dato-/Tidsvelger\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For nyeste versjon, gå til: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuert under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detaljer." +
+"\n\n" +
+"Datovalg:\n" +
+"- Bruk knappene \xab og \xbb for å velge år\n" +
+"- Bruk knappene " + String.fromCharCode(0x2039) + " og " + String.fromCharCode(0x203a) + " for å velge måned\n" +
+"- Hold inne musknappen eller knappene over for raskere valg.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Tidsvalg:\n" +
+"- Klikk på en av tidsdelene for å øke den\n" +
+"- eller Shift-klikk for å senke verdien\n" +
+"- eller klikk-og-dra for raskere valg..";
+
+Calendar._TT["PREV_YEAR"] = "Forrige. år (hold for meny)";
+Calendar._TT["PREV_MONTH"] = "Forrige. måned (hold for meny)";
+Calendar._TT["GO_TODAY"] = "GÃ¥ til idag";
+Calendar._TT["NEXT_MONTH"] = "Neste måned (hold for meny)";
+Calendar._TT["NEXT_YEAR"] = "Neste år (hold for meny)";
+Calendar._TT["SEL_DATE"] = "Velg dato";
+Calendar._TT["DRAG_TO_MOVE"] = "Dra for å flytte";
+Calendar._TT["PART_TODAY"] = " (idag)";
+Calendar._TT["MON_FIRST"] = "Vis mandag først";
+Calendar._TT["SUN_FIRST"] = "Vis søndag først";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+
+Calendar._TT["CLOSE"] = "Lukk";
+Calendar._TT["TODAY"] = "Idag";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikk eller dra for å endre verdi";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "uke";
diff --git a/js/jscalendar/lang/calendar-pl-utf8.js b/js/jscalendar/lang/calendar-pl-utf8.js
new file mode 100644
index 0000000..6b8ca67
--- /dev/null
+++ b/js/jscalendar/lang/calendar-pl-utf8.js
@@ -0,0 +1,93 @@
+// ** I18N
+
+// Calendar PL language
+// Author: Dariusz Pietrzak, <eyck@ghost.anime.pl>
+// Author: Janusz Piwowarski, <jpiw@go2.pl>
+// Encoding: utf-8
+// Distributed under the same terms as the calendar itself.
+
+Calendar._DN = new Array
+("Niedziela",
+ "Poniedziałek",
+ "Wtorek",
+ "Åšroda",
+ "Czwartek",
+ "PiÄ…tek",
+ "Sobota",
+ "Niedziela");
+Calendar._SDN = new Array
+("Nie",
+ "Pn",
+ "Wt",
+ "Åšr",
+ "Cz",
+ "Pt",
+ "So",
+ "Nie");
+Calendar._MN = new Array
+("Styczeń",
+ "Luty",
+ "Marzec",
+ "Kwiecień",
+ "Maj",
+ "Czerwiec",
+ "Lipiec",
+ "Sierpień",
+ "Wrzesień",
+ "Październik",
+ "Listopad",
+ "Grudzień");
+Calendar._SMN = new Array
+("Sty",
+ "Lut",
+ "Mar",
+ "Kwi",
+ "Maj",
+ "Cze",
+ "Lip",
+ "Sie",
+ "Wrz",
+ "Paź",
+ "Lis",
+ "Gru");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O kalendarzu";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Aby pobrać najnowszą wersję, odwiedź: http://www.dynarch.com/projects/calendar/\n" +
+"Dostępny na licencji GNU LGPL. Zobacz szczegóły na http://gnu.org/licenses/lgpl.html." +
+"\n\n" +
+"Wybór daty:\n" +
+"- Użyj przycisków \xab, \xbb by wybrać rok\n" +
+"- Użyj przycisków " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " by wybrać miesiąc\n" +
+"- Przytrzymaj klawisz myszy nad jednym z powyższych przycisków dla szybszego wyboru.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Wybór czasu:\n" +
+"- Kliknij na jednym z pól czasu by zwiększyć jego wartość\n" +
+"- lub kliknij trzymając Shift by zmiejszyć jego wartość\n" +
+"- lub kliknij i przeciÄ…gnij dla szybszego wyboru.";
+
+//Calendar._TT["TOGGLE"] = "Zmień pierwszy dzień tygodnia";
+Calendar._TT["PREV_YEAR"] = "Poprzedni rok (przytrzymaj dla menu)";
+Calendar._TT["PREV_MONTH"] = "Poprzedni miesiÄ…c (przytrzymaj dla menu)";
+Calendar._TT["GO_TODAY"] = "Idź do dzisiaj";
+Calendar._TT["NEXT_MONTH"] = "Następny miesiąc (przytrzymaj dla menu)";
+Calendar._TT["NEXT_YEAR"] = "Następny rok (przytrzymaj dla menu)";
+Calendar._TT["SEL_DATE"] = "Wybierz datÄ™";
+Calendar._TT["DRAG_TO_MOVE"] = "Przeciągnij by przesunąć";
+Calendar._TT["PART_TODAY"] = " (dzisiaj)";
+Calendar._TT["MON_FIRST"] = "Wyświetl poniedziałek jako pierwszy";
+Calendar._TT["SUN_FIRST"] = "Wyświetl niedzielę jako pierwszą";
+Calendar._TT["CLOSE"] = "Zamknij";
+Calendar._TT["TODAY"] = "Dzisiaj";
+Calendar._TT["TIME_PART"] = "(Shift-)Kliknij lub przeciągnij by zmienić wartość";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%e %B, %A";
+
+Calendar._TT["WK"] = "ty";
diff --git a/js/jscalendar/lang/calendar-pl.js b/js/jscalendar/lang/calendar-pl.js
new file mode 100644
index 0000000..c2846a3
--- /dev/null
+++ b/js/jscalendar/lang/calendar-pl.js
@@ -0,0 +1,133 @@
+// ** I18N
+
+// ** I18N
+// Calendar PL language
+// Original translator
+// Author: Artur Filipiak, <imagen@poczta.fm>
+// January, 2004
+// Encoding: UTF-8
+// Little modifications to help use this calendar translation with Flyspray:
+// Paweł W., <dawajpoczte@gmail.com>
+// May 2007.
+
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Niedziela",
+ "Poniedziałek",
+ "Wtorek",
+ "Åšroda",
+ "Czwartek",
+ "PiÄ…tek",
+ "Sobota",
+ "Niedziela");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Nie",
+ "Pon",
+ "Wto",
+ "Åšr.",
+ "Czw",
+ "Pt.",
+ "Sb.",
+ "Nie");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Styczeń",
+ "Luty",
+ "Marzec",
+ "Kwiecień",
+ "Maj",
+ "Czerwiec",
+ "Lipiec",
+ "Sierpień",
+ "Wrzesień",
+ "Październik",
+ "Listopad",
+ "Grudzień");
+
+// short month names
+Calendar._SMN = new Array
+("Sty",
+ "Lut",
+ "Mar",
+ "Kwi",
+ "Maj",
+ "Cze",
+ "Lip",
+ "Sie",
+ "Wrz",
+ "Paź",
+ "Lis",
+ "Gru");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O kalendarzu";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Autor: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Wybór daty:\n" +
+"- Użyj przycisków \xab, \xbb aby wybrać rok year\n" +
+"- Uzyj przycisków " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " aby wybrać miesiąc\n" +
+"- aby przyspieszyć wybór przytrzymaj wciśnięty przycisk myszy nad ww. przyciskami.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Wybór czasu:\n" +
+"- aby zwiększyć wartość kliknij na dowolnym elemencie selekcji czasu\n" +
+"- aby zmniejszyć wartość użyj dodatkowo klawisza Shift\n" +
+"- możesz również poruszać myszkę w lewo i prawo wraz z wciśniętym lewym klawiszem.";
+
+Calendar._TT["PREV_YEAR"] = "Poprz. rok (przytrzymaj dla menu)";
+Calendar._TT["PREV_MONTH"] = "Poprz. miesiÄ…c (przytrzymaj dla menu)";
+Calendar._TT["GO_TODAY"] = "Pokaż dziś";
+Calendar._TT["NEXT_MONTH"] = "Nast. miesiÄ…c (przytrzymaj dla menu)";
+Calendar._TT["NEXT_YEAR"] = "Nast. rok (przytrzymaj dla menu)";
+Calendar._TT["SEL_DATE"] = "Wybierz datÄ™";
+Calendar._TT["DRAG_TO_MOVE"] = "Kliknij aby przesunąć okienko";
+Calendar._TT["PART_TODAY"] = " (dziÅ›)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "%s jako pierwszy dzień tygodnia";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Zamknij";
+Calendar._TT["TODAY"] = "DziÅ›";
+Calendar._TT["TIME_PART"] = "(Shift-)klik | przyciśnij, aby zmienić wartość";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/js/jscalendar/lang/calendar-pt.js b/js/jscalendar/lang/calendar-pt.js
new file mode 100644
index 0000000..deee8a1
--- /dev/null
+++ b/js/jscalendar/lang/calendar-pt.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar pt_BR language
+// Author: Adalberto Machado, <betosm@terra.com.br>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Domingo",
+ "Segunda",
+ "Terca",
+ "Quarta",
+ "Quinta",
+ "Sexta",
+ "Sabado",
+ "Domingo");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Dom",
+ "Seg",
+ "Ter",
+ "Qua",
+ "Qui",
+ "Sex",
+ "Sab",
+ "Dom");
+
+// full month names
+Calendar._MN = new Array
+("Janeiro",
+ "Fevereiro",
+ "Marco",
+ "Abril",
+ "Maio",
+ "Junho",
+ "Julho",
+ "Agosto",
+ "Setembro",
+ "Outubro",
+ "Novembro",
+ "Dezembro");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Fev",
+ "Mar",
+ "Abr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Set",
+ "Out",
+ "Nov",
+ "Dez");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Sobre o calendario";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Ultima versao visite: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuido sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." +
+"\n\n" +
+"Selecao de data:\n" +
+"- Use os botoes \xab, \xbb para selecionar o ano\n" +
+"- Use os botoes " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mes\n" +
+"- Segure o botao do mouse em qualquer um desses botoes para selecao rapida.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selecao de hora:\n" +
+"- Clique em qualquer parte da hora para incrementar\n" +
+"- ou Shift-click para decrementar\n" +
+"- ou clique e segure para selecao rapida.";
+
+Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)";
+Calendar._TT["PREV_MONTH"] = "Ant. mes (segure para menu)";
+Calendar._TT["GO_TODAY"] = "Hoje";
+Calendar._TT["NEXT_MONTH"] = "Prox. mes (segure para menu)";
+Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)";
+Calendar._TT["SEL_DATE"] = "Selecione a data";
+Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover";
+Calendar._TT["PART_TODAY"] = " (hoje)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Mostre %s primeiro";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Fechar";
+Calendar._TT["TODAY"] = "Hoje";
+Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b";
+
+Calendar._TT["WK"] = "sm";
+Calendar._TT["TIME"] = "Hora:";
diff --git a/js/jscalendar/lang/calendar-ro.js b/js/jscalendar/lang/calendar-ro.js
new file mode 100644
index 0000000..116e358
--- /dev/null
+++ b/js/jscalendar/lang/calendar-ro.js
@@ -0,0 +1,66 @@
+// ** I18N
+Calendar._DN = new Array
+("Duminică",
+ "Luni",
+ "Marţi",
+ "Miercuri",
+ "Joi",
+ "Vineri",
+ "Sâmbătă",
+ "Duminică");
+Calendar._SDN_len = 2;
+Calendar._MN = new Array
+("Ianuarie",
+ "Februarie",
+ "Martie",
+ "Aprilie",
+ "Mai",
+ "Iunie",
+ "Iulie",
+ "August",
+ "Septembrie",
+ "Octombrie",
+ "Noiembrie",
+ "Decembrie");
+
+// tooltips
+Calendar._TT = {};
+
+Calendar._TT["INFO"] = "Despre calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Pentru ultima versiune vizitaţi: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuit sub GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Selecţia datei:\n" +
+"- Folosiţi butoanele \xab, \xbb pentru a selecta anul\n" +
+"- Folosiţi butoanele " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pentru a selecta luna\n" +
+"- Tineţi butonul mouse-ului apăsat pentru selecţie mai rapidă.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selecţia orei:\n" +
+"- Click pe ora sau minut pentru a mări valoarea cu 1\n" +
+"- Sau Shift-Click pentru a micÅŸora valoarea cu 1\n" +
+"- Sau Click ÅŸi drag pentru a selecta mai repede.";
+
+Calendar._TT["PREV_YEAR"] = "Anul precedent (lung pt menu)";
+Calendar._TT["PREV_MONTH"] = "Luna precedentă (lung pt menu)";
+Calendar._TT["GO_TODAY"] = "Data de azi";
+Calendar._TT["NEXT_MONTH"] = "Luna următoare (lung pt menu)";
+Calendar._TT["NEXT_YEAR"] = "Anul următor (lung pt menu)";
+Calendar._TT["SEL_DATE"] = "Selectează data";
+Calendar._TT["DRAG_TO_MOVE"] = "Trage pentru a miÅŸca";
+Calendar._TT["PART_TODAY"] = " (astăzi)";
+Calendar._TT["DAY_FIRST"] = "Afişează %s prima zi";
+Calendar._TT["WEEKEND"] = "0,6";
+Calendar._TT["CLOSE"] = "ÃŽnchide";
+Calendar._TT["TODAY"] = "Astăzi";
+Calendar._TT["TIME_PART"] = "(Shift-)Click sau drag pentru a selecta";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %d %B";
+
+Calendar._TT["WK"] = "spt";
+Calendar._TT["TIME"] = "Ora:";
diff --git a/js/jscalendar/lang/calendar-ru.js b/js/jscalendar/lang/calendar-ru.js
new file mode 100644
index 0000000..5ed2d53
--- /dev/null
+++ b/js/jscalendar/lang/calendar-ru.js
@@ -0,0 +1,126 @@
+// ** I18N
+
+// Calendar RU language
+// Translation: Sly Golovanov, http://golovanov.net, <sly@golovanov.net>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("воÑкреÑенье",
+ "понедельник",
+ "вторник",
+ "Ñреда",
+ "четверг",
+ "пÑтница",
+ "Ñуббота",
+ "воÑкреÑенье");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("вÑк",
+ "пон",
+ "втр",
+ "Ñрд",
+ "чет",
+ "пÑÑ‚",
+ "Ñуб",
+ "вÑк");
+
+// First day of the week. 0 means Sunday, 1 means Monday
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("Ñнварь",
+ "февраль",
+ "март",
+ "апрель",
+ "май",
+ "июнь",
+ "июль",
+ "авгуÑÑ‚",
+ "ÑентÑбрь",
+ "октÑбрь",
+ "ноÑбрь",
+ "декабрь");
+
+// short month names
+Calendar._SMN = new Array
+("Ñнв",
+ "фев",
+ "мар",
+ "апр",
+ "май",
+ "июн",
+ "июл",
+ "авг",
+ "Ñен",
+ "окт",
+ "ноÑ",
+ "дек");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "О календаре...";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Как выбрать дату:\n" +
+"- При помощи кнопок \xab, \xbb можно выбрать год\n" +
+"- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать меÑÑц\n" +
+"- Подержите Ñти кнопки нажатыми, чтобы поÑвилоÑÑŒ меню быÑтрого выбора.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Как выбрать времÑ:\n" +
+"- При клике на чаÑах или минутах они увеличиваютÑÑ\n" +
+"- при клике Ñ Ð½Ð°Ð¶Ð°Ñ‚Ð¾Ð¹ клавишей Shift они уменьшаютÑÑ\n" +
+"- еÑли нажать и двигать мышкой влево/вправо, они будут менÑÑ‚ÑŒÑÑ Ð±Ñ‹Ñтрее.";
+
+Calendar._TT["PREV_YEAR"] = "Ðа год назад (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)";
+Calendar._TT["PREV_MONTH"] = "Ðа меÑÑц назад (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)";
+Calendar._TT["GO_TODAY"] = "СегоднÑ";
+Calendar._TT["NEXT_MONTH"] = "Ðа меÑÑц вперед (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)";
+Calendar._TT["NEXT_YEAR"] = "Ðа год вперед (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)";
+Calendar._TT["SEL_DATE"] = "Выберите дату";
+Calendar._TT["DRAG_TO_MOVE"] = "ПеретаÑкивайте мышкой";
+Calendar._TT["PART_TODAY"] = " (ÑегоднÑ)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Первый день недели будет %s";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Закрыть";
+Calendar._TT["TODAY"] = "СегоднÑ";
+Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a";
+
+Calendar._TT["WK"] = "нед";
+Calendar._TT["TIME"] = "ВремÑ:";
diff --git a/js/jscalendar/lang/calendar-si.js b/js/jscalendar/lang/calendar-si.js
new file mode 100644
index 0000000..dfe83a0
--- /dev/null
+++ b/js/jscalendar/lang/calendar-si.js
@@ -0,0 +1,97 @@
+/* Slovenian language file for the DHTML Calendar version 0.9.2
+* Author David Milost <mercy@volja.net>, January 2004.
+* Feel free to use this script under the terms of the GNU Lesser General
+* Public License, as long as you do not remove or alter this notice.
+*/
+ // full day names
+Calendar._DN = new Array
+("Nedelja",
+ "Ponedeljek",
+ "Torek",
+ "Sreda",
+ "ÄŒetrtek",
+ "Petek",
+ "Sobota",
+ "Nedelja");
+ // short day names
+ Calendar._SDN = new Array
+("Ned",
+ "Pon",
+ "Tor",
+ "Sre",
+ "ÄŒet",
+ "Pet",
+ "Sob",
+ "Ned");
+
+Calendar._FD = 1;
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avg",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec");
+ // full month names
+Calendar._MN = new Array
+("Januar",
+ "Februar",
+ "Marec",
+ "April",
+ "Maj",
+ "Junij",
+ "Julij",
+ "Avgust",
+ "September",
+ "Oktober",
+ "November",
+ "December");
+
+// tooltips
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O koledarju";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Za zadnjo verzijo pojdine na naslov: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuirano pod GNU LGPL. Poglejte http://gnu.org/licenses/lgpl.html za podrobnosti." +
+"\n\n" +
+"Izbor datuma:\n" +
+"- Uporabite \xab, \xbb gumbe za izbor leta\n" +
+"- Uporabite " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gumbe za izbor meseca\n" +
+"- Zadržite klik na kateremkoli od zgornjih gumbov za hiter izbor.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Izbor ćasa:\n" +
+"- Kliknite na katerikoli del ćasa za poveć. le-tega\n" +
+"- ali Shift-click za zmanj. le-tega\n" +
+"- ali kliknite in povlecite za hiter izbor.";
+
+Calendar._TT["TOGGLE"] = "Spremeni dan s katerim se prićne teden";
+Calendar._TT["PREV_YEAR"] = "Predhodnje leto (dolg klik za meni)";
+Calendar._TT["PREV_MONTH"] = "Predhodnji mesec (dolg klik za meni)";
+Calendar._TT["GO_TODAY"] = "Pojdi na tekoći dan";
+Calendar._TT["NEXT_MONTH"] = "Naslednji mesec (dolg klik za meni)";
+Calendar._TT["NEXT_YEAR"] = "Naslednje leto (dolg klik za meni)";
+Calendar._TT["SEL_DATE"] = "Izberite datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Pritisni in povleci za spremembo pozicije";
+Calendar._TT["PART_TODAY"] = " (danes)";
+Calendar._TT["MON_FIRST"] = "Prikaži ponedeljek kot prvi dan";
+Calendar._TT["SUN_FIRST"] = "Prikaži nedeljo kot prvi dan";
+Calendar._TT["CLOSE"] = "Zapri";
+Calendar._TT["TODAY"] = "Danes";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "Ted";
diff --git a/js/jscalendar/lang/calendar-sk.js b/js/jscalendar/lang/calendar-sk.js
new file mode 100644
index 0000000..c54d9ac
--- /dev/null
+++ b/js/jscalendar/lang/calendar-sk.js
@@ -0,0 +1,68 @@
+/*
+ calendar-sk.js
+ language: Slovak
+ encoding: UTF-8
+ author: Stanislav Pach (stano.pach@seznam.cz)
+*/
+
+// ** I18N
+Calendar._DN = new Array('Nedeľa','Pondelok','Utorok','Streda','Štvrtok','Piatok','Sobota','Nedeľa');
+Calendar._SDN = new Array('Ne','Po','Ut','St','Å t','Pi','So','Ne');
+Calendar._MN = new Array('Január','Február','Marec','Apríl','Máj','Jún','Júl','August','September','Október','November','December');
+Calendar._SMN = new Array('Jan','Feb','Mar','Apr','Máj','Jún','Júl','Aug','Sep','Okt','Nov','Dec');
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "O komponente kalendár";
+Calendar._TT["TOGGLE"] = "Zmena prvého dňa v týždni";
+Calendar._TT["PREV_YEAR"] = "Predchádzajúci rok (pridrž pre menu)";
+Calendar._TT["PREV_MONTH"] = "Predchádzajúci mesiac (pridrž pre menu)";
+Calendar._TT["GO_TODAY"] = "Dnešný dátum";
+Calendar._TT["NEXT_MONTH"] = "Ďalší mesiac (pridrž pre menu)";
+Calendar._TT["NEXT_YEAR"] = "Ďalší rok (pridrž pre menu)";
+Calendar._TT["SEL_DATE"] = "Zvoľ dátum";
+Calendar._TT["DRAG_TO_MOVE"] = "Chyť a ťahaj pre presun";
+Calendar._TT["PART_TODAY"] = " (dnes)";
+Calendar._TT["MON_FIRST"] = "Ukáž ako prvný Pondelok";
+//Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Výber dátumu:\n" +
+"- Použijte tlaÄítka \xab, \xbb pre voľbu roku\n" +
+"- Použijte tlaÄítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pre výber mesiaca\n" +
+"- Podržte tlaÄítko myÅ¡i na akomkoľvek z týchto tlaÄítok pre rýchlejší výber.";
+
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Výber Äasu:\n" +
+"- Kliknite na akúkoľvek ÄasÅ¥ z výberu Äasu pre zvýšenie.\n" +
+"- alebo Shift-klick pre zníženie\n" +
+"- alebo kliknite a ťahajte pre rýchlejší výber.";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Zobraz %s ako prvý";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Zavrieť";
+Calendar._TT["TODAY"] = "Dnes";
+Calendar._TT["TIME_PART"] = "(Shift-)Klikni alebo ťahaj pre zmenu hodnoty";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "týž";
+Calendar._TT["TIME"] = "ÄŒas:";
diff --git a/js/jscalendar/lang/calendar-sp.js b/js/jscalendar/lang/calendar-sp.js
new file mode 100644
index 0000000..239d1b3
--- /dev/null
+++ b/js/jscalendar/lang/calendar-sp.js
@@ -0,0 +1,110 @@
+// ** I18N
+
+// Calendar SP language
+// Author: Rafael Velasco <rvu_at_idecnet_dot_com>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Domingo",
+ "Lunes",
+ "Martes",
+ "Miercoles",
+ "Jueves",
+ "Viernes",
+ "Sabado",
+ "Domingo");
+
+Calendar._SDN = new Array
+("Dom",
+ "Lun",
+ "Mar",
+ "Mie",
+ "Jue",
+ "Vie",
+ "Sab",
+ "Dom");
+
+// full month names
+Calendar._MN = new Array
+("Enero",
+ "Febrero",
+ "Marzo",
+ "Abril",
+ "Mayo",
+ "Junio",
+ "Julio",
+ "Agosto",
+ "Septiembre",
+ "Octubre",
+ "Noviembre",
+ "Diciembre");
+
+// short month names
+Calendar._SMN = new Array
+("Ene",
+ "Feb",
+ "Mar",
+ "Abr",
+ "May",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dic");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Información del Calendario";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"Nuevas versiones en: http://www.dynarch.com/projects/calendar/\n" +
+"Distribuida bajo licencia GNU LGPL. Para detalles vea http://gnu.org/licenses/lgpl.html ." +
+"\n\n" +
+"Selección de Fechas:\n" +
+"- Use \xab, \xbb para seleccionar el año\n" +
+"- Use " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" +
+"- Mantenga presionado el botón del ratón en cualquiera de las opciones superiores para un acceso rapido .";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Selección del Reloj:\n" +
+"- Seleccione la hora para cambiar el reloj\n" +
+"- o presione Shift-click para disminuirlo\n" +
+"- o presione click y arrastre del ratón para una selección rapida.";
+
+Calendar._TT["PREV_YEAR"] = "Año anterior (Presione para menu)";
+Calendar._TT["PREV_MONTH"] = "Mes Anterior (Presione para menu)";
+Calendar._TT["GO_TODAY"] = "Ir a Hoy";
+Calendar._TT["NEXT_MONTH"] = "Mes Siguiente (Presione para menu)";
+Calendar._TT["NEXT_YEAR"] = "Año Siguiente (Presione para menu)";
+Calendar._TT["SEL_DATE"] = "Seleccione fecha";
+Calendar._TT["DRAG_TO_MOVE"] = "Arrastre y mueva";
+Calendar._TT["PART_TODAY"] = " (Hoy)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Mostrar %s primero";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Cerrar";
+Calendar._TT["TODAY"] = "Hoy";
+Calendar._TT["TIME_PART"] = "(Shift-)Click o arrastra para cambar el valor";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%dd-%mm-%yy";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y";
+
+Calendar._TT["WK"] = "Sm";
+Calendar._TT["TIME"] = "Hora:";
diff --git a/js/jscalendar/lang/calendar-sr.js b/js/jscalendar/lang/calendar-sr.js
new file mode 100644
index 0000000..0b5383b
--- /dev/null
+++ b/js/jscalendar/lang/calendar-sr.js
@@ -0,0 +1,122 @@
+// Calendar SR serbian language
+
+// 2019-03-11: based on calendar-en.js to make jscalendar work when serbian language is choosen
+// (sr or sr_lat) in Flyspray currently, as L('locale') needs to be set, so other date/locale stuff within
+// Flyspray works for serbian as expected.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 1;
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/js/jscalendar/lang/calendar-sv.js b/js/jscalendar/lang/calendar-sv.js
new file mode 100644
index 0000000..3370d16
--- /dev/null
+++ b/js/jscalendar/lang/calendar-sv.js
@@ -0,0 +1,98 @@
+// ** I18N
+
+// Calendar SV language (Swedish, svenska)
+// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
+// Translation team: <sv@li.org>
+// Translator: Leonard Norrgård <leonard.norrgard@refactor.fi>
+// Last translator: Leonard Norrgård <leonard.norrgard@refactor.fi>
+// Last Minor fixes: Mikael Silfver, mikael dot silfver gmail com
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("söndag",
+ "måndag",
+ "tisdag",
+ "onsdag",
+ "torsdag",
+ "fredag",
+ "lördag",
+ "söndag");
+
+Calendar._FD = 1;
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+Calendar._SDN_len = 2;
+Calendar._SMN_len = 3;
+
+// full month names
+Calendar._MN = new Array
+("januari",
+ "februari",
+ "mars",
+ "april",
+ "maj",
+ "juni",
+ "juli",
+ "augusti",
+ "september",
+ "oktober",
+ "november",
+ "december");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "Om kalendern";
+
+Calendar._TT["ABOUT"] =
+"DHTML Datum/tid-väljare\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"För senaste version gå till: http://www.dynarch.com/projects/calendar/\n" +
+"Distribueras under GNU LGPL. Se http://gnu.org/licenses/lgpl.html för detaljer." +
+"\n\n" +
+"Val av datum:\n" +
+"- Använd knapparna \xab, \xbb för att välja år\n" +
+"- Använd knapparna " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " för att välja månad\n" +
+"- Håll musknappen nedtryckt på någon av ovanstående knappar för snabbare val.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Val av tid:\n" +
+"- Klicka på en del av tiden för att öka den delen\n" +
+"- eller skift-klicka för att minska den\n" +
+"- eller klicka och drag för snabbare val.";
+
+Calendar._TT["PREV_YEAR"] = "Föregående år (håll för menu)";
+Calendar._TT["PREV_MONTH"] = "Föregående månad (håll för menu)";
+Calendar._TT["GO_TODAY"] = "GÃ¥ till dagens datum";
+Calendar._TT["NEXT_MONTH"] = "Följande månad (håll för menu)";
+Calendar._TT["NEXT_YEAR"] = "Följande år (håll för menu)";
+Calendar._TT["SEL_DATE"] = "Välj datum";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag för att flytta";
+Calendar._TT["PART_TODAY"] = " (idag)";
+Calendar._TT["MON_FIRST"] = "Visa måndag först";
+Calendar._TT["SUN_FIRST"] = "Visa söndag först";
+Calendar._TT["CLOSE"] = "Stäng";
+Calendar._TT["TODAY"] = "Idag";
+Calendar._TT["TIME_PART"] = "(Skift-)klicka eller drag för att ändra tid";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%A %d %b %Y";
+
+Calendar._TT["WK"] = "vecka";
+Calendar._TT["WEEKEND"] = "veckoslut";
+Calendar._TT["DAY_FIRST"] = "%s som första dag";
diff --git a/js/jscalendar/lang/calendar-tr.js b/js/jscalendar/lang/calendar-tr.js
new file mode 100644
index 0000000..34eafae
--- /dev/null
+++ b/js/jscalendar/lang/calendar-tr.js
@@ -0,0 +1,50 @@
+// Calendar Turkish Translation
+// Author: Nuri AKMAN, <nuriakman@hotmail.com>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// ** I18N
+Calendar._DN = new Array
+("Pazar",
+ "Pazartesi",
+ "Salı",
+ "Çarşamba",
+ "PerÅŸembe",
+ "Cuma",
+ "Cumartesi",
+ "Pazar");
+Calendar._MN = new Array
+("Ocak",
+ "Åžubat",
+ "Mart",
+ "Nisan",
+ "Mayıs",
+ "Haziran",
+ "Temmuz",
+ "AÄŸustos",
+ "Eylül",
+ "Ekim",
+ "Kasım",
+ "Aralık");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["TOGGLE"] = "Haftanın ilk gününü kaydır";
+Calendar._TT["PREV_YEAR"] = "Önceki Yıl (Menü için basılı tutunuz)";
+Calendar._TT["PREV_MONTH"] = "Önceki Ay (Menü için basılı tutunuz)";
+Calendar._TT["GO_TODAY"] = "Bugün'e git";
+Calendar._TT["NEXT_MONTH"] = "Sonraki Ay (Menü için basılı tutunuz)";
+Calendar._TT["NEXT_YEAR"] = "Sonraki Yıl (Menü için basılı tutunuz)";
+Calendar._TT["SEL_DATE"] = "Tarih seçiniz";
+Calendar._TT["DRAG_TO_MOVE"] = "Taşımak için sürükleyiniz";
+Calendar._TT["PART_TODAY"] = " (bugün)";
+Calendar._TT["MON_FIRST"] = "Takvim Pazartesi gününden başlasın";
+Calendar._TT["SUN_FIRST"] = "Takvim Pazar gününden başlasın";
+Calendar._TT["CLOSE"] = "Kapat";
+Calendar._TT["TODAY"] = "Bugün";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y";
+Calendar._TT["TT_DATE_FORMAT"] = "d MM y, DD";
+
+Calendar._TT["WK"] = "Hafta";
diff --git a/js/jscalendar/lang/calendar-zh.js b/js/jscalendar/lang/calendar-zh.js
new file mode 100644
index 0000000..58aa0a7
--- /dev/null
+++ b/js/jscalendar/lang/calendar-zh.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar ZH language
+// Author: muziq, <muziq@sina.com>
+// Encoding: UTF-8
+// Distributed under the same terms as the calendar itself.
+
+// full day names
+Calendar._DN = new Array
+("星期日",
+ "星期一",
+ "星期二",
+ "星期三",
+ "星期四",
+ "星期五",
+ "星期六",
+ "星期日");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("æ—¥",
+ "一",
+ "二",
+ "三",
+ "å››",
+ "五",
+ "å…­",
+ "æ—¥");
+
+// full month names
+Calendar._MN = new Array
+("一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "ä¹æœˆ",
+ "å月",
+ "å一月",
+ "å二月");
+
+// short month names
+Calendar._SMN = new Array
+("一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "ä¹æœˆ",
+ "å月",
+ "å一月",
+ "å二月");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "帮助";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"选择日期:\n" +
+"- 点击 \xab, \xbb 按钮选择年份\n" +
+"- 点击 " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 按钮选择月份\n" +
+"- 长按以上按钮å¯ä»Žèœå•ä¸­å¿«é€Ÿé€‰æ‹©å¹´ä»½æˆ–月份";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"选择时间:\n" +
+"- 点击å°æ—¶æˆ–分钟å¯ä½¿æ”¹æ•°å€¼åŠ ä¸€\n" +
+"- 按ä½Shift键点击å°æ—¶æˆ–分钟å¯ä½¿æ”¹æ•°å€¼å‡ä¸€\n" +
+"- 点击拖动鼠标å¯è¿›è¡Œå¿«é€Ÿé€‰æ‹©";
+
+Calendar._TT["PREV_YEAR"] = "上一年 (按ä½å‡ºèœå•)";
+Calendar._TT["PREV_MONTH"] = "上一月 (按ä½å‡ºèœå•)";
+Calendar._TT["GO_TODAY"] = "转到今日";
+Calendar._TT["NEXT_MONTH"] = "下一月 (按ä½å‡ºèœå•)";
+Calendar._TT["NEXT_YEAR"] = "下一年 (按ä½å‡ºèœå•)";
+Calendar._TT["SEL_DATE"] = "选择日期";
+Calendar._TT["DRAG_TO_MOVE"] = "拖动";
+Calendar._TT["PART_TODAY"] = " (今日)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "最左边显示%s";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "关闭";
+Calendar._TT["TODAY"] = "今日";
+Calendar._TT["TIME_PART"] = "(Shift-)点击鼠标或拖动改å˜å€¼";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%A, %b %eæ—¥";
+
+Calendar._TT["WK"] = "周";
+Calendar._TT["TIME"] = "时间:";
+
+// First day of the week. 0 means display Sunday first, 1 means display
+// Monday first, etc.
+Calendar._FD = 1;
diff --git a/js/jscalendar/menuarrow.gif b/js/jscalendar/menuarrow.gif
new file mode 100644
index 0000000..ed2dee0
--- /dev/null
+++ b/js/jscalendar/menuarrow.gif
Binary files differ
diff --git a/js/jscalendar/menuarrow2.gif b/js/jscalendar/menuarrow2.gif
new file mode 100644
index 0000000..40c0aad
--- /dev/null
+++ b/js/jscalendar/menuarrow2.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/active-bg.gif b/js/jscalendar/skins/aqua/active-bg.gif
new file mode 100644
index 0000000..d608c54
--- /dev/null
+++ b/js/jscalendar/skins/aqua/active-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/dark-bg.gif b/js/jscalendar/skins/aqua/dark-bg.gif
new file mode 100644
index 0000000..1dea48a
--- /dev/null
+++ b/js/jscalendar/skins/aqua/dark-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/hover-bg.gif b/js/jscalendar/skins/aqua/hover-bg.gif
new file mode 100644
index 0000000..fbf94fc
--- /dev/null
+++ b/js/jscalendar/skins/aqua/hover-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/menuarrow.gif b/js/jscalendar/skins/aqua/menuarrow.gif
new file mode 100644
index 0000000..40c0aad
--- /dev/null
+++ b/js/jscalendar/skins/aqua/menuarrow.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/normal-bg.gif b/js/jscalendar/skins/aqua/normal-bg.gif
new file mode 100644
index 0000000..bdb5068
--- /dev/null
+++ b/js/jscalendar/skins/aqua/normal-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/rowhover-bg.gif b/js/jscalendar/skins/aqua/rowhover-bg.gif
new file mode 100644
index 0000000..7715342
--- /dev/null
+++ b/js/jscalendar/skins/aqua/rowhover-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/status-bg.gif b/js/jscalendar/skins/aqua/status-bg.gif
new file mode 100644
index 0000000..857108c
--- /dev/null
+++ b/js/jscalendar/skins/aqua/status-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/theme.css b/js/jscalendar/skins/aqua/theme.css
new file mode 100644
index 0000000..18dd6cf
--- /dev/null
+++ b/js/jscalendar/skins/aqua/theme.css
@@ -0,0 +1,236 @@
+/* Distributed as part of The Coolest DHTML Calendar
+ Author: Mihai Bazon, www.bazon.net/mishoo
+ Copyright Dynarch.com 2005, www.dynarch.com
+*/
+
+/* The main calendar widget. DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+ border: 1px solid #bdb2bf;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: url("normal-bg.gif");
+ font-family: "trebuchet ms",verdana,tahoma,sans-serif;
+}
+
+.calendar {
+ border-color: #797979;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center; /* They are the navigation buttons */
+ padding: 2px; /* Make the buttons seem like they're pressing */
+ background: url("title-bg.gif") repeat-x 0 100%; color: #000;
+ font-weight: bold;
+}
+
+.calendar .nav {
+ font-family: verdana,tahoma,sans-serif;
+}
+
+.calendar .nav div {
+ background: transparent url("menuarrow.gif") no-repeat 100% 100%;
+}
+
+.calendar thead tr { background: url("title-bg.gif") repeat-x 0 100%; color: #000; }
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold; /* Pressing it will take you to the current date */
+ text-align: center;
+ padding: 2px;
+ background: url("title-bg.gif") repeat-x 0 100%; color: #000;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #797979;
+ padding: 2px;
+ text-align: center;
+ color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #c44;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ background: url("hover-bg.gif");
+ border-bottom: 1px solid #797979;
+ padding: 2px 2px 1px 2px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ background: url("active-bg.gif"); color: #fff;
+ padding: 3px 1px 0px 3px;
+ border-bottom: 1px solid #797979;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+ background: url("dark-bg.gif");
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ font-family: verdana,tahoma,sans-serif;
+ width: 2em;
+ color: #000;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #999;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #f99;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #797979;
+ background: url("dark-bg.gif");
+}
+
+.calendar tbody .rowhilite td,
+.calendar tbody .rowhilite td.wn {
+ background: url("rowhover-bg.gif");
+}
+
+.calendar tbody td.today { font-weight: bold; /* background: url("today-bg.gif") no-repeat 70% 50%; */ }
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ background: url("hover-bg.gif");
+ padding: 1px 3px 1px 1px;
+ border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #c44;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border: 1px solid #797979;
+ padding: 1px 3px 1px 1px;
+ background: url("active-bg.gif"); color: #fff;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+ text-align: center;
+ background: #565;
+ color: #fff;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ padding: 2px;
+ background: url("status-bg.gif") repeat-x 0 0; color: #000;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ background: #afa;
+ border: 1px solid #084;
+ color: #000;
+ padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ background: #7c7;
+ padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ top: 0px;
+ left: 0px;
+ width: 4em;
+ cursor: default;
+ border-width: 0 1px 1px 1px;
+ border-style: solid;
+ border-color: #797979;
+ background: url("normal-bg.gif"); color: #000;
+ z-index: 100;
+ font-size: 90%;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .hilite {
+ background: url("hover-bg.gif"); color: #000;
+}
+
+.calendar .combo .active {
+ background: url("active-bg.gif"); color: #fff;
+ font-weight: bold;
+}
+
+.calendar td.time {
+ border-top: 1px solid #797979;
+ padding: 1px 0px;
+ text-align: center;
+ background: url("dark-bg.gif");
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 5px 0px 6px;
+ font-weight: bold;
+ background: url("normal-bg.gif"); color: #000;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute {
+ font-family: monospace;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ background: url("hover-bg.gif"); color: #000;
+}
+
+.calendar td.time span.active {
+ background: url("active-bg.gif"); color: #fff;
+}
diff --git a/js/jscalendar/skins/aqua/title-bg.gif b/js/jscalendar/skins/aqua/title-bg.gif
new file mode 100644
index 0000000..6a541b3
--- /dev/null
+++ b/js/jscalendar/skins/aqua/title-bg.gif
Binary files differ
diff --git a/js/jscalendar/skins/aqua/today-bg.gif b/js/jscalendar/skins/aqua/today-bg.gif
new file mode 100644
index 0000000..7161538
--- /dev/null
+++ b/js/jscalendar/skins/aqua/today-bg.gif
Binary files differ
diff --git a/js/lightbox/css/lightbox.css b/js/lightbox/css/lightbox.css
new file mode 100644
index 0000000..38b7301
--- /dev/null
+++ b/js/lightbox/css/lightbox.css
@@ -0,0 +1,27 @@
+#lightbox{ position: absolute; left: 0; width: 100%; z-index: 100; text-align: center; line-height: 0;}
+#lightbox img{ width: auto; height: auto;}
+#lightbox a img{ border: none; }
+
+#outerImageContainer{ position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; }
+#imageContainer{ padding: 10px; }
+
+#loading{ position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; }
+#hoverNav{ position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 10; }
+#imageContainer>#hoverNav{ left: 0;}
+#hoverNav a{ outline: none;}
+
+#prevLink, #nextLink{ width: 49%; height: 100%; background-image: url(data:image/gif;base64,AAAA); /* Trick IE into showing hover */ display: block; }
+#prevLink { left: 0; float: left;}
+#nextLink { right: 0; float: right;}
+#prevLink:hover, #prevLink:visited:hover { background: url(../images/prevlabel.gif) left 15% no-repeat; }
+#nextLink:hover, #nextLink:visited:hover { background: url(../images/nextlabel.gif) right 15% no-repeat; }
+
+#imageDataContainer{ font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; overflow: auto; width: 100%; min-width:250px;}
+
+#imageData{ padding:0 10px; color: #666; }
+#imageData #imageDetails{ width: 70%; float: left; text-align: left; }
+#imageData #caption{ font-weight: bold; }
+#imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em; }
+#imageData #bottomNavClose{ width: 66px; float: right; padding-bottom: 0.7em; outline: none;}
+
+#overlay{ position: absolute; top: 0; left: 0; z-index: 90; width: 100%; height: 500px; background-color: #000; }
diff --git a/js/lightbox/images/bullet.gif b/js/lightbox/images/bullet.gif
new file mode 100644
index 0000000..bf8e3c6
--- /dev/null
+++ b/js/lightbox/images/bullet.gif
Binary files differ
diff --git a/js/lightbox/images/close.gif b/js/lightbox/images/close.gif
new file mode 100644
index 0000000..ca517b6
--- /dev/null
+++ b/js/lightbox/images/close.gif
Binary files differ
diff --git a/js/lightbox/images/closelabel.gif b/js/lightbox/images/closelabel.gif
new file mode 100644
index 0000000..87b4f8b
--- /dev/null
+++ b/js/lightbox/images/closelabel.gif
Binary files differ
diff --git a/js/lightbox/images/loading.gif b/js/lightbox/images/loading.gif
new file mode 100644
index 0000000..f864d5f
--- /dev/null
+++ b/js/lightbox/images/loading.gif
Binary files differ
diff --git a/js/lightbox/images/nextlabel.gif b/js/lightbox/images/nextlabel.gif
new file mode 100644
index 0000000..6c40e51
--- /dev/null
+++ b/js/lightbox/images/nextlabel.gif
Binary files differ
diff --git a/js/lightbox/images/prevlabel.gif b/js/lightbox/images/prevlabel.gif
new file mode 100644
index 0000000..51a31c2
--- /dev/null
+++ b/js/lightbox/images/prevlabel.gif
Binary files differ
diff --git a/js/lightbox/js/lightbox.js b/js/lightbox/js/lightbox.js
new file mode 100644
index 0000000..2a9b1a7
--- /dev/null
+++ b/js/lightbox/js/lightbox.js
@@ -0,0 +1,497 @@
+// -----------------------------------------------------------------------------------
+//
+// Lightbox v2.04
+// by Lokesh Dhakar - http://www.lokeshdhakar.com
+// Last Modification: 2/9/08
+//
+// For more information, visit:
+// http://lokeshdhakar.com/projects/lightbox2/
+//
+// Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+// - Free for use in both personal and commercial projects
+// - Attribution requires leaving author name, author link, and the license info intact.
+//
+// Thanks: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), and Thomas Fuchs(mir.aculo.us) for ideas, libs, and snippets.
+// Artemy Tregubenko (arty.name) for cleanup and help in updating to latest ver of proto-aculous.
+//
+// -----------------------------------------------------------------------------------
+/*
+
+ Table of Contents
+ -----------------
+ Configuration
+
+ Lightbox Class Declaration
+ - initialize()
+ - updateImageList()
+ - start()
+ - changeImage()
+ - resizeImageContainer()
+ - showImage()
+ - updateDetails()
+ - updateNav()
+ - enableKeyboardNav()
+ - disableKeyboardNav()
+ - keyboardAction()
+ - preloadNeighborImages()
+ - end()
+
+ Function Calls
+ - document.observe()
+
+*/
+// -----------------------------------------------------------------------------------
+
+//
+// Configurationl
+//
+LightboxOptions = Object.extend({
+ fileLoadingImage: 'js/lightbox/images/loading.gif',
+ fileBottomNavCloseImage: 'js/lightbox/images/closelabel.gif',
+
+ overlayOpacity: 0.8, // controls transparency of shadow overlay
+
+ animate: true, // toggles resizing animations
+ resizeSpeed: 7, // controls the speed of the image resizing animations (1=slowest and 10=fastest)
+
+ borderSize: 10, //if you adjust the padding in the CSS, you will need to update this variable
+
+ // When grouping images this is used to write: Image # of #.
+ // Change it for non-english localization
+ labelImage: "Image",
+ labelOf: "of"
+}, window.LightboxOptions || {});
+
+// -----------------------------------------------------------------------------------
+
+var Lightbox = Class.create();
+
+Lightbox.prototype = {
+ imageArray: [],
+ activeImage: undefined,
+
+ // initialize()
+ // Constructor runs on completion of the DOM loading. Calls updateImageList and then
+ // the function inserts html at the bottom of the page which is used to display the shadow
+ // overlay and the image container.
+ //
+ initialize: function() {
+
+ this.updateImageList();
+
+ this.keyboardAction = this.keyboardAction.bindAsEventListener(this);
+
+ if (LightboxOptions.resizeSpeed > 10) LightboxOptions.resizeSpeed = 10;
+ if (LightboxOptions.resizeSpeed < 1) LightboxOptions.resizeSpeed = 1;
+
+ this.resizeDuration = LightboxOptions.animate ? ((11 - LightboxOptions.resizeSpeed) * 0.15) : 0;
+ this.overlayDuration = LightboxOptions.animate ? 0.2 : 0; // shadow fade in/out duration
+
+ // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension.
+ // If animations are turned off, it will be hidden as to prevent a flicker of a
+ // white 250 by 250 box.
+ var size = (LightboxOptions.animate ? 250 : 1) + 'px';
+
+
+ // Code inserts html at the bottom of the page that looks similar to this:
+ //
+ // <div id="overlay"></div>
+ // <div id="lightbox">
+ // <div id="outerImageContainer">
+ // <div id="imageContainer">
+ // <img id="lightboxImage">
+ // <div style="" id="hoverNav">
+ // <a href="#" id="prevLink"></a>
+ // <a href="#" id="nextLink"></a>
+ // </div>
+ // <div id="loading">
+ // <a href="#" id="loadingLink">
+ // <img src="images/loading.gif">
+ // </a>
+ // </div>
+ // </div>
+ // </div>
+ // <div id="imageDataContainer">
+ // <div id="imageData">
+ // <div id="imageDetails">
+ // <span id="caption"></span>
+ // <span id="numberDisplay"></span>
+ // </div>
+ // <div id="bottomNav">
+ // <a href="#" id="bottomNavClose">
+ // <img src="images/close.gif">
+ // </a>
+ // </div>
+ // </div>
+ // </div>
+ // </div>
+
+
+ var objBody = $$('body')[0];
+
+ objBody.appendChild(Builder.node('div',{id:'overlay'}));
+
+ objBody.appendChild(Builder.node('div',{id:'lightbox'}, [
+ Builder.node('div',{id:'outerImageContainer'},
+ Builder.node('div',{id:'imageContainer'}, [
+ Builder.node('img',{id:'lightboxImage'}),
+ Builder.node('div',{id:'hoverNav'}, [
+ Builder.node('a',{id:'prevLink', href: '#' }),
+ Builder.node('a',{id:'nextLink', href: '#' })
+ ]),
+ Builder.node('div',{id:'loading'},
+ Builder.node('a',{id:'loadingLink', href: '#' },
+ Builder.node('img', {src: LightboxOptions.fileLoadingImage})
+ )
+ )
+ ])
+ ),
+ Builder.node('div', {id:'imageDataContainer'},
+ Builder.node('div',{id:'imageData'}, [
+ Builder.node('div',{id:'imageDetails'}, [
+ Builder.node('span',{id:'caption'}),
+ Builder.node('span',{id:'numberDisplay'})
+ ]),
+ Builder.node('div',{id:'bottomNav'},
+ Builder.node('a',{id:'bottomNavClose', href: '#' },
+ Builder.node('img', { src: LightboxOptions.fileBottomNavCloseImage })
+ )
+ )
+ ])
+ )
+ ]));
+
+
+ $('overlay').hide().observe('click', (function() { this.end(); }).bind(this));
+ $('lightbox').hide().observe('click', (function(event) { if (event.element().id == 'lightbox') this.end(); }).bind(this));
+ $('outerImageContainer').setStyle({ width: size, height: size });
+ $('prevLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage - 1); }).bindAsEventListener(this));
+ $('nextLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage + 1); }).bindAsEventListener(this));
+ $('loadingLink').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+ $('bottomNavClose').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+
+ var th = this;
+ (function(){
+ var ids =
+ 'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' +
+ 'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';
+ $w(ids).each(function(id){ th[id] = $(id); });
+ }).defer();
+ },
+
+ //
+ // updateImageList()
+ // Loops through anchor tags looking for 'lightbox' references and applies onclick
+ // events to appropriate links. You can rerun after dynamically adding images w/ajax.
+ //
+ updateImageList: function() {
+ this.updateImageList = Prototype.emptyFunction;
+
+ document.observe('click', (function(event){
+ var target = event.findElement('a[rel^=lightbox]') || event.findElement('area[rel^=lightbox]');
+ if (target) {
+ event.stop();
+ this.start(target);
+ }
+ }).bind(this));
+ },
+
+ //
+ // start()
+ // Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
+ //
+ start: function(imageLink) {
+
+ $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'hidden' });
+
+ // stretch overlay to fill page and fade in
+ var arrayPageSize = this.getPageSize();
+ $('overlay').setStyle({ width: arrayPageSize[0] + 'px', height: arrayPageSize[1] + 'px' });
+
+ new Effect.Appear(this.overlay, { duration: this.overlayDuration, from: 0.0, to: LightboxOptions.overlayOpacity });
+
+ this.imageArray = [];
+ var imageNum = 0;
+
+ if ((imageLink.rel == 'lightbox')){
+ // if image is NOT part of a set, add single image to imageArray
+ this.imageArray.push([imageLink.href, imageLink.title]);
+ } else {
+ // if image is part of a set..
+ this.imageArray =
+ $$(imageLink.tagName + '[href][rel="' + imageLink.rel + '"]').
+ collect(function(anchor){ return [anchor.href, anchor.title]; }).
+ uniq();
+
+ while (this.imageArray[imageNum][0] != imageLink.href) { imageNum++; }
+ }
+
+ // calculate top and left offset for the lightbox
+ var arrayPageScroll = document.viewport.getScrollOffsets();
+ var lightboxTop = arrayPageScroll[1] + (document.viewport.getHeight() / 10);
+ var lightboxLeft = arrayPageScroll[0];
+ this.lightbox.setStyle({ top: lightboxTop + 'px', left: lightboxLeft + 'px' }).show();
+
+ this.changeImage(imageNum);
+ },
+
+ //
+ // changeImage()
+ // Hide most elements and preload image in preparation for resizing image container.
+ //
+ changeImage: function(imageNum) {
+
+ this.activeImage = imageNum; // update global var
+
+ // hide elements during transition
+ if (LightboxOptions.animate) this.loading.show();
+ this.lightboxImage.hide();
+ this.hoverNav.hide();
+ this.prevLink.hide();
+ this.nextLink.hide();
+ // HACK: Opera9 does not currently support scriptaculous opacity and appear fx
+ this.imageDataContainer.setStyle({opacity: .0001});
+ this.numberDisplay.hide();
+
+ var imgPreloader = new Image();
+
+ // once image is preloaded, resize image container
+
+
+ imgPreloader.onload = (function(){
+ this.lightboxImage.src = this.imageArray[this.activeImage][0];
+ this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
+ }).bind(this);
+ imgPreloader.src = this.imageArray[this.activeImage][0];
+ },
+
+ //
+ // resizeImageContainer()
+ //
+ resizeImageContainer: function(imgWidth, imgHeight) {
+
+ // get current width and height
+ var widthCurrent = this.outerImageContainer.getWidth();
+ var heightCurrent = this.outerImageContainer.getHeight();
+
+ // get new width and height
+ var widthNew = (imgWidth + LightboxOptions.borderSize * 2);
+ var heightNew = (imgHeight + LightboxOptions.borderSize * 2);
+
+ // scalars based on change from old to new
+ var xScale = (widthNew / widthCurrent) * 100;
+ var yScale = (heightNew / heightCurrent) * 100;
+
+ // calculate size difference between new and old image, and resize if necessary
+ var wDiff = widthCurrent - widthNew;
+ var hDiff = heightCurrent - heightNew;
+
+ if (hDiff != 0) new Effect.Scale(this.outerImageContainer, yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'});
+ if (wDiff != 0) new Effect.Scale(this.outerImageContainer, xScale, {scaleY: false, duration: this.resizeDuration, delay: this.resizeDuration});
+
+ // if new and old image are same size and no scaling transition is necessary,
+ // do a quick pause to prevent image flicker.
+ var timeout = 0;
+ if ((hDiff == 0) && (wDiff == 0)){
+ timeout = 100;
+ if (Prototype.Browser.IE) timeout = 250;
+ }
+
+ (function(){
+ this.prevLink.setStyle({ height: imgHeight + 'px' });
+ this.nextLink.setStyle({ height: imgHeight + 'px' });
+ this.imageDataContainer.setStyle({ width: widthNew + 'px' });
+
+ this.showImage();
+ }).bind(this).delay(timeout / 1000);
+ },
+
+ //
+ // showImage()
+ // Display image and begin preloading neighbors.
+ //
+ showImage: function(){
+ this.loading.hide();
+ new Effect.Appear(this.lightboxImage, {
+ duration: this.resizeDuration,
+ queue: 'end',
+ afterFinish: (function(){ this.updateDetails(); }).bind(this)
+ });
+ this.preloadNeighborImages();
+ },
+
+ //
+ // updateDetails()
+ // Display caption, image number, and bottom nav.
+ //
+ updateDetails: function() {
+
+ // if caption is not null
+ if (this.imageArray[this.activeImage][1] != ""){
+ this.caption.update(this.imageArray[this.activeImage][1]).show();
+ }
+
+ // if image is part of set display 'Image x of x'
+ if (this.imageArray.length > 1){
+ this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + ' ' + this.imageArray.length).show();
+ }
+
+ new Effect.Parallel(
+ [
+ new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resizeDuration, from: 0.0, to: 1.0 }),
+ new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resizeDuration })
+ ],
+ {
+ duration: this.resizeDuration,
+ afterFinish: (function() {
+ // update overlay size and update nav
+ var arrayPageSize = this.getPageSize();
+ this.overlay.setStyle({ height: arrayPageSize[1] + 'px' });
+ this.updateNav();
+ }).bind(this)
+ }
+ );
+ },
+
+ //
+ // updateNav()
+ // Display appropriate previous and next hover navigation.
+ //
+ updateNav: function() {
+
+ this.hoverNav.show();
+
+ // if not first image in set, display prev image button
+ if (this.activeImage > 0) this.prevLink.show();
+
+ // if not last image in set, display next image button
+ if (this.activeImage < (this.imageArray.length - 1)) this.nextLink.show();
+
+ this.enableKeyboardNav();
+ },
+
+ //
+ // enableKeyboardNav()
+ //
+ enableKeyboardNav: function() {
+ document.observe('keydown', this.keyboardAction);
+ },
+
+ //
+ // disableKeyboardNav()
+ //
+ disableKeyboardNav: function() {
+ document.stopObserving('keydown', this.keyboardAction);
+ },
+
+ //
+ // keyboardAction()
+ //
+ keyboardAction: function(event) {
+ var keycode = event.keyCode;
+
+ var escapeKey;
+ if (event.DOM_VK_ESCAPE) { // mozilla
+ escapeKey = event.DOM_VK_ESCAPE;
+ } else { // ie
+ escapeKey = 27;
+ }
+
+ var key = String.fromCharCode(keycode).toLowerCase();
+
+ if (key.match(/x|o|c/) || (keycode == escapeKey)){ // close lightbox
+ this.end();
+ } else if ((key == 'p') || (keycode == 37)){ // display previous image
+ if (this.activeImage != 0){
+ this.disableKeyboardNav();
+ this.changeImage(this.activeImage - 1);
+ }
+ } else if ((key == 'n') || (keycode == 39)){ // display next image
+ if (this.activeImage != (this.imageArray.length - 1)){
+ this.disableKeyboardNav();
+ this.changeImage(this.activeImage + 1);
+ }
+ }
+ },
+
+ //
+ // preloadNeighborImages()
+ // Preload previous and next images.
+ //
+ preloadNeighborImages: function(){
+ var preloadNextImage, preloadPrevImage;
+ if (this.imageArray.length > this.activeImage + 1){
+ preloadNextImage = new Image();
+ preloadNextImage.src = this.imageArray[this.activeImage + 1][0];
+ }
+ if (this.activeImage > 0){
+ preloadPrevImage = new Image();
+ preloadPrevImage.src = this.imageArray[this.activeImage - 1][0];
+ }
+
+ },
+
+ //
+ // end()
+ //
+ end: function() {
+ this.disableKeyboardNav();
+ this.lightbox.hide();
+ new Effect.Fade(this.overlay, { duration: this.overlayDuration });
+ $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'visible' });
+ },
+
+ //
+ // getPageSize()
+ //
+ getPageSize: function() {
+
+ var xScroll, yScroll;
+
+ if (window.innerHeight && window.scrollMaxY) {
+ xScroll = window.innerWidth + window.scrollMaxX;
+ yScroll = window.innerHeight + window.scrollMaxY;
+ } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+ xScroll = document.body.scrollWidth;
+ yScroll = document.body.scrollHeight;
+ } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+ xScroll = document.body.offsetWidth;
+ yScroll = document.body.offsetHeight;
+ }
+
+ var windowWidth, windowHeight;
+
+ if (self.innerHeight) { // all except Explorer
+ if(document.documentElement.clientWidth){
+ windowWidth = document.documentElement.clientWidth;
+ } else {
+ windowWidth = self.innerWidth;
+ }
+ windowHeight = self.innerHeight;
+ } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+ windowWidth = document.documentElement.clientWidth;
+ windowHeight = document.documentElement.clientHeight;
+ } else if (document.body) { // other Explorers
+ windowWidth = document.body.clientWidth;
+ windowHeight = document.body.clientHeight;
+ }
+
+ // for small pages with total height less then height of the viewport
+ if(yScroll < windowHeight){
+ pageHeight = windowHeight;
+ } else {
+ pageHeight = yScroll;
+ }
+
+ // for small pages with total width less then width of the viewport
+ if(xScroll < windowWidth){
+ pageWidth = xScroll;
+ } else {
+ pageWidth = windowWidth;
+ }
+
+ return [pageWidth,pageHeight];
+ }
+}
+
+document.observe('dom:loaded', function () { new Lightbox(); });
diff --git a/js/prototype/prototype.js b/js/prototype/prototype.js
new file mode 100644
index 0000000..04a4779
--- /dev/null
+++ b/js/prototype/prototype.js
@@ -0,0 +1,6081 @@
+/* Prototype JavaScript framework, version 1.7
+ * (c) 2005-2010 Sam Stephenson
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+
+ Version: '1.7',
+
+ Browser: (function(){
+ var ua = navigator.userAgent;
+ var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+ return {
+ IE: !!window.attachEvent && !isOpera,
+ Opera: isOpera,
+ WebKit: ua.indexOf('AppleWebKit/') > -1,
+ Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+ MobileSafari: /Apple.*Mobile/.test(ua)
+ }
+ })(),
+
+ BrowserFeatures: {
+ XPath: !!document.evaluate,
+
+ SelectorsAPI: !!document.querySelector,
+
+ ElementExtensions: (function() {
+ var constructor = window.Element || window.HTMLElement;
+ return !!(constructor && constructor.prototype);
+ })(),
+ SpecificElementExtensions: (function() {
+ if (typeof window.HTMLDivElement !== 'undefined')
+ return true;
+
+ var div = document.createElement('div'),
+ form = document.createElement('form'),
+ isSupported = false;
+
+ if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+ isSupported = true;
+ }
+
+ div = form = null;
+
+ return isSupported;
+ })()
+ },
+
+ ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+ JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+ emptyFunction: function() { },
+
+ K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+ Prototype.BrowserFeatures.SpecificElementExtensions = false;
+/* Based on Alex Arnell's inheritance implementation. */
+
+var Class = (function() {
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
+ function subclass() {};
+ function create() {
+ var parent = null, properties = $A(arguments);
+ if (Object.isFunction(properties[0]))
+ parent = properties.shift();
+
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ Object.extend(klass, Class.Methods);
+ klass.superclass = parent;
+ klass.subclasses = [];
+
+ if (parent) {
+ subclass.prototype = parent.prototype;
+ klass.prototype = new subclass;
+ parent.subclasses.push(klass);
+ }
+
+ for (var i = 0, length = properties.length; i < length; i++)
+ klass.addMethods(properties[i]);
+
+ if (!klass.prototype.initialize)
+ klass.prototype.initialize = Prototype.emptyFunction;
+
+ klass.prototype.constructor = klass;
+ return klass;
+ }
+
+ function addMethods(source) {
+ var ancestor = this.superclass && this.superclass.prototype,
+ properties = Object.keys(source);
+
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString != Object.prototype.toString)
+ properties.push("toString");
+ if (source.valueOf != Object.prototype.valueOf)
+ properties.push("valueOf");
+ }
+
+ for (var i = 0, length = properties.length; i < length; i++) {
+ var property = properties[i], value = source[property];
+ if (ancestor && Object.isFunction(value) &&
+ value.argumentNames()[0] == "$super") {
+ var method = value;
+ value = (function(m) {
+ return function() { return ancestor[m].apply(this, arguments); };
+ })(property).wrap(method);
+
+ value.valueOf = method.valueOf.bind(method);
+ value.toString = method.toString.bind(method);
+ }
+ this.prototype[property] = value;
+ }
+
+ return this;
+ }
+
+ return {
+ create: create,
+ Methods: {
+ addMethods: addMethods
+ }
+ };
+})();
+(function() {
+
+ var _toString = Object.prototype.toString,
+ NULL_TYPE = 'Null',
+ UNDEFINED_TYPE = 'Undefined',
+ BOOLEAN_TYPE = 'Boolean',
+ NUMBER_TYPE = 'Number',
+ STRING_TYPE = 'String',
+ OBJECT_TYPE = 'Object',
+ FUNCTION_CLASS = '[object Function]',
+ BOOLEAN_CLASS = '[object Boolean]',
+ NUMBER_CLASS = '[object Number]',
+ STRING_CLASS = '[object String]',
+ ARRAY_CLASS = '[object Array]',
+ DATE_CLASS = '[object Date]',
+ NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
+ typeof JSON.stringify === 'function' &&
+ JSON.stringify(0) === '0' &&
+ typeof JSON.stringify(Prototype.K) === 'undefined';
+
+ function Type(o) {
+ switch(o) {
+ case null: return NULL_TYPE;
+ case (void 0): return UNDEFINED_TYPE;
+ }
+ var type = typeof o;
+ switch(type) {
+ case 'boolean': return BOOLEAN_TYPE;
+ case 'number': return NUMBER_TYPE;
+ case 'string': return STRING_TYPE;
+ }
+ return OBJECT_TYPE;
+ }
+
+ function extend(destination, source) {
+ for (var property in source)
+ destination[property] = source[property];
+ return destination;
+ }
+
+ function inspect(object) {
+ try {
+ if (isUndefined(object)) return 'undefined';
+ if (object === null) return 'null';
+ return object.inspect ? object.inspect() : String(object);
+ } catch (e) {
+ if (e instanceof RangeError) return '...';
+ throw e;
+ }
+ }
+
+ function toJSON(value) {
+ return Str('', { '': value }, []);
+ }
+
+ function Str(key, holder, stack) {
+ var value = holder[key],
+ type = typeof value;
+
+ if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+ var _class = _toString.call(value);
+
+ switch (_class) {
+ case NUMBER_CLASS:
+ case BOOLEAN_CLASS:
+ case STRING_CLASS:
+ value = value.valueOf();
+ }
+
+ switch (value) {
+ case null: return 'null';
+ case true: return 'true';
+ case false: return 'false';
+ }
+
+ type = typeof value;
+ switch (type) {
+ case 'string':
+ return value.inspect(true);
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+ case 'object':
+
+ for (var i = 0, length = stack.length; i < length; i++) {
+ if (stack[i] === value) { throw new TypeError(); }
+ }
+ stack.push(value);
+
+ var partial = [];
+ if (_class === ARRAY_CLASS) {
+ for (var i = 0, length = value.length; i < length; i++) {
+ var str = Str(i, value, stack);
+ partial.push(typeof str === 'undefined' ? 'null' : str);
+ }
+ partial = '[' + partial.join(',') + ']';
+ } else {
+ var keys = Object.keys(value);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i], str = Str(key, value, stack);
+ if (typeof str !== "undefined") {
+ partial.push(key.inspect(true)+ ':' + str);
+ }
+ }
+ partial = '{' + partial.join(',') + '}';
+ }
+ stack.pop();
+ return partial;
+ }
+ }
+
+ function stringify(object) {
+ return JSON.stringify(object);
+ }
+
+ function toQueryString(object) {
+ return $H(object).toQueryString();
+ }
+
+ function toHTML(object) {
+ return object && object.toHTML ? object.toHTML() : String.interpret(object);
+ }
+
+ function keys(object) {
+ if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
+ var results = [];
+ for (var property in object) {
+ if (object.hasOwnProperty(property)) {
+ results.push(property);
+ }
+ }
+ return results;
+ }
+
+ function values(object) {
+ var results = [];
+ for (var property in object)
+ results.push(object[property]);
+ return results;
+ }
+
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ function isElement(object) {
+ return !!(object && object.nodeType == 1);
+ }
+
+ function isArray(object) {
+ return _toString.call(object) === ARRAY_CLASS;
+ }
+
+ var hasNativeIsArray = (typeof Array.isArray == 'function')
+ && Array.isArray([]) && !Array.isArray({});
+
+ if (hasNativeIsArray) {
+ isArray = Array.isArray;
+ }
+
+ function isHash(object) {
+ return object instanceof Hash;
+ }
+
+ function isFunction(object) {
+ return _toString.call(object) === FUNCTION_CLASS;
+ }
+
+ function isString(object) {
+ return _toString.call(object) === STRING_CLASS;
+ }
+
+ function isNumber(object) {
+ return _toString.call(object) === NUMBER_CLASS;
+ }
+
+ function isDate(object) {
+ return _toString.call(object) === DATE_CLASS;
+ }
+
+ function isUndefined(object) {
+ return typeof object === "undefined";
+ }
+
+ extend(Object, {
+ extend: extend,
+ inspect: inspect,
+ toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
+ toQueryString: toQueryString,
+ toHTML: toHTML,
+ keys: Object.keys || keys,
+ values: values,
+ clone: clone,
+ isElement: isElement,
+ isArray: isArray,
+ isHash: isHash,
+ isFunction: isFunction,
+ isString: isString,
+ isNumber: isNumber,
+ isDate: isDate,
+ isUndefined: isUndefined
+ });
+})();
+Object.extend(Function.prototype, (function() {
+ var slice = Array.prototype.slice;
+
+ function update(array, args) {
+ var arrayLength = array.length, length = args.length;
+ while (length--) array[arrayLength + length] = args[length];
+ return array;
+ }
+
+ function merge(array, args) {
+ array = slice.call(array, 0);
+ return update(array, args);
+ }
+
+ function argumentNames() {
+ var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+ .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+ .replace(/\s+/g, '').split(',');
+ return names.length == 1 && !names[0] ? [] : names;
+ }
+
+ function bind(context) {
+ if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+ var __method = this, args = slice.call(arguments, 1);
+ return function() {
+ var a = merge(args, arguments);
+ return __method.apply(context, a);
+ }
+ }
+
+ function bindAsEventListener(context) {
+ var __method = this, args = slice.call(arguments, 1);
+ return function(event) {
+ var a = update([event || window.event], args);
+ return __method.apply(context, a);
+ }
+ }
+
+ function curry() {
+ if (!arguments.length) return this;
+ var __method = this, args = slice.call(arguments, 0);
+ return function() {
+ var a = merge(args, arguments);
+ return __method.apply(this, a);
+ }
+ }
+
+ function delay(timeout) {
+ var __method = this, args = slice.call(arguments, 1);
+ timeout = timeout * 1000;
+ return window.setTimeout(function() {
+ return __method.apply(__method, args);
+ }, timeout);
+ }
+
+ function defer() {
+ var args = update([0.01], arguments);
+ return this.delay.apply(this, args);
+ }
+
+ function wrap(wrapper) {
+ var __method = this;
+ return function() {
+ var a = update([__method.bind(this)], arguments);
+ return wrapper.apply(this, a);
+ }
+ }
+
+ function methodize() {
+ if (this._methodized) return this._methodized;
+ var __method = this;
+ return this._methodized = function() {
+ var a = update([this], arguments);
+ return __method.apply(null, a);
+ };
+ }
+
+ return {
+ argumentNames: argumentNames,
+ bind: bind,
+ bindAsEventListener: bindAsEventListener,
+ curry: curry,
+ delay: delay,
+ defer: defer,
+ wrap: wrap,
+ methodize: methodize
+ }
+})());
+
+
+
+(function(proto) {
+
+
+ function toISOString() {
+ return this.getUTCFullYear() + '-' +
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+ this.getUTCDate().toPaddedString(2) + 'T' +
+ this.getUTCHours().toPaddedString(2) + ':' +
+ this.getUTCMinutes().toPaddedString(2) + ':' +
+ this.getUTCSeconds().toPaddedString(2) + 'Z';
+ }
+
+
+ function toJSON() {
+ return this.toISOString();
+ }
+
+ if (!proto.toISOString) proto.toISOString = toISOString;
+ if (!proto.toJSON) proto.toJSON = toJSON;
+
+})(Date.prototype);
+
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+ return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+var PeriodicalExecuter = Class.create({
+ initialize: function(callback, frequency) {
+ this.callback = callback;
+ this.frequency = frequency;
+ this.currentlyExecuting = false;
+
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ execute: function() {
+ this.callback(this);
+ },
+
+ stop: function() {
+ if (!this.timer) return;
+ clearInterval(this.timer);
+ this.timer = null;
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.execute();
+ this.currentlyExecuting = false;
+ } catch(e) {
+ this.currentlyExecuting = false;
+ throw e;
+ }
+ }
+ }
+});
+Object.extend(String, {
+ interpret: function(value) {
+ return value == null ? '' : String(value);
+ },
+ specialChar: {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '\\': '\\\\'
+ }
+});
+
+Object.extend(String.prototype, (function() {
+ var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
+ typeof JSON.parse === 'function' &&
+ JSON.parse('{"test": true}').test;
+
+ function prepareReplacement(replacement) {
+ if (Object.isFunction(replacement)) return replacement;
+ var template = new Template(replacement);
+ return function(match) { return template.evaluate(match) };
+ }
+
+ function gsub(pattern, replacement) {
+ var result = '', source = this, match;
+ replacement = prepareReplacement(replacement);
+
+ if (Object.isString(pattern))
+ pattern = RegExp.escape(pattern);
+
+ if (!(pattern.length || pattern.source)) {
+ replacement = replacement('');
+ return replacement + source.split('').join(replacement) + replacement;
+ }
+
+ while (source.length > 0) {
+ if (match = source.match(pattern)) {
+ result += source.slice(0, match.index);
+ result += String.interpret(replacement(match));
+ source = source.slice(match.index + match[0].length);
+ } else {
+ result += source, source = '';
+ }
+ }
+ return result;
+ }
+
+ function sub(pattern, replacement, count) {
+ replacement = prepareReplacement(replacement);
+ count = Object.isUndefined(count) ? 1 : count;
+
+ return this.gsub(pattern, function(match) {
+ if (--count < 0) return match[0];
+ return replacement(match);
+ });
+ }
+
+ function scan(pattern, iterator) {
+ this.gsub(pattern, iterator);
+ return String(this);
+ }
+
+ function truncate(length, truncation) {
+ length = length || 30;
+ truncation = Object.isUndefined(truncation) ? '...' : truncation;
+ return this.length > length ?
+ this.slice(0, length - truncation.length) + truncation : String(this);
+ }
+
+ function strip() {
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
+ }
+
+ function stripTags() {
+ return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
+ }
+
+ function stripScripts() {
+ return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+ }
+
+ function extractScripts() {
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
+ matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+ return (this.match(matchAll) || []).map(function(scriptTag) {
+ return (scriptTag.match(matchOne) || ['', ''])[1];
+ });
+ }
+
+ function evalScripts() {
+ return this.extractScripts().map(function(script) { return eval(script) });
+ }
+
+ function escapeHTML() {
+ return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+ }
+
+ function unescapeHTML() {
+ return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
+ }
+
+
+ function toQueryParams(separator) {
+ var match = this.strip().match(/([^?#]*)(#.*)?$/);
+ if (!match) return { };
+
+ return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+ if ((pair = pair.split('='))[0]) {
+ var key = decodeURIComponent(pair.shift()),
+ value = pair.length > 1 ? pair.join('=') : pair[0];
+
+ if (value != undefined) value = decodeURIComponent(value);
+
+ if (key in hash) {
+ if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+ hash[key].push(value);
+ }
+ else hash[key] = value;
+ }
+ return hash;
+ });
+ }
+
+ function toArray() {
+ return this.split('');
+ }
+
+ function succ() {
+ return this.slice(0, this.length - 1) +
+ String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+ }
+
+ function times(count) {
+ return count < 1 ? '' : new Array(count + 1).join(this);
+ }
+
+ function camelize() {
+ return this.replace(/-+(.)?/g, function(match, chr) {
+ return chr ? chr.toUpperCase() : '';
+ });
+ }
+
+ function capitalize() {
+ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+ }
+
+ function underscore() {
+ return this.replace(/::/g, '/')
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+ .replace(/-/g, '_')
+ .toLowerCase();
+ }
+
+ function dasherize() {
+ return this.replace(/_/g, '-');
+ }
+
+ function inspect(useDoubleQuotes) {
+ var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
+ if (character in String.specialChar) {
+ return String.specialChar[character];
+ }
+ return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
+ });
+ if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+ return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+ }
+
+ function unfilterJSON(filter) {
+ return this.replace(filter || Prototype.JSONFilter, '$1');
+ }
+
+ function isJSON() {
+ var str = this;
+ if (str.blank()) return false;
+ str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
+ str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
+ str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+ return (/^[\],:{}\s]*$/).test(str);
+ }
+
+ function evalJSON(sanitize) {
+ var json = this.unfilterJSON(),
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ if (cx.test(json)) {
+ json = json.replace(cx, function (a) {
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+ try {
+ if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+ } catch (e) { }
+ throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+ }
+
+ function parseJSON() {
+ var json = this.unfilterJSON();
+ return JSON.parse(json);
+ }
+
+ function include(pattern) {
+ return this.indexOf(pattern) > -1;
+ }
+
+ function startsWith(pattern) {
+ return this.lastIndexOf(pattern, 0) === 0;
+ }
+
+ function endsWith(pattern) {
+ var d = this.length - pattern.length;
+ return d >= 0 && this.indexOf(pattern, d) === d;
+ }
+
+ function empty() {
+ return this == '';
+ }
+
+ function blank() {
+ return /^\s*$/.test(this);
+ }
+
+ function interpolate(object, pattern) {
+ return new Template(this, pattern).evaluate(object);
+ }
+
+ return {
+ gsub: gsub,
+ sub: sub,
+ scan: scan,
+ truncate: truncate,
+ strip: String.prototype.trim || strip,
+ stripTags: stripTags,
+ stripScripts: stripScripts,
+ extractScripts: extractScripts,
+ evalScripts: evalScripts,
+ escapeHTML: escapeHTML,
+ unescapeHTML: unescapeHTML,
+ toQueryParams: toQueryParams,
+ parseQuery: toQueryParams,
+ toArray: toArray,
+ succ: succ,
+ times: times,
+ camelize: camelize,
+ capitalize: capitalize,
+ underscore: underscore,
+ dasherize: dasherize,
+ inspect: inspect,
+ unfilterJSON: unfilterJSON,
+ isJSON: isJSON,
+ evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
+ include: include,
+ startsWith: startsWith,
+ endsWith: endsWith,
+ empty: empty,
+ blank: blank,
+ interpolate: interpolate
+ };
+})());
+
+var Template = Class.create({
+ initialize: function(template, pattern) {
+ this.template = template.toString();
+ this.pattern = pattern || Template.Pattern;
+ },
+
+ evaluate: function(object) {
+ if (object && Object.isFunction(object.toTemplateReplacements))
+ object = object.toTemplateReplacements();
+
+ return this.template.gsub(this.pattern, function(match) {
+ if (object == null) return (match[1] + '');
+
+ var before = match[1] || '';
+ if (before == '\\') return match[2];
+
+ var ctx = object, expr = match[3],
+ pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+
+ match = pattern.exec(expr);
+ if (match == null) return before;
+
+ while (match != null) {
+ var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
+ ctx = ctx[comp];
+ if (null == ctx || '' == match[3]) break;
+ expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+ match = pattern.exec(expr);
+ }
+
+ return before + String.interpret(ctx);
+ });
+ }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = (function() {
+ function each(iterator, context) {
+ var index = 0;
+ try {
+ this._each(function(value) {
+ iterator.call(context, value, index++);
+ });
+ } catch (e) {
+ if (e != $break) throw e;
+ }
+ return this;
+ }
+
+ function eachSlice(number, iterator, context) {
+ var index = -number, slices = [], array = this.toArray();
+ if (number < 1) return array;
+ while ((index += number) < array.length)
+ slices.push(array.slice(index, index+number));
+ return slices.collect(iterator, context);
+ }
+
+ function all(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result = true;
+ this.each(function(value, index) {
+ result = result && !!iterator.call(context, value, index);
+ if (!result) throw $break;
+ });
+ return result;
+ }
+
+ function any(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result = false;
+ this.each(function(value, index) {
+ if (result = !!iterator.call(context, value, index))
+ throw $break;
+ });
+ return result;
+ }
+
+ function collect(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var results = [];
+ this.each(function(value, index) {
+ results.push(iterator.call(context, value, index));
+ });
+ return results;
+ }
+
+ function detect(iterator, context) {
+ var result;
+ this.each(function(value, index) {
+ if (iterator.call(context, value, index)) {
+ result = value;
+ throw $break;
+ }
+ });
+ return result;
+ }
+
+ function findAll(iterator, context) {
+ var results = [];
+ this.each(function(value, index) {
+ if (iterator.call(context, value, index))
+ results.push(value);
+ });
+ return results;
+ }
+
+ function grep(filter, iterator, context) {
+ iterator = iterator || Prototype.K;
+ var results = [];
+
+ if (Object.isString(filter))
+ filter = new RegExp(RegExp.escape(filter));
+
+ this.each(function(value, index) {
+ if (filter.match(value))
+ results.push(iterator.call(context, value, index));
+ });
+ return results;
+ }
+
+ function include(object) {
+ if (Object.isFunction(this.indexOf))
+ if (this.indexOf(object) != -1) return true;
+
+ var found = false;
+ this.each(function(value) {
+ if (value == object) {
+ found = true;
+ throw $break;
+ }
+ });
+ return found;
+ }
+
+ function inGroupsOf(number, fillWith) {
+ fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+ return this.eachSlice(number, function(slice) {
+ while(slice.length < number) slice.push(fillWith);
+ return slice;
+ });
+ }
+
+ function inject(memo, iterator, context) {
+ this.each(function(value, index) {
+ memo = iterator.call(context, memo, value, index);
+ });
+ return memo;
+ }
+
+ function invoke(method) {
+ var args = $A(arguments).slice(1);
+ return this.map(function(value) {
+ return value[method].apply(value, args);
+ });
+ }
+
+ function max(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator.call(context, value, index);
+ if (result == null || value >= result)
+ result = value;
+ });
+ return result;
+ }
+
+ function min(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var result;
+ this.each(function(value, index) {
+ value = iterator.call(context, value, index);
+ if (result == null || value < result)
+ result = value;
+ });
+ return result;
+ }
+
+ function partition(iterator, context) {
+ iterator = iterator || Prototype.K;
+ var trues = [], falses = [];
+ this.each(function(value, index) {
+ (iterator.call(context, value, index) ?
+ trues : falses).push(value);
+ });
+ return [trues, falses];
+ }
+
+ function pluck(property) {
+ var results = [];
+ this.each(function(value) {
+ results.push(value[property]);
+ });
+ return results;
+ }
+
+ function reject(iterator, context) {
+ var results = [];
+ this.each(function(value, index) {
+ if (!iterator.call(context, value, index))
+ results.push(value);
+ });
+ return results;
+ }
+
+ function sortBy(iterator, context) {
+ return this.map(function(value, index) {
+ return {
+ value: value,
+ criteria: iterator.call(context, value, index)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }).pluck('value');
+ }
+
+ function toArray() {
+ return this.map();
+ }
+
+ function zip() {
+ var iterator = Prototype.K, args = $A(arguments);
+ if (Object.isFunction(args.last()))
+ iterator = args.pop();
+
+ var collections = [this].concat(args).map($A);
+ return this.map(function(value, index) {
+ return iterator(collections.pluck(index));
+ });
+ }
+
+ function size() {
+ return this.toArray().length;
+ }
+
+ function inspect() {
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+
+
+
+
+
+
+
+
+
+ return {
+ each: each,
+ eachSlice: eachSlice,
+ all: all,
+ every: all,
+ any: any,
+ some: any,
+ collect: collect,
+ map: collect,
+ detect: detect,
+ findAll: findAll,
+ select: findAll,
+ filter: findAll,
+ grep: grep,
+ include: include,
+ member: include,
+ inGroupsOf: inGroupsOf,
+ inject: inject,
+ invoke: invoke,
+ max: max,
+ min: min,
+ partition: partition,
+ pluck: pluck,
+ reject: reject,
+ sortBy: sortBy,
+ toArray: toArray,
+ entries: toArray,
+ zip: zip,
+ size: size,
+ inspect: inspect,
+ find: detect
+ };
+})();
+
+function $A(iterable) {
+ if (!iterable) return [];
+ if ('toArray' in Object(iterable)) return iterable.toArray();
+ var length = iterable.length || 0, results = new Array(length);
+ while (length--) results[length] = iterable[length];
+ return results;
+}
+
+
+function $w(string) {
+ if (!Object.isString(string)) return [];
+ string = string.strip();
+ return string ? string.split(/\s+/) : [];
+}
+
+Array.from = $A;
+
+
+(function() {
+ var arrayProto = Array.prototype,
+ slice = arrayProto.slice,
+ _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
+
+ function each(iterator, context) {
+ for (var i = 0, length = this.length >>> 0; i < length; i++) {
+ if (i in this) iterator.call(context, this[i], i, this);
+ }
+ }
+ if (!_each) _each = each;
+
+ function clear() {
+ this.length = 0;
+ return this;
+ }
+
+ function first() {
+ return this[0];
+ }
+
+ function last() {
+ return this[this.length - 1];
+ }
+
+ function compact() {
+ return this.select(function(value) {
+ return value != null;
+ });
+ }
+
+ function flatten() {
+ return this.inject([], function(array, value) {
+ if (Object.isArray(value))
+ return array.concat(value.flatten());
+ array.push(value);
+ return array;
+ });
+ }
+
+ function without() {
+ var values = slice.call(arguments, 0);
+ return this.select(function(value) {
+ return !values.include(value);
+ });
+ }
+
+ function reverse(inline) {
+ return (inline === false ? this.toArray() : this)._reverse();
+ }
+
+ function uniq(sorted) {
+ return this.inject([], function(array, value, index) {
+ if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+ array.push(value);
+ return array;
+ });
+ }
+
+ function intersect(array) {
+ return this.uniq().findAll(function(item) {
+ return array.detect(function(value) { return item === value });
+ });
+ }
+
+
+ function clone() {
+ return slice.call(this, 0);
+ }
+
+ function size() {
+ return this.length;
+ }
+
+ function inspect() {
+ return '[' + this.map(Object.inspect).join(', ') + ']';
+ }
+
+ function indexOf(item, i) {
+ i || (i = 0);
+ var length = this.length;
+ if (i < 0) i = length + i;
+ for (; i < length; i++)
+ if (this[i] === item) return i;
+ return -1;
+ }
+
+ function lastIndexOf(item, i) {
+ i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+ var n = this.slice(0, i).reverse().indexOf(item);
+ return (n < 0) ? n : i - n - 1;
+ }
+
+ function concat() {
+ var array = slice.call(this, 0), item;
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ item = arguments[i];
+ if (Object.isArray(item) && !('callee' in item)) {
+ for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
+ array.push(item[j]);
+ } else {
+ array.push(item);
+ }
+ }
+ return array;
+ }
+
+ Object.extend(arrayProto, Enumerable);
+
+ if (!arrayProto._reverse)
+ arrayProto._reverse = arrayProto.reverse;
+
+ Object.extend(arrayProto, {
+ _each: _each,
+ clear: clear,
+ first: first,
+ last: last,
+ compact: compact,
+ flatten: flatten,
+ without: without,
+ reverse: reverse,
+ uniq: uniq,
+ intersect: intersect,
+ clone: clone,
+ toArray: clone,
+ size: size,
+ inspect: inspect
+ });
+
+ var CONCAT_ARGUMENTS_BUGGY = (function() {
+ return [].concat(arguments)[0][0] !== 1;
+ })(1,2)
+
+ if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
+
+ if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
+ if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
+})();
+function $H(object) {
+ return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+ function initialize(object) {
+ this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+ }
+
+
+ function _each(iterator) {
+ for (var key in this._object) {
+ var value = this._object[key], pair = [key, value];
+ pair.key = key;
+ pair.value = value;
+ iterator(pair);
+ }
+ }
+
+ function set(key, value) {
+ return this._object[key] = value;
+ }
+
+ function get(key) {
+ if (this._object[key] !== Object.prototype[key])
+ return this._object[key];
+ }
+
+ function unset(key) {
+ var value = this._object[key];
+ delete this._object[key];
+ return value;
+ }
+
+ function toObject() {
+ return Object.clone(this._object);
+ }
+
+
+
+ function keys() {
+ return this.pluck('key');
+ }
+
+ function values() {
+ return this.pluck('value');
+ }
+
+ function index(value) {
+ var match = this.detect(function(pair) {
+ return pair.value === value;
+ });
+ return match && match.key;
+ }
+
+ function merge(object) {
+ return this.clone().update(object);
+ }
+
+ function update(object) {
+ return new Hash(object).inject(this, function(result, pair) {
+ result.set(pair.key, pair.value);
+ return result;
+ });
+ }
+
+ function toQueryPair(key, value) {
+ if (Object.isUndefined(value)) return key;
+ return key + '=' + encodeURIComponent(String.interpret(value));
+ }
+
+ function toQueryString() {
+ return this.inject([], function(results, pair) {
+ var key = encodeURIComponent(pair.key), values = pair.value;
+
+ if (values && typeof values == 'object') {
+ if (Object.isArray(values)) {
+ var queryValues = [];
+ for (var i = 0, len = values.length, value; i < len; i++) {
+ value = values[i];
+ queryValues.push(toQueryPair(key, value));
+ }
+ return results.concat(queryValues);
+ }
+ } else results.push(toQueryPair(key, values));
+ return results;
+ }).join('&');
+ }
+
+ function inspect() {
+ return '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ }
+
+ function clone() {
+ return new Hash(this);
+ }
+
+ return {
+ initialize: initialize,
+ _each: _each,
+ set: set,
+ get: get,
+ unset: unset,
+ toObject: toObject,
+ toTemplateReplacements: toObject,
+ keys: keys,
+ values: values,
+ index: index,
+ merge: merge,
+ update: update,
+ toQueryString: toQueryString,
+ inspect: inspect,
+ toJSON: toObject,
+ clone: clone
+ };
+})());
+
+Hash.from = $H;
+Object.extend(Number.prototype, (function() {
+ function toColorPart() {
+ return this.toPaddedString(2, 16);
+ }
+
+ function succ() {
+ return this + 1;
+ }
+
+ function times(iterator, context) {
+ $R(0, this, true).each(iterator, context);
+ return this;
+ }
+
+ function toPaddedString(length, radix) {
+ var string = this.toString(radix || 10);
+ return '0'.times(length - string.length) + string;
+ }
+
+ function abs() {
+ return Math.abs(this);
+ }
+
+ function round() {
+ return Math.round(this);
+ }
+
+ function ceil() {
+ return Math.ceil(this);
+ }
+
+ function floor() {
+ return Math.floor(this);
+ }
+
+ return {
+ toColorPart: toColorPart,
+ succ: succ,
+ times: times,
+ toPaddedString: toPaddedString,
+ abs: abs,
+ round: round,
+ ceil: ceil,
+ floor: floor
+ };
+})());
+
+function $R(start, end, exclusive) {
+ return new ObjectRange(start, end, exclusive);
+}
+
+var ObjectRange = Class.create(Enumerable, (function() {
+ function initialize(start, end, exclusive) {
+ this.start = start;
+ this.end = end;
+ this.exclusive = exclusive;
+ }
+
+ function _each(iterator) {
+ var value = this.start;
+ while (this.include(value)) {
+ iterator(value);
+ value = value.succ();
+ }
+ }
+
+ function include(value) {
+ if (value < this.start)
+ return false;
+ if (this.exclusive)
+ return value < this.end;
+ return value <= this.end;
+ }
+
+ return {
+ initialize: initialize,
+ _each: _each,
+ include: include
+ };
+})());
+
+
+
+var Abstract = { };
+
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) { }
+ }
+
+ return returnValue;
+ }
+};
+
+var Ajax = {
+ getTransport: function() {
+ return Try.these(
+ function() {return new XMLHttpRequest()},
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+ ) || false;
+ },
+
+ activeRequestCount: 0
+};
+
+Ajax.Responders = {
+ responders: [],
+
+ _each: function(iterator) {
+ this.responders._each(iterator);
+ },
+
+ register: function(responder) {
+ if (!this.include(responder))
+ this.responders.push(responder);
+ },
+
+ unregister: function(responder) {
+ this.responders = this.responders.without(responder);
+ },
+
+ dispatch: function(callback, request, transport, json) {
+ this.each(function(responder) {
+ if (Object.isFunction(responder[callback])) {
+ try {
+ responder[callback].apply(responder, [request, transport, json]);
+ } catch (e) { }
+ }
+ });
+ }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+ onCreate: function() { Ajax.activeRequestCount++ },
+ onComplete: function() { Ajax.activeRequestCount-- }
+});
+Ajax.Base = Class.create({
+ initialize: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ contentType: 'application/x-www-form-urlencoded',
+ encoding: 'UTF-8',
+ parameters: '',
+ evalJSON: true,
+ evalJS: true
+ };
+ Object.extend(this.options, options || { });
+
+ this.options.method = this.options.method.toLowerCase();
+
+ if (Object.isHash(this.options.parameters))
+ this.options.parameters = this.options.parameters.toObject();
+ }
+});
+Ajax.Request = Class.create(Ajax.Base, {
+ _complete: false,
+
+ initialize: function($super, url, options) {
+ $super(options);
+ this.transport = Ajax.getTransport();
+ this.request(url);
+ },
+
+ request: function(url) {
+ this.url = url;
+ this.method = this.options.method;
+ var params = Object.isString(this.options.parameters) ?
+ this.options.parameters :
+ Object.toQueryString(this.options.parameters);
+
+ if (!['get', 'post'].include(this.method)) {
+ params += (params ? '&' : '') + "_method=" + this.method;
+ this.method = 'post';
+ }
+
+ if (params && this.method === 'get') {
+ this.url += (this.url.include('?') ? '&' : '?') + params;
+ }
+
+ this.parameters = params.toQueryParams();
+
+ try {
+ var response = new Ajax.Response(this);
+ if (this.options.onCreate) this.options.onCreate(response);
+ Ajax.Responders.dispatch('onCreate', this, response);
+
+ this.transport.open(this.method.toUpperCase(), this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ this.setRequestHeaders();
+
+ this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+ this.transport.send(this.body);
+
+ /* Force Firefox to handle ready state 4 for synchronous requests */
+ if (!this.options.asynchronous && this.transport.overrideMimeType)
+ this.onStateChange();
+
+ }
+ catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState > 1 && !((readyState == 4) && this._complete))
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ setRequestHeaders: function() {
+ var headers = {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-Prototype-Version': Prototype.Version,
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ };
+
+ if (this.method == 'post') {
+ headers['Content-type'] = this.options.contentType +
+ (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+ /* Force "Connection: close" for older Mozilla browsers to work
+ * around a bug where XMLHttpRequest sends an incorrect
+ * Content-length header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType &&
+ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+ headers['Connection'] = 'close';
+ }
+
+ if (typeof this.options.requestHeaders == 'object') {
+ var extras = this.options.requestHeaders;
+
+ if (Object.isFunction(extras.push))
+ for (var i = 0, length = extras.length; i < length; i += 2)
+ headers[extras[i]] = extras[i+1];
+ else
+ $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+ }
+
+ for (var name in headers)
+ this.transport.setRequestHeader(name, headers[name]);
+ },
+
+ success: function() {
+ var status = this.getStatus();
+ return !status || (status >= 200 && status < 300) || status == 304;
+ },
+
+ getStatus: function() {
+ try {
+ if (this.transport.status === 1223) return 204;
+ return this.transport.status || 0;
+ } catch (e) { return 0 }
+ },
+
+ respondToReadyState: function(readyState) {
+ var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+ if (state == 'Complete') {
+ try {
+ this._complete = true;
+ (this.options['on' + response.status]
+ || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(response, response.headerJSON);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ var contentType = response.getHeader('Content-type');
+ if (this.options.evalJS == 'force'
+ || (this.options.evalJS && this.isSameOrigin() && contentType
+ && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+ this.evalResponse();
+ }
+
+ try {
+ (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+ Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if (state == 'Complete') {
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ }
+ },
+
+ isSameOrigin: function() {
+ var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+ return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+ protocol: location.protocol,
+ domain: document.domain,
+ port: location.port ? ':' + location.port : ''
+ }));
+ },
+
+ getHeader: function(name) {
+ try {
+ return this.transport.getResponseHeader(name) || null;
+ } catch (e) { return null; }
+ },
+
+ evalResponse: function() {
+ try {
+ return eval((this.transport.responseText || '').unfilterJSON());
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ dispatchException: function(exception) {
+ (this.options.onException || Prototype.emptyFunction)(this, exception);
+ Ajax.Responders.dispatch('onException', this, exception);
+ }
+});
+
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+
+
+
+
+
+
+
+Ajax.Response = Class.create({
+ initialize: function(request){
+ this.request = request;
+ var transport = this.transport = request.transport,
+ readyState = this.readyState = transport.readyState;
+
+ if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+ this.status = this.getStatus();
+ this.statusText = this.getStatusText();
+ this.responseText = String.interpret(transport.responseText);
+ this.headerJSON = this._getHeaderJSON();
+ }
+
+ if (readyState == 4) {
+ var xml = transport.responseXML;
+ this.responseXML = Object.isUndefined(xml) ? null : xml;
+ this.responseJSON = this._getResponseJSON();
+ }
+ },
+
+ status: 0,
+
+ statusText: '',
+
+ getStatus: Ajax.Request.prototype.getStatus,
+
+ getStatusText: function() {
+ try {
+ return this.transport.statusText || '';
+ } catch (e) { return '' }
+ },
+
+ getHeader: Ajax.Request.prototype.getHeader,
+
+ getAllHeaders: function() {
+ try {
+ return this.getAllResponseHeaders();
+ } catch (e) { return null }
+ },
+
+ getResponseHeader: function(name) {
+ return this.transport.getResponseHeader(name);
+ },
+
+ getAllResponseHeaders: function() {
+ return this.transport.getAllResponseHeaders();
+ },
+
+ _getHeaderJSON: function() {
+ var json = this.getHeader('X-JSON');
+ if (!json) return null;
+ json = decodeURIComponent(escape(json));
+ try {
+ return json.evalJSON(this.request.options.sanitizeJSON ||
+ !this.request.isSameOrigin());
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ },
+
+ _getResponseJSON: function() {
+ var options = this.request.options;
+ if (!options.evalJSON || (options.evalJSON != 'force' &&
+ !(this.getHeader('Content-type') || '').include('application/json')) ||
+ this.responseText.blank())
+ return null;
+ try {
+ return this.responseText.evalJSON(options.sanitizeJSON ||
+ !this.request.isSameOrigin());
+ } catch (e) {
+ this.request.dispatchException(e);
+ }
+ }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+ initialize: function($super, container, url, options) {
+ this.container = {
+ success: (container.success || container),
+ failure: (container.failure || (container.success ? null : container))
+ };
+
+ options = Object.clone(options);
+ var onComplete = options.onComplete;
+ options.onComplete = (function(response, json) {
+ this.updateContent(response.responseText);
+ if (Object.isFunction(onComplete)) onComplete(response, json);
+ }).bind(this);
+
+ $super(url, options);
+ },
+
+ updateContent: function(responseText) {
+ var receiver = this.container[this.success() ? 'success' : 'failure'],
+ options = this.options;
+
+ if (!options.evalScripts) responseText = responseText.stripScripts();
+
+ if (receiver = $(receiver)) {
+ if (options.insertion) {
+ if (Object.isString(options.insertion)) {
+ var insertion = { }; insertion[options.insertion] = responseText;
+ receiver.insert(insertion);
+ }
+ else options.insertion(receiver, responseText);
+ }
+ else receiver.update(responseText);
+ }
+ }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+ initialize: function($super, container, url, options) {
+ $super(options);
+ this.onComplete = this.options.onComplete;
+
+ this.frequency = (this.options.frequency || 2);
+ this.decay = (this.options.decay || 1);
+
+ this.updater = { };
+ this.container = container;
+ this.url = url;
+
+ this.start();
+ },
+
+ start: function() {
+ this.options.onComplete = this.updateComplete.bind(this);
+ this.onTimerEvent();
+ },
+
+ stop: function() {
+ this.updater.options.onComplete = undefined;
+ clearTimeout(this.timer);
+ (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+ },
+
+ updateComplete: function(response) {
+ if (this.options.decay) {
+ this.decay = (response.responseText == this.lastText ?
+ this.decay * this.options.decay : 1);
+
+ this.lastText = response.responseText;
+ }
+ this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+ },
+
+ onTimerEvent: function() {
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
+ }
+});
+
+
+function $(element) {
+ if (arguments.length > 1) {
+ for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+ elements.push($(arguments[i]));
+ return elements;
+ }
+ if (Object.isString(element))
+ element = document.getElementById(element);
+ return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+ document._getElementsByXPath = function(expression, parentElement) {
+ var results = [];
+ var query = document.evaluate(expression, $(parentElement) || document,
+ null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ for (var i = 0, length = query.snapshotLength; i < length; i++)
+ results.push(Element.extend(query.snapshotItem(i)));
+ return results;
+ };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+ Object.extend(Node, {
+ ELEMENT_NODE: 1,
+ ATTRIBUTE_NODE: 2,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ ENTITY_REFERENCE_NODE: 5,
+ ENTITY_NODE: 6,
+ PROCESSING_INSTRUCTION_NODE: 7,
+ COMMENT_NODE: 8,
+ DOCUMENT_NODE: 9,
+ DOCUMENT_TYPE_NODE: 10,
+ DOCUMENT_FRAGMENT_NODE: 11,
+ NOTATION_NODE: 12
+ });
+}
+
+
+
+(function(global) {
+ function shouldUseCache(tagName, attributes) {
+ if (tagName === 'select') return false;
+ if ('type' in attributes) return false;
+ return true;
+ }
+
+ var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
+ try {
+ var el = document.createElement('<input name="x">');
+ return el.tagName.toLowerCase() === 'input' && el.name === 'x';
+ }
+ catch(err) {
+ return false;
+ }
+ })();
+
+ var element = global.Element;
+
+ global.Element = function(tagName, attributes) {
+ attributes = attributes || { };
+ tagName = tagName.toLowerCase();
+ var cache = Element.cache;
+
+ if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
+ tagName = '<' + tagName + ' name="' + attributes.name + '">';
+ delete attributes.name;
+ return Element.writeAttribute(document.createElement(tagName), attributes);
+ }
+
+ if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+
+ var node = shouldUseCache(tagName, attributes) ?
+ cache[tagName].cloneNode(false) : document.createElement(tagName);
+
+ return Element.writeAttribute(node, attributes);
+ };
+
+ Object.extend(global.Element, element || { });
+ if (element) global.Element.prototype = element.prototype;
+
+})(this);
+
+Element.idCounter = 1;
+Element.cache = { };
+
+Element._purgeElement = function(element) {
+ var uid = element._prototypeUID;
+ if (uid) {
+ Element.stopObserving(element);
+ element._prototypeUID = void 0;
+ delete Element.Storage[uid];
+ }
+}
+
+Element.Methods = {
+ visible: function(element) {
+ return $(element).style.display != 'none';
+ },
+
+ toggle: function(element) {
+ element = $(element);
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
+ return element;
+ },
+
+ hide: function(element) {
+ element = $(element);
+ element.style.display = 'none';
+ return element;
+ },
+
+ show: function(element) {
+ element = $(element);
+ element.style.display = '';
+ return element;
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ return element;
+ },
+
+ update: (function(){
+
+ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
+ var el = document.createElement("select"),
+ isBuggy = true;
+ el.innerHTML = "<option value=\"test\">test</option>";
+ if (el.options && el.options[0]) {
+ isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
+ }
+ el = null;
+ return isBuggy;
+ })();
+
+ var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
+ try {
+ var el = document.createElement("table");
+ if (el && el.tBodies) {
+ el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
+ var isBuggy = typeof el.tBodies[0] == "undefined";
+ el = null;
+ return isBuggy;
+ }
+ } catch (e) {
+ return true;
+ }
+ })();
+
+ var LINK_ELEMENT_INNERHTML_BUGGY = (function() {
+ try {
+ var el = document.createElement('div');
+ el.innerHTML = "<link>";
+ var isBuggy = (el.childNodes.length === 0);
+ el = null;
+ return isBuggy;
+ } catch(e) {
+ return true;
+ }
+ })();
+
+ var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY ||
+ TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY;
+
+ var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
+ var s = document.createElement("script"),
+ isBuggy = false;
+ try {
+ s.appendChild(document.createTextNode(""));
+ isBuggy = !s.firstChild ||
+ s.firstChild && s.firstChild.nodeType !== 3;
+ } catch (e) {
+ isBuggy = true;
+ }
+ s = null;
+ return isBuggy;
+ })();
+
+
+ function update(element, content) {
+ element = $(element);
+ var purgeElement = Element._purgeElement;
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+ while (i--) purgeElement(descendants[i]);
+
+ if (content && content.toElement)
+ content = content.toElement();
+
+ if (Object.isElement(content))
+ return element.update().insert(content);
+
+ content = Object.toHTML(content);
+
+ var tagName = element.tagName.toUpperCase();
+
+ if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
+ element.text = content;
+ return element;
+ }
+
+ if (ANY_INNERHTML_BUGGY) {
+ if (tagName in Element._insertionTranslations.tags) {
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+ .each(function(node) {
+ element.appendChild(node)
+ });
+ } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf('<link') > -1) {
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true);
+ nodes.each(function(node) { element.appendChild(node) });
+ }
+ else {
+ element.innerHTML = content.stripScripts();
+ }
+ }
+ else {
+ element.innerHTML = content.stripScripts();
+ }
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ }
+
+ return update;
+ })(),
+
+ replace: function(element, content) {
+ element = $(element);
+ if (content && content.toElement) content = content.toElement();
+ else if (!Object.isElement(content)) {
+ content = Object.toHTML(content);
+ var range = element.ownerDocument.createRange();
+ range.selectNode(element);
+ content.evalScripts.bind(content).defer();
+ content = range.createContextualFragment(content.stripScripts());
+ }
+ element.parentNode.replaceChild(content, element);
+ return element;
+ },
+
+ insert: function(element, insertions) {
+ element = $(element);
+
+ if (Object.isString(insertions) || Object.isNumber(insertions) ||
+ Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+ insertions = {bottom:insertions};
+
+ var content, insert, tagName, childNodes;
+
+ for (var position in insertions) {
+ content = insertions[position];
+ position = position.toLowerCase();
+ insert = Element._insertionTranslations[position];
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ insert(element, content);
+ continue;
+ }
+
+ content = Object.toHTML(content);
+
+ tagName = ((position == 'before' || position == 'after')
+ ? element.parentNode : element).tagName.toUpperCase();
+
+ childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+ if (position == 'top' || position == 'after') childNodes.reverse();
+ childNodes.each(insert.curry(element));
+
+ content.evalScripts.bind(content).defer();
+ }
+
+ return element;
+ },
+
+ wrap: function(element, wrapper, attributes) {
+ element = $(element);
+ if (Object.isElement(wrapper))
+ $(wrapper).writeAttribute(attributes || { });
+ else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+ else wrapper = new Element('div', wrapper);
+ if (element.parentNode)
+ element.parentNode.replaceChild(wrapper, element);
+ wrapper.appendChild(element);
+ return wrapper;
+ },
+
+ inspect: function(element) {
+ element = $(element);
+ var result = '<' + element.tagName.toLowerCase();
+ $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+ var property = pair.first(),
+ attribute = pair.last(),
+ value = (element[property] || '').toString();
+ if (value) result += ' ' + attribute + '=' + value.inspect(true);
+ });
+ return result + '>';
+ },
+
+ recursivelyCollect: function(element, property, maximumLength) {
+ element = $(element);
+ maximumLength = maximumLength || -1;
+ var elements = [];
+
+ while (element = element[property]) {
+ if (element.nodeType == 1)
+ elements.push(Element.extend(element));
+ if (elements.length == maximumLength)
+ break;
+ }
+
+ return elements;
+ },
+
+ ancestors: function(element) {
+ return Element.recursivelyCollect(element, 'parentNode');
+ },
+
+ descendants: function(element) {
+ return Element.select(element, "*");
+ },
+
+ firstDescendant: function(element) {
+ element = $(element).firstChild;
+ while (element && element.nodeType != 1) element = element.nextSibling;
+ return $(element);
+ },
+
+ immediateDescendants: function(element) {
+ var results = [], child = $(element).firstChild;
+ while (child) {
+ if (child.nodeType === 1) {
+ results.push(Element.extend(child));
+ }
+ child = child.nextSibling;
+ }
+ return results;
+ },
+
+ previousSiblings: function(element, maximumLength) {
+ return Element.recursivelyCollect(element, 'previousSibling');
+ },
+
+ nextSiblings: function(element) {
+ return Element.recursivelyCollect(element, 'nextSibling');
+ },
+
+ siblings: function(element) {
+ element = $(element);
+ return Element.previousSiblings(element).reverse()
+ .concat(Element.nextSiblings(element));
+ },
+
+ match: function(element, selector) {
+ element = $(element);
+ if (Object.isString(selector))
+ return Prototype.Selector.match(element, selector);
+ return selector.match(element);
+ },
+
+ up: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return $(element.parentNode);
+ var ancestors = Element.ancestors(element);
+ return Object.isNumber(expression) ? ancestors[expression] :
+ Prototype.Selector.find(ancestors, expression, index);
+ },
+
+ down: function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return Element.firstDescendant(element);
+ return Object.isNumber(expression) ? Element.descendants(element)[expression] :
+ Element.select(element, expression)[index || 0];
+ },
+
+ previous: function(element, expression, index) {
+ element = $(element);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.previousSiblings(), expression, index);
+ } else {
+ return element.recursivelyCollect("previousSibling", index + 1)[index];
+ }
+ },
+
+ next: function(element, expression, index) {
+ element = $(element);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.nextSiblings(), expression, index);
+ } else {
+ var maximumLength = Object.isNumber(index) ? index + 1 : 1;
+ return element.recursivelyCollect("nextSibling", index + 1)[index];
+ }
+ },
+
+
+ select: function(element) {
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element);
+ },
+
+ adjacent: function(element) {
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element.parentNode).without(element);
+ },
+
+ identify: function(element) {
+ element = $(element);
+ var id = Element.readAttribute(element, 'id');
+ if (id) return id;
+ do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
+ Element.writeAttribute(element, 'id', id);
+ return id;
+ },
+
+ readAttribute: function(element, name) {
+ element = $(element);
+ if (Prototype.Browser.IE) {
+ var t = Element._attributeTranslations.read;
+ if (t.values[name]) return t.values[name](element, name);
+ if (t.names[name]) name = t.names[name];
+ if (name.include(':')) {
+ return (!element.attributes || !element.attributes[name]) ? null :
+ element.attributes[name].value;
+ }
+ }
+ return element.getAttribute(name);
+ },
+
+ writeAttribute: function(element, name, value) {
+ element = $(element);
+ var attributes = { }, t = Element._attributeTranslations.write;
+
+ if (typeof name == 'object') attributes = name;
+ else attributes[name] = Object.isUndefined(value) ? true : value;
+
+ for (var attr in attributes) {
+ name = t.names[attr] || attr;
+ value = attributes[attr];
+ if (t.values[attr]) name = t.values[attr](element, value);
+ if (value === false || value === null)
+ element.removeAttribute(name);
+ else if (value === true)
+ element.setAttribute(name, name);
+ else element.setAttribute(name, value);
+ }
+ return element;
+ },
+
+ getHeight: function(element) {
+ return Element.getDimensions(element).height;
+ },
+
+ getWidth: function(element) {
+ return Element.getDimensions(element).width;
+ },
+
+ classNames: function(element) {
+ return new Element.ClassNames(element);
+ },
+
+ hasClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ var elementClassName = element.className;
+ return (elementClassName.length > 0 && (elementClassName == className ||
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+ },
+
+ addClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ if (!Element.hasClassName(element, className))
+ element.className += (element.className ? ' ' : '') + className;
+ return element;
+ },
+
+ removeClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ element.className = element.className.replace(
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+ return element;
+ },
+
+ toggleClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element[Element.hasClassName(element, className) ?
+ 'removeClassName' : 'addClassName'](element, className);
+ },
+
+ cleanWhitespace: function(element) {
+ element = $(element);
+ var node = element.firstChild;
+ while (node) {
+ var nextNode = node.nextSibling;
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ element.removeChild(node);
+ node = nextNode;
+ }
+ return element;
+ },
+
+ empty: function(element) {
+ return $(element).innerHTML.blank();
+ },
+
+ descendantOf: function(element, ancestor) {
+ element = $(element), ancestor = $(ancestor);
+
+ if (element.compareDocumentPosition)
+ return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+ if (ancestor.contains)
+ return ancestor.contains(element) && ancestor !== element;
+
+ while (element = element.parentNode)
+ if (element == ancestor) return true;
+
+ return false;
+ },
+
+ scrollTo: function(element) {
+ element = $(element);
+ var pos = Element.cumulativeOffset(element);
+ window.scrollTo(pos[0], pos[1]);
+ return element;
+ },
+
+ getStyle: function(element, style) {
+ element = $(element);
+ style = style == 'float' ? 'cssFloat' : style.camelize();
+ var value = element.style[style];
+ if (!value || value == 'auto') {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css[style] : null;
+ }
+ if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+ return value == 'auto' ? null : value;
+ },
+
+ getOpacity: function(element) {
+ return $(element).getStyle('opacity');
+ },
+
+ setStyle: function(element, styles) {
+ element = $(element);
+ var elementStyle = element.style, match;
+ if (Object.isString(styles)) {
+ element.style.cssText += ';' + styles;
+ return styles.include('opacity') ?
+ element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+ }
+ for (var property in styles)
+ if (property == 'opacity') element.setOpacity(styles[property]);
+ else
+ elementStyle[(property == 'float' || property == 'cssFloat') ?
+ (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+ property] = styles[property];
+
+ return element;
+ },
+
+ setOpacity: function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+ return element;
+ },
+
+ makePositioned: function(element) {
+ element = $(element);
+ var pos = Element.getStyle(element, 'position');
+ if (pos == 'static' || !pos) {
+ element._madePositioned = true;
+ element.style.position = 'relative';
+ if (Prototype.Browser.Opera) {
+ element.style.top = 0;
+ element.style.left = 0;
+ }
+ }
+ return element;
+ },
+
+ undoPositioned: function(element) {
+ element = $(element);
+ if (element._madePositioned) {
+ element._madePositioned = undefined;
+ element.style.position =
+ element.style.top =
+ element.style.left =
+ element.style.bottom =
+ element.style.right = '';
+ }
+ return element;
+ },
+
+ makeClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return element;
+ element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+ if (element._overflow !== 'hidden')
+ element.style.overflow = 'hidden';
+ return element;
+ },
+
+ undoClipping: function(element) {
+ element = $(element);
+ if (!element._overflow) return element;
+ element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+ element._overflow = null;
+ return element;
+ },
+
+ clonePosition: function(element, source) {
+ var options = Object.extend({
+ setLeft: true,
+ setTop: true,
+ setWidth: true,
+ setHeight: true,
+ offsetTop: 0,
+ offsetLeft: 0
+ }, arguments[2] || { });
+
+ source = $(source);
+ var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
+
+ element = $(element);
+
+ if (Element.getStyle(element, 'position') == 'absolute') {
+ parent = Element.getOffsetParent(element);
+ delta = Element.viewportOffset(parent);
+ }
+
+ if (parent == document.body) {
+ delta[0] -= document.body.offsetLeft;
+ delta[1] -= document.body.offsetTop;
+ }
+
+ if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
+ if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
+ if (options.setWidth) element.style.width = source.offsetWidth + 'px';
+ if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+ return element;
+ }
+};
+
+Object.extend(Element.Methods, {
+ getElementsBySelector: Element.Methods.select,
+
+ childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+ write: {
+ names: {
+ className: 'class',
+ htmlFor: 'for'
+ },
+ values: { }
+ }
+};
+
+if (Prototype.Browser.Opera) {
+ Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+ function(proceed, element, style) {
+ switch (style) {
+ case 'height': case 'width':
+ if (!Element.visible(element)) return null;
+
+ var dim = parseInt(proceed(element, style), 10);
+
+ if (dim !== element['offset' + style.capitalize()])
+ return dim + 'px';
+
+ var properties;
+ if (style === 'height') {
+ properties = ['border-top-width', 'padding-top',
+ 'padding-bottom', 'border-bottom-width'];
+ }
+ else {
+ properties = ['border-left-width', 'padding-left',
+ 'padding-right', 'border-right-width'];
+ }
+ return properties.inject(dim, function(memo, property) {
+ var val = proceed(element, property);
+ return val === null ? memo : memo - parseInt(val, 10);
+ }) + 'px';
+ default: return proceed(element, style);
+ }
+ }
+ );
+
+ Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+ function(proceed, element, attribute) {
+ if (attribute === 'title') return element.title;
+ return proceed(element, attribute);
+ }
+ );
+}
+
+else if (Prototype.Browser.IE) {
+ Element.Methods.getStyle = function(element, style) {
+ element = $(element);
+ style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+ var value = element.style[style];
+ if (!value && element.currentStyle) value = element.currentStyle[style];
+
+ if (style == 'opacity') {
+ if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+ if (value[1]) return parseFloat(value[1]) / 100;
+ return 1.0;
+ }
+
+ if (value == 'auto') {
+ if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+ return element['offset' + style.capitalize()] + 'px';
+ return null;
+ }
+ return value;
+ };
+
+ Element.Methods.setOpacity = function(element, value) {
+ function stripAlpha(filter){
+ return filter.replace(/alpha\([^\)]*\)/gi,'');
+ }
+ element = $(element);
+ var currentStyle = element.currentStyle;
+ if ((currentStyle && !currentStyle.hasLayout) ||
+ (!currentStyle && element.style.zoom == 'normal'))
+ element.style.zoom = 1;
+
+ var filter = element.getStyle('filter'), style = element.style;
+ if (value == 1 || value === '') {
+ (filter = stripAlpha(filter)) ?
+ style.filter = filter : style.removeAttribute('filter');
+ return element;
+ } else if (value < 0.00001) value = 0;
+ style.filter = stripAlpha(filter) +
+ 'alpha(opacity=' + (value * 100) + ')';
+ return element;
+ };
+
+ Element._attributeTranslations = (function(){
+
+ var classProp = 'className',
+ forProp = 'for',
+ el = document.createElement('div');
+
+ el.setAttribute(classProp, 'x');
+
+ if (el.className !== 'x') {
+ el.setAttribute('class', 'x');
+ if (el.className === 'x') {
+ classProp = 'class';
+ }
+ }
+ el = null;
+
+ el = document.createElement('label');
+ el.setAttribute(forProp, 'x');
+ if (el.htmlFor !== 'x') {
+ el.setAttribute('htmlFor', 'x');
+ if (el.htmlFor === 'x') {
+ forProp = 'htmlFor';
+ }
+ }
+ el = null;
+
+ return {
+ read: {
+ names: {
+ 'class': classProp,
+ 'className': classProp,
+ 'for': forProp,
+ 'htmlFor': forProp
+ },
+ values: {
+ _getAttr: function(element, attribute) {
+ return element.getAttribute(attribute);
+ },
+ _getAttr2: function(element, attribute) {
+ return element.getAttribute(attribute, 2);
+ },
+ _getAttrNode: function(element, attribute) {
+ var node = element.getAttributeNode(attribute);
+ return node ? node.value : "";
+ },
+ _getEv: (function(){
+
+ var el = document.createElement('div'), f;
+ el.onclick = Prototype.emptyFunction;
+ var value = el.getAttribute('onclick');
+
+ if (String(value).indexOf('{') > -1) {
+ f = function(element, attribute) {
+ attribute = element.getAttribute(attribute);
+ if (!attribute) return null;
+ attribute = attribute.toString();
+ attribute = attribute.split('{')[1];
+ attribute = attribute.split('}')[0];
+ return attribute.strip();
+ };
+ }
+ else if (value === '') {
+ f = function(element, attribute) {
+ attribute = element.getAttribute(attribute);
+ if (!attribute) return null;
+ return attribute.strip();
+ };
+ }
+ el = null;
+ return f;
+ })(),
+ _flag: function(element, attribute) {
+ return $(element).hasAttribute(attribute) ? attribute : null;
+ },
+ style: function(element) {
+ return element.style.cssText.toLowerCase();
+ },
+ title: function(element) {
+ return element.title;
+ }
+ }
+ }
+ }
+ })();
+
+ Element._attributeTranslations.write = {
+ names: Object.extend({
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing'
+ }, Element._attributeTranslations.read.names),
+ values: {
+ checked: function(element, value) {
+ element.checked = !!value;
+ },
+
+ style: function(element, value) {
+ element.style.cssText = value ? value : '';
+ }
+ }
+ };
+
+ Element._attributeTranslations.has = {};
+
+ $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+ 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+ Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+ Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+ });
+
+ (function(v) {
+ Object.extend(v, {
+ href: v._getAttr2,
+ src: v._getAttr2,
+ type: v._getAttr,
+ action: v._getAttrNode,
+ disabled: v._flag,
+ checked: v._flag,
+ readonly: v._flag,
+ multiple: v._flag,
+ onload: v._getEv,
+ onunload: v._getEv,
+ onclick: v._getEv,
+ ondblclick: v._getEv,
+ onmousedown: v._getEv,
+ onmouseup: v._getEv,
+ onmouseover: v._getEv,
+ onmousemove: v._getEv,
+ onmouseout: v._getEv,
+ onfocus: v._getEv,
+ onblur: v._getEv,
+ onkeypress: v._getEv,
+ onkeydown: v._getEv,
+ onkeyup: v._getEv,
+ onsubmit: v._getEv,
+ onreset: v._getEv,
+ onselect: v._getEv,
+ onchange: v._getEv
+ });
+ })(Element._attributeTranslations.read.values);
+
+ if (Prototype.BrowserFeatures.ElementExtensions) {
+ (function() {
+ function _descendants(element) {
+ var nodes = element.getElementsByTagName('*'), results = [];
+ for (var i = 0, node; node = nodes[i]; i++)
+ if (node.tagName !== "!") // Filter out comment nodes.
+ results.push(node);
+ return results;
+ }
+
+ Element.Methods.down = function(element, expression, index) {
+ element = $(element);
+ if (arguments.length == 1) return element.firstDescendant();
+ return Object.isNumber(expression) ? _descendants(element)[expression] :
+ Element.select(element, expression)[index || 0];
+ }
+ })();
+ }
+
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1) ? 0.999999 :
+ (value === '') ? '' : (value < 0.00001) ? 0 : value;
+ return element;
+ };
+}
+
+else if (Prototype.Browser.WebKit) {
+ Element.Methods.setOpacity = function(element, value) {
+ element = $(element);
+ element.style.opacity = (value == 1 || value === '') ? '' :
+ (value < 0.00001) ? 0 : value;
+
+ if (value == 1)
+ if (element.tagName.toUpperCase() == 'IMG' && element.width) {
+ element.width++; element.width--;
+ } else try {
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch (e) { }
+
+ return element;
+ };
+}
+
+if ('outerHTML' in document.documentElement) {
+ Element.Methods.replace = function(element, content) {
+ element = $(element);
+
+ if (content && content.toElement) content = content.toElement();
+ if (Object.isElement(content)) {
+ element.parentNode.replaceChild(content, element);
+ return element;
+ }
+
+ content = Object.toHTML(content);
+ var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+ if (Element._insertionTranslations.tags[tagName]) {
+ var nextSibling = element.next(),
+ fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+ parent.removeChild(element);
+ if (nextSibling)
+ fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+ else
+ fragments.each(function(node) { parent.appendChild(node) });
+ }
+ else element.outerHTML = content.stripScripts();
+
+ content.evalScripts.bind(content).defer();
+ return element;
+ };
+}
+
+Element._returnOffset = function(l, t) {
+ var result = [l, t];
+ result.left = l;
+ result.top = t;
+ return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html, force) {
+ var div = new Element('div'),
+ t = Element._insertionTranslations.tags[tagName];
+
+ var workaround = false;
+ if (t) workaround = true;
+ else if (force) {
+ workaround = true;
+ t = ['', '', 0];
+ }
+
+ if (workaround) {
+ div.innerHTML = '&nbsp;' + t[0] + html + t[1];
+ div.removeChild(div.firstChild);
+ for (var i = t[2]; i--; ) {
+ div = div.firstChild;
+ }
+ }
+ else {
+ div.innerHTML = html;
+ }
+ return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+ before: function(element, node) {
+ element.parentNode.insertBefore(node, element);
+ },
+ top: function(element, node) {
+ element.insertBefore(node, element.firstChild);
+ },
+ bottom: function(element, node) {
+ element.appendChild(node);
+ },
+ after: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
+ },
+ tags: {
+ TABLE: ['<table>', '</table>', 1],
+ TBODY: ['<table><tbody>', '</tbody></table>', 2],
+ TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
+ TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+ SELECT: ['<select>', '</select>', 1]
+ }
+};
+
+(function() {
+ var tags = Element._insertionTranslations.tags;
+ Object.extend(tags, {
+ THEAD: tags.TBODY,
+ TFOOT: tags.TBODY,
+ TH: tags.TD
+ });
+})();
+
+Element.Methods.Simulated = {
+ hasAttribute: function(element, attribute) {
+ attribute = Element._attributeTranslations.has[attribute] || attribute;
+ var node = $(element).getAttributeNode(attribute);
+ return !!(node && node.specified);
+ }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+(function(div) {
+
+ if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
+ window.HTMLElement = { };
+ window.HTMLElement.prototype = div['__proto__'];
+ Prototype.BrowserFeatures.ElementExtensions = true;
+ }
+
+ div = null;
+
+})(document.createElement('div'));
+
+Element.extend = (function() {
+
+ function checkDeficiency(tagName) {
+ if (typeof window.Element != 'undefined') {
+ var proto = window.Element.prototype;
+ if (proto) {
+ var id = '_' + (Math.random()+'').slice(2),
+ el = document.createElement(tagName);
+ proto[id] = 'x';
+ var isBuggy = (el[id] !== 'x');
+ delete proto[id];
+ el = null;
+ return isBuggy;
+ }
+ }
+ return false;
+ }
+
+ function extendElementWith(element, methods) {
+ for (var property in methods) {
+ var value = methods[property];
+ if (Object.isFunction(value) && !(property in element))
+ element[property] = value.methodize();
+ }
+ }
+
+ var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
+
+ if (Prototype.BrowserFeatures.SpecificElementExtensions) {
+ if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
+ return function(element) {
+ if (element && typeof element._extendedByPrototype == 'undefined') {
+ var t = element.tagName;
+ if (t && (/^(?:object|applet|embed)$/i.test(t))) {
+ extendElementWith(element, Element.Methods);
+ extendElementWith(element, Element.Methods.Simulated);
+ extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
+ }
+ }
+ return element;
+ }
+ }
+ return Prototype.K;
+ }
+
+ var Methods = { }, ByTag = Element.Methods.ByTag;
+
+ var extend = Object.extend(function(element) {
+ if (!element || typeof element._extendedByPrototype != 'undefined' ||
+ element.nodeType != 1 || element == window) return element;
+
+ var methods = Object.clone(Methods),
+ tagName = element.tagName.toUpperCase();
+
+ if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+ extendElementWith(element, methods);
+
+ element._extendedByPrototype = Prototype.emptyFunction;
+ return element;
+
+ }, {
+ refresh: function() {
+ if (!Prototype.BrowserFeatures.ElementExtensions) {
+ Object.extend(Methods, Element.Methods);
+ Object.extend(Methods, Element.Methods.Simulated);
+ }
+ }
+ });
+
+ extend.refresh();
+ return extend;
+})();
+
+if (document.documentElement.hasAttribute) {
+ Element.hasAttribute = function(element, attribute) {
+ return element.hasAttribute(attribute);
+ };
+}
+else {
+ Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
+}
+
+Element.addMethods = function(methods) {
+ var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+ if (!methods) {
+ Object.extend(Form, Form.Methods);
+ Object.extend(Form.Element, Form.Element.Methods);
+ Object.extend(Element.Methods.ByTag, {
+ "FORM": Object.clone(Form.Methods),
+ "INPUT": Object.clone(Form.Element.Methods),
+ "SELECT": Object.clone(Form.Element.Methods),
+ "TEXTAREA": Object.clone(Form.Element.Methods),
+ "BUTTON": Object.clone(Form.Element.Methods)
+ });
+ }
+
+ if (arguments.length == 2) {
+ var tagName = methods;
+ methods = arguments[1];
+ }
+
+ if (!tagName) Object.extend(Element.Methods, methods || { });
+ else {
+ if (Object.isArray(tagName)) tagName.each(extend);
+ else extend(tagName);
+ }
+
+ function extend(tagName) {
+ tagName = tagName.toUpperCase();
+ if (!Element.Methods.ByTag[tagName])
+ Element.Methods.ByTag[tagName] = { };
+ Object.extend(Element.Methods.ByTag[tagName], methods);
+ }
+
+ function copy(methods, destination, onlyIfAbsent) {
+ onlyIfAbsent = onlyIfAbsent || false;
+ for (var property in methods) {
+ var value = methods[property];
+ if (!Object.isFunction(value)) continue;
+ if (!onlyIfAbsent || !(property in destination))
+ destination[property] = value.methodize();
+ }
+ }
+
+ function findDOMClass(tagName) {
+ var klass;
+ var trans = {
+ "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+ "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+ "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+ "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+ "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+ "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+ "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+ "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+ "FrameSet", "IFRAME": "IFrame"
+ };
+ if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName + 'Element';
+ if (window[klass]) return window[klass];
+ klass = 'HTML' + tagName.capitalize() + 'Element';
+ if (window[klass]) return window[klass];
+
+ var element = document.createElement(tagName),
+ proto = element['__proto__'] || element.constructor.prototype;
+
+ element = null;
+ return proto;
+ }
+
+ var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
+ Element.prototype;
+
+ if (F.ElementExtensions) {
+ copy(Element.Methods, elementPrototype);
+ copy(Element.Methods.Simulated, elementPrototype, true);
+ }
+
+ if (F.SpecificElementExtensions) {
+ for (var tag in Element.Methods.ByTag) {
+ var klass = findDOMClass(tag);
+ if (Object.isUndefined(klass)) continue;
+ copy(T[tag], klass.prototype);
+ }
+ }
+
+ Object.extend(Element, Element.Methods);
+ delete Element.ByTag;
+
+ if (Element.extend.refresh) Element.extend.refresh();
+ Element.cache = { };
+};
+
+
+document.viewport = {
+
+ getDimensions: function() {
+ return { width: this.getWidth(), height: this.getHeight() };
+ },
+
+ getScrollOffsets: function() {
+ return Element._returnOffset(
+ window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+ window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+ }
+};
+
+(function(viewport) {
+ var B = Prototype.Browser, doc = document, element, property = {};
+
+ function getRootElement() {
+ if (B.WebKit && !doc.evaluate)
+ return document;
+
+ if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
+ return document.body;
+
+ return document.documentElement;
+ }
+
+ function define(D) {
+ if (!element) element = getRootElement();
+
+ property[D] = 'client' + D;
+
+ viewport['get' + D] = function() { return element[property[D]] };
+ return viewport['get' + D]();
+ }
+
+ viewport.getWidth = define.curry('Width');
+
+ viewport.getHeight = define.curry('Height');
+})(document.viewport);
+
+
+Element.Storage = {
+ UID: 1
+};
+
+Element.addMethods({
+ getStorage: function(element) {
+ if (!(element = $(element))) return;
+
+ var uid;
+ if (element === window) {
+ uid = 0;
+ } else {
+ if (typeof element._prototypeUID === "undefined")
+ element._prototypeUID = Element.Storage.UID++;
+ uid = element._prototypeUID;
+ }
+
+ if (!Element.Storage[uid])
+ Element.Storage[uid] = $H();
+
+ return Element.Storage[uid];
+ },
+
+ store: function(element, key, value) {
+ if (!(element = $(element))) return;
+
+ if (arguments.length === 2) {
+ Element.getStorage(element).update(key);
+ } else {
+ Element.getStorage(element).set(key, value);
+ }
+
+ return element;
+ },
+
+ retrieve: function(element, key, defaultValue) {
+ if (!(element = $(element))) return;
+ var hash = Element.getStorage(element), value = hash.get(key);
+
+ if (Object.isUndefined(value)) {
+ hash.set(key, defaultValue);
+ value = defaultValue;
+ }
+
+ return value;
+ },
+
+ clone: function(element, deep) {
+ if (!(element = $(element))) return;
+ var clone = element.cloneNode(deep);
+ clone._prototypeUID = void 0;
+ if (deep) {
+ var descendants = Element.select(clone, '*'),
+ i = descendants.length;
+ while (i--) {
+ descendants[i]._prototypeUID = void 0;
+ }
+ }
+ return Element.extend(clone);
+ },
+
+ purge: function(element) {
+ if (!(element = $(element))) return;
+ var purgeElement = Element._purgeElement;
+
+ purgeElement(element);
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+
+ while (i--) purgeElement(descendants[i]);
+
+ return null;
+ }
+});
+
+(function() {
+
+ function toDecimal(pctString) {
+ var match = pctString.match(/^(\d+)%?$/i);
+ if (!match) return null;
+ return (Number(match[1]) / 100);
+ }
+
+ function getPixelValue(value, property, context) {
+ var element = null;
+ if (Object.isElement(value)) {
+ element = value;
+ value = element.getStyle(property);
+ }
+
+ if (value === null) {
+ return null;
+ }
+
+ if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
+ return window.parseFloat(value);
+ }
+
+ var isPercentage = value.include('%'), isViewport = (context === document.viewport);
+
+ if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) {
+ var style = element.style.left, rStyle = element.runtimeStyle.left;
+ element.runtimeStyle.left = element.currentStyle.left;
+ element.style.left = value || 0;
+ value = element.style.pixelLeft;
+ element.style.left = style;
+ element.runtimeStyle.left = rStyle;
+
+ return value;
+ }
+
+ if (element && isPercentage) {
+ context = context || element.parentNode;
+ var decimal = toDecimal(value);
+ var whole = null;
+ var position = element.getStyle('position');
+
+ var isHorizontal = property.include('left') || property.include('right') ||
+ property.include('width');
+
+ var isVertical = property.include('top') || property.include('bottom') ||
+ property.include('height');
+
+ if (context === document.viewport) {
+ if (isHorizontal) {
+ whole = document.viewport.getWidth();
+ } else if (isVertical) {
+ whole = document.viewport.getHeight();
+ }
+ } else {
+ if (isHorizontal) {
+ whole = $(context).measure('width');
+ } else if (isVertical) {
+ whole = $(context).measure('height');
+ }
+ }
+
+ return (whole === null) ? 0 : whole * decimal;
+ }
+
+ return 0;
+ }
+
+ function toCSSPixels(number) {
+ if (Object.isString(number) && number.endsWith('px')) {
+ return number;
+ }
+ return number + 'px';
+ }
+
+ function isDisplayed(element) {
+ var originalElement = element;
+ while (element && element.parentNode) {
+ var display = element.getStyle('display');
+ if (display === 'none') {
+ return false;
+ }
+ element = $(element.parentNode);
+ }
+ return true;
+ }
+
+ var hasLayout = Prototype.K;
+ if ('currentStyle' in document.documentElement) {
+ hasLayout = function(element) {
+ if (!element.currentStyle.hasLayout) {
+ element.style.zoom = 1;
+ }
+ return element;
+ };
+ }
+
+ function cssNameFor(key) {
+ if (key.include('border')) key = key + '-width';
+ return key.camelize();
+ }
+
+ Element.Layout = Class.create(Hash, {
+ initialize: function($super, element, preCompute) {
+ $super();
+ this.element = $(element);
+
+ Element.Layout.PROPERTIES.each( function(property) {
+ this._set(property, null);
+ }, this);
+
+ if (preCompute) {
+ this._preComputing = true;
+ this._begin();
+ Element.Layout.PROPERTIES.each( this._compute, this );
+ this._end();
+ this._preComputing = false;
+ }
+ },
+
+ _set: function(property, value) {
+ return Hash.prototype.set.call(this, property, value);
+ },
+
+ set: function(property, value) {
+ throw "Properties of Element.Layout are read-only.";
+ },
+
+ get: function($super, property) {
+ var value = $super(property);
+ return value === null ? this._compute(property) : value;
+ },
+
+ _begin: function() {
+ if (this._prepared) return;
+
+ var element = this.element;
+ if (isDisplayed(element)) {
+ this._prepared = true;
+ return;
+ }
+
+ var originalStyles = {
+ position: element.style.position || '',
+ width: element.style.width || '',
+ visibility: element.style.visibility || '',
+ display: element.style.display || ''
+ };
+
+ element.store('prototype_original_styles', originalStyles);
+
+ var position = element.getStyle('position'),
+ width = element.getStyle('width');
+
+ if (width === "0px" || width === null) {
+ element.style.display = 'block';
+ width = element.getStyle('width');
+ }
+
+ var context = (position === 'fixed') ? document.viewport :
+ element.parentNode;
+
+ element.setStyle({
+ position: 'absolute',
+ visibility: 'hidden',
+ display: 'block'
+ });
+
+ var positionedWidth = element.getStyle('width');
+
+ var newWidth;
+ if (width && (positionedWidth === width)) {
+ newWidth = getPixelValue(element, 'width', context);
+ } else if (position === 'absolute' || position === 'fixed') {
+ newWidth = getPixelValue(element, 'width', context);
+ } else {
+ var parent = element.parentNode, pLayout = $(parent).getLayout();
+
+ newWidth = pLayout.get('width') -
+ this.get('margin-left') -
+ this.get('border-left') -
+ this.get('padding-left') -
+ this.get('padding-right') -
+ this.get('border-right') -
+ this.get('margin-right');
+ }
+
+ element.setStyle({ width: newWidth + 'px' });
+
+ this._prepared = true;
+ },
+
+ _end: function() {
+ var element = this.element;
+ var originalStyles = element.retrieve('prototype_original_styles');
+ element.store('prototype_original_styles', null);
+ element.setStyle(originalStyles);
+ this._prepared = false;
+ },
+
+ _compute: function(property) {
+ var COMPUTATIONS = Element.Layout.COMPUTATIONS;
+ if (!(property in COMPUTATIONS)) {
+ throw "Property not found.";
+ }
+
+ return this._set(property, COMPUTATIONS[property].call(this, this.element));
+ },
+
+ toObject: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var obj = {};
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ var value = this.get(key);
+ if (value != null) obj[key] = value;
+ }, this);
+ return obj;
+ },
+
+ toHash: function() {
+ var obj = this.toObject.apply(this, arguments);
+ return new Hash(obj);
+ },
+
+ toCSS: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var css = {};
+
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
+
+ var value = this.get(key);
+ if (value != null) css[cssNameFor(key)] = value + 'px';
+ }, this);
+ return css;
+ },
+
+ inspect: function() {
+ return "#<Element.Layout>";
+ }
+ });
+
+ Object.extend(Element.Layout, {
+ PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
+
+ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
+
+ COMPUTATIONS: {
+ 'height': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bHeight = this.get('border-box-height');
+ if (bHeight <= 0) {
+ if (!this._preComputing) this._end();
+ return 0;
+ }
+
+ var bTop = this.get('border-top'),
+ bBottom = this.get('border-bottom');
+
+ var pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ if (!this._preComputing) this._end();
+
+ return bHeight - bTop - bBottom - pTop - pBottom;
+ },
+
+ 'width': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bWidth = this.get('border-box-width');
+ if (bWidth <= 0) {
+ if (!this._preComputing) this._end();
+ return 0;
+ }
+
+ var bLeft = this.get('border-left'),
+ bRight = this.get('border-right');
+
+ var pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ if (!this._preComputing) this._end();
+
+ return bWidth - bLeft - bRight - pLeft - pRight;
+ },
+
+ 'padding-box-height': function(element) {
+ var height = this.get('height'),
+ pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ return height + pTop + pBottom;
+ },
+
+ 'padding-box-width': function(element) {
+ var width = this.get('width'),
+ pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ return width + pLeft + pRight;
+ },
+
+ 'border-box-height': function(element) {
+ if (!this._preComputing) this._begin();
+ var height = element.offsetHeight;
+ if (!this._preComputing) this._end();
+ return height;
+ },
+
+ 'border-box-width': function(element) {
+ if (!this._preComputing) this._begin();
+ var width = element.offsetWidth;
+ if (!this._preComputing) this._end();
+ return width;
+ },
+
+ 'margin-box-height': function(element) {
+ var bHeight = this.get('border-box-height'),
+ mTop = this.get('margin-top'),
+ mBottom = this.get('margin-bottom');
+
+ if (bHeight <= 0) return 0;
+
+ return bHeight + mTop + mBottom;
+ },
+
+ 'margin-box-width': function(element) {
+ var bWidth = this.get('border-box-width'),
+ mLeft = this.get('margin-left'),
+ mRight = this.get('margin-right');
+
+ if (bWidth <= 0) return 0;
+
+ return bWidth + mLeft + mRight;
+ },
+
+ 'top': function(element) {
+ var offset = element.positionedOffset();
+ return offset.top;
+ },
+
+ 'bottom': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pHeight = parent.measure('height');
+
+ var mHeight = this.get('border-box-height');
+
+ return pHeight - mHeight - offset.top;
+ },
+
+ 'left': function(element) {
+ var offset = element.positionedOffset();
+ return offset.left;
+ },
+
+ 'right': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pWidth = parent.measure('width');
+
+ var mWidth = this.get('border-box-width');
+
+ return pWidth - mWidth - offset.left;
+ },
+
+ 'padding-top': function(element) {
+ return getPixelValue(element, 'paddingTop');
+ },
+
+ 'padding-bottom': function(element) {
+ return getPixelValue(element, 'paddingBottom');
+ },
+
+ 'padding-left': function(element) {
+ return getPixelValue(element, 'paddingLeft');
+ },
+
+ 'padding-right': function(element) {
+ return getPixelValue(element, 'paddingRight');
+ },
+
+ 'border-top': function(element) {
+ return getPixelValue(element, 'borderTopWidth');
+ },
+
+ 'border-bottom': function(element) {
+ return getPixelValue(element, 'borderBottomWidth');
+ },
+
+ 'border-left': function(element) {
+ return getPixelValue(element, 'borderLeftWidth');
+ },
+
+ 'border-right': function(element) {
+ return getPixelValue(element, 'borderRightWidth');
+ },
+
+ 'margin-top': function(element) {
+ return getPixelValue(element, 'marginTop');
+ },
+
+ 'margin-bottom': function(element) {
+ return getPixelValue(element, 'marginBottom');
+ },
+
+ 'margin-left': function(element) {
+ return getPixelValue(element, 'marginLeft');
+ },
+
+ 'margin-right': function(element) {
+ return getPixelValue(element, 'marginRight');
+ }
+ }
+ });
+
+ if ('getBoundingClientRect' in document.documentElement) {
+ Object.extend(Element.Layout.COMPUTATIONS, {
+ 'right': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.right - rect.right).round();
+ },
+
+ 'bottom': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.bottom - rect.bottom).round();
+ }
+ });
+ }
+
+ Element.Offset = Class.create({
+ initialize: function(left, top) {
+ this.left = left.round();
+ this.top = top.round();
+
+ this[0] = this.left;
+ this[1] = this.top;
+ },
+
+ relativeTo: function(offset) {
+ return new Element.Offset(
+ this.left - offset.left,
+ this.top - offset.top
+ );
+ },
+
+ inspect: function() {
+ return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
+ },
+
+ toString: function() {
+ return "[#{left}, #{top}]".interpolate(this);
+ },
+
+ toArray: function() {
+ return [this.left, this.top];
+ }
+ });
+
+ function getLayout(element, preCompute) {
+ return new Element.Layout(element, preCompute);
+ }
+
+ function measure(element, property) {
+ return $(element).getLayout().get(property);
+ }
+
+ function getDimensions(element) {
+ element = $(element);
+ var display = Element.getStyle(element, 'display');
+
+ if (display && display !== 'none') {
+ return { width: element.offsetWidth, height: element.offsetHeight };
+ }
+
+ var style = element.style;
+ var originalStyles = {
+ visibility: style.visibility,
+ position: style.position,
+ display: style.display
+ };
+
+ var newStyles = {
+ visibility: 'hidden',
+ display: 'block'
+ };
+
+ if (originalStyles.position !== 'fixed')
+ newStyles.position = 'absolute';
+
+ Element.setStyle(element, newStyles);
+
+ var dimensions = {
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+
+ Element.setStyle(element, originalStyles);
+
+ return dimensions;
+ }
+
+ function getOffsetParent(element) {
+ element = $(element);
+
+ if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
+ return $(document.body);
+
+ var isInline = (Element.getStyle(element, 'display') === 'inline');
+ if (!isInline && element.offsetParent) return $(element.offsetParent);
+
+ while ((element = element.parentNode) && element !== document.body) {
+ if (Element.getStyle(element, 'position') !== 'static') {
+ return isHtml(element) ? $(document.body) : $(element);
+ }
+ }
+
+ return $(document.body);
+ }
+
+
+ function cumulativeOffset(element) {
+ element = $(element);
+ var valueT = 0, valueL = 0;
+ if (element.parentNode) {
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ }
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function positionedOffset(element) {
+ element = $(element);
+
+ var layout = element.getLayout();
+
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (isBody(element)) break;
+ var p = Element.getStyle(element, 'position');
+ if (p !== 'static') break;
+ }
+ } while (element);
+
+ valueL -= layout.get('margin-top');
+ valueT -= layout.get('margin-left');
+
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function cumulativeScrollOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function viewportOffset(forElement) {
+ element = $(element);
+ var valueT = 0, valueL = 0, docBody = document.body;
+
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == docBody &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ if (element != docBody) {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ }
+ } while (element = element.parentNode);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function absolutize(element) {
+ element = $(element);
+
+ if (Element.getStyle(element, 'position') === 'absolute') {
+ return element;
+ }
+
+ var offsetParent = getOffsetParent(element);
+ var eOffset = element.viewportOffset(),
+ pOffset = offsetParent.viewportOffset();
+
+ var offset = eOffset.relativeTo(pOffset);
+ var layout = element.getLayout();
+
+ element.store('prototype_absolutize_original_styles', {
+ left: element.getStyle('left'),
+ top: element.getStyle('top'),
+ width: element.getStyle('width'),
+ height: element.getStyle('height')
+ });
+
+ element.setStyle({
+ position: 'absolute',
+ top: offset.top + 'px',
+ left: offset.left + 'px',
+ width: layout.get('width') + 'px',
+ height: layout.get('height') + 'px'
+ });
+
+ return element;
+ }
+
+ function relativize(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') === 'relative') {
+ return element;
+ }
+
+ var originalStyles =
+ element.retrieve('prototype_absolutize_original_styles');
+
+ if (originalStyles) element.setStyle(originalStyles);
+ return element;
+ }
+
+ if (Prototype.Browser.IE) {
+ getOffsetParent = getOffsetParent.wrap(
+ function(proceed, element) {
+ element = $(element);
+
+ if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
+ return $(document.body);
+
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+
+ positionedOffset = positionedOffset.wrap(function(proceed, element) {
+ element = $(element);
+ if (!element.parentNode) return new Element.Offset(0, 0);
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+
+ var offsetParent = element.getOffsetParent();
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+ hasLayout(offsetParent);
+
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ });
+ } else if (Prototype.Browser.Webkit) {
+ cumulativeOffset = function(element) {
+ element = $(element);
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == document.body)
+ if (Element.getStyle(element, 'position') == 'absolute') break;
+
+ element = element.offsetParent;
+ } while (element);
+
+ return new Element.Offset(valueL, valueT);
+ };
+ }
+
+
+ Element.addMethods({
+ getLayout: getLayout,
+ measure: measure,
+ getDimensions: getDimensions,
+ getOffsetParent: getOffsetParent,
+ cumulativeOffset: cumulativeOffset,
+ positionedOffset: positionedOffset,
+ cumulativeScrollOffset: cumulativeScrollOffset,
+ viewportOffset: viewportOffset,
+ absolutize: absolutize,
+ relativize: relativize
+ });
+
+ function isBody(element) {
+ return element.nodeName.toUpperCase() === 'BODY';
+ }
+
+ function isHtml(element) {
+ return element.nodeName.toUpperCase() === 'HTML';
+ }
+
+ function isDocument(element) {
+ return element.nodeType === Node.DOCUMENT_NODE;
+ }
+
+ function isDetached(element) {
+ return element !== document.body &&
+ !Element.descendantOf(element, document.body);
+ }
+
+ if ('getBoundingClientRect' in document.documentElement) {
+ Element.addMethods({
+ viewportOffset: function(element) {
+ element = $(element);
+ if (isDetached(element)) return new Element.Offset(0, 0);
+
+ var rect = element.getBoundingClientRect(),
+ docEl = document.documentElement;
+ return new Element.Offset(rect.left - docEl.clientLeft,
+ rect.top - docEl.clientTop);
+ }
+ });
+ }
+})();
+window.$$ = function() {
+ var expression = $A(arguments).join(', ');
+ return Prototype.Selector.select(expression, document);
+};
+
+Prototype.Selector = (function() {
+
+ function select() {
+ throw new Error('Method "Prototype.Selector.select" must be defined.');
+ }
+
+ function match() {
+ throw new Error('Method "Prototype.Selector.match" must be defined.');
+ }
+
+ function find(elements, expression, index) {
+ index = index || 0;
+ var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
+
+ for (i = 0; i < length; i++) {
+ if (match(elements[i], expression) && index == matchIndex++) {
+ return Element.extend(elements[i]);
+ }
+ }
+ }
+
+ function extendElements(elements) {
+ for (var i = 0, length = elements.length; i < length; i++) {
+ Element.extend(elements[i]);
+ }
+ return elements;
+ }
+
+
+ var K = Prototype.K;
+
+ return {
+ select: select,
+ match: match,
+ find: find,
+ extendElements: (Element.extend === K) ? K : extendElements,
+ extendElement: Element.extend
+ };
+})();
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || selector);
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: " + expr;
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+(function(){
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ if ( div.getElementsByClassName("e").length === 0 )
+ return;
+
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 )
+ return;
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+var contains = document.compareDocumentPosition ? function(a, b){
+ return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+
+window.Sizzle = Sizzle;
+
+})();
+
+Prototype._original_property = window.Sizzle;
+
+;(function(engine) {
+ var extendElements = Prototype.Selector.extendElements;
+
+ function select(selector, scope) {
+ return extendElements(engine(selector, scope || document));
+ }
+
+ function match(element, selector) {
+ return engine.matches(selector, [element]).length == 1;
+ }
+
+ Prototype.Selector.engine = engine;
+ Prototype.Selector.select = select;
+ Prototype.Selector.match = match;
+})(Sizzle);
+
+window.Sizzle = Prototype._original_property;
+delete Prototype._original_property;
+
+var Form = {
+ reset: function(form) {
+ form = $(form);
+ form.reset();
+ return form;
+ },
+
+ serializeElements: function(elements, options) {
+ if (typeof options != 'object') options = { hash: !!options };
+ else if (Object.isUndefined(options.hash)) options.hash = true;
+ var key, value, submitted = false, submit = options.submit, accumulator, initial;
+
+ if (options.hash) {
+ initial = {};
+ accumulator = function(result, key, value) {
+ if (key in result) {
+ if (!Object.isArray(result[key])) result[key] = [result[key]];
+ result[key].push(value);
+ } else result[key] = value;
+ return result;
+ };
+ } else {
+ initial = '';
+ accumulator = function(result, key, value) {
+ return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value);
+ }
+ }
+
+ return elements.inject(initial, function(result, element) {
+ if (!element.disabled && element.name) {
+ key = element.name; value = $(element).getValue();
+ if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
+ submit !== false && (!submit || key == submit) && (submitted = true)))) {
+ result = accumulator(result, key, value);
+ }
+ }
+ return result;
+ });
+ }
+};
+
+Form.Methods = {
+ serialize: function(form, options) {
+ return Form.serializeElements(Form.getElements(form), options);
+ },
+
+ getElements: function(form) {
+ var elements = $(form).getElementsByTagName('*'),
+ element,
+ arr = [ ],
+ serializers = Form.Element.Serializers;
+ for (var i = 0; element = elements[i]; i++) {
+ arr.push(element);
+ }
+ return arr.inject([], function(elements, child) {
+ if (serializers[child.tagName.toLowerCase()])
+ elements.push(Element.extend(child));
+ return elements;
+ })
+ },
+
+ getInputs: function(form, typeName, name) {
+ form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+ for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) || (name && input.name != name))
+ continue;
+ matchingInputs.push(Element.extend(input));
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('disable');
+ return form;
+ },
+
+ enable: function(form) {
+ form = $(form);
+ Form.getElements(form).invoke('enable');
+ return form;
+ },
+
+ findFirstElement: function(form) {
+ var elements = $(form).getElements().findAll(function(element) {
+ return 'hidden' != element.type && !element.disabled;
+ });
+ var firstByIndex = elements.findAll(function(element) {
+ return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+ }).sortBy(function(element) { return element.tabIndex }).first();
+
+ return firstByIndex ? firstByIndex : elements.find(function(element) {
+ return /^(?:input|select|textarea)$/i.test(element.tagName);
+ });
+ },
+
+ focusFirstElement: function(form) {
+ form = $(form);
+ var element = form.findFirstElement();
+ if (element) element.activate();
+ return form;
+ },
+
+ request: function(form, options) {
+ form = $(form), options = Object.clone(options || { });
+
+ var params = options.parameters, action = form.readAttribute('action') || '';
+ if (action.blank()) action = window.location.href;
+ options.parameters = form.serialize(true);
+
+ if (params) {
+ if (Object.isString(params)) params = params.toQueryParams();
+ Object.extend(options.parameters, params);
+ }
+
+ if (form.hasAttribute('method') && !options.method)
+ options.method = form.method;
+
+ return new Ajax.Request(action, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Form.Element = {
+ focus: function(element) {
+ $(element).focus();
+ return element;
+ },
+
+ select: function(element) {
+ $(element).select();
+ return element;
+ }
+};
+
+Form.Element.Methods = {
+
+ serialize: function(element) {
+ element = $(element);
+ if (!element.disabled && element.name) {
+ var value = element.getValue();
+ if (value != undefined) {
+ var pair = { };
+ pair[element.name] = value;
+ return Object.toQueryString(pair);
+ }
+ }
+ return '';
+ },
+
+ getValue: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ return Form.Element.Serializers[method](element);
+ },
+
+ setValue: function(element, value) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ Form.Element.Serializers[method](element, value);
+ return element;
+ },
+
+ clear: function(element) {
+ $(element).value = '';
+ return element;
+ },
+
+ present: function(element) {
+ return $(element).value != '';
+ },
+
+ activate: function(element) {
+ element = $(element);
+ try {
+ element.focus();
+ if (element.select && (element.tagName.toLowerCase() != 'input' ||
+ !(/^(?:button|reset|submit)$/i.test(element.type))))
+ element.select();
+ } catch (e) { }
+ return element;
+ },
+
+ disable: function(element) {
+ element = $(element);
+ element.disabled = true;
+ return element;
+ },
+
+ enable: function(element) {
+ element = $(element);
+ element.disabled = false;
+ return element;
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = (function() {
+ function input(element, value) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ return inputSelector(element, value);
+ default:
+ return valueSelector(element, value);
+ }
+ }
+
+ function inputSelector(element, value) {
+ if (Object.isUndefined(value))
+ return element.checked ? element.value : null;
+ else element.checked = !!value;
+ }
+
+ function valueSelector(element, value) {
+ if (Object.isUndefined(value)) return element.value;
+ else element.value = value;
+ }
+
+ function select(element, value) {
+ if (Object.isUndefined(value))
+ return (element.type === 'select-one' ? selectOne : selectMany)(element);
+
+ var opt, currentValue, single = !Object.isArray(value);
+ for (var i = 0, length = element.length; i < length; i++) {
+ opt = element.options[i];
+ currentValue = this.optionValue(opt);
+ if (single) {
+ if (currentValue == value) {
+ opt.selected = true;
+ return;
+ }
+ }
+ else opt.selected = value.include(currentValue);
+ }
+ }
+
+ function selectOne(element) {
+ var index = element.selectedIndex;
+ return index >= 0 ? optionValue(element.options[index]) : null;
+ }
+
+ function selectMany(element) {
+ var values, length = element.length;
+ if (!length) return null;
+
+ for (var i = 0, values = []; i < length; i++) {
+ var opt = element.options[i];
+ if (opt.selected) values.push(optionValue(opt));
+ }
+ return values;
+ }
+
+ function optionValue(opt) {
+ return Element.hasAttribute(opt, 'value') ? opt.value : opt.text;
+ }
+
+ return {
+ input: input,
+ inputSelector: inputSelector,
+ textarea: valueSelector,
+ select: select,
+ selectOne: selectOne,
+ selectMany: selectMany,
+ optionValue: optionValue,
+ button: valueSelector
+ };
+})();
+
+/*--------------------------------------------------------------------------*/
+
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+ initialize: function($super, element, frequency, callback) {
+ $super(callback, frequency);
+ this.element = $(element);
+ this.lastValue = this.getValue();
+ },
+
+ execute: function() {
+ var value = this.getValue();
+ if (Object.isString(this.lastValue) && Object.isString(value) ?
+ this.lastValue != value : String(this.lastValue) != String(value)) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+ initialize: function(element, callback) {
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ if (this.element.tagName.toLowerCase() == 'form')
+ this.registerFormCallbacks();
+ else
+ this.registerCallback(this.element);
+ },
+
+ onElementEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ },
+
+ registerFormCallbacks: function() {
+ Form.getElements(this.element).each(this.registerCallback, this);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
+ break;
+ default:
+ Event.observe(element, 'change', this.onElementEvent.bind(this));
+ break;
+ }
+ }
+ }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+(function() {
+
+ var Event = {
+ KEY_BACKSPACE: 8,
+ KEY_TAB: 9,
+ KEY_RETURN: 13,
+ KEY_ESC: 27,
+ KEY_LEFT: 37,
+ KEY_UP: 38,
+ KEY_RIGHT: 39,
+ KEY_DOWN: 40,
+ KEY_DELETE: 46,
+ KEY_HOME: 36,
+ KEY_END: 35,
+ KEY_PAGEUP: 33,
+ KEY_PAGEDOWN: 34,
+ KEY_INSERT: 45,
+
+ cache: {}
+ };
+
+ var docEl = document.documentElement;
+ var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
+ && 'onmouseleave' in docEl;
+
+
+
+ var isIELegacyEvent = function(event) { return false; };
+
+ if (window.attachEvent) {
+ if (window.addEventListener) {
+ isIELegacyEvent = function(event) {
+ return !(event instanceof window.Event);
+ };
+ } else {
+ isIELegacyEvent = function(event) { return true; };
+ }
+ }
+
+ var _isButton;
+
+ function _isButtonForDOMEvents(event, code) {
+ return event.which ? (event.which === code + 1) : (event.button === code);
+ }
+
+ var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
+ function _isButtonForLegacyEvents(event, code) {
+ return event.button === legacyButtonMap[code];
+ }
+
+ function _isButtonForWebKit(event, code) {
+ switch (code) {
+ case 0: return event.which == 1 && !event.metaKey;
+ case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
+ case 2: return event.which == 3;
+ default: return false;
+ }
+ }
+
+ if (window.attachEvent) {
+ if (!window.addEventListener) {
+ _isButton = _isButtonForLegacyEvents;
+ } else {
+ _isButton = function(event, code) {
+ return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
+ _isButtonForDOMEvents(event, code);
+ }
+ }
+ } else if (Prototype.Browser.WebKit) {
+ _isButton = _isButtonForWebKit;
+ } else {
+ _isButton = _isButtonForDOMEvents;
+ }
+
+ function isLeftClick(event) { return _isButton(event, 0) }
+
+ function isMiddleClick(event) { return _isButton(event, 1) }
+
+ function isRightClick(event) { return _isButton(event, 2) }
+
+ function element(event) {
+ event = Event.extend(event);
+
+ var node = event.target, type = event.type,
+ currentTarget = event.currentTarget;
+
+ if (currentTarget && currentTarget.tagName) {
+ if (type === 'load' || type === 'error' ||
+ (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+ && currentTarget.type === 'radio'))
+ node = currentTarget;
+ }
+
+ if (node.nodeType == Node.TEXT_NODE)
+ node = node.parentNode;
+
+ return Element.extend(node);
+ }
+
+ function findElement(event, expression) {
+ var element = Event.element(event);
+
+ if (!expression) return element;
+ while (element) {
+ if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
+ return Element.extend(element);
+ }
+ element = element.parentNode;
+ }
+ }
+
+ function pointer(event) {
+ return { x: pointerX(event), y: pointerY(event) };
+ }
+
+ function pointerX(event) {
+ var docElement = document.documentElement,
+ body = document.body || { scrollLeft: 0 };
+
+ return event.pageX || (event.clientX +
+ (docElement.scrollLeft || body.scrollLeft) -
+ (docElement.clientLeft || 0));
+ }
+
+ function pointerY(event) {
+ var docElement = document.documentElement,
+ body = document.body || { scrollTop: 0 };
+
+ return event.pageY || (event.clientY +
+ (docElement.scrollTop || body.scrollTop) -
+ (docElement.clientTop || 0));
+ }
+
+
+ function stop(event) {
+ Event.extend(event);
+ event.preventDefault();
+ event.stopPropagation();
+
+ event.stopped = true;
+ }
+
+
+ Event.Methods = {
+ isLeftClick: isLeftClick,
+ isMiddleClick: isMiddleClick,
+ isRightClick: isRightClick,
+
+ element: element,
+ findElement: findElement,
+
+ pointer: pointer,
+ pointerX: pointerX,
+ pointerY: pointerY,
+
+ stop: stop
+ };
+
+ var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+ m[name] = Event.Methods[name].methodize();
+ return m;
+ });
+
+ if (window.attachEvent) {
+ function _relatedTarget(event) {
+ var element;
+ switch (event.type) {
+ case 'mouseover':
+ case 'mouseenter':
+ element = event.fromElement;
+ break;
+ case 'mouseout':
+ case 'mouseleave':
+ element = event.toElement;
+ break;
+ default:
+ return null;
+ }
+ return Element.extend(element);
+ }
+
+ var additionalMethods = {
+ stopPropagation: function() { this.cancelBubble = true },
+ preventDefault: function() { this.returnValue = false },
+ inspect: function() { return '[object Event]' }
+ };
+
+ Event.extend = function(event, element) {
+ if (!event) return false;
+
+ if (!isIELegacyEvent(event)) return event;
+
+ if (event._extendedByPrototype) return event;
+ event._extendedByPrototype = Prototype.emptyFunction;
+
+ var pointer = Event.pointer(event);
+
+ Object.extend(event, {
+ target: event.srcElement || element,
+ relatedTarget: _relatedTarget(event),
+ pageX: pointer.x,
+ pageY: pointer.y
+ });
+
+ Object.extend(event, methods);
+ Object.extend(event, additionalMethods);
+
+ return event;
+ };
+ } else {
+ Event.extend = Prototype.K;
+ }
+
+ if (window.addEventListener) {
+ Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
+ Object.extend(Event.prototype, methods);
+ }
+
+ function _createResponder(element, eventName, handler) {
+ var registry = Element.retrieve(element, 'prototype_event_registry');
+
+ if (Object.isUndefined(registry)) {
+ CACHE.push(element);
+ registry = Element.retrieve(element, 'prototype_event_registry', $H());
+ }
+
+ var respondersForEvent = registry.get(eventName);
+ if (Object.isUndefined(respondersForEvent)) {
+ respondersForEvent = [];
+ registry.set(eventName, respondersForEvent);
+ }
+
+ if (respondersForEvent.pluck('handler').include(handler)) return false;
+
+ var responder;
+ if (eventName.include(":")) {
+ responder = function(event) {
+ if (Object.isUndefined(event.eventName))
+ return false;
+
+ if (event.eventName !== eventName)
+ return false;
+
+ Event.extend(event, element);
+ handler.call(element, event);
+ };
+ } else {
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
+ (eventName === "mouseenter" || eventName === "mouseleave")) {
+ if (eventName === "mouseenter" || eventName === "mouseleave") {
+ responder = function(event) {
+ Event.extend(event, element);
+
+ var parent = event.relatedTarget;
+ while (parent && parent !== element) {
+ try { parent = parent.parentNode; }
+ catch(e) { parent = element; }
+ }
+
+ if (parent === element) return;
+
+ handler.call(element, event);
+ };
+ }
+ } else {
+ responder = function(event) {
+ Event.extend(event, element);
+ handler.call(element, event);
+ };
+ }
+ }
+
+ responder.handler = handler;
+ respondersForEvent.push(responder);
+ return responder;
+ }
+
+ function _destroyCache() {
+ for (var i = 0, length = CACHE.length; i < length; i++) {
+ Event.stopObserving(CACHE[i]);
+ CACHE[i] = null;
+ }
+ }
+
+ var CACHE = [];
+
+ if (Prototype.Browser.IE)
+ window.attachEvent('onunload', _destroyCache);
+
+ if (Prototype.Browser.WebKit)
+ window.addEventListener('unload', Prototype.emptyFunction, false);
+
+
+ var _getDOMEventName = Prototype.K,
+ translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
+
+ if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
+ _getDOMEventName = function(eventName) {
+ return (translations[eventName] || eventName);
+ };
+ }
+
+ function observe(element, eventName, handler) {
+ element = $(element);
+
+ var responder = _createResponder(element, eventName, handler);
+
+ if (!responder) return element;
+
+ if (eventName.include(':')) {
+ if (element.addEventListener)
+ element.addEventListener("dataavailable", responder, false);
+ else {
+ element.attachEvent("ondataavailable", responder);
+ element.attachEvent("onlosecapture", responder);
+ }
+ } else {
+ var actualEventName = _getDOMEventName(eventName);
+
+ if (element.addEventListener)
+ element.addEventListener(actualEventName, responder, false);
+ else
+ element.attachEvent("on" + actualEventName, responder);
+ }
+
+ return element;
+ }
+
+ function stopObserving(element, eventName, handler) {
+ element = $(element);
+
+ var registry = Element.retrieve(element, 'prototype_event_registry');
+ if (!registry) return element;
+
+ if (!eventName) {
+ registry.each( function(pair) {
+ var eventName = pair.key;
+ stopObserving(element, eventName);
+ });
+ return element;
+ }
+
+ var responders = registry.get(eventName);
+ if (!responders) return element;
+
+ if (!handler) {
+ responders.each(function(r) {
+ stopObserving(element, eventName, r.handler);
+ });
+ return element;
+ }
+
+ var i = responders.length, responder;
+ while (i--) {
+ if (responders[i].handler === handler) {
+ responder = responders[i];
+ break;
+ }
+ }
+ if (!responder) return element;
+
+ if (eventName.include(':')) {
+ if (element.removeEventListener)
+ element.removeEventListener("dataavailable", responder, false);
+ else {
+ element.detachEvent("ondataavailable", responder);
+ element.detachEvent("onlosecapture", responder);
+ }
+ } else {
+ var actualEventName = _getDOMEventName(eventName);
+ if (element.removeEventListener)
+ element.removeEventListener(actualEventName, responder, false);
+ else
+ element.detachEvent('on' + actualEventName, responder);
+ }
+
+ registry.set(eventName, responders.without(responder));
+
+ return element;
+ }
+
+ function fire(element, eventName, memo, bubble) {
+ element = $(element);
+
+ if (Object.isUndefined(bubble))
+ bubble = true;
+
+ if (element == document && document.createEvent && !element.dispatchEvent)
+ element = document.documentElement;
+
+ var event;
+ if (document.createEvent) {
+ event = document.createEvent('HTMLEvents');
+ event.initEvent('dataavailable', bubble, true);
+ } else {
+ event = document.createEventObject();
+ event.eventType = bubble ? 'ondataavailable' : 'onlosecapture';
+ }
+
+ event.eventName = eventName;
+ event.memo = memo || { };
+
+ if (document.createEvent)
+ element.dispatchEvent(event);
+ else
+ element.fireEvent(event.eventType, event);
+
+ return Event.extend(event);
+ }
+
+ Event.Handler = Class.create({
+ initialize: function(element, eventName, selector, callback) {
+ this.element = $(element);
+ this.eventName = eventName;
+ this.selector = selector;
+ this.callback = callback;
+ this.handler = this.handleEvent.bind(this);
+ },
+
+ start: function() {
+ Event.observe(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ stop: function() {
+ Event.stopObserving(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ handleEvent: function(event) {
+ var element = Event.findElement(event, this.selector);
+ if (element) this.callback.call(this.element, event, element);
+ }
+ });
+
+ function on(element, eventName, selector, callback) {
+ element = $(element);
+ if (Object.isFunction(selector) && Object.isUndefined(callback)) {
+ callback = selector, selector = null;
+ }
+
+ return new Event.Handler(element, eventName, selector, callback).start();
+ }
+
+ Object.extend(Event, Event.Methods);
+
+ Object.extend(Event, {
+ fire: fire,
+ observe: observe,
+ stopObserving: stopObserving,
+ on: on
+ });
+
+ Element.addMethods({
+ fire: fire,
+
+ observe: observe,
+
+ stopObserving: stopObserving,
+
+ on: on
+ });
+
+ Object.extend(document, {
+ fire: fire.methodize(),
+
+ observe: observe.methodize(),
+
+ stopObserving: stopObserving.methodize(),
+
+ on: on.methodize(),
+
+ loaded: false
+ });
+
+ if (window.Event) Object.extend(window.Event, Event);
+ else window.Event = Event;
+})();
+
+(function() {
+ /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+ Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
+
+ var timer;
+
+ function fireContentLoadedEvent() {
+ if (document.loaded) return;
+ if (timer) window.clearTimeout(timer);
+ document.loaded = true;
+ document.fire('dom:loaded');
+ }
+
+ function checkReadyState() {
+ if (document.readyState === 'complete') {
+ document.stopObserving('readystatechange', checkReadyState);
+ fireContentLoadedEvent();
+ }
+ }
+
+ function pollDoScroll() {
+ try { document.documentElement.doScroll('left'); }
+ catch(e) {
+ timer = pollDoScroll.defer();
+ return;
+ }
+ fireContentLoadedEvent();
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
+ } else {
+ document.observe('readystatechange', checkReadyState);
+ if (window == top)
+ timer = pollDoScroll.defer();
+ }
+
+ Event.observe(window, 'load', fireContentLoadedEvent);
+})();
+
+
+Element.addMethods();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+ Before: function(element, content) {
+ return Element.insert(element, {before:content});
+ },
+
+ Top: function(element, content) {
+ return Element.insert(element, {top:content});
+ },
+
+ Bottom: function(element, content) {
+ return Element.insert(element, {bottom:content});
+ },
+
+ After: function(element, content) {
+ return Element.insert(element, {after:content});
+ }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+var Position = {
+ includeScrollOffsets: false,
+
+ prepare: function() {
+ this.deltaX = window.pageXOffset
+ || document.documentElement.scrollLeft
+ || document.body.scrollLeft
+ || 0;
+ this.deltaY = window.pageYOffset
+ || document.documentElement.scrollTop
+ || document.body.scrollTop
+ || 0;
+ },
+
+ within: function(element, x, y) {
+ if (this.includeScrollOffsets)
+ return this.withinIncludingScrolloffsets(element, x, y);
+ this.xcomp = x;
+ this.ycomp = y;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (y >= this.offset[1] &&
+ y < this.offset[1] + element.offsetHeight &&
+ x >= this.offset[0] &&
+ x < this.offset[0] + element.offsetWidth);
+ },
+
+ withinIncludingScrolloffsets: function(element, x, y) {
+ var offsetcache = Element.cumulativeScrollOffset(element);
+
+ this.xcomp = x + offsetcache[0] - this.deltaX;
+ this.ycomp = y + offsetcache[1] - this.deltaY;
+ this.offset = Element.cumulativeOffset(element);
+
+ return (this.ycomp >= this.offset[1] &&
+ this.ycomp < this.offset[1] + element.offsetHeight &&
+ this.xcomp >= this.offset[0] &&
+ this.xcomp < this.offset[0] + element.offsetWidth);
+ },
+
+ overlap: function(mode, element) {
+ if (!mode) return 0;
+ if (mode == 'vertical')
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+ element.offsetHeight;
+ if (mode == 'horizontal')
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+ element.offsetWidth;
+ },
+
+
+ cumulativeOffset: Element.Methods.cumulativeOffset,
+
+ positionedOffset: Element.Methods.positionedOffset,
+
+ absolutize: function(element) {
+ Position.prepare();
+ return Element.absolutize(element);
+ },
+
+ relativize: function(element) {
+ Position.prepare();
+ return Element.relativize(element);
+ },
+
+ realOffset: Element.Methods.cumulativeScrollOffset,
+
+ offsetParent: Element.Methods.getOffsetParent,
+
+ page: Element.Methods.viewportOffset,
+
+ clone: function(source, target, options) {
+ options = options || { };
+ return Element.clonePosition(target, source, options);
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+ function iter(name) {
+ return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+ }
+
+ instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+ function(element, className) {
+ className = className.toString().strip();
+ var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+ return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+ } : function(element, className) {
+ className = className.toString().strip();
+ var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+ if (!classNames && !className) return elements;
+
+ var nodes = $(element).getElementsByTagName('*');
+ className = ' ' + className + ' ';
+
+ for (var i = 0, child, cn; child = nodes[i]; i++) {
+ if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+ (classNames && classNames.all(function(name) {
+ return !name.toString().blank() && cn.include(' ' + name + ' ');
+ }))))
+ elements.push(Element.extend(child));
+ }
+ return elements;
+ };
+
+ return function(className, parentElement) {
+ return $(parentElement || document.body).getElementsByClassName(className);
+ };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+ initialize: function(element) {
+ this.element = $(element);
+ },
+
+ _each: function(iterator) {
+ this.element.className.split(/\s+/).select(function(name) {
+ return name.length > 0;
+ })._each(iterator);
+ },
+
+ set: function(className) {
+ this.element.className = className;
+ },
+
+ add: function(classNameToAdd) {
+ if (this.include(classNameToAdd)) return;
+ this.set($A(this).concat(classNameToAdd).join(' '));
+ },
+
+ remove: function(classNameToRemove) {
+ if (!this.include(classNameToRemove)) return;
+ this.set($A(this).without(classNameToRemove).join(' '));
+ },
+
+ toString: function() {
+ return $A(this).join(' ');
+ }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+(function() {
+ window.Selector = Class.create({
+ initialize: function(expression) {
+ this.expression = expression.strip();
+ },
+
+ findElements: function(rootElement) {
+ return Prototype.Selector.select(this.expression, rootElement);
+ },
+
+ match: function(element) {
+ return Prototype.Selector.match(element, this.expression);
+ },
+
+ toString: function() {
+ return this.expression;
+ },
+
+ inspect: function() {
+ return "#<Selector: " + this.expression + ">";
+ }
+ });
+
+ Object.extend(Selector, {
+ matchElements: function(elements, expression) {
+ var match = Prototype.Selector.match,
+ results = [];
+
+ for (var i = 0, length = elements.length; i < length; i++) {
+ var element = elements[i];
+ if (match(element, expression)) {
+ results.push(Element.extend(element));
+ }
+ }
+ return results;
+ },
+
+ findElement: function(elements, expression, index) {
+ index = index || 0;
+ var matchIndex = 0, element;
+ for (var i = 0, length = elements.length; i < length; i++) {
+ element = elements[i];
+ if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
+ return Element.extend(element);
+ }
+ }
+ },
+
+ findChildElements: function(element, expressions) {
+ var selector = expressions.toArray().join(', ');
+ return Prototype.Selector.select(selector, element || document);
+ }
+ });
+})();
diff --git a/js/script.aculo.us/builder.js b/js/script.aculo.us/builder.js
new file mode 100644
index 0000000..7325038
--- /dev/null
+++ b/js/script.aculo.us/builder.js
@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+ NODEMAP: {
+ AREA: 'map',
+ CAPTION: 'table',
+ COL: 'table',
+ COLGROUP: 'table',
+ LEGEND: 'fieldset',
+ OPTGROUP: 'select',
+ OPTION: 'select',
+ PARAM: 'object',
+ TBODY: 'table',
+ TD: 'table',
+ TFOOT: 'table',
+ TH: 'table',
+ THEAD: 'table',
+ TR: 'table'
+ },
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+ // due to a Firefox bug
+ node: function(elementName) {
+ elementName = elementName.toUpperCase();
+
+ // try innerHTML approach
+ var parentTag = this.NODEMAP[elementName] || 'div';
+ var parentElement = document.createElement(parentTag);
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+ } catch(e) {}
+ var element = parentElement.firstChild || null;
+
+ // see if browser added wrapping tags
+ if(element && (element.tagName.toUpperCase() != elementName))
+ element = element.getElementsByTagName(elementName)[0];
+
+ // fallback to createElement approach
+ if(!element) element = document.createElement(elementName);
+
+ // abort if nothing could be created
+ if(!element) return;
+
+ // attributes (or text)
+ if(arguments[1])
+ if(this._isStringOrNumber(arguments[1]) ||
+ (arguments[1] instanceof Array) ||
+ arguments[1].tagName) {
+ this._children(element, arguments[1]);
+ } else {
+ var attrs = this._attributes(arguments[1]);
+ if(attrs.length) {
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+ parentElement.innerHTML = "<" +elementName + " " +
+ attrs + "></" + elementName + ">";
+ } catch(e) {}
+ element = parentElement.firstChild || null;
+ // workaround firefox 1.0.X bug
+ if(!element) {
+ element = document.createElement(elementName);
+ for(attr in arguments[1])
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+ }
+ if(element.tagName.toUpperCase() != elementName)
+ element = parentElement.getElementsByTagName(elementName)[0];
+ }
+ }
+
+ // text, or array of children
+ if(arguments[2])
+ this._children(element, arguments[2]);
+
+ return $(element);
+ },
+ _text: function(text) {
+ return document.createTextNode(text);
+ },
+
+ ATTR_MAP: {
+ 'className': 'class',
+ 'htmlFor': 'for'
+ },
+
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ if(children.tagName) {
+ element.appendChild(children);
+ return;
+ }
+ if(typeof children=='object') { // array can hold nodes and text
+ children.flatten().each( function(e) {
+ if(typeof e=='object')
+ element.appendChild(e);
+ else
+ if(Builder._isStringOrNumber(e))
+ element.appendChild(Builder._text(e));
+ });
+ } else
+ if(Builder._isStringOrNumber(children))
+ element.appendChild(Builder._text(children));
+ },
+ _isStringOrNumber: function(param) {
+ return(typeof param=='string' || typeof param=='number');
+ },
+ build: function(html) {
+ var element = this.node('div');
+ $(element).update(html.strip());
+ return element.down();
+ },
+ dump: function(scope) {
+ if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
+
+ var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+ "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+ "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+ "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+ "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+ "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+
+ tags.each( function(tag){
+ scope[tag] = function() {
+ return Builder.node.apply(Builder, [tag].concat($A(arguments)));
+ };
+ });
+ }
+}; \ No newline at end of file
diff --git a/js/script.aculo.us/controls.js b/js/script.aculo.us/controls.js
new file mode 100644
index 0000000..5137ab5
--- /dev/null
+++ b/js/script.aculo.us/controls.js
@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2010 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005-2010 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { };
+Autocompleter.Base = Class.create({
+ baseInitialize: function(element, update, options) {
+ element = $(element);
+ this.element = element;
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+ this.oldElementValue = this.element.value;
+
+ if(this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || { };
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {
+ setHeight: false,
+ offsetTop: element.offsetHeight
+ });
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if(typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+ // Force carriage returns as token delimiters anyway
+ if (!this.options.tokens.include('\n'))
+ this.options.tokens.push('\n');
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (Prototype.Browser.IE) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+ },
+
+ activate: function() {
+ this.changed = false;
+ this.hasFocus = true;
+ this.getUpdatedChoices();
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--;
+ else this.index = this.entryCount-1;
+ this.getEntry(this.index).scrollIntoView(true);
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++;
+ else this.index = 0;
+ this.getEntry(this.index).scrollIntoView(false);
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+ var value = '';
+ if (this.options.select) {
+ var nodes = $(selectedElement).select('.' + this.options.select) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var bounds = this.getTokenBounds();
+ if (bounds[0] != -1) {
+ var newValue = this.element.value.substr(0, bounds[0]);
+ var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+ } else {
+ this.element.value = value;
+ }
+ this.oldElementValue = this.element.value;
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.down());
+
+ if(this.update.firstChild && this.update.down().childNodes) {
+ this.entryCount =
+ this.update.down().childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+ this.entryCount = 0;
+ }
+
+ this.stopIndicator();
+ this.index = 0;
+
+ if(this.entryCount==1 && this.options.autoSelect) {
+ this.selectEntry();
+ this.hide();
+ } else {
+ this.render();
+ }
+ }
+ },
+
+ addObservers: function(element) {
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+ },
+
+ onObserverEvent: function() {
+ this.changed = false;
+ this.tokenBounds = null;
+ if(this.getToken().length>=this.options.minChars) {
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ this.oldElementValue = this.element.value;
+ },
+
+ getToken: function() {
+ var bounds = this.getTokenBounds();
+ return this.element.value.substring(bounds[0], bounds[1]).strip();
+ },
+
+ getTokenBounds: function() {
+ if (null != this.tokenBounds) return this.tokenBounds;
+ var value = this.element.value;
+ if (value.strip().empty()) return [-1, 0];
+ var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+ var offset = (diff == this.oldElementValue.length ? 1 : 0);
+ var prevTokenPos = -1, nextTokenPos = value.length;
+ var tp;
+ for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+ tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+ if (tp > prevTokenPos) prevTokenPos = tp;
+ tp = value.indexOf(this.options.tokens[index], diff + offset);
+ if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+ }
+ return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+ }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+ var boundary = Math.min(newS.length, oldS.length);
+ for (var index = 0; index < boundary; ++index)
+ if (newS[index] != oldS[index])
+ return index;
+ return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ this.startIndicator();
+
+ var entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || { });
+ }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+};
+
+Ajax.InPlaceEditor = Class.create({
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = element = $(element);
+ this.prepareOptions();
+ this._controls = { };
+ arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+ Object.extend(this.options, options || { });
+ if (!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + '-inplaceeditor';
+ if ($(this.options.formId))
+ this.options.formId = '';
+ }
+ if (this.options.externalControl)
+ this.options.externalControl = $(this.options.externalControl);
+ if (!this.options.externalControl)
+ this.options.externalControlOnly = false;
+ this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+ this.element.title = this.options.clickToEditText;
+ this._boundCancelHandler = this.handleFormCancellation.bind(this);
+ this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+ this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+ this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+ this._boundWrapperHandler = this.wrapUp.bind(this);
+ this.registerListeners();
+ },
+ checkForEscapeOrReturn: function(e) {
+ if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+ if (Event.KEY_ESC == e.keyCode)
+ this.handleFormCancellation(e);
+ else if (Event.KEY_RETURN == e.keyCode)
+ this.handleFormSubmission(e);
+ },
+ createControl: function(mode, handler, extraClasses) {
+ var control = this.options[mode + 'Control'];
+ var text = this.options[mode + 'Text'];
+ if ('button' == control) {
+ var btn = document.createElement('input');
+ btn.type = 'submit';
+ btn.value = text;
+ btn.className = 'editor_' + mode + '_button';
+ if ('cancel' == mode)
+ btn.onclick = this._boundCancelHandler;
+ this._form.appendChild(btn);
+ this._controls[mode] = btn;
+ } else if ('link' == control) {
+ var link = document.createElement('a');
+ link.href = '#';
+ link.appendChild(document.createTextNode(text));
+ link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+ link.className = 'editor_' + mode + '_link';
+ if (extraClasses)
+ link.className += ' ' + extraClasses;
+ this._form.appendChild(link);
+ this._controls[mode] = link;
+ }
+ },
+ createEditField: function() {
+ var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+ var fld;
+ if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+ fld = document.createElement('input');
+ fld.type = 'text';
+ var size = this.options.size || this.options.cols || 0;
+ if (0 < size) fld.size = size;
+ } else {
+ fld = document.createElement('textarea');
+ fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+ fld.cols = this.options.cols || 40;
+ }
+ fld.name = this.options.paramName;
+ fld.value = text; // No HTML breaks conversion anymore
+ fld.className = 'editor_field';
+ if (this.options.submitOnBlur)
+ fld.onblur = this._boundSubmitHandler;
+ this._controls.editor = fld;
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+ createForm: function() {
+ var ipe = this;
+ function addText(mode, condition) {
+ var text = ipe.options['text' + mode + 'Controls'];
+ if (!text || condition === false) return;
+ ipe._form.appendChild(document.createTextNode(text));
+ };
+ this._form = $(document.createElement('form'));
+ this._form.id = this.options.formId;
+ this._form.addClassName(this.options.formClassName);
+ this._form.onsubmit = this._boundSubmitHandler;
+ this.createEditField();
+ if ('textarea' == this._controls.editor.tagName.toLowerCase())
+ this._form.appendChild(document.createElement('br'));
+ if (this.options.onFormCustomization)
+ this.options.onFormCustomization(this, this._form);
+ addText('Before', this.options.okControl || this.options.cancelControl);
+ this.createControl('ok', this._boundSubmitHandler);
+ addText('Between', this.options.okControl && this.options.cancelControl);
+ this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+ addText('After', this.options.okControl || this.options.cancelControl);
+ },
+ destroy: function() {
+ if (this._oldInnerHTML)
+ this.element.innerHTML = this._oldInnerHTML;
+ this.leaveEditMode();
+ this.unregisterListeners();
+ },
+ enterEditMode: function(e) {
+ if (this._saving || this._editing) return;
+ this._editing = true;
+ this.triggerCallback('onEnterEditMode');
+ if (this.options.externalControl)
+ this.options.externalControl.hide();
+ this.element.hide();
+ this.createForm();
+ this.element.parentNode.insertBefore(this._form, this.element);
+ if (!this.options.loadTextURL)
+ this.postProcessEditField();
+ if (e) Event.stop(e);
+ },
+ enterHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.addClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onEnterHover');
+ },
+ getText: function() {
+ return this.element.innerHTML.unescapeHTML();
+ },
+ handleAJAXFailure: function(transport) {
+ this.triggerCallback('onFailure', transport);
+ if (this._oldInnerHTML) {
+ this.element.innerHTML = this._oldInnerHTML;
+ this._oldInnerHTML = null;
+ }
+ },
+ handleFormCancellation: function(e) {
+ this.wrapUp();
+ if (e) Event.stop(e);
+ },
+ handleFormSubmission: function(e) {
+ var form = this._form;
+ var value = $F(this._controls.editor);
+ this.prepareSubmission();
+ var params = this.options.callback(form, value) || '';
+ if (Object.isString(params))
+ params = params.toQueryParams();
+ params.editorId = this.element.id;
+ if (this.options.htmlResponse) {
+ var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Updater({ success: this.element }, this.url, options);
+ } else {
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: params,
+ onComplete: this._boundWrapperHandler,
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.url, options);
+ }
+ if (e) Event.stop(e);
+ },
+ leaveEditMode: function() {
+ this.element.removeClassName(this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ if (this.options.externalControl)
+ this.options.externalControl.show();
+ this._saving = false;
+ this._editing = false;
+ this._oldInnerHTML = null;
+ this.triggerCallback('onLeaveEditMode');
+ },
+ leaveHover: function(e) {
+ if (this.options.hoverClassName)
+ this.element.removeClassName(this.options.hoverClassName);
+ if (this._saving) return;
+ this.triggerCallback('onLeaveHover');
+ },
+ loadExternalText: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this._controls.editor.disabled = true;
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._form.removeClassName(this.options.loadingClassName);
+ var text = transport.responseText;
+ if (this.options.stripLoadedTextTags)
+ text = text.stripTags();
+ this._controls.editor.value = text;
+ this._controls.editor.disabled = false;
+ this.postProcessEditField();
+ }.bind(this),
+ onFailure: this._boundFailureHandler
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+ postProcessEditField: function() {
+ var fpc = this.options.fieldPostCreation;
+ if (fpc)
+ $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+ },
+ prepareOptions: function() {
+ this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+ Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+ [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+ Object.extend(this.options, defs);
+ }.bind(this));
+ },
+ prepareSubmission: function() {
+ this._saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ registerListeners: function() {
+ this._listeners = { };
+ var listener;
+ $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+ listener = this[pair.value].bind(this);
+ this._listeners[pair.key] = listener;
+ if (!this.options.externalControlOnly)
+ this.element.observe(pair.key, listener);
+ if (this.options.externalControl)
+ this.options.externalControl.observe(pair.key, listener);
+ }.bind(this));
+ },
+ removeForm: function() {
+ if (!this._form) return;
+ this._form.remove();
+ this._form = null;
+ this._controls = { };
+ },
+ showSaving: function() {
+ this._oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ this.element.addClassName(this.options.savingClassName);
+ this.element.style.backgroundColor = this._originalBackground;
+ this.element.show();
+ },
+ triggerCallback: function(cbName, arg) {
+ if ('function' == typeof this.options[cbName]) {
+ this.options[cbName](this, arg);
+ }
+ },
+ unregisterListeners: function() {
+ $H(this._listeners).each(function(pair) {
+ if (!this.options.externalControlOnly)
+ this.element.stopObserving(pair.key, pair.value);
+ if (this.options.externalControl)
+ this.options.externalControl.stopObserving(pair.key, pair.value);
+ }.bind(this));
+ },
+ wrapUp: function(transport) {
+ this.leaveEditMode();
+ // Can't use triggerCallback due to backward compatibility: requires
+ // binding + direct element
+ this._boundComplete(transport, this.element);
+ }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+ dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+ initialize: function($super, element, url, options) {
+ this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+ $super(element, url, options);
+ },
+
+ createEditField: function() {
+ var list = document.createElement('select');
+ list.name = this.options.paramName;
+ list.size = 1;
+ this._controls.editor = list;
+ this._collection = this.options.collection || [];
+ if (this.options.loadCollectionURL)
+ this.loadCollection();
+ else
+ this.checkForExternalText();
+ this._form.appendChild(this._controls.editor);
+ },
+
+ loadCollection: function() {
+ this._form.addClassName(this.options.loadingClassName);
+ this.showLoadingText(this.options.loadingCollectionText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ var js = transport.responseText.strip();
+ if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+ throw('Server returned an invalid collection representation.');
+ this._collection = eval(js);
+ this.checkForExternalText();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadCollectionURL, options);
+ },
+
+ showLoadingText: function(text) {
+ this._controls.editor.disabled = true;
+ var tempOption = this._controls.editor.firstChild;
+ if (!tempOption) {
+ tempOption = document.createElement('option');
+ tempOption.value = '';
+ this._controls.editor.appendChild(tempOption);
+ tempOption.selected = true;
+ }
+ tempOption.update((text || '').stripScripts().stripTags());
+ },
+
+ checkForExternalText: function() {
+ this._text = this.getText();
+ if (this.options.loadTextURL)
+ this.loadExternalText();
+ else
+ this.buildOptionList();
+ },
+
+ loadExternalText: function() {
+ this.showLoadingText(this.options.loadingText);
+ var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+ Object.extend(options, {
+ parameters: 'editorId=' + encodeURIComponent(this.element.id),
+ onComplete: Prototype.emptyFunction,
+ onSuccess: function(transport) {
+ this._text = transport.responseText.strip();
+ this.buildOptionList();
+ }.bind(this),
+ onFailure: this.onFailure
+ });
+ new Ajax.Request(this.options.loadTextURL, options);
+ },
+
+ buildOptionList: function() {
+ this._form.removeClassName(this.options.loadingClassName);
+ this._collection = this._collection.map(function(entry) {
+ return 2 === entry.length ? entry : [entry, entry].flatten();
+ });
+ var marker = ('value' in this.options) ? this.options.value : this._text;
+ var textFound = this._collection.any(function(entry) {
+ return entry[0] == marker;
+ }.bind(this));
+ this._controls.editor.update('');
+ var option;
+ this._collection.each(function(entry, index) {
+ option = document.createElement('option');
+ option.value = entry[0];
+ option.selected = textFound ? entry[0] == marker : 0 == index;
+ option.appendChild(document.createTextNode(entry[1]));
+ this._controls.editor.appendChild(option);
+ }.bind(this));
+ this._controls.editor.disabled = false;
+ Field.scrollFreeActivate(this._controls.editor);
+ }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only exists for a while, in order to let ****
+//**** users adapt to the new API. Read up on the new ****
+//**** API and convert your code to it ASAP! ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+ if (!options) return;
+ function fallback(name, expr) {
+ if (name in options || expr === undefined) return;
+ options[name] = expr;
+ };
+ fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+ options.cancelLink == options.cancelButton == false ? false : undefined)));
+ fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+ options.okLink == options.okButton == false ? false : undefined)));
+ fallback('highlightColor', options.highlightcolor);
+ fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+ DefaultOptions: {
+ ajaxOptions: { },
+ autoRows: 3, // Use when multi-line w/ rows == 1
+ cancelControl: 'link', // 'link'|'button'|false
+ cancelText: 'cancel',
+ clickToEditText: 'Click to edit',
+ externalControl: null, // id|elt
+ externalControlOnly: false,
+ fieldPostCreation: 'activate', // 'activate'|'focus'|false
+ formClassName: 'inplaceeditor-form',
+ formId: null, // id|elt
+ highlightColor: '#ffff99',
+ highlightEndColor: '#ffffff',
+ hoverClassName: '',
+ htmlResponse: true,
+ loadingClassName: 'inplaceeditor-loading',
+ loadingText: 'Loading...',
+ okControl: 'button', // 'link'|'button'|false
+ okText: 'ok',
+ paramName: 'value',
+ rows: 1, // If 1 and multi-line, uses autoRows
+ savingClassName: 'inplaceeditor-saving',
+ savingText: 'Saving...',
+ size: 0,
+ stripLoadedTextTags: false,
+ submitOnBlur: false,
+ textAfterControls: '',
+ textBeforeControls: '',
+ textBetweenControls: ''
+ },
+ DefaultCallbacks: {
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ onComplete: function(transport, element) {
+ // For backward compatibility, this one is bound to the IPE, and passes
+ // the element directly. It was too often customized, so we don't break it.
+ new Effect.Highlight(element, {
+ startcolor: this.options.highlightColor, keepBackgroundImage: true });
+ },
+ onEnterEditMode: null,
+ onEnterHover: function(ipe) {
+ ipe.element.style.backgroundColor = ipe.options.highlightColor;
+ if (ipe._effect)
+ ipe._effect.cancel();
+ },
+ onFailure: function(transport, ipe) {
+ alert('Error communication with the server: ' + transport.responseText.stripTags());
+ },
+ onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+ onLeaveEditMode: null,
+ onLeaveHover: function(ipe) {
+ ipe._effect = new Effect.Highlight(ipe.element, {
+ startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+ restorecolor: ipe._originalBackground, keepBackgroundImage: true
+ });
+ }
+ },
+ Listeners: {
+ click: 'enterEditMode',
+ keydown: 'checkForEscapeOrReturn',
+ mouseover: 'enterHover',
+ mouseout: 'leaveHover'
+ }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+ loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+ initialize: function(element, delay, callback) {
+ this.delay = delay || 0.5;
+ this.element = $(element);
+ this.callback = callback;
+ this.timer = null;
+ this.lastValue = $F(this.element);
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+ },
+ delayedListener: function(event) {
+ if(this.lastValue == $F(this.element)) return;
+ if(this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+ this.lastValue = $F(this.element);
+ },
+ onTimerEvent: function() {
+ this.timer = null;
+ this.callback(this.element, $F(this.element));
+ }
+}); \ No newline at end of file
diff --git a/js/script.aculo.us/dragdrop.js b/js/script.aculo.us/dragdrop.js
new file mode 100644
index 0000000..9ebfe24
--- /dev/null
+++ b/js/script.aculo.us/dragdrop.js
@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+ throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+ drops: [],
+
+ remove: function(element) {
+ this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+ },
+
+ add: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ greedy: true,
+ hoverclass: null,
+ tree: false
+ }, arguments[1] || { });
+
+ // cache containers
+ if(options.containment) {
+ options._containers = [];
+ var containment = options.containment;
+ if(Object.isArray(containment)) {
+ containment.each( function(c) { options._containers.push($(c)) });
+ } else {
+ options._containers.push($(containment));
+ }
+ }
+
+ if(options.accept) options.accept = [options.accept].flatten();
+
+ Element.makePositioned(element); // fix IE
+ options.element = element;
+
+ this.drops.push(options);
+ },
+
+ findDeepestChild: function(drops) {
+ deepest = drops[0];
+
+ for (i = 1; i < drops.length; ++i)
+ if (Element.isParent(drops[i].element, deepest.element))
+ deepest = drops[i];
+
+ return deepest;
+ },
+
+ isContained: function(element, drop) {
+ var containmentNode;
+ if(drop.tree) {
+ containmentNode = element.treeNode;
+ } else {
+ containmentNode = element.parentNode;
+ }
+ return drop._containers.detect(function(c) { return containmentNode == c });
+ },
+
+ isAffected: function(point, element, drop) {
+ return (
+ (drop.element!=element) &&
+ ((!drop._containers) ||
+ this.isContained(element, drop)) &&
+ ((!drop.accept) ||
+ (Element.classNames(element).detect(
+ function(v) { return drop.accept.include(v) } ) )) &&
+ Position.within(drop.element, point[0], point[1]) );
+ },
+
+ deactivate: function(drop) {
+ if(drop.hoverclass)
+ Element.removeClassName(drop.element, drop.hoverclass);
+ this.last_active = null;
+ },
+
+ activate: function(drop) {
+ if(drop.hoverclass)
+ Element.addClassName(drop.element, drop.hoverclass);
+ this.last_active = drop;
+ },
+
+ show: function(point, element) {
+ if(!this.drops.length) return;
+ var drop, affected = [];
+
+ this.drops.each( function(drop) {
+ if(Droppables.isAffected(point, element, drop))
+ affected.push(drop);
+ });
+
+ if(affected.length>0)
+ drop = Droppables.findDeepestChild(affected);
+
+ if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+ if (drop) {
+ Position.within(drop.element, point[0], point[1]);
+ if(drop.onHover)
+ drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+
+ if (drop != this.last_active) Droppables.activate(drop);
+ }
+ },
+
+ fire: function(event, element) {
+ if(!this.last_active) return;
+ Position.prepare();
+
+ if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+ if (this.last_active.onDrop) {
+ this.last_active.onDrop(element, this.last_active.element, event);
+ return true;
+ }
+ },
+
+ reset: function() {
+ if(this.last_active)
+ this.deactivate(this.last_active);
+ }
+};
+
+var Draggables = {
+ drags: [],
+ observers: [],
+
+ register: function(draggable) {
+ if(this.drags.length == 0) {
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ }
+ this.drags.push(draggable);
+ },
+
+ unregister: function(draggable) {
+ this.drags = this.drags.reject(function(d) { return d==draggable });
+ if(this.drags.length == 0) {
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ Event.stopObserving(document, "keypress", this.eventKeypress);
+ }
+ },
+
+ activate: function(draggable) {
+ if(draggable.options.delay) {
+ this._timeout = setTimeout(function() {
+ Draggables._timeout = null;
+ window.focus();
+ Draggables.activeDraggable = draggable;
+ }.bind(this), draggable.options.delay);
+ } else {
+ window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+ this.activeDraggable = draggable;
+ }
+ },
+
+ deactivate: function() {
+ this.activeDraggable = null;
+ },
+
+ updateDrag: function(event) {
+ if(!this.activeDraggable) return;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ // Mozilla-based browsers fire successive mousemove events with
+ // the same coordinates, prevent needless redrawing (moz bug?)
+ if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+ this._lastPointer = pointer;
+
+ this.activeDraggable.updateDrag(event, pointer);
+ },
+
+ endDrag: function(event) {
+ if(this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ if(!this.activeDraggable) return;
+ this._lastPointer = null;
+ this.activeDraggable.endDrag(event);
+ this.activeDraggable = null;
+ },
+
+ keyPress: function(event) {
+ if(this.activeDraggable)
+ this.activeDraggable.keyPress(event);
+ },
+
+ addObserver: function(observer) {
+ this.observers.push(observer);
+ this._cacheObserverCallbacks();
+ },
+
+ removeObserver: function(element) { // element instead of observer fixes mem leaks
+ this.observers = this.observers.reject( function(o) { return o.element==element });
+ this._cacheObserverCallbacks();
+ },
+
+ notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
+ if(this[eventName+'Count'] > 0)
+ this.observers.each( function(o) {
+ if(o[eventName]) o[eventName](eventName, draggable, event);
+ });
+ if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+ },
+
+ _cacheObserverCallbacks: function() {
+ ['onStart','onEnd','onDrag'].each( function(eventName) {
+ Draggables[eventName+'Count'] = Draggables.observers.select(
+ function(o) { return o[eventName]; }
+ ).length;
+ });
+ }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+ initialize: function(element) {
+ var defaults = {
+ handle: false,
+ reverteffect: function(element, top_offset, left_offset) {
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+ new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+ queue: {scope:'_draggable', position:'end'}
+ });
+ },
+ endeffect: function(element) {
+ var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
+ queue: {scope:'_draggable', position:'end'},
+ afterFinish: function(){
+ Draggable._dragging[element] = false
+ }
+ });
+ },
+ zindex: 1000,
+ revert: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
+ delay: 0
+ };
+
+ if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+ Object.extend(defaults, {
+ starteffect: function(element) {
+ element._opacity = Element.getOpacity(element);
+ Draggable._dragging[element] = true;
+ new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
+ }
+ });
+
+ var options = Object.extend(defaults, arguments[1] || { });
+
+ this.element = $(element);
+
+ if(options.handle && Object.isString(options.handle))
+ this.handle = this.element.down('.'+options.handle, 0);
+
+ if(!this.handle) this.handle = $(options.handle);
+ if(!this.handle) this.handle = this.element;
+
+ if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+ options.scroll = $(options.scroll);
+ this._isScrollChild = Element.childOf(this.element, options.scroll);
+ }
+
+ Element.makePositioned(this.element); // fix IE
+
+ this.options = options;
+ this.dragging = false;
+
+ this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
+
+ Draggables.register(this);
+ },
+
+ destroy: function() {
+ Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+ Draggables.unregister(this);
+ },
+
+ currentDelta: function() {
+ return([
+ parseInt(Element.getStyle(this.element,'left') || '0'),
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
+ },
+
+ initDrag: function(event) {
+ if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+ Draggable._dragging[this.element]) return;
+ if(Event.isLeftClick(event)) {
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if((tag_name = src.tagName.toUpperCase()) && (
+ tag_name=='INPUT' ||
+ tag_name=='SELECT' ||
+ tag_name=='OPTION' ||
+ tag_name=='BUTTON' ||
+ tag_name=='TEXTAREA')) return;
+
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var pos = this.element.cumulativeOffset();
+ this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+
+ Draggables.activate(this);
+ Event.stop(event);
+ }
+ },
+
+ startDrag: function(event) {
+ this.dragging = true;
+ if(!this.delta)
+ this.delta = this.currentDelta();
+
+ if(this.options.zindex) {
+ this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+ this.element.style.zIndex = this.options.zindex;
+ }
+
+ if(this.options.ghosting) {
+ this._clone = this.element.cloneNode(true);
+ this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+ if (!this._originallyAbsolute)
+ Position.absolutize(this.element);
+ this.element.parentNode.insertBefore(this._clone, this.element);
+ }
+
+ if(this.options.scroll) {
+ if (this.options.scroll == window) {
+ var where = this._getWindowScroll(this.options.scroll);
+ this.originalScrollLeft = where.left;
+ this.originalScrollTop = where.top;
+ } else {
+ this.originalScrollLeft = this.options.scroll.scrollLeft;
+ this.originalScrollTop = this.options.scroll.scrollTop;
+ }
+ }
+
+ Draggables.notify('onStart', this, event);
+
+ if(this.options.starteffect) this.options.starteffect(this.element);
+ },
+
+ updateDrag: function(event, pointer) {
+ if(!this.dragging) this.startDrag(event);
+
+ if(!this.options.quiet){
+ Position.prepare();
+ Droppables.show(pointer, this.element);
+ }
+
+ Draggables.notify('onDrag', this, event);
+
+ this.draw(pointer);
+ if(this.options.change) this.options.change(this);
+
+ if(this.options.scroll) {
+ this.stopScrolling();
+
+ var p;
+ if (this.options.scroll == window) {
+ with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+ } else {
+ p = Position.page(this.options.scroll).toArray();
+ p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+ p[1] += this.options.scroll.scrollTop + Position.deltaY;
+ p.push(p[0]+this.options.scroll.offsetWidth);
+ p.push(p[1]+this.options.scroll.offsetHeight);
+ }
+ var speed = [0,0];
+ if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+ if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+ if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+ this.startScrolling(speed);
+ }
+
+ // fix AppleWebKit rendering
+ if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+
+ Event.stop(event);
+ },
+
+ finishDrag: function(event, success) {
+ this.dragging = false;
+
+ if(this.options.quiet){
+ Position.prepare();
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ Droppables.show(pointer, this.element);
+ }
+
+ if(this.options.ghosting) {
+ if (!this._originallyAbsolute)
+ Position.relativize(this.element);
+ delete this._originallyAbsolute;
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
+ var dropped = false;
+ if(success) {
+ dropped = Droppables.fire(event, this.element);
+ if (!dropped) dropped = false;
+ }
+ if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+ Draggables.notify('onEnd', this, event);
+
+ var revert = this.options.revert;
+ if(revert && Object.isFunction(revert)) revert = revert(this.element);
+
+ var d = this.currentDelta();
+ if(revert && this.options.reverteffect) {
+ if (dropped == 0 || revert != 'failure')
+ this.options.reverteffect(this.element,
+ d[1]-this.delta[1], d[0]-this.delta[0]);
+ } else {
+ this.delta = d;
+ }
+
+ if(this.options.zindex)
+ this.element.style.zIndex = this.originalZ;
+
+ if(this.options.endeffect)
+ this.options.endeffect(this.element);
+
+ Draggables.deactivate(this);
+ Droppables.reset();
+ },
+
+ keyPress: function(event) {
+ if(event.keyCode!=Event.KEY_ESC) return;
+ this.finishDrag(event, false);
+ Event.stop(event);
+ },
+
+ endDrag: function(event) {
+ if(!this.dragging) return;
+ this.stopScrolling();
+ this.finishDrag(event, true);
+ Event.stop(event);
+ },
+
+ draw: function(point) {
+ var pos = this.element.cumulativeOffset();
+ if(this.options.ghosting) {
+ var r = Position.realOffset(this.element);
+ pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+ }
+
+ var d = this.currentDelta();
+ pos[0] -= d[0]; pos[1] -= d[1];
+
+ if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+ pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+ pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+ }
+
+ var p = [0,1].map(function(i){
+ return (point[i]-pos[i]-this.offset[i])
+ }.bind(this));
+
+ if(this.options.snap) {
+ if(Object.isFunction(this.options.snap)) {
+ p = this.options.snap(p[0],p[1],this);
+ } else {
+ if(Object.isArray(this.options.snap)) {
+ p = p.map( function(v, i) {
+ return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
+ } else {
+ p = p.map( function(v) {
+ return (v/this.options.snap).round()*this.options.snap }.bind(this));
+ }
+ }}
+
+ var style = this.element.style;
+ if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+ style.left = p[0] + "px";
+ if((!this.options.constraint) || (this.options.constraint=='vertical'))
+ style.top = p[1] + "px";
+
+ if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+ },
+
+ stopScrolling: function() {
+ if(this.scrollInterval) {
+ clearInterval(this.scrollInterval);
+ this.scrollInterval = null;
+ Draggables._lastScrollPointer = null;
+ }
+ },
+
+ startScrolling: function(speed) {
+ if(!(speed[0] || speed[1])) return;
+ this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+ this.lastScrolled = new Date();
+ this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+ },
+
+ scroll: function() {
+ var current = new Date();
+ var delta = current - this.lastScrolled;
+ this.lastScrolled = current;
+ if(this.options.scroll == window) {
+ with (this._getWindowScroll(this.options.scroll)) {
+ if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+ var d = delta / 1000;
+ this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+ }
+ }
+ } else {
+ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+ this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
+ }
+
+ Position.prepare();
+ Droppables.show(Draggables._lastPointer, this.element);
+ Draggables.notify('onDrag', this);
+ if (this._isScrollChild) {
+ Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+ Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+ Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+ if (Draggables._lastScrollPointer[0] < 0)
+ Draggables._lastScrollPointer[0] = 0;
+ if (Draggables._lastScrollPointer[1] < 0)
+ Draggables._lastScrollPointer[1] = 0;
+ this.draw(Draggables._lastScrollPointer);
+ }
+
+ if(this.options.change) this.options.change(this);
+ },
+
+ _getWindowScroll: function(w) {
+ var T, L, W, H;
+ with (w.document) {
+ if (w.document.documentElement && documentElement.scrollTop) {
+ T = documentElement.scrollTop;
+ L = documentElement.scrollLeft;
+ } else if (w.document.body) {
+ T = body.scrollTop;
+ L = body.scrollLeft;
+ }
+ if (w.innerWidth) {
+ W = w.innerWidth;
+ H = w.innerHeight;
+ } else if (w.document.documentElement && documentElement.clientWidth) {
+ W = documentElement.clientWidth;
+ H = documentElement.clientHeight;
+ } else {
+ W = body.offsetWidth;
+ H = body.offsetHeight;
+ }
+ }
+ return { top: T, left: L, width: W, height: H };
+ }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+ initialize: function(element, observer) {
+ this.element = $(element);
+ this.observer = observer;
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onStart: function() {
+ this.lastValue = Sortable.serialize(this.element);
+ },
+
+ onEnd: function() {
+ Sortable.unmark();
+ if(this.lastValue != Sortable.serialize(this.element))
+ this.observer(this.element)
+ }
+});
+
+var Sortable = {
+ SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+
+ sortables: { },
+
+ _findRootElement: function(element) {
+ while (element.tagName.toUpperCase() != "BODY") {
+ if(element.id && Sortable.sortables[element.id]) return element;
+ element = element.parentNode;
+ }
+ },
+
+ options: function(element) {
+ element = Sortable._findRootElement($(element));
+ if(!element) return;
+ return Sortable.sortables[element.id];
+ },
+
+ destroy: function(element){
+ element = $(element);
+ var s = Sortable.sortables[element.id];
+
+ if(s) {
+ Draggables.removeObserver(s.element);
+ s.droppables.each(function(d){ Droppables.remove(d) });
+ s.draggables.invoke('destroy');
+
+ delete Sortable.sortables[s.element.id];
+ }
+ },
+
+ create: function(element) {
+ element = $(element);
+ var options = Object.extend({
+ element: element,
+ tag: 'li', // assumes li children, override with tag: 'tagname'
+ dropOnEmpty: false,
+ tree: false,
+ treeTag: 'ul',
+ overlap: 'vertical', // one of 'vertical', 'horizontal'
+ constraint: 'vertical', // one of 'vertical', 'horizontal', false
+ containment: element, // also takes array of elements (or id's); or false
+ handle: false, // or a CSS class
+ only: false,
+ delay: 0,
+ hoverclass: null,
+ ghosting: false,
+ quiet: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ format: this.SERIALIZE_RULE,
+
+ // these take arrays of elements or ids and can be
+ // used for better initialization performance
+ elements: false,
+ handles: false,
+
+ onChange: Prototype.emptyFunction,
+ onUpdate: Prototype.emptyFunction
+ }, arguments[1] || { });
+
+ // clear any old sortable with same element
+ this.destroy(element);
+
+ // build options for the draggables
+ var options_for_draggable = {
+ revert: true,
+ quiet: options.quiet,
+ scroll: options.scroll,
+ scrollSpeed: options.scrollSpeed,
+ scrollSensitivity: options.scrollSensitivity,
+ delay: options.delay,
+ ghosting: options.ghosting,
+ constraint: options.constraint,
+ handle: options.handle };
+
+ if(options.starteffect)
+ options_for_draggable.starteffect = options.starteffect;
+
+ if(options.reverteffect)
+ options_for_draggable.reverteffect = options.reverteffect;
+ else
+ if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+ element.style.top = 0;
+ element.style.left = 0;
+ };
+
+ if(options.endeffect)
+ options_for_draggable.endeffect = options.endeffect;
+
+ if(options.zindex)
+ options_for_draggable.zindex = options.zindex;
+
+ // build options for the droppables
+ var options_for_droppable = {
+ overlap: options.overlap,
+ containment: options.containment,
+ tree: options.tree,
+ hoverclass: options.hoverclass,
+ onHover: Sortable.onHover
+ };
+
+ var options_for_tree = {
+ onHover: Sortable.onEmptyHover,
+ overlap: options.overlap,
+ containment: options.containment,
+ hoverclass: options.hoverclass
+ };
+
+ // fix for gecko engine
+ Element.cleanWhitespace(element);
+
+ options.draggables = [];
+ options.droppables = [];
+
+ // drop on empty handling
+ if(options.dropOnEmpty || options.tree) {
+ Droppables.add(element, options_for_tree);
+ options.droppables.push(element);
+ }
+
+ (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+ var handle = options.handles ? $(options.handles[i]) :
+ (options.handle ? $(e).select('.' + options.handle)[0] : e);
+ options.draggables.push(
+ new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+ Droppables.add(e, options_for_droppable);
+ if(options.tree) e.treeNode = element;
+ options.droppables.push(e);
+ });
+
+ if(options.tree) {
+ (Sortable.findTreeElements(element, options) || []).each( function(e) {
+ Droppables.add(e, options_for_tree);
+ e.treeNode = element;
+ options.droppables.push(e);
+ });
+ }
+
+ // keep reference
+ this.sortables[element.identify()] = options;
+
+ // for onupdate
+ Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+ },
+
+ // return all suitable-for-sortable elements in a guaranteed order
+ findElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.tag);
+ },
+
+ findTreeElements: function(element, options) {
+ return Element.findChildren(
+ element, options.only, options.tree ? true : false, options.treeTag);
+ },
+
+ onHover: function(element, dropon, overlap) {
+ if(Element.isParent(dropon, element)) return;
+
+ if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+ return;
+ } else if(overlap>0.5) {
+ Sortable.mark(dropon, 'before');
+ if(dropon.previousSibling != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, dropon);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ } else {
+ Sortable.mark(dropon, 'after');
+ var nextElement = dropon.nextSibling || null;
+ if(nextElement != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, nextElement);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ }
+ },
+
+ onEmptyHover: function(element, dropon, overlap) {
+ var oldParentNode = element.parentNode;
+ var droponOptions = Sortable.options(dropon);
+
+ if(!Element.isParent(dropon, element)) {
+ var index;
+
+ var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+ var child = null;
+
+ if(children) {
+ var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+
+ for (index = 0; index < children.length; index += 1) {
+ if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+ offset -= Element.offsetSize (children[index], droponOptions.overlap);
+ } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+ child = index + 1 < children.length ? children[index + 1] : null;
+ break;
+ } else {
+ child = children[index];
+ break;
+ }
+ }
+ }
+
+ dropon.insertBefore(element, child);
+
+ Sortable.options(oldParentNode).onChange(element);
+ droponOptions.onChange(element);
+ }
+ },
+
+ unmark: function() {
+ if(Sortable._marker) Sortable._marker.hide();
+ },
+
+ mark: function(dropon, position) {
+ // mark on ghosting only
+ var sortable = Sortable.options(dropon.parentNode);
+ if(sortable && !sortable.ghosting) return;
+
+ if(!Sortable._marker) {
+ Sortable._marker =
+ ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+ hide().addClassName('dropmarker').setStyle({position:'absolute'});
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = dropon.cumulativeOffset();
+ Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+
+ if(position=='after')
+ if(sortable.overlap == 'horizontal')
+ Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+ else
+ Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+
+ Sortable._marker.show();
+ },
+
+ _tree: function(element, options, parent) {
+ var children = Sortable.findElements(element, options) || [];
+
+ for (var i = 0; i < children.length; ++i) {
+ var match = children[i].id.match(options.format);
+
+ if (!match) continue;
+
+ var child = {
+ id: encodeURIComponent(match ? match[1] : null),
+ element: element,
+ parent: parent,
+ children: [],
+ position: parent.children.length,
+ container: $(children[i]).down(options.treeTag)
+ };
+
+ /* Get the element containing the children and recurse over it */
+ if (child.container)
+ this._tree(child.container, options, child);
+
+ parent.children.push (child);
+ }
+
+ return parent;
+ },
+
+ tree: function(element) {
+ element = $(element);
+ var sortableOptions = this.options(element);
+ var options = Object.extend({
+ tag: sortableOptions.tag,
+ treeTag: sortableOptions.treeTag,
+ only: sortableOptions.only,
+ name: element.id,
+ format: sortableOptions.format
+ }, arguments[1] || { });
+
+ var root = {
+ id: null,
+ parent: null,
+ children: [],
+ container: element,
+ position: 0
+ };
+
+ return Sortable._tree(element, options, root);
+ },
+
+ /* Construct a [i] index for a particular node */
+ _constructIndex: function(node) {
+ var index = '';
+ do {
+ if (node.id) index = '[' + node.position + ']' + index;
+ } while ((node = node.parent) != null);
+ return index;
+ },
+
+ sequence: function(element) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[1] || { });
+
+ return $(this.findElements(element, options) || []).map( function(item) {
+ return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+ });
+ },
+
+ setSequence: function(element, new_sequence) {
+ element = $(element);
+ var options = Object.extend(this.options(element), arguments[2] || { });
+
+ var nodeMap = { };
+ this.findElements(element, options).each( function(n) {
+ if (n.id.match(options.format))
+ nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+ n.parentNode.removeChild(n);
+ });
+
+ new_sequence.each(function(ident) {
+ var n = nodeMap[ident];
+ if (n) {
+ n[1].appendChild(n[0]);
+ delete nodeMap[ident];
+ }
+ });
+ },
+
+ serialize: function(element) {
+ element = $(element);
+ var options = Object.extend(Sortable.options(element), arguments[1] || { });
+ var name = encodeURIComponent(
+ (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+
+ if (options.tree) {
+ return Sortable.tree(element, arguments[1]).children.map( function (item) {
+ return [name + Sortable._constructIndex(item) + "[id]=" +
+ encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+ }).flatten().join('&');
+ } else {
+ return Sortable.sequence(element, arguments[1]).map( function(item) {
+ return name + "[]=" + encodeURIComponent(item);
+ }).join('&');
+ }
+ }
+};
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+ if (!child.parentNode || child == element) return false;
+ if (child.parentNode == element) return true;
+ return Element.isParent(child.parentNode, element);
+};
+
+Element.findChildren = function(element, only, recursive, tagName) {
+ if(!element.hasChildNodes()) return null;
+ tagName = tagName.toUpperCase();
+ if(only) only = [only].flatten();
+ var elements = [];
+ $A(element.childNodes).each( function(e) {
+ if(e.tagName && e.tagName.toUpperCase()==tagName &&
+ (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+ elements.push(e);
+ if(recursive) {
+ var grandchildren = Element.findChildren(e, only, recursive, tagName);
+ if(grandchildren) elements.push(grandchildren);
+ }
+ });
+
+ return (elements.length>0 ? elements.flatten() : []);
+};
+
+Element.offsetSize = function (element, type) {
+ return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}; \ No newline at end of file
diff --git a/js/script.aculo.us/effects.js b/js/script.aculo.us/effects.js
new file mode 100644
index 0000000..860ddc0
--- /dev/null
+++ b/js/script.aculo.us/effects.js
@@ -0,0 +1,1123 @@
+// script.aculo.us effects.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ var color = '#';
+ if (this.slice(0,4) == 'rgb(') {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if (this.slice(0,1) == '#') {
+ if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+ if (this.length==7) color = this.toLowerCase();
+ }
+ }
+ return (color.length==7 ? color : (arguments[0] || this));
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+ }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
+ Element.collectTextNodesIgnoreClass(node, className) : ''));
+ }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.setStyle({fontSize: (percent/100) + 'em'});
+ if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+ return element;
+};
+
+Element.getInlineOpacity = function(element){
+ return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+ try {
+ element = $(element);
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ _elementDoesNotExistError: {
+ name: 'ElementDoesNotExistError',
+ message: 'The specified DOM element does not exist, but is required for this effect to operate'
+ },
+ Transitions: {
+ linear: Prototype.K,
+ sinoidal: function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + .5;
+ },
+ reverse: function(pos) {
+ return 1-pos;
+ },
+ flicker: function(pos) {
+ var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
+ return pos > 1 ? 1 : pos;
+ },
+ wobble: function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
+ },
+ pulse: function(pos, pulses) {
+ return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
+ },
+ spring: function(pos) {
+ return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+ },
+ none: function(pos) {
+ return 0;
+ },
+ full: function(pos) {
+ return 1;
+ }
+ },
+ DefaultOptions: {
+ duration: 1.0, // seconds
+ fps: 100, // 100= assume 66fps max.
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+ },
+ tagifyText: function(element) {
+ var tagifyStyle = 'position:relative';
+ if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if (child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ new Element('span', {style: tagifyStyle}).update(
+ character == ' ' ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if (((typeof element == 'object') ||
+ Object.isFunction(element)) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || { });
+ var masterDelay = options.delay;
+
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+ });
+ },
+ PAIRS: {
+ 'slide': ['SlideDown','SlideUp'],
+ 'blind': ['BlindDown','BlindUp'],
+ 'appear': ['Appear','Fade']
+ },
+ toggle: function(element, effect, options) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+
+ return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, options || {}));
+ }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+ initialize: function() {
+ this.effects = [];
+ this.interval = null;
+ },
+ _each: function(iterator) {
+ this.effects._each(iterator);
+ },
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ var position = Object.isString(effect.options.queue) ?
+ effect.options.queue : effect.options.queue.position;
+
+ switch(position) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'with-last':
+ timestamp = this.effects.pluck('startOn').max() || timestamp;
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+
+ if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+ this.effects.push(effect);
+
+ if (!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 15);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if (this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ for(var i=0, len=this.effects.length;i<len;i++)
+ this.effects[i] && this.effects[i].loop(timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if (!Object.isString(queueName)) return queueName;
+
+ return this.instances.get(queueName) ||
+ this.instances.set(queueName, new Effect.ScopedQueue());
+ }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+ position: null,
+ start: function(options) {
+ if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+ this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn+(this.options.duration*1000);
+ this.fromToDelta = this.options.to-this.options.from;
+ this.totalTime = this.finishOn-this.startOn;
+ this.totalFrames = this.options.fps*this.options.duration;
+
+ this.render = (function() {
+ function dispatch(effect, eventName) {
+ if (effect.options[eventName + 'Internal'])
+ effect.options[eventName + 'Internal'](effect);
+ if (effect.options[eventName])
+ effect.options[eventName](effect);
+ }
+
+ return function(pos) {
+ if (this.state === "idle") {
+ this.state = "running";
+ dispatch(this, 'beforeSetup');
+ if (this.setup) this.setup();
+ dispatch(this, 'afterSetup');
+ }
+ if (this.state === "running") {
+ pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
+ this.position = pos;
+ dispatch(this, 'beforeUpdate');
+ if (this.update) this.update(pos);
+ dispatch(this, 'afterUpdate');
+ }
+ };
+ })();
+
+ this.event('beforeStart');
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if (timePos >= this.startOn) {
+ if (timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if (this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / this.totalTime,
+ frame = (pos * this.totalFrames).round();
+ if (frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ cancel: function() {
+ if (!this.options.sync)
+ Effect.Queues.get(Object.isString(this.options.queue) ?
+ 'global' : this.options.queue.scope).remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+ if (this.options[eventName]) this.options[eventName](this);
+ },
+ inspect: function() {
+ var data = $H();
+ for(property in this)
+ if (!Object.isFunction(this[property])) data.set(property, this[property]);
+ return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+ }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+ initialize: function(effects) {
+ this.effects = effects || [];
+ this.start(arguments[1]);
+ },
+ update: function(position) {
+ this.effects.invoke('render', position);
+ },
+ finish: function(position) {
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if (effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
+ }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+ initialize: function(object, from, to) {
+ object = Object.isString(object) ? $(object) : object;
+ var args = $A(arguments), method = args.last(),
+ options = args.length == 5 ? args[3] : null;
+ this.method = Object.isFunction(method) ? method.bind(object) :
+ Object.isFunction(object[method]) ? object[method].bind(object) :
+ function(value) { object[method] = value };
+ this.start(Object.extend({ from: from, to: to }, options || { }));
+ },
+ update: function(position) {
+ this.method(position);
+ }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+ initialize: function() {
+ this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+ },
+ update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ // make this work on IE on elements without 'layout'
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ var options = Object.extend({
+ from: this.element.getOpacity() || 0.0,
+ to: 1.0
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ update: function(position) {
+ this.element.setOpacity(position);
+ }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.element.makePositioned();
+ this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+ this.originalTop = parseFloat(this.element.getStyle('top') || '0');
+ if (this.options.mode == 'absolute') {
+ this.options.x = this.options.x - this.originalLeft;
+ this.options.y = this.options.y - this.originalTop;
+ }
+ },
+ update: function(position) {
+ this.element.setStyle({
+ left: (this.options.x * position + this.originalLeft).round() + 'px',
+ top: (this.options.y * position + this.originalTop).round() + 'px'
+ });
+ }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+ return new Effect.Move(element,
+ Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+ initialize: function(element, percent) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or { } with provided values
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || { });
+ this.start(options);
+ },
+ setup: function() {
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = this.element.getStyle('position');
+
+ this.originalStyle = { };
+ ['top','left','width','height','fontSize'].each( function(k) {
+ this.originalStyle[k] = this.element.style[k];
+ }.bind(this));
+
+ this.originalTop = this.element.offsetTop;
+ this.originalLeft = this.element.offsetLeft;
+
+ var fontSize = this.element.getStyle('font-size') || '100%';
+ ['em','px','%','pt'].each( function(fontSizeType) {
+ if (fontSize.indexOf(fontSizeType)>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = fontSizeType;
+ }
+ }.bind(this));
+
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+ this.dims = null;
+ if (this.options.scaleMode=='box')
+ this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+ if (/^content/.test(this.options.scaleMode))
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+ if (!this.dims)
+ this.dims = [this.options.scaleMode.originalHeight,
+ this.options.scaleMode.originalWidth];
+ },
+ update: function(position) {
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+ if (this.options.scaleContent && this.fontSize)
+ this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+ },
+ finish: function(position) {
+ if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+ },
+ setDimensions: function(height, width) {
+ var d = { };
+ if (this.options.scaleX) d.width = width.round() + 'px';
+ if (this.options.scaleY) d.height = height.round() + 'px';
+ if (this.options.scaleFromCenter) {
+ var topd = (height - this.dims[0])/2;
+ var leftd = (width - this.dims[1])/2;
+ if (this.elementPositioning == 'absolute') {
+ if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+ if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+ } else {
+ if (this.options.scaleY) d.top = -topd + 'px';
+ if (this.options.scaleX) d.left = -leftd + 'px';
+ }
+ }
+ this.element.setStyle(d);
+ }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+ this.start(options);
+ },
+ setup: function() {
+ // Prevent executing on elements not in the layout flow
+ if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+ // Disable background image during the effect
+ this.oldStyle = { };
+ if (!this.options.keepBackgroundImage) {
+ this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+ this.element.setStyle({backgroundImage: 'none'});
+ }
+ if (!this.options.endcolor)
+ this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+ if (!this.options.restorecolor)
+ this.options.restorecolor = this.element.getStyle('background-color');
+ // init color calculations
+ this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+ this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+ },
+ update: function(position) {
+ this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+ },
+ finish: function() {
+ this.element.setStyle(Object.extend(this.oldStyle, {
+ backgroundColor: this.options.restorecolor
+ }));
+ }
+});
+
+Effect.ScrollTo = function(element) {
+ var options = arguments[1] || { },
+ scrollOffsets = document.viewport.getScrollOffsets(),
+ elementOffsets = $(element).cumulativeOffset();
+
+ if (options.offset) elementOffsets[1] += options.offset;
+
+ return new Effect.Tween(null,
+ scrollOffsets.top,
+ elementOffsets[1],
+ options,
+ function(p){ scrollTo(scrollOffsets.left, p.round()); }
+ );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ var options = Object.extend({
+ from: element.getOpacity() || 1.0,
+ to: 0.0,
+ afterFinishInternal: function(effect) {
+ if (effect.options.to!=0) return;
+ effect.element.hide().setStyle({opacity: oldOpacity});
+ }
+ }, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+ to: 1.0,
+ // force Safari to render floated elements properly
+ afterFinishInternal: function(effect) {
+ effect.element.forceRerendering();
+ },
+ beforeSetup: function(effect) {
+ effect.element.setOpacity(effect.options.from).show();
+ }}, arguments[1] || { });
+ return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldStyle = {
+ opacity: element.getInlineOpacity(),
+ position: element.getStyle('position'),
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height
+ };
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ Position.absolutize(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().setStyle(oldStyle); }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindUp = function(element) {
+ element = $(element);
+ element.makeClipping();
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.BlindDown = function(element) {
+ element = $(element);
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ return new Effect.Appear(element, Object.extend({
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+ }
+ });
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left'),
+ opacity: element.getInlineOpacity() };
+ return new Effect.Parallel(
+ [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ effect.effects[0].element.makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+ }
+ }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ distance: 20,
+ duration: 0.5
+ }, arguments[1] || {});
+ var distance = parseFloat(options.distance);
+ var split = parseFloat(options.duration) / 10.0;
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+ return new Effect.Move(element,
+ { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+ effect.element.undoPositioned().setStyle(oldStyle);
+ }}); }}); }}); }}); }}); }});
+};
+
+Effect.SlideDown = function(element) {
+ element = $(element).cleanWhitespace();
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: window.opera ? 0 : 1,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().setStyle({height: '0px'}).show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+ }, arguments[1] || { })
+ );
+};
+
+Effect.SlideUp = function(element) {
+ element = $(element).cleanWhitespace();
+ var oldInnerBottom = element.down().getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, window.opera ? 0 : 1,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.down().makePositioned();
+ if (window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping().show();
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.down().setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().undoPositioned();
+ effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+ }
+ }, arguments[1] || { })
+ );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish
+Effect.Squish = function(element) {
+ return new Effect.Scale(element, window.opera ? 1 : 0, {
+ restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping();
+ }
+ });
+};
+
+Effect.Grow = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.full
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var initialMoveX, initialMoveY;
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ initialMoveX = initialMoveY = moveX = moveY = 0;
+ break;
+ case 'top-right':
+ initialMoveX = dims.width;
+ initialMoveY = moveY = 0;
+ moveX = -dims.width;
+ break;
+ case 'bottom-left':
+ initialMoveX = moveX = 0;
+ initialMoveY = dims.height;
+ moveY = -dims.height;
+ break;
+ case 'bottom-right':
+ initialMoveX = dims.width;
+ initialMoveY = dims.height;
+ moveX = -dims.width;
+ moveY = -dims.height;
+ break;
+ case 'center':
+ initialMoveX = dims.width / 2;
+ initialMoveY = dims.height / 2;
+ moveX = -dims.width / 2;
+ moveY = -dims.height / 2;
+ break;
+ }
+
+ return new Effect.Move(element, {
+ x: initialMoveX,
+ y: initialMoveY,
+ duration: 0.01,
+ beforeSetup: function(effect) {
+ effect.element.hide().makeClipping().makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ new Effect.Parallel(
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+ new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+ new Effect.Scale(effect.element, 100, {
+ scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.setStyle({height: '0px'}).show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
+ }
+ }, options)
+ );
+ }
+ });
+};
+
+Effect.Shrink = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.none
+ }, arguments[1] || { });
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ moveX = moveY = 0;
+ break;
+ case 'top-right':
+ moveX = dims.width;
+ moveY = 0;
+ break;
+ case 'bottom-left':
+ moveX = 0;
+ moveY = dims.height;
+ break;
+ case 'bottom-right':
+ moveX = dims.width;
+ moveY = dims.height;
+ break;
+ case 'center':
+ moveX = dims.width / 2;
+ moveY = dims.height / 2;
+ break;
+ }
+
+ return new Effect.Parallel(
+ [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+ new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ effect.effects[0].element.makePositioned().makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+ }, options)
+ );
+};
+
+Effect.Pulsate = function(element) {
+ element = $(element);
+ var options = arguments[1] || { },
+ oldOpacity = element.getInlineOpacity(),
+ transition = options.transition || Effect.Transitions.linear,
+ reverser = function(pos){
+ return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
+ };
+
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 2.0, from: 0,
+ afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+ }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height };
+ element.makeClipping();
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ effect.element.hide().undoClipping().setStyle(oldStyle);
+ } });
+ }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+ initialize: function(element) {
+ this.element = $(element);
+ if (!this.element) throw(Effect._elementDoesNotExistError);
+ var options = Object.extend({
+ style: { }
+ }, arguments[1] || { });
+
+ if (!Object.isString(options.style)) this.style = $H(options.style);
+ else {
+ if (options.style.include(':'))
+ this.style = options.style.parseStyle();
+ else {
+ this.element.addClassName(options.style);
+ this.style = $H(this.element.getStyles());
+ this.element.removeClassName(options.style);
+ var css = this.element.getStyles();
+ this.style = this.style.reject(function(style) {
+ return style.value == css[style.key];
+ });
+ options.afterFinishInternal = function(effect) {
+ effect.element.addClassName(effect.options.style);
+ effect.transforms.each(function(transform) {
+ effect.element.style[transform.style] = '';
+ });
+ };
+ }
+ }
+ this.start(options);
+ },
+
+ setup: function(){
+ function parseColor(color){
+ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+ color = color.parseColor();
+ return $R(0,2).map(function(i){
+ return parseInt( color.slice(i*2+1,i*2+3), 16 );
+ });
+ }
+ this.transforms = this.style.map(function(pair){
+ var property = pair[0], value = pair[1], unit = null;
+
+ if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+ value = value.parseColor();
+ unit = 'color';
+ } else if (property == 'opacity') {
+ value = parseFloat(value);
+ if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ } else if (Element.CSS_LENGTH.test(value)) {
+ var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+ value = parseFloat(components[1]);
+ unit = (components.length == 3) ? components[2] : null;
+ }
+
+ var originalValue = this.element.getStyle(property);
+ return {
+ style: property.camelize(),
+ originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
+ targetValue: unit=='color' ? parseColor(value) : value,
+ unit: unit
+ };
+ }.bind(this)).reject(function(transform){
+ return (
+ (transform.originalValue == transform.targetValue) ||
+ (
+ transform.unit != 'color' &&
+ (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+ )
+ );
+ });
+ },
+ update: function(position) {
+ var style = { }, transform, i = this.transforms.length;
+ while(i--)
+ style[(transform = this.transforms[i]).style] =
+ transform.unit=='color' ? '#'+
+ (Math.round(transform.originalValue[0]+
+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+ (Math.round(transform.originalValue[1]+
+ (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+ (Math.round(transform.originalValue[2]+
+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+ (transform.originalValue +
+ (transform.targetValue - transform.originalValue) * position).toFixed(3) +
+ (transform.unit === null ? '' : transform.unit);
+ this.element.setStyle(style, true);
+ }
+});
+
+Effect.Transform = Class.create({
+ initialize: function(tracks){
+ this.tracks = [];
+ this.options = arguments[1] || { };
+ this.addTracks(tracks);
+ },
+ addTracks: function(tracks){
+ tracks.each(function(track){
+ track = $H(track);
+ var data = track.values().first();
+ this.tracks.push($H({
+ ids: track.keys().first(),
+ effect: Effect.Morph,
+ options: { style: data }
+ }));
+ }.bind(this));
+ return this;
+ },
+ play: function(){
+ return new Effect.Parallel(
+ this.tracks.map(function(track){
+ var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+ var elements = [$(ids) || $$(ids)].flatten();
+ return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+ }).flatten(),
+ this.options
+ );
+ }
+});
+
+Element.CSS_PROPERTIES = $w(
+ 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
+ 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+ 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+ 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+ 'fontSize fontWeight height left letterSpacing lineHeight ' +
+ 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+ 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+ 'right textIndent top width wordSpacing zIndex');
+
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+ var style, styleRules = $H();
+ if (Prototype.Browser.WebKit)
+ style = new Element('div',{style:this}).style;
+ else {
+ String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+ style = String.__parseStyleElement.childNodes[0].style;
+ }
+
+ Element.CSS_PROPERTIES.each(function(property){
+ if (style[property]) styleRules.set(property, style[property]);
+ });
+
+ if (Prototype.Browser.IE && this.include('opacity'))
+ styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+ return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+ Element.getStyles = function(element) {
+ var css = document.defaultView.getComputedStyle($(element), null);
+ return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+ styles[property] = css[property];
+ return styles;
+ });
+ };
+} else {
+ Element.getStyles = function(element) {
+ element = $(element);
+ var css = element.currentStyle, styles;
+ styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+ results[property] = css[property];
+ return results;
+ });
+ if (!styles.opacity) styles.opacity = element.getOpacity();
+ return styles;
+ };
+}
+
+Effect.Methods = {
+ morph: function(element, style) {
+ element = $(element);
+ new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+ return element;
+ },
+ visualEffect: function(element, effect, options) {
+ element = $(element);
+ var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+ new Effect[klass](element, options);
+ return element;
+ },
+ highlight: function(element, options) {
+ element = $(element);
+ new Effect.Highlight(element, options);
+ return element;
+ }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+ 'pulsate shake puff squish switchOff dropOut').each(
+ function(effect) {
+ Effect.Methods[effect] = function(element, options){
+ element = $(element);
+ Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+ return element;
+ };
+ }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
+ function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods); \ No newline at end of file
diff --git a/js/script.aculo.us/scriptaculous.js b/js/script.aculo.us/scriptaculous.js
new file mode 100644
index 0000000..0ea5c44
--- /dev/null
+++ b/js/script.aculo.us/scriptaculous.js
@@ -0,0 +1,68 @@
+// script.aculo.us scriptaculous.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// 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.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+ Version: '1.9.0',
+ require: function(libraryName) {
+ try{
+ // inserting via DOM fails in Safari 2.0, so brute force approach
+ document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+ } catch(e) {
+ // for xhtml+xml served content, fall back to DOM methods
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = libraryName;
+ document.getElementsByTagName('head')[0].appendChild(script);
+ }
+ },
+ REQUIRED_PROTOTYPE: '1.6.0.3',
+ load: function() {
+ function convertVersionString(versionString) {
+ var v = versionString.replace(/_.*|\./g, '');
+ v = parseInt(v + '0'.times(4-v.length));
+ return versionString.indexOf('_') > -1 ? v-1 : v;
+ }
+
+ if((typeof Prototype=='undefined') ||
+ (typeof Element == 'undefined') ||
+ (typeof Element.Methods=='undefined') ||
+ (convertVersionString(Prototype.Version) <
+ convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+ throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+ Scriptaculous.REQUIRED_PROTOTYPE);
+
+ var js = /scriptaculous\.js(\?.*)?$/;
+ $$('script[src]').findAll(function(s) {
+ return s.src.match(js);
+ }).each(function(s) {
+ var path = s.src.replace(js, ''),
+ includes = s.src.match(/\?.*load=([a-z,]*)/);
+ (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+ function(include) { Scriptaculous.require(path+include+'.js') });
+ });
+ }
+};
+
+Scriptaculous.load(); \ No newline at end of file
diff --git a/js/script.aculo.us/slider.js b/js/script.aculo.us/slider.js
new file mode 100644
index 0000000..438e689
--- /dev/null
+++ b/js/script.aculo.us/slider.js
@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Marty Haught, Thomas Fuchs
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+// axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+// onChange(value)
+// onSlide(value)
+Control.Slider = Class.create({
+ initialize: function(handle, track, options) {
+ var slider = this;
+
+ if (Object.isArray(handle)) {
+ this.handles = handle.collect( function(e) { return $(e) });
+ } else {
+ this.handles = [$(handle)];
+ }
+
+ this.track = $(track);
+ this.options = options || { };
+
+ this.axis = this.options.axis || 'horizontal';
+ this.increment = this.options.increment || 1;
+ this.step = parseInt(this.options.step || '1');
+ this.range = this.options.range || $R(0,1);
+
+ this.value = 0; // assure backwards compat
+ this.values = this.handles.map( function() { return 0 });
+ this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+ this.options.startSpan = $(this.options.startSpan || null);
+ this.options.endSpan = $(this.options.endSpan || null);
+
+ this.restricted = this.options.restricted || false;
+
+ this.maximum = this.options.maximum || this.range.end;
+ this.minimum = this.options.minimum || this.range.start;
+
+ // Will be used to align the handle onto the track, if necessary
+ this.alignX = parseInt(this.options.alignX || '0');
+ this.alignY = parseInt(this.options.alignY || '0');
+
+ this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+ this.handleLength = this.isVertical() ?
+ (this.handles[0].offsetHeight != 0 ?
+ this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
+ (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
+ this.handles[0].style.width.replace(/px$/,""));
+
+ this.active = false;
+ this.dragging = false;
+ this.disabled = false;
+
+ if (this.options.disabled) this.setDisabled();
+
+ // Allowed values array
+ this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+ if (this.allowedValues) {
+ this.minimum = this.allowedValues.min();
+ this.maximum = this.allowedValues.max();
+ }
+
+ this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.update.bindAsEventListener(this);
+
+ // Initialize handles in reverse (make sure first handle is active)
+ this.handles.each( function(h,i) {
+ i = slider.handles.length-1-i;
+ slider.setValue(parseFloat(
+ (Object.isArray(slider.options.sliderValue) ?
+ slider.options.sliderValue[i] : slider.options.sliderValue) ||
+ slider.range.start), i);
+ h.makePositioned().observe("mousedown", slider.eventMouseDown);
+ });
+
+ this.track.observe("mousedown", this.eventMouseDown);
+ document.observe("mouseup", this.eventMouseUp);
+ document.observe("mousemove", this.eventMouseMove);
+
+ this.initialized = true;
+ },
+ dispose: function() {
+ var slider = this;
+ Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ this.handles.each( function(h) {
+ Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+ });
+ },
+ setDisabled: function(){
+ this.disabled = true;
+ },
+ setEnabled: function(){
+ this.disabled = false;
+ },
+ getNearestValue: function(value){
+ if (this.allowedValues){
+ if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+ if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+
+ var offset = Math.abs(this.allowedValues[0] - value);
+ var newValue = this.allowedValues[0];
+ this.allowedValues.each( function(v) {
+ var currentOffset = Math.abs(v - value);
+ if (currentOffset <= offset){
+ newValue = v;
+ offset = currentOffset;
+ }
+ });
+ return newValue;
+ }
+ if (value > this.range.end) return this.range.end;
+ if (value < this.range.start) return this.range.start;
+ return value;
+ },
+ setValue: function(sliderValue, handleIdx){
+ if (!this.active) {
+ this.activeHandleIdx = handleIdx || 0;
+ this.activeHandle = this.handles[this.activeHandleIdx];
+ this.updateStyles();
+ }
+ handleIdx = handleIdx || this.activeHandleIdx || 0;
+ if (this.initialized && this.restricted) {
+ if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+ sliderValue = this.values[handleIdx-1];
+ if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+ sliderValue = this.values[handleIdx+1];
+ }
+ sliderValue = this.getNearestValue(sliderValue);
+ this.values[handleIdx] = sliderValue;
+ this.value = this.values[0]; // assure backwards compat
+
+ this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
+ this.translateToPx(sliderValue);
+
+ this.drawSpans();
+ if (!this.dragging || !this.event) this.updateFinished();
+ },
+ setValueBy: function(delta, handleIdx) {
+ this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
+ handleIdx || this.activeHandleIdx || 0);
+ },
+ translateToPx: function(value) {
+ return Math.round(
+ ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
+ (value - this.range.start)) + "px";
+ },
+ translateToValue: function(offset) {
+ return ((offset/(this.trackLength-this.handleLength) *
+ (this.range.end-this.range.start)) + this.range.start);
+ },
+ getRange: function(range) {
+ var v = this.values.sortBy(Prototype.K);
+ range = range || 0;
+ return $R(v[range],v[range+1]);
+ },
+ minimumOffset: function(){
+ return(this.isVertical() ? this.alignY : this.alignX);
+ },
+ maximumOffset: function(){
+ return(this.isVertical() ?
+ (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+ this.track.style.height.replace(/px$/,"")) - this.alignY :
+ (this.track.offsetWidth != 0 ? this.track.offsetWidth :
+ this.track.style.width.replace(/px$/,"")) - this.alignX);
+ },
+ isVertical: function(){
+ return (this.axis == 'vertical');
+ },
+ drawSpans: function() {
+ var slider = this;
+ if (this.spans)
+ $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+ if (this.options.startSpan)
+ this.setSpan(this.options.startSpan,
+ $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+ if (this.options.endSpan)
+ this.setSpan(this.options.endSpan,
+ $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+ },
+ setSpan: function(span, range) {
+ if (this.isVertical()) {
+ span.style.top = this.translateToPx(range.start);
+ span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+ } else {
+ span.style.left = this.translateToPx(range.start);
+ span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+ }
+ },
+ updateStyles: function() {
+ this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+ Element.addClassName(this.activeHandle, 'selected');
+ },
+ startDrag: function(event) {
+ if (Event.isLeftClick(event)) {
+ if (!this.disabled){
+ this.active = true;
+
+ var handle = Event.element(event);
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var track = handle;
+ if (track==this.track) {
+ var offsets = this.track.cumulativeOffset();
+ this.event = event;
+ this.setValue(this.translateToValue(
+ (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+ ));
+ var offsets = this.activeHandle.cumulativeOffset();
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
+ } else {
+ // find the handle (prevents issues with Safari)
+ while((this.handles.indexOf(handle) == -1) && handle.parentNode)
+ handle = handle.parentNode;
+
+ if (this.handles.indexOf(handle)!=-1) {
+ this.activeHandle = handle;
+ this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+ this.updateStyles();
+
+ var offsets = this.activeHandle.cumulativeOffset();
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
+ }
+ }
+ }
+ Event.stop(event);
+ }
+ },
+ update: function(event) {
+ if (this.active) {
+ if (!this.dragging) this.dragging = true;
+ this.draw(event);
+ if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+ Event.stop(event);
+ }
+ },
+ draw: function(event) {
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = this.track.cumulativeOffset();
+ pointer[0] -= this.offsetX + offsets[0];
+ pointer[1] -= this.offsetY + offsets[1];
+ this.event = event;
+ this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+ if (this.initialized && this.options.onSlide)
+ this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+ },
+ endDrag: function(event) {
+ if (this.active && this.dragging) {
+ this.finishDrag(event, true);
+ Event.stop(event);
+ }
+ this.active = false;
+ this.dragging = false;
+ },
+ finishDrag: function(event, success) {
+ this.active = false;
+ this.dragging = false;
+ this.updateFinished();
+ },
+ updateFinished: function() {
+ if (this.initialized && this.options.onChange)
+ this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+ this.event = null;
+ }
+}); \ No newline at end of file
diff --git a/js/script.aculo.us/sound.js b/js/script.aculo.us/sound.js
new file mode 100644
index 0000000..d3f8464
--- /dev/null
+++ b/js/script.aculo.us/sound.js
@@ -0,0 +1,59 @@
+// script.aculo.us sound.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+ tracks: {},
+ _enabled: true,
+ template:
+ new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+ enable: function(){
+ Sound._enabled = true;
+ },
+ disable: function(){
+ Sound._enabled = false;
+ },
+ play: function(url){
+ if(!Sound._enabled) return;
+ var options = Object.extend({
+ track: 'global', url: url, replace: false
+ }, arguments[1] || {});
+
+ if(options.replace && this.tracks[options.track]) {
+ $R(0, this.tracks[options.track].id).each(function(id){
+ var sound = $('sound_'+options.track+'_'+id);
+ sound.Stop && sound.Stop();
+ sound.remove();
+ });
+ this.tracks[options.track] = null;
+ }
+
+ if(!this.tracks[options.track])
+ this.tracks[options.track] = { id: 0 };
+ else
+ this.tracks[options.track].id++;
+
+ options.id = this.tracks[options.track].id;
+ $$('body')[0].insert(
+ Prototype.Browser.IE ? new Element('bgsound',{
+ id: 'sound_'+options.track+'_'+options.id,
+ src: options.url, loop: 1, autostart: true
+ }) : Sound.template.evaluate(options));
+ }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+ if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+ Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>');
+ else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('Windows Media') != -1 }))
+ Sound.template = new Template('<object id="sound_#{track}_#{id}" type="application/x-mplayer2" data="#{url}"></object>');
+ else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('RealPlayer') != -1 }))
+ Sound.template = new Template('<embed type="audio/x-pn-realaudio-plugin" style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>');
+ else
+ Sound.play = function(){};
+} \ No newline at end of file
diff --git a/js/script.aculo.us/unittest.js b/js/script.aculo.us/unittest.js
new file mode 100644
index 0000000..9555c22
--- /dev/null
+++ b/js/script.aculo.us/unittest.js
@@ -0,0 +1,568 @@
+// script.aculo.us unittest.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010
+
+// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005-2010 Jon Tirsen (http://www.tirsen.com)
+// (c) 2005-2010 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+ var options = Object.extend({
+ pointerX: 0,
+ pointerY: 0,
+ buttons: 0,
+ ctrlKey: false,
+ altKey: false,
+ shiftKey: false,
+ metaKey: false
+ }, arguments[2] || {});
+ var oEvent = document.createEvent("MouseEvents");
+ oEvent.initMouseEvent(eventName, true, true, document.defaultView,
+ options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
+
+ if(this.mark) Element.remove(this.mark);
+ this.mark = document.createElement('div');
+ this.mark.appendChild(document.createTextNode(" "));
+ document.body.appendChild(this.mark);
+ this.mark.style.position = 'absolute';
+ this.mark.style.top = options.pointerY + "px";
+ this.mark.style.left = options.pointerX + "px";
+ this.mark.style.width = "5px";
+ this.mark.style.height = "5px;";
+ this.mark.style.borderTop = "1px solid red;";
+ this.mark.style.borderLeft = "1px solid red;";
+
+ if(this.step)
+ alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+
+ $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+ var options = Object.extend({
+ ctrlKey: false,
+ altKey: false,
+ shiftKey: false,
+ metaKey: false,
+ keyCode: 0,
+ charCode: 0
+ }, arguments[2] || {});
+
+ var oEvent = document.createEvent("KeyEvents");
+ oEvent.initKeyEvent(eventName, true, true, window,
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+ options.keyCode, options.charCode );
+ $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+ for(var i=0; i<command.length; i++) {
+ Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+ }
+};
+
+var Test = {};
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+ initialize: function(log) {
+ this.log = $(log);
+ if (this.log) {
+ this._createLogTable();
+ }
+ },
+ start: function(testName) {
+ if (!this.log) return;
+ this.testName = testName;
+ this.lastLogLine = document.createElement('tr');
+ this.statusCell = document.createElement('td');
+ this.nameCell = document.createElement('td');
+ this.nameCell.className = "nameCell";
+ this.nameCell.appendChild(document.createTextNode(testName));
+ this.messageCell = document.createElement('td');
+ this.lastLogLine.appendChild(this.statusCell);
+ this.lastLogLine.appendChild(this.nameCell);
+ this.lastLogLine.appendChild(this.messageCell);
+ this.loglines.appendChild(this.lastLogLine);
+ },
+ finish: function(status, summary) {
+ if (!this.log) return;
+ this.lastLogLine.className = status;
+ this.statusCell.innerHTML = status;
+ this.messageCell.innerHTML = this._toHTML(summary);
+ this.addLinksToResults();
+ },
+ message: function(message) {
+ if (!this.log) return;
+ this.messageCell.innerHTML = this._toHTML(message);
+ },
+ summary: function(summary) {
+ if (!this.log) return;
+ this.logsummary.innerHTML = this._toHTML(summary);
+ },
+ _createLogTable: function() {
+ this.log.innerHTML =
+ '<div id="logsummary"></div>' +
+ '<table id="logtable">' +
+ '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+ '<tbody id="loglines"></tbody>' +
+ '</table>';
+ this.logsummary = $('logsummary');
+ this.loglines = $('loglines');
+ },
+ _toHTML: function(txt) {
+ return txt.escapeHTML().replace(/\n/g,"<br/>");
+ },
+ addLinksToResults: function(){
+ $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+ td.title = "Run only this test";
+ Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+ });
+ $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+ td.title = "Run all tests";
+ Event.observe(td, 'click', function(){ window.location.search = "";});
+ });
+ }
+};
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+ initialize: function(testcases) {
+ this.options = Object.extend({
+ testLog: 'testlog'
+ }, arguments[1] || {});
+ this.options.resultsURL = this.parseResultsURLQueryParameter();
+ this.options.tests = this.parseTestsQueryParameter();
+ if (this.options.testLog) {
+ this.options.testLog = $(this.options.testLog) || null;
+ }
+ if(this.options.tests) {
+ this.tests = [];
+ for(var i = 0; i < this.options.tests.length; i++) {
+ if(/^test/.test(this.options.tests[i])) {
+ this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+ }
+ }
+ } else {
+ if (this.options.test) {
+ this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+ } else {
+ this.tests = [];
+ for(var testcase in testcases) {
+ if(/^test/.test(testcase)) {
+ this.tests.push(
+ new Test.Unit.Testcase(
+ this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
+ testcases[testcase], testcases["setup"], testcases["teardown"]
+ ));
+ }
+ }
+ }
+ }
+ this.currentTest = 0;
+ this.logger = new Test.Unit.Logger(this.options.testLog);
+ setTimeout(this.runTests.bind(this), 1000);
+ },
+ parseResultsURLQueryParameter: function() {
+ return window.location.search.parseQuery()["resultsURL"];
+ },
+ parseTestsQueryParameter: function(){
+ if (window.location.search.parseQuery()["tests"]){
+ return window.location.search.parseQuery()["tests"].split(',');
+ };
+ },
+ // Returns:
+ // "ERROR" if there was an error,
+ // "FAILURE" if there was a failure, or
+ // "SUCCESS" if there was neither
+ getResult: function() {
+ var hasFailure = false;
+ for(var i=0;i<this.tests.length;i++) {
+ if (this.tests[i].errors > 0) {
+ return "ERROR";
+ }
+ if (this.tests[i].failures > 0) {
+ hasFailure = true;
+ }
+ }
+ if (hasFailure) {
+ return "FAILURE";
+ } else {
+ return "SUCCESS";
+ }
+ },
+ postResults: function() {
+ if (this.options.resultsURL) {
+ new Ajax.Request(this.options.resultsURL,
+ { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+ }
+ },
+ runTests: function() {
+ var test = this.tests[this.currentTest];
+ if (!test) {
+ // finished!
+ this.postResults();
+ this.logger.summary(this.summary());
+ return;
+ }
+ if(!test.isWaiting) {
+ this.logger.start(test.name);
+ }
+ test.run();
+ if(test.isWaiting) {
+ this.logger.message("Waiting for " + test.timeToWait + "ms");
+ setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+ } else {
+ this.logger.finish(test.status(), test.summary());
+ this.currentTest++;
+ // tail recursive, hopefully the browser will skip the stackframe
+ this.runTests();
+ }
+ },
+ summary: function() {
+ var assertions = 0;
+ var failures = 0;
+ var errors = 0;
+ var messages = [];
+ for(var i=0;i<this.tests.length;i++) {
+ assertions += this.tests[i].assertions;
+ failures += this.tests[i].failures;
+ errors += this.tests[i].errors;
+ }
+ return (
+ (this.options.context ? this.options.context + ': ': '') +
+ this.tests.length + " tests, " +
+ assertions + " assertions, " +
+ failures + " failures, " +
+ errors + " errors");
+ }
+};
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+ initialize: function() {
+ this.assertions = 0;
+ this.failures = 0;
+ this.errors = 0;
+ this.messages = [];
+ },
+ summary: function() {
+ return (
+ this.assertions + " assertions, " +
+ this.failures + " failures, " +
+ this.errors + " errors" + "\n" +
+ this.messages.join("\n"));
+ },
+ pass: function() {
+ this.assertions++;
+ },
+ fail: function(message) {
+ this.failures++;
+ this.messages.push("Failure: " + message);
+ },
+ info: function(message) {
+ this.messages.push("Info: " + message);
+ },
+ error: function(error) {
+ this.errors++;
+ this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+ },
+ status: function() {
+ if (this.failures > 0) return 'failed';
+ if (this.errors > 0) return 'error';
+ return 'passed';
+ },
+ assert: function(expression) {
+ var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+ try { expression ? this.pass() :
+ this.fail(message); }
+ catch(e) { this.error(e); }
+ },
+ assertEqual: function(expected, actual) {
+ var message = arguments[2] || "assertEqual";
+ try { (expected == actual) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertInspect: function(expected, actual) {
+ var message = arguments[2] || "assertInspect";
+ try { (expected == actual.inspect()) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertEnumEqual: function(expected, actual) {
+ var message = arguments[2] || "assertEnumEqual";
+ try { $A(expected).length == $A(actual).length &&
+ expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+ this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
+ ', actual ' + Test.Unit.inspect(actual)); }
+ catch(e) { this.error(e); }
+ },
+ assertNotEqual: function(expected, actual) {
+ var message = arguments[2] || "assertNotEqual";
+ try { (expected != actual) ? this.pass() :
+ this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertIdentical: function(expected, actual) {
+ var message = arguments[2] || "assertIdentical";
+ try { (expected === actual) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertNotIdentical: function(expected, actual) {
+ var message = arguments[2] || "assertNotIdentical";
+ try { !(expected === actual) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertNull: function(obj) {
+ var message = arguments[1] || 'assertNull';
+ try { (obj==null) ? this.pass() :
+ this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertMatch: function(expected, actual) {
+ var message = arguments[2] || 'assertMatch';
+ var regex = new RegExp(expected);
+ try { (regex.exec(actual)) ? this.pass() :
+ this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertHidden: function(element) {
+ var message = arguments[1] || 'assertHidden';
+ this.assertEqual("none", element.style.display, message);
+ },
+ assertNotNull: function(object) {
+ var message = arguments[1] || 'assertNotNull';
+ this.assert(object != null, message);
+ },
+ assertType: function(expected, actual) {
+ var message = arguments[2] || 'assertType';
+ try {
+ (actual.constructor == expected) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + (actual.constructor) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertNotOfType: function(expected, actual) {
+ var message = arguments[2] || 'assertNotOfType';
+ try {
+ (actual.constructor != expected) ? this.pass() :
+ this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
+ '", actual "' + (actual.constructor) + '"'); }
+ catch(e) { this.error(e); }
+ },
+ assertInstanceOf: function(expected, actual) {
+ var message = arguments[2] || 'assertInstanceOf';
+ try {
+ (actual instanceof expected) ? this.pass() :
+ this.fail(message + ": object was not an instance of the expected type"); }
+ catch(e) { this.error(e); }
+ },
+ assertNotInstanceOf: function(expected, actual) {
+ var message = arguments[2] || 'assertNotInstanceOf';
+ try {
+ !(actual instanceof expected) ? this.pass() :
+ this.fail(message + ": object was an instance of the not expected type"); }
+ catch(e) { this.error(e); }
+ },
+ assertRespondsTo: function(method, obj) {
+ var message = arguments[2] || 'assertRespondsTo';
+ try {
+ (obj[method] && typeof obj[method] == 'function') ? this.pass() :
+ this.fail(message + ": object doesn't respond to [" + method + "]"); }
+ catch(e) { this.error(e); }
+ },
+ assertReturnsTrue: function(method, obj) {
+ var message = arguments[2] || 'assertReturnsTrue';
+ try {
+ var m = obj[method];
+ if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+ m() ? this.pass() :
+ this.fail(message + ": method returned false"); }
+ catch(e) { this.error(e); }
+ },
+ assertReturnsFalse: function(method, obj) {
+ var message = arguments[2] || 'assertReturnsFalse';
+ try {
+ var m = obj[method];
+ if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+ !m() ? this.pass() :
+ this.fail(message + ": method returned true"); }
+ catch(e) { this.error(e); }
+ },
+ assertRaise: function(exceptionName, method) {
+ var message = arguments[2] || 'assertRaise';
+ try {
+ method();
+ this.fail(message + ": exception expected but none was raised"); }
+ catch(e) {
+ ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
+ }
+ },
+ assertElementsMatch: function() {
+ var expressions = $A(arguments), elements = $A(expressions.shift());
+ if (elements.length != expressions.length) {
+ this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+ return false;
+ }
+ elements.zip(expressions).all(function(pair, index) {
+ var element = $(pair.first()), expression = pair.last();
+ if (element.match(expression)) return true;
+ this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+ }.bind(this)) && this.pass();
+ },
+ assertElementMatches: function(element, expression) {
+ this.assertElementsMatch([element], expression);
+ },
+ benchmark: function(operation, iterations) {
+ var startAt = new Date();
+ (iterations || 1).times(operation);
+ var timeTaken = ((new Date())-startAt);
+ this.info((arguments[2] || 'Operation') + ' finished ' +
+ iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+ return timeTaken;
+ },
+ _isVisible: function(element) {
+ element = $(element);
+ if(!element.parentNode) return true;
+ this.assertNotNull(element);
+ if(element.style && Element.getStyle(element, 'display') == 'none')
+ return false;
+
+ return this._isVisible(element.parentNode);
+ },
+ assertNotVisible: function(element) {
+ this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+ },
+ assertVisible: function(element) {
+ this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+ },
+ benchmark: function(operation, iterations) {
+ var startAt = new Date();
+ (iterations || 1).times(operation);
+ var timeTaken = ((new Date())-startAt);
+ this.info((arguments[2] || 'Operation') + ' finished ' +
+ iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+ return timeTaken;
+ }
+};
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+ initialize: function(name, test, setup, teardown) {
+ Test.Unit.Assertions.prototype.initialize.bind(this)();
+ this.name = name;
+
+ if(typeof test == 'string') {
+ test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+ test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+ this.test = function() {
+ eval('with(this){'+test+'}');
+ }
+ } else {
+ this.test = test || function() {};
+ }
+
+ this.setup = setup || function() {};
+ this.teardown = teardown || function() {};
+ this.isWaiting = false;
+ this.timeToWait = 1000;
+ },
+ wait: function(time, nextPart) {
+ this.isWaiting = true;
+ this.test = nextPart;
+ this.timeToWait = time;
+ },
+ run: function() {
+ try {
+ try {
+ if (!this.isWaiting) this.setup.bind(this)();
+ this.isWaiting = false;
+ this.test.bind(this)();
+ } finally {
+ if(!this.isWaiting) {
+ this.teardown.bind(this)();
+ }
+ }
+ }
+ catch(e) { this.error(e); }
+ }
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+ var METHODMAP = {
+ shouldEqual: 'assertEqual',
+ shouldNotEqual: 'assertNotEqual',
+ shouldEqualEnum: 'assertEnumEqual',
+ shouldBeA: 'assertType',
+ shouldNotBeA: 'assertNotOfType',
+ shouldBeAn: 'assertType',
+ shouldNotBeAn: 'assertNotOfType',
+ shouldBeNull: 'assertNull',
+ shouldNotBeNull: 'assertNotNull',
+
+ shouldBe: 'assertReturnsTrue',
+ shouldNotBe: 'assertReturnsFalse',
+ shouldRespondTo: 'assertRespondsTo'
+ };
+ var makeAssertion = function(assertion, args, object) {
+ this[assertion].apply(this,(args || []).concat([object]));
+ };
+
+ Test.BDDMethods = {};
+ $H(METHODMAP).each(function(pair) {
+ Test.BDDMethods[pair.key] = function() {
+ var args = $A(arguments);
+ var scope = args.shift();
+ makeAssertion.apply(scope, [pair.value, args, this]); };
+ });
+
+ [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+ function(p){ Object.extend(p, Test.BDDMethods) }
+ );
+};
+
+Test.context = function(name, spec, log){
+ Test.setupBDDExtensionMethods();
+
+ var compiledSpec = {};
+ var titles = {};
+ for(specName in spec) {
+ switch(specName){
+ case "setup":
+ case "teardown":
+ compiledSpec[specName] = spec[specName];
+ break;
+ default:
+ var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+ var body = spec[specName].toString().split('\n').slice(1);
+ if(/^\{/.test(body[0])) body = body.slice(1);
+ body.pop();
+ body = body.map(function(statement){
+ return statement.strip()
+ });
+ compiledSpec[testName] = body.join('\n');
+ titles[testName] = specName;
+ }
+ }
+ new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+}; \ No newline at end of file
diff --git a/js/tablecontrol.js b/js/tablecontrol.js
new file mode 100644
index 0000000..f389c42
--- /dev/null
+++ b/js/tablecontrol.js
@@ -0,0 +1,487 @@
+/** coding:utf-8 */
+
+/** Some helper's functions
+ * Public domain or unknown author(s)
+ * TODO : move it to another file
+ **/
+function findPos(obj) {
+ var curleft = curtop = 0;
+ if (obj.offsetParent) {
+ curleft = obj.offsetLeft
+ curtop = obj.offsetTop
+ while (obj = obj.offsetParent) {
+ curleft += obj.offsetLeft
+ curtop += obj.offsetTop
+ }
+ }
+ return [curleft,curtop];
+}
+
+function getParent(element, parentTagName)
+{
+ if ( ! element )
+ return null;
+ else if ( element.nodeType == 1 && element.tagName.toLowerCase() == parentTagName.toLowerCase() )
+ return element;
+ else
+ return getParent(element.parentNode, parentTagName);
+}
+function getNextSibling(elt)
+{
+ var sibling = elt.nextSibling;
+ while (sibling != null) {
+ if (sibling.nodeName == elt.nodeName) return sibling;
+ sibling = sibling.nextSibling;
+ }
+ return null;
+}
+function getPreviousSibling(elt)
+{
+ var sibling = elt.previousSibling;
+ while (sibling != null) {
+ if (sibling.nodeName == elt.nodeName)
+ return sibling;
+ sibling = sibling.previousSibling;
+ }
+ return null;
+}
+function strRepeat(str, n) {
+ var i, ret = '';
+ for ( i = 0; i < n; i++ )
+ ret += str;
+ return ret;
+}
+
+/** @class TableControl
+ * @author Rémi Lanvin
+ */
+var TableControl = {
+ controlled_tables: [],
+ active_rows: [],
+ default_options: {
+ tree: false, // way of sorting rows
+ activeClassName: 'active',
+ spreadActiveClass: true, // spread the 'active' class to childrens ?
+ controlBox: null // an id
+ },
+
+ /**
+ * Init the table 'table_id'
+ * @param table_id table id
+ * @param options an 'options' object - optional
+ */
+ create: function(table_id)
+ {
+
+ var table = $(table_id);
+ if ( ! table || table.tagName != 'TABLE' )
+ return;
+
+ var options = Object.extend(this.default_options, arguments[1] || {});
+
+ var tbody = table.getElementsByTagName('TBODY');
+ var row = tbody[0].getElementsByTagName('TR');
+
+ var i, j, input;
+ for (i = 0; i < row.length; i++ ) {
+ row[i].table_id = table_id;
+
+ input = row[i].getElementsByTagName("INPUT");
+ // initialize tree values
+ if ( options.tree ) {
+ row[i].tree_left = 0;
+ row[i].tree_right = 0;
+
+ for ( j = 0; j < input.length; j++ ) {
+ if ( input[j].name.substring(0,3) == 'lft' )
+ row[i].tree_left = input[j];
+ else if ( input[j].name.substring(0,3) == 'rgt' )
+ row[i].tree_right = input[j];
+ }
+
+ row[i].tree_diff = parseInt(row[i].tree_right.value,10) - parseInt(row[i].tree_left.value,10);
+
+ span = row[i].getElementsByTagName("SPAN");
+ for ( j = 0; j < span.length; j++ ) {
+ if ( span[j].className == "depthmark" )
+ row[i].tree_depthmark = span[j];
+ }
+ }
+ // initialize classical position values
+ else {
+ for ( j = 0; j < input.length; j++ ) {
+ if ( input[j].name.substring(0,13) == 'list_position' )
+ row[i].list_position = input[j];
+ }
+ }
+ Event.observe(row[i], 'click', function(e) { TableControl._onClick(e) }, false);
+ }
+ if ( options.controlBox ) {
+ options.controlBoxElt = $(options.controlBox);
+ // may not be a control box if there are no rows currently
+ if (options.controlBoxElt)
+ options.controlBoxElt.style.left = parseInt(findPos(table)) + parseInt (table.scrollWidth - 30) + 'px';
+ }
+
+
+ if ( options.tree ) {
+ this._buildTree(table);
+ var form = getParent(table, "FORM");
+ if ( form )
+ Event.observe(form, 'submit', function(e) { TableControl._calculateValues(table.root_node) }, false);
+ }
+
+ this.controlled_tables[table_id] = options;
+ },
+
+ /**
+ * Move up the active row
+ */
+ up: function(table_id)
+ {
+ var options;
+ if ( ! ( options = this.controlled_tables[table_id] ) )
+ return;
+
+ var row = this.active_rows[table_id];
+ if ( ! row )
+ return;
+
+ if ( options.tree ) {
+ var previous_row = this._getPreviousTreeNode(row);
+ // already on the top
+ if ( ! previous_row ) return;
+
+ var parent = row.parentNode;
+ this._swapTreeNode(row, previous_row);
+ this._moveSubTreeBefore(row, previous_row, parent);
+ }
+ else {
+ var previous_row = getPreviousSibling(row);
+ if ( ! previous_row )
+ return;
+ var parent = row.parentNode;
+ parent.removeChild(row);
+ parent.insertBefore(row, previous_row);
+
+ var tmp = row.list_position.value;
+ row.list_position.value = previous_row.list_position.value;
+ previous_row.list_position.value = tmp;
+ }
+ },
+
+ /**
+ * Move down the active row
+ */
+ down: function(table_id)
+ {
+ var options;
+ if ( ! ( options = this.controlled_tables[table_id]) )
+ return;
+
+ var row = this.active_rows[table_id];
+ if ( ! row )
+ return;
+
+ if ( options.tree ) {
+ var next_row = this._getNextTreeNode(row);
+ // already on the bottom
+ if ( ! next_row ) return;
+
+ var next_next_row = this._getNextTreeNode(next_row);
+ var parent = row.parentNode;
+
+ this._swapTreeNode(row, next_row);
+
+ if ( next_next_row )
+ this._moveSubTreeBefore(row, next_next_row, parent);
+ else {
+ var last_child = ( next_row.childs.length > 0 ? next_row.childs[next_row.childs.length - 1] : next_row );
+ var end_row = getNextSibling(last_child);
+ this._moveSubTreeBefore(row, end_row, parent);
+ }
+ }
+ else {
+ var next_row = getNextSibling(row);
+ if ( ! next_row )
+ return;
+ var parent = next_row.parentNode;
+ parent.removeChild(next_row);
+ parent.insertBefore(next_row, row);
+
+ var tmp = row.list_position.value;
+ row.list_position.value = next_row.list_position.value;
+ next_row.list_position.value = tmp;
+ }
+ },
+
+ /**
+ * Move deeper the active row
+ * Only works when the option 'tree' is true
+ */
+ deeper: function(table_id)
+ {
+ var options;
+ if ( ! ( options = this.controlled_tables[table_id] ) )
+ return;
+ if ( ! options.tree )
+ return;
+
+ var row = this.active_rows[table_id];
+ if ( ! row )
+ return;
+
+ // check if futur parent is deeper enough
+ var previous_row = getPreviousSibling(row);
+ if ( ! previous_row || (this._getDepth(previous_row) < this._getDepth(row) ))
+ return;
+
+ previous_row = this._getPreviousTreeNode(row);
+ this._removeTreeNode(row);
+ this._addTreeNode(row, previous_row);
+
+ this._forEachChilds(row, function(node) {
+ var old_depth = TableControl._getDepth(node);
+ var new_depth = old_depth + 1;
+ var e = new RegExp('depth' + old_depth, "g");
+ node.className = node.className.replace(e, 'depth' + new_depth);
+ node.tree_depthmark.innerHTML = strRepeat('&rarr;', new_depth);
+ });
+ },
+
+ /**
+ * Move shallower the active row
+ * Only works when the option 'tree' is true
+ */
+ shallower: function (table_id)
+ {
+ var options;
+ if ( ! ( options = this.controlled_tables[table_id] ) )
+ return;
+ if ( ! options.tree )
+ return;
+
+ var row = this.active_rows[table_id];
+ if ( ! row )
+ return;
+
+ // check the depth
+ if ( this._getDepth(row) <= 0 )
+ return;
+
+ var parent = row.parent;
+ this._removeTreeNode(row);
+ this._addTreeNode(row, parent.parent, parent);
+
+ this._forEachChilds(row, function(node) {
+ var old_depth = TableControl._getDepth(node);
+ var new_depth = old_depth - 1;
+ var e = new RegExp('depth' + old_depth, "g");
+ node.className = node.className.replace(e, 'depth' + new_depth);
+ node.tree_depthmark.innerHTML = strRepeat('&rarr;', new_depth);
+ });
+ },
+
+ /** @internal stuff **/
+
+ /** @private
+ * OnClick handler. Activate the selected row
+ */
+ _onClick: function(e)
+ {
+ var row = Event.element(e);
+ if ( row.tagName != 'TR' )
+ row = getParent(row, 'TR');
+
+ var active_row = this.active_rows[row.table_id];
+ var options = this.controlled_tables[row.table_id];
+
+ // desactivate previously active row
+ if ( active_row ) {
+ Element.removeClassName(active_row, options.activeClassName);
+ if ( options.tree && options.spreadActiveClass ) {
+ this._forEachChilds(active_row, function(e) {
+ Element.removeClassName(e, options.activeClassName);
+ });
+ }
+ }
+ else if ( options.controlBoxElt ) {
+ Element.addClassName(options.controlBoxElt, options.activeClassName);
+ }
+ // if clicking on the same row : no more active row
+ if ( active_row && active_row == row ) {
+ this.active_rows[row.table_id] = null;
+ Element.removeClassName(options.controlBoxElt, options.activeClassName);
+ }
+ // else activate the new selected row
+ else {
+ Element.addClassName(row, options.activeClassName);
+ if ( options.tree && options.spreadActiveClass ) {
+ this._forEachChilds(row, function(e) {
+ Element.addClassName(e, options.activeClassName);
+ });
+ }
+ this.active_rows[row.table_id] = row;
+ }
+ return;
+ },
+
+ /** @private
+ * Convert a class name like "depthN" to "N"
+ */
+ _getDepth: function(elt)
+ {
+ return ( elt ? parseInt(elt.className.substr(5),10) : -1 );
+ },
+
+ /** @private
+ * Return the previous node in the tree
+ */
+ _getPreviousTreeNode: function(node)
+ {
+ if ( ! node || ! node.parent ) return null;
+ var i = node.parent.childs.indexOf(node);
+ return (i > 0 ? node.parent.childs[i-1] : null);
+ },
+
+ /** @private
+ * Return the next node in the tree
+ */
+ _getNextTreeNode: function (node)
+ {
+ if ( ! node || ! node.parent ) return null;
+ var i = node.parent.childs.indexOf(node);
+ return (i < node.parent.childs.length - 1 ? node.parent.childs[i+1] : null);
+ },
+
+ /** @private
+ * Swap 'node1' and 'node2' in the tree
+ */
+ _swapTreeNode: function(node1, node2)
+ {
+ if ( !node1 || !node2 || (node1.parent != node2.parent) )
+ return false;
+
+ var node1_pos = node1.parent.childs.indexOf(node1);
+ var node2_pos = node1.parent.childs.indexOf(node2);
+ node1.parent.childs[node1_pos] = node2;
+ node1.parent.childs[node2_pos] = node1;
+ return true;
+ },
+
+ /** @private
+ * Remove 'nove' from the tree
+ */
+ _removeTreeNode: function(node)
+ {
+ if ( ! node || ! node.parent )
+ return false;
+ var i = node.parent.childs.indexOf(node);
+ node.parent.childs.splice(i, 1);
+ node.parent = null;
+ },
+
+ /** @private
+ * Add 'node' as child of 'parent'.
+ * If 'position' if given, 'node' is added before 'position'. Else it is
+ * added after the last child.
+ */
+ _addTreeNode: function(node, parent, position)
+ {
+ if ( ! node || ! parent )
+ return;
+ node.parent = parent;
+ if ( position ) {
+ var i = parent.childs.indexOf(position) + 1;
+ if ( i < parent.childs.length )
+ parent.childs.splice(i, 0, node);
+ else
+ parent.childs.push(node);
+ }
+ else
+ parent.childs.push(node);
+ },
+
+ /** @private
+ * Do f(node) for each node's childs (including itself).
+ */
+ _forEachChilds: function(node, f)
+ {
+ f(node);
+ var i;
+ for (i = 0; i < node.childs.length; i++ )
+ this._forEachChilds(node.childs[i], f);
+ },
+ /** @private
+ * Move the subtree starting from 'node' before 'position'
+ * @param parent the DOM parent
+ */
+ _moveSubTreeBefore: function(node, position, parent)
+ {
+ this._forEachChilds(node, function(node) {
+ parent.removeChild(node);
+ if ( position )
+ parent.insertBefore(node, position);
+ else
+ parent.appendChild(node);
+ });
+ },
+ /** @private
+ * Rebuild a tree from a non-recursive DOM structure
+ */
+ _buildTree: function(table)
+ {
+ if ( ! table )
+ return;
+
+ table.root_node = {
+ is_root_node: true,
+ childs: new Array()
+ };
+ var tbody = table.getElementsByTagName("TBODY");
+ var row = tbody[0].getElementsByTagName("TR");
+ var i;
+ for ( i = 0; i < row.length; i++ ) {
+ if ( this._getDepth(row[i]) == 0 ) {
+ table.root_node.childs.push(row[i])
+ this._buildSubTree(row[i], table.root_node);
+ }
+ }
+ },
+ _buildSubTree: function(node, parent)
+ {
+ node.parent = parent;
+ var node_depth = this._getDepth(node);
+ var child_depth = node_depth + 1;
+ var next_node = getNextSibling(node);
+ var next_node_depth = -1;
+ node.childs = new Array();
+ while ( next_node && ( (next_node_depth = this._getDepth(next_node)) > node_depth ) ) {
+ if ( next_node_depth == child_depth ) {
+ node.childs.push(next_node);
+ this._buildSubTree(next_node, node);
+ }
+ next_node = getNextSibling(next_node);
+ }
+ },
+ /** @private
+ * Do a tree traversal algorithm and fill "lft" and "rgt" values
+ */
+ _calculateValues: function(node, n)
+ {
+ if ( node.is_root_node ) {
+ n = new Array();
+ n.push(1);
+ }
+ else {
+ node.tree_left.value = ++(n[0]);
+ }
+ var i;
+ for ( i = 0; i < node.childs.length; i++ )
+ this._calculateValues(node.childs[i], n);
+
+ if ( ! node.is_root_node ) {
+ node.tree_right.value = ++(n[0]);
+ }
+ }
+}
diff --git a/js/tabs.js b/js/tabs.js
new file mode 100644
index 0000000..58828a7
--- /dev/null
+++ b/js/tabs.js
@@ -0,0 +1,148 @@
+/*
+ * $Id$
+ */
+addEvent(window, "load", initTabs);
+function addEvent(elm, evType, fn, useCapture)
+// addEvent and removeEvent
+// cross-browser event handling for IE5+, NS6 and Mozilla
+// By Scott Andrew
+{
+ if (elm.addEventListener){
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ } else if (elm.attachEvent){
+ var r = elm.attachEvent("on"+evType, fn);
+ return r;
+ } else {
+ alert("Handler could not be removed");
+ }
+}
+
+var _TAB_DIVS;
+
+// tabs handling {{{
+// show tab with given id
+function showTabById(tabid, noEval) { // {{{
+ var divs = document.getElementsByTagName('div');
+ var tab = document.getElementById(tabid);
+ var submenu = document.getElementById('submenu');
+ var i;
+ var el = document.getElementById(tabid + "taba");
+
+ if (!noEval && el) {
+ eval(el.getAttribute('onmousedown'));
+ }
+ for (i=0; i<divs.length; i++) {
+ if (divs[i].className && (divs[i].className.indexOf('tab') > -1)) {
+ //divs[i].style.display = 'none';
+ if (divs[i].className && (divs[i].className.indexOf(' active') > -1)) {
+ divs[i].className = divs[i].className.substr(0, divs[i].className.length-7);
+ }
+ }
+ }
+
+ if (tab) {
+ //tab.style.display = 'block';
+ tab.className = tab.className +' active';
+
+ if (submenu) {
+ var links = submenu.getElementsByTagName('a');
+ for (i=0; i<links.length; i++) {
+ if (links[i].href.match('^.*#'+tabid+'$')) {
+ links[i].className = links[i].className.replace(/ active\b/g, '') + ' active';
+ } else { links[i].className = links[i].className.replace(/ active\b/g, ''); }
+ }
+ }
+ }
+ //if (window.scrollTo(0,0)) {
+ // window.scrollTo(0,0);
+ //}
+} // }}}
+
+// create JavaScript calls to switch tabs
+function makeTabLinks() { // {{{
+ var submenu = document.getElementById('submenu');
+ var links, i, target;
+
+ if (submenu) {
+ links = submenu.getElementsByTagName('a');
+ for (i=0; i<links.length; i++) {
+ var href = links[i].getAttribute('href');
+ target = href.substring(href.indexOf('#')+1);
+ links[i]['onclick'] = new Function("showTabById('"+target+"'); return false;");
+ }
+ }
+} // }}}
+
+// show tab with given number
+function showTabByNumber(number) { // {{{
+ var targets = new Array(); // tab names
+ var divs = document.getElementsByTagName('div');
+ var i;
+
+ for (i=0; i<divs.length; i++) {
+ // tweak for displaying comments-tab 'tab active' also if javascript is disabled.
+ if (divs[i].className == 'tab' || divs[i].className == 'tab active') {
+ targets[targets.length] = divs[i].id; //array[array.length]= same as .push, but IE-compatible.
+ }
+ }
+ if (number >= targets.length) {
+ number = targets.length-1;
+ }
+ showTabById(targets[number]);
+} // }}}
+
+// get list of all DIVs that contain tabs
+function getTabDivs() {/*{{{*/
+ if (_TAB_DIVS == null) {
+ _TAB_DIVS = new Array();
+ var divs = document.getElementsByTagName('div');
+ var i;
+ for (i=0; i<divs.length; i++) {
+ if (divs[i].className && (divs[i].className.indexOf('tab') > -1)) {
+ _TAB_DIVS[_TAB_DIVS.length] = divs[i]; //array[array.length]= same as .push, but IE-compatible.
+ }
+ }
+ }
+ return _TAB_DIVS;
+}/*}}}*/
+
+// tabs init
+// show first tab or tab with given name (string after #)
+function initTabs() {/*{{{*/
+ var target = location.href.substring(location.href.indexOf('#')+1);
+ if (target.match(/comment/)) {
+ target = "comments";
+ }
+
+ makeTabLinks();
+
+ if (target && document.getElementById(target) && document.getElementById(target).className == 'tab') {
+ showTabById(target);
+ } else {
+ showTabByNumber(0);
+ }
+ var history = document.getElementById('history');
+ if (history) {
+ addEvent(history,'click',uglyHistoryCommentFix);
+ }
+
+}/*}}}*/
+function uglyHistoryCommentFix(e) {
+ var target = e.target || window.event.srcElement;
+ if ('A' == target.nodeName) {
+ var re= /#comments/;
+ if (re.test(target.href)) {
+ showTabById('comments');
+ }
+ }
+ var history = document.getElementById('history');
+ if (history) {
+ addEvent(history,'click',uglyHistoryCommentFix);
+ }
+}
+// }}}
+
+// }}}
+
+// vim:enc=utf-8:fenc=utf-8:fdm=marker
diff --git a/lang/bg.php b/lang/bg.php
new file mode 100644
index 0000000..b9d2b0b
--- /dev/null
+++ b/lang/bg.php
@@ -0,0 +1,826 @@
+<?php
+//
+// This file is auto generated with .langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the .langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Потребител',
+'username' => 'ПотребителÑко име',
+'realname' => 'ИÑтинÑко име',
+'emailaddress' => 'Е-мейл адреÑ',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Тип на извеÑÑ‚Ñване',
+'group' => 'Група',
+'accountenabled' => 'Ðкаунтът е активиран',
+'updatedetails' => 'ОбновÑване',
+'setglobally' => 'Това предпочитание е уÑтановено глобално.',
+'usergroupmanage' => 'Управление на потребители и групи',
+'newuser' => 'РегиÑтриране на нов потребител',
+'newgroup' => 'Създаване на нова група',
+'yes' => 'Да',
+'no' => 'Ðе',
+'editgroup' => 'Редактиране на група',
+'groupname' => 'Име на групата',
+'description' => 'ОпиÑание',
+'admin' => 'ÐдминиÑтративна група',
+'opennewtasks' => 'ОтварÑне на нови задачи',
+'modifytasks' => 'Редактиране на ÑъщеÑтвуващи задачи',
+'addcomments' => 'ДобавÑне на коментари',
+'attachfiles' => 'Прикачане на файлове',
+'vote' => 'ГлаÑуване',
+'groupenabled' => 'Членовете могат да влизат в ÑиÑтемата',
+'groupopen' => 'Членовете могат да влизат в ÑиÑтемата',
+'tasktypelist' => 'СпиÑък на типовете задачи',
+'categorylist' => 'СпиÑък на категориите',
+'oslist' => 'СпиÑък на операционните ÑиÑтеми',
+'resolutionlist' => 'СпиÑък на резолюциите',
+'versionlist' => 'СпиÑък на верÑиите',
+'severitylist' => 'СпиÑък на Ñтепените на трудноÑÑ‚',
+'listnote' => 'Забележка: Изключването на поле "Показване" може да промени нÑкои задачи в режим на редактиране. ПромÑната в полето "Име" ще Ñе отрази във вÑички задачи, които използват промененото име. ÐÑкои от полетата Ñа забранени за изтриване. Това е така или за коректното функциониране на ÑиÑтемата или пък те Ñе използват в задачите.',
+'name' => 'Име',
+'order' => 'Ред',
+'back' => 'Ðазад',
+'text' => 'ТекÑÑ‚',
+'highlight' => 'ОÑветÑване',
+'show' => 'Показване',
+'owner' => 'СобÑтвеник',
+'selectowner' => 'Избери ÑобÑтвеник',
+'update' => 'ОбновÑване',
+'addnew' => 'ДобавÑне',
+'flysprayprefs' => 'Flyspray конфигурациÑ',
+'projecttitle' => 'Име на проекта',
+'baseurl' => 'Базов URL за тази инÑталациÑ',
+'replyaddress' => 'Обратен е-майл Ð°Ð´Ñ€ÐµÑ Ð·Ð° извеÑÑ‚Ñване',
+'themestyle' => 'Тема / Ñтил',
+'language' => 'Език',
+'anonview' => 'Разрешаване на анонимните потребители да виждат задачите',
+'allowanon' => 'Разрешаване на анонимните потребители да отварÑÑ‚ нови задачи',
+'never' => 'Ðикога',
+'anonymously' => 'Ðнонимно',
+'afterregister' => 'Само Ñлед региÑтриране',
+'spamproof' => 'Разрешаване на код за потвърждение за региÑтрации на нови потребители',
+'anongroup' => 'Група за новите потребители',
+'groupassigned' => 'Ðа членовете на тези групи могат да бъдат назначавани задачи',
+'forcenotify' => 'ИзвеÑтиÑта за задачи да бъдат като',
+'neversend' => 'Да не Ñе изпращат',
+'userchoose' => 'Ð’Ñеки потребител да избира',
+'email' => 'Е-майл',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'СобÑтвеник на категориÑта по подразбиране',
+'noone' => 'Ðикой',
+'jabbernotify' => 'Jabber извеÑÑ‚Ñване',
+'jabberserver' => 'Сървър',
+'jabberport' => 'Порт',
+'jabberuser' => 'ПотребителÑко име',
+'jabberpass' => 'ПотребителÑка парола',
+'saveoptions' => 'Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° конфигурациÑта',
+'editcomment' => 'Редактиране на коментар',
+'commentby' => 'Коментар от',
+'saveeditedcomment' => 'Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ð½Ð¸Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€',
+'projectprefs' => 'Опции на проекта',
+'pagetitle' => 'Заглавие на Ñтраницата',
+'defaultproject' => 'Проект по подразбиране',
+'projectlists' => 'СпиÑък от проекти',
+'showlogo' => 'Показване на лого изображение',
+'intromessage' => 'Уводно Ñъобщение',
+'isactive' => 'Проектът е активен',
+'createproject' => 'Създаване на нов проект',
+'nopermission' => 'ÐÑмате разрешение да използвате тази Ñтраница.',
+'listordertip' => 'Ред, в който елементите ще Ñе поÑвÑват в ÑпиÑъка',
+'listshowtip' => 'Показване на този елемент в ÑпиÑъка',
+'categoryownertip' => 'Този потребител ще получава извеÑтиÑ, когато бъде отворена задача в тази категориÑ',
+'categoryparenttip' => 'РодителÑката категориÑ, под коÑто ще бъде тази нова категориÑ',
+'notsubcategory' => 'ÐÐ¸ÐºÐ¾Ñ (ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð½Ð° най-горно ниво)',
+'showinlineimages' => 'Показване на прикачените Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² текÑта',
+'dateformat' => 'Формат на датата',
+'dateformat_extended' => 'Подробен формат на датата',
+'cache_feeds' => 'Кеширане на емиÑиите',
+'no_cache' => 'Без кеширане',
+'cache_disk' => 'Кеширане на диÑк',
+'cache_db' => 'Кеширане в база данни',
+'subcategoryof' => 'Под-ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð½Ð°',
+'visiblecolumns' => 'Колони, показвани в ÑпиÑъка ÑÑŠÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'tense' => 'Време',
+'listtensetip' => 'Минало, ÐаÑтоÑще или Бъдеще',
+'past' => 'Минало',
+'present' => 'ÐаÑтоÑще',
+'future' => 'Бъдеще',
+'oldpass' => 'Стара парола',
+'nooldpass' => 'Ðе е била уÑтановена Ñтара парола',
+'oldpasswrong' => 'Ðеправилна Ñтара парола',
+'changepass' => 'Смени паролата',
+'confirmpass' => 'Потвърждение на паролата',
+'projectmanager' => 'Проект Менажер',
+'viewtasks' => 'Разглеждане на задачи',
+'modifyowntasks' => 'Редактиране на ÑобÑтвените задачи',
+'modifyalltasks' => 'Редактиране на задачи на други потребители',
+'viewcomments' => 'Разглеждане на коментари',
+'editcomments' => 'Редактиране на коментари',
+'deletecomments' => 'Изтриване на коментари',
+'viewattachments' => 'Разглеждане на прикачени файлове',
+'createattachments' => 'Създаване на прикачени файлове',
+'deleteattachments' => 'Изтриване на прикачени файлове',
+'viewhistory' => 'Разглеждане на иÑториÑта',
+'closeowntasks' => 'ЗатварÑне на ÑобÑтвени задачи',
+'closeothertasks' => 'ЗатварÑне на задачи на други потребители',
+'assigntoself' => 'Ðазначаване на задачи на Ñебе Ñи, ако вече не Ñа назначени на друг',
+'assignotherstoself' => 'Ðазначаване на чужди задачи на Ñебе Ñи',
+'viewreports' => 'Разглеждане на ÑпиÑъка ÑÑŠÑ ÑъбитиÑ',
+'othersview' => 'ПозволÑване на вÑеки да разглежда този проект',
+'usersandgroups' => 'Потребители и групи',
+'globalgroup' => 'Глобална група',
+'globalgroups' => 'Глобални групи',
+'defaultglobalgroup' => 'Подразбираща Ñе глобална група за нови потребители',
+'addtogroup' => 'ДобавÑне към групата',
+'moveuserstogroup' => 'ПремеÑтване на тези потребители към група',
+'nogroup' => 'ÐÑма група - премахване от проекта',
+'eventdesc' => 'ОпиÑание на Ñъбитието',
+'requestedby' => 'ЗаÑвено от',
+'daterequested' => 'ЗаÑвена дата',
+'closetask' => 'ЗатварÑне на задачата',
+'reopentask' => 'Повторно отварÑне на задачата',
+'applymember' => 'ЗаÑвка за членÑтво в проекта',
+'forcurrentproj' => 'За Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚',
+'lostpw' => 'ВъзÑтановÑване на забравена парола',
+'lostpwexplain' => 'Въведете вашето потребителÑко име, за да ви бъде изпратена връзка за ÑмÑна на паролата. Ð¢Ñ Ñ‰Ðµ бъде изпратена на адреÑа за извеÑÑ‚Ñване на във Ð²Ð°ÑˆÐ¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð».',
+'sendlink' => 'Изпращане',
+'savenewpass' => 'СъхранÑване на новата парола',
+'anonreg' => 'Разрешаване на региÑтрации на нови потребители',
+'allowanonopentask' => 'Разрешаване на анонимни потребители да отварÑÑ‚ задачи',
+'editglobalgroup' => 'Редактиране на глобална група',
+'editgroupforproj' => 'Редактиране на група за проект',
+'notshownforadmin' => 'РазрешениÑта за админиÑтративната група не Ñа показани. ÐÑма нужда да ги редактирате.',
+'general' => 'Общи',
+'userregistration' => 'РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð½Ð° потребител',
+'notifications' => 'ИзвеÑтиÑ',
+'resetoptions' => 'Ðачално уÑтановÑване',
+'preferences' => 'ПредпочитаниÑ',
+'tasktypes' => 'Типове задачи',
+'resolutions' => 'Резолюции',
+'categories' => 'Категории',
+'operatingsystems' => 'Операционни ÑиÑтеми',
+'versions' => 'ВерÑии',
+'admintoolboxlong' => 'ÐдминиÑтративни инÑтрументи',
+'newproject' => 'Ðов проект',
+'delete' => 'Изтриване',
+'listdeletetip' => 'Изтриване на този елемент от ÑпиÑъка',
+'lookandfeel' => 'Външен вид (оформление)',
+'globaltheme' => 'Глобална тема',
+'emailnotify' => 'ИзвеÑÑ‚Ñване по е-майл',
+'fromaddress' => 'Обратен адреÑ',
+'smtpserver' => 'SMTP Ñървър',
+'smtpuser' => 'SMTP потребителÑко име',
+'smtppass' => 'SMTP парола',
+'addrewrite' => 'Използване на пренапиÑване на URL адреÑи (rewrite)',
+'usereminderdaemon' => 'Разрешаване на фонов демон/Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° напомнÑне',
+'tasksperpage' => 'Задачи на Ñтраница в ÑпиÑъка ÑÑŠÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'addtoassignees' => 'ДобавÑне на Ñебе Ñи към ÑпиÑъка Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸, които могат да приемат задачи',
+'taskstatuses' => 'СъÑтоÑние на задачите',
+'canvote' => 'Може да глаÑува за задачите',
+'loginsuccessful' => 'УÑпешен вход.',
+'youareloggedout' => 'УÑпешен изход.',
+'waitwhiletransfer' => 'ÐœÐ¾Ð»Ñ Ð¸Ð·Ñ‡Ð°ÐºÐ°Ð¹Ñ‚Ðµ докато бъдете прехвърлен...',
+'clicknowait' => 'ÐатиÑнете тук, ако не желаете да чакате.',
+'accountdisabled' => 'ВашиÑÑ‚ акаунт е заключен. Обърнете Ñе към админиÑтратора.',
+'task' => 'Задача',
+'edittask' => 'Редактиране на тази задача',
+'openedby' => 'Отворена от',
+'editedby' => 'ПоÑледно редактирана от',
+'tasktype' => 'Тип на задачата',
+'category' => 'КатегориÑ',
+'status' => 'СъÑтоÑние',
+'assignedto' => 'Ðазначена на',
+'operatingsystem' => 'Операционна ÑиÑтема',
+'severity' => 'ТрудноÑÑ‚',
+'reportedversion' => 'ВерÑÐ¸Ñ Ð½Ð° поÑвÑване на задачата',
+'dueinversion' => 'ГотовноÑÑ‚ за верÑиÑ',
+'undecided' => 'Ðерешено',
+'percentcomplete' => 'Завършено (%)',
+'details' => 'ПодробноÑти',
+'savedetails' => 'ЗапиÑ',
+'canceledit' => 'ОтмÑна на редактирането',
+'anonymous' => 'Ðнонимен подател',
+'complete' => 'завършено',
+'closedby' => 'Затворена от',
+'reasonforclosing' => 'Причина за затварÑнето:',
+'reopenthistask' => 'Повторно отварÑне на задачата',
+'comments' => 'Коментари',
+'attachments' => 'Прикачени файлове',
+'relatedtasks' => 'Свързани задачи',
+'edit' => 'Редактиране',
+'addcomment' => 'ДобавÑне на коментар',
+'fileuploadedby' => 'Файлът е качен от',
+'uploadafile' => 'Прикачане на файл',
+'uploadnow' => 'Качи Ñега!',
+'thesearerelated' => 'Следващите задачи Ñа Ñвързани Ñ Ñ‚Ð°Ð·Ð¸ задача',
+'remove' => 'Премахване',
+'addnewrelated' => 'ДобавÑне на нова Ñвързана задача',
+'add' => 'ДобавÑне',
+'otherrelated' => 'Други задачи, към които тази задача е Ñвързана',
+'receivenotify' => 'Тези потребители ще приемат извеÑтиÑ, когато има промени в задачата.',
+'addusertolist' => 'ДобавÑне на потребител към ÑпиÑъка',
+'addtolist' => 'ДобавÑне към ÑпиÑъка',
+'addmyself' => 'ДобавÑне на Ñебе Ñи към ÑпиÑъка',
+'removemyself' => 'Премахване на Ñебе Ñи към ÑпиÑъка',
+'theseusersnotify' => 'Тези потребители ще приемат извеÑтиÑ, когато има промени в задачата.',
+'attachedtoproject' => 'Прикачена към проект',
+'reminders' => 'ÐапомнÑниÑ',
+'system' => 'СиÑтема',
+'remindthisuser' => 'Ðапомни на този потребител',
+'thisoften' => 'ЧеÑтота',
+'startafter' => 'Изчакване преди да започне напомнÑнето',
+'hours' => 'ЧаÑ(ове)',
+'days' => 'Ден(дни)',
+'weeks' => 'Седмица(и)',
+'addreminder' => 'ДобавÑне на напомнÑне',
+'defaultreminder' => 'Това Ñъобщение ви Ð½Ð°Ð¿Ð¾Ð¼Ð½Ñ Ð´Ð° обърнете внимание на Ñледващата Flyspray задача:',
+'message' => 'Съобщение',
+'closed' => 'Затворена',
+'filename' => 'Име на файл:',
+'date' => 'Дата',
+'filesize' => 'Големина на файла:',
+'closurecomment' => 'Допълнителен коментар при затварÑнето:',
+'history' => 'ИÑториÑ',
+'nohistory' => 'ÐÑма запиÑана иÑÑ‚Ð¾Ñ€Ð¸Ñ Ð½Ð° задачата.',
+'eventdate' => 'Дата',
+'user' => 'Потребител',
+'event' => 'Събитие',
+'fieldchanged' => 'Променено поле',
+'taskopened' => 'Отворена задача',
+'taskreopened' => 'Отворена отново задача',
+'taskclosed' => 'Затворена задача',
+'commentadded' => 'Добавен коментар',
+'commentedited' => 'Редактиран коментар',
+'commentdeleted' => 'Премахнат коментар',
+'attachmentadded' => 'Прикачен е файл',
+'attachmentdeleted' => 'ПрикачениÑÑ‚ файл е премахнат',
+'taskedited' => 'Данните за задачата Ñа редактирани',
+'notificationadded' => 'Добавен потребител към ÑпиÑъка за извеÑÑ‚Ñване',
+'notificationdeleted' => 'Премахнат потребител от ÑпиÑъка за извеÑÑ‚Ñване',
+'relatedadded' => 'Добавени Ñвързани задачи',
+'relateddeleted' => 'Премахнати Ñвързани задачи',
+'taskassigned' => 'Задачата е назначена на',
+'taskreassigned' => 'Задачата е преназначена на',
+'assignmentremoved' => 'Ðазначаването отменено',
+'summary' => 'Обобщение',
+'addedasrelated' => 'Задачата е добавена ÑпиÑъка ÑÑŠÑ Ñвързани задачи на',
+'deletedasrelated' => 'Задачата е премахната от ÑпиÑъка ÑÑŠÑ Ñвързани задачи на',
+'reminderadded' => 'Добавено напомнÑне',
+'reminderdeleted' => 'Премахнато напомнÑне',
+'priority' => 'Приоритет',
+'previousvalue' => 'Предишна ÑтойноÑÑ‚',
+'newvalue' => 'Ðова ÑтойноÑÑ‚',
+'selectareason' => 'Изберете причина',
+'assigntome' => 'Ðазначи на мен',
+'reopenrequest' => 'ЗаÑви повторно отварÑне',
+'requestclose' => 'ЗаÑви затварÑне',
+'ownershiptaken' => 'ПотребителÑÑ‚ взе задачата',
+'closerequestmade' => 'ЗаÑвка за затварÑне на задача',
+'reopenrequestmade' => 'ЗаÑвка за повторно отварÑне на задачата',
+'taskdependson' => 'Тази задача завиÑи от',
+'taskblocks' => 'Тази задача блокира Ñледните задачи от затварÑне',
+'depadded' => 'Добавена завиÑимоÑÑ‚',
+'depaddedother' => 'Тази задача беше добавена като завиÑимоÑÑ‚',
+'depremoved' => 'ЗавиÑмоÑтта е премахната',
+'depremovedother' => 'Тази задача е премахната от ÑпиÑъка на завиÑимоÑтите на друга задача',
+'showdetailserror' => 'Тази задача не ÑъщеÑтвува или нÑмате право да Ñ Ñ€Ð°Ð³Ð»ÐµÐ¶Ð´Ð°Ñ‚Ðµ.',
+'makeprivate' => 'Ðаправи задачата лична',
+'makepublic' => 'Ðаправи задачата публична',
+'taskmadeprivate' => 'Задачата е направена лична',
+'taskmadepublic' => 'Задачата е направена публична',
+'confirmdeletecomment' => 'ÐаиÑтина ли желаете да премахнете този коментар? %s',
+'confirmdeleteattach' => 'ÐаиÑтина ли да бъде премахнат този прикачен файл?',
+'selectedhistory' => 'Показване на избрани ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¾Ñ‚ иÑториÑта',
+'showallhistory' => 'Показване на пълната иÑториÑ',
+'hidethis' => 'Скриване на тази облаÑÑ‚ отново',
+'mark100' => 'Маркиране на тази задача 100% изпълнена',
+'watchtask' => 'Ðаблюдаване на задачата',
+'stopwatching' => 'Край на наблюдаване на задачата',
+'commentlink' => 'Връзка към този коментар',
+'submitreq' => 'Изпращане на заÑвка',
+'reasonforreq' => 'Причина за тази заÑвка',
+'pmreqdenied' => 'Проект Менажерът е забранил заÑвката',
+'taskpendingreq' => 'ЗаÑвка към Проект Менажера в очакване. Вижте иÑториÑта за подробноÑти.',
+'previoustask' => 'Предишна задача',
+'nexttask' => 'Следваща задача',
+'duedate' => 'Краен Ñрок',
+'attachnoperms' => 'Към този коментар има прикачени файлове, но вие нÑмате право да ги разглеждате.',
+'open' => 'Отворени',
+'depgraph' => 'Разглеждане на графиката на завиÑимоÑтите',
+'reset' => 'Ðачално уÑтановÑване',
+'selectusers' => 'Избери потребители...',
+'addmetoassignees' => 'Добави ме към ÑпиÑъка за назначаване на задачи',
+'addedtoassignees' => 'ПотребителÑÑ‚ е добавен към ÑпиÑъка за назначаване на задачи',
+'dependencygraph' => 'Графика на завиÑимоÑтите',
+'attachanotherfile' => 'Прикачане на нов файл',
+'OK' => 'ОК',
+'addvote' => 'ДобавÑне на вот',
+'notifyfromfs' => 'ИзвеÑтие от Flyspray',
+'autogenerated' => 'ТОВРСЪОБЩЕÐИЕ Е ГЕÐЕРИРÐÐО ÐВТОМÐТИЧÐО. ÐЕ ОТГОВÐРЯЙТЕ.',
+'forward' => 'Ðапред',
+'previous' => 'Предишна',
+'next' => 'Следваща',
+'first' => 'Първа',
+'last' => 'ПоÑледна',
+'page' => 'Страница %d от %d',
+'search' => 'ТърÑене',
+'alltasktypes' => 'Ð’Ñички типове задачи',
+'allseverities' => 'Ð’Ñички Ñтепени на важноÑÑ‚',
+'alldevelopers' => 'Ð’Ñички разработчици',
+'notyetassigned' => 'Ð’Ñе още не назначена',
+'allcategories' => 'Ð’Ñички категории',
+'allstatuses' => 'Ð’Ñички ÑÑŠÑтоÑниÑ',
+'allopentasks' => 'Ð’Ñички отворени задачи',
+'sortthiscolumn' => 'Сортиране по тази колона',
+'id' => 'ID',
+'project' => 'Проект',
+'dateopened' => 'Отворен',
+'progress' => 'ПрогреÑ',
+'searchthisproject' => 'ТърÑене в този проект за',
+'dueanyversion' => 'За изпълнение във коÑто и да е верÑиÑ',
+'anyversion' => 'Съобщено във вÑÑка верÑиÑ',
+'dueversion' => 'За изпълнение във верÑиÑ',
+'lastedit' => 'ПоÑледно редактиране',
+'os' => 'Операционна ÑиÑтема',
+'reportedin' => 'Съобщено в',
+'taskrange' => 'Показани задачи %d - %d от %d',
+'noresults' => 'ТърÑенето не върна резултати.',
+'takeaction' => 'Изпълнение на избраното дейÑтвие',
+'watchtasks' => 'Ðаблюдаване на избраните задачи',
+'stopwatchingtasks' => 'Край на наблюдаването на избраните задачи',
+'assigntaskstome' => 'Ðазначаване на избраните задачи на мен',
+'dueby' => 'За изпълнение от',
+'dueanytime' => 'За изпълнение когато и да е',
+'selectduedate' => 'Изберете краен Ñрок за изпълнение',
+'toggleselected' => 'Обръщане на избраните',
+'due' => 'Срок',
+'assignedtome' => 'Ðазначени на мене',
+'tasklist' => 'СпиÑък от задачи',
+'dateclosed' => 'Дата на затварÑне',
+'advanced' => 'Разширено',
+'searchcomments' => 'ТърÑене в коментарите',
+'searchforall' => 'Ñ‚ÑŠÑ€Ñене за вÑички думи',
+'anonusers' => 'Ðнонимни потребители',
+'miscellaneous' => 'Разнообразни',
+'users' => 'Потребители',
+'taskproperties' => 'СвойÑтва на задачите',
+'selectsincedate' => 'Изберете променени Ñлед',
+'changedsince' => 'Променени Ñлед',
+'updatefs' => 'ÐœÐ¾Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²ÐµÑ‚Ðµ Flyspray.',
+'currentversion' => 'Вашата текуща верÑÐ¸Ñ Ðµ',
+'latestversion' => 'а поÑледната доÑтъпна верÑÐ¸Ñ Ðµ',
+'hidemessage' => '(напомни ми по-къÑно)',
+'saveas' => 'ЗапиÑване на заÑвката за Ñ‚ÑŠÑ€Ñене като',
+'nosearches' => 'ÐÑма запиÑани заÑвки за Ñ‚ÑŠÑ€Ñене',
+'saving' => 'ЗапиÑ...',
+'votes' => 'ГлаÑуване',
+'allclosedtasks' => 'Ð’Ñички затворени задачи',
+'password' => 'Парола',
+'login' => 'Влизане',
+'rememberme' => 'Запомни ме',
+'lostpassword' => 'Забравена парола?',
+'lostpwforfs' => 'Забравена парола за Flyspray',
+'lostpwmsg1' => "Здравейте.\n\nÐз Ñъм загубил паролата Ñи за Flyspray на _",
+'lostpwmsg2' => ", Ð¼Ð¾Ð»Ñ Ð´Ð°Ð¹Ñ‚Ðµ ми нова парола.\n\nПотребителÑко име: _",
+'regards' => "\n\nПоздрави,",
+'yourusername' => '_ вашето потребителÑко име _',
+'locale' => 'bg-BG',
+'filenotexist' => 'Файлът не ÑъщеÑтвува или вие нÑмате доÑтъп до него.',
+'showtask' => 'Показване на задача',
+'now' => 'Сега',
+'go' => 'Давай!',
+'opentaskanon' => 'Отвори нова задача анонимно',
+'register' => 'РегиÑтриране',
+'addnewtask' => 'Ðова задача',
+'reports' => 'Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ (ÑпиÑък)',
+'editmydetails' => 'Редактиране на данните ми',
+'logout' => 'Изход',
+'disabledaccount' => 'ВашиÑÑ‚ акаунт е забранен!<br />Сега излизате от ÑиÑтемата...',
+'poweredby' => 'Тази ÑиÑтема е базирана на Flyspray',
+'projects' => 'Проекти',
+'allprojects' => 'Ð’Ñички проекти',
+'selectproject' => 'за проект:',
+'tasksall' => 'Ð’Ñички задачи',
+'tasksassigned' => 'Задачи, назначени на мен',
+'tasksreported' => 'Задачи, reported от мен',
+'taskswatched' => 'Задачи, които ÑледÑ',
+'mysearch' => 'Моите заÑвки за Ñ‚ÑŠÑ€Ñене',
+'admintoolbox' => 'ÐдминиÑтративен панел',
+'manageproject' => 'КонфигурациÑ',
+'permissions' => 'Виж разрешениÑта',
+'hide' => 'Скрий',
+'pendingreq' => 'Има чакащи заÑвки за проект менажера',
+'errorpage' => "Flyspray не може да покаже Ñтраницата, коÑто Ñте заÑвили.\n Може би Ñте поиÑкали задача, коÑто не ÑъщеÑтвува, или нÑмате\n разрешение да Ñ Ð²Ð¸Ð¶Ð´Ð°Ñ‚Ðµ.<br /><br />\n Може да Ñте опитали неправилен URL, за да работите Ñ Ð±Ð°Ð·Ð°Ñ‚Ð° данни\n използвайки SQL injection. Ðко това е така, отидете в ъгъла и помиÑлете\n какви ги вършите. Като Ñе върнете, не правете ката отново!",
+'permissionsforproject' => 'Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð·Ð° _',
+'switchto' => 'Превключи към',
+'lastsearch' => 'ПоÑледно Ñ‚ÑŠÑ€Ñене',
+'modify' => 'ПромÑна',
+'noticefrom' => 'ИзвеÑтие от',
+'hasopened' => 'е ОТВОРИЛ ÐОВРFlyspray задача и Ñ Ðµ назначил на ваÑ:',
+'moreinfonew' => 'Можете да намерите повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° тази грешка на Flyspray Ñтраница:',
+'newtaskcategory' => 'Ðова Flyspray задача беше отворена в тази категориÑ',
+'categoryowner' => 'Вие получавате това, понеже Ñте ÑобÑтвеник на категориÑта.',
+'tasksummary' => 'ПодробноÑти на задачата:',
+'newtaskadded' => 'Вашата нова задача беше добавена.',
+'summaryanddetails' => 'Вие Ñ‚Ñ€Ñбва да запишете нещо както в заглавието, така и в подробноÑтите на задачата.',
+'goback' => 'Връщане назад.',
+'messagefrom' => 'Това е Ñъобщение от Flyspray - ÑиÑтема за проÑледÑване на грешки на',
+'hasjustmodified' => 'е редактирал Ñледващата задача.',
+'changedfields' => 'Променените полета Ñа отбелÑзани ÑÑŠÑ Ð·Ð²ÐµÐ·Ð´Ð¸Ñ‡ÐºÐ¸ (**)',
+'moreinfomodify' => 'Вие можете да получите повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° тази задача на ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð°Ð´Ñ€ÐµÑ:',
+'nolongerassigned' => 'Следващата задача повече не е назнаечна на ваÑ. Ð¢Ñ Ñега е назначена на',
+'hasassigned' => 'е назначил на Ð²Ð°Ñ Ñледващата Flyspray задача:',
+'taskupdated' => 'Задачата беше променена.',
+'hasclosedassigned' => 'е затворил Ñледващата Flyspray, коÑто е била назначена на ваÑ:',
+'unassigned' => 'Ðеназначена',
+'hasclosed' => 'е затворил Ñледващата задача.',
+'youonnotify' => 'Вие получавате това Ñъобщение, понеже Ñте в ÑпиÑъка за извеÑÑ‚Ñване.',
+'taskclosedmsg' => 'Задачата е затворена.',
+'returntotask' => 'Връщане към задачата',
+'backtoindex' => 'Връщане към ÑпиÑъка ÑÑŠÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'noclosereason' => 'Ðе Ñте избрали причина за затварÑнето на задачата.',
+'hasreopened' => 'е отворил отново Ñледващата Flyspray задача, коÑто вие Ñте затворил:',
+'taskreopenedmsg' => 'Задачата е отворена отново.',
+'backtotask' => 'Обратно към задачата.',
+'commentaddedmsg' => 'Беше добавен коментар.',
+'commenttoassigned' => 'е добавил коментар към задача, назначена на ваÑ:',
+'commenttotask' => 'е добавил ÑÐ»ÐµÐ´Ð½Ð¸Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€ към тази задача.',
+'nocommententered' => 'ТрÑбва вÑе пак да въведете коментар преди на натиÑнете бутон "Изпращане".',
+'fillinfields' => 'Ðе Ñте попълнили вÑички полета.',
+'notcurrentpass' => 'Това не е вашата наÑтоÑща парола!',
+'passchanged' => 'Вашата парола беше променена.',
+'closewindow' => 'Сега можете да затворите този прозорец.',
+'passnomatch' => 'Ðовата парола и потвърждението й не Ñа еднакви!',
+'usernametaken' => 'Това потребителÑко име вече е заето. ТрÑбва да изберете нÑкое друго.',
+'newusercreated' => 'Беше Ñъздаден нов потребителÑки акаунт.',
+'accountcreated' => 'ВашиÑÑ‚ акаунт беше Ñъздаден.',
+'newuserwarning' => 'ÐœÐ¾Ð»Ñ Ð´Ð° имате предвид, че глобалната ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да изиÑква вашиÑÑ‚ акаунт да бъде одобрен от админиÑтратор. Ðко не можете да влезете в ÑиÑтемата, това може да е причината.',
+'nomatchpass' => 'паролите не Ñъвпадат.',
+'confirmwrong' => 'Кодът за потвърждение не е верен!',
+'formnotcomplete' => 'Ðе Ñа попълнени вÑички полета.',
+'formnotnumeric' => 'Въведените данни не Ñа чиÑлови!',
+'groupnametaken' => 'Това име на група вече е заето.',
+'newgroupadded' => 'Беше добавена нова група.',
+'optionssaved' => 'КонфигурациÑта на Flyspray е запиÑана.',
+'hasuploaded' => 'е прикачил файл към задача, коÑто е назначена на ваÑ:',
+'hasattached' => 'е прикачил файл към Ñледващата задача.',
+'fileuploaded' => 'Файлът е прикачен.',
+'fileerror' => 'Грешка по време на прикачване на файла. Може би разрешениÑта на Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ <i>attachments/</i> не Ñа коректни.',
+'contactadmin' => 'Обърнете Ñе към админиÑтратора на проекта.',
+'selectfileerror' => 'Ðе Ñте избрали файл.',
+'userupdated' => 'ИнформациÑта за Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ðµ обновена',
+'realandemail' => 'Ðе Ñте попълнили полетата за иÑтинÑко име и e-mail адреÑ.',
+'groupupdated' => 'КонфигурациÑта на групата беше обновена.',
+'groupanddesc' => 'Ðе Ñте задали име на група.',
+'fillallfields' => 'ÐœÐ¾Ð»Ñ Ð¿Ð¾Ð¿ÑŠÐ»Ð½ÐµÑ‚Ðµ вÑички полета.',
+'listPmustN' => 'Поле "Ред" Ñ‚Ñ€Ñбва да бъде чиÑло.',
+'listupdated' => 'СпиÑъкът беше обновен.',
+'listitemadded' => 'Беше добавен нов елемент към ÑпиÑъка.',
+'relatedaddedmsg' => 'Беше добавена Ñвързана задача към ÑпиÑъка.',
+'relatederror' => 'Тази задача вече Ñе намира в този ÑпиÑък.',
+'relatedremoved' => 'От ÑпиÑъка на Ñвързани задачи беше премахната задача.',
+'notifyadded' => 'Към ÑпиÑъка за извеÑÑ‚Ñване беше добавен потребител.',
+'notifyerror' => 'Този потребител вече Ñе намира в ÑпиÑъка за извеÑÑ‚Ñване на тази задача.',
+'notifyremoved' => 'От ÑпиÑъка за извеÑÑ‚Ñване беше премахнат потребител.',
+'editcommentsaved' => 'Коментарът беше обновен.',
+'commentdeletedmsg' => 'Коментарът беше изтрит.',
+'gotonewtask' => 'Преход към задачата, коÑто току-що Ñъздадохте',
+'projectcreated' => 'ВашиÑÑ‚ нов проект е Ñъздаден. Сега можете да го конфигурирате допълнително.',
+'customiseproject' => 'Конфигуриране на проекта',
+'projectupdated' => 'КонфигурациÑта на проекта беше обновена.',
+'emptytitle' => 'Вие Ñте оÑтавил името на проекта празно. МолÑ, върнете Ñе обратно и въведете име.',
+'loginbelow' => 'Сега може да да пробвате да влезете в ÑиÑтемата.',
+'attachmentdeletedmsg' => 'ПрикачениÑÑ‚ файл беше изтрит.',
+'reminderaddedmsg' => 'Вашето напомнÑне беше добавено.',
+'reminderdeletedmsg' => 'Избраното напомнÑне беше изтрито.',
+'flyspraytask' => 'Flyspray задача',
+'fieldsmissing' => 'ÐÑкои полета Ñа празни или Ñъдържат невалидни данни.',
+'relatedinvalid' => 'ÐÑма такава задача.',
+'relatedproject' => 'Тази задача е от друг проект. Да бъде ли добавена връзка към Ð½ÐµÑ Ð½ÐµÐ·Ð°Ð²Ð¸Ñимо от това?',
+'addanyway' => 'ДобавÑне въпреки това',
+'cancel' => 'Отказ',
+'alreadyedited' => 'Тази задача е редактирана от нÑкой друг преди да Ñ Ð·Ð°Ð¿Ð¸ÑˆÐµÑ‚Ðµ. Ð’Ñе още ли иÑкате да направите запиÑ?',
+'saveanyway' => 'ЗапиÑване въпреки това',
+'nouserselected' => 'Ðе е избран потребител. Изберете най-малкожедин потребител преди да опитате отново.',
+'groupswitchupdated' => 'ПотребителÑките групи Ñа модифицирани уÑпешно.',
+'takenownershipmsg' => 'Тази задача Ñега е назначена на ваÑ.',
+'adminrequestmade' => 'Вашата заÑвка беше изпратена на проект менажера.',
+'newdepnotify' => 'Ðова завиÑимоÑÑ‚ беше добавена към Ñледващата задача:',
+'dependadded' => 'Добавена завиÑимоÑÑ‚ между задачи',
+'dependaddfailed' => 'Грешка по време на добавÑне на завиÑимоÑÑ‚ между задачи. Проверете дали задачата ÑъщеÑтвува и че нÑма взаимна завиÑимоÑÑ‚ между Ñ‚ÑÑ….',
+'depremovedmsg' => 'Беше премахната завиÑимоÑÑ‚ между задачи',
+'newdepis' => 'Ðовата завиÑимоÑÑ‚ е',
+'magicurlsent' => 'Беше изпратено Ñъобщение на Ð²Ð°ÑˆÐ¸Ñ Ð°Ð´Ñ€ÐµÑ Ð·Ð° извеÑÑ‚Ñване. То Ñъдържа връзка, коÑто ще ви изпрати на Ñтраницата, за да попълните данните за задачата.',
+'changefspass' => 'ПромÑна на Flyspray парола',
+'magicurlmessage' => 'ÐœÐ¾Ð»Ñ Ð¿Ð¾Ñледвайте тази връзка, за да промените вашата Flyspray парола:',
+'erroronform' => 'Има нÑкакъв проблем Ñ Ð¸Ð·Ð¿Ñ€Ð°Ñщането на формулÑра',
+'addressused' => 'Този Ð°Ð´Ñ€ÐµÑ Ðµ използван за региÑтриране на Flyspray акаунт. Ðко не очаквате това Ñъобщение, Ð¼Ð¾Ð»Ñ Ð¸Ð³Ð½Ð¾Ñ€Ð¸Ñ€Ð°Ð¹Ñ‚Ðµ го. Отидете на ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð°Ð´Ñ€ÐµÑ Ð¸ направете вашата региÑтрациÑ:',
+'confirmcodeis' => 'ВашиÑÑ‚ код за потвърждение е:',
+'codesent' => 'ВашиÑÑ‚ код е изпратен. ÐœÐ¾Ð»Ñ Ñледвайте инÑтрукциите в Ñъобщението, което ще получите.',
+'codenotsent' => 'Кодът не може да бъде изпратен. Опитайте по-къÑно.',
+'taskmadeprivatemsg' => 'Тази задача беше направена лична',
+'taskmadepublicmsg' => 'Тази задача беше направена отново публично',
+'realandnotify' => 'ТрÑбва да попълните полето за дейÑтвително име и поне едно от полетата за е-майл Ð°Ð´Ñ€ÐµÑ Ð¸Ð»Ð¸ Jabber ID.',
+'pmreqdeniedmsg' => 'ЗаÑвката към Проект менажера е отхвърлена',
+'massopsuccess' => 'ОперациÑта е уÑпешна - където има подходÑщи разрешениÑ',
+'usernotexist' => 'Този потребител не ÑъщеÑтвува в тази инÑÑ‚Ð°Ð»Ð°Ñ†Ð¸Ñ Ð½Ð° Flyspray',
+'commentattachperms' => 'Ðе можете да изтриете този коментар - нÑмате разрешение да изтривате прикачени файлове',
+'voterecorded' => 'ВашиÑÑ‚ вот е запиÑан.',
+'votefailed' => 'ВашиÑÑ‚ вот не може да бъде добавен в този момент.',
+'createnewgroup' => 'Създаване на нова група',
+'requiredfields' => 'Задължителните полета Ñа маркирани Ñ',
+'addthisgroup' => 'ДобавÑне на тази група',
+'createnewproject' => 'Създаване на нов проект',
+'addnewproject' => 'ДобавÑне на нов проект',
+'htmlallowed' => 'HTML кодът е позволен',
+'createthisproject' => 'Създаване на този проект',
+'inlineimages' => 'Показване на изображениÑта в текÑта',
+'createnewtask' => 'Създаване на нова задача в проект:',
+'addanother' => 'ДобавÑне на нова задача Ñлед тази',
+'addthistask' => 'ДобавÑне на тази задача',
+'notifyme' => 'ИзвеÑÑ‚Ñвай ме при промÑна в задачата',
+'newtask' => 'Ðова задача',
+'attachafile' => 'Прикачване на файл',
+'registernewuser' => 'РегиÑтриране на нов потребител',
+'none' => 'ÐÑма',
+'registeraccount' => 'РегиÑтриране на този потребител',
+'both' => 'И двете',
+'notifyfrom' => 'ФормулÑÑ€ за извеÑÑ‚Ñване _',
+'donotreply' => 'ТОВРСЪОБЩЕÐИЕ Е ГЕÐЕРИРÐÐО ÐВТОМÐТИЧÐО, МОЛЯ ÐЕ ОТГОВÐРЯЙТЕ.',
+'disclaimer' => 'Получавате това Ñъобщение, понеже така Ñте заÑвили в Flyspray bugtracking system. Ðко не очаквате това Ñъобщение или не желате да получавате е-майли за в бъдеще, можете да промените конфигурациÑта за извеÑÑ‚Ñване на адреÑа по-горе.',
+'userwho' => 'ПотребителÑÑ‚, който е направил това',
+'moreinfo' => 'Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да бъде намерена на ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð°Ð´Ñ€ÐµÑ:',
+'newtaskopened' => 'Ðова Flyspray задача беше отворена. ПодробноÑтите Ñа по-долу.',
+'notify.taskclosed' => 'Следващата задача Ñега е затворена:',
+'notify.taskreopened' => 'Следващата задача е отворена отново:',
+'newdep' => 'Следващата задача има нова завиÑимоÑÑ‚:',
+'notify.depremoved' => 'Следващата задача има премахната завиÑимоÑÑ‚:',
+'olddepwas' => 'Старата завиÑимоÑÑ‚ беше',
+'notify.commentadded' => 'Към Ñледващата задача има добавен нов коментар:',
+'commentis' => 'Съдържанието на коментара е по-долу.',
+'newattachment' => 'Към Ñледващата задача беше прикачен нов файл:',
+'detailsbelow' => 'ПодробноÑтите Ñа по-долу.',
+'notify.relatedadded' => 'Ðова Ñвързана задача беше добавена към Ñледващата задача:',
+'relatedis' => 'Свързаната задача е',
+'assignedtoyou' => 'Следващата задача е назначена на ваÑ:',
+'takenownership' => 'взе Ñледващата задача:',
+'requiresaction' => 'Следващата задача изиÑква Ñ€ÐµÐ°ÐºÑ†Ð¸Ñ Ð¾Ñ‚ проект менажера:',
+'requiresactionnotify' => 'Задача изиÑква Ñ€ÐµÐ°ÐºÑ†Ð¸Ñ Ð¾Ñ‚ проект менажера',
+'pmdeny' => 'Проект Менажерът е забранил заÑвките за Ñледващата задача:',
+'pmdenynotify' => 'Проект Менажерът е забранил заÑвката',
+'fileaddedtoo' => 'Един или повече файлове Ñа прикачени.',
+'taskwatching' => 'Задачата, коÑто наблюдавате',
+'isdepfor' => 'е нова завиÑимоÑÑ‚ за',
+'denialreason' => 'Причина за забрана',
+'taskchanged' => 'Следващата задача е променена. Промените Ñа показани по-долу. За пълна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно промените отидете на адреÑа и натиÑнете "ИÑториÑ".',
+'useraddedtoassignees' => 'Потребител Ñе е добавил към ÑпиÑъка наназначените на тази задача.',
+'removeddepis' => 'Премахнатата завиÑимоÑÑ‚ е',
+'isnodepfor' => 'не е повече завиÑимоÑÑ‚ за',
+'usergroups' => 'ПотребителÑки групи',
+'pmtoolbox' => 'ИнÑтрументариум на Проект Менажера',
+'groupmanage' => 'Управление на групите',
+'pendingrequests' => 'Чакащи заÑвки',
+'reasongiven' => 'Причина',
+'nopendingreq' => 'ÐÑма чакащи заÑвки за Проект Менажера.',
+'givereason' => 'Укажете причина',
+'catlisted' => 'Редактор на ÑпиÑъка от категории',
+'oslisted' => 'Редактор на ÑпиÑъка от операционни ÑиÑтеми',
+'verlisted' => 'Редактор на ÑпиÑъка от верÑии',
+'tasktypeed' => 'Редактор на ÑпиÑъка от типове задачи',
+'resed' => 'Редактор на ÑпиÑъка от резолюциите',
+'deny' => 'Забрана',
+'notifiedwhen' => 'ИзвеÑÑ‚Ñване когато',
+'onlynewtasks' => 'Ðови задачи Ñа отворени',
+'allevents' => 'Събитие в задача',
+'feeds' => 'ЕмиÑии',
+'feeddescription' => 'ОпиÑание на емиÑиÑта',
+'feedimgurl' => 'Връзка към изображение за емиÑиÑта (оÑтавете празно, ако нÑма изображение)',
+'notifysubject' => 'Предмет на извеÑÑ‚Ñването',
+'notifysubjectinfo' => '(%p = име на проекта, %s = обобщение на задачата, %t = идентификатор на задачата, %a = дейÑтвие, %u = ПотребителÑко име)',
+'priority6' => 'Светкавично',
+'priority5' => 'Веднага',
+'priority4' => 'Ðеотложен',
+'priority3' => 'ВиÑок',
+'priority2' => 'Ðормален',
+'priority1' => 'ÐиÑък',
+'sendcode' => 'Изпрати кода!',
+'entercode' => 'Въведете кода за потвърждение, който Ñе намира в Ñъобщението. Също така въведете и вашата парола.',
+'confirmationcode' => 'Код за потвърждаване',
+'registererror' => 'Уверете Ñе, че Ñте попълнили вÑички задължителни полета, и че Ñте въвели коректни данни за Ð¸Ð·Ð±Ñ€Ð°Ð½Ð¸Ñ Ñ‚Ð¸Ð¿ на извеÑÑ‚Ñване.',
+'validusername' => '(позволени Ñа Ñамо букви, цифри и _)',
+'emailtaken' => 'Този е-майл Ð°Ð´Ñ€ÐµÑ Ð¸Ð»Ð¸ Jabber-ID вече е зает. ТрÑбва да изберете нÑкой друг.',
+'note' => '<strong>Забележка:</strong> Ще ви бъде изпратен код за потвърждение преди вашиÑÑ‚ акаунт да бъде Ñъздаден. Този код ще бъде изпратен чрез Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ñ‡ÐµÑ‚ÐµÐ½Ð¸Ñ Ð¼ÐµÑ‚Ð¾Ð´ на извеÑÑ‚Ñване по-горе.<br />Ðко Ñте въвели фалшиви данни, вие <strong>нÑма да получите Ð²Ð°ÑˆÐ¸Ñ ÐºÐ¾Ð´</strong>.',
+'changelog' => 'Ð¥Ñ€Ð¾Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ Ð½Ð° промените',
+'changeloggen' => 'Генератор на Ñ…Ñ€Ð¾Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ Ð½Ð° промените',
+'listfrom' => 'СпиÑък на промените от',
+'to' => 'до',
+'oldestfirst' => 'Ðай-Ñтарата първо',
+'recentfirst' => 'Ðай-новата първо',
+'severityrep' => 'Отчет за Ñтепента на трудноÑÑ‚',
+'totalopen' => 'Общо отворени задачи',
+'age' => 'ВъзраÑÑ‚',
+'agerep' => 'Отчет на възраÑтта на задачите',
+'eventsrep' => 'Отчет на ÑъбитиÑта',
+'events' => 'СъбитиÑ',
+'Tasks' => 'Задачи',
+'opened' => 'Отворени',
+'edited' => 'Редактирани',
+'assigned' => 'Ðазначени',
+'within' => 'Ð’',
+'pastday' => 'ПоÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð´ÐµÐ½',
+'pastweek' => 'ПоÑледната Ñедмица',
+'pastmonth' => 'ПоÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец',
+'pastyear' => 'ПоÑледната година',
+'nolimit' => 'Ðеограничено',
+'from' => 'От',
+'duein' => 'За изпълнение в',
+'selectfromdate' => 'Изберете От дата',
+'selecttodate' => 'Изберете До дата',
+'showvoters' => 'Скриване/Показване на глаÑуващите',
+'roadmap' => 'Пътна карта',
+'roadmapfor' => 'Пътна карта за верÑиÑ',
+'tasks' => 'задачи',
+'completed' => 'изпълнени.',
+'opentasks' => 'отворени задачи',
+'of' => '% от',
+'severity5' => 'Критична',
+'severity4' => 'ВиÑока',
+'severity3' => 'Средна',
+'severity2' => 'ÐиÑка',
+'severity1' => 'Много ниÑка',
+'Redirect' => 'ПренаÑочване',
+'redirectmsg' => 'Ðко вашиат браузър не поддържа пренаÑочване Ð¼Ð¾Ð»Ñ Ð½Ð°Ñ‚Ð¸Ñнете %sHERE%s, за да бъдете пренаÑочени',
+'allowclosedcomments' => 'Разрешаване на коментари на затворени задачи',
+'comment' => 'Коментар',
+'editowncomments' => 'Редактиране на ÑобÑтвените коментари',
+'reopened' => 'Повторно отворени',
+'loading' => 'Зареждане...',
+'notifyown' => 'ИзвеÑÑ‚Ñване за ÑобÑтвени промени',
+'youremail' => 'Вашиат е-майл адреÑ',
+'thankyouforbug' => 'Ð‘Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð·Ð° Ñъобщаването на този проблем. Вие можете да видите задачата и наблюдавате развитието й по вÑÑко време на този адреÑ:',
+'anonuser' => 'Ðнонимен потребител',
+'conflict' => 'Конфликт',
+'file' => 'Файл',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => 'Големина',
+'projectgroup' => 'Проектна група',
+'profile' => 'Профил:',
+'viewprofile' => 'Разглеждане на профила',
+'regdate' => 'РегиÑтриран от',
+'tasksopened' => 'Отворени задачи',
+'replyto' => 'Отговори на',
+'notifytypes' => 'Типове извеÑÑ‚Ñване',
+'pm.taskchanged' => 'Променени задачи',
+'pm.taskreopened' => 'Отворени отново задачи',
+'pm.depadded' => 'Добавени завиÑимоÑти',
+'pm.depremoved' => 'Премахнати завиÑимоÑти',
+'pmrequest' => 'ЗаÑвка към проект менажера',
+'pmrequestdenied' => 'Отхвърлена заÑвка проект менажера',
+'newassignee' => 'Ðов изпълнител',
+'revdepadded' => 'Добавена обратна завиÑимоÑÑ‚',
+'revdepaddedremoved' => 'Премахната обратна завиÑимоÑÑ‚',
+'assigneeadded' => 'Добавен изпълнител на задача',
+'addusergroup' => 'ДобавÑне на потребител към тази група',
+'groupmembers' => 'Членове на групата',
+'deleteuser' => 'Изтриване на този потребител',
+'userdeleted' => 'ПотребителÑÑ‚ е изтрит',
+'autoassign' => 'Ðвтоматично назначаване на задачата на ÑобÑтвеника на категориÑта',
+'ssl' => 'SSL',
+'updatewrong' => "Проверката за обновÑване е позволена, но е възникнала грешка по време опита за контакт ÑÑŠÑ Ñървъра. Възможно е вашиÑÑ‚ Ñървър да не позволÑва външни връзки или да има нÑкакъв мрежов проблем.\nÐœÐ¾Ð»Ñ Ð¿Ð¾Ñетете Ñайта на flyspray, за да проверите дали имате поÑледната верÑиÑ.",
+'deleteproject' => 'Изтриване на този проект и премеÑтване на Ñъдържанието му в',
+'projectdeleted' => 'Проектът е изтрит уÑпешно',
+'feedforall' => 'ЕмиÑÐ¸Ñ Ð·Ð° вÑички проекти',
+'usercreated' => 'ПотребителÑÑ‚ е Ñъздаден',
+'created' => 'Създаден потребител',
+'deleted' => 'Изтрит',
+'userid' => 'ПотребителÑки ID',
+'editassignments' => 'Редактиране на назначениÑта',
+'preview' => 'Преглед',
+'anyprogress' => 'Ð’ÑÑкакъв прогреÑ',
+'tasksrelated' => 'Задачи, Ñвързани Ñ Ñ‚Ð°Ð·Ð¸ задача',
+'duplicatetasks' => 'Дублиране на задачите на тази задача',
+'databasemodfailed' => 'ÐеуÑпешна Ð¼Ð¾Ð´Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ð° базата данни. Възможно е да имате Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° доÑтъпа.',
+'frequency' => 'ЧеÑтота',
+'newuserregistered' => 'Ðов потребител е региÑтриран във вашата Flyspray ÑиÑтема. Данните за него Ñа както Ñледва:',
+'newuserregisterednotify' => 'Ðов потребител е региÑтриран',
+'notify_registration' => 'ИзвеÑÑ‚Ñване на админиÑтраторите при региÑтриране на нов потребител',
+'textversion' => 'ТекÑтова верÑиÑ',
+'onlyprimary' => 'Задачи, неблокиращи други задачи',
+'switch' => 'Превключване',
+'max' => 'макÑ.',
+'dates' => 'Дати',
+'selectduedatefrom' => 'За изпълнение от',
+'selectduedateto' => 'до',
+'selectsincedatefrom' => 'Променена от',
+'selectsincedateto' => 'до',
+'selectdate' => 'Изберете дата',
+'selectopenedfrom' => 'Отворена от',
+'selectopenedto' => 'до',
+'selectclosedfrom' => 'Затворена от',
+'selectclosedto' => 'до',
+'startat' => 'Стартиране на',
+'hasattachment' => 'Има прикачен файл',
+'private' => 'ЧаÑтна',
+'watching' => 'Ðаблюдаване',
+'alreadyvotedthistask' => 'вие глаÑувахте за тази задача',
+'alreadyvotedthisday' => 'вече глаÑувахте днеÑ',
+'visibility' => 'ВидимоÑÑ‚',
+'public' => 'Публична',
+'leaveemptyauto' => 'ОÑтавете полетата за паролата празни, ако желаете Ñ‚Ñ Ð´Ð° бъде генерирана автоматично.',
+'novalidemail' => 'Ðе Ñте въвели валиден е-майл адреÑ.',
+'novalidjabber' => 'Ðе Ñте въвели валиден Jabber адреÑ.',
+'missingrequired' => 'Ðе Ñте попълнили вÑички задължителни полета.',
+'entervalidusername' => 'ÐœÐ¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ валидно потребителÑко и реално име.',
+'couldnotaddusernotif' => 'ПотребителÑÑ‚ не беше добавен към ÑпиÑъка за извеÑÑ‚Ñване по нÑкаква причина.',
+'defaulttask' => 'Подразбиращо Ñе опиÑание на задачата',
+'all' => 'вÑички',
+'events.useraddedtoassignees'=> 'Добавен е потребител към ÑпиÑъка Ñ Ð¸Ð·Ð¿ÑŠÐ»Ð½Ð¸Ñ‚ÐµÐ»Ð¸',
+'vote(s)' => 'глаÑ(ове)',
+'eventlog' => 'Ð¥Ñ€Ð¾Ð½Ð¾Ð»Ð¾Ð³Ð¸Ñ Ð½Ð° ÑъбитиÑта',
+'assignmentchanged' => 'Assignment changed',
+'detailedinfo' => 'Подробна информациÑ',
+'All' => 'Ð’Ñички',
+'tasksireported' => 'Задачи, за които аз Ñъм Ñъобщил',
+'recentlyopened' => 'ПоÑледно отворени',
+'stats' => 'СтатиÑтика',
+'totaltasks' => 'общо задачи',
+'mostwanted' => 'Ðай-Ñ‚ÑŠÑ€Ñени задачи',
+'defaultentry' => 'Ðачална Ñтраница по подразбиране',
+'toplevel' => 'Изглед на най-горно ниво',
+'overview' => 'Обобщение',
+'error#' => 'Грешка #',
+'error1' => 'ÐÑмате доÑтатъчно права да видите този прикачен файл.',
+'error3' => 'Повторено дейÑтвие, препращане към главната Ñтраница.',
+'error4' => 'Вие нÑмате админиÑтративни права.',
+'error5' => 'Този потребител не ÑъщеÑтвува в базата данни.',
+'error6' => 'Ðевалидна админиÑтративна облаÑÑ‚.',
+'error7' => 'ÐеуÑпешен вход (неправилно потребителÑко име или парола)!',
+'error71' => 'ПотребителÑкото име е блокирано за %d минути вÑледÑтвие твърде много неуÑпешни опити за влизане в ÑиÑтемата!',
+'error8' => 'Ðе Ñте въвели потребителÑкото име и/или паролата.',
+'error9' => 'Задачата не ÑъщеÑтвува или нÑмате права да видите тази задача.',
+'error10' => 'Задачата не ÑъщеÑтвува или нÑмате права да видите тази задача.',
+'error101' => 'ÐÑмате разрешение да видите тази задача.',
+'error102' => 'ÐÑмате разрешение да видите тази задача, влезте в ÑиÑтемата Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñкото Ñи име и парола и пробвайте отново.',
+'error11' => 'ÐÑмате право да редактирате този коментар.',
+'error12' => 'Ðевалиден ключ! Сигурен ли Ñте, че Ñте го взели от изпратеното ви извеÑтие?',
+'error13' => 'Ðнонимните потребители нÑмат профил.',
+'error14' => 'ÐÑмате доÑтатъчно права да Ñъздавате нова група.',
+'error15' => 'ÐÑмате доÑтатъчно права да отварÑте задача.',
+'error16' => 'Вие не Ñте проект менажер.',
+'error17' => 'Ðевалидна облаÑÑ‚ за проект менажер.',
+'error18' => 'Ðевалиден URL адреÑ.',
+'error19' => 'Този потребител не ÑъщеÑтвува в тази база данни.',
+'error20' => 'Ðевалидна Ð¼Ð¾Ð´Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ð° базата данни.',
+'error21' => 'Един или повече е-майли не Ñа изпратени. Проверете конфигурациÑта.',
+'error22' => 'Ðе е позволена региÑтрациÑта на нови потребители.',
+'error23' => 'ПотребителÑÑ‚ или групата нÑмат разрешение за влизане в ÑиÑтемата.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Пътна карта може да бъде показана Ñамо за Ñпецифичен проект.',
+'done' => 'готово',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Проектът не може да бъде изтрит.',
+'GMT' => 'GMT',
+'timezone' => 'Времева зона',
+'accept' => 'Приемане',
+'reasonfordeinal' => 'Причина за забраната',
+'pruneclosedlinks' => 'Премахване на затворените връзки',
+'pruneclosedtasks' => 'Премахване на затворените задачи',
+'pagegenerated' => 'Страницата и изображението Ñа генерирани за %d Ñекунди.',
+'pruninglevel' => 'Ðиво на очиÑтване',
+'lastuser' => 'ПоÑледниÑÑ‚ потребител не може да бъде изтрит.',
+'allprivate' => 'Ð’Ñички проекти Ñа чаÑтни.',
+'deletegroup' => 'Изтриване на тази група и премеÑтване на потребителите в',
+'parent' => 'Родител',
+'ordertip' => 'Ред, в който тези елементи да Ñе поÑвÑÑ‚ в ÑпиÑъка',
+'showtip' => 'Покажи този елемент в ÑпиÑъка',
+'deletetip' => 'Изтрий този елемент от ÑпиÑъка',
+'del' => 'del',
+'request1' => 'Има заÑвка за затварÑне на задача.',
+'request2' => 'Има заÑвка за повторно отварÑне на задача.',
+'allpriorities' => 'Ð’Ñички приоритети',
+'noroadmap' => 'ÐÑма работна карта (не ÑъщеÑтвуват бъдещи верÑии за проекта).',
+'expand' => 'ОтварÑне',
+'collapse' => 'ЗатварÑне',
+'expandall' => 'Пълно отварÑне',
+'collapseall' => 'Пълно затварÑне',
+'minpwsize' => 'Минималната дължина на паролата е 5 Ñимвола',
+'passwordtoosmall' => 'Дължината на паролата е твърде малка.',
+'accountwaslocked' => 'ВашиÑÑ‚ акаунт е блокиран поради твърде много неуÑпешни опити за влизане.',
+'failedattempts' => '%d неуÑпешни опита за влизане.',
+'groupnotexist' => 'Избраната група не ÑъщеÑтвува в този проект.',
+'searchindetails' => 'Разширено Ñ‚ÑŠÑ€Ñене',
+'showasassignees' => 'Показване като потребител, който може да приема задачи',
+'find' => 'ТърÑене',
+'tls' => 'TLS',
+);
+
+?>
diff --git a/lang/de.php b/lang/de.php
new file mode 100644
index 0000000..83a0557
--- /dev/null
+++ b/lang/de.php
@@ -0,0 +1,1101 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Benutzer bearbeiten',
+'accountenabled' => 'Konto aktiviert',
+'editallusers' => 'alle Nutzer anzeigen',
+'username' => 'Benutzername',
+'usersupdated' => 'Nutzer erfolgreich aktualisiert',
+'realname' => 'vollständiger Name',
+'emailaddress' => 'E-Mail',
+'jabberid' => 'Jabber-ID',
+'profileimage' => 'Profilbild',
+'notifytype' => 'Benachrichtigungsweg',
+'group' => 'Gruppe',
+'enableaccounts' => 'Zugänge aktivieren',
+'disableaccounts' => 'Zugänge sperren',
+'deleteaccounts' => 'Zugänge löschen',
+'updatedetails' => 'speichern',
+'setglobally' => 'Diese Einstellung ist global gesetzt.',
+'usergroupmanage' => 'Benutzer- und Gruppenmanagement',
+'newuser' => 'Neuen Benutzer registrieren',
+'newuserbulk' => 'mehrere neue Benutzer anlegen',
+'bulkuserstoadd' => 'Liste neuer Nutzer',
+'optionsforallusers' => 'Einstellungen für alle neuen Nutzer',
+'newgroup' => 'Neue Gruppe erstellen',
+'yes' => 'ja',
+'no' => 'nein',
+'editgroup' => 'Gruppe bearbeiten',
+'groupname' => 'Gruppenname',
+'description' => 'Beschreibung',
+'admin' => 'Administratorengruppe',
+'opennewtasks' => 'neue Aufgaben anlegen',
+'modifytasks' => 'vorhandene Aufgaben ändern',
+'addcomments' => 'Kommentare hinzufügen',
+'attachfiles' => 'Dateien anhängen',
+'vote' => 'Stimme',
+'groupenabled' => 'Mitglieder können sich anmelden (Gruppe aktiv)',
+'groupopen' => 'Mitglieder können sich anmelden (Gruppe aktiv)',
+'tasktypelist' => 'Aufgabentypen',
+'categorylist' => 'Kategorien',
+'oslist' => 'Betriebssysteme',
+'resolutionlist' => 'Erledigungsstatus',
+'versionlist' => 'Versionen',
+'severitylist' => 'Schweregrad',
+'listnote' => 'Hinweis: Wenn Sie "Anzeigen" deaktivieren, können einige Aufgaben beim Bearbeiten anders dargestellt werden. Änderungen des Namens wirken sich automatisch auf alle zugehörigen Aufgaben aus. Einträge, die Aufgaben zugeordnet sind oder die zum Funktionieren der Anwendung erforderlich sind, können nicht gelöscht werden.',
+'name' => 'Name',
+'order' => 'Reihenfolge',
+'back' => 'zurück',
+'text' => 'Text',
+'highlight' => 'hervorheben',
+'show' => 'anzeigen',
+'owner' => 'Zuständiger',
+'selectowner' => 'Zuständigen auswählen',
+'update' => 'aktualisieren',
+'addnew' => 'hinzufügen',
+'flysprayprefs' => 'Flyspray Optionen',
+'projecttitle' => 'Projektname',
+'baseurl' => 'Basis-URL für diese Installation',
+'replyaddress' => 'Antwortadresse für E-Mail-Benachrichtigungen',
+'themestyle' => 'Design',
+'customstyle' => 'angepasst',
+'language' => 'Sprache',
+'anonview' => 'Anonyme Benutzer dürfen Aufgaben sehen',
+'allowanon' => 'Anonyme Benutzer dürfen neue Aufgaben anlegen',
+'never' => 'niemals',
+'anonymously' => 'anonym',
+'afterregister' => 'nach Registrierung',
+'spamproof' => 'Bestätigungscode für das Registrieren neuer Benutzer aktivieren',
+'anongroup' => 'Gruppe für anonyme Registrierungen',
+'groupassigned' => 'Mitglieder dieser Gruppen können Aufgaben übertragen werden',
+'forcenotify' => 'Benachrichtigungsweg erzwingen',
+'neversend' => 'niemals senden',
+'userchoose' => 'Benutzerauswahl',
+'email' => 'E-Mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Standard-Kategorie-Inhaber',
+'noone' => 'niemand',
+'jabbernotify' => 'Jabber-Benachrichtigungen',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Benutzername',
+'jabberpass' => 'Passwort',
+'saveoptions' => 'speichern',
+'editcomment' => 'Kommentar bearbeiten',
+'commentby' => 'Kommentar von',
+'saveeditedcomment' => 'Bearbeiteten Kommentar speichern',
+'projectprefs' => 'Projekteinstellungen',
+'pagetitle' => 'Seitentitel',
+'defaultproject' => 'Standard-Projekt',
+'projectlists' => 'Projekt-Listen',
+'showlogo' => 'Logografik im Titel anzeigen',
+'showgravatars' => 'Gravatare anzeigen',
+'emailNoHTML' => 'kein HTML in E-Mails',
+'intromessage' => 'Begrüßungstext',
+'active' => 'aktiv',
+'inactive' => 'inaktiv',
+'isactive' => 'Projekt ist aktiv',
+'showinactive' => 'Zeige inaktive Projekte',
+'hideinactive' => 'inaktive Projekte ausblenden',
+'createproject' => 'Neues Projekt erstellen',
+'nopermission' => 'Sie sind nicht zum Zugriff auf diese Seite berechtigt.',
+'listordertip' => 'Reihenfolge der Einträge in der Liste',
+'listshowtip' => 'Diesen Eintrag in der Liste anzeigen',
+'categoryownertip' => 'Dieser Benutzer erhält eine Benachrichtigung, wenn eine Aufgabe in dieser Kategorie angelegt wird: ',
+'categoryparenttip' => 'Kategorie, die durch die neue Kategorie untergliedert wird',
+'notsubcategory' => 'keine - (oberste Ebene)',
+'showinlineimages' => 'Angehängte Bilder als Vorschau direkt anzeigen',
+'dateformat' => 'Datumsformat',
+'dateformat_extended' => 'erweitertes Datumsformat',
+'cache_feeds' => 'Cache für Newsfeeds aktivieren',
+'no_cache' => 'ohne Cache',
+'cache_disk' => 'Cache als Datei',
+'cache_db' => 'Cache in Datenbank',
+'subcategoryof' => 'Unterkategorie von',
+'visiblecolumns' => 'Spalten in der Aufgabenliste',
+'visiblefields' => 'sichtbare Felder, wenn Aufgaben hinzugefügt,bearbeitet oder angeschaut werden',
+'tense' => 'zeitliche Einordnung',
+'listtensetip' => 'abgeschlossen, aktuell oder zukünftig',
+'past' => 'abgeschlossen',
+'present' => 'aktuell',
+'future' => 'zukünftig',
+'oldpass' => 'altes Passwort',
+'nooldpass' => 'kein altes Passwort gesetzt',
+'oldpasswrong' => 'Altes Passwort war falsch',
+'changepass' => 'neues Passwort',
+'confirmpass' => 'neues Passwort bestätigen',
+'projectmanager' => 'Projektmanager',
+'viewtasks' => 'Aufgaben anzeigen',
+'modifyowntasks' => 'eigene Aufgaben bearbeiten',
+'modifyalltasks' => 'jemand anderem übertragene Aufgaben bearbeiten',
+'viewcomments' => 'Kommentare anzeigen',
+'editcomments' => 'Kommentare bearbeiten',
+'deletecomments' => 'Kommentare löschen',
+'viewattachments' => 'Anhänge anzeigen',
+'createattachments' => 'Anhänge erstellen',
+'deleteattachments' => 'Anhänge löschen',
+'viewhistory' => 'Verlauf anzeigen',
+'closeowntasks' => 'eigene Aufgaben schließen',
+'closeothertasks' => 'jemand anderem übertragene Aufgaben schließen',
+'assigntoself' => 'niemandem übertragene Aufgaben selbst übernehmen',
+'assignotherstoself' => 'jemand anderem übertragene Aufgaben selbst übernehmen',
+'viewreports' => 'Berichte anzeigen',
+'othersview' => 'Jeder darf die Aufgaben dieses Projektes anzeigen',
+'othersviewroadmap' => 'Jeder darf die Planung dieses Projektes anzeigen',
+'usersandgroups' => 'Benutzer & Gruppen',
+'globalgroup' => 'Globale Gruppe',
+'globalgroups' => 'Globale Gruppen',
+'defaultglobalgroup' => 'Standardgruppe für neue Benutzer',
+'addtogroup' => 'Zur Gruppe hinzufügen',
+'moveuserstogroup' => 'In Gruppe verschieben',
+'nogroup' => 'keine Gruppe (aus Projekt entfernen)',
+'eventdesc' => 'Ereignisbeschreibung',
+'requestedby' => 'angefragt von',
+'daterequested' => 'Datum der Anfrage',
+'closetask' => 'Aufgabe schließen',
+'reopentask' => 'Aufgabe wieder öffnen',
+'applymember' => 'Bitte um Projektmitgliedschaft',
+'forcurrentproj' => 'Für das aktuelle Projekt',
+'lostpw' => 'neues Passwort anfordern',
+'lostpwexplain' => 'Geben Sie Ihren Benutzernamen ein, um einen Link zum Ändern Ihres Passwortes an die in Ihrem Profil eingestellte Benachrichtigungsadresse zu senden.',
+'sendlink' => 'Link senden',
+'savenewpass' => 'neues Passwort speichern',
+'anonreg' => 'neue Benutzerregistrierungen zulassen',
+'allowanonopentask' => 'Jeder darf neue Aufgaben zu diesem Projekt hinzufügen',
+'editglobalgroup' => 'Globale Gruppe bearbeiten',
+'editgroupforproj' => 'Gruppe für dieses Projekt bearbeiten',
+'notshownforadmin' => 'Berechtigungen der Administratorengruppe können nicht geändert werden.',
+'general' => 'allgemein',
+'userregistration' => 'Benutzerregistrierung',
+'notifications' => 'Benachrichtigungen',
+'resetoptions' => 'rückgängig',
+'preferences' => 'Einstellungen',
+'tasktypes' => 'Aufgabentypen',
+'resolutions' => 'Lösungen',
+'categories' => 'Kategorien',
+'categoriesglobal' => 'Kategorien systemweit',
+'categoriesproject' => 'Kategorien projekteigen',
+'categoriestarget' => 'Kategorien Zielprojekt',
+'operatingsystems' => 'Betriebssysteme',
+'versions' => 'Versionen',
+'admintoolboxlong' => 'Verwaltungswerkzeuge',
+'newproject' => 'neues Projekt',
+'delete' => 'löschen',
+'link' => 'Verknüpfung',
+'referencelinks' => 'Referenzverbindung',
+'listdeletetip' => 'Dieses Element von der Liste löschen',
+'lookandfeel' => 'Erscheinungsbild',
+'globaltheme' => 'globales Design',
+'emailnotify' => 'E-Mail-Benachrichtigungen',
+'fromaddress' => 'Absender-Adresse',
+'smtpserver' => 'SMTP-Server',
+'smtpuser' => 'SMTP-Benutzername',
+'smtppass' => 'SMTP-Passwort',
+'addrewrite' => 'E-Mail-Adresse umschreiben (address rewriting)',
+'usereminderdaemon' => 'Erinnerungsnachrichten aktivieren (benötigt konfigurierten cronjob, der periodisch schedule.php aufruft)',
+'tasksperpage' => 'Aufgaben pro Seite',
+'addtoassignees' => 'Selbst mit übernehmen',
+'taskstatuses' => 'Aufgabenstatus',
+'canvote' => 'Stimme abgeben',
+'loginsuccessful' => 'Login erfolgreich.',
+'youareloggedout' => 'Sie wurden abgemeldet.',
+'waitwhiletransfer' => 'Sie werden weitergeleitet...',
+'clicknowait' => 'weiter',
+'accountdisabled' => 'Ihr Benutzerkonto wurde deaktiviert. Wenden Sie sich an einen Administrator.',
+'task' => 'Aufgabe',
+'edittask' => 'Aufgabe bearbeiten',
+'openedby' => 'angelegt von',
+'editedby' => 'zuletzt bearbeitet von',
+'tasktype' => 'Aufgabentyp',
+'category' => 'Kategorie',
+'status' => 'Status',
+'assignedto' => 'zuständig',
+'operatingsystem' => 'Betriebssystem',
+'severity' => 'Schweregrad',
+'reportedversion' => 'betrifft Version',
+'dueinversion' => 'fällig in Version',
+'defaultdueinversion' => 'Voreinstellung fällig in Version',
+'undecided' => 'unbestimmt',
+'percentcomplete' => 'Prozent erledigt',
+'details' => 'Beschreibung',
+'savedetails' => 'speichern',
+'canceledit' => 'abbrechen',
+'anonymous' => 'Anonymer Reporter',
+'complete' => 'erledigt',
+'closedby' => 'geschlossen von',
+'reasonforclosing' => 'Grund für das Schließen:',
+'reopenthistask' => 'Aufgabe wieder öffnen',
+'comments' => 'Kommentare',
+'attachments' => 'Dateianhänge',
+'relatedtasks' => 'Verknüpfte Aufgaben',
+'edit' => 'bearbeiten',
+'addcomment' => 'Kommentar hinzufügen',
+'fileuploadedby' => 'Datei hochgeladen von',
+'uploadafile' => 'Datei zum Anhängen hochladen',
+'addalink' => 'Verknüpfung hinzufügen',
+'addanotherlink' => 'weitere Verknüpfung hinzufügen',
+'uploadnow' => 'hochladen',
+'thesearerelated' => 'Folgende Aufgaben sind mit dieser verknüpft: ',
+'remove' => 'entfernen',
+'addnewrelated' => 'mit einer Aufgabe verbinden',
+'add' => 'hinzufügen',
+'otherrelated' => 'Aufgaben, mit denen diese verknüpft ist',
+'receivenotify' => 'Diese Benutzer werden detaillierte Benachrichtigungen erhalten, wenn sich diese Aufgabe ändert.',
+'addusertolist' => 'Benutzer zu dieser Liste hinzufügen',
+'addtolist' => 'zu Liste hinzufügen',
+'addmyself' => 'mich selbst zu dieser Liste hinzufügen',
+'removemyself' => 'mich selbst von dieser Liste entfernen',
+'theseusersnotify' => 'Folgende Benutzer erhalten detaillierte Benachrichtigungen, sobald sich die Aufgabe ändert: ',
+'attachedtoproject' => 'gehört zu Projekt',
+'reminders' => 'Erinnerungen',
+'system' => 'System',
+'systemvalues' => 'Systemweite Listenwerte',
+'projectvalues' => 'Projektspezifische Listenwerte',
+'remindthisuser' => 'diesen Benutzer',
+'thisoften' => 'alle',
+'startafter' => 'Zeit bis zur ersten Erinnerung',
+'hour' => 'Stunde',
+'hours' => 'Stunden',
+'day' => 'Tag',
+'days' => 'Tage',
+'week' => 'Woche',
+'weeks' => 'Wochen',
+'addreminder' => 'Erinnerung hinzufügen',
+'defaultreminder' => 'Erinnerung: Bitte sehen Sie sich die folgende Flyspray-Aufgabe an:',
+'message' => 'Nachricht',
+'closed' => 'geschlossen',
+'filename' => 'Dateiname:',
+'date' => 'Zeitraum',
+'filesize' => 'Dateigröße:',
+'closurecomment' => 'Kommentar zum Schließen:',
+'history' => 'Verlauf',
+'nohistory' => 'Keine Daten verfügbar.',
+'eventdate' => 'Datum',
+'user' => 'Benutzer',
+'event' => 'Ereignis',
+'fieldchanged' => 'Feld geändert',
+'taskopened' => 'Aufgabe angelegt',
+'taskreopened' => 'Aufgabe wieder geöffnet',
+'taskclosed' => 'Aufgabe geschlossen',
+'commentadded' => 'Kommentar hinzugefügt',
+'commentedited' => 'Kommentar geändert',
+'commentdeleted' => 'Kommentar gelöscht',
+'attachmentadded' => 'Anhang hinzugefügt',
+'attachmentdeleted' => 'Anhang gelöscht',
+'taskedited' => 'Aufgabendetails geändert',
+'notificationadded' => 'Benutzer zur Benachrichtigungsliste hinzugefügt',
+'notificationdeleted' => 'Benutzer von der Benachrichtigungsliste entfernt',
+'relatedadded' => 'Verknüpfung zur Aufgabe hinzugefügt',
+'relateddeleted' => 'Verknüpfung zur Aufgabe entfernt',
+'taskassigned' => 'Aufgabe übertragen an: ',
+'taskreassigned' => 'Aufgabe neu übertragen an: ',
+'assignmentremoved' => 'Zuständigkeit entfernt: ',
+'summary' => 'Zusammenfassung',
+'addedasrelated' => 'Aufgabe zur Liste verknüpfter Aufgaben hinzugefügt von',
+'deletedasrelated' => 'Aufgabe aus der Liste verknüpfter Aufgaben entfernt von',
+'reminderadded' => 'Erinnerung erstellt',
+'reminderdeleted' => 'Erinnerung gelöscht',
+'priority' => 'Dringlichkeit',
+'previousvalue' => 'vorheriger Wert',
+'newvalue' => 'neuer Wert',
+'selectareason' => 'Begründung wählen...',
+'assigntome' => 'Aufgabe allein übernehmen',
+'reopenrequest' => 'Wiederöffnung angefragt',
+'requestclose' => 'Schließen angefragt',
+'ownershiptaken' => 'Benutzer hat die Aufgabe übernommen',
+'closerequestmade' => 'Schließen einer Aufgabe angefragt',
+'reopenrequestmade' => 'Wiederöffnung einer Aufgabe angefragt',
+'taskdependson' => 'Diese Aufgabe ist abhängig von',
+'taskdependsontask' => 'Diese Aufgabe ist abhängig von',
+'taskdependsontasks' => 'Diese Aufgabe ist abhängig von',
+'taskblock' => 'Diese Aufgabe verhindert das Schließen von',
+'taskblocks' => 'Diese Aufgabe verhindert das Schließen von',
+'depadded' => 'Abhängigkeit hinzugefügt',
+'depaddedother' => 'abhängige Aufgabe hinzugefügt',
+'depremoved' => 'Abhängigkeit entfernt',
+'depremovedother' => 'abhängige Aufgabe entfernt',
+'showdetailserror' => 'Die Aufgabe ist nicht vorhanden oder Sie sind nicht zur Anzeige berechtigt.',
+'makeprivate' => 'verstecken',
+'makepublic' => 'veröffentlichen',
+'taskmadeprivate' => 'Diese Aufgabe wurde versteckt',
+'taskmadepublic' => 'Diese Aufgabe wurde wieder veröffentlicht',
+'confirmdeletecomment' => 'Diesen Kommentar wirklich löschen? %s',
+'attachementswilldeleted' => 'Alle Anhänge werden ebenfalls gelöscht!',
+'confirmdeleteattach' => 'Diesen Anhang wirklich löschen?',
+'selectedhistory' => 'Verlauf für markierten Eintrag anzeigen',
+'showallhistory' => 'Kompletten "Verlauf"-Tab wieder anzeigen',
+'hidethis' => 'Diesen Bereich wieder ausblenden',
+'mark100' => 'Aufgabe als 100% erledigt markieren',
+'watchtask' => 'Aufgabe beobachten',
+'stopwatching' => 'Aufgabe nicht mehr beobachten',
+'commentlink' => 'Diesen Kommentar verlinken',
+'submitreq' => 'Anfrage abschicken',
+'reasonforreq' => 'Grund für die Anfrage',
+'pmreqdenied' => 'Projektmanager hat die Anfrage abgelehnt',
+'taskpendingreq' => 'Warte auf Bearbeitung durch den Projektmanager. Einzelheiten siehe "Verlauf"-Tab.',
+'previoustask' => 'vorherige Aufgabe',
+'nexttask' => 'nächste Aufgabe',
+'duedate' => 'fällig am',
+'attachnoperms' => 'Dieser Kommentar hat Anhänge, aber Ihnen fehlt die Berechtigung diese anzuzeigen.',
+'linknoperms' => 'Es gibt Verknüpfungen mit diesem Kommentar, aber Ihnen fehlt die Berechtigung diese anzuzeigen.',
+'open' => 'offene',
+'depgraph' => 'Abhängigkeitsdiagramm anzeigen',
+'reset' => 'rückgängig',
+'selectusers' => 'Benutzer wählen...',
+'addmetoassignees' => 'Aufgabe mit übernehmen',
+'addedtoassignees' => 'Benutzer als Zuständigen hinzugefügt',
+'dependencygraph' => 'Abhängigkeitsdiagramm ',
+'attachanotherfile' => 'weitere Datei anfügen',
+'OK' => 'OK',
+'addvote' => 'Stimme hinzufügen',
+'disable_lostpw' => 'vergessene Passwortfunktion abschalten',
+'disable_changepw' => 'erstellen oder ändern des Passwortes abschalten',
+'notifyfromfs' => 'Flyspray Benachrichtigung',
+'autogenerated' => 'Dies ist eine automatisch erstellte Nachricht. Bitte antworten Sie nicht darauf.',
+'forward' => 'vorwärts',
+'previous' => 'vorige',
+'next' => 'nächste',
+'first' => 'erste',
+'last' => 'letzte',
+'page' => 'Seite %d von %d',
+'search' => 'suchen',
+'alltasktypes' => 'alle Aufgabentypen',
+'allseverities' => 'alle Schweregrade',
+'alldevelopers' => 'alle Entwickler',
+'notyetassigned' => 'niemandem übertragen',
+'allcategories' => 'alle Kategorien',
+'allstatuses' => 'alle Status',
+'allopentasks' => 'alle offenen Aufgaben',
+'sortthiscolumn' => 'Nach dieser Spalte sortieren',
+'id' => 'ID',
+'project' => 'Projekt',
+'dateopened' => 'angelegt',
+'progress' => 'Fortschritt',
+'searchthisproject' => 'suchen',
+'dueanyversion' => 'fällig in beliebiger Version',
+'anyversion' => 'in beliebiger Version geöffnet',
+'dueversion' => 'fällig in Version',
+'lastedit' => 'zuletzt geändert',
+'os' => 'Betriebssystem',
+'reportedin' => 'betrifft Version',
+'taskrange' => 'zeige Aufgaben %d - %d von %d',
+'noresults' => 'Ihre Suche ergab keine Treffer.',
+'takeaction' => 'ausführen',
+'watchtasks' => 'markierte Aufgaben beobachten',
+'stopwatchingtasks' => 'markierte Aufgaben nicht mehr beobachten',
+'assigntaskstome' => 'markierte Aufgaben mir übertragen',
+'dueby' => 'fällig bis',
+'dueanytime' => 'unbestimmt',
+'selectduedate' => 'Fälligkeit auswählen',
+'toggleselected' => 'Auswahl umkehren',
+'due' => 'fällig',
+'assignedtome' => 'mir übertragene Aufgaben',
+'tasklist' => 'Aufgabenliste',
+'dateclosed' => 'geschlossen am',
+'advanced' => 'erweitert',
+'searchcomments' => 'Suche in Kommentaren',
+'searchforall' => 'Suche nach allen Wörtern',
+'anonusers' => 'anonyme Benutzer',
+'miscellaneous' => 'Sonstiges',
+'users' => 'Benutzer',
+'taskproperties' => 'Eigenschaften der Aufgabe',
+'selectsincedate' => 'Wähle nach Datum der Änderung',
+'changedsince' => 'geändert seit',
+'updatefs' => 'Bitte aktualisieren Sie Ihre Flyspray-Installation.',
+'currentversion' => 'Die installierte Version ist',
+'latestversion' => 'und die aktuellste Version ist',
+'hidemessage' => '(später erinnern)',
+'saveas' => 'Suche speichern als',
+'nosearches' => 'Keine Suchprofile vorhanden',
+'saving' => 'Speichern...',
+'votes' => 'Stimmen',
+'tovote' => 'abstimmen',
+'allclosedtasks' => 'alle geschlossenen ',
+'password' => 'Passwort',
+'login' => 'Login',
+'rememberme' => 'Bei jedem Besuch automatisch anmelden',
+'lostpassword' => 'Passwort vergessen?',
+'lostpwforfs' => 'Hat das Passwort für Flyspray vergessen',
+'lostpwmsg1' => "Hallo,\n\nich habe mein Passwort für Flyspray auf ",
+'lostpwmsg2' => "vergessen. Bitte senden Sie mir ein neues Passwort.\n\nBenutzername: ",
+'regards' => "Vielen Dank,\n",
+'yourusername' => ' Ihr Benutzername ',
+'locale' => 'de-DE',
+'filenotexist' => "Die Datei ist nicht vorhanden oder Sie sind nicht zum Zugriff berechtigt.\n",
+'showtask' => 'Zeige Aufgabe',
+'now' => 'jetzt',
+'go' => 'Los!',
+'opentaskanon' => 'neue Aufgabe anonym anlegen',
+'register' => 'als neuer Benutzer registrieren',
+'addnewtask' => 'neue Aufgabe anlegen',
+'reports' => 'Berichte',
+'editmydetails' => 'meine Benutzerdaten bearbeiten',
+'logout' => 'Logout',
+'disabledaccount' => "Ihr Konto wurde deaktiviert!\n\nSie werden jetzt abgemeldet...",
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray wird finanziert durch ',
+'projects' => 'Projekte',
+'allprojects' => 'Alle Projekte',
+'selectproject' => 'im Projekt:',
+'tasksall' => 'Alle Aufgaben',
+'tasksassigned' => 'mir übertragene Aufgaben',
+'tasksreported' => 'Aufgaben, die ich angelegt habe',
+'taskswatched' => 'von mir beobachtete',
+'mysearch' => 'meine Suchprofile',
+'admintoolbox' => 'Verwaltungswerkzeuge',
+'manageproject' => 'Projektverwaltung',
+'permissions' => 'Berechtigungen',
+'hide' => 'verstecken',
+'pendingreq' => 'Anstehende Anfragen an den Projektmanager',
+'errorpage' => "Die angeforderte Seite kann nicht angezeigt werden.\n\nMöglicherweise haben Sie eine nicht vorhandene Aufgabe ausgewählt oder Sie besitzen keine Berechtigung, um die aufgerufene Seite anzuzeigen. \n\nFalls Sie versucht haben eine Sicherheitslücke wie beispielsweise SQL-Injection auszunutzen, gehen Sie in die Ecke und schämen sich! Bitte versuchen Sie es nicht noch einmal.",
+'permissionsforproject' => 'Berechtigungen für ',
+'switchto' => 'auswählen',
+'lastsearch' => 'meine letzte Suche',
+'modify' => 'ändern',
+'noticefrom' => 'Nachricht von',
+'hasopened' => 'hat eine neue Flyspray-Aufgabe angelegt und Ihnen übertragen:',
+'moreinfonew' => 'Einzelheiten zu dieser Aufgabe erfahren Sie auf folgender Flyspray-Seite:',
+'newtaskcategory' => 'Eine neue Flyspray-Aufgabe wurde in dieser Kategorie angelegt',
+'categoryowner' => 'Sie erhalten diese Nachricht, weil Sie für diese Kategorie zuständig sind.',
+'tasksummary' => 'Zusammenfassung:',
+'newtaskadded' => 'Ihre neue Aufgabe wurde hinzugefügt.',
+'summaryanddetails' => 'Sie müssen sowohl eine Zusammenfassung als auch eine Beschreibung angeben.',
+'summaryrequired' => 'Bitte geben Sie eine kurze Aufgabenbeschreibung an.',
+'goback' => 'zurück',
+'messagefrom' => 'Dies ist eine Nachricht vom Flyspray Bugtracking-System auf ',
+'hasjustmodified' => 'hat soeben folgende Aufgabe geändert:',
+'changedfields' => 'Veränderte Felder sind mit Sternchen gekennzeichnet (**)',
+'moreinfomodify' => 'Weitere Informationen zu dieser Aufgabe auf',
+'nolongerassigned' => 'Für die nachstehende Aufgabe sind Sie nicht mehr zuständig. Die Aufgabe wurde übertragen an ',
+'hasassigned' => 'hat Ihnen die folgende Flyspray-Aufgabe übertragen:',
+'taskupdated' => 'Aufgabe wurde aktualisiert.',
+'tasksupdated' => 'Die Aufgabe wurde aktualisiert.',
+'hasclosedassigned' => 'hat die folgende Flyspray-Aufgabe geschlossen, die Ihnen übertragen war:',
+'unassigned' => 'niemandem übertragen',
+'hasclosed' => 'hat die folgende Aufgabe geschlossen:',
+'youonnotify' => 'Sie erhalten diese Nachricht, weil Sie auf der Benachrichtigungsliste geführt sind.',
+'taskclosedmsg' => 'Die Aufgabe wurde geschlossen.',
+'returntotask' => 'zurück zu den Aufgabendetails',
+'backtoindex' => 'zurück zur Aufgabenliste',
+'noclosereason' => 'Sie haben keinen Grund für das Schließen der Aufgabe angegeben.',
+'hasreopened' => 'hat folgende Flyspray-Aufgabe wieder geöffnet, die Sie bereits geschlossen hatten:',
+'taskreopenedmsg' => 'Die Aufgabe wurde wieder geöffnet.',
+'backtotask' => 'zurück zur Aufgabe.',
+'commentaddedmsg' => 'Der Kommentar wurde hinzugefügt',
+'commenttoassigned' => 'hat einen Kommentar zu einer Aufgabe hinzugefügt, die Ihnen übertragen ist: ',
+'commenttotask' => 'hat folgenden Kommentar zur Aufgabe abgegeben: ',
+'nocommententered' => 'Schreiben Sie einen Kommentar bevor Sie das Formular abschicken.',
+'fillinfields' => 'Sie haben nicht alle Felder ausgefüllt.',
+'notcurrentpass' => 'Das ist nicht Ihr aktuelles Passwort!',
+'passchanged' => 'Ihr Passwort wurde geändert.',
+'closewindow' => 'Sie können dieses Fenster jetzt schließen.',
+'passnomatch' => 'Neues Passwort und Bestätigung stimmen nicht überein!',
+'usernametaken' => 'Dieser Benutzername wurde bereits vergeben. Wählen Sie bitte einen anderen Namen.',
+'usernametakenbulk' => 'Benutzername schon vorhanden',
+'newusercreated' => 'Neues Benutzerkonto wurde angelegt.',
+'accountcreated' => 'Ihr Konto wurde angelegt.',
+'newuserwarning' => 'Ihr Konto muss möglicherweise von einem Administrator aktiviert werden, bevor Sie sich das erste Mal anmelden können.',
+'nomatchpass' => 'Ihre Passwörter stimmen nicht überein!',
+'confirmwrong' => 'Bestätigungscode ungültig!',
+'formnotcomplete' => 'Formular wurde nicht vollständig ausgefüllt.',
+'formnotnumeric' => 'Die Eingabe war nicht numerisch!',
+'groupnametaken' => 'Dieser Gruppenname ist bereits vorhanden.',
+'newgroupadded' => 'Neue Gruppe hinzugefügt.',
+'optionssaved' => 'Flyspray-Optionen gespeichert.',
+'hasuploaded' => 'hat eine Datei an eine Aufgabe angehängt, die Ihnen übertragen ist: ',
+'hasattached' => 'hat eine Datei an folgende Aufgabe angehängt: ',
+'fileuploaded' => 'Datei wurde hochgeladen.',
+'fileerror' => 'Fehler beim Hochladen der Datei. Vielleicht stimmen die Berechtigungen für das <code>attachments</code> Verzeichnis nicht.',
+'contactadmin' => 'Wenden Sie sich an den Administrator des Projekts.',
+'selectfileerror' => 'Sie haben keine Datei ausgewählt.',
+'userupdated' => 'Benutzerdetails wurden aktualisiert',
+'realandemail' => 'Sie müssen beide Felder "vollständiger Name" und "E-Mail" ausfüllen.',
+'groupupdated' => 'Gruppendefinition aktualisiert.',
+'groupanddesc' => 'Sie haben keinen Gruppennamen und/oder keine Beschreibung eingegeben.',
+'fillallfields' => 'Bitte füllen Sie alle Felder aus.',
+'listPmustN' => 'Der Wert für die Reihenfolge muss numerisch sein.',
+'listupdated' => 'Die Liste wurde aktualisiert.',
+'listitemadded' => 'Neues Listenelement hinzugefügt.',
+'relatedaddedmsg' => 'Verknüpfte Aufgabe zur Liste hinzugefügt.',
+'relatederror' => 'Zu dieser Aufgabe besteht bereits eine Verbindung.',
+'relatedremoved' => 'Die Verbindung zu der Aufgabe wurde entfernt.',
+'notifyadded' => 'Der Benutzer wird ab jetzt benachrichtigt.',
+'notifyerror' => 'Dieser Benutzer erhält bereits Benachrichtigungen zu dieser Aufgabe.',
+'notifyremoved' => 'Der Benutzer wird ab jetzt nicht mehr benachrichtigt.',
+'editcommentsaved' => 'Geänderten Kommentar gespeichert.',
+'commentdeletedmsg' => 'Der Kommentar wurde gelöscht.',
+'gotonewtask' => 'Zu den Details der soeben erstellten Aufgabe',
+'projectcreated' => 'Ihr neues Projekt wurde erstellt. Sie können das Projekt jetzt unter "Projektverwaltung" einrichten.',
+'customiseproject' => 'Dieses Projekt anpassen',
+'projectupdated' => 'Projekteinstellungen gespeichert.',
+'emptytitle' => 'Bitte geben Sie einen Projektnamen an.',
+'loginbelow' => 'Sie können sich nun anmelden.',
+'attachmentdeletedmsg' => 'Der Anhang wurde gelöscht',
+'reminderaddedmsg' => 'Ihre Erinnerung wurde eingerichtet.',
+'reminderdeletedmsg' => 'Die ausgewählte Erinnerung wurde gelöscht.',
+'flyspraytask' => 'Flyspray-Aufgabe',
+'fieldsmissing' => 'Felder ohne oder mit ungültigen Werten wurden ignoriert.',
+'relatedinvalid' => 'Aufgabe nicht vorhanden.',
+'relatedproject' => 'Die gewählte Aufgabe gehört zu einem anderen Projekt.',
+'addanyway' => 'Dennoch hinzufügen',
+'cancel' => 'abbrechen',
+'alreadyedited' => 'Die Aufgabe wurde von einem anderen Benutzer bearbeitet, während Sie Ihre Änderungen vorgenommen haben. Wollen Sie dennoch speichern und die Bearbeitung des anderen Benutzers damit überschreiben?',
+'saveanyway' => 'Meine Änderungen speichern',
+'nouserselected' => 'Keinen Benutzer ausgewählt. Markieren Sie mindestens einen Benutzer und versuchen Sie es erneut.',
+'groupswitchupdated' => 'Benutzergruppen erfolgreich geändert.',
+'takenownershipmsg' => 'Jetzt wurde Ihnen die Aufgabe übertragen.',
+'adminrequestmade' => 'Ein Projektmanager wurde über Ihre Anfrage informiert.',
+'newdepnotify' => 'Eine neue Abhängigkeit wurde zur folgenden Aufgabe hinzugefügt: ',
+'dependadded' => 'Abhängigkeit wurde hinzugefügt',
+'dependaddfailed' => 'Sie können diese Aufgabe im Moment nicht als Abhängigkeit hinzufügen',
+'depremovedmsg' => 'Die Abhängigkeit von der anderen Aufgabe wurde entfernt.',
+'newdepis' => 'Die neue Abhängigkeit ist',
+'magicurlsent' => 'Eine Nachricht mit einem Link auf die Seite zum Vervollständigen der Aufgabe wurde an Ihre Benachrichtigungsadresse geschickt.',
+'changefspass' => 'Flyspray-Passwort ändern',
+'magicurlmessage' => 'Bitte folgen Sie diesem Link, um Ihr Passwort zu ändern: ',
+'erroronform' => 'Bei der Verarbeitung der Formulareingaben ist ein Problem aufgetreten',
+'addressused' => "Diese Adresse wurde verwendet, um ein Flyspray-Benutzerkonto anzulegen. Wenn Sie diese Nachricht nicht erwartet haben, ignorieren und löschen Sie diese bitte. \n\n(This address has been used to register a Flyspray account. If you were not expecting this message, please ignore and delete it.)\n\nBenutzen Sie folgenden Aktivierungslink um die Registrierung abzuschließen: ",
+'confirmcodeis' => 'Ihr Bestätigungscode:',
+'codesent' => 'Ihr Bestätigungscode wurde versandt. Folgen Sie bitte den Anweisungen in der Nachricht.',
+'codenotsent' => 'Ihr Bestätigungscode konnte nicht versandt werden. Bitte versuchen Sie es später erneut.',
+'taskmadeprivatemsg' => 'Diese Aufgabe ist nun versteckt',
+'taskmadepublicmsg' => 'Diese Aufgabe ist nun wieder öffentlich sichtbar',
+'realandnotify' => 'Sie müssen die Felder "vollständiger Name" und entweder "E-Mail" oder "Jabber-ID" ausfüllen.',
+'pmreqdeniedmsg' => 'Der Projektmanager hat die Anfrage abgelehnt.',
+'massopsuccess' => 'Vorgang auf allen Elementen erfolgreich, bei denen die Berechtigungen ausreichend waren',
+'usernotexist' => 'Dieser Benutzer ist in dieser Flyspray-Instanz nicht vorhanden.',
+'commentattachperms' => 'Sie können diesen Kommentar nicht löschen - Sie haben keine Berechtigung zum Löschen von Anhängen.',
+'voterecorded' => 'Ihre Stimme wurde registriert.',
+'votefailed' => 'Ihre Stimme kann momentan nicht hinzugefügt werden.',
+'createnewgroup' => 'Neue Gruppe erstellen',
+'requiredfields' => 'Notwendige Felder sind gekennzeichnet:',
+'addthisgroup' => 'Gruppe hinzufügen',
+'createnewproject' => 'neues Projekt',
+'addnewproject' => 'neues Projekt hinzufügen',
+'htmlallowed' => 'HTML-Code zulassen',
+'createthisproject' => 'Projekt erstellen',
+'inlineimages' => 'angehängte Bilder als Vorschau anzeigen',
+'createnewtask' => 'Neue Aufgabe in diesem Projekt anlegen:',
+'addanother' => 'weitere Aufgabe nach dieser hinzufügen',
+'addthistask' => 'Aufgabe hinzufügen',
+'notifyme' => 'Automatische Benachrichtigung bei Änderungen an dieser Aufgabe',
+'newtask' => 'neue Aufgabe',
+'attachafile' => 'Datei anhängen',
+'registernewuser' => 'neuen Benutzer registrieren',
+'none' => 'nichts',
+'registeraccount' => 'Konto registrieren',
+'registerbulkaccount' => 'Nutzer registrieren',
+'both' => 'E-Mail und Jabber',
+'notifyfrom' => 'Benachrichtigung von ',
+'donotreply' => 'DIES IST EINE AUTOMATISCH ERSTELLTE NACHRICHT, BITTE NICHT ANTWORTEN.',
+'disclaimer' => 'Sie erhalten diese Nachricht, weil Sie in Flyspray Benachrichtigungen aktiviert haben. Wenn Sie zukünftig keine solchen Benachrichtigungen mehr erhalten wollen, können Sie die Einstellungen für Benachrichtigungen über die obenstehende URL ändern.',
+'userwho' => 'Benutzer der dies getan hat',
+'moreinfo' => 'Mehr Informationen können unter der folgenden URL abgerufen werden: ',
+'newtaskopened' => 'Eine neue Flyspray Aufgabe wurde angelegt. Details siehe unten.',
+'notify.taskclosed' => 'Diese Aufgabe ist nun geschlossen:',
+'notify.taskreopened' => 'Diese Aufgabe wurde wieder geöffnet:',
+'newdep' => 'Diese Aufgabe besitzt eine neue Abhängigkeit:',
+'notify.depremoved' => 'Von dieser Aufgabe wurde die Abhängigkeit entfernt:',
+'olddepwas' => 'Die alte Abhängigkeit war',
+'notify.commentadded' => 'Dieser Aufgabe wurde ein neuer Kommentar hinzugefügt:',
+'commentis' => 'Der Kommentartext folgt.',
+'newattachment' => 'Dieser Aufgabe wurde eine Datei angehängt:',
+'detailsbelow' => 'Details siehe unten.',
+'notify.relatedadded' => 'Dieser Aufgabe wurde eine Beziehung zu einer anderen Aufgabe zugeordnet:',
+'relatedis' => 'Die abhängige Aufgabe ist',
+'assignedtoyou' => 'Die folgende Aufgabe wurde Ihnen übertragen: ',
+'takenownership' => 'ist jetzt Eigentümer der folgenden Aufgabe: ',
+'requiresaction' => 'Diese Aufgabe benötigt die Bearbeitung durch einen Projektmanager:',
+'requiresactionnotify' => 'Die Aufgabe benötigt die Bearbeitung durch einen Projektmanager.',
+'pmdeny' => 'Ein Projektmanager hat die für folgende Aufgabe anstehende Anfrage abgelehnt: ',
+'pmdenynotify' => 'Ein Projektmanager hat die Anfrage abgelehnt',
+'fileaddedtoo' => 'Eine oder mehrere Dateien wurden hinzugefügt.',
+'taskwatching' => 'Diese Aufgabe beobachten Sie',
+'isdepfor' => 'bildet eine neue Abhängigkeit für Aufgabe ',
+'denialreason' => 'Grund der Ablehnung',
+'taskchanged' => 'Die nachstehende Aufgabe wurde geändert (Details siehe unten). Vollständige Informationen über Änderungen erreichen Sie über die angegebene URL unter dem Tab "Verlauf".',
+'useraddedtoassignees' => 'Ein Benutzer hat die Aufgabe selbst übernommen.',
+'removeddepis' => 'Die gelöschte Abhängigkeit ist',
+'isnodepfor' => 'ist nicht mehr abhängig von',
+'usergroups' => 'Benutzergruppen',
+'pmtoolbox' => 'Projektverwaltung',
+'groupmanage' => 'Benutzer und Gruppen',
+'pendingrequests' => 'anliegende Anfragen',
+'reasongiven' => 'Begründung',
+'nopendingreq' => 'Es gibt zur Zeit keine offenen Anfragen an den Projektmanager',
+'givereason' => 'Geben Sie eine Begründung',
+'catlisted' => 'Kategorien-Editor',
+'oslisted' => 'Betriebssystems-Editor',
+'verlisted' => 'Versions-Editor',
+'tasktypeed' => 'Aufgabentypen-Editor',
+'resed' => 'Lösungen-Editor',
+'deny' => 'ablehnen',
+'notifiedwhen' => 'Benachrichtigen wenn',
+'onlynewtasks' => 'neu angelegte Aufgaben',
+'allevents' => 'Bei allen Ereignissen in einer beliebigen Aufgabe',
+'feeds' => 'Newsfeeds',
+'feeddescription' => 'Newsfeed Beschreibung',
+'feedimgurl' => 'Newsfeed Bild URL (leer zeigt kein Bild an)',
+'notifysubject' => 'Betreff',
+'notifysubjectinfo' => '(%p = Projektname, %s = Zusammenfassung, %t = Aufgaben-ID, %a = Aktion, %u = Benutzer)',
+'priority6' => 'sofort',
+'priority5' => 'eilig',
+'priority4' => 'hoch',
+'priority3' => 'normal',
+'priority2' => 'gering',
+'priority1' => 'verschoben',
+'sendcode' => 'Code senden!',
+'entercode' => 'Ihr Bestätigungscode wurde versandt. Geben Sie ihn nach Erhalt zusammen mit Ihrem gewünschten Passwort in untenstehendem Formular ein.',
+'confirmationcode' => 'Bestätigungscode',
+'registererror' => 'Stellen Sie sicher, dass Sie alle notwendigen Felder ausgefüllt und korrekte Angaben zu Ihrem gewählten Benachrichtigungsweg gemacht haben.',
+'validusername' => '(nur alphanumerische Zeichen und - _ . sind erlaubt)',
+'validemail' => '( Semikolon ; nutzen, um mehrere Emailadressen anzugeben)',
+'emailtakenbulk' => 'Emailadresse schon vergeben',
+'emailtaken' => 'Diese E-Mail Adresse oder Jabber-ID wird bereits benutzt. Bitte wählen Sie eine andere.',
+'note' => '<em>Hinweis:</em> Ihnen wird auf dem gewählten Weg ein Bestätigungscode zugesandt, bevor Ihr Konto eröffnet wird. <strong>Wenn Sie falsche Angaben machen, werden Sie den Code nicht erhalten!</strong>',
+'changelog' => 'Verlauf',
+'changeloggen' => 'Verlauf Generator',
+'listfrom' => 'Liste der Änderungen von',
+'to' => 'bis',
+'oldestfirst' => 'Älteste zuerst',
+'recentfirst' => 'Aktuellste zuerst',
+'severityrep' => 'Bericht nach Schweregrad',
+'totalopen' => 'Offene Aufgaben insgesamt',
+'age' => 'Alter',
+'agerep' => 'Bericht nach Alter',
+'eventsrep' => 'Ereignisbericht',
+'events' => 'Ereignisse',
+'Tasks' => 'Aufgaben',
+'opened' => 'angelegt',
+'edited' => 'geändert',
+'assigned' => 'Ãœbertragen',
+'within' => 'innerhalb',
+'pastday' => 'des letzten Tages',
+'pastweek' => 'der letzten Woche',
+'pastmonth' => 'des letzten Monats',
+'pastyear' => 'des letzten Jahres',
+'nolimit' => 'unbegrenzt',
+'from' => 'von',
+'duein' => 'fällig in',
+'selectfromdate' => 'Startdatum',
+'selecttodate' => 'Enddatum',
+'showvoters' => 'Anzeigen/Ausblenden der Wähler',
+'roadmap' => 'Planung',
+'roadmapfor' => 'Planung für',
+'tasks' => 'Aufgaben',
+'completed' => 'erledigt.',
+'opentasks' => 'offene Aufgaben',
+'of' => '% von',
+'severity5' => 'kritisch',
+'severity4' => 'hoch',
+'severity3' => 'mittel',
+'severity2' => 'niedrig',
+'severity1' => 'sehr niedrig',
+'Redirect' => 'weiterleiten',
+'redirectmsg' => 'Falls Ihr Browser Sie nicht automatisch weiterleitet, klicken Sie bitte %sHERE%s.',
+'allowclosedcomments' => 'Kommentare zu geschlossenen Aufgaben erlauben',
+'comment' => 'Kommentar',
+'editowncomments' => 'eigene Kommentare bearbeiten',
+'reopened' => 'wieder geöffnet',
+'loading' => 'Lade...',
+'notifyown' => 'bei eigenen Änderungen benachrichtigen',
+'youremail' => 'Ihre E-Mail Adresse',
+'thankyouforbug' => 'Vielen Dank für den Problembericht. Sie können Ihren Bericht und seine Bearbeitung jederzeit unter folgender URL verfolgen: ',
+'anonuser' => 'Anonymer Benutzer',
+'conflict' => 'Widerspruch',
+'file' => 'Datei',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => 'Größe',
+'projectgroup' => 'Projektgruppe',
+'profile' => 'Profil',
+'viewprofile' => 'Profil anzeigen',
+'regdate' => 'registriert seit',
+'tasksopened' => 'angelegte Aufgaben',
+'replyto' => 'Antwort an',
+'notifytypes' => 'Benachrichtigungsanlässe',
+'pm.taskchanged' => 'Aufgabe geändert',
+'pm.taskreopened' => 'Aufgabe wieder geöffnet',
+'pm.depadded' => 'Abhängigkeit hinzugefügt',
+'pm.depremoved' => 'Abhängigkeit entfernt',
+'pmrequest' => 'Anfrage an den Projektmanager',
+'pmrequestdenied' => 'Anfrage an den Projektmanager abgelehnt',
+'newassignee' => 'neue Zuständigkeit',
+'revdepadded' => 'Umgekehrte Abhängigkeit hinzugefügt',
+'revdepaddedremoved' => 'Umgekehrte Abhängigkeit entfernt',
+'assigneeadded' => 'Zuständigen hinzugefügt',
+'addusergroup' => 'Benutzer zur Gruppe hinzufügen',
+'groupmembers' => 'Gruppenmitglieder',
+'deleteuser' => 'Benutzer löschen',
+'userdeleted' => 'Benutzer gelöscht',
+'autoassign' => 'Aufgabe automatisch dem Kategorie-Inhaber übertragen',
+'ssl' => 'SSL',
+'updatewrong' => 'Die Update-Prüfung ist aktiviert. Allerdings gab es Probleme, auf den Update-Server zuzugreifen. Entweder sind ausgehende Verbindungen nicht möglich oder es gab ein Netzwerk-Problem. Bitte besuchen Sie die Flyspray Website (http://flyspray.org), um die Aktualität Ihrer Version zu überprüfen.',
+'deleteproject' => 'Projekt löschen und Inhalte verschieben nach',
+'projectdeleted' => 'Das Projekt wurde gelöscht',
+'feedforall' => 'Newsfeed für alle Projekte',
+'usercreated' => 'Benutzer hinzugefügt',
+'created' => 'erzeugt',
+'deleted' => 'gelöscht',
+'userid' => 'Benutzer-ID',
+'editassignments' => 'Zuständigkeit bearbeiten',
+'preview' => 'Vorschau',
+'anyprogress' => 'beliebiger Fortschritt',
+'tasksrelated' => 'Aufgaben, die mit dieser verknüpft sind',
+'duplicatetasks' => 'Andere Aufgaben für dasselbe Problem',
+'databasemodfailed' => 'Änderungen an der Datenbank nicht möglich. Wahrscheinlich besitzen Sie nicht die notwendigen Rechte.',
+'frequency' => 'Häufigkeit',
+'newuserregistered' => 'Ein neuer Benutzer hat sich an Ihrer Flyspray-Instanz mit folgenden Benutzerdaten registriert: ',
+'newuserregisterednotify' => 'Ein neuer Benutzer hat sich registriert',
+'notify_registration' => 'Administrator über neue Registrierungen informieren',
+'textversion' => 'Textversion',
+'onlyprimary' => 'Aufgaben, die keine anderen Aufgaben blockieren',
+'onlyblocker' => 'Aufgaben, die andere Aufgaben blockieren',
+'blockerornoblocker' => 'Entweder Blockierer oder Nichtblockierer wählen, beides zusammen bringt nix.',
+'switch' => 'auswählen',
+'max' => 'max.',
+'dates' => 'Datum',
+'selectduedatefrom' => 'Fälligkeit von',
+'selectduedateto' => 'bis',
+'selectsincedatefrom' => 'verändert von',
+'selectsincedateto' => 'bis',
+'selectdate' => 'Datum wählen',
+'selectopenedfrom' => 'angelegt seit',
+'selectopenedto' => 'bis',
+'selectclosedfrom' => 'geschlossen von',
+'selectclosedto' => 'bis',
+'startat' => 'begonnen von',
+'hasattachment' => 'mit Anhängen',
+'private' => 'versteckt',
+'watching' => 'beobachtet',
+'alreadyvotedthistask' => 'Sie haben für diese Aufgabe gestimmt',
+'alreadyvotedthisday' => 'Sie haben heute schon abgestimmt',
+'visibility' => 'Sichtbarkeit',
+'public' => 'Öffentlich sichtbar',
+'leaveemptyauto' => 'Lassen Sie das Passwort-Feld leer, um automatisch ein Passwort zu erzeugen.',
+'novalidemail' => 'Sie haben keine gültige E-Mail Adresse eingegeben.',
+'novalidjabber' => 'Sie haben keine gültige Jabber-Adresse eingegeben.',
+'missingrequired' => 'Sie haben nicht alle erforderlichen Felder ausgefüllt.',
+'entervalidusername' => 'Bitte geben Sie einen gültigen Benutzernamen und den vollständigen Namen an.',
+'couldnotaddusernotif' => 'Für den Benutzer konnte keine Benachrichtigung eingerichtet werden.',
+'defaulttask' => 'Standard Aufgabenbeschreibung',
+'all' => 'alles',
+'events.useraddedtoassignees'=> 'Zuständigen hinzugefügt',
+'eventlog' => 'Berichte',
+'assignmentchanged' => 'Zuständigkeit geändert',
+'detailedinfo' => 'Details',
+'All' => 'alle',
+'tasksireported' => 'von mir angelegte',
+'recentlyopened' => 'kürzlich angelegte',
+'stats' => 'Statistik',
+'totaltasks' => 'Aufgaben insgesamt',
+'mostwanted' => 'häufig angefragte Aufgaben',
+'defaultentry' => 'Zeige auf der Startseite',
+'toplevel' => 'Top Level Ansicht',
+'overview' => 'Ãœberblick',
+'error#' => 'FEHLER #',
+'error1' => 'Sie sind nicht zur Anzeige dieses Anhangs berechtigt!',
+'error3' => 'Wiederholte Aktion, zur Hauptseite umgeleitet!',
+'error4' => 'Sie haben keine Administrator-Rechte!',
+'error5' => 'Dieser Benutzer ist in dieser Flyspray-Instanz nicht vorhanden!',
+'error6' => 'Ungültiger Administrator-Bereich!',
+'error7' => 'Anmeldung nicht möglich (Benutzername oder Passwort falsch)!',
+'error71' => 'Zugang wurde für %d Minuten gesperrt wegen zu vieler fehlerhafter Loginversuche.',
+'error8' => 'Sie müssen einen Benutzernamen UND ein Passwort eingeben.',
+'error9' => 'Die Aufgabe ist nicht vorhanden oder Sie sind nicht zur Anzeige berechtigt!',
+'error10' => 'Die Aufgabe ist nicht vorhanden oder Sie sind zur Anzeige nicht berechtigt!',
+'error101' => 'Sie haben keine Berechtigung diese Aufgabe anzuschauen.',
+'error102' => 'Sie haben keine Berechtigung diese Aufgabe anzuschauen, vielleicht hilft sich anzumelden.',
+'error11' => 'Sie sind nicht berechtigt, diesen Kommentar zu bearbeiten!',
+'error12' => "Ungültiger Aktivierungsschlüssel! \nHaben Sie diesen korrekt aus der Nachricht übernommen?",
+'error13' => 'Anonyme Benutzer besitzen kein Profil!',
+'error14' => 'Sie sind nicht berechtigt eine neue Gruppe zu erzeugen!',
+'error15' => 'Sie sind nicht berechtigt eine Aufgabe anzulegen!',
+'error16' => 'Sie sind kein Projektmanager!',
+'error17' => 'Ungültiger Projektmanager-Bereich!',
+'error18' => 'Ungültige Aktivierungs-URL!',
+'error19' => 'Dieser Benutzer ist in dieser Flyspray-Instanz nicht vorhanden!',
+'error20' => 'Ungültiger Schreibvorgang in der Datenbank!',
+'error21' => "Ein oder mehrere E-Mails konnten nicht versandt werden! \nPrüfen Sie die Konfiguration.",
+'error22' => 'Die Registrierung neuer Benutzer ist nicht erlaubt!',
+'error23' => 'Der Benutzer oder die Gruppe wurde nicht zum Login berechtigt!',
+'error24' => 'Das Programm "dot" ist weder lokal noch für einen öffentlichen dot-Server konfiguriert!',
+'error25' => 'Die Planung gibt es nur jeweils für ein einzelnes Projekt!',
+'error26' => 'derzeit nicht unterstützter OAuth-Anbieter.',
+'error27' => 'Konnte den Nutzer nicht anmelden. Bitte erlauben Sie uns ihre Emailadresse zu sehen.',
+'error28' => 'Sie haben keine Erlaubnis für diesen Bereich.',
+'done' => 'erledigt',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Das Projekt konnte nicht gelöscht werden.',
+'GMT' => 'UTC',
+'timezone' => 'Zeitzone',
+'accept' => 'akzeptiert',
+'reasonfordeinal' => 'Grund der Ablehnung',
+'pruneclosedlinks' => 'Geschlossene Links ausblenden',
+'pruneclosedtasks' => 'Geschlossene Aufgaben ausblenden',
+'pagegenerated' => 'Seite und Bilder in %d Sekunden erzeugt.',
+'pruninglevel' => 'Darstellung',
+'lastuser' => 'Es muss mindestens ein Benutzer erhalten bleiben.',
+'allprivate' => 'Es gibt keine öffentlich sichtbaren Projekte.',
+'deletegroup' => 'Lösche diese Gruppe und verschiebe die Benutzer in',
+'parent' => 'übergeordnetes Element',
+'ordertip' => 'Reihenfolge der Elemente',
+'showtip' => 'Element anzeigen',
+'deletetip' => 'Element löschen',
+'del' => 'entfernen',
+'request1' => 'Schließen einer Aufgaben wurde angefragt.',
+'request2' => 'Wiedereröffnung einer Aufgabe wurde angefragt.',
+'allpriorities' => 'Alle Prioritäten',
+'noroadmap' => 'Es ist keine Planung verfügbar, da für das Projekt keine Version für die "Zukunft" vorhanden ist.',
+'expand' => 'aufklappen',
+'collapse' => 'zusammenklappen',
+'expandall' => 'alle aufklappen',
+'collapseall' => 'alle zusammenklappen',
+'minpwsize' => 'Das Passwort muss mindestens 5 Zeichen haben',
+'passwordtoosmall' => 'Das Passwort ist zu kurz.',
+'accountwaslocked' => 'Ihr Zugang wurde wegen zu vielen fehlerhaften Loginversuchen gesperrt.',
+'failedattempts' => 'Es gab %d fehlerhafte Loginversuche.',
+'groupnotexist' => 'Die gewählte Gruppe existiert nicht in diesem Projekt.',
+'searchindetails' => 'Suche in Aufgabentexten',
+'showasassignees' => 'Als mögliche Bearbeiter anzeigen',
+'find' => 'finden',
+'tls' => 'TLS',
+'isadmin' => 'ist Admin',
+'addvotes' => 'Abstimmungen hinzufügen',
+'removevote' => 'Abstimmung entfernen',
+'voteremoved' => 'Ihre Abstimmung wurde entfernt',
+'voteremovefailed' => 'Ihre Abstimmung konnte momentan nicht entfernt werden.',
+'novotes' => 'Du hast derzeit für keine Aufgabe abgestimmt.',
+'connectedtasks' => 'Verbundene Aufgaben:',
+'taskdependencies' => 'Abhängigkeiten',
+'viewgraph' => 'Diagramm anzeigen',
+'notaskdependencies' => 'Diese Aufgabe hängt von keiner anderen Aufgabe ab.',
+'dependson' => 'abhängig von',
+'blocks' => 'blockiert',
+'newdependency' => 'Neue Abhängigkeit:',
+'nouserstoadd' => 'Kein Nutzer eingetragen. Name, Nutzername und Emailadresse sind notwendig für jeden Nutzer.',
+'dispintro' => 'Zeige Projektbeschreibung',
+'mainmessage' => 'Einführungstext',
+'setsupertask' => 'übergeordnete Aufgabe setzen (ID):',
+'supertaskmodified' => 'übergeordnete Aufgabe geändert',
+'set' => 'Setzen',
+'supertask' => 'übergeordnete Aufgabe',
+'setparent' => 'Oberaufgabe für diese Aufgabe',
+'selfsupertasknotallowed' => 'Aufgabe darf nicht sich selbst übergeordnete Aufgabe sein',
+'quickaction' => 'Schnellaktionen',
+'updateselectedtasks' => 'Aktualisiere ausgewählte Aufgaben',
+'notspecified' => 'nicht angegeben',
+'editselectedtasks' => 'Bearbeite ausgewählte Aufgaben',
+'information' => 'Information',
+'taskclosedisabled' => 'Das Schließen dieser Aufgabe ist derzeit aus folgenden Gründen nicht möglich:',
+'daysleft' => 'Tage verbleibend',
+'dayoverdue' => 'Tage überfällig',
+'duetoday' => 'bis heute',
+'daysbeforealert' => 'Tage vor Erinnerung',
+'associatedsubtask' => 'Erfolgreich Unteraufgabe #FS%d verknüpft',
+'associatesubtask' => 'Verknüpfe eine Unteraufgabe mit dieser Aufgabe',
+'subtaskid' => 'UnteraufgabenID',
+'subtaskalreadyhasparent' => 'Diese Unteraufgabe hat bereits eine Oberaufgabe. Bitte diese Verknüpfung vorher auftrennen bevor andere Oberaufgabe.',
+'subtaskisparent' => 'Die eingegebene Unteraufgabe ist schon die Oberaufgabe der Aufgabe. Zyklische Verknüpfung nicht erlaubt.',
+'subtasknotexist' => 'Diese Unteraufgabe existiert nicht.',
+'subtaskremovedmsg' => 'Die Unteraufgabe wurde erfolgreich entfernt.',
+'subtaskadded' => 'Unteraufgabe hinzugefügt',
+'subtaskremoved' => 'Unteraufgabe entfernt',
+'addnewsubtask' => 'Neue untergeordnete Aufgabe',
+'hidesubtasks' => 'untergeordnete Aufgaben ausblenden',
+'voteforthistask' => 'Stimme für diese Aufgabe',
+'watchthistask' => 'Beobachte diese Aufgabe',
+'privatethistask' => 'Aufgabe auf privat setzen',
+'adddependenttask' => 'abhängige Aufgabe verknüpfen',
+'associatetaskid' => 'ID der Unteraufgabe',
+'parenttaskid' => 'Oberaufgaben-ID',
+'invalidsupertaskid' => 'Oberaufgaben-ID ist ungültig.',
+'supertaskadded' => 'Gruppierungsaufgabe hinzugefügt',
+'supertaskremoved' => 'übergeordnete Aufgabe gelöscht',
+'effort' => 'Aufwand',
+'efforttracking' => 'Aufwandmessung',
+'useeffort' => 'Projekt nutzt Aufwandmessung',
+'estimatedeffort' => 'geschätzter Aufwand',
+'totalestimatedeffort' => 'geschätzter Gesamtaufwand',
+'currenteffortdone' => 'bisher erfolgter Aufwand',
+'starteffort' => 'Starte Aufwandmessung',
+'endeffort' => 'Stoppe Aufwandmessung',
+'cleareffort' => 'Entferne Aufwandmessung',
+'addeffort' => 'Aufwand hinzufügen',
+'manualeffort' => 'Aufwand eintragen (H:M)',
+'efforttrackingstarted' => 'Aufwandmessung für diese Aufgabe.',
+'efforttrackingnotstarted'=> 'Die Aufwandmessung kann nicht gestarted werden, da für diese Aufgabe schon eine Messung läuft.',
+'efforttrackingstopped' => 'Aufwandsmessung für diese Aufgabe wurde gestoppt.',
+'efforttrackingcancelled' => 'Aufwandsmessung abgebrochen für diese Aufgabe.',
+'efforttrackingadded' => 'Aufwandseintrag für diese Aufgabe wurde gespeichert.',
+'trackinginprogress' => 'Aufwandsmessung läuft',
+'viewestimatedeffort' => 'Kann Aufwandsmessung anschauen',
+'viewcurrenteffortdone' => 'darf erfolgten Arbeitsaufwand anschauen',
+'trackeffort' => 'kann Aufwandsmessung nutzen',
+'invalideffort' => 'Der eingegebene Aufwand ist ungültig. Es muss eine Zahl sein.',
+'showpass' => 'Zeige Passwort',
+'chooseafile' => 'Bitte eine Datei wählen.',
+'incorrectfiletype' => 'ungültiger Dateityp. Erlaubt sind jpg, jpeg, gif und png.',
+'oauthreqpass' => 'Kein Passwort vorhanden. Sie haben sich mit %s registriert.',
+'addmultipletasks' => 'mehrere Aufgaben anlegen',
+'pendingnewuserrequest' => 'wartende neue Nutzeranfrage',
+'adminrequestswaiting' => 'wartende Administratoranfragen',
+'clicktoedit' => 'Werte anklicken zur Schnellbearbeitung',
+'confirmedit' => 'speichern',
+'regapprovedbyadmin' => 'Registrierung muß durch einen Administrator bestätigt werden (Bestätigungscode ohne Bedeutung)',
+'activity' => 'Aktivität',
+'myactivity' => 'meine Aktivitäten',
+'emailverificationwrong' => 'Die beiden eingegebenen Emailadressen stimmen nicht überein',
+'verifyemailaddress' => 'Emailadresse wiederholen',
+'hideemails' => 'Emailadressen verbergen',
+'hidemyemail' => 'Emailadresse verbergen',
+'exporttasklist' => 'Aufgabenliste exportieren',
+'onedecimal' => 'eine Dezimalstelle',
+'manday' => 'Manntag',
+'mandays' => 'Manntage',
+'mandayabbrev' => 'Manntag',
+'hourspermanday' => 'Arbeitsstunden pro Manntag',
+'itemexists' => 'Eintrag %s existiert bereits in der Datenbank.',
+'categoryitemexists' => 'Eintrag %s existiert bereits in der Kategorie %s in der Datenbank.',
+'pageswelcomemsg' => 'Seiten, auf denen der Flysprayeinführungstext zu sehen ist',
+'pagesintromsg' => 'Seiten, auf denen der Projekteinführungstext zu sehen ist',
+'activeoauths' => 'zugelassene OAuth-Anbieter',
+'onlyoauthreg' => 'nur Anmeldung per OAuth erlauben',
+'estimatedeffortformat' => 'Anzeigeformat für geschätzten Arbeitsaufwand',
+'currenteffortdoneformat' => 'Anzeigeformat für erfolgten Arbeitsaufwand',
+'minute' => 'Minute',
+'minutes' => 'Minuten',
+'minuteplural' => 'Minuten',
+'minutesingular' => 'Minute',
+'minuteabbrev' => 'min',
+'hourplural' => 'Stunden',
+'hoursingular' => 'Stunde',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'geschätzter Arbeitsaufwand offener Aufgaben',
+'currenteffortdoneopen' => 'bisher erfolgter Arbeitsaufwand bei offenen Aufgaben',
+'signinwith' => 'Anmelden mit %s',
+'canviewroadmap' => 'darf Planung anschauen',
+'enableavatars' => 'Profilfotos aktivieren',
+'maxavatarsize' => 'maximale Profilfotobreite in Pixeln',
+'taskhassubtask' => 'Diese Aufgabe hat folgende Unteraufgabe',
+'taskhassubtasks' => 'Diese Aufgabe hat folgende Unteraufgaben',
+'translations' => 'Ãœbersetzungen',
+'translate' => 'Ãœbersetze',
+'taskdescription' => 'Aufgabenbeschreibung',
+'notaskdescription' => 'nicht vorhanden',
+'pleaseselect' => 'Bitte auswählen',
+'closeselectedtasks' => 'ausgewählte Aufgaben schließen',
+'closetasks' => 'Aufgaben schließen',
+'hintforbulkimport' => "<b>Hinweise zum Mehrfachimport</b>\n <ol>\n <li>Copy and paste from an excel spreadsheet or CSV by pasting one entire column.</li>\n <li>Currently you can only paste Summary and Details.</li>\n <li>Die Aufgabenzuweisung hat eine Autovervollständigenfunktion, einfach anfangen Namen einzutippen und Vorschläge werden angezeigt.</li>\n </ol>",
+'taskissubtaskof' => 'Die Aufgabe ist eine Unteraufgabe von',
+'applyfirstline' => 'Erste Zeile in alle darunter kopieren',
+'addmorerows' => 'weitere Zeilen einfügen',
+'addtasks' => 'Aufgaben eintragen',
+'massopsdisabled' => 'Die Massenbearbeitung von Aufgaben ist derzeit abgeschaltet für Flyspray 1.0. Diese Fähigkeit wird wieder freigeschaltet, sobald die Überarbeitung abgeschlossen ist.',
+'viewroadmap' => 'Planung anschauen',
+'nosuicide' => 'Sehr geehrter Nutzer, es ist nicht erlaubt Ihren Zugang zu Flyspray zu zerstören in dem Sie Ihr eigenes Konto deaktivieren oder Ihre Gruppe wechseln. The empathic brother of HAL9000',
+'movingtodifferentproject'=> 'Verschieben von einer Aufgabe zu einem anderen Projekt welche eine Ober- oder Unteraufgabe ist, ist nicht erlaubt. Sie müssen erst die Verbindung zwischen den Aufgaben trennen.',
+'musthavesameproject' => 'Ober- und Unteraufgabe müssen das gleiche Projekt haben.',
+'defaultorderby' => 'Aufgabenliste standardmäßig sortieren nach',
+'defaultorderby2' => '2. Sortierkriterium',
+'viewowntasks' => 'eigene Aufgaben anzeigen',
+'viewgroupstasks' => 'Gruppenaufgaben anzeigen',
+'urlrewriting' => 'hübsche URLs',
+'enablehtaccess' => 'Bitte aktivieren Sie Ihre .htaccess Datei im Flyspray Hauptverzeichnis bevor Sie "url rewriting" anwenden',
+'nomodrewrite' => 'Der "Mod rewrite" ist nicht auf Ihrem Server verfügbar, daher kann "url rewriting" nicht angewendet werden',
+'on' => 'aktiviert',
+'off' => 'deaktiviert',
+'defaultorderbydirection' => 'Standardmäßig sortiert als',
+'ascending' => 'aufsteigend',
+'descending' => 'absteigend',
+'myassignedtasks' => 'Meine Aufgaben',
+'commentedon' => 'schrieb am',
+'maxvoteperday' => 'Stimmen pro Tag',
+'votesperproject' => 'Stimmen pro Projekt',
+'votelimitreached' => 'Sie haben die maximale Anzahl an Stimmen erreicht. Wird eine Aufgabe, für die Sie gestimmt haben geschlossen, wird ihre Stimme freigegeben. Oder Sie ziehen auf ihrer Profilseite Stimmen zurück, um wieder freie Stimmen zu bekommen.',
+'myvotes' => 'Meine Stimmen',
+'tag' => 'Schildchen',
+'tags' => 'Schildchen',
+'tagsinfo' => 'Trenne die Schildchen (Label/Aufkleber/Tags) durch Semikolon ; voneinander. Eine optische Gestaltung, Filterung oder Einbeziehung in die Suche ist derzeit noch nicht möglich.',
+'novalues' => 'keine Einträge',
+'youhaveregistered' => 'Sie haben sich bei Flyspray registriert. Ihre Anmeldedaten sind folgende:',
+'youhaveregisterednotify' => 'Ihre Registrierung bei Flyspray wurde bestätigt.',
+'usedintasks' => 'Verwendung',
+'freetagging' => 'nutzerdefinierte Schildchen',
+'keyboardshortcuts' => 'Tastenkürzel',
+'testmailsettings' => 'Testet die aktuellen Email-Einstellungen',
+'test' => 'Testen',
+'testmailsettingsnotice' => 'Prüfen Sie anschließend auch, ob an das Email-Konto des aktuell eingeloggten Benutzers (siehe \'Meine Benutzerdaten\') eine Test-Email eingegangen ist',
+'invalidinput' => 'Es liegen ungültige Aufgaben-Eigenschaften vor, die korrigiert werden müssen, um die Aufgabe zu einem anderen Projekt verschieben zu können',
+'invalidprogress' => 'Bitte wählen Sie einen gültigen Eintrag für \'Prozent erledigt\' aus',
+'invalidpriority' => 'Bitte wählen Sie einen gültigen Eintrag für \'Priorität\' aus',
+'invalidseverity' => 'Bitte wählen Sie einen gültigen Eintrag für \'Schweregrad\' aus',
+'invalidstatus' => 'Bitte wählen Sie einen gültigen Eintrag für \'Status\' aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invalidcategory' => 'Bitte wählen Sie einen gültigen Eintrag für \'Kategorie\' aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invalidreportedversion' => 'Bitte wählen Sie einen gültigen Eintrag für \'betrifft Version\' aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invaliddueversion' => 'Bitte wählen Sie einen gültigen Eintrag für \'fällig in Version\' aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invalidos' => 'Bitte wählen Sie einen gültigen Eintrag für \'Betriebssystem\' aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invalidtags' => 'Bitte wählen Sie nur gültige Schildchen aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'invalidassignees' => 'Bitte wählen Sie nur gültige Zuständige aus, um die Aufgabe zu einem anderen Projekt zu verschieben',
+'customize' => 'anpassen',
+'hidesubs' => 'verstecke Unteraufgaben',
+'hideprivate' => 'verstecke private Aufgaben',
+'hideclosed' => 'verstecke geschlossene Aufgaben',
+'currentproject' => 'aktuelles Projekt',
+'targetproject' => 'Zielprojekt',
+'availablekeybshortcuts' => 'verfügbare Tastenkürzel',
+'logindialoglogout' => 'Login-Dialog / Logout',
+'focustaskidsearch' => 'fokussiere \'Zeige Aufgabe #\'',
+'openselectedtask' => 'öffne ausgewählte Aufgabe',
+'movecursorup' => 'bewege Cursor nach oben',
+'movecursordown' => 'bewege Cursor nach unten',
+'taskdetails' => 'Aufgabendetails',
+'taskediting' => 'Aufgabenbearbeitung',
+'savetask' => 'speichere Aufgabe',
+'generalintegration' => 'Integration allgemein',
+'generalintegrationdesc' => 'Der Inhalt wird innerhalb des "body"-Tags nach dem Hauptinhalt und vor dem Fußbereich eingesetzt. Wo dieser Inhalt dann wirklich auf dem Display zu sehen ist, ist durch CSS beeinflußbar. Sie können dafür eine custom_*.css nutzen.',
+'footerintegration' => 'Integration Fußbereich',
+'footerintegrationdesc' => 'Der Inhalt wird innerhalb des Fußbereiches eingesetzt. Wo dieser Inhalt dann wirklich auf dem Display zu sehen ist, ist durch CSS beeinflußbar. Sie können dafür eine custom_*.css nutzen.',
+'editorbold' => 'f',
+'editorboldhint' => 'fett',
+'editoritalic' => 'k',
+'editoritalichint' => 'kursiv',
+'editorunderline' => 'u',
+'editorunderlinehint' => 'unterstrichen',
+'editorstrikethrough' => 'd',
+'editorstrikethroughhint' => 'durchgestrichen',
+'captchaerror' => 'CAPTCHA nicht gelöst. Bitte erneut versuchen..',
+'registercaptcha' => 'Bitte löse das CAPTCHA-Rätsel.',
+'regcaptcha' => 'Zeige ein CAPTCHA-Rätsel bei der Nutzeregistrierung.',
+'invalidsecurimage' => 'ungültiges CAPTCHA',
+'invalidrecaptcha' => 'ungültiges Google reCAPTCHA',
+'antispam' => 'Spamschutz',
+'antispamprefsinfo' => 'Einstellungen zum Schutz vor automatisiertem Werbemüll.',
+'securimageprefsinfo' => 'Securimage ist ein klassisches CAPTCHA bei dem Nutzer eine Textfolge erkennen und eingeben müssen um ihr Menschsein zu "beweisen".',
+'securimageenable' => 'Securimage aktivieren',
+'recaptchaprefsinfo' => 'Google reCAPTCHA ist eine Alternative zum klassischen CAPTCHA.
+Google entscheidet anhand von gesammelten Daten und Nutzerverhalten, ob es sich um einen menschlichen Nutzer handelt.
+Um reCAPTCHA für Flyspray zu nutzen, wird ein Google Account und ein für die Domain konfiguriertes reCAPTCHA ( https://www.google.com/recaptcha ) benötigt.
+Die zwei Werte "sitekey" and "secret" müssen hier dann eingetragen werden.
+Abhängig von der Konfiguration bei Google - "reCAPTCHA V2" oder "Invisible reCAPTCHA" - müssen Ihre Nutzer eventuell darüber bei der Nutzerregistrierung und/oder in der Datenschutzerklärung informiert werden.
+Flyspray lädt und verwendet es nur für die Seiten der Nutzerregistrierung, falls konfiguriert.',
+'recaptchaenable' => 'Google reCAPTCHA aktivieren',
+'adminchecks' => 'Checks',
+'adminchecksinfo' => 'Info and checks for your current installation',
+'repeatpassword' => 'Passwort wiederholen',
+'repeatemailaddress' => 'E-Mail-Adresse wiederholen',
+'tooltipshorttasktitle' => 'Bitte eine kurze, knackige Zusammenfassung der Aufgabe angeben.',
+'lastlogin' => 'letzte Anmeldung',
+);
+?>
diff --git a/lang/dk.php b/lang/dk.php
new file mode 100644
index 0000000..881a757
--- /dev/null
+++ b/lang/dk.php
@@ -0,0 +1,809 @@
+<?php
+//
+// This file is auto generated with .langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the .langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Rediger bruger',
+'username' => 'Brugernavn',
+'realname' => 'Fulde navn',
+'emailaddress' => 'Email adresse',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Meddelelsestype',
+'group' => 'Gruppe',
+'accountenabled' => 'Konto aktiv',
+'updatedetails' => 'Gem',
+'setglobally' => 'Denne preference er sat globalt',
+'usergroupmanage' => 'Bruger og gruppe administration',
+'newuser' => 'Opret ny bruger',
+'newgroup' => 'Opret ny gruppe',
+'yes' => 'Ja',
+'no' => 'Nej',
+'editgroup' => 'Ret gruppe',
+'groupname' => 'Gruppenavn',
+'description' => 'Beskrivelse',
+'admin' => 'Administrator gruppe',
+'opennewtasks' => 'Ã…bn nye opgaver',
+'modifytasks' => 'Rediger opgaver',
+'addcomments' => 'Tilføj kommentar',
+'attachfiles' => 'Vedhæft fil',
+'vote' => 'Stem',
+'groupenabled' => 'Medlemmer kan logge ind',
+'groupopen' => 'Medlemmer kan logge ind',
+'tasktypelist' => 'Opgave typer',
+'categorylist' => 'Kategorier',
+'oslist' => 'Operativsystemer',
+'resolutionlist' => 'Erklæringsliste',
+'versionlist' => 'Versions liste',
+'severitylist' => 'Vigtighedsliste',
+'listnote' => 'NB! Fjernelse af markeringen "vis" kan ændre opgaver ved redigering. Ændring af navne feltet vil ændre alle opgaver med det navn. Enheder der ikke kan slettes, er beskyttede fordi de bruges andre steder i systemet.',
+'name' => 'Navn',
+'order' => 'Sortering',
+'back' => 'Tilbage',
+'text' => 'Tekst',
+'highlight' => 'Fremhæv',
+'show' => 'Vis',
+'owner' => 'Ejer',
+'selectowner' => 'Vælg ejer',
+'update' => 'Opdater',
+'addnew' => 'Tilføj ny',
+'flysprayprefs' => 'Flyspray indstillinger',
+'projecttitle' => 'Projekt titel',
+'baseurl' => 'URL for denne installation',
+'replyaddress' => 'Email-fra adresse ved meddelelser',
+'themestyle' => 'Tema',
+'language' => 'Sprog',
+'anonview' => 'Tillad anonyme brugere at se denne opgave',
+'allowanon' => 'Tillad anonyme brugere at oprette nye opgaver',
+'never' => 'Aldrig',
+'anonymously' => 'Anonymt',
+'afterregister' => 'Kun efter registrering',
+'spamproof' => 'Aktiver bekræftelseskode for nye brugere',
+'anongroup' => 'Standard gruppe for nye brugere',
+'groupassigned' => 'Medlemmer i disse grupper kan tildeles opgaver',
+'forcenotify' => 'Tving opgave meddelelser som',
+'neversend' => 'Send aldrig',
+'userchoose' => 'Lad hver bruger selv vælge',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Standard kategori ejer',
+'noone' => 'Ingen',
+'jabbernotify' => 'Jabber meddelelser',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Brugernavn',
+'jabberpass' => 'Kodeord',
+'saveoptions' => 'Gem',
+'editcomment' => 'Rediger kommentar',
+'commentby' => 'Kommenteret af',
+'saveeditedcomment' => 'Gem ændringer',
+'projectprefs' => 'Projekt indstillinger',
+'pagetitle' => 'Side titel',
+'defaultproject' => 'Standard projekt',
+'projectlists' => 'Projekter',
+'showlogo' => 'Vis titel-logo billede',
+'intromessage' => 'Introduktions besked',
+'isactive' => 'Projektet er aktivt',
+'createproject' => 'Opret nyt projekt',
+'nopermission' => 'Du har ikke adgang til denne side',
+'listordertip' => 'Rækkefølgen som disse vil fremstå i listen',
+'listshowtip' => 'Vis denne i listen',
+'categoryownertip' => 'Denne person vil modtage besked når nye opgaver oprettes i denne kategori',
+'categoryparenttip' => 'Overordnet kategori til denne',
+'notsubcategory' => 'Ingen (øverste kategori)',
+'showinlineimages' => 'Vis vedhæftede billeder inline',
+'dateformat' => 'Datoformat',
+'dateformat_extended' => 'Langt datoformat',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'Ingen caching',
+'cache_disk' => 'Cache på disk',
+'cache_db' => 'Cache i databasen',
+'subcategoryof' => 'Underkategori af',
+'visiblecolumns' => 'Kolonner vist i opgavelisten',
+'tense' => 'Tid',
+'listtensetip' => 'Datid, nutid eller fremtid',
+'past' => 'Datid',
+'present' => 'Nutid',
+'future' => 'Fremtid',
+'oldpass' => 'Gammelt kodeord',
+'nooldpass' => 'Gammelt kodeord er ikke angivet',
+'oldpasswrong' => 'Gammelt kodeord er ikke korrekt',
+'changepass' => 'Skift kodeord',
+'confirmpass' => 'Bekræft kodeord',
+'projectmanager' => 'Projektansvarlig',
+'viewtasks' => 'Vis opgaver',
+'modifyowntasks' => 'Rediger egne opgaver',
+'modifyalltasks' => 'Rediger andres opgaver',
+'viewcomments' => 'Vis kommentarer',
+'editcomments' => 'Ret kommentarer',
+'deletecomments' => 'Slet kommentarer',
+'viewattachments' => 'Vis vedhæftede filer',
+'createattachments' => 'Vedhæft fil',
+'deleteattachments' => 'Slet vedhæftet fil',
+'viewhistory' => 'Vis tidligere (history)',
+'closeowntasks' => 'Luk egne opgaver',
+'closeothertasks' => 'Luk andres opgaver',
+'assigntoself' => 'Tildel opgaver til dig selv som ikke er tildelte til andre',
+'assignotherstoself' => 'Tildel andres opgaver til dig selv',
+'viewreports' => 'Vis log',
+'othersview' => 'Tillad alle at se dette projekt',
+'usersandgroups' => 'Brugere og grupper',
+'globalgroup' => 'Global gruppe',
+'globalgroups' => 'Globale grupper',
+'defaultglobalgroup' => 'Standard global gruppe for nye brugere',
+'addtogroup' => 'Tilføj til gruppe',
+'moveuserstogroup' => 'Flyt brugere til gruppe',
+'nogroup' => 'Ingen gruppe - Fjerne fra dette projekt',
+'eventdesc' => 'Event beskrivelse',
+'requestedby' => 'Anmodet af',
+'daterequested' => 'Anmodningsdato',
+'closetask' => 'Luk opgave',
+'reopentask' => 'Genåbn opgave',
+'applymember' => 'Anmod om projekt medlemsskab',
+'forcurrentproj' => 'Til dette projekt',
+'lostpw' => 'Glemt kodeord',
+'lostpwexplain' => 'Skriv dit brugernavn, så sender vi et link til den Email der er angivet i din profil.',
+'sendlink' => 'Send',
+'savenewpass' => 'Gem nyt kodeord',
+'anonreg' => 'Tillad oprettelse af nye brugere',
+'allowanonopentask' => 'Tillad anonyme brugere at oprette opgaver',
+'editglobalgroup' => 'Rediger global gruppe',
+'editgroupforproj' => 'Rediger gruppe til projekt ???',
+'notshownforadmin' => 'Rettigeheder vises ikke for administratorgruppen, da de ikke skal redigeres.',
+'general' => 'Generelt',
+'userregistration' => 'Opret bruger',
+'notifications' => 'Meddelelser',
+'resetoptions' => 'Nulstil',
+'preferences' => 'Indstillinger',
+'tasktypes' => 'Opgavetyper',
+'resolutions' => 'Erklæringer',
+'categories' => 'Kategorier',
+'operatingsystems' => 'Operativsystemer',
+'versions' => 'Versioner',
+'admintoolboxlong' => 'Admin værktøjskasse',
+'newproject' => 'Nyt projekt',
+'delete' => 'Slet',
+'listdeletetip' => 'Slet fra denne liste',
+'lookandfeel' => 'Udseende',
+'globaltheme' => 'Globalt tema',
+'emailnotify' => 'Email meddelelser',
+'fromaddress' => 'Fra adresse',
+'smtpserver' => 'SMTP server',
+'smtpuser' => 'SMTP brugernavn',
+'smtppass' => 'SMTP kodeord',
+'addrewrite' => 'Brug "URL rewriting"',
+'usereminderdaemon' => 'Aktiver påmindelser',
+'tasksperpage' => 'Antal opgaver pr side',
+'addtoassignees' => 'Tilføj dig selv til modtagere',
+'taskstatuses' => 'Opgavestatus',
+'canvote' => 'Kan stemme for opgaver',
+'loginsuccessful' => 'Du er logget ind',
+'youareloggedout' => 'Du er logget af',
+'waitwhiletransfer' => 'Vent venligst mens du viderestilles',
+'clicknowait' => 'Klik her hvis du ikke ønsker at vente',
+'accountdisabled' => 'Din konto er lukket! Kontakt administrator.',
+'task' => 'Opgave',
+'edittask' => 'Rediger denne opgave',
+'openedby' => 'Oprettet af',
+'editedby' => 'Sidst rettet af',
+'tasktype' => 'Opgavetype',
+'category' => 'Kategori',
+'status' => 'Status',
+'assignedto' => 'Tildelt til',
+'operatingsystem' => 'Operativsystem',
+'severity' => 'Vigtighed',
+'reportedversion' => 'Rapporteret version',
+'dueinversion' => 'Forfalder i version',
+'undecided' => 'Ubesluttet',
+'percentcomplete' => 'Procent færdig',
+'details' => 'Detaljer',
+'savedetails' => 'Gem',
+'canceledit' => 'Fortryd',
+'anonymous' => 'Anonym afsender',
+'complete' => 'Færdig',
+'closedby' => 'Lukket af',
+'reasonforclosing' => 'Ã…rsag til lukning',
+'reopenthistask' => 'Genåbn denne opgave',
+'comments' => 'Kommentarer',
+'attachments' => 'Vedhæftede filer',
+'relatedtasks' => 'Relaterede opgaver',
+'edit' => 'Rediger',
+'addcomment' => 'Tilføj kommentar',
+'fileuploadedby' => 'Fil tilføjet af',
+'uploadafile' => 'Vedhæft en fil',
+'uploadnow' => 'Upload nu!',
+'thesearerelated' => 'Opgaver relateret til denne opgave',
+'remove' => 'Fjern',
+'addnewrelated' => 'Tilføj ny relateret opgave',
+'add' => 'Tilføj',
+'otherrelated' => 'Andre opgaver som denne er relateret til',
+'receivenotify' => 'Disse brugere vil modtage detaljerede meddelelser når denne opgave opdateres',
+'addusertolist' => 'Tilføj bruger til denne liste',
+'addtolist' => 'Tilføj til liste',
+'addmyself' => 'Tilføj mig selv til denne liste',
+'removemyself' => 'Fjern mig selv fra denne liste',
+'theseusersnotify' => 'Disse brugere vil modtage detaljerede meddelelser hver gang denne opgave opdateres',
+'attachedtoproject' => 'Tilknyttet til projekt',
+'reminders' => 'PÃ¥mindelser',
+'system' => 'System',
+'remindthisuser' => 'PÃ¥mind denne bruger',
+'thisoften' => 'SÃ¥ tit (this often)',
+'startafter' => 'Vent før påmindelser startes (Wait before starting reminders)',
+'hours' => 'Time(r)',
+'days' => 'Dag(e)',
+'weeks' => 'Uge(r)',
+'addreminder' => 'Tilføj påmindelse',
+'defaultreminder' => 'Dette er en påmindelse om at se på følgende Flyspray opgave:',
+'message' => 'Besked',
+'closed' => 'Lukket',
+'filename' => 'Filnavn',
+'date' => 'Dato',
+'filesize' => 'Fil størrelse:',
+'closurecomment' => 'Yderligere kommentarer vedr. lukning:',
+'history' => 'History ???',
+'nohistory' => 'Ingen (history) tilgængelig',
+'eventdate' => 'Dato',
+'user' => 'Bruger',
+'event' => 'Aktivitet',
+'fieldchanged' => 'Fil ændret',
+'taskopened' => 'Opgave åbnet',
+'taskreopened' => 'Opgave genåbnet',
+'taskclosed' => 'Opgave lukket',
+'commentadded' => 'Kommentar tilføjet',
+'commentedited' => 'Kommentar redigeret',
+'commentdeleted' => 'Kommentar slettet',
+'attachmentadded' => 'Fil tilføjet',
+'attachmentdeleted' => 'Fil slettet',
+'taskedited' => 'Opgavebeskrivelse redigeret',
+'notificationadded' => 'Bruger tilføjet til meddelelsesliste',
+'notificationdeleted' => 'Bruger slettet fra meddelelsesliste',
+'relatedadded' => 'Relateret opgave tilføjet',
+'relateddeleted' => 'Relateret opgave fjernet',
+'taskassigned' => 'Opgave tildelt til',
+'taskreassigned' => 'Opgave gen-tildelt til',
+'assignmentremoved' => 'Tildeling flyttet',
+'summary' => 'Resumé',
+'addedasrelated' => 'Opgave tilføjet til relateret liste af',
+'deletedasrelated' => 'Opgave lettet fra relateret liste af',
+'reminderadded' => 'PÃ¥mindelse aktiveret',
+'reminderdeleted' => 'PÃ¥mindelse slettet',
+'priority' => 'Prioritet',
+'previousvalue' => 'Før',
+'newvalue' => 'Nu',
+'selectareason' => 'Vælg en årsag',
+'assigntome' => 'Tildel til mig',
+'reopenrequest' => 'Anmod om genåbning',
+'requestclose' => 'Anmod om lukning',
+'ownershiptaken' => 'Bruger tog ejerskab',
+'closerequestmade' => 'Anmodet opgave lukket',
+'reopenrequestmade' => 'Anmodet opgave genåbnet',
+'taskdependson' => 'Denne opgave er sammenkædet med',
+'taskblocks' => 'Denne opgave blokerer disse fra lukning',
+'depadded' => 'Sammenkædning tilføjet',
+'depaddedother' => 'Denne opgave er sammenkædet med',
+'depremoved' => 'Sammenkædning fjernet',
+'depremovedother' => 'Denne opgave er flyttet fra anden opgaves sammenkædningsliste',
+'showdetailserror' => 'Denne opgave er enten slettet eller du har ikke rettigheder til at se den.',
+'makeprivate' => 'Privat',
+'makepublic' => 'Offentlig',
+'taskmadeprivate' => 'Denne opgave er privat',
+'taskmadepublic' => 'Denne opgave er offentlig',
+'confirmdeletecomment' => "Er du sikker på at du vil slette denne kommentar ? %s",
+'confirmdeleteattach' => 'Er du sikker på at du vil slette denne fil ?',
+'selectedhistory' => 'Log detaljer',
+'showallhistory' => 'Vis fuld log (tab) igen',
+'hidethis' => 'Skjul dette område',
+'mark100' => 'Marker opgave 100% færdig',
+'watchtask' => 'Overvåg opgave',
+'stopwatching' => 'Stop overvågning',
+'commentlink' => 'Link til denne kommentar',
+'submitreq' => 'Send anmodning',
+'reasonforreq' => 'Ã…rsag for anmodning',
+'pmreqdenied' => 'En projektansvarlig afviste anmodning',
+'taskpendingreq' => 'Afventer projektansvarlig. Se log for detaljer.',
+'previoustask' => 'Forrige opgave',
+'nexttask' => 'Næste opgave',
+'duedate' => 'Forfalds dato',
+'attachnoperms' => 'Der er vedhæftede filer til denne kommentar, men du har ikke rettigheder til at se dem.',
+'open' => 'Ã…bne',
+'depgraph' => 'Vis sammenkædnings graf',
+'reset' => 'Nulstil',
+'selectusers' => 'Vælg brugere',
+'addmetoassignees' => 'Tilføj mig til modtagere',
+'addedtoassignees' => 'Bruger tilføjet til modtagerliste',
+'dependencygraph' => 'Sammenkædnings graf',
+'attachanotherfile' => 'Vedhæft yderligere fil(er)',
+'OK' => 'OK',
+'addvote' => 'Tilføj stemme',
+'notifyfromfs' => 'Meddelelse fra Flyspray',
+'autogenerated' => 'DETTE ER EN AUTOMATISK GENERERET MEDDELELSE SOM IKKE KAN BESVARES!!!',
+'forward' => 'Næste (forward)',
+'previous' => 'Forrige',
+'next' => 'Næste',
+'first' => 'Første',
+'last' => 'Sidste',
+'page' => 'Side %d af %d',
+'search' => 'Søg',
+'alltasktypes' => 'Alle opgave typer',
+'allseverities' => 'Alle vigtigheder',
+'alldevelopers' => 'Alle udviklere',
+'notyetassigned' => 'Endnu ikke tildelte',
+'allcategories' => 'Alle kategorier',
+'allstatuses' => 'Alle statusser',
+'allopentasks' => 'Alle åbne opgaver',
+'sortthiscolumn' => 'Sorter efter denne kolonne',
+'id' => 'ID',
+'project' => 'Projekt',
+'dateopened' => 'Oprettet den',
+'progress' => 'Forløb',
+'searchthisproject' => 'Søg i dette projekt',
+'dueanyversion' => 'Forfald i hvilken som helst version',
+'anyversion' => 'Rapporteret i hvilken som helst version',
+'dueversion' => 'Forfald i version',
+'lastedit' => 'Sidst opdateret',
+'os' => 'Operativsystem',
+'reportedin' => 'Rapporteret i',
+'taskrange' => 'Viser opgaver %d - %d af %d',
+'noresults' => 'Din søgning gav ingen resultater',
+'takeaction' => 'Skrid til handling (Take action)',
+'watchtasks' => 'Overvåg valgte opgaver',
+'stopwatchingtasks' => 'Stop overvågning af valgte opgaver',
+'assigntaskstome' => 'Tildel valgte opgaver til mig',
+'dueby' => 'Til forfald den',
+'dueanytime' => 'Til forfald lige om straks',
+'selectduedate' => 'Vælg forfaldsdato',
+'toggleselected' => 'Skift mellem valgte (inverter ???)',
+'due' => 'Forfald',
+'assignedtome' => 'Tildelte til mig selv',
+'tasklist' => 'Opgaveliste',
+'dateclosed' => 'Lukket dato',
+'advanced' => 'Avanceret',
+'searchcomments' => 'Søg i kommentarer',
+'searchforall' => 'Søg efter alle ord',
+'anonusers' => 'Anonyme brugere',
+'miscellaneous' => 'Diverse',
+'users' => 'Brugere',
+'taskproperties' => 'Opgave indstillinger',
+'selectsincedate' => 'Vælg Redigeret siden',
+'changedsince' => 'Ændret siden',
+'updatefs' => 'Venligst opdater Flyspray.',
+'currentversion' => 'Din nuværende version er',
+'latestversion' => 'og den nyeste version er',
+'hidemessage' => '(udsæt til senere)',
+'saveas' => 'Gem søgning som',
+'nosearches' => 'Ingen gemte søgninger',
+'saving' => 'Gemmer...',
+'votes' => 'Stemmer',
+'allclosedtasks' => 'Alle lukkede opgaver',
+'password' => 'Kodeord',
+'login' => 'Log ind',
+'rememberme' => 'Husk mig',
+'lostpassword' => 'Glemt kodeord ?',
+'lostpwforfs' => 'Glemt kodeord til Flyspray ?',
+'lostpwmsg1' => "Hej\n\nJeg har glemt mit kodeord til Flyspray på (on) ??? _",
+'lostpwmsg2' => ", vil I venligst lave et nyt kodeord til mig.\n\nBrugernavn: _",
+'regards' => "\nMed venlig hilsen",
+'yourusername' => '_ dit brugernavn _',
+'locale' => 'da-DK',
+'filenotexist' => 'Filen eksisterer ikke eller du har ikke rettigheder til at se den.',
+'showtask' => 'Vis opgave',
+'now' => 'Nu',
+'go' => 'GÃ¥ til (Go) ???',
+'opentaskanon' => 'Ã…bn ny opgave anonymt',
+'register' => 'Opret (register) ???',
+'addnewtask' => 'Tilføj ny opgave',
+'reports' => 'Aktivitetslog',
+'editmydetails' => 'Rediger mine oplysninger',
+'logout' => 'Log af',
+'disabledaccount' => 'Din konto er lukket!<br />Du logges af...',
+'poweredby' => 'Powered by Flyspray',
+'projects' => 'Projekter',
+'allprojects' => 'Alle projekter',
+'selectproject' => 'til projekt:',
+'tasksall' => 'Alle opgaver',
+'tasksassigned' => 'Opgaver tildelt mig',
+'tasksreported' => 'Opgaver oprettet af mig',
+'taskswatched' => 'Opgaver jeg overvåger',
+'mysearch' => 'Mine søgninger',
+'admintoolbox' => 'Admin værktøjskasse',
+'manageproject' => 'Administrer projekt',
+'permissions' => 'Rettigheder',
+'hide' => 'Skjul',
+'pendingreq' => 'PM anmodning venter',
+'errorpage' => "Flyspray kunne ikke finde siden.\nDette kan hænde hvis du har forsøgt at få adgang til en opgave der ikke længere eksisterer, eller hvis du ikke har de nødvendige rettigheder.",
+'permissionsforproject' => 'Rettigheder til _',
+'switchto' => 'Skift til',
+'lastsearch' => 'Sidste søgning',
+'modify' => 'Rediger',
+'noticefrom' => 'Besked fra',
+'hasopened' => 'har oprettet en ny opgave og tildelt den til dig:',
+'moreinfonew' => 'Du kan finde mere information om denne fejl på Flyspray siden:',
+'newtaskcategory' => 'Der er oprettet en ny Flyspray opgave i denne kategori',
+'categoryowner' => 'Du får denne besked fordi du er listet som ejer af kategorien.',
+'tasksummary' => 'Opgave resumé',
+'newtaskadded' => 'Din opgave er tilføjet.',
+'summaryanddetails' => 'Du skal skrive både resumé og beskrivelse.',
+'goback' => 'Tilbage.',
+'messagefrom' => 'Dette er en besked fra Flyspray på _',
+'hasjustmodified' => 'har lige redigeret nedenstående opgave.',
+'changedfields' => 'Ændrede felter er markeret med stjerner (**)',
+'moreinfomodify' => 'Du kan få mere information om denne opgave på adressen:',
+'nolongerassigned' => 'Nedenstående opgave er ikke længere tildelt dig, men er nu tildelt til',
+'hasassigned' => 'har tildelt følgende Flyspray opgave til dig:',
+'taskupdated' => 'Opgave opdateret.',
+'hasclosedassigned' => 'har lukket følgende Flyspray opgave som var tildelt dig:',
+'unassigned' => 'Ikke tildelt',
+'hasclosed' => 'har lukket nedenstående opgave.',
+'youonnotify' => 'Du modtager dette fordi du er skrevet på meddelelses listen',
+'taskclosedmsg' => 'Opgave lukket.',
+'returntotask' => 'GÃ¥ tilbage til opgavebeskrivelsen.',
+'backtoindex' => 'GÃ¥ tilbage til opgavelisten.',
+'noclosereason' => 'Du har ikke valgt en årsag til lukning af denne opgave.',
+'hasreopened' => 'har genåbnet følgende Flyspray opgave som du lukkede:',
+'taskreopenedmsg' => 'Opgave genåbnet.',
+'backtotask' => 'GÃ¥ tilbage til opgaven.',
+'commentaddedmsg' => 'Kommentar tilføjet.',
+'commenttoassigned' => 'har tilføjet en kommentar til en opgave som er tildelt dig:',
+'commenttotask' => 'har tilføjet nedenstående kommentar til denne opgave.',
+'nocommententered' => 'Du bør tilføje en kommentar.',
+'fillinfields' => 'Du har ikke udfyldt alle felter.',
+'notcurrentpass' => 'Dette er ikke dit nuværende kodeord!',
+'passchanged' => 'Dit kodeord er ændret.',
+'closewindow' => 'Du kan nu lukke dette vindue.',
+'passnomatch' => 'Det nye kodeord er ikke skrevet ens begge gange!',
+'usernametaken' => 'Dette brugernavn er allerede i brug. Du skal vælge et andet.',
+'newusercreated' => 'Ny bruger konto er oprettet',
+'accountcreated' => 'Din bruger konto er oprettet.',
+'newuserwarning' => 'Bemærk at det kan være at din bruger konto skal godkendes af en administrator. Hvis du ikke kan logge ind med det samme, kan dette evt. være årsagen.',
+'nomatchpass' => 'Kodeord stemmer ikke overens.',
+'confirmwrong' => 'Bekræftelseskode er forkert!',
+'formnotcomplete' => 'Formularen er ikke udfyldt korrekt.',
+'formnotnumeric' => 'De indtastede data er ikke numeriske!',
+'groupnametaken' => 'Dette gruppenavn er allerede i brug.',
+'newgroupadded' => 'Ny gruppe oprettet.',
+'optionssaved' => 'Flyspray indstillinger er gemt.',
+'hasuploaded' => 'har vedhæftet en fil til en opgave som er tildelt dig:',
+'hasattached' => 'har vedhæftet en fil til nedenstående opgave.',
+'fileuploaded' => 'Filen er vedhæftet.',
+'fileerror' => 'Der skete en fejl ved upload af filen. Dette kan hænde hvis rettighederne på <i>attachments/</i> mappen ikke er sat rigtigt.',
+'contactadmin' => 'Kontakt administratoren af dette projekt.',
+'selectfileerror' => 'Du har ikke valgt en fil.',
+'userupdated' => 'Brugeroplysninger er opdaterede.',
+'realandemail' => 'Du har ikke udfyldt felterne til din Email og dit fulde navn.',
+'groupupdated' => 'Gruppedefinition opdateret.',
+'groupanddesc' => 'Du har ikke skrevet et gruppenavn.',
+'fillallfields' => 'Du skal ydfylde alle felter.',
+'listPmustN' => '"Sortering" skal være et heltal.',
+'listupdated' => 'Listen er opdateret.',
+'listitemadded' => 'Nyt emne til listen er tilføjet.',
+'relatedaddedmsg' => 'Relateret opgave er tilføjet til listen.',
+'relatederror' => 'Denne opgave er allerede på den relaterede opgaveliste.',
+'relatedremoved' => 'Relateret opgave slettet fra listen.',
+'notifyadded' => 'Bruger tilføjet til påmindelsesliste.',
+'notifyerror' => 'Denne bruger er allerede på listen for denne opgave.',
+'notifyremoved' => 'Bruger slettet fra liste.',
+'editcommentsaved' => 'Opdateret kommentar gemt.',
+'commentdeletedmsg' => 'Kommentar slettet.',
+'gotonewtask' => 'GÃ¥ til den nye opgave som du lige har oprettet.',
+'projectcreated' => 'Dit nye projekt er nu oprettet og du kan begynde at administrere det.',
+'customiseproject' => 'Administrer dette projekt',
+'projectupdated' => 'Projekt indstillinger opdaterede',
+'emptytitle' => 'Du har ikke indtastet en titel til projektet. GÃ¥ tilbage og ret.',
+'loginbelow' => 'Du kan nu prøve at logg ind.',
+'attachmentdeletedmsg' => 'Filen er slettet.',
+'reminderaddedmsg' => 'Påmindelsen er tilføjet.',
+'reminderdeletedmsg' => 'Den valgte påmindelse er slettet.',
+'flyspraytask' => 'Flyspray opgave',
+'fieldsmissing' => 'Et eller flere felter indeholder forkerte eller ingen data.',
+'relatedinvalid' => 'Den opgave findes ikke.',
+'relatedproject' => 'Denne opgave er tilknyttet et andet projekt. Opret relation alligevel ?',
+'addanyway' => 'Tilføj alligevel ?',
+'cancel' => 'Annuller',
+'alreadyedited' => "Denne opgave er blevet opdateret af en anden inden du gemte dine ændringer. Er du sikker på at du vil gemme dine ændringer ?\nDette kan medføre at den andens ændringer overskrives!",
+'saveanyway' => 'Gem mine ændringer',
+'nouserselected' => 'Ingen bruger valgt. Vælg mindst en før du forsøger igen.',
+'groupswitchupdated' => 'Brugergrupper modificeret.',
+'takenownershipmsg' => 'Denne opgave er nu tildelt dig.',
+'adminrequestmade' => 'Din anmodning er nu sendt til den projektansvarlige.',
+'newdepnotify' => 'En ny sammenkædning er tilføjet opgaven:',
+'dependadded' => 'Opgavesammenkædning er tilføjet.',
+'dependaddfailed' => 'Der skete en fejl ved sammenkædningen. Check at der ikke er nogen gensidige blokeringer.',
+'depremovedmsg' => 'Opgavesammenkædning er fjernet.',
+'newdepis' => 'Den nye sammenkædning er',
+'magicurlsent' => 'En besked er sendt til din Email. Den indeholder et link til en side hvor du kan færdiggøre denne handling.',
+'changefspass' => 'Skift Flyspray kodeord',
+'magicurlmessage' => 'Brug nedenstående link for at skifte dit Flyspray kodeord:',
+'erroronform' => 'Der var et problem med udfyldelsen af formularen.',
+'addressused' => 'Denne adresse er allerede brugt på en anden konto. Du kan vælge at ignorere denne besked og benytte nedenstående link til at færdiggøre oprettelsen, eller du kan forsøge igen med en ny.',
+'confirmcodeis' => 'Din bekræftelseskode er:',
+'codesent' => 'Din bekræftelseskode er afsendt. Følg instruktionerne i meddelelsen.',
+'codenotsent' => 'Din bekræftelseskode kunne ikke sendes på nuværende tidspunkt. Prøv igen lidt senere.',
+'taskmadeprivatemsg' => 'Denne opgave er privat.',
+'taskmadepublicmsg' => 'Denne opgave er offentlig igen.',
+'realandnotify' => 'Du skal skrive dit fulde navn og enten din Email adresse eller dit Jabber ID.',
+'pmreqdeniedmsg' => 'En projektansvarlig anmodning afvist.',
+'massopsuccess' => 'Alle operationer er gennemført hvor rettighederne har tilladt dette.',
+'usernotexist' => 'Denne bruger eksisterer ikke i denne installation.',
+'commentattachperms' => 'Du kan ikke slette denne kommentar, da du ikke har rettigheder til at slette de vedhæftede filer.',
+'voterecorded' => 'Din stemme er registreret.',
+'votefailed' => 'Din stemme kunne ikke registreres på nuværende tidspunkt.',
+'createnewgroup' => 'Opret ny gruppe',
+'requiredfields' => 'Felter der skal udfyldes er markeret med',
+'addthisgroup' => 'Tilføj denne gruppe',
+'createnewproject' => 'Opret nyt projekt',
+'addnewproject' => 'Tilføj nyt projekt',
+'htmlallowed' => 'HTML kode tilladt',
+'createthisproject' => 'Opret dette projekt',
+'inlineimages' => 'Vis billeder inline',
+'createnewtask' => 'Opret ny opgave i projekt:',
+'addanother' => 'Tilføj ny opgave efter denne',
+'addthistask' => 'Tilføj denne opgave',
+'notifyme' => 'Giv mig besked når denne opgave ændres.',
+'newtask' => 'Ny opgave',
+'attachafile' => 'Vedhæft en fil',
+'registernewuser' => 'Opret ny bruger',
+'none' => 'Ingen',
+'registeraccount' => 'Opret denne konto',
+'both' => 'Begge',
+'notifyfrom' => 'Besked fra _',
+'donotreply' => 'DETTE ER EN AUTOMATISK GENERERET MEDDELELSE SOM IKKE KAN BESVARES!!!',
+'disclaimer' => 'Du modtager denne besked fordi du er tilmeldt Flyspray opgave system. Hvis du ikke vil modtage flere beskeder i fremtiden, kan du ændre dine indstillinger ved at følge adressen herover.',
+'userwho' => 'Bruger som gjorde dette',
+'moreinfo' => 'Der findes mere information på adressen:',
+'newtaskopened' => 'En ny Flyspray opgave er oprettet. Se flere detaljer herunder.',
+'notify.taskclosed' => 'Følgende opgave er blevet lukket:',
+'notify.taskreopened' => 'Følgende opgave er blevet genåbnet:',
+'newdep' => 'Der er tilføjet en sammenkædning til opgaven:',
+'notify.depremoved' => 'Der er fjernet en sammenkædning fra opgaven:',
+'olddepwas' => 'Den forhenværende sammenkædning var',
+'notify.commentadded' => 'Der er tilføjet en kommentar til opgaven:',
+'commentis' => 'Kommentaren er skrevet herunder.',
+'newattachment' => 'En ny fil er vedhæftet følgende opgave:',
+'detailsbelow' => 'Se detaljer herunder.',
+'notify.relatedadded' => 'En ny relateret opgave er tilføjet opgaven:',
+'relatedis' => 'Den relaterede opgave er',
+'assignedtoyou' => 'Du er blevet tildelt opgaven:',
+'takenownership' => 'har taget ejerskab over opgaven:',
+'requiresaction' => 'Der mangler en handling fra en projektansvarlig på opgaven:',
+'requiresactionnotify' => 'Opgaven mangler handling fra en projektansvarlig',
+'pmdeny' => 'En projektansvarlig har afvist den afventende anmodning på opgaven:',
+'pmdenynotify' => 'En projektansvarlig har afvist anmodningen',
+'fileaddedtoo' => 'En eller flere filer er vedhæftet.',
+'taskwatching' => 'Opgaven som du overvåger',
+'isdepfor' => 'er en sammenkædning til',
+'denialreason' => 'Ã…rsag til afvisning',
+'taskchanged' => 'Der er foretaget ændringer på nedenstående opgave. Ændringerne er listet herunder. For mere information, følg adressen og klik på Log.',
+'useraddedtoassignees' => 'En bruger har tildelt sig selv denne opgave.',
+'removeddepis' => 'Den fjernede sammenkædning er',
+'isnodepfor' => 'er ikke længere en sammenkædning for',
+'usergroups' => 'Brugergrupper',
+'pmtoolbox' => 'Projektansvarliges værktøjskasse',
+'groupmanage' => 'Gruppe administration',
+'pendingrequests' => 'Ventende anmodninger',
+'reasongiven' => 'Ã…rsag',
+'nopendingreq' => 'Der er ingen ventende projektansvarlige anmodninger.',
+'givereason' => 'Skriv en årsag',
+'catlisted' => 'Kategori-liste editor',
+'oslisted' => 'Operativsystem-liste editor',
+'verlisted' => 'Version-liste editor',
+'tasktypeed' => 'Opgavetype-liste editor',
+'resed' => 'Erklæringsliste editor',
+'deny' => 'Afvis',
+'notifiedwhen' => 'Besked, når ???',
+'onlynewtasks' => 'Nye opgaver er oprettet',
+'allevents' => 'Ved enhver aktivitet i enhver opgave ???',
+'feeds' => 'Newsfeed',
+'feeddescription' => 'Newsfeed beskrivelse',
+'feedimgurl' => 'Newsfeed billede URL',
+'notifysubject' => 'Emne ved beskeder',
+'notifysubjectinfo' => '(%p = projekt titel, %s = opgave resumé, %t = opgave id, %a = aktivitet, %u = bruger)',
+'priority6' => 'Flash',
+'priority5' => 'Omgående',
+'priority4' => 'Vigtig',
+'priority3' => 'Høj',
+'priority2' => 'Normal',
+'priority1' => 'Lav',
+'sendcode' => 'Send kode',
+'entercode' => 'Skriv bekræftelseskoden som du fik i din Email, samt det ønskede kodeord til din brugerkonto.',
+'confirmationcode' => 'Bekræftelseskode',
+'registererror' => 'Du skal udfylde alle påkrævede felter korrekt, samt sørge for at vælge hvorledes du vil modtage beskeder fra systemet. ',
+'validusername' => '(kun alphanumeriske tegn og -_. er tilladte)',
+'emailtaken' => 'Email adressen eller Jabber ID\'et er allerede i brug. Vælg venligst noget andet.',
+'note' => '<strong>NB!</strong> Du får tilsendt en bekræftelseskode som du skal bekræfte før din konto aktiveres. Koden sendes som du har valgt det ovenfor.<br />Hvis du skriver forkerte data vil du <strong>ikke modtage din bekræftelseskode!</strong>.',
+'changelog' => 'Ændringslog',
+'changeloggen' => 'Ændringslog generator',
+'listfrom' => 'Vis log fra',
+'to' => 'til',
+'oldestfirst' => 'Ældste først',
+'recentfirst' => 'Nyeste først',
+'severityrep' => 'Vigtighedsrapport',
+'totalopen' => 'Totalt antal åbne opgaver',
+'age' => 'Alder',
+'agerep' => 'Aldersrapport',
+'eventsrep' => 'Aktivitetsrapport',
+'events' => 'Aktiviteter',
+'Tasks' => 'Opgaver',
+'opened' => 'Oprettet',
+'edited' => 'Redigeret',
+'assigned' => 'Tildelte',
+'within' => 'Mellem (Within) ???',
+'pastday' => 'Den seneste dag',
+'pastweek' => 'Den seneste uge',
+'pastmonth' => 'Den seneste måned',
+'pastyear' => 'Det seneste år',
+'nolimit' => 'Ingen grænse',
+'from' => 'Fra',
+'duein' => 'Forfald i',
+'selectfromdate' => 'Vælg fra dato',
+'selecttodate' => 'Vælg Til dato',
+'showvoters' => 'Vis/skjul stemmegivere',
+'roadmap' => 'Roadmap',
+'roadmapfor' => 'Roadmap for version',
+'tasks' => 'opgaver',
+'completed' => 'færdige.',
+'opentasks' => 'Ã¥bne opagver',
+'of' => '% af',
+'severity5' => 'Kritisk',
+'severity4' => 'Høj',
+'severity3' => 'Medium',
+'severity2' => 'Lav',
+'severity1' => 'Meget lav',
+'Redirect' => 'Videresend',
+'redirectmsg' => 'Hvis du ikke sendes videre indenfor et øjeblik, så klik %sHERE%s',
+'allowclosedcomments' => 'Tillad kommentarer på lukkede opgaver',
+'comment' => 'Kommentar',
+'editowncomments' => 'Rediger egne kommentarer',
+'reopened' => 'Genåbnet',
+'loading' => 'Loading...',
+'notifyown' => 'Giv besked ved egne ændringer',
+'youremail' => 'Din Email adresse',
+'thankyouforbug' => 'Tak for at rapportere din problem. Du kan se opgaven og følge dens udvikling fra adressen:',
+'anonuser' => 'Anonym bruger',
+'conflict' => 'Konflikt',
+'file' => 'Fil',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => 'Størrelse',
+'projectgroup' => 'Projektgruppe',
+'profile' => 'Profil:',
+'viewprofile' => 'Vis profil',
+'regdate' => 'Oprettet siden',
+'tasksopened' => 'Opgaver åbnet',
+'replyto' => 'Svar til',
+'notifytypes' => 'Beskedtyper',
+'pm.taskchanged' => 'Opgave ændret',
+'pm.taskreopened' => 'Opgave genåbnet',
+'pm.depadded' => 'Sammenkædning tilføjet',
+'pm.depremoved' => 'Sammenkædning fjernet',
+'pmrequest' => 'PL anmodning',
+'pmrequestdenied' => 'PL anmodning afvist',
+'newassignee' => 'Ny modtager',
+'revdepadded' => 'Modsat sammenkædning tilføjet',
+'revdepaddedremoved' => 'Modsat sammenkædning fjernet',
+'assigneeadded' => 'Modtager tilføjet',
+'addusergroup' => 'Bruger tilføjet til gruppe',
+'groupmembers' => 'Gruppe medlemmer',
+'deleteuser' => 'Slet denne bruger',
+'userdeleted' => 'Bruger slettet',
+'autoassign' => 'Auto-tildel opgaver til kategori ejer',
+'ssl' => 'SSL',
+'updatewrong' => "Du har valgt at du vil modtage information om nye opdateringer, men der skete en fejl da systemet prøvede at kontakte Flyspray serveren. Dette kan skyldes problemer på netværket, eller en indstilling i en firewall.\nGå til Flysprays hjemmeside for at se om du benytter den nyeste version.",
+'deleteproject' => 'Slet dette projekt og flyt indhold til',
+'projectdeleted' => 'Projektet blev slettet',
+'feedforall' => 'Newsfeed fra alle projekter',
+'usercreated' => 'Bruger oprettet',
+'created' => 'Oprettet',
+'deleted' => 'Slettet',
+'userid' => 'Bruger ID',
+'editassignments' => 'Rediger tildelinger',
+'preview' => 'Vis',
+'tasksrelated' => 'Opgaver relateret til denne opgave',
+'duplicatetasks' => 'Kopier af denne opgave',
+'databasemodfailed' => 'Modificering af dabasen mislykkes. Dette kan skyldes manglende rettigheder.',
+'frequency' => 'Hyppighed',
+'newuserregistered' => 'En ny bruger har registreret sig i din Flyspray installation med følgende data:',
+'newuserregisterednotify' => 'Ny brugeroprettelse',
+'notify_registration' => 'Send besked til administratorer når nye brugerre oprettes',
+'textversion' => 'Tekst version',
+'onlyprimary' => 'Opgaver der ikke blokerer andre opgaver',
+'switch' => 'Skift',
+'max' => 'max.',
+'dates' => 'Datoer',
+'selectduedatefrom' => 'Forfaldsdato fra',
+'selectduedateto' => 'til',
+'selectsincedatefrom' => 'Redigeret fra',
+'selectsincedateto' => 'til',
+'selectdate' => 'Vælg dato',
+'selectopenedfrom' => 'Oprettet fra',
+'selectopenedto' => 'til',
+'selectclosedfrom' => 'Lukket fra',
+'selectclosedto' => 'til',
+'startat' => 'Startet fra',
+'hasattachment' => 'Har vedhæftede filer',
+'private' => 'Privat',
+'watching' => 'Overvåger',
+'alreadyvotedthistask' => 'Du stemte',
+'visibility' => 'Synlighed',
+'public' => 'Offentlig',
+'leaveemptyauto' => 'Udfyld ikke kodeord hvis det skal autogenereres.',
+'novalidemail' => 'Du har ikke skrevet en gyldig Email adresse.',
+'novalidjabber' => 'Du har ikke skrevet et gyldigt Jabber ID.',
+'missingrequired' => 'Du har ikke udfyldt alle felter.',
+'entervalidusername' => 'Du skal skrive et gyldigt brugernavn, samt det fulde navn.',
+'couldnotaddusernotif' => 'Denne bruger kunne ikke tilføjes påmindelseslisten.',
+'defaulttask' => 'Standard opgavebeskrivelse',
+'all' => 'alle',
+'events.useraddedtoassignees'=> 'Bruger tildelt denne opgave',
+'vote(s)' => 'Stemme(r)',
+'eventlog' => 'Aktivitetslog',
+'assignmentchanged' => 'Tildeling ændret',
+'detailedinfo' => 'Detaljer',
+'All' => 'Alle',
+'tasksireported' => 'Opgaver jeg har oprettet',
+'recentlyopened' => 'Nyligt åbnede',
+'stats' => 'Statistik',
+'totaltasks' => 'opgaver i alt',
+'mostwanted' => 'Mest populære opgaver',
+'defaultentry' => 'Standardside (Default entry page) ???',
+'overview' => 'Oversigt',
+'error#' => 'Fejl #',
+'error1' => 'Du har ikke rettigheder til at se den vedhæftede fil.',
+'error3' => 'Gentaget handling. Du videresendes til forsiden. ???',
+'error4' => 'Du har ikke administrative rettigheder.',
+'error5' => 'Denne bruger eksisterer ikke.',
+'error6' => 'Forkert administrationsområde (Invalid admin area) ???',
+'error7' => 'Forkert brugernavn eller kodeord!',
+'error71' => 'Din brugerkonto er låst i %d minutter grundet for mange mislykkede forsøg på at logge ind!',
+'error8' => 'Du har ikke skrevet både brugernavn og kodeord.',
+'error9' => 'Opgaven eksisterer ikke eller du har ikke rettigheder til at se den.',
+'error10' => 'Opgaven eksisterer ikke.',
+'error101' => 'Du har ikke rettigheder til at se denne opgave.',
+'error102' => 'Du har ikke rettigheder til at se denne opgave. Du skal logge ind først.',
+'error11' => 'Du har ikke rettigheder til at redigere denne kommentar.',
+'error13' => 'Anonyme brugere har ikke en profil.',
+'error14' => 'Du har ikke rettigheder til at oprette grupper.',
+'error15' => 'Du har ikke rettigheder til at åbne opgaver.',
+'error16' => 'Du er ikke projektansvarlig',
+'error17' => 'Ugyldigt PM område (PM = PA) ???',
+'error18' => 'Ugyldig Magic URL',
+'error19' => 'Denne bruger eksisterer ikke.',
+'error20' => 'Ugyldig database modificering.',
+'error21' => 'En eller flere mails kunne ikke sendes. Check din cinfiguration.',
+'error22' => 'Der er lukket for oprettelse af nye brugere.',
+'error23' => 'Bruger eller gruppe ikke aktiveret for log ind.',
+'error24' => 'Der er hverken angivet en dot executable eller en offentlig dot server.',
+'error25' => 'Roadmap er kun tilgængelig for et specifikt projekt.',
+'done' => 'færdig',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projektet kunne ikke slettes.',
+'GMT' => 'GMT',
+'timezone' => 'Tids zone',
+'accept' => 'Accept',
+'reasonfordeinal' => 'Ã…rsag til afvisning',
+'lastuser' => 'Den sidste bruger kan ikke slettes.',
+'allprivate' => 'Alle projekter er private.',
+'deletegroup' => 'Slet denne gruppe og flyt brugere til',
+'parent' => 'Overordnet',
+'allpriorities' => 'Alle prioriteter',
+'expand' => 'Fold ud',
+'collapse' => 'Fold sammen',
+'expandall' => 'Fold alle ud',
+'collapseall' => 'Fold alle sammen',
+'minpwsize' => 'Kodeord skal minimum bestå af 5 tegn',
+'passwordtoosmall' => 'Kodeord er for kort.',
+'accountwaslocked' => 'Din brugerkonto er blevet lukket grundet for mange mislykkede forsøg på at logge ind.',
+'failedattempts' => '%d mislykkede forsøg på at logge ind.',
+'groupnotexist' => 'Den valgte gruppe er ikke tilkyttet dette projekt.',
+'find' => 'Søg (find) ???',
+'tls' => 'TLS',
+);
+
+?>
diff --git a/lang/el.php b/lang/el.php
new file mode 100644
index 0000000..7529a75
--- /dev/null
+++ b/lang/el.php
@@ -0,0 +1,1065 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'ΕπεξεÏγασία χÏήστη',
+'accountenabled' => 'Ο λογαÏιασμός ενεÏγοποιήθηκε',
+'editallusers' => 'ΠÏοβολή όλων των χÏηστών',
+'username' => 'Όνομα χÏήστη',
+'usersupdated' => 'Οι χÏήστες ενημεÏώθηκαν επιτυχώς',
+'realname' => 'ΠÏαγματικό όνομα',
+'emailaddress' => 'ΔιεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'jabberid' => 'ΑναγνωÏιστικό Jabber',
+'profileimage' => 'Εικόνα Ï€Ïοφίλ',
+'notifytype' => 'ΤÏπος ειδοποίησης',
+'group' => 'Ομάδα',
+'enableaccounts' => 'ΕνεÏγοποίηση λογαÏιασμών',
+'disableaccounts' => 'ΑπενεÏγοποίηση λογαÏιασμών',
+'deleteaccounts' => 'ΔιαγÏαφή λογαÏιασμών',
+'updatedetails' => 'ΕνημέÏωση πληÏοφοÏιών',
+'setglobally' => 'Αυτή η Ï€Ïοτίμηση έχει οÏιστεί καθολικά.',
+'usergroupmanage' => 'ΔιαχείÏιση χÏηστών και ομάδων',
+'newuser' => 'ΕγγÏαφή νέου χÏήστη',
+'newuserbulk' => 'Μαζική εγγÏαφή νέων χÏηστών',
+'bulkuserstoadd' => 'Κατάλογος νέων χÏηστών',
+'optionsforallusers' => 'Επιλογές για όλους τους νέους χÏήστες',
+'newgroup' => 'ΔημιουÏγία νέας ομάδας',
+'yes' => 'Îαι',
+'no' => 'Όχι',
+'editgroup' => 'ΕπεξεÏγασία ομάδας',
+'groupname' => 'Όνομα ομάδας',
+'description' => 'ΠεÏιγÏαφή',
+'admin' => 'Ομάδα διαχειÏιστών',
+'opennewtasks' => 'Άνοιγμα νέων εÏγασιών',
+'modifytasks' => 'ΤÏοποποίηση υπαÏχουσών εÏγασιών',
+'addcomments' => 'ΠÏοσθήκη σχολίων',
+'attachfiles' => 'ΕπισÏναψη αÏχείων',
+'vote' => 'Ψήφος',
+'groupenabled' => 'Δυνατότητα σÏνδεσης',
+'groupopen' => 'Δυνατότητα σÏνδεσης',
+'tasktypelist' => 'Λίστα Ï„Ïπων εÏγασιών',
+'categorylist' => 'Λίστα κατηγοÏιών',
+'oslist' => 'Λίστα λειτουÏγικών συστημάτων',
+'resolutionlist' => 'Λίστα επιλÏσεων',
+'versionlist' => 'Λίστα εκδόσεων',
+'severitylist' => 'Λίστα βαθμών σοβαÏότητας',
+'listnote' => 'Σημείωση: Αν ξετσεκάÏετε το κουτάκι «Εμφάνιση» μποÏεί να μεταβληθοÏν κάποιες εÏγασίες εφόσον βÏίσκονται σε κατάσταση επεξεÏγασίας. Αν αλλάξετε το πεδίο «Όνομα» θα Ï„ÏοποποιηθοÏν όλες οι εÏγασίες με αυτό το όνομα. Στοιχεία που δεν μποÏοÏν να διαγÏαφοÏν είναι είτε Ï€Ïοστατευμένα γιατί χÏειάζονται για σωστή λειτουÏγία, είτε χÏησιμοποιοÏνται σε εÏγασίες.',
+'name' => 'Όνομα',
+'order' => 'ΣειÏά',
+'back' => 'Πίσω',
+'text' => 'Κείμενο',
+'highlight' => 'Επισήμανση',
+'show' => 'Εμφάνιση',
+'owner' => 'Κάτοχος',
+'selectowner' => 'Επιλογή κατόχου',
+'update' => 'ΕνημέÏωση',
+'addnew' => 'ΠÏοσθήκη νέου στοιχείου',
+'flysprayprefs' => 'ΠÏοτιμήσεις Flyspray',
+'projecttitle' => 'Τίτλος έÏγου',
+'baseurl' => 'ΔιεÏθυνση URL βάσης για αυτήν την εγκατάσταση',
+'replyaddress' => 'ΔιεÏθυνση απάντησης ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου για ειδοποιήσεις',
+'themestyle' => 'Θέμα / Στυλ',
+'customstyle' => 'Ï„Ïοποποίηση',
+'language' => 'Γλώσσα',
+'anonview' => 'Îα επιτÏέπεται σε ανώνυμους χÏήστες να Ï€Ïοβάλλουν εÏγασίες',
+'allowanon' => 'Îα επιτÏέπεται σε ανώνυμους χÏήστες να ανοίγουν νέες εÏγασίες',
+'never' => 'Ποτέ',
+'anonymously' => 'Ανώνυμα',
+'afterregister' => 'Μόνο μετά από εγγÏαφή',
+'spamproof' => 'ΕνεÏγοποίηση ÎºÏ‰Î´Î¹ÎºÎ¿Ï ÎµÏ€Î±Î»Î®Î¸ÎµÏ…ÏƒÎ·Ï‚ για εγγÏαφές νέων χÏηστών',
+'anongroup' => 'Ομάδα για εγγÏαφές νέων χÏηστών',
+'groupassigned' => 'Στα μέλη αυτών των ομάδων μποÏοÏν να ανατίθενται ενέÏγειες',
+'forcenotify' => 'Εξαναγκασμός των ειδοποιήσεων εÏγασιών σε',
+'neversend' => 'Îα μην αποστέλλονται ποτέ',
+'userchoose' => 'Îα μποÏεί ο κάθε χÏήστης να επιλέγει',
+'email' => 'ΗλεκτÏονικό ταχυδÏομείο',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'ΠÏοεπιλεγμένος κάτοχος κατηγοÏίας',
+'noone' => 'Κανείς',
+'jabbernotify' => 'Ειδοποιήσεις Jabber',
+'jabberserver' => 'Διακομιστής',
+'jabberport' => 'ΘÏÏα',
+'jabberuser' => 'Όνομα χÏήστη λογαÏιασμοÏ',
+'jabberpass' => 'Συνθηματικό λογαÏιασμοÏ',
+'saveoptions' => 'Αποθήκευση επιλογών',
+'editcomment' => 'ΕπεξεÏγασία σχολίου',
+'commentby' => 'Σχόλιο από',
+'saveeditedcomment' => 'Αποθήκευση επεξεÏγασμένου σχολίου',
+'projectprefs' => 'ΠÏοτιμήσεις έÏγου',
+'pagetitle' => 'Τίτλος σελίδας',
+'defaultproject' => 'ΠÏοεπιλεγμένο έÏγο',
+'projectlists' => 'Λίστα έÏγων',
+'showlogo' => 'Εμφάνιση εικόνας λογοτÏπου τίτλου',
+'showgravatars' => 'Εμφάνιση Gravatar',
+'emailNoHTML' => 'Όχι HTML στα μηνÏματα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'intromessage' => 'Εισαγωγικό μήνυμα',
+'active' => 'ενεÏγό',
+'inactive' => 'μη ενεÏγό',
+'isactive' => 'Το έÏγο είναι ενεÏγό',
+'showinactive' => 'Εμφάνιση ανενεÏγών έÏγων',
+'hideinactive' => 'ΑπόκÏυψη ανενεÏγών έÏγων',
+'createproject' => 'ΔημιουÏγία νέου έÏγου',
+'nopermission' => 'Δεν έχετε εξουσιοδότηση να χÏησιμοποιήσετε αυτήν τη σελίδα.',
+'listordertip' => 'Η σειÏά με την οποίαν αυτά τα στοιχεία θα εμφανίζονται στη λίστα',
+'listshowtip' => 'Εμφάνιση Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… στοιχείου στη λίστα',
+'categoryownertip' => 'Αυτό το άτομο θα λάβει ειδοποιήσεις όταν ανοιχτεί εÏγασία σε αυτήν την κατηγοÏία',
+'categoryparenttip' => 'Η μητÏική κατηγοÏία κάτω από την οποία θα υπαχθεί αυτή εδώ η κατηγοÏία',
+'notsubcategory' => 'Καμία (κατηγοÏία ανωτάτου επιπέδου)',
+'showinlineimages' => 'Εμφάνιση συνημμένων εικόνων ένθετα στο κείμενο',
+'dateformat' => 'ΜοÏφή ημεÏομηνίας',
+'dateformat_extended' => 'Αναλυτική μοÏφή ημεÏομηνίας',
+'cache_feeds' => 'ΠÏοσωÏινή αποθήκευση Ïοών',
+'no_cache' => 'ΧωÏίς Ï€ÏοσωÏινή αποθήκευση',
+'cache_disk' => 'ΠÏοσωÏινή αποθήκευση στο δίσκο',
+'cache_db' => 'ΠÏοσωÏινή αποθήκευση στη βάση δεδομένων',
+'subcategoryof' => 'ΥποκατηγοÏία του',
+'visiblecolumns' => 'Στήλες που να εμφανίζονται στη λίστα εÏγασιών',
+'visiblefields' => 'Πεδία κατά την Ï€Ïοσθήκη/επεξεÏγασία/Ï€Ïοβολή εÏγασίας',
+'tense' => 'ΧÏόνος',
+'listtensetip' => 'ΠαÏελθόν, παÏόν και μέλλον',
+'past' => 'ΠαÏελθόν',
+'present' => 'ΠαÏόν',
+'future' => 'Μέλλον',
+'oldpass' => 'Παλιό συνθηματικό',
+'nooldpass' => 'Δεν οÏίστηκε παλιό συνθηματικό',
+'oldpasswrong' => 'Το παλιό συνθηματικό ήταν λάθος',
+'changepass' => 'Αλλαγή συνθηματικοÏ',
+'confirmpass' => 'Επιβεβαίωση συνθηματικοÏ',
+'projectmanager' => 'ΔιαχειÏιστής έÏγου',
+'viewtasks' => 'ΠÏοβολή εÏγασιών',
+'modifyowntasks' => 'ΤÏοποποίηση ιδίων εÏγασιών',
+'modifyalltasks' => 'ΤÏοποποίηση μη ιδίων εÏγασιών',
+'viewcomments' => 'ΠÏοβολή σχολίων',
+'editcomments' => 'ΕπεξεÏγασία σχολίων',
+'deletecomments' => 'ΔιαγÏαφή σχολίων',
+'viewattachments' => 'ΠÏοβολή συνημμένων',
+'createattachments' => 'ΔημιουÏγία συνημμένων',
+'deleteattachments' => 'ΔιαγÏαφή συνημμένων',
+'viewhistory' => 'ΠÏοβολή ιστοÏικοÏ',
+'closeowntasks' => 'Κλείσιμο ιδίων εÏγασιών',
+'closeothertasks' => 'Κλείσιμο μη ιδίων εÏγασιών',
+'assigntoself' => 'Αυτοανάθεση εÏγασιών αν δεν είναι ήδη ανατεθειμένες',
+'assignotherstoself' => 'Αυτοανάθεση εÏγασιών που είναι ανατεθειμένες σε άλλους',
+'viewreports' => 'ΠÏοβολή καταγÏαφής συμβάντων',
+'othersview' => 'Îα επιτÏέπεται στον οποιονδήποτε να Ï€Ïοβάλλει αυτό το έÏγο',
+'othersviewroadmap' => 'Îα επιτÏέπεται σε οποιονδήποτε να δει το χάÏτη ποÏείας Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… έÏγου',
+'usersandgroups' => 'ΧÏήστες και ομάδες',
+'globalgroup' => 'Καθολική ομάδα',
+'globalgroups' => 'Καθολικές ομάδες',
+'defaultglobalgroup' => 'ΠÏοεπιλεγμένη καθολική ομάδα για νέους χÏήστες',
+'addtogroup' => 'ΠÏοσθήκη στην ομάδα',
+'moveuserstogroup' => 'Μετακίνηση χÏηστών στην ομάδα',
+'nogroup' => 'Καμία ομάδα - ΑφαίÏεση από το έÏγο',
+'eventdesc' => 'ΠεÏιγÏαφή γεγονότος',
+'requestedby' => 'Το αίτημα έγινε από',
+'daterequested' => 'ΗμεÏομηνία αιτήματος',
+'closetask' => 'Κλείσιμο εÏγασίας',
+'reopentask' => 'Εν νέου άνοιγμα εÏγασίας',
+'applymember' => 'Αίτηση για να γίνετε μέλος στο έÏγο',
+'forcurrentproj' => 'Για το Ï„Ïέχον έÏγο',
+'lostpw' => 'Ανάκτηση απωλεσθέντος συνθηματικοÏ',
+'lostpwexplain' => 'Εισαγάγετε το όνομα χÏήστη σας για να σας αποσταλεί ένας σÏνδεσμος για να αλλάξετε το συνθηματικό σας. Αυτός ο σÏνδεσμος θα αποσταλεί στην διεÏθυνση ειδοποιήσεων που έχετε οÏίσει στο Ï€Ïοφίλ σας.',
+'sendlink' => 'Αποστολή συνδέσμου',
+'savenewpass' => 'Αποθήκευση νέου συνθηματικοÏ',
+'anonreg' => 'Îα επιτÏέπονται εγγÏαφές νέων χÏηστών',
+'allowanonopentask' => 'Îα επιτÏέπεται σε ανώνυμους χÏήστες να ανοίγουν εÏγασίες',
+'editglobalgroup' => 'ΕπεξεÏγασία καθολικής ομάδας',
+'editgroupforproj' => 'ΕπεξεÏγασία ομάδας έÏγου',
+'notshownforadmin' => 'Οι άδειες για την ομάδα διαχειÏιστών δεν εμφανίζονται. Δεν χÏειάζεται να τις επεξεÏγαστείτε.',
+'general' => 'Γενικά',
+'userregistration' => 'ΕγγÏαφή χÏήστη',
+'notifications' => 'Ειδοποιήσεις',
+'resetoptions' => 'ΕπαναφοÏά επιλογών',
+'preferences' => 'ΠÏοτιμήσεις',
+'tasktypes' => 'ΤÏποι εÏγασιών',
+'resolutions' => 'ΕπιλÏσεις',
+'categories' => 'ΚατηγοÏίες',
+'operatingsystems' => 'ΛειτουÏγικά συστήματα',
+'versions' => 'Εκδόσεις',
+'admintoolboxlong' => 'ΕÏγαλειοθήκη διαχειÏιστή',
+'newproject' => 'Îέο έÏγο',
+'delete' => 'ΔιαγÏαφή',
+'link' => 'ΥπεÏσÏνδεσμος',
+'referencelinks' => 'ΥπεÏσÏνδεσμοι αναφοÏάς:',
+'listdeletetip' => 'ΔιαγÏαφή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… στοιχείου από τη λίστα',
+'lookandfeel' => 'Όψη και αίσθηση',
+'globaltheme' => 'Καθολικό θέμα',
+'emailnotify' => 'Ειδοποιήσεις ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'fromaddress' => 'Από τη διεÏθυνση',
+'smtpserver' => 'Διακομιστής SMTP',
+'smtpuser' => 'Όνομα χÏήστη SMTP',
+'smtppass' => 'Συνθηματικό SMTP',
+'addrewrite' => 'ΧÏήση μεταγÏαφής διεÏθυνσης',
+'usereminderdaemon' => 'ΕνεÏγοποίηση δαίμονα υπενθυμίσεων παÏασκηνίου',
+'tasksperpage' => 'ΕÏγασίες ανά σελίδα της λίστας εÏγασιών',
+'addtoassignees' => 'ΑυτοπÏοσθήκη στους αναδόχους',
+'taskstatuses' => 'Καταστάσεις εÏγασιών',
+'canvote' => 'Δικαίωμα ψήφου σε εÏγασίες',
+'loginsuccessful' => 'Επιτυχής σÏνδεση.',
+'youareloggedout' => 'Έχετε αποσυνδεθεί.',
+'waitwhiletransfer' => 'ΠαÏακαλοÏμε πεÏιμένετε καθώς μεταφέÏεστε...',
+'clicknowait' => 'Κάντε κλικ εδώ αν δεν επιθυμείτε να πεÏιμένετε.',
+'accountdisabled' => 'Ο λογαÏιασμός σας έχει απενεÏγοποιηθεί. Επικοινωνήστε με έναν πλήÏη διαχειÏιστή.',
+'task' => 'ΕÏγασία',
+'edittask' => 'ΕπεξεÏγασία αυτής της εÏγασίας',
+'openedby' => 'Ανοίχτηκε από',
+'editedby' => 'Τελευταία επεξεÏγασία από',
+'tasktype' => 'ΤÏπος εÏγασίας',
+'category' => 'ΚατηγοÏία',
+'status' => 'Κατάσταση',
+'assignedto' => 'Ανάδοχος',
+'operatingsystem' => 'ΛειτουÏγικό σÏστημα',
+'severity' => 'ΣοβαÏότητα',
+'reportedversion' => 'Η αναφοÏά έγινε στην έκδοση',
+'dueinversion' => 'Αναμενόμενη στην έκδοση',
+'defaultdueinversion' => 'ΠÏοεπιλογή για νέες εÏγασίες της έκδοσης στην οποία η εÏγασία θα είναι αναμενόμενη',
+'undecided' => 'Μη αποφασισμένη',
+'percentcomplete' => 'ΟλοκληÏωμένη σε ποσοστό',
+'details' => 'ΛεπτομέÏειες',
+'savedetails' => 'Αποθήκευση λεπτομεÏειών',
+'canceledit' => 'ΑκÏÏωση επεξεÏγασίας',
+'anonymous' => 'Ανώνυμος υποβάλλων',
+'complete' => 'ολοκληÏωμένη',
+'closedby' => 'Κλείστηκε από',
+'reasonforclosing' => 'Αιτιολογία κλεισίματος:',
+'reopenthistask' => 'Άνοιγμα εκ νέου αυτής της εÏγασίας',
+'comments' => 'Σχόλια',
+'attachments' => 'Συνημμένα',
+'relatedtasks' => 'Σχετικές εÏγασίες',
+'edit' => 'ΕπεξεÏγασία',
+'addcomment' => 'ΠÏοσθήκη σχολίου',
+'fileuploadedby' => 'Το αÏχείο ανέβηκε από',
+'uploadafile' => 'ΕπισÏναψη αÏχείου',
+'addalink' => 'ΠÏοσθήκη υπεÏσυνδέσμου',
+'addanotherlink' => 'ΠÏοσθήκη κι άλλου υπεÏσυνδέσμου',
+'uploadnow' => 'Ανέβασμα Ï„ÏŽÏα!',
+'thesearerelated' => 'Αυτές οι εÏγασίες σχετίζονται με αυτήν την εÏγασία',
+'remove' => 'ΚατάÏγηση',
+'addnewrelated' => 'ΠÏοσθήκη νέα σχετικής εÏγασίας',
+'add' => 'ΠÏοσθήκη',
+'otherrelated' => 'Άλλες εÏγασίες με τις οποίες σχετίζεται αυτή η εÏγασία',
+'receivenotify' => 'Αυτοί οι χÏήστες θα λάβουν αναλυτικές ειδοποιήσεις όταν Ï„Ïοποποιηθεί αυτή η εÏγασία.',
+'addusertolist' => 'ΠÏοσθήκη χÏήστη σε αυτή τη λίστα',
+'addtolist' => 'ΠÏοσθήκη σε λίστα',
+'addmyself' => 'ΠÏοσθήκη του ÎµÎ±Ï…Ï„Î¿Ï Î¼Î¿Ï… σε αυτήν τη λίστα',
+'removemyself' => 'ΑφαίÏεση του ÎµÎ±Ï…Ï„Î¿Ï Î¼Î¿Ï… από αυτήν τη λίστα',
+'theseusersnotify' => 'Αυτοί οι χÏήστες θα λάβουν αναλυτικές ειδοποιήσεις οποτεδήποτε Ï„Ïοποποιηθεί αυτή η εÏγασία.',
+'attachedtoproject' => 'Συνημμένη στο έÏγο',
+'reminders' => 'Υπενθυμίσεις',
+'system' => 'ΣÏστημα',
+'systemvalues' => 'Λίστα τιμών σε εÏÏος συστήματος',
+'projectvalues' => 'Λίστα τιμών στο συγκεκÏιμένο έÏγο',
+'remindthisuser' => 'ΥπενθÏμιση σε αυτόν το χÏήστη',
+'thisoften' => 'Τόσο συχνά',
+'startafter' => 'Αναμονή Ï€Ïιν την εκκίνηση των υπενθυμίσεων',
+'hour' => 'ÏŽÏα',
+'hours' => 'ÎÏα(ες)',
+'day' => 'ημέÏα',
+'days' => 'ΜέÏα(ες)',
+'week' => 'εβδομάδα',
+'weeks' => 'Εβδομάδα(ες)',
+'addreminder' => 'ΠÏοσθήκη υπενθÏμισης',
+'defaultreminder' => 'Αυτή είναι μια υπενθÏμιση για να κοιτάξετε την ακόλουθη εÏγασία στο Flyspray:',
+'message' => 'Μήνυμα',
+'closed' => 'Κλεισμένες',
+'filename' => 'Όνομα αÏχείου:',
+'date' => 'ΗμεÏομηνία',
+'filesize' => 'Μέγεθος αÏχείου:',
+'closurecomment' => 'ΣυμπληÏωματικές παÏατηÏήσεις σχετικά με το κλείσιμο:',
+'history' => 'ΙστοÏικό',
+'nohistory' => 'Δεν υπάÏχει διαθέσιμο ιστοÏικό.',
+'eventdate' => 'ΗμεÏομηνία',
+'user' => 'ΧÏήστης',
+'event' => 'Συμβάν',
+'fieldchanged' => 'Το πεδίο άλλαξε',
+'taskopened' => 'Άνοιγμα εÏγασίας',
+'taskreopened' => 'Εκ νέου άνοιγμα εÏγασίας',
+'taskclosed' => 'Κλείσιμο εÏγασίας',
+'commentadded' => 'ΠÏοσθήκη σχολίου',
+'commentedited' => 'ΤÏοποποίηση σχολίου',
+'commentdeleted' => 'ΔιαγÏαφή σχολίου',
+'attachmentadded' => 'ΠÏοσθήκη συνημμένου',
+'attachmentdeleted' => 'ΔιαγÏαφή συνημμένου',
+'taskedited' => 'ΤÏοποποίηση λεπτομεÏειών της εÏγασίας',
+'notificationadded' => 'Ο χÏήστης Ï€Ïοστέθηκε στη λίστα ειδοποιήσεων',
+'notificationdeleted' => 'Ο χÏήστης αφαιÏέθηκε από τη λίστα ειδοποιήσεων',
+'relatedadded' => 'Η σχετική εÏγασία Ï€Ïοστέθηκε',
+'relateddeleted' => 'Η σχετική εÏγασία καταÏγήθηκε',
+'taskassigned' => 'Η εÏγασία ανατέθηκε σε',
+'taskreassigned' => 'Η εÏγασία ανατέθηκε σε',
+'assignmentremoved' => 'Η εÏγασία ανατέθηκε εκ νέου σε',
+'summary' => 'ΣÏνοψη',
+'addedasrelated' => 'Η εÏγασία Ï€Ïοστέθηκε στη λίστα με τις σχετικές εÏγασίες της',
+'deletedasrelated' => 'Η εÏγασία αφαιÏέθηκε από τη λίστα με τις σχετικές εÏγασίες της',
+'reminderadded' => 'Η υπενθÏμιση Ï€Ïοστέθηκε',
+'reminderdeleted' => 'Η υπενθÏμιση καταÏγήθηκε',
+'priority' => 'ΠÏοτεÏαιότητα',
+'previousvalue' => 'ΠÏοηγοÏμενη τιμή',
+'newvalue' => 'Îέα τιμή',
+'selectareason' => 'Επιλογή αιτιολογίας',
+'assigntome' => 'Ανάθεση σε εμένα',
+'reopenrequest' => 'Αίτημα να ανοίξει εκ νέου',
+'requestclose' => 'Αίτημα κλεισίματος',
+'ownershiptaken' => 'Ο χÏήστης ανέλαβε την κυÏιότητα',
+'closerequestmade' => 'Αίτημα κλεισίματος εÏγασίας',
+'reopenrequestmade' => 'Αιτηθέν εκ νέου άνοιγμα εÏγασίας',
+'taskdependson' => 'Αυτή η εÏγασία εξαÏτάται από',
+'taskdependsontask' => 'Η εÏγασία εξαÏτάται από',
+'taskdependsontasks' => 'Η εÏγασία εξαÏτάται από',
+'taskblock' => 'Η εÏγασία κωλÏει το κλείσιμο των',
+'taskblocks' => 'Αυτή η εÏγασία κωλÏει αυτές να κλειστοÏν',
+'depadded' => 'Η εξάÏτηση Ï€Ïοστέθηκε',
+'depaddedother' => 'Αυτή η εÏγασία Ï€Ïοστέθηκε ως εξάÏτηση',
+'depremoved' => 'Η εξάÏτηση καταÏγήθηκε',
+'depremovedother' => 'Αυτή η εÏγασία καταÏγήθηκε από τη λίστα εξαÏτήσεων άλλης εÏγασίας',
+'showdetailserror' => 'Εκείνη η εÏγασία δεν υπάÏχει, ή δεν έχετε εξουσιοδότηση για να τη δείτε.',
+'makeprivate' => 'να γίνει ιδιωτική',
+'makepublic' => 'να γίνει δημόσια',
+'taskmadeprivate' => 'Η εÏγασία έγινε ιδιωτική',
+'taskmadepublic' => 'Η ιδιωτικότητα καταÏγήθηκε - η εÏγασία έγινε δημόσια',
+'confirmdeletecomment' => 'ΣίγουÏα να διαγÏαφεί αυτό το σχόλιο; Όλα τα συνημμένα θα διαγÏαφοÏν επίσης!',
+'attachementswilldeleted' => 'Όλα τα συνημμένα θα διαγÏαφοÏν επίσης!',
+'confirmdeleteattach' => 'ΣίγουÏα να διαγÏαφεί αυτό το συνημμένο;',
+'selectedhistory' => 'Γίνεται εμφάνιση επιλεγμένων λεπτομεÏιών ιστοÏικοÏ',
+'showallhistory' => 'Εμφάνιση καÏτέλας πλήÏους ιστοÏÎ¹ÎºÎ¿Ï Î¾Î±Î½Î¬',
+'hidethis' => 'ΑπόκÏυψη αυτής της πεÏιοχής ξανά',
+'mark100' => 'Σήμανση εÏγασίας ως 100% ολοκληÏωμένη',
+'watchtask' => 'παÏακολοÏθηση εÏγασίας',
+'stopwatching' => 'σταμάτημα παÏακολοÏθησης',
+'commentlink' => 'ΣÏνδεσμος Ï€Ïος αυτό το σχόλιο',
+'submitreq' => 'Υποβολή αιτήματος',
+'reasonforreq' => 'Αιτιολογία για το αίτημα',
+'pmreqdenied' => 'Ο διαχειÏιστής έÏγου αÏνήθηκε το αίτημα',
+'taskpendingreq' => 'Η ενέÏγεια του διαχειÏιστή έÏγου εκκÏεμεί. Βλ. καÏτέλα ΙστοÏικό για λεπτομέÏειες.',
+'previoustask' => 'ΠÏοηγοÏμενη εÏγασία',
+'nexttask' => 'Επόμενη εÏγασία',
+'duedate' => 'ΠÏοθεσμία',
+'attachnoperms' => 'ΥπάÏχουν συνημμένα με αυτό το σχόλιο, αλλά δεν έχετε εξουσιοδότηση να τα δείτε.',
+'linknoperms' => 'ΥπάÏχουν υπεÏσÏνδεσμοι με αυτό το σχόλιο, αλλά δεν έχετε εξουσιοδότηση να τους Ï€Ïοβάλετε.',
+'open' => 'Ανοικτές',
+'depgraph' => 'ΠÏοβολή γÏαφήματος εξάÏτησης',
+'reset' => 'ΕπαναφοÏά',
+'selectusers' => 'Επιλογή χÏηστών...',
+'addmetoassignees' => 'Îα Ï€Ïοστεθώ στους αναδόχους',
+'addedtoassignees' => 'Ο χÏήστης Ï€Ïοστέθηκε στη λίστα αναδόχων',
+'dependencygraph' => 'ΓÏάφημα εξαÏτήσης',
+'attachanotherfile' => 'ΕπισÏναψη κι άλλου αÏχείου',
+'OK' => 'Εντάξει',
+'addvote' => 'Ï€Ïοσθήκη ψήφου',
+'disable_lostpw' => 'ΑπενεÏγοποίηση ανάκτησης απολεσθέντος συνθηματικοÏ',
+'disable_changepw' => 'ΑπενεÏγοποίηση δημιουÏγίας/επεξεÏγασίας συνθηματικοÏ',
+'notifyfromfs' => 'Ειδοποίηση από Flyspray',
+'autogenerated' => 'ΑΥΤΟ ΕΙÎΑΙ ΕÎΑ ΑΥΤΟΜΑΤΑ ΔΗΜΙΟΥΡΓΗΜΕÎΟ ΜΗÎΥΜΑ, ΜΗΠΑΠΑÎΤΗΣΕΤΕ',
+'forward' => 'ΠÏος τα εμπÏός',
+'previous' => 'ΠÏοηγοÏμενη',
+'next' => 'Επόμενη',
+'first' => 'ΠÏώτη',
+'last' => 'Τελευταία',
+'page' => 'Σελίδα %d από %d',
+'search' => 'Αναζήτηση',
+'alltasktypes' => 'Όλοι οι Ï„Ïποι εÏγασιών',
+'allseverities' => 'Όλοι οι βαθμοί σοβαÏότητας',
+'alldevelopers' => 'Όλοι οι Ï€ÏογÏαμματιστές',
+'notyetassigned' => 'Μη ανατεθειμένη ακόμα',
+'allcategories' => 'Όλες οι κατηγοÏίες',
+'allstatuses' => 'Όλες οι καταστάσεις',
+'allopentasks' => 'Όλες οι ανοιχτές εÏγασίες',
+'sortthiscolumn' => 'Ταξινόμηση με βάση αυτήν τη στήλη',
+'id' => 'FS#',
+'project' => 'ΈÏγο',
+'dateopened' => 'Ανοιγμένη στις',
+'progress' => 'ΠÏόοδος',
+'searchthisproject' => 'Αναζήτηση σε αυτό το έÏγο για',
+'dueanyversion' => 'Αναμενόμενη σε οποιαδήποτε έκδοση',
+'anyversion' => 'Οποιαδήποτε έκδοση',
+'dueversion' => 'Αναμενόμενη στην έκδοση',
+'lastedit' => 'Τελευταία επεξεÏγασία',
+'os' => 'ΛειτουÏγικό σÏστημα',
+'reportedin' => 'ΑναφέÏθηκε στις',
+'taskrange' => 'Εμφάνιση εÏγασιών %d - %d από %d',
+'noresults' => 'Η αναζήτησή σας δεν επέστÏεψε κανένα αποτέλεσμα.',
+'takeaction' => 'Εκτέλεση ενέÏγειας',
+'watchtasks' => 'ΠαÏακολοÏθηση επιλεγμένων εÏγασιών',
+'stopwatchingtasks' => 'Διακοπή παÏακολοÏθησης επιλεγμένων εÏγασιών',
+'assigntaskstome' => 'Ανάθεση επιλεγμένων εÏγασιών σε εμένα',
+'dueby' => 'ΠÏοθεσμία',
+'dueanytime' => 'ΠÏοσθεσμία οποτεδήποτε',
+'selectduedate' => 'Επιλογή Ï€Ïοθεσμίας',
+'toggleselected' => 'ΑντιστÏοφή επιλογής',
+'due' => 'Λήξη',
+'assignedtome' => 'Ανατεθειμένες σε εμένα',
+'tasklist' => 'Λίστα εÏγασιών',
+'dateclosed' => 'ΗμεÏομηνία κλεισίματος',
+'advanced' => 'ΠÏοχωÏημένη αναζήτηση',
+'searchcomments' => 'Αναζήτηση σε σχόλια',
+'searchforall' => 'Αναζήτηση για όλες τις λέξεις',
+'anonusers' => 'Ανώνυμοι χÏήστες',
+'miscellaneous' => 'ΔιάφοÏα',
+'users' => 'ΧÏήστες',
+'taskproperties' => 'Ιδιότητες εÏγασίας',
+'selectsincedate' => 'Επιλογή αλλαγμένων από τις',
+'changedsince' => 'ΤÏοποποιήθηκε από',
+'updatefs' => 'ΠαÏακαλοÏμε να αναβαθμίσετε το Flyspray.',
+'currentversion' => 'Η Ï„Ïέχουσα έκδοσή σας είναι',
+'latestversion' => 'και η πιο Ï€Ïόσφατη έκδοση είναι',
+'hidemessage' => '(υπενθÏμιση αÏγότεÏα)',
+'saveas' => 'Αποθήκευση αναζήτησης ως',
+'nosearches' => 'Δεν υπάÏχουν αποθηκευμένες αναζητήσεις',
+'saving' => 'Αποθήκευση σε εξέλιξη...',
+'votes' => 'Ψήφοι',
+'tovote' => 'Ψήφος',
+'allclosedtasks' => 'Όλες οι κλεισμένες εÏγασίες',
+'password' => 'Συνθηματικό',
+'login' => 'ΣÏνδεση!',
+'rememberme' => 'Îα με θυμάσαι',
+'lostpassword' => 'Απώλεια συνθηματικοÏ;',
+'lostpwforfs' => 'Απώλεια ÏƒÏ…Î½Î¸Î·Î¼Î±Ï„Î¹ÎºÎ¿Ï Î³Î¹Î± το Flyspray',
+'lostpwmsg1' => "ΧαίÏετε.\n\nΈχω απωλέσει το συνθηματικό μου στο Flyspray στις _",
+'lostpwmsg2' => ", παÏακαλώ να μου χοÏηγήσετε ένα νέο συνθηματικό.\n\nΌνομα χÏήστη: _",
+'regards' => "Φιλικά,\n\n",
+'yourusername' => '_ το όνομα χÏήστη σας _',
+'locale' => 'el-GR',
+'filenotexist' => 'Το αÏχείο δεν υπάÏχει, ή δεν έχετε εξουσιοδότηση Ï€Ïόσβασης σε αυτό.',
+'showtask' => 'Εμφάνιση εÏγασίας',
+'now' => 'ΤώÏα',
+'go' => 'Μετάβαση!',
+'opentaskanon' => 'Άνοιγμα νέας εÏγασίας ανώνυμα',
+'register' => 'ΕγγÏαφή',
+'addnewtask' => 'Îέα εÏγασία',
+'reports' => 'ΜητÏώο συμβάντων',
+'editmydetails' => 'ΕπεξεÏγασία των στοιχείων μου',
+'logout' => 'ΑποσÏνδεση',
+'disabledaccount' => 'Ο λογαÏιασμός σας έχει απενεÏγοποιηθεί!<br />Γίνεται άμεση αποσÏνδεσή σας...',
+'poweredby' => 'ΛειτουÏγεί με το Flyspray',
+'sponsoredby' => 'Το Flyspray είναι με υπεÏηφάνεια υπό την αιγίδα του',
+'projects' => 'ΈÏγα',
+'allprojects' => 'Όλα τα έÏγα',
+'selectproject' => 'για το έÏγο:',
+'tasksall' => 'Όλες οι εÏγασίες',
+'tasksassigned' => 'ΕÏγασίες που έχουν ανατεθεί σε εμένα',
+'tasksreported' => 'ΕÏγασίες που έχουν αναφεÏθεί από εμένα',
+'taskswatched' => 'ΕÏγασίες που παÏακολουθώ',
+'mysearch' => 'Οι αναζητήσεις μου',
+'admintoolbox' => 'ΕÏγαλειοθήκη διαχειÏιστή',
+'manageproject' => 'ΔιαχείÏιση έÏγου',
+'permissions' => 'ΠÏοβολή αδειών',
+'hide' => 'ΑπόκÏυψη',
+'pendingreq' => 'εκκÏεμή αιτήματα ΔΕ',
+'errorpage' => "Το Flyspray δεν μποÏεί να παÏάσχει τη σελίδα που ζητήσατε. \nΊσως ζητήσατε μια εÏγασία που δεν υπάÏχει, ή δεν έχετε εξουσιοδότηση να Ï€Ïοβάλετε τη σελίδα που θέλατε.<br /><br /> \nΜποÏεί να αποπειÏαθήκατε να χÏησιμοποιήσετε ένα πονηÏÏŒ URL για να αλληλεπιδÏάσετε με τη βάση δεδομένων χÏησιμοποιώντας ένεση SQL. Αν αυτό είναι αλήθεια, πηγαίνετε τιμωÏία στη γωνία να αναλογιστείτε τις Ï€Ïάξεις. Όταν επιστÏέψετε, σας παÏακαλοÏμε να μην το ξανακάνετε!",
+'permissionsforproject' => 'Άδειες για _',
+'switchto' => 'Εναλλαγή σε',
+'lastsearch' => 'Τελευταία αναζήτηση',
+'modify' => 'ΤÏοποποίηση',
+'noticefrom' => 'Ειδοποίηση από',
+'hasopened' => 'ΑÎΟΙΞΕ ΜΙΑ ÎΕΑ εÏγασία στο Flyspray και την ανέθεσε σε εσάς:',
+'moreinfonew' => 'ΜποÏείτε να βÏείτε πεÏισσότεÏες πληÏοφοÏίες σχετικά με αυτό το bug στη σελίδα του Flyspray:',
+'newtaskcategory' => 'Μια νέα εÏγασία Flyspray έχει ανοιχτεί σε αυτήν την κατηγοÏία',
+'categoryowner' => 'Αυτό το λαμβάνετε γιατί έχετε καταχωÏιστεί ως κάτοχος της κατηγοÏίας.',
+'tasksummary' => 'ΣÏνοψη εÏγασίας:',
+'newtaskadded' => 'Η νέα σας εÏγασία έχει Ï€Ïοστεθεί.',
+'summaryanddetails' => 'ΠÏέπει να συμπληÏώσετε και τη σÏνοψη και τις αναλυτικές πληÏοφοÏίες.',
+'summaryrequired' => 'ΧÏειάζεται να συμπληÏώσετε μια σÏντομη σÏνοψη της εÏγασίας.',
+'goback' => 'ΕπιστÏοφή πίσω.',
+'messagefrom' => 'Αυτό είναι ένα μήνυμα από το σÏστημα παÏακολοÏθησης σφαλμάτων Flyspray στις _',
+'hasjustmodified' => 'μόλις Ï„Ïοποποίησε την ακόλουθη εÏγασία.',
+'changedfields' => 'Τα Ï„Ïοποποιημένα πεδία έχουν Ï€Ïόθεμα αστεÏίσκους (**)',
+'moreinfomodify' => 'ΜποÏείτε να βÏείτε πεÏισσότεÏες πληÏοφοÏίες για αυτήν την εÏγασία στην ακόλουθη διεÏθυνση URL:',
+'nolongerassigned' => 'Η ακόλουθη εÏγασία δεν είναι πια ανατεθειμένη σε εσάς. Είναι πλέον ανατεθειμένη σε',
+'hasassigned' => 'σας ανέθεσε την ακόλουθη εÏγασία στο Flyspray:',
+'taskupdated' => 'Η εÏγασία ενημεÏώθηκε.',
+'tasksupdated' => 'Οι εÏγασίες έχουν ενημεÏωθεί.',
+'hasclosedassigned' => 'έκλεισε την ακόλουθη εÏγασία στο Flyspray η οποία σας είχε ανατεθεί:',
+'unassigned' => 'Μη ανατεθειμένη',
+'hasclosed' => 'έκλεισε την ακόλουθη εÏγασία',
+'youonnotify' => 'Αυτό το λαμβάνετε γιατί είστε στη λίστα ειδοποιήσεων.',
+'taskclosedmsg' => 'Η εÏγασία έχει κλειστεί.',
+'returntotask' => 'ΕπιστÏοφή στην πεÏιγÏαφή της εÏγασίας',
+'backtoindex' => 'ΕπιστÏοφή στη λίστα εÏγασιών',
+'noclosereason' => 'Δεν επιλέξατε αιτιολογία για το κλείσιμο αυτής της εÏγασίας.',
+'hasreopened' => 'άνοιξε εκ νέου την ακόλουθη εÏγασία στο Flyspray την οποία είχατε κλείσει:',
+'taskreopenedmsg' => 'H εÏγασία ανοίχτηκε εκ νέου.',
+'backtotask' => 'ΕπιστÏοφή στην εÏγασία.',
+'commentaddedmsg' => 'Το σχόλιο Ï€Ïοστέθηκε.',
+'commenttoassigned' => 'Ï€Ïοσέθεσε ένα σχόλιο σε μια εÏγασία που σας έχει ανατεθεί:',
+'commenttotask' => 'Ï€Ïοσέθεσε το ακόλουθο σχόλιο σε αυτήν την εÏγασία.',
+'nocommententered' => 'ΠÏέπει να εισαγάγετε όντως κάποιο σχόλιο Ï€Ïιν κάνετε κλικ στο κουμπί υποβολής.',
+'fillinfields' => 'Δεν συμπληÏώσατε όλα τα πεδία.',
+'notcurrentpass' => 'Δεν είναι αυτό το Ï„Ïέχον συνθηματικό σας!',
+'passchanged' => 'Το συνθηματικό σας άλλαξε.',
+'closewindow' => 'ΜποÏείτε Ï„ÏŽÏα να κλείσετε αυτό τα παÏάθυÏο.',
+'passnomatch' => 'Τα νέα συνθηματικά σας δεν ήταν ίδια Î¼ÎµÏ„Î±Î¾Ï Ï„Î¿Ï…Ï‚!',
+'usernametaken' => 'Αυτό το όνομα χÏήστη είναι κατειλημμένο. Θα χÏειαστεί να επιλέξετε κάποιο άλλο. ',
+'usernametakenbulk' => 'Το όνομα χÏήστη είναι κατειλημμένο.',
+'newusercreated' => 'Ο νέος λογαÏιασμός χÏήστη δημιουÏγήθηκε.',
+'accountcreated' => 'Ο λογαÏιασμός σας δημιουÏγήθηκε.',
+'newuserwarning' => 'Îα σημειωθεί ότι οι γενικές Ïυθμίσεις μποÏεί να απαιτοÏν έγκÏιση του λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï ÏƒÎ±Ï‚ από διαχειÏιστή. Εάν δεν μποÏείτε να συνδεθείτε, μάλλον αυτός είναι ο λόγος.',
+'nomatchpass' => 'Τα συνθηματικά δεν ταιÏιάζουν.',
+'confirmwrong' => 'Ο κωδικός επαλήθευσης είναι εσφαλμένος!',
+'formnotcomplete' => 'Η φόÏμα δεν συμπληÏώθηκε πλήÏως.',
+'formnotnumeric' => 'Τα εισαχθέντα δεδομένα δεν ήταν αÏιθμητικά!',
+'groupnametaken' => 'Αυτό το όνομα ομάδας είναι ήδη κατειλημμένο.',
+'newgroupadded' => 'ΠÏοστέθηκε νέα ομάδα.',
+'optionssaved' => 'Οι επιλογές του Flyspray αποθηκεÏτηκαν.',
+'hasuploaded' => 'ανέβασε ένα συνημμένο αÏχείο σε μια εÏγασία που έχει ανατεθεί σε:',
+'hasattached' => 'επισÏναψε ένα αÏχείο στην ακόλουθη εÏγασία.',
+'fileuploaded' => 'Το αÏχείο ανέβηκε.',
+'fileerror' => 'ΠαÏουσιάστηκε ένα σφάλμα κατά το ανέβασμα του αÏχείου σας. Ίσως τα δικαιώματα στο φάκελο <i>attachments/</i> να μην είναι σωστά.',
+'contactadmin' => 'Επικοινωνήστε με το διαχειÏιστή για αυτό το έÏγο.',
+'selectfileerror' => 'Δεν επιλέξατε αÏχείο.',
+'userupdated' => 'Τα στοιχεία χÏήστη ενημεÏώθηκαν',
+'realandemail' => 'Δεν συμπληÏώσατε και τα δÏο πεδία, Ï€Ïαγματικό όνομα και διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου.',
+'groupupdated' => 'Ο οÏισμός ομάδας ενημεÏώθηκε.',
+'groupanddesc' => 'Δεν συμπληÏώσατε το όνομα ομάδας.',
+'fillallfields' => 'ΠαÏακαλοÏμε συμπληÏώστε όλα τα πεδία.',
+'listPmustN' => 'Η καταχώÏιση στη «ΣειÏά» Ï€Ïέπει να είναι αÏιθμητική.',
+'listupdated' => 'Η λίστα ενημεÏώθηκε.',
+'listitemadded' => 'ΠÏοστέθηκε νέο στοιχείο στη λίστα.',
+'relatedaddedmsg' => 'Η σχετική εÏγασία Ï€Ïοστέθηκε στη λίστα.',
+'relatederror' => 'Αυτή η εÏγασία είναι ήδη στη λίστα σχετικών εÏγασιών.',
+'relatedremoved' => 'Η σχετική εÏγασία καταÏγήθηκε από τη λίστα.',
+'notifyadded' => 'Ο χÏήστης Ï€Ïοστέθηκε στη λίστα ειδοποιήσεων.',
+'notifyerror' => 'Ο χÏήστης είναι ήδη στη λίστα ειδοποιήσεων αυτής της εÏγασίας.',
+'notifyremoved' => 'Ο χÏήστης καταÏγήθηκε από τη λίστα ειδοποιήσεων.',
+'editcommentsaved' => 'Το ενημεÏωμένο σχόλιο αποθηκεÏτηκε.',
+'commentdeletedmsg' => 'Το σχόλιο διεγÏάφη.',
+'gotonewtask' => 'Μετάβαση στην νέα εÏγασία που μόλις δημιουÏγήσατε',
+'projectcreated' => 'Το νέο σας έÏγο έχει δημιουÏγηθεί. ΜποÏείτε Ï„ÏŽÏα να Ï€ÏοσαÏμόσετε το έÏγο στην πεÏιοχή διαχείÏισης έÏγου.',
+'customiseproject' => 'ΠÏοσαÏμογή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… έÏγου',
+'projectupdated' => 'Οι Ï€Ïοτιμήσεις του έÏγου ενημεÏώθηκαν',
+'emptytitle' => 'Αφήσατε ασυμπλήÏωτο τον τίτλο του έÏγου. Πηγαίνετε πίσω και διοÏθώστε το.',
+'loginbelow' => 'ΤώÏα μποÏείτε να Ï€Ïοσπαθήσετε να συνδεθείτε.',
+'attachmentdeletedmsg' => 'Το συνημμένο διεγÏάφη',
+'reminderaddedmsg' => 'Η υπενθÏμισή σας Ï€Ïοστέθηκε.',
+'reminderdeletedmsg' => 'Η επιλεγμένη υπενθÏμιση διεγÏάφη.',
+'flyspraytask' => 'ΕÏγασία Flyspray',
+'fieldsmissing' => 'ΜεÏικά πεδία πεÏιέχουν μη έγκυÏα ή καθόλου δεδομένα.',
+'relatedinvalid' => 'Δεν υπάÏχει τέτοια εÏγασία.',
+'relatedproject' => 'Αυτή η εÏγασία είναι συνημμένη σε άλλο έÏγο. Îα Ï€Ïοστεθεί σχέση οÏτως ή άλλως;',
+'addanyway' => 'ΠÏοσθήκη οÏτως ή άλλως',
+'cancel' => 'ΑκÏÏωση',
+'alreadyedited' => 'Αυτή η εÏγασία Ï„Ïοποποιήθηκε από κάποιον άλλον Ï€Ïιν την αποθηκεÏσετε. Θέλετε ακόμα να αποθηκεÏσετε τις αλλαγές;',
+'saveanyway' => 'Αποθήκευση των αλλαγών μου οÏτως ή άλλως',
+'nouserselected' => 'Δεν επιλέγη χÏήστης. Επιλέξτε τουλάχιστον έναν χÏήστη Ï€Ïιν ξαναπÏοσπαθήσετε.',
+'groupswitchupdated' => 'Οι ομάδες χÏηστών Ï„Ïοποποιήθηκαν επιτυχώς.',
+'takenownershipmsg' => 'Αυτή η εÏγασία έχει πλέον ανατεθεί σε εσάς.',
+'adminrequestmade' => 'Το αίτημά σας απεστάλη σε διαχειÏιστή έÏγου.',
+'newdepnotify' => 'Μια νέα εξάÏτηση Ï€Ïοστέθηκε στην ακόλουθη εÏγασία:',
+'dependadded' => 'Η εξάÏτηση από άλλη εÏγασία Ï€Ïοστέθηκε',
+'dependaddfailed' => 'ΥπήÏξε ένα σφάλμα κατά την Ï€Ïοσθήκη της εξάÏτησης. Ελέγξτε ότι η εÏγασία υπάÏχει και ότι δεν υπάÏχει κώλυμα και από τις δÏο πλευÏές.',
+'depremovedmsg' => 'Η εξάÏτηση της εÏγασίας καταÏγήθηκε',
+'newdepis' => 'Η νέα εξάÏτηση είναι',
+'magicurlsent' => 'Εστάλη μήνυμα στη διεÏθυνση ειδοποιήσεών σας. ΠεÏιέχει σÏνδεσμο που θα σας μεταφέÏει σε μια σελίδα για να ολοκληÏώσετε αυτήν την εÏγασία.',
+'changefspass' => 'Αλλαγή ÏƒÏ…Î½Î¸Î·Î¼Î±Ï„Î¹ÎºÎ¿Ï ÏƒÏ„Î¿ Flyspray',
+'magicurlmessage' => 'ΠαÏακαλοÏμε ακολουθήστε τον παÏακάτω σÏνδεσμο για να αλλάξετε το συνθηματικό σας στο Flyspray:',
+'erroronform' => 'ΥπήÏξε κάποιο Ï€Ïόβλημα με την υποβολή της φόÏμας σας',
+'addressused' => 'Η παÏοÏσα διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου χÏησιμοποιήθηκε για την εγγÏαφή ενός λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï ÏƒÏ„Î¿ Flyspray. Αν δεν αναμένατε αυτό το μήνυμα, παÏακαλοÏμε αγνοήστε το και διαγÏάψτε το.',
+'confirmcodeis' => 'Ο κωδικός επαλήθευσής σας είναι:',
+'codesent' => 'Ο κωδικός επαλήθευσής σας έχει αποσταλεί. ΠαÏακαλοÏμε ακολουθήστε τις οδηγίες που υπάÏχουν στο μήνυμα.',
+'codenotsent' => 'Ο κωδικός σας δεν ήταν δυνατόν να αποσταλεί, παÏακαλοÏμε ξαναδοκιμάστε αÏγότεÏα.',
+'taskmadeprivatemsg' => 'Αυτή η εÏγασία μετετÏάπη σε ιδιωτική',
+'taskmadepublicmsg' => 'Αυτή η εÏγασία μετετÏάπη ξανά σε δημόσια',
+'realandnotify' => 'ΠÏέπει να συμπληÏώσετε το πεδίο «ΠÏαγματικό όνομα» καθώς επίσης και, είτε το πεδίο «ΔιεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου» είτε το πεδίο «ΑναγνωÏιστικό Jabber».',
+'pmreqdeniedmsg' => 'Το αίτημα του διαχειÏιστή έÏγου αποÏÏίφθηκε',
+'massopsuccess' => 'Οι μαζικές ενέÏγειες ήταν επιτυχείς - όπου το επιτÏέπουν τα δικαιώματα',
+'usernotexist' => 'Αυτός ο χÏήστης δεν υπάÏχει σε αυτό το Flyspray',
+'commentattachperms' => 'Δεν μποÏείτε να διαγÏάψετε αυτό το σχόλιο - δεν έχετε εξουσιοδότηση να διαγÏάφετε συνημμένα',
+'voterecorded' => 'Η ψήφος σας καταγÏάφηκε',
+'votefailed' => 'Η ψήφος δεν ήταν δυνατόν να Ï€Ïοστεθεί αυτή τη στιγμή.',
+'createnewgroup' => 'ΔημιουÏγία νέας ομάδας',
+'requiredfields' => 'Τα απαιτοÏμενα πεδία σημειώνονται με',
+'addthisgroup' => 'ΠÏοσθήκη αυτής της ομάδας',
+'createnewproject' => 'ΔημιουÏγία νέου έÏγου',
+'addnewproject' => 'ΠÏοσθήκη νέου έÏγου',
+'htmlallowed' => 'ΕπιτÏέπεται κώδικας HTML',
+'createthisproject' => 'ΔημιουÏγία Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… έÏγου',
+'inlineimages' => 'Εμφάνιση των συνημμένων εικόνων ένθετα',
+'createnewtask' => 'ΔημιουÏγία νέας εÏγασίας στο έÏγο:',
+'addanother' => 'ΠÏοσθήκη κι άλλης εÏγασίας μετά από αυτήν εδώ',
+'addthistask' => 'ΠÏοσθήκη αυτής της εÏγασίας',
+'notifyme' => 'Îα ειδοποιοÏμαι κάθε φοÏά που Ï„Ïοποποιείται αυτή η εÏγασία',
+'newtask' => 'Îέα εÏγασία',
+'attachafile' => 'ΕπισÏναψη αÏχείου',
+'registernewuser' => 'ΕγγÏαφή νέου χÏήστη',
+'none' => 'Κανένα',
+'registeraccount' => 'ΕγγÏαφή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… λογαÏιασμοÏ',
+'registerbulkaccount' => 'ΕγγÏαφή λογαÏιασμών',
+'both' => 'Και τα δÏο',
+'notifyfrom' => 'Ειδοποίηση από _',
+'donotreply' => 'ΑΥΤΟ ΕΙÎΑΙ ΕÎΑ ΑΥΤΟΜΑΤΟΠΟΙΗΜΕÎΟ ΜΗÎΥΜΑ, ΜΗΠΑΠΑÎΤΑΤΕ.',
+'disclaimer' => 'Λαμβάνετε αυτό το μήνυμα γιατί το έχετε ζητήσει από το σÏστημα παÏακολοÏθησης σφαλμάτων Flyspray. Αν δεν αναμένατε αυτό το μήνυμα ή αν δεν επιθυμείτε να λαμβάνετε μηνÏματα στο μέλλον, μποÏείτε να αλλάξετε τις Ïυθμίσεις ειδοποιήσεών σας στην παÏαπάνω διεÏθυνση URL.',
+'userwho' => 'ΧÏήστης που το έκανε αυτό',
+'moreinfo' => 'ΜποÏείτε να βÏείτε πεÏισσότεÏες πληÏοφοÏίες στην ακόλουθη διεÏθυνση URL:',
+'newtaskopened' => 'Μια νέα εÏγασία έχει ανοιχτεί στο Flyspray. Όλες οι λεπτομέÏειες παÏακάτω.',
+'notify.taskclosed' => 'Η ακόλουθη εÏγασία είναι πλέον κλεισμένη:',
+'notify.taskreopened' => 'Η ακόλουθη εÏγασία ανοίχτηκε εκ νέου:',
+'newdep' => 'Η ακόλουθη εÏγασία έχει νέα εξάÏτηση:',
+'notify.depremoved' => 'Η ακόλουθη εÏγασία είχε μια εξάÏτηση που καταÏγήθηκε:',
+'olddepwas' => 'Η παλιά εξάÏτηση ήταν',
+'notify.commentadded' => 'Στην ακόλουθη εÏγασία Ï€Ïοστέθηκε νέο σχόλιο:',
+'commentis' => 'Το κείμενο του σχολίου είναι παÏακάτω.',
+'newattachment' => 'Ένα νέο αÏχείο Ï€Ïοστέθηκε στην ακόλουθη εÏγασία:',
+'detailsbelow' => 'Οι λεπτομέÏειες είναι παÏακάτω.',
+'notify.relatedadded' => 'Μια νέα σχετική εÏγασία Ï€Ïοστέθηκε στην ακόλουθη εÏγασία:',
+'relatedis' => 'Η σχετική εÏγασία είναι',
+'assignedtoyou' => 'Σας έχει ανατεθεί η ακόλουθη εÏγασία:',
+'takenownership' => 'ανέλαβε την κυÏιότητα της ακόλουθης εÏγασίας:',
+'requiresaction' => 'Η ακόλουθη εÏγασία απαιτεί ενέÏγεια από διαχειÏιστή έÏγου:',
+'requiresactionnotify' => 'ΕÏγασίες που απαιτοÏν ενέÏγεια από διαχειÏιστή έÏγου',
+'pmdeny' => 'Ένας διαχειÏιστής έÏγου απέÏÏιψε το αίτημα που εκκÏεμοÏσε για την ακόλουθη εÏγασία:',
+'pmdenynotify' => 'Ένας διαχειÏιστής έÏγου απέÏÏιψε το αίτημα',
+'fileaddedtoo' => 'Ένα ή πεÏισσότεÏα αÏχεία επισυνάφθηκαν.',
+'taskwatching' => 'Η ακόλουθη εÏγασία την οποίαν παÏακαλουθείτε',
+'isdepfor' => 'αποτελεί νέα εξάÏτηση για',
+'denialreason' => 'Αιτιολογία απόÏÏιψης',
+'taskchanged' => 'Η ακόλουθη εÏγασία έχει Ï„Ïοποποιηθεί. Οι αλλαγές καταγÏάφονται παÏακάτω. Για πλήÏεις πληÏοφοÏίες σχετικά με το τι έχει αλλάξει, επισκεφτείτε τη διεÏθυνση URL και κάντε κλικ στην καÏτέλα «ΙστοÏικό».',
+'useraddedtoassignees' => 'Ένας χÏήστης Ï€Ïοσέθεσε τον εαυτό του στη λίστα χÏηστών που τους έχει ανατεθεί αυτή η εÏγασία.',
+'removeddepis' => 'Η εξάÏτηση που καταÏγήθηκε ήταν',
+'isnodepfor' => 'δεν αποτελεί πλέον εξάÏτηση για',
+'usergroups' => 'Ομάδες χÏηστών',
+'pmtoolbox' => 'ΕÏγαλειοθήκη διαχειÏιστή έÏγου',
+'groupmanage' => 'ΔιαχείÏιση ομάδων',
+'pendingrequests' => 'ΕκκÏεμή αιτήματα',
+'reasongiven' => 'Αιτιολογία που δόθηκε',
+'nopendingreq' => 'Δεν υπάÏχουν εκκÏεμή αιτήματα Ï€Ïος το διαχειÏιστή έÏγου.',
+'givereason' => 'Αιτιολόγηση',
+'catlisted' => 'ΕπεξεÏγασία λίστας κατηγοÏιών',
+'oslisted' => 'ΕπεξεÏγασία λίστας λειτουÏγικών συστημάτων',
+'verlisted' => 'ΕπεξεÏγασία λίστας εκδόσεων',
+'tasktypeed' => 'ΕπεξεÏγασία λίστας Ï„Ïπων εÏγασιών',
+'resed' => 'ΕπεξεÏγασία λίστας επιλÏσεων',
+'deny' => 'ΑπόÏÏιψη',
+'notifiedwhen' => 'Ειδοποίηση όταν',
+'onlynewtasks' => 'Ανοίχτηκαν νέες εÏγασίες',
+'allevents' => 'Οποιαδήποτε εÏγασία που απαντά σε οποιοδήποτε έÏγο',
+'feeds' => 'Ροές',
+'feeddescription' => 'ΠεÏιγÏαφή Ïοής',
+'feedimgurl' => 'ΔιεÏθυνση URL εικόνας Ïοής (να αφεθεί κενό για μη εικόνα)',
+'notifysubject' => 'Θέμα ειδοποιήσεων',
+'notifysubjectinfo' => '(%p = τίτλος έÏγου, %s = σÏνοψη εÏγασίας, %t = αναγνωÏιστικό εÏγασίας, %a = ενέÏγεια, %u = χÏήστης)',
+'priority6' => 'ΑστÏαπιαία',
+'priority5' => 'Άμεση',
+'priority4' => 'Επείγουσα',
+'priority3' => 'Υψηλή',
+'priority2' => 'Κανονική',
+'priority1' => 'Χαμηλή',
+'sendcode' => 'Αποστολή κωδικοÏ!',
+'entercode' => 'Εισαγάγετε τον κωδικό επαλήθευσης που λάβατε στο ειδοποιητήÏιο μήνυμά σας. Επίσης εισαγάγετε το επιθυμητό συνθηματικό για το λογαÏιασμό σας.',
+'confirmationcode' => 'Κωδικός επιβεβαίωσης',
+'registererror' => 'ΣιγουÏευτείτε ότι έχετε συμπληÏώσει όλα τα απαιτοÏμενα πεδία και ότι έχετε εισαγάγει ακÏιβή στοιχεία για τον επιθυμητό Ï„Ïπο ειδοποιήσεων.',
+'validusername' => '(επιτÏέπονται μόνο λατινικοί χαÏακτήÏες, νοÏμεÏα και κάτω παÏλα «_»)',
+'validemail' => '(χÏησιμοποιήστε ; για να διαχωÏίσετε πολλαπλές διευθÏνσεις ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου)',
+'emailtakenbulk' => 'Η διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου είναι κατειλημμένη ',
+'emailtaken' => 'Αυτή η διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου ή το αναγνωÏιστικό Jabber είναι κατειλημμένη. Θα χÏειαστεί να επιλέξετε κάποια άλλη.',
+'note' => '<strong>Σημείωση:</strong> ΠÏÎ¿Ï„Î¿Ï Î½Î± δημιουÏγηθεί ο λογαÏιασμός σας θα σας αποσταλεί ένας κωδικός επαλήθευσης. Ο κωδικός θα αποσταλεί μέσω της Ï€Ïοτιμώμενης μεθόδου ειδοποιήσεων, παÏαπάνω.<br />Αν εισαγάγετε ψευδείς πληÏοφοÏίες <strong>δεν θα λάβετε τον κωδικό σας</strong>.',
+'changelog' => 'ΜητÏώο αλλαγών',
+'changeloggen' => 'ΓεννήτÏια μητÏώου αλλαγών',
+'listfrom' => 'Λίστα εγγÏαφών στο μητÏώο αλλαγών από',
+'to' => 'έως',
+'oldestfirst' => 'Οι πιο παλιές Ï€Ïώτες',
+'recentfirst' => 'Οι πιο Ï€Ïόσφατες Ï€Ïώτες',
+'severityrep' => 'ΑναφοÏά σοβαÏότητας',
+'totalopen' => 'Συνολικές ανοιχτές εÏγασίες',
+'age' => 'Ηλικία',
+'agerep' => 'ΑναφοÏά ηλικίας',
+'eventsrep' => 'ΑναφοÏά συμβάντων',
+'events' => 'Συμβάντα',
+'Tasks' => 'ΕÏγασίες',
+'opened' => 'Ανοικτές',
+'edited' => 'ΤÏοποποιημένες',
+'assigned' => 'Ανατεθειμένες',
+'within' => 'Εντός',
+'pastday' => 'Την Ï€ÏοηγοÏμενη ημέÏα',
+'pastweek' => 'Την Ï€ÏοηγοÏμενη εβδομάδα',
+'pastmonth' => 'Τον Ï€ÏοηγοÏμενο μήνα',
+'pastyear' => 'Το Ï€ÏοηγοÏμενο έτος',
+'nolimit' => 'ΧωÏίς ÏŒÏιο',
+'from' => 'Από',
+'duein' => 'ΠÏοθεσμία',
+'selectfromdate' => 'Επιλογή ημεÏομηνίας «Από»',
+'selecttodate' => 'Επιλογή ημεÏομηνίας «Έως»',
+'showvoters' => 'Εμφάνιση/απόκÏυψη ψηφοφόÏων',
+'roadmap' => 'ΧάÏτης ποÏείας',
+'roadmapfor' => 'ΧάÏτης ποÏείας για',
+'tasks' => 'εÏγασίες',
+'completed' => 'ολοκληÏώθηκε.',
+'opentasks' => 'ανοικτές εÏγασίες',
+'of' => '% από',
+'severity5' => 'ΚÏίσιμη',
+'severity4' => 'Υψηλή',
+'severity3' => 'ΜέτÏια',
+'severity2' => 'Χαμηλή',
+'severity1' => 'Î Î¿Î»Ï Ï‡Î±Î¼Î·Î»Î®',
+'Redirect' => 'ΑνακατεÏθυνση',
+'redirectmsg' => 'Αν ο πεÏιηγητής σας δεν υποστηÏίζει ανακατεÏθυνση meta παÏακαλοÏμε κάντε κλικ %sΕΔΩ%s για να ανακατευθυνθείτε.',
+'allowclosedcomments' => 'Îα επιτÏέπονται σχόλια σε κλεισμένες εÏγασίες',
+'comment' => 'Σχόλιο',
+'editowncomments' => 'ΕπεξεÏγασία ιδίων σχολίων',
+'reopened' => 'Εκ νέου ανοιγμένες ',
+'loading' => 'ΦόÏτωση σε εξέλιξη...',
+'notifyown' => 'Ειδοποίηση για ιδίες αλλαγές',
+'youremail' => 'ΔιεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου σας',
+'thankyouforbug' => 'ΕυχαÏιστοÏμε που υποβάλλατε αναφοÏά για το Ï€Ïόβλημά σας. ΜποÏείτε να δείτε την εÏγασία και να παÏακολουθείτε την Ï€Ïόοδό της οποιαδήποτε στιγμή χÏησιμοποιώντας την ακόλουθη διεÏθυνση URL:',
+'anonuser' => 'Ανώνυμος χÏήστης',
+'conflict' => 'ΣÏγκÏουση',
+'file' => 'ΑÏχείο',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Μέγεθος',
+'projectgroup' => 'Ομάδα έÏγου',
+'profile' => 'ΠÏοφίλ:',
+'viewprofile' => 'ΠÏοβολή Ï€Ïοφίλ',
+'regdate' => 'ΕγγεγÏαμμένος από',
+'tasksopened' => 'ΕÏγασίες που έχει ανοίξει',
+'replyto' => 'Απάντηση σε',
+'notifytypes' => 'ΤÏποι ειδοποιήσεων',
+'pm.taskchanged' => 'ΤÏοποποίηση εÏγασίας',
+'pm.taskreopened' => 'Εκ νέου άνοιγμα εÏγασίας',
+'pm.depadded' => 'ΠÏοσθήκη εξάÏτησης',
+'pm.depremoved' => 'ΚατάÏγηση εξάÏτησης',
+'pmrequest' => 'PM request',
+'pmrequestdenied' => 'PM request denied',
+'newassignee' => 'Îέος ανάδοχος',
+'revdepadded' => 'ΠÏοσθήκη αντίστÏοφης εξάÏτησης',
+'revdepaddedremoved' => 'ΚατάÏγηση αντίστÏοφης εξάÏτησης',
+'assigneeadded' => 'ΠÏοσθήκη αναδόχου',
+'addusergroup' => 'ΠÏοσθήκη χÏήστη σε αυτήν την ομάδα',
+'groupmembers' => 'Μέλη ομάδας',
+'deleteuser' => 'ΔιαγÏαφή χÏήστη',
+'userdeleted' => 'Ο χÏήστης διεγÏάφη',
+'autoassign' => 'Αυτόματη ανάθεση των εÏγασιών σε αυτόν που έχει την κυÏιότητα της κατηγοÏίας',
+'ssl' => 'SSL',
+'updatewrong' => 'Έχετε ενεÏγοποιημένη τη λειτουÏγία ελέγχου για ενημεÏώσεις, αλλά συνέβη κάποιο σφάλμα κατά τη σÏνδεση στο διακομιστή ενημεÏώσεων, είτε γιατί ο πάÏοχός σας δεν επιτÏέπει εξεÏχόμενες συνδέσεις, είτε το σφάλμα Ï€Ïοκλήθηκε από Ï€Ïόβλημα δικτÏου.',
+'deleteproject' => 'ΔιαγÏαφή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… έÏγου και μετακίνηση των πεÏιεχομένων του σε',
+'projectdeleted' => 'Το έÏγο διαγÏάφηκε επιτυχώς',
+'feedforall' => 'Ροή για όλα τα έÏγα',
+'usercreated' => 'Ο χÏήστης δημιουÏγήθηκε',
+'created' => 'ΔημιουÏγήθηκε',
+'deleted' => 'ΔιεγÏάφη',
+'userid' => 'ΑναγνωÏιστικό χÏήστη',
+'editassignments' => 'ΕπεξεÏγασία αναθέσεων',
+'preview' => 'ΠÏοεπισκόπηση',
+'anyprogress' => 'Οποιοδήποτε ποσοστό',
+'tasksrelated' => 'ΕÏγασίες που σχετίζονται με αυτήν την εÏγασία',
+'duplicatetasks' => 'Διπλότυπες εÏγασίες αυτής της εÏγασίας',
+'databasemodfailed' => 'Η Ï„Ïοποποίηση της βάσης δεδομένων απέτυχε. Πιθανός λόγος είναι να μην υπάÏχουν επαÏκή δικαιώματα.',
+'frequency' => 'Συχνότητα',
+'newuserregistered' => 'Ένας νέος χÏήστης έχει εγγÏαφεί στο Flyspray σας. Τα στοιχεία του χÏήστη είναι τα ακόλουθα:',
+'newuserregisterednotify' => 'Ένας νέος χÏήστης έχει εγγÏαφεί',
+'notify_registration' => 'Ειδοποίηση διαχειÏιστών κατά την εγγÏαφή νέου χÏήστη',
+'textversion' => 'Έκδοση κειμένου',
+'onlyprimary' => 'ΕÏγασίες που δεν κωλÏουν άλλες εÏγασίες',
+'onlyblocker' => 'ΕÏγασίες που κωλÏουν άλλες εÏγασίες',
+'blockerornoblocker' => 'Ή κωλÏουν ή δεν κωλÏουν, το να επιλέγετε και τα δÏο φίλτÏα είναι άτοπο.',
+'switch' => 'Εναλλαγή',
+'max' => 'μέγιστο μέγεθος',
+'dates' => 'ΗμεÏομηνίες',
+'selectduedatefrom' => 'ΠÏοθεσμία από',
+'selectduedateto' => 'έως',
+'selectsincedatefrom' => 'ΤÏοποποιημένες από',
+'selectsincedateto' => 'έως',
+'selectdate' => 'Επιλογή ημεÏομηνίας',
+'selectopenedfrom' => 'Έχουν ανοιχτεί από',
+'selectopenedto' => 'έως',
+'selectclosedfrom' => 'Κλεισμένες από',
+'selectclosedto' => 'έως',
+'startat' => 'ΈναÏξη σε',
+'hasattachment' => 'Έχει συνημμένο',
+'private' => 'Ιδιωτική',
+'watching' => 'ΠαÏακολοÏθηση',
+'alreadyvotedthistask' => 'έχετε ψηφίσει αυτήν την εÏγασία',
+'alreadyvotedthisday' => 'συμπληÏώθηκαν οι ψήφοι για σήμεÏα',
+'visibility' => 'ΟÏατότητα',
+'public' => 'Δημόσια',
+'leaveemptyauto' => 'Αφήστε τα πεδία συνθηματικών κενά αν θέλετε το συνθηματικό να παÏαχθεί αυτόματα.',
+'novalidemail' => 'Δεν εισαγάγατε έγκυÏη διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου.',
+'novalidjabber' => 'Δεν εισαγάγατε έγκυÏη διεÏθυνση Jabber.',
+'missingrequired' => 'Δεν συμπληÏώσατε όλα τα απαιτοÏμενα πεδία.',
+'entervalidusername' => 'ΠαÏακαλοÏμε εισαγάγετε έγκυÏο όνομα χÏήστη και Ï€Ïαγματικό όνομα.',
+'couldnotaddusernotif' => 'Δεν ήταν δυνατή η Ï€Ïοσθήκη Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… χÏήστη στη λίστα ειδοποιήσεων.',
+'defaulttask' => 'ΠεÏιγÏαφή Ï€Ïοεπιλεγμένης εÏγασίας',
+'all' => 'όλα',
+'events.useraddedtoassignees'=> 'Ο χÏήστης Ï€Ïοστέθηκε στους αναδόχους',
+'eventlog' => 'ΜητÏώο συμβάντων',
+'assignmentchanged' => 'Η ανάθεση άλλαξε',
+'detailedinfo' => 'Αναλυτικές πληÏοφοÏίες',
+'All' => 'Όλες',
+'tasksireported' => 'ΕÏγασίες που έχω αναφέÏει',
+'recentlyopened' => 'ΠÏόσφατα ανοιγμένες',
+'stats' => 'Στατιστικά',
+'totaltasks' => 'συνολικές εÏγασίες',
+'mostwanted' => 'Οι πιο επιθυμητές εÏγασίες',
+'defaultentry' => 'ΠÏοεπιλεγμένη σελίδα εισόδου',
+'toplevel' => 'ΠÏοβολή του ανώτεÏου επιπέδου',
+'overview' => 'Επισκόπηση',
+'error#' => 'Σφάλμα Îο',
+'error1' => 'Δεν έχετε επαÏκή δικαιώματα για να Ï€Ïοβάλετε αυτό το συνημμένο.',
+'error3' => 'Επαναλαμβανόμενη ενέÏγεια. Γίνεται ανακατεÏθυνση στην αÏχική σελίδα.',
+'error4' => 'Δεν έχετε διαχειÏιστικά δικαιώματα.',
+'error5' => 'Αυτός ο χÏήστης δεν υπάÏχει σε αυτό το Flyspray',
+'error6' => 'Μη έγκυÏη διαχειÏιστική πεÏιοχή.',
+'error7' => 'Η σÏνδεση απέτυχε, εσφαλμένο συνθηματικό!',
+'error71' => 'Ο λογαÏιασμός κλειδώθηκε για %d λεπτά λόγω πολλών αποτυχημένων αποπειÏών σÏνδεσης!',
+'error8' => 'Δεν εισηγάγατε και όνομα χÏήστη και συνθηματικό.',
+'error9' => 'Η εÏγασία δεν υπάÏχει ή δεν έχετε εξουσιοδότηση να την Ï€Ïοβάλετε.',
+'error10' => 'Αυτή η εÏγασία δεν υπάÏχει.',
+'error101' => 'Δεν έχετε εξουσιοδότηση να Ï€Ïοβάλετε αυτήν την εÏγασία.',
+'error102' => 'Δεν έχετε εξουσιοδότηση να Ï€Ïοβάλετε αυτήν την εÏγασία, ίσως αν συνδεόσασταν να βοηθοÏσε.',
+'error11' => 'Δεν έχετε εξουσιοδότηση να επεξεÏγαστείτε αυτό το σχόλιο.',
+'error12' => 'Το μαγικό κλειδί δεν είναι έγκυÏο! Είστε βέβαιος ότι το λάβατε από το μήνυμα ειδοποίησής σας;',
+'error13' => 'Οι ανώνυμοι χÏήστες δεν έχουν Ï€Ïοφίλ.',
+'error14' => 'Δεν έχετε επαÏκή δικαιώματα για να δημιουÏγήσετε ομάδα.',
+'error15' => 'Δεν έχετε επαÏκή δικαιώματα για να ανοίξετε εÏγασία.',
+'error16' => 'Δεν είστε διαχειÏιστής έÏγου.',
+'error17' => 'Μη έγκυÏη πεÏιοχή διαχείÏισης έÏγου.',
+'error18' => 'Μη έγκυÏη μαγική διεÏθυνση URL.',
+'error19' => 'Αυτός ο χÏήστης δεν υπάÏχει σε αυτό το Flyspray.',
+'error20' => 'Μη έγκυÏη Ï„Ïοποποίηση βάσης δεδομένων.',
+'error21' => 'Δεν ήταν δυνατόν να αποσταλοÏν ένα ή πεÏισσότεÏα μηνÏματα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου. Ελέγξτε τις Ïυθμίσεις σας.',
+'error22' => 'Δεν επιτÏέπεται εγγÏαφή νέων χÏηστών.',
+'error23' => 'Ο χÏήστης ή η ομάδα του δεν είναι εξουσιοδιοτημένοι για σÏνδεση.',
+'error24' => 'Το Ï€ÏόγÏαμμα dot δεν έχει οÏιστεί οÏτε τοπικά οÏτε σε δημόσιο εξυπηÏετητή.',
+'error25' => 'Δεν υπάÏχει διαθέσιμος χάÏτης ποÏείας για το συγκεκÏιμένο έÏγο.',
+'error26' => 'Μη υποστηÏιζόμενος πάÏοχος OAuth',
+'error27' => 'Αδυναμία σÏνδεσης. Εξουσιοδοτήστε μας να μποÏοÏμε Ï€Ïοβάλουμε τη διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï ÏƒÎ±Ï‚ ταχυδÏομείου.',
+'error28' => 'Δεν έχετε εξουσιοδότηση να Ï€Ïοσπελάσετε αυτήν την πεÏιοχή.',
+'done' => 'ολοκληÏωμένο',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Το έÏγο δεν ήταν δυνατόν να διαγÏαφεί.',
+'GMT' => 'ÎÏα ΓκÏήνουιτς',
+'timezone' => 'Ζώνη ÏŽÏας',
+'accept' => 'Αποδοχή',
+'reasonfordeinal' => 'Αιτιολογία άÏνησης',
+'pruneclosedlinks' => 'Κλάδεμα κλεισμένων συνδέσμων',
+'pruneclosedtasks' => 'Κλάδεμα κλεισμένων εÏγασιών',
+'pagegenerated' => 'Σελίδα και εικόνα παÏήχθησαν σε %d δευτεÏόλεπτα.',
+'pruninglevel' => 'Επίπεδο κλαδέματος',
+'lastuser' => 'Ο τελευταίος χÏήστης δεν μποÏεί να διαγÏαφεί.',
+'allprivate' => 'Όλα τα έÏγα είναι ιδιωτικά.',
+'deletegroup' => 'ΔιαγÏαφή αυτής της ομάδας και μετακίνηση των χÏηστών σε',
+'parent' => 'ΜητÏική',
+'ordertip' => 'Η σειÏά με την οποίαν αυτά τα στοιχεία θα εμφανίζονται στη λίστα',
+'showtip' => 'Εμφάνιση Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… στοιχείου στη λίστα',
+'deletetip' => 'ΔιαγÏαφή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… στοιχείου από τη λίστα',
+'del' => 'διαγÏ',
+'request1' => 'Έγινε αίτημα για κλείσιμο εÏγασίας.',
+'request2' => 'Ο λογαÏιασμός σας έχει κλειδωθεί λόγω πολλών αποτυχημένων αποπειÏών Ï€Ïόσβασης.',
+'allpriorities' => 'Όλοι οι βαθμοί Ï€ÏοτεÏαιότητας',
+'noroadmap' => 'Δεν υπάÏχει διαθέσιμος χάÏτης ποÏείας (καθώς δεν έχουν οÏιστεί «μελλοντικές» εκδόσεις του συγκεκÏιμένου έÏγου)',
+'expand' => 'Ανάπτυξη',
+'collapse' => 'ΣÏμπτυξη',
+'expandall' => 'Ανάπτυξη όλων',
+'collapseall' => 'ΣÏμπτυξη όλων',
+'minpwsize' => 'Το ελάχιστο μέγεθος ÏƒÏ…Î½Î¸Î·Î¼Î±Ï„Î¹ÎºÎ¿Ï ÎµÎ¯Î½Î±Î¹ 5 χαÏακτήÏες',
+'passwordtoosmall' => 'Î Î¿Î»Ï Î¼Î¹ÎºÏÏŒ μέγεθος συνθηματικοÏ.',
+'accountwaslocked' => 'Ο λογαÏιασμός σας έχει κλειδωθεί λόγω των πολλών αποτυχημένων αποπειÏών σÏνδεσης.',
+'failedattempts' => 'ΥπήÏξαν %d αποτυχημένες απόπειÏες Ï€Ïόσβασης.',
+'groupnotexist' => 'Η επιλεγμένη ομάδα δεν υπάÏχει σε αυτό το έÏγο.',
+'searchindetails' => 'ΛεπτομέÏειες αναζήτησης',
+'showasassignees' => 'Εμφάνιση στους υποψήφιους αναδόχους',
+'find' => 'ΕÏÏεση',
+'tls' => 'TLS',
+'isadmin' => 'Είναι διαχειÏιστής',
+'addvotes' => 'ΠÏοσθήκη ψήφων',
+'removevote' => 'ΚατάÏγηση ψήφου',
+'voteremoved' => 'Η ψήφος σας έχει καταÏγηθεί',
+'voteremovefailed' => 'Η ψήφος σας δεν ήταν δυνατόν να καταÏγηθεί αυτή τη στιγμή.',
+'novotes' => 'Επί του παÏόντος δεν έχετε ψήφους σε εÏγασίες.',
+'connectedtasks' => 'Συνδεδεμένες εÏγασίες:',
+'taskdependencies' => 'ΕξαÏτήσεις εÏγασίας',
+'viewgraph' => 'Ï€Ïοβολή διαγÏάμματος',
+'notaskdependencies' => 'Αυτή η εÏγασία δεν εξαÏτάται από άλλες εÏγασίες.',
+'dependson' => 'ΕξαÏτάται από',
+'blocks' => 'ΠÏοαπαιτοÏμενη της',
+'newdependency' => 'Îέα εξάÏτηση:',
+'nouserstoadd' => 'Δεν υπάÏχουν χÏήστες για Ï€Ïοσθήκη. ΠαÏακαλοÏμε βεβαιωθείτε ότι έχουν οÏιστεί όνομα, όνομα χÏήστη και διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου για κάθε χÏήστη.',
+'dispintro' => 'ΠαÏουσίαση κÏÏιου ÎµÎ¹ÏƒÎ±Î³Ï‰Î³Î¹ÎºÎ¿Ï Î¼Î·Î½Ïματος',
+'mainmessage' => 'ΚÏÏιο εισαγωγικό μήνυμα',
+'setsupertask' => 'ΟÏισμός αναγνωÏÎ¹ÏƒÏ„Î¹ÎºÎ¿Ï Ï…Ï€ÎµÏεÏγασίας:',
+'supertaskmodified' => 'Το αναγνωÏιστικό υπεÏεÏγασίας Ï„Ïοποποιήθηκε',
+'set' => 'ΟÏισμός',
+'supertask' => 'ΥπεÏεÏγασία',
+'setparent' => 'ΟÏισμός αναγνωÏÎ¹ÏƒÏ„Î¹ÎºÎ¿Ï Î³Î¿Î½Î¹ÎºÎ®Ï‚ εÏγασίας για αυτήν την εÏγασία',
+'selfsupertasknotallowed' => 'Το αναγνωÏιστικό της υπεÏ-εÏγασίας δεν μποÏεί να είναι το ίδιο με το αναγνωÏιστικό της παÏοÏσας εÏγασίας',
+'quickaction' => 'ΓÏήγοÏες ενέÏγειες',
+'updateselectedtasks' => 'ΕνημέÏωση επιλεγμένων εÏγασιών',
+'notspecified' => 'Δεν έχει οÏιστεί',
+'editselectedtasks' => 'ΕπεξεÏγασία επιλεγμένων εÏγασιών',
+'information' => 'ΠληÏοφοÏίες',
+'taskclosedisabled' => 'Το κλείσιμο της εÏγασίας είναι επί του παÏόντος ανενεÏγό καθώς οι ακόλουθες εÏγασίες από τις οποίες εξαÏτάται η εÏγασία είναι ακόμα ανοικτές:',
+'daysleft' => 'ημέÏες απομένουν',
+'dayoverdue' => 'ημέÏες εκπÏόθεσμη',
+'duetoday' => 'ΠÏοθεσμία μέχÏι σήμεÏα.',
+'daysbeforealert' => 'ΗμέÏες Ï€Ïιν από Ï€Ïοειδοποίηση',
+'associatedsubtask' => 'Επιτυχής σÏνδεση υποεÏγασίας FS#',
+'associatesubtask' => 'ΣÏνδεση αναγνωÏÎ¹ÏƒÏ„Î¹ÎºÎ¿Ï Ï…Ï€Î¿ÎµÏγασίας με αυτήν την εÏγασία',
+'subtaskid' => 'ΑναγνωÏιστικό υπο-εÏγασίας',
+'subtaskalreadyhasparent' => 'Η υποεÏγασία που εισηγάγατε έχει ήδη γονική εÏγασία, παÏακαλοÏμε απαλείψτε την Ï€Ïιν κάνετε τη σÏνδεση.',
+'subtaskisparent' => 'Η υποεÏγασία που εισηγάγατε είναι η γονική εÏγασία αυτής της εÏγασίας. Η υποεÏγασία δεν συνδέθηκε.',
+'subtasknotexist' => 'Η υποεÏγασία που εισηγάγατε δεν υπάÏχει.',
+'subtaskremovedmsg' => 'Η υποεÏγασία έχει αφαιÏεθεί με επιτυχία',
+'subtaskadded' => 'ΠÏοστέθηκε υποεÏγασία',
+'subtaskremoved' => 'Η υποεÏγασία αφαιÏέθηκε',
+'addnewsubtask' => 'ΠÏοσθήκη νέας υποεÏγασίας',
+'hidesubtasks' => 'ΑπόκÏυψη υποεÏγασιών',
+'voteforthistask' => 'Ψήφο σε αυτήν την εÏγασία',
+'watchthistask' => 'ΠαÏακολοÏθηση αυτής της εÏγασίας',
+'privatethistask' => 'Îα γίνει αυτή η εÏγασία ιδιωτική',
+'adddependenttask' => 'ΣÏνδεση εξαÏτώμενης εÏγασίας',
+'associatetaskid' => 'ΑναγνωÏιστικό υποεÏγασίας',
+'parenttaskid' => 'ΑναγνωÏιστικό γονικής εÏγασίας',
+'invalidsupertaskid' => 'Το αναγνωÏιστικό γονικής εÏγασίας δεν είναι έγκυÏο.',
+'supertaskadded' => 'ΠÏοστέθηκε υπεÏεÏγασία',
+'supertaskremoved' => 'Η υπεÏεÏγασία αφαιÏέθηκε',
+'effort' => 'ΠÏοσπάθεια',
+'efforttracking' => 'ΠαÏακολοÏθηση Ï€Ïοσπάθειας',
+'useeffort' => 'Το έÏγο χÏησιμοποιεί παÏακολοÏθηση Ï€Ïοσπάθειας',
+'estimatedeffort' => 'Εκτιμώμενη Ï€Ïοσπάθεια',
+'totalestimatedeffort' => 'Συνολική εκτιμώμενη Ï€Ïοσπάθεια',
+'currenteffortdone' => 'ΤÏέχουσα Ï€Ïοσπάθεια που έχει επιτευχθεί',
+'starteffort' => 'ΈναÏξη παÏακολοÏθησης',
+'endeffort' => 'Διακοπή παÏακολοÏθησης',
+'cleareffort' => 'ΔιαγÏαφή παÏακολοÏθησης',
+'addeffort' => 'ΠÏοσθήκη Ï€Ïοσπάθειας',
+'manualeffort' => 'ΠÏοσθήκη Ï€Ïοσπάθειας με το χέÏι (Ω:Λ)',
+'efforttrackingstarted' => 'Η παÏακολοÏθηση Ï€Ïοσπάθειας ξεκίνησε για αυτήν την εÏγασία.',
+'efforttrackingnotstarted'=> 'ΑδÏνατη η έναÏξη παÏακολοÏθησης σε ένα θέμα που ήδη παÏακολουθείται',
+'efforttrackingstopped' => 'Η παÏακολοÏθηση Ï€Ïοσπάθειας έληξε για αυτήν την εÏγασία.',
+'efforttrackingcancelled' => 'Η παÏακολοÏθηση Ï€Ïοσπάθειας ακυÏώθηκε για αυτήν την εÏγασία.',
+'efforttrackingadded' => 'ΚαταγÏάφηκε με το χέÏι Ï€Ïοσπάθεια για αυτήν την εÏγασία.',
+'trackinginprogress' => 'ΠαÏακολοÏθηση σε εξέλιξη',
+'viewestimatedeffort' => 'Δυνατότητα Ï€Ïοβολής παÏακολοÏθησης Ï€Ïοσπάθειας',
+'viewcurrenteffortdone' => 'Δυνατότητα Ï€Ïοβολής Ï„Ïέχουσας Ï€Ïοσπάθειας που έχει δαπανηθεί',
+'trackeffort' => 'Δυνατότητα παÏακολοÏθησης Ï€Ïοσπάθειας',
+'invalideffort' => 'Η Ï€Ïοσπάθεια που εισήχθη είναι μη έγκυÏη. ΠÏέπει να είναι αÏιθμός.',
+'showpass' => 'Εμφάνιση συνθηματικοÏ',
+'chooseafile' => 'ΠαÏακαλοÏμε επιλέξτε κάποιο αÏχείο!',
+'incorrectfiletype' => 'Λανθασμένος Ï„Ïπος αÏχείου. ΕπιτÏεπτοί: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Δεν υπάÏχει δυνατότητα αίτησης συνθηματικοÏ. ΕγγÏαφήκατε μέσω %s',
+'addmultipletasks' => 'Μαζική Ï€Ïοσθήκη εÏγασιών',
+'pendingnewuserrequest' => 'ΕκκÏεμή αιτήματα νέων χÏηστών',
+'adminrequestswaiting' => 'εκκÏεμή διαχειÏιστικά αιτήματα',
+'clicktoedit' => 'Κλικ σε κάθε ένα πεδίο για γÏήγοÏη επεξεÏγασία',
+'confirmedit' => 'επιβεβαίωση',
+'regapprovedbyadmin' => 'Οι εγγÏαφές να εγκÏίνονται από διαχειÏιστές (απενεÏγοποίηση ÎºÏ‰Î´Î¹ÎºÎ¿Ï ÎµÏ€Î±Î»Î®Î¸ÎµÏ…ÏƒÎ·Ï‚)',
+'activity' => 'ΔÏαστηÏιότητα',
+'myactivity' => 'Η δÏαστηÏιότητά μου',
+'emailverificationwrong' => 'Η επαλήθευση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου δεν ταιÏιάζει με τη δεδομένη διεÏθυνση ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'verifyemailaddress' => 'Επιβεβαίωση διεÏθυνσης ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'hideemails' => 'ΑπόκÏυψη της διεÏθυνσης ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου των χÏηστών',
+'hidemyemail' => 'ΑπόκÏυψη της διεÏθυνσης ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Î¼Î¿Ï… ταχυδÏομείου',
+'exporttasklist' => 'Εξαγωγή λίστας εÏγασιών',
+'onedecimal' => 'ένα δεκαδικό',
+'manday' => 'εÏγατοημέÏα',
+'mandays' => 'εÏγατοημέÏες',
+'mandayabbrev' => 'εη',
+'hourspermanday' => 'ÎÏες ανά εÏγατοημέÏα (ΩΩ:λλ)',
+'itemexists' => 'Το στοιχείο %s υπάÏχει ήδη στη βάση δεδομένων.',
+'categoryitemexists' => 'Το στοιχείο %s υπάÏχει ήδη στην κατηγοÏία %s στη βάση δεδομένων.',
+'pageswelcomemsg' => 'Σελίδες στις οποίες να εμφανίζεται το κÏÏιο εισαγωγικό μήνυμα',
+'pagesintromsg' => 'Σελίδες στις οποίες να εμφανίζεται το εισαγωγικό μήνυμα',
+'activeoauths' => 'ΕνεÏγοί πάÏοχοι OAuth',
+'onlyoauthreg' => 'Îα επιτÏέπονται μόνο εγγÏαφές OAuth',
+'estimatedeffortformat' => 'ΜοÏφή εμφάνισης εκτιμώμενης Ï€Ïοσπάθειας',
+'currenteffortdoneformat' => 'ΜοÏφή εμφάνισης Ï„Ïέχουσας Ï€Ïοσπάθειας που έχει επιτευχθεί',
+'minute' => 'λεπτό',
+'minutes' => 'λεπτά',
+'minuteplural' => 'λεπτά',
+'minutesingular' => 'λεπτό',
+'minuteabbrev' => 'λεπ',
+'hourplural' => 'ÏŽÏες',
+'hoursingular' => 'ÏŽÏα',
+'hourabbrev' => 'ω',
+'estimatedeffortopen' => 'Εκτιμώμενη Ï€Ïοσπάθεια για ανοικτές εÏγασίες',
+'currenteffortdoneopen' => 'ΤÏέχουσα Ï€Ïοσπάθεια που έχει δαπανηθεί σε ανοικτές εÏγασίες',
+'signinwith' => 'ΣÏνδεση με %s',
+'canviewroadmap' => 'Δυνατότητα Ï€Ïοβολής χάÏτη ποÏείας',
+'enableavatars' => 'ΕνεÏγοποίηση άβαταÏ',
+'maxavatarsize' => 'Μέγιστο μέγεθος Î¬Î²Î±Ï„Î±Ï ÏƒÎµ εικονοστοιχεία',
+'taskhassubtask' => 'Αυτή η εÏγασία έχει την ακόλουθη υποεÏγασία',
+'taskhassubtasks' => 'Αυτή η εÏγασία έχει τις ακόλουθες υποεÏγασίες',
+'translations' => 'ΜεταφÏάσεις',
+'translate' => 'ΜετάφÏαση',
+'taskdescription' => 'ΠεÏιγÏαφή εÏγασίας',
+'notaskdescription' => 'χωÏίς πεÏιγÏαφή εÏγασίας',
+'pleaseselect' => 'ΠαÏακαλοÏμε επιλέξτε',
+'closeselectedtasks' => 'Κλείσιμο επιλεγμένων εÏγασιών',
+'closetasks' => 'κλείσιμο εÏγασιών',
+'hintforbulkimport' => "<b>Συμβουλές για μαζική εισαγωγή:</b>\n<ol>\n<li>ΑντιγÏάψτε και επικολλήστε από ένα λογιστικό φÏλλο Excel ή ένα CSV επικολλώντας μία ολόκληÏη στήλη.</li>\n<li>ΠÏος το παÏόν μποÏείτε να επικολλήσετε μόνο ΣÏνοψη και ΛεπτομέÏειες.</li>\n<li>ΥπάÏχουν Ï€Ïοτάσεις όταν αναθέτετε σε κάποιον, και σε κανέναν αν δεν υπάÏχει αντιστοίχιση ονόματος.</li>\n</ol>",
+'taskissubtaskof' => 'Αυτή η εÏγασία είναι υποεÏγασία της',
+'applyfirstline' => 'ΕφαÏμογή Ï€Ïώτης γÏαμμής',
+'addmorerows' => 'ΠÏοσθήκη πεÏισσότεÏων γÏαμμών',
+'addtasks' => 'ΠÏοσθήκη εÏγασιών',
+'massopsdisabled' => 'ΛυποÏμαστε, η μαζική επεξεÏγασία είναι επί του παÏόντος απενεÏγοποιημένη για το Flyspray 1.0. Σχεδιάζουμε να ολοκληÏώσουμε την υλοποίηση σε μια νεότεÏη έκδοση του Flyspray. ΜποÏείτε να τους ενεÏγοποιήσετε στον πηγαίο κώδικα ξανά με δική σας ευθÏνη, αλλά διαβάστε τα σχόλια εκεί Ï€Ïιν το κάνετε.',
+'viewroadmap' => 'Δυνατότητα Ï€Ïοβολής χάÏτη ποÏείας',
+'nosuicide' => 'Αγαπητέ χÏήστη, ο Ï€ÏογÏαμματισμός μου δεν επιτÏέπει να καταστÏέψεις την Ï€Ïόσβασή σου στο Flyspray απενεÏγοποιώντας τον ίδιο το λογαÏιασμό σου ή αλλάζοντας την ομάδα χÏηστών σου. Ο ενσυναίσθητικός αδελφός του HAL9000',
+'movingtodifferentproject'=> 'Η μετακίνηση μιας εÏγασίας που έχει είτε γονική εÏγασία είτε υποεÏγασία δεν επιτÏέπεται. ΠÏέπει Ï€Ïώτα να σπάσετε τη σÏνδεση Î¼ÎµÏ„Î±Î¾Ï Ï„Î¿Ï…Ï‚.',
+'musthavesameproject' => 'Γονική εÏγασία και υποεÏγασία Ï€Ïέπει να ανήκουν στο ίδιο έÏγο.',
+'defaultorderby' => 'ΠÏοεπιλεγμένη ταξινόμηση λίστας εÏγασιών κατά',
+'defaultorderby2' => 'μετά κατά',
+'viewowntasks' => 'ΠÏοβολή ιδίων εÏγασιών',
+'viewgroupstasks' => 'ΠÏοβολή εÏγασιών ομάδων',
+'urlrewriting' => 'ΕπανεγγÏαφή διεÏθυνσης url',
+'enablehtaccess' => 'ΠαÏακαλοÏμε ενεÏγοποιήστε το αÏχείο .htaccess σας στον Ïιζικό κατάλογο του Flyspray Ï€Ïιν ενεÏγοποιήσετε την επανεγγÏαφή διεÏθυνσης url',
+'nomodrewrite' => 'Η μονάδα επανεγγÏαφής (mod rewrite) δεν φαίνεται να είναι διαθέσιμη σε αυτόν τον εξυπηÏετητή, λυποÏμαστε αλλά δεν μποÏοÏμε να ενεÏγοποιήσουμε την επανεγγÏαφή διεÏθυνσης url.',
+'on' => 'ΕνεÏγό',
+'off' => 'Μη ενεÏγό',
+'defaultorderbydirection' => 'ΠÏοεπιλεγμένη σειÏά κατά διεÏθυνση',
+'ascending' => 'ΑÏξουσα',
+'descending' => 'Φθίνουσα',
+'myassignedtasks' => 'Οι ανατεθειμένες μου εÏγασίες',
+'commentedon' => 'σχολίασε στις',
+'maxvoteperday' => 'Μέγιστες ψήφοι ανά ημέÏα',
+'votesperproject' => 'ÎŒÏιο ψήφων χÏήστη ανά έÏγο',
+'votelimitreached' => 'Φτάσατε το ÏŒÏιο ψήφων σας για το έÏγο αυτό. Δείτε τη σελίδα του Ï€Ïοφίλ σας για το ποιες εÏγασίες έχετε ψηφισμένες επί του παÏόντος. Εκεί μποÏείτε επίσης να αποσÏÏετε ψήφους. Έτσι μποÏοÏμε να δοÏμε ποιες εÏγασίες είναι πιο σημαντικές για εσάς. Επιλυθείσες εÏγασίες επιστÏέφουν την ψήφο στο διαθέσιμο ÏŒÏιο ψήφων σας.',
+'myvotes' => 'Οι ψήφοι μου',
+'tag' => 'Ετικέτα',
+'tags' => 'Ετικέτες',
+'tagsinfo' => 'ΕλεÏθεÏη χÏήση ετικετών εÏγασιών στο Flyspray: ΔιαχωÏίστε τις ετικέτες με ; Δεν χÏησιμοποιοÏνται ακόμα για αναζήτηση, ταξινόμηση ή φιλτÏάÏισμα.',
+'novalues' => 'καμία καταχώÏιση',
+'youhaveregistered' => 'Έχετε εγγÏαφεί στο Flyspray. Τα στοιχεία σας είναι ως ακολοÏθως:',
+'youhaveregisterednotify' => 'Η εγγÏαφή σας στο Flyspray έχει γίνει δεκτή από τους διαχειÏιστές.',
+'usedintasks' => 'ΧÏήση',
+'freetagging' => 'Îα επιτÏέπονται ετικέτες οÏισμένες από χÏήστη',
+'keyboardshortcuts' => 'ΣυντομεÏσεις πληκτÏολογίου',
+'testmailsettings' => 'Δοκιμή Ï„Ïεχουσών ενεÏγών Ïυθμίσεων ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου',
+'test' => 'Δοκιμή',
+'testmailsettingsnotice' => 'Και επίσης ελέγξτε αν λάβατε το δοκιμαστικό μήνυμα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου στο λογαÏιασμό ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου του Ï„Ïέχοντος χÏήστη (βλ. σελίδα Ï€Ïοφίλ σας)',
+'invalidinput' => 'Ωπ! υπάÏχουν κάποιες ασÏμβατες ιδιότητες που έχουν οÏιστεί οι οποίες Ï€Ïέπει να επιλυθοÏν Ï€Ïιν τη μετακίνηση αυτής της εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidprogress' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη τιμή Ï€Ïοόδου.',
+'invalidpriority' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη τιμή Ï€ÏοτεÏαιότητας.',
+'invalidseverity' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη τιμή σοβαÏότητας.',
+'invalidstatus' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη κατάσταση εÏγασίας κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidcategory' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη κατηγοÏία κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidreportedversion' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη έκδοση στην οποία έγινε η αναφοÏά κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invaliddueversion' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏη έκδοση στην οποία αναμένεται κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidos' => 'ΠαÏακαλοÏμε επιλέξτε έγκυÏο λειτουÏγικό σÏστημα κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidtags' => 'ΠαÏακαλοÏμε επιλέξτε μόνο επιτÏεπτές ετικέτες κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'invalidassignees' => 'ΠαÏακαλοÏμε επιλέξτε μόνο έγκυÏους αναδόχους κατά τη μετακίνηση εÏγασίας σε διαφοÏετικό έÏγο.',
+'customize' => 'Ï„Ïοποποίηση',
+'hidesubs' => 'απόκÏυψη υποεÏγασιών',
+'hideprivate' => 'απόκÏυψη ιδιωτικών εÏγασιών',
+'hideclosed' => 'απόκÏυψη κλεισμένων εÏγασιών',
+'currentproject' => 'Ï„Ïέχον έÏγο',
+'targetproject' => 'έÏγο Ï€ÏοοÏισμοÏ',
+'availablekeybshortcuts' => 'Διαθέσιμες συντομεÏσεις πληκτÏολογίου',
+'logindialoglogout' => 'Πλαίσιο διαλόγου σÏνδεσης / αποσÏνδεσης',
+'focustaskidsearch' => 'εστίαση στο πλαίσιο αναζήτησης εÏγασιών με αναγνωÏιστικό',
+'openselectedtask' => 'άνοιγμα επιλεγμένης εÏγασίας',
+'movecursorup' => 'μετακίνηση δÏομέα επάνω',
+'movecursordown' => 'μετακίνηση δÏομέα κάτω',
+'taskdetails' => 'ΛεπτομέÏειες εÏγασίας',
+'taskediting' => 'ΕπεξεÏγασία εÏγασιών',
+'savetask' => 'αποθήκευση εÏγασίας',
+);
+
+?>
diff --git a/lang/en.php b/lang/en.php
new file mode 100644
index 0000000..98fd251
--- /dev/null
+++ b/lang/en.php
@@ -0,0 +1,1117 @@
+<?php
+
+$language = array(
+'edituser' => 'Edit user',
+'accountenabled' => 'Account Enabled',
+'editallusers' => 'View All Users',
+'username' => 'Username',
+'usersupdated' => 'Users successfully updated',
+'realname' => 'Real Name',
+'emailaddress' => 'Email Address',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Profile Image',
+'notifytype' => 'Notify Type',
+'group' => 'Group',
+'enableaccounts' => 'Enable Accounts',
+'disableaccounts' => 'Disable Accounts',
+'deleteaccounts' => 'Delete Accounts',
+'updatedetails' => 'Update details',
+'setglobally' => 'This preference has been set globally.',
+'usergroupmanage' => 'User and Group management',
+'newuser' => 'Register New User',
+'newuserbulk' => 'Register Multiple New Users',
+'bulkuserstoadd' => 'List of new users',
+'optionsforallusers' => 'Options For All New Users',
+'newgroup' => 'Create New Group',
+'yes' => 'Yes',
+'no' => 'No',
+'editgroup' => 'Edit Group',
+'groupname' => 'Group Name',
+'description' => 'Description',
+'admin' => 'Admin group',
+'opennewtasks' => 'Open new tasks',
+'modifytasks' => 'Modify existing tasks',
+'addcomments' => 'Add comments',
+'attachfiles' => 'Attach files',
+'vote' => 'Vote',
+'groupenabled' => 'Members can login',
+'groupopen' => 'Members can login',
+'tasktypelist' => 'Task Types list',
+'categorylist' => 'Categories list',
+'oslist' => 'Operating Systems list',
+'resolutionlist' => 'Resolutions list',
+'versionlist' => 'Versions list',
+'severitylist' => 'Severities list',
+'listnote' => 'Note: Unchecking the "show" box may alter some tasks when in edit mode. Changing the "Name" field will change all tasks with that name. Items that can not be deleted are either protected because they are needed for correct functioning or used in tasks.',
+'name' => 'Name',
+'order' => 'Order',
+'back' => 'Back',
+'text' => 'Text',
+'highlight' => 'Highlight',
+'show' => 'Show',
+'owner' => 'Owner',
+'selectowner' => 'Select Owner',
+'update' => 'Update',
+'addnew' => 'Add new',
+'flysprayprefs' => 'Flyspray preferences',
+'projecttitle' => 'Project Title',
+'baseurl' => 'Base URL for this installation',
+'replyaddress' => 'Reply email address for notifications',
+'themestyle' => 'Theme / Style',
+'customstyle' => 'custom',
+'language' => 'Language',
+'anonview' => 'Allow anonymous users to view tasks',
+'allowanon' => 'Allow anonymous users to open new tasks',
+'never' => 'Never',
+'anonymously' => 'Anonymously',
+'afterregister' => 'Only after registering',
+'spamproof' => 'Enable confirmation code for new user registrations',
+'anongroup' => 'Group for new user registrations',
+'groupassigned' => 'Members of these groups can be assigned tasks',
+'forcenotify' => 'Force task notifications as',
+'neversend' => 'Never send',
+'userchoose' => 'Let each user choose',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Default category owner',
+'noone' => 'No-one',
+'jabbernotify' => 'Jabber Notifications',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Account Username',
+'jabberpass' => 'Account Password',
+'saveoptions' => 'Save Options',
+'editcomment' => 'Edit Comment',
+'commentby' => 'Comment by',
+'saveeditedcomment' => 'Save edited comment',
+'projectprefs' => 'Project Preferences',
+'pagetitle' => 'Page Title',
+'defaultproject' => 'Default Project',
+'projectlists' => 'Project lists',
+'showlogo' => 'Show title logo image',
+'showgravatars' => 'Show Gravatars',
+'emailNoHTML' => 'No HTML in E-Mails',
+'intromessage' => 'Introductory message',
+'active' => 'active',
+'inactive' => 'inactive',
+'isactive' => 'Project is active',
+'showinactive' => 'Show inactive projects',
+'hideinactive' => 'Hide inactive projects',
+'createproject' => 'Create a new Project',
+'nopermission' => 'You don\'t have permission to use this page.',
+'listordertip' => 'The order these items will appear in the list',
+'listshowtip' => 'Show this item in the list',
+'categoryownertip' => 'This person will receive notifications when a task in this category is opened',
+'categoryparenttip' => 'The parent category this new one will fall under',
+'notsubcategory' => 'None (top-level category)',
+'showinlineimages' => 'Show image attachments inline',
+'dateformat' => 'Date format',
+'dateformat_extended' => 'Detailed date format',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'No caching',
+'cache_disk' => 'Cache on disk',
+'cache_db' => 'Cache in DB',
+'subcategoryof' => 'Sub-category of',
+'visiblecolumns' => 'Columns displayed in task list',
+'visiblefields' => 'Fields when add/edit/view task',
+'tense' => 'Tense',
+'listtensetip' => 'Past, Present or Future',
+'past' => 'Past',
+'present' => 'Present',
+'future' => 'Future',
+'oldpass' => 'Old Password',
+'nooldpass' => 'No Old Password was set',
+'oldpasswrong' => 'Old password was wrong',
+'changepass' => 'Change Password',
+'confirmpass' => 'Confirm Password',
+'projectmanager' => 'Project Manager',
+'viewtasks' => 'View tasks',
+'modifyowntasks' => 'Modify own tasks',
+'modifyalltasks' => 'Modify tasks that are not user\'s own',
+'viewcomments' => 'View comments',
+'editcomments' => 'Edit comments',
+'deletecomments' => 'Delete comments',
+'viewattachments' => 'View attachments',
+'createattachments' => 'Create attachments',
+'deleteattachments' => 'Delete attachments',
+'viewhistory' => 'View history',
+'closeowntasks' => 'Close own tasks',
+'closeothertasks' => 'Close tasks that are not user\'s own',
+'assigntoself' => 'Assign tasks to self if not already assigned',
+'assignotherstoself' => 'Assign others\' tasks to self',
+'viewreports' => 'View event log',
+'othersview' => 'Allow anyone to view tasks of this project',
+'othersviewroadmap' => 'Allow anyone to view roadmap of this project',
+'usersandgroups' => 'Users and Groups',
+'globalgroup' => 'Global Group',
+'globalgroups' => 'Global Groups',
+'defaultglobalgroup' => 'Default global group for new users',
+'addtogroup' => 'Add to group',
+'moveuserstogroup' => 'Move users to group',
+'nogroup' => 'No Group - Remove from project',
+'eventdesc' => 'Event Description',
+'requestedby' => 'Requested by',
+'daterequested' => 'Date Requested',
+'closetask' => 'Close Task',
+'reopentask' => 'Re-open Task',
+'applymember' => 'Apply for Project membership',
+'forcurrentproj' => 'For current project',
+'lostpw' => 'Lost password retrieval',
+'lostpwexplain' => 'Enter your username to send a link to change your password. This will be sent to the notification address in your profile.',
+'sendlink' => 'Send link',
+'savenewpass' => 'Save new password',
+'anonreg' => 'Allow new user registrations',
+'allowanonopentask' => 'Allow anonymous users to open tasks',
+'editglobalgroup' => 'Edit Global Group',
+'editgroupforproj' => 'Edit group for Project',
+'notshownforadmin' => 'Permissions are not shown for the admin group. You don\'t need to edit them.',
+'general' => 'General',
+'userregistration' => 'User Registration',
+'notifications' => 'Notifications',
+'resetoptions' => 'Reset Options',
+'preferences' => 'Preferences',
+'tasktypes' => 'Task Types',
+'resolutions' => 'Resolutions',
+'categories' => 'Categories',
+'categoriesglobal' => 'Global Categories',
+'categoriesproject' => 'Project Categories',
+'categoriestarget' => 'Target Categories',
+'operatingsystems' => 'Operating Systems',
+'versions' => 'Versions',
+'admintoolboxlong' => 'Administrator\'s Toolbox',
+'newproject' => 'New Project',
+'delete' => 'Delete',
+'link' => 'Link',
+'referencelinks' => 'Reference links:',
+'listdeletetip' => 'Delete this item from the list',
+'lookandfeel' => 'Look and Feel',
+'globaltheme' => 'Global Theme',
+'emailnotify' => 'Email Notifications',
+'fromaddress' => 'From address',
+'smtpserver' => 'SMTP Server',
+'smtpuser' => 'SMTP Username',
+'smtppass' => 'SMTP Password',
+'addrewrite' => 'Use address rewriting',
+'usereminderdaemon' => 'Enable background reminder daemon',
+'tasksperpage' => 'Tasks per page of the task list',
+'addtoassignees' => 'Add self to assignees',
+'taskstatuses' => 'Task Statuses',
+'canvote' => 'Can Vote for tasks',
+'loginsuccessful' => 'Login successful.',
+'youareloggedout' => 'You have been logged out.',
+'waitwhiletransfer' => 'Please wait while you are transferred...',
+'clicknowait' => 'Click here if you do not wish to wait.',
+'accountdisabled' => 'Your account is disabled. Contact a full admin.',
+'task' => 'Task',
+'edittask' => 'Edit this task',
+'openedby' => 'Opened by',
+'editedby' => 'Last edited by',
+'tasktype' => 'Task Type',
+'category' => 'Category',
+'status' => 'Status',
+'assignedto' => 'Assigned To',
+'operatingsystem' => 'Operating System',
+'severity' => 'Severity',
+'reportedversion' => 'Reported Version',
+'dueinversion' => 'Due in Version',
+'defaultdueinversion' => 'Default due in Version for new tasks',
+'undecided' => 'Undecided',
+'percentcomplete' => 'Percent Complete',
+'details' => 'Details',
+'savedetails' => 'Save Details',
+'canceledit' => 'Cancel Edit',
+'anonymous' => 'Anonymous Submitter',
+'complete' => 'complete',
+'closedby' => 'Closed by',
+'reasonforclosing' => 'Reason for closing:',
+'reopenthistask' => 'Re-open this task',
+'comments' => 'Comments',
+'attachments' => 'Attachments',
+'relatedtasks' => 'Related Tasks',
+'edit' => 'Edit',
+'addcomment' => 'Add comment',
+'fileuploadedby' => 'File uploaded by',
+'uploadafile' => 'Attach a file',
+'addalink' => 'Add a link',
+'addanotherlink' => 'Add another link',
+'uploadnow' => 'Upload now!',
+'thesearerelated' => 'These tasks are related to this task',
+'remove' => 'Remove',
+'addnewrelated' => 'Add a new related task',
+'add' => 'Add',
+'otherrelated' => 'Other tasks that this task is related to',
+'receivenotify' => 'These users will receive detailed notifications when this task changes.',
+'addusertolist' => 'Add user to this list',
+'addtolist' => 'Add to list',
+'addmyself' => 'Add myself to this list',
+'removemyself' => 'Remove myself from this list',
+'theseusersnotify' => 'These users will receive detailed notifications whenever this task changes.',
+'attachedtoproject' => 'Attached to Project',
+'reminders' => 'Reminders',
+'system' => 'System',
+'systemvalues' => 'System wide list values',
+'projectvalues' => 'Project specific list values',
+'remindthisuser' => 'Remind this user',
+'thisoften' => 'This often',
+'startafter' => 'Wait before starting reminders',
+'hour' => 'hour',
+'hours' => 'hours',
+'day' => 'day',
+'days' => 'days',
+'week' => 'week',
+'weeks' => 'weeks',
+'addreminder' => 'Add Reminder',
+'defaultreminder' => 'This is a reminder to look at the following Flyspray task:',
+'message' => 'Message',
+'closed' => 'Closed',
+'filename' => 'Filename:',
+'date' => 'Date',
+'filesize' => 'File Size:',
+'closurecomment' => 'Additional comments about closing:',
+'history' => 'History',
+'nohistory' => 'No history available.',
+'eventdate' => 'Date',
+'user' => 'User',
+'event' => 'Event',
+'fieldchanged' => 'Field changed',
+'taskopened' => 'Task opened',
+'taskreopened' => 'Task re-opened',
+'taskclosed' => 'Task closed',
+'commentadded' => 'Comment added',
+'commentedited' => 'Comment edited',
+'commentdeleted' => 'Comment deleted',
+'attachmentadded' => 'Attachment added',
+'attachmentdeleted' => 'Attachment deleted',
+'taskedited' => 'Task details edited',
+'notificationadded' => 'User added to notification list',
+'notificationdeleted' => 'User removed from notification list',
+'relatedadded' => 'Related task added',
+'relateddeleted' => 'Related task removed',
+'taskassigned' => 'Task assigned to',
+'taskreassigned' => 'Task reassigned to',
+'assignmentremoved' => 'Assignment removed',
+'summary' => 'Summary',
+'addedasrelated' => 'Task added to the related list of',
+'deletedasrelated' => 'Task removed from the related list of',
+'reminderadded' => 'Reminder added',
+'reminderdeleted' => 'Reminder removed',
+'priority' => 'Priority',
+'previousvalue' => 'Previous value',
+'newvalue' => 'New value',
+'selectareason' => 'Select a reason',
+'assigntome' => 'Assign to me',
+'reopenrequest' => 'Request re-open',
+'requestclose' => 'Request closure',
+'ownershiptaken' => 'User took ownership',
+'closerequestmade' => 'Requested task closure',
+'reopenrequestmade' => 'Requested task to be re-opened',
+'taskdependson' => 'The task depends upon',
+'taskdependsontask' => 'The task depends upon',
+'taskdependsontasks' => 'The task depends upon',
+'taskblock' => 'The task blocks this from closing',
+'taskblocks' => 'The task blocks these from closing',
+'depadded' => 'Dependency added',
+'depaddedother' => 'This task added as dependency',
+'depremoved' => 'Dependency removed',
+'depremovedother' => 'This task removed from other task\'s dependency list',
+'showdetailserror' => 'That task does not exist, or you don\'t have permission to view it.',
+'makeprivate' => 'make private',
+'makepublic' => 'make public',
+'taskmadeprivate' => 'Task made private',
+'taskmadepublic' => 'Privacy removed - task made public',
+'confirmdeletecomment' => 'Are you sure you want to delete this comment? %s',
+'attachementswilldeleted' => 'All attachments will also be deleted!',
+'confirmdeleteattach' => 'Are you sure you want to delete this attachment?',
+'selectedhistory' => 'Showing selected history details',
+'showallhistory' => 'Show full history tab again',
+'hidethis' => 'Hide this area again',
+'mark100' => 'Mark task 100% complete',
+'watchtask' => 'watch task',
+'stopwatching' => 'stop watching',
+'commentlink' => 'Link to this comment',
+'submitreq' => 'Submit request',
+'reasonforreq' => 'Reason for request',
+'pmreqdenied' => 'Project Manager denied request',
+'taskpendingreq' => 'Project Manager action pending. See the History tab for details.',
+'previoustask' => 'Previous task',
+'nexttask' => 'Next task',
+'duedate' => 'Due Date',
+'attachnoperms' => 'There are attachments with this comment, but you have no permission to view them.',
+'linknoperms' => 'There are links with this comment, but you have no permission to view them.',
+'open' => 'Open',
+'depgraph' => 'View Dependency Graph',
+'reset' => 'Reset',
+'selectusers' => 'Select Users...',
+'addmetoassignees' => 'Add me to assignees',
+'addedtoassignees' => 'User added to assignees list',
+'dependencygraph' => 'Dependency graph',
+'attachanotherfile' => 'Attach another file',
+'OK' => 'OK',
+'addvote' => 'Add Vote',
+'disable_lostpw' => 'Disable lost password retrieval',
+'disable_changepw' => 'Disable create/edit password',
+'notifyfromfs' => 'Notification from Flyspray',
+'autogenerated' => 'THIS IS AN AUTOMATICALLY GENERATED MESSAGE, DO NOT REPLY',
+'forward' => 'Forward',
+'previous' => 'Previous',
+'next' => 'Next',
+'first' => 'First',
+'last' => 'Last',
+'page' => 'Page %d of %d',
+'search' => 'Search',
+'alltasktypes' => 'All Task Types',
+'allseverities' => 'All Severities',
+'alldevelopers' => 'All Developers',
+'notyetassigned' => 'Not yet assigned',
+'allcategories' => 'All Categories',
+'allstatuses' => 'All Statuses',
+'allopentasks' => 'All Open Tasks',
+'sortthiscolumn' => 'Sort by this column',
+'id' => 'ID',
+'project' => 'Project',
+'dateopened' => 'Opened',
+'progress' => 'Progress',
+'searchthisproject' => 'Search this project for',
+'dueanyversion' => 'Due In Any Version',
+'anyversion' => 'Reported In Any Version',
+'dueversion' => 'Due In Version',
+'lastedit' => 'Last Edited',
+'os' => 'Operating System',
+'reportedin' => 'Reported In',
+'taskrange' => 'Showing tasks %d - %d of %d',
+'noresults' => 'Your search returned no results.',
+'takeaction' => 'Take action',
+'watchtasks' => 'Watch selected tasks',
+'stopwatchingtasks' => 'Stop watching selected tasks',
+'assigntaskstome' => 'Assign selected tasks to me',
+'dueby' => 'Due by',
+'dueanytime' => 'Due anytime',
+'selectduedate' => 'Select Due Date',
+'toggleselected' => 'Toggle Selected',
+'due' => 'Due',
+'assignedtome' => 'Assigned to myself',
+'tasklist' => 'Tasklist',
+'dateclosed' => 'Date closed',
+'advanced' => 'Advanced',
+'searchcomments' => 'Search in comments',
+'searchforall' => 'Search for all words',
+'anonusers' => 'Anonymous Users',
+'miscellaneous' => 'Miscellaneous',
+'users' => 'Users',
+'taskproperties' => 'Task Properties',
+'selectsincedate' => 'Select Changed since',
+'changedsince' => 'Changed since',
+'updatefs' => 'Please update Flyspray.',
+'currentversion' => 'Your current version is',
+'latestversion' => 'and the latest version is',
+'hidemessage' => '(remind me later)',
+'saveas' => 'Save search as',
+'nosearches' => 'No saved searches',
+'saving' => 'Saving...',
+'votes' => 'Votes',
+'tovote' => 'Vote',
+'allclosedtasks' => 'All Closed Tasks',
+'password' => 'Password',
+'login' => 'Login!',
+'rememberme' => 'Remember me',
+'lostpassword' => 'Lost password?',
+'lostpwforfs' => 'Lost password for Flyspray',
+'lostpwmsg1' => 'Hi.
+
+I have lost my password for Flyspray on ',
+'lostpwmsg2' => ', please provide me with a new password.
+
+Username: ',
+'regards' => '
+
+Regards,',
+'yourusername' => ' your username ',
+'locale' => 'en-US',
+'filenotexist' => 'File does not exist, or you do not have permission to access it.',
+'showtask' => 'Show Task',
+'now' => 'Now',
+'go' => 'Go!',
+'opentaskanon' => 'Open a new Task anonymously',
+'register' => 'Register',
+'addnewtask' => 'Add new task',
+'reports' => 'Event log',
+'editmydetails' => 'Edit my details',
+'logout' => 'Logout',
+'disabledaccount' => 'Your account has been disabled!<br />Immediately logging you out...',
+'poweredby' => 'powered by Flyspray',
+'sponsoredby' => 'Flyspray is proudly sponsored by',
+'projects' => 'Projects',
+'allprojects' => 'All Projects',
+'selectproject' => 'for Project:',
+'tasksall' => 'All tasks',
+'tasksassigned' => 'Tasks assigned to me',
+'tasksreported' => 'Tasks reported by me',
+'taskswatched' => 'Tasks I watch',
+'mysearch' => 'My searches',
+'admintoolbox' => 'Admin Toolbox',
+'manageproject' => 'Manage Project',
+'permissions' => 'View Permissions',
+'hide' => 'Hide',
+'pendingreq' => 'PM requests waiting',
+'errorpage' => 'Flyspray cannot provide the page you requested.
+ Perhaps you requested a task that does not exist, or you
+ do not have permission to view the page you wanted.<br /><br />
+ You may have tried to use a naughty URL to interact with the database
+ backend using SQL injection. If this is true, go to the corner and think
+ about your actions. When you return, please do not do it again!',
+'permissionsforproject' => 'Permissions for ',
+'switchto' => 'Switch to',
+'lastsearch' => 'Last search',
+'modify' => 'Modify',
+'noticefrom' => 'Notice from',
+'hasopened' => 'has OPENED A NEW Flyspray task, and assigned it to you:',
+'moreinfonew' => 'You can find more information about this bug at the Flyspray page:',
+'newtaskcategory' => 'A new Flyspray task has been opened in this category',
+'categoryowner' => 'You are receiving this because you are listed as the category owner.',
+'tasksummary' => 'Task summary:',
+'newtaskadded' => 'Your new task has been added.',
+'summaryanddetails' => 'You need to fill in both the summary and the details.',
+'summaryrequired' => 'You need to fill in a short task summary.',
+'goback' => 'Go back.',
+'messagefrom' => 'This is a message from the Flyspray bug tracking system at ',
+'hasjustmodified' => 'has just modified the following task.',
+'changedfields' => 'Changed fields are prefixed with asterisks (**)',
+'moreinfomodify' => 'You can get more information about this task at the following URL:',
+'nolongerassigned' => 'The following task is no longer assigned to you. It is now assigned to',
+'hasassigned' => 'has assigned to you the following Flyspray task:',
+'taskupdated' => 'Task has been updated.',
+'tasksupdated' => 'Tasks have been updated.',
+'hasclosedassigned' => 'has closed the following Flyspray task that you were assigned:',
+'unassigned' => 'Unassigned',
+'hasclosed' => 'has closed the following task.',
+'youonnotify' => 'You are receiving this because you are on the notification list.',
+'taskclosedmsg' => 'Task has been closed.',
+'returntotask' => 'Return to the task details',
+'backtoindex' => 'Go back to the task list',
+'noclosereason' => 'You didn\'t select a reason for closing this task.',
+'hasreopened' => 'has re-opened the following Flyspray task that you closed:',
+'taskreopenedmsg' => 'Task has been re-opened.',
+'backtotask' => 'Go back to the task.',
+'commentaddedmsg' => 'Comment has been added.',
+'commenttoassigned' => 'has added a comment to a task that you have been assigned:',
+'commenttotask' => 'has added the following comment to this task.',
+'nocommententered' => 'You really should enter a comment before clicking the submit button.',
+'fillinfields' => 'You didn\'t fill in all the fields.',
+'notcurrentpass' => 'That\'s not your current password!',
+'passchanged' => 'Your password has been changed.',
+'closewindow' => 'You can now close this window.',
+'passnomatch' => 'Your new passwords weren\'t the same!',
+'usernametaken' => 'That username is already taken. You will need to choose another one.',
+'usernametakenbulk' => 'Username Already Taken',
+'newusercreated' => 'New User Account has been created.',
+'accountcreated' => 'Your account has been created.',
+'newuserwarning' => 'Note that the global preferences might require your account to be approved by an admin. If you cannot login, this is probably why.',
+'nomatchpass' => 'Passwords didn\'t match.',
+'confirmwrong' => 'Confirmation code is incorrect!',
+'formnotcomplete' => 'Form was not completely filled in.',
+'formnotnumeric' => 'Inserted data was not numeric!',
+'groupnametaken' => 'That Group name is already taken.',
+'newgroupadded' => 'New Group added.',
+'optionssaved' => 'Flyspray Options saved.',
+'hasuploaded' => 'has uploaded a file attachment to a task that have been assigned:',
+'hasattached' => 'has attached a file to the following task.',
+'fileuploaded' => 'File has been uploaded.',
+'fileerror' => 'There was an error uploading your file. Perhaps the permissions on the <i>attachments/</i> directory are incorrect.',
+'contactadmin' => 'Contact the Administrator for this project.',
+'selectfileerror' => 'You didn\'t select a file.',
+'userupdated' => 'User details have been updated',
+'realandemail' => 'You didn\'t fill in both the Real Name and Email Address fields.',
+'groupupdated' => 'Group definition updated.',
+'groupanddesc' => 'You didn\'t fill in the group name.',
+'fillallfields' => 'Please fill in all fields.',
+'listPmustN' => '"Order" entry has to be numeric.',
+'listupdated' => 'List has been updated.',
+'listitemadded' => 'New list item added.',
+'relatedaddedmsg' => 'Related task added to list.',
+'relatederror' => 'That task is already on this related task list.',
+'relatedremoved' => 'Related task removed from list.',
+'notifyadded' => 'User added to notification list.',
+'notifyerror' => 'That user is already on the notification list for that task.',
+'notifyremoved' => 'User removed from notification list.',
+'editcommentsaved' => 'Updated comment saved.',
+'commentdeletedmsg' => 'Comment has been deleted.',
+'gotonewtask' => 'Go to the new task you just created',
+'projectcreated' => 'Your new Project has been created. You can now customise the project in the project management area.',
+'customiseproject' => 'Customise this project',
+'projectupdated' => 'Project preferences updated',
+'emptytitle' => 'You left the Project Title field blank. Go back and fix it.',
+'loginbelow' => 'You may now attempt to login.',
+'attachmentdeletedmsg' => 'The attachment has been deleted',
+'reminderaddedmsg' => 'Your reminder has been added.',
+'reminderdeletedmsg' => 'The selected reminder has been deleted.',
+'flyspraytask' => 'Flyspray task',
+'fieldsmissing' => 'Some fields contained no or invalid data.',
+'relatedinvalid' => 'There is no such task.',
+'relatedproject' => 'This task is attached to a different project. Add relation anyway?',
+'addanyway' => 'Add anyway',
+'cancel' => 'Cancel',
+'alreadyedited' => 'This task was edited by someone else before you saved. Do you still want to save your changes?',
+'saveanyway' => 'Save my changes anyway',
+'nouserselected' => 'No user selected. Select at least one user before trying again.',
+'groupswitchupdated' => 'User groups modified successfully.',
+'takenownershipmsg' => 'This task is now assigned to you.',
+'adminrequestmade' => 'Your request has been sent to a Project Manager.',
+'newdepnotify' => 'A new dependency has been added to the following task:',
+'dependadded' => 'Task dependency has been added',
+'dependaddfailed' => 'There was an error when adding the dependency. Check that the task exists and there is no mutual block.',
+'depremovedmsg' => 'Task dependency has been removed',
+'newdepis' => 'The new dependency is',
+'magicurlsent' => 'A message has been sent to your notification address. It contains a link that will take you to a page to complete this task.',
+'changefspass' => 'Change Flyspray password',
+'magicurlmessage' => 'Please follow the link below to change your Flyspray password:',
+'erroronform' => 'There was an problem with your form submission',
+'addressused' => 'This address has been used to register a Flyspray account. If you were not expecting this message, please ignore and delete it. Go to the following URL to complete your registration:',
+'confirmcodeis' => 'Your confirmation code is:',
+'codesent' => 'Your confirmation code has been sent. Please follow the instructions contained in the message.',
+'codenotsent' => 'Cannot send your code, please try again later.',
+'taskmadeprivatemsg' => 'This task has been made private',
+'taskmadepublicmsg' => 'This task has been made public again',
+'realandnotify' => 'You need to fill in the Real Name field, and either the Email Address or Jabber ID field.',
+'pmreqdeniedmsg' => 'Project Manager request denied',
+'massopsenable' => 'Enable mass operations',
+'massopsuccess' => 'Mass operations successful - where permissions allowed',
+'usernotexist' => 'That user does not exist on this Flyspray installation',
+'commentattachperms' => 'You cannot delete that comment - no permission to delete attachments',
+'voterecorded' => 'Your vote has been recorded',
+'votefailed' => 'Your vote could not be added at this time.',
+'createnewgroup' => 'Create new group',
+'requiredfields' => 'Required fields are marked with a',
+'addthisgroup' => 'Add this group',
+'createnewproject' => 'Create a new Project',
+'addnewproject' => 'Add New Project',
+'htmlallowed' => 'HTML code is allowed',
+'createthisproject' => 'Create this project',
+'inlineimages' => 'Show image attachments inline',
+'createnewtask' => 'Create a new task in project:',
+'addanother' => 'Add another task after this one',
+'addthistask' => 'Add this task',
+'notifyme' => 'Notify me whenever this task changes',
+'newtask' => 'New Task',
+'attachafile' => 'Attach a file',
+'registernewuser' => 'Register new user',
+'none' => 'None',
+'registeraccount' => 'Register this account',
+'registerbulkaccount' => 'Register accounts',
+'both' => 'Both',
+'notifyfrom' => 'Notification from ',
+'donotreply' => 'THIS IS AN AUTOMATED MESSAGE, DO NOT REPLY.',
+'disclaimer' => 'You are receiving this message because you have requested it from the Flyspray bugtracking system. If you did not expect this message or don\'t want to receive mails in future, you can change your notification settings at the URL shown above.',
+'userwho' => 'User who did this',
+'moreinfo' => 'More information can be found at the following URL:',
+'newtaskopened' => 'A new Flyspray task has been opened. Details are below.',
+'notify.taskclosed' => 'The following task is now closed:',
+'notify.taskreopened' => 'The following task has been re-opened:',
+'newdep' => 'The following task has a new dependency:',
+'notify.depremoved' => 'The following task has had a dependency removed:',
+'olddepwas' => 'The old dependency was',
+'notify.commentadded' => 'The following task has a new comment added:',
+'commentis' => 'The comment text is below.',
+'newattachment' => 'A new file has been attached to the following task:',
+'detailsbelow' => 'The details are below.',
+'notify.relatedadded' => 'A new related task has been added to the following task:',
+'relatedis' => 'The related task is',
+'assignedtoyou' => 'You have been assigned the following task:',
+'takenownership' => 'has taken ownership of the following task:',
+'requiresaction' => 'The following task requires action by a Project Manager:',
+'requiresactionnotify' => 'Task requires action by a Project Manager',
+'pmdeny' => 'A Project Manager has denied the request pending for the following task:',
+'pmdenynotify' => 'A Project Manager has denied the request',
+'fileaddedtoo' => 'One or more files have been attached.',
+'taskwatching' => 'The following task you are watching',
+'isdepfor' => 'is a new dependency for',
+'denialreason' => 'Reason for denial',
+'taskchanged' => 'The following task has been changed. The changes are listed below. For full information about what has changed, visit the URL and click the History tab.',
+'useraddedtoassignees' => 'A user has added themself to the list of users assigned to this task.',
+'removeddepis' => 'The removed dependency is',
+'isnodepfor' => 'is no dependency anymore for',
+'usergroups' => 'User Groups',
+'pmtoolbox' => 'Project Manager\'s Toolbox',
+'groupmanage' => 'Group Management',
+'pendingrequests' => 'Pending Requests',
+'reasongiven' => 'Reason given',
+'nopendingreq' => 'There are no pending Project Manager requests.',
+'givereason' => 'Give a reason',
+'catlisted' => 'Category List Editor',
+'oslisted' => 'Operating System List Editor',
+'verlisted' => 'Version List Editor',
+'tasktypeed' => 'Task Types List Editor',
+'resed' => 'Resolutions List Editor',
+'deny' => 'Deny',
+'notifiedwhen' => 'Notified when',
+'onlynewtasks' => 'New tasks are opened',
+'allevents' => 'Any event occurs in any task',
+'feeds' => 'Feeds',
+'feeddescription' => 'Feed description',
+'feedimgurl' => 'Feed image URL (leave blank for no image)',
+'notifysubject' => 'Subject for notifications',
+'notifysubjectinfo' => '(%p = project title, %s = task summary, %t = task id, %a = action, %u = user)',
+'priority6' => 'Very High',
+'priority5' => 'High',
+'priority4' => 'Medium',
+'priority3' => 'Low',
+'priority2' => 'Very Low',
+'priority1' => 'Defer',
+'sendcode' => 'Send code!',
+'entercode' => 'Enter the confirmation code you received in your notification message. Also enter your desired account password.',
+'confirmationcode' => 'Confirmation Code',
+'registererror' => 'Ensure that you have filled in all required fields, and that you have entered correct details for your desired notification type.',
+'validusername' => '(only alphanumeric chars and - _ . are allowed)',
+'validemail' => '(use ; to seperate multiple email addresses)',
+'emailtakenbulk' => 'Email Address Already Taken',
+'emailtaken' => 'That email address or Jabber-ID is already taken. You will need to choose another one.',
+'note' => '<strong>Note:</strong> You will be sent a confirmation code before your account is created. The code will be sent using your preferred notification method, above.<br />If you enter fake details, you will <strong>not receive your code</strong>.',
+'changelog' => 'Changelog',
+'changeloggen' => 'Changelog Generator',
+'listfrom' => 'List changelog entries from',
+'to' => 'to',
+'oldestfirst' => 'Oldest first',
+'recentfirst' => 'Most recent first',
+'severityrep' => 'Severity Report',
+'totalopen' => 'Total open tasks',
+'age' => 'Age',
+'agerep' => 'Age Report',
+'eventsrep' => 'Events Report',
+'events' => 'Events',
+'Tasks' => 'Tasks',
+'opened' => 'Opened',
+'edited' => 'Edited',
+'assigned' => 'Assigned',
+'within' => 'Within',
+'pastday' => 'The past day',
+'pastweek' => 'The past week',
+'pastmonth' => 'The past month',
+'pastyear' => 'The past year',
+'nolimit' => 'No limit',
+'from' => 'From',
+'duein' => 'Due in',
+'selectfromdate' => 'Select From Date',
+'selecttodate' => 'Select To Date',
+'showvoters' => 'Show/hide voters',
+'roadmap' => 'Roadmap',
+'roadmapfor' => 'Roadmap for version',
+'tasks' => 'tasks',
+'completed' => 'completed.',
+'opentasks' => 'open tasks',
+'of' => '% of',
+'severity5' => 'Critical',
+'severity4' => 'High',
+'severity3' => 'Medium',
+'severity2' => 'Low',
+'severity1' => 'Very Low',
+'Redirect' => 'Redirect',
+'redirectmsg' => 'If your browser does not support meta redirection please click %sHERE%s to be redirected',
+'allowclosedcomments' => 'Allow comments on closed tasks',
+'comment' => 'Comment',
+'editowncomments' => 'Edit own comments',
+'reopened' => 'Reopened',
+'loading' => 'Loading...',
+'notifyown' => 'Notify for own changes',
+'youremail' => 'Your e-mail address',
+'thankyouforbug' => 'Thank you for reporting your problem. You can see the task and observe its progress any time using the following URL:',
+'anonuser' => 'Anonymous user',
+'conflict' => 'Conflict',
+'file' => 'File',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Size',
+'projectgroup' => 'Project Group',
+'profile' => 'Profile:',
+'viewprofile' => 'View Profile',
+'regdate' => 'Registered since',
+'tasksopened' => 'Tasks opened',
+'replyto' => 'Reply to',
+'notifytypes' => 'Notification types',
+'pm.taskchanged' => 'Task changed',
+'pm.taskreopened' => 'Task reopened',
+'pm.depadded' => 'Dependency added',
+'pm.depremoved' => 'Dependency removed',
+'pmrequest' => 'PM request',
+'pmrequestdenied' => 'PM request denied',
+'newassignee' => 'New assignee',
+'revdepadded' => 'Reverse dependency added',
+'revdepaddedremoved' => 'Reverse dependency removed',
+'assigneeadded' => 'Assignee added',
+'addusergroup' => 'Add user to this group',
+'groupmembers' => 'Group Members',
+'deleteuser' => 'Delete this user',
+'userdeleted' => 'User deleted',
+'autoassign' => 'Auto-assign a task to the category owner',
+'ssl' => 'SSL',
+'updatewrong' => 'You have the update check feature enabled, but an error ocurred while trying
+ to contact the update server, either your host do not allow outbound connections
+ or the error was caused by a network problem.
+ Please visit the flyspray website to make sure you are running the latest version.',
+'deleteproject' => 'Delete this project and move contents to',
+'projectdeleted' => 'Project deleted successfully',
+'feedforall' => 'Feed for all projects',
+'usercreated' => 'User created',
+'userdeleted' => 'User deleted',
+'created' => 'Created',
+'deleted' => 'Deleted',
+'userid' => 'User ID',
+'editassignments' => 'Edit assignments',
+'preview' => 'Preview',
+'anyprogress' => 'Any Progress',
+'tasksrelated' => 'Tasks related to this task',
+'duplicatetasks' => 'Duplicate tasks of this task',
+'databasemodfailed' => 'Database modification failed. Possible reasons are insufficient permissions.',
+'frequency' => 'Frequency',
+'newuserregistered' => 'A new user has registered at your Flyspray installation. The user details are as follows:',
+'newuserregisterednotify' => 'A new user has registered',
+'notify_registration' => 'Notify admins on new user registration',
+'textversion' => 'Text Version',
+'onlyprimary' => 'Tasks not blocking other tasks',
+'onlyblocker' => 'Tasks blocking other tasks',
+'blockerornoblocker' => 'Blocker or nonblocker, selecting both filter options doesn\'t make sense.',
+'switch' => 'Switch',
+'max' => 'max.',
+'dates' => 'Dates',
+'selectduedatefrom' => 'Due from',
+'selectduedateto' => 'to',
+'selectsincedatefrom' => 'Changed from',
+'selectsincedateto' => 'to',
+'selectdate' => 'Select Date',
+'selectopenedfrom' => 'Opened from',
+'selectopenedto' => 'to',
+'selectclosedfrom' => 'Closed from',
+'selectclosedto' => 'to',
+'startat' => 'Start at',
+'hasattachment' => 'Has attachment',
+'private' => 'Private',
+'watching' => 'Watching',
+'alreadyvotedthistask' => 'you voted for this task',
+'alreadyvotedthisday' => 'already voted today',
+'visibility' => 'Visibility',
+'public' => 'Public',
+'leaveemptyauto' => 'Leave password fields empty if you want the password to be automatically generated.',
+'novalidemail' => 'You did not enter a valid email address.',
+'novalidjabber' => 'You did not enter a valid Jabber address.',
+'missingrequired' => 'You did not fill in all required fields.',
+'entervalidusername' => 'Please enter a valid user and real name.',
+'couldnotaddusernotif' => 'Could not add this user to the notification list.',
+'defaulttask' => 'Default task description',
+'all' => 'all',
+'events.useraddedtoassignees' => 'User added to assignees',
+'eventlog' => 'Event log',
+'assignmentchanged' => 'Assignment changed',
+'detailedinfo' => 'Detailed information',
+'All' => 'All',
+'tasksireported' => 'Tasks I reported',
+'recentlyopened' => 'Recently opened',
+'stats' => 'Stats',
+'totaltasks' => 'total tasks',
+'mostwanted' => 'Most wanted tasks',
+'defaultentry' => 'Default entry page',
+'toplevel' => 'Top level view',
+'overview' => 'Overview',
+'error#' => 'Error #',
+'error1' => 'You don\'t have sufficient permissions to view this attachment.',
+'error3' => 'Repeated action, redirecting to main page.',
+'error4' => 'You don\'t have administrative rights.',
+'error5' => 'That user does not exist on this Flyspray installation.',
+'error6' => 'Invalid admin area.',
+'error7' => 'Login failed, password incorrect!',
+'error71' => 'Account locked for %d minutes due to too many failed login attempts!',
+'error8' => 'You didn\'t enter both a username and password.',
+'error9' => 'Task does not exist or no permissions to view this task.',
+'error10' => 'This task does not exist.',
+'error101' => 'You have no permission to view this task.',
+'error102' => 'You have no permission to view this task, logging in might help.',
+'error11' => 'No permission to edit this comment.',
+'error12' => 'No valid magic key! Are you sure that you got that from your notification message?',
+'error13' => 'Anonymous users don\'t have a profile.',
+'error14' => 'You don\'t have sufficient permissions to create a new group.',
+'error15' => 'You don\'t have sufficient permissions to open a task.',
+'error16' => 'You are not a project manager.',
+'error17' => 'Invalid PM area.',
+'error18' => 'Invalid magic URL.',
+'error19' => 'That user does not exist on this Flyspray installation.',
+'error20' => 'Invalid database modification.',
+'error21' => 'One or more emails could not be sent. Check your config.',
+'error22' => 'No new user registration allowed.',
+'error23' => 'User or group not enabled for login.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Roadmap only available for a specific project.',
+'error26' => 'Unsupported oauth provider.',
+'error27' => 'Unable to login in. Authorize us to view your email address.',
+'error28' => 'You have no permission to access this area.',
+'done' => 'done',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Project couldn\'t be deleted.',
+'GMT' => 'GMT',
+'timezone' => 'Time zone',
+'accept' => 'Accept',
+'reasonfordeinal' => 'Reason for denial',
+'pruneclosedlinks' => 'Prune Closed Links',
+'pruneclosedtasks' => 'Prune Closed Tasks',
+'pagegenerated' => 'Page and image generated in %d seconds.',
+'pruninglevel' => 'Pruning Level',
+'lastuser' => 'The last user cannot be deleted.',
+'allprivate' => 'All projects are private.',
+'deletegroup' => 'Delete this group and move users to',
+'parent' => 'Parent',
+'ordertip' => 'The order these items will appear in the list',
+'showtip' => 'Show this item in the list',
+'deletetip' => 'Delete this item from the list',
+'del' => 'del',
+'request1' => 'A task closure has been requested.',
+'request2' => 'A request to re-open the task has been made.',
+'allpriorities' => 'All Priorities',
+'noroadmap' => 'No roadmap available (no project specific "future" versions exist).',
+'expand' => 'Expand',
+'collapse' => 'Collapse',
+'expandall' => 'Expand all',
+'collapseall' => 'Collapse all',
+'minpwsize' => 'Minimum password size is 5 chars',
+'passwordtoosmall' => 'Password size too small.',
+'accountwaslocked' => 'Your account had been locked due to too many failed login attempts.',
+'failedattempts' => 'There were %d failed login attempts.',
+'groupnotexist' => 'Selected group does not exist in this project.',
+'searchindetails' => 'Search details',
+'showasassignees' => 'Show as assignees',
+'find' => 'Find',
+'tls' => 'TLS',
+'isadmin' => 'Is admin',
+'addvotes' => 'Add votes',
+'removevote' => 'Remove vote',
+'voteremoved' => 'Your vote has been removed',
+'voteremovefailed' => 'Your vote could not be removed at this time.',
+'novotes' => 'You currently have no votes on any task.',
+'connectedtasks' => 'Connected tasks:',
+'taskdependencies' => 'Task dependencies',
+'viewgraph' => 'view graph',
+'notaskdependencies' => 'This task does not depend on any other tasks.',
+'dependson' => 'Depends on',
+'blocks' => 'Blocks',
+'newdependency' => 'New dependency:',
+'nouserstoadd' => 'No Users To Add. Please ensure Name, Username, and Email are defined for each user.',
+'dispintro' => 'Display main introductory message',
+'mainmessage' => 'Main introductory message',
+/* note only the English version for 'dispintro' is supported
+ * other languages should also be taken cared of in the future*/
+
+'setsupertask' => 'Set Super-Task ID:',
+'supertaskmodified' => 'Super-Task ID has been modified',
+'set' => 'Set',
+'supertask' => 'Super-Task',
+'setparent' => 'Set parent task id for this task',
+'selfsupertasknotallowed' => 'Super-Task ID cannot be same as self Task ID',
+'quickaction' => 'Quick Actions',
+'updateselectedtasks' => 'Update Selected Tasks',
+'notspecified' => 'Not Specified',
+'editselectedtasks' => 'Edit Selected Tasks',
+'information' => 'Information',
+'taskclosedisabled' => 'Close Task is currently disabled as the following dependant tasks are still open:-',
+'daysleft' => 'days left',
+'dayoverdue' => 'days overdue',
+'duetoday' => 'Due today.',
+'daysbeforealert' => 'Days before alert',
+'associatedsubtask' => 'Successfully associated subtask #FS%d',
+'associatesubtask' => 'Associate sub task ID with this task',
+'subtaskid' => 'Sub task ID',
+'subtaskalreadyhasparent' => 'The sub task you entered already has a parent task, please clear this before associating.',
+'subtaskisparent' => 'The sub task you entered is the parent task of this task. Sub task not associated.',
+'subtasknotexist' => 'The sub task you entered does not exist.',
+'subtaskremovedmsg' => 'The sub task has successfully been removed',
+'subtaskadded' => 'Sub task added',
+'subtaskremoved' => 'Sub task removed',
+'addnewsubtask' => 'Add new sub task',
+'hidesubtasks' => 'Hide SubTasks',
+'voteforthistask' => 'Vote for this task',
+'watchthistask' => 'Watch this task',
+'privatethistask' => 'Make this task private',
+'adddependenttask' => 'Associate dependent task',
+'associatetaskid' => 'Sub task id',
+'parenttaskid' => 'Parent task id',
+'invalidsupertaskid' => 'Parent task id is not valid.',
+'supertaskadded' => 'Supertask added',
+'supertaskremoved' => 'Supertask removed',
+'effort' => 'Effort',
+'efforttracking' => 'Effort Tracking',
+'useeffort' => 'Project Uses Effort Tracking',
+'estimatedeffort' => 'Estimated Effort',
+'totalestimatedeffort' => 'Total estimated effort',
+'currenteffortdone' => 'Current effort done',
+'starteffort' => 'Start Tracking',
+'endeffort' => 'Stop Tracking',
+'cleareffort' => 'Clear Tracking',
+'addeffort' => 'Add Effort',
+'manualeffort' => 'Manually Add Effort (H:M)',
+'efforttrackingstarted' => 'Effort Tracking Commenced for this task.',
+'efforttrackingnotstarted'=> 'Unable to start tracking on an issue that is already being tracked',
+'efforttrackingstopped' => 'Effort Tracking Ended for this task.',
+'efforttrackingcancelled' => 'Effort Tracking Cancelled for this task.',
+'efforttrackingadded' => 'Manual effort recorded for task.',
+'trackinginprogress' => 'Tracking in Progress',
+'viewestimatedeffort' => 'Can View Effort Tracking',
+'viewcurrenteffortdone' => 'Can View Current Effort done',
+'trackeffort' => 'Can Track Effort',
+'invalideffort' => 'The effort entered is invalid. Must be a number',
+'showpass' => 'Show Password',
+'chooseafile' => 'Please choose a file!',
+'incorrectfiletype' => 'Incorrect file type. Allowed: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'No password to request. You registered through %s',
+'addmultipletasks' => 'Add multiple tasks',
+'pendingnewuserrequest' => 'Pending New User Request',
+'adminrequestswaiting' => 'Admin requests waiting',
+'clicktoedit' => 'Click on each field to quick edit',
+'confirmedit' => 'confirm',
+'canceledit' => 'cancel',
+'regapprovedbyadmin' => 'Registrations approved by admins (disable confirmation code)',
+'activity' => 'Activity',
+'myactivity' => 'My activity',
+'emailverificationwrong' => 'The email confirmation doesn\'t match the given email address',
+'verifyemailaddress' => 'Confirm email address',
+'hideemails' => 'Hide users email addresses',
+'hidemyemail' => 'Hide my email address',
+'exporttasklist' => 'Export Tasklist',
+'onedecimal' => 'one decimal',
+'manday' => 'man-day',
+'mandays' => 'man-days',
+'mandayabbrev' => 'md',
+'hourspermanday' => 'Hours per one manday (HH:mm)',
+'itemexists' => 'Item %s already exists in database.',
+'categoryitemexists' => 'Item %s already exists under category %s in database.',
+'pageswelcomemsg' => 'Pages where to show main intro message',
+'pagesintromsg' => 'Pages where to show intro message',
+'activeoauths' => 'Active Oauth providers',
+'onlyoauthreg' => 'Only allow Oauths registrations',
+'estimatedeffortformat' => 'Estimated effort display format',
+'currenteffortdoneformat' => 'Current effort done display format',
+'minute' => 'minute',
+'minutes' => 'minutes',
+'minuteplural' => 'minutes',
+'minutesingular' => 'minute',
+'minuteabbrev' => 'min',
+'hourplural' => 'hours',
+'hoursingular' => 'hour',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Estimated effort for open tasks',
+'currenteffortdoneopen' => 'Current effort spent in open tasks',
+'signinwith' => 'Sign in with %s',
+'canviewroadmap' => 'Can view roadmap',
+'enableavatars' => 'Enable avatars',
+'maxavatarsize' => 'Max avatar size in pixels',
+'taskhassubtask' => 'This task has the following sub-task',
+'taskhassubtasks' => 'This task has the following sub-tasks',
+'translations' => 'Translations',
+'translate' => 'Translate',
+'taskdescription' => 'Task Description',
+'notaskdescription' => 'no task description',
+'pleaseselect' => 'Please select',
+'closeselectedtasks' => 'Close selected tasks',
+'closetasks' => 'close tasks',
+'hintforbulkimport' => '<b>Tips for bulk importing:</b>
+ <ol>
+ <li>Copy and paste from an excel spreadsheet or CSV by pasting one entire column.</li>
+ <li>Currently you can only paste Summary and Details.</li>
+ <li>There are suggestions when you assign to someone, and to no-one if there is no matched name.</li>
+ </ol>',
+'taskissubtaskof' => 'This task is a sub task of',
+'applyfirstline' => 'Apply first line',
+'addmorerows' => 'Add more rows',
+'addtasks' => 'Add tasks',
+'massopsdisabled' => 'Sorry, bulk editing is currently disabled for Flyspray 1.0. We plan to finish implementation for a later release of Flyspray. You can enable them in source code again at your own risk, but read the comments there before doing it.',
+'viewroadmap' => 'Can view roadmap',
+'nosuicide' => 'Dear user, my program doesn\'t allow you to destroy your access to Flyspray by disabling your own account or switching your own group. The empathic brother of HAL9000',
+'movingtodifferentproject' => 'Moving a task that has either a parent or subtasks to a different project is not allowed. You must break the connection between them first.',
+'musthavesameproject' => 'Parent and subtask must belong to the same project.',
+'defaultorderby' => 'Order tasklist by default by',
+'defaultorderby2' => 'then by',
+'viewowntasks' => 'View own tasks',
+'viewgroupstasks' => 'View groups tasks',
+'urlrewriting' => 'Url rewriting',
+'enablehtaccess' => 'Please enable your .htaccess file at Flyspray root before turning url rewriting on',
+'nomodrewrite' => 'Mod rewrite doesn\'t seem to be available on this server, sorry but I can\'t turn url rewriting on',
+'on' => 'On',
+'off' => 'Off',
+'defaultorderbydirection' => 'Default order by direction',
+'ascending' => 'Ascending',
+'descending' => 'Descending',
+'myassignedtasks' => 'My assigned tasks',
+'commentedon' => 'commented on',
+'maxvoteperday' => 'Maximum votes per day',
+'votesperproject' => 'User\'s limit of votes per project',
+'votelimitreached' => 'You reached your vote limit for this project. See your profile page for which tasks you currently vote. There you can also take back votes. So we can see what tasks are most important for you. Solved tasks get your vote back into your available voting limit.',
+'myvotes' => 'My Votes',
+'tag' => 'Tag',
+'tags' => 'Tags',
+'tagsinfo' => 'Free tagging/labeling of tasks in Flyspray: Separate tags by ; They aren\'t used yet for searching, sorting or filtering.',
+'novalues' => 'no entries',
+'youhaveregistered' => 'You have registered at Flyspray. Your details are as follows:',
+'youhaveregisterednotify' => 'Your registration at Flyspray has been accepted by Administrators.',
+'usedintasks' => 'Usage',
+'freetagging' => 'allow user defined tags',
+'keyboardshortcuts' => 'Keyboard shortcuts',
+'testmailsettings' => 'Test currently active email settings',
+'test' => 'Test',
+'testmailsettingsnotice' => 'And also check if you received the test email in the mail account of the current user (see \'myprofile\'-page)',
+'invalidinput' => 'Oh, there are some incompatible properties sent or missing.',
+'invalidprogress' => 'Please choose a valid progress value.',
+'invalidpriority' => 'Please choose a valid priority value.',
+'invalidseverity' => 'Please choose a valid severity value.',
+'invalidstatus' => 'Please choose a valid task status.',
+'invalidtasktype' => 'Please choose a valid task type.',
+'invalidcategory' => 'Please choose a valid category.',
+'invalidreportedversion' => 'Please choose a valid report version.',
+'invaliddueversion' => 'Please choose a valid due version.',
+'invalidos' => 'Please choose a valid operating system.',
+'invalidtags' => 'Please choose only allowed tags.',
+'invalidassignees' => 'Please choose only allowed assignees.',
+'invalidtargetproject' => 'Please choose a valid target project.',
+'customize' => 'customize',
+'hidesubs' => 'hide subtasks',
+'hideprivate' => 'hide private tasks',
+'hideclosed' => 'hide closed tasks',
+'globaloptions' => 'global options',
+'currentproject' => 'current project',
+'targetproject' => 'target project',
+'availablekeybshortcuts' => 'Available keyboard shortcuts',
+'logindialoglogout' => 'Login Dialog / Logout',
+'focustaskidsearch' => 'focus taskid search',
+'openselectedtask' => 'open selected task',
+'movecursorup' => 'move cursor up',
+'movecursordown' => 'move cursor down',
+'taskdetails' => 'Task Details',
+'taskediting' => 'Task Editing',
+'savetask' => 'save task',
+'generalintegration' => 'General Integration Strings',
+'generalintegrationdesc' => 'This content is put by default theme inside the body-tag, below main content and before footer tag. Where it really appears on screen depends on CSS. Use a custom_*.css for that.',
+'footerintegration' => 'Footer Integration Strings',
+'footerintegrationdesc' => 'This content is put by default theme into the logical footer area by default. Where it really appears on screen depends on CSS. Use a custom_*.css for that.',
+'editorbold' => 'B',
+'editorboldhint' => 'bold',
+'editoritalic' => 'I',
+'editoritalichint' => 'italic',
+'editorunderline' => 'U',
+'editorunderlinehint' => 'underline',
+'editorstrikethrough' => 'S',
+'editorstrikethroughhint' => 'strikethrough',
+'captchaerror' => 'CAPTCHA not solved. Please try again.',
+'registercaptcha' => 'Please solve the CAPTCHA.',
+'regcaptcha' => 'Show a CAPTCHA for new user registration.',
+'invalidsecurimage' => 'Invalid CAPTCHA',
+'invalidrecaptcha' => 'Invalid Google reCAPTCHA',
+'antispam' => 'Spam Prevention',
+'antispamprefsinfo' => 'Settings for countermeasures against spambots. Currently Securimage and Google reCAPTCHA are used for new user registration.',
+'securimageprefsinfo' => 'A classic CAPTCHA riddle where users must recognize a text sequence to prove to be human.',
+'securimageenable' => 'Enable Securimage',
+'recaptchaprefsinfo' => 'Google reCAPTCHA is an alternative to a classic CAPTCHA.
+Google decides on collected data and user behavior and maybe additional image recognition riddles if you are human or bot.
+To use this you need an Google Account and configure reCAPTCHA for your domain at https://www.google.com/recaptcha and insert "sitekey" and "secret" of that configuration.
+Depending on the configuration at Google - "reCAPTCHA V2" or "Invisible reCAPTCHA" - you maybe must inform your potential users about "Data protection".',
+'recaptchaenable' => 'Enable Google reCAPTCHA',
+'adminchecks' => 'Checks',
+'adminchecksinfo' => 'Info and checks for your current installation',
+'repeatpassword' => 'repeat password',
+'repeatemailaddress' => 'repeat email address',
+'tooltipshorttasktitle' => 'Write a title that summarizes the problem and makes it understandable at a glance',
+'lastlogin' => 'Last Login',
+);
+?>
diff --git a/lang/es.php b/lang/es.php
new file mode 100644
index 0000000..6694051
--- /dev/null
+++ b/lang/es.php
@@ -0,0 +1,1039 @@
+<?php
+
+/**
+* Esta tradución es actualmente mantenida por
+* Cristian Rodriguez.
+* Envia un mail con tus correcciones a judas.iscariote@flyspray.org
+*
+* Asegurate de usar un editor que guarde y lea este archivo en utf-8 asi
+* como que utilize finales de linea UNIX..ahh.. y NO USES entidades html ¡¡
+*
+* Añadidas más traducciones por Armando Camarero arcepi@arcepi.net
+* Agregadas más correcciones por Pablo Lezaeta prflr88@gmail.com
+*
+*/
+$translation = array(
+'edituser' => 'Editar usuario',
+'username' => 'Nombre de usuario',
+'realname' => 'Nombre real',
+'emailaddress' => 'Direccion de correo',
+'jabberid' => 'Id de Jabber',
+'notifytype' => 'Tipo de notificacion',
+'group' => 'Grupo',
+'accountenabled' => 'Cuenta habilitada',
+'updatedetails' => 'Actualizar detalles',
+'setglobally' => 'Estas preferencias fueron configuradas globalmente.',
+'usergroupmanage' => 'Administrar de Usuarios y Grupos',
+'newuser' => 'Registrar nuevo usuario',
+'newgroup' => 'Crear un grupo nuevo',
+'yes' => 'Sí',
+'no' => 'No',
+'editgroup' => 'Editar grupo',
+'groupname' => 'Nombre del grupo',
+'description' => 'Descripción',
+'admin' => 'Grupo administrador',
+'opennewtasks' => 'Abrir nuevas tareas',
+'modifytasks' => 'Modificar tareas existentes',
+'addcomments' => 'Agregar comentarios',
+'attachfiles' => 'Adjuntar archivos',
+'vote' => 'Votar',
+'groupenabled' => 'Los miembros pueden identificarse',
+'groupopen' => 'Los miembros pueden identificarse',
+'tasktypelist' => 'Lista de tipos de tarea',
+'categorylist' => 'Lista de categorías',
+'oslist' => 'Lista de sistemas operativos',
+'resolutionlist' => 'Lista de resoluciones',
+'versionlist' => 'Lista de versiones',
+'severitylist' => 'Lista de importancias',
+'listnote' => 'Nota: Desmarcando el recuadro «Mostrar» puede alterar algunas tareas en modo edición. Si modifica el campo «Nombre» el sistema cambiará todas las tareas con ese nombre.',
+'name' => 'Nombre',
+'order' => 'Orden',
+'back' => 'Atras',
+'text' => 'Texto',
+'highlight' => 'Resaltar',
+'show' => 'Mostrar',
+'owner' => 'Propietario',
+'selectowner' => 'Elija Propietario',
+'update' => 'Actualizar',
+'addnew' => 'Agregar nuevo',
+'flysprayprefs' => 'Preferencias de Flyspray',
+'projecttitle' => 'Titulo del proyecto',
+'baseurl' => 'URL base para esta instalacion',
+'replyaddress' => 'Direccion de correo de respuesta para notificaciones',
+'themestyle' => 'Tema / Estilo',
+'language' => 'Lenguaje',
+'anonview' => 'Los usuarios anónimos pueden ver las tareas',
+'allowanon' => 'Los usuarios anónimos pueden abrir nuevas tareas',
+'never' => 'Nunca',
+'anonymously' => 'Anonimamente',
+'afterregister' => 'Solamente después de registrarse',
+'spamproof' => 'Habilitar Código de confirmación para el registro de nuevos usuarios',
+'anongroup' => 'Grupo para los nuevos usuarios registrados',
+'groupassigned' => 'A los miembros de estos grupos se les puede asignar tareas',
+'forcenotify' => 'Forzar notificacion como',
+'neversend' => 'Nunca enviar',
+'userchoose' => 'Dejar decidir al usuario',
+'email' => 'Correo electrónico',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Dueño de la categoría por omición',
+'noone' => 'Nadie',
+'jabbernotify' => 'Notificaciones Jabber',
+'jabberserver' => 'Servidor',
+'jabberport' => 'Puerto',
+'jabberuser' => 'Usuario',
+'jabberpass' => 'Contraseña',
+'saveoptions' => 'Grabar opciones',
+'editcomment' => 'Modificar comentario',
+'commentby' => 'Comentario por',
+'saveeditedcomment' => 'Guardar comentario modificado',
+'projectprefs' => 'Preferencias del proyecto',
+'pagetitle' => 'Título',
+'defaultproject' => 'Proyecto por omición',
+'projectlists' => 'Lista de proyectos',
+'showlogo' => 'Mostrar la imagen con el logo',
+'intromessage' => 'Mensaje introductorio',
+'isactive' => 'Proyecto activo',
+'createproject' => 'Crear un nuevo proyecto',
+'nopermission' => 'No tiene permisos para usar esta página.',
+'listordertip' => 'El orden en que los elementos aparecerán listados',
+'listshowtip' => 'Mostrar este elemento en la lista',
+'categoryownertip' => 'Esta persona recibirá notificaciones cuando una tarea se abra dentro de esta categoría',
+'categoryparenttip' => 'La categoría padre de esta categoría',
+'notsubcategory' => 'Ninguna - es una categoría de nivel superior',
+'showinlineimages' => 'Mostrar las imágenes adjuntas',
+'dateformat' => 'Formato de la fecha',
+'dateformat_extended' => 'Formato detallado de la fecha',
+'cache_feeds' => 'Caché para RSS',
+'no_cache' => 'Sin caché',
+'cache_disk' => 'Caché en disco',
+'cache_db' => 'Caché en la base de datos',
+'subcategoryof' => 'Subcategoría de',
+'visiblecolumns' => 'Columnas visibles en la lista de tareas',
+'tense' => 'Tiempo',
+'listtensetip' => 'Pasado, presente o futuro',
+'past' => 'Pasado',
+'present' => 'Presente',
+'future' => 'Futuro',
+'oldpass' => 'Contraseña antigua',
+'nooldpass' => 'No hay contraseñas antiguas',
+'oldpasswrong' => 'La contraseña antigua es incorrecta',
+'changepass' => 'Cambiar Contraseña',
+'confirmpass' => 'Confirme Contraseña',
+'projectmanager' => 'Administración del proyecto',
+'viewtasks' => 'Ver tareas',
+'modifyowntasks' => 'Modificar tareas propias',
+'modifyalltasks' => 'Modificar tareas de terceros',
+'viewcomments' => 'Ver comentarios',
+'editcomments' => 'Editar comentarios',
+'deletecomments' => 'Borrar comentarios',
+'viewattachments' => 'Ver adjuntos',
+'createattachments' => 'Crear adjuntos',
+'deleteattachments' => 'Borrar adjuntos',
+'viewhistory' => 'Ver historial',
+'closeowntasks' => 'Cerrar tareas propias',
+'closeothertasks' => 'Cerrar tareas de terceros',
+'assigntoself' => 'Asignarse tareas disponibles',
+'assignotherstoself' => 'Asignarse tareas de terceros',
+'viewreports' => 'Ver reportes',
+'othersview' => 'Permitir que cualquiera observe este proyecto',
+'usersandgroups' => 'Usuarios y grupos',
+'globalgroup' => 'Grupo Global',
+'globalgroups' => 'Grupos Globales',
+'defaultglobalgroup' => 'Grupo Global por defecto para nuevos usuarios',
+'addtogroup' => 'Agregar al grupo',
+'moveuserstogroup' => 'Mover usuarios al grupo',
+'nogroup' => 'Sin grupo - Eliminar del proyecto',
+'eventdesc' => 'Descripción del evento',
+'requestedby' => 'Pedido por',
+'daterequested' => 'Fecha del pedido',
+'closetask' => 'Cerrar tarea',
+'reopentask' => 'Reabrir tarea',
+'applymember' => 'Aplicar para ser miembro del proyecto',
+'forcurrentproj' => 'Para el actual poyecto',
+'lostpw' => 'Recuperar contraseña perdida',
+'lostpwexplain' => 'Ingrese su nombre de usuario para enviarle un enlace y poder cambiar su contraseña. Este enlace se le enviará a la dirección de notificación que consta en su pefil.',
+'sendlink' => 'Enviar enlace',
+'savenewpass' => 'Grabar nueva contraseña',
+'anonreg' => 'Permitir el registro de nuevos usuarios',
+'allowanonopentask' => 'Permitir abrir tareas a usuarios anónimos',
+'editglobalgroup' => 'Editar el grupo global',
+'editgroupforproj' => 'Editar el grupo para el proyecto',
+'notshownforadmin' => 'Los permisos no se muestran para el grupo administrador, no necesita de ellos.',
+'general' => 'General',
+'userregistration' => 'Registro de usuario',
+'notifications' => 'Notificaciones',
+'resetoptions' => 'Reiniciar opciones',
+'preferences' => 'Preferencias',
+'tasktypes' => 'Tipos de tarea',
+'resolutions' => 'Resoluciones',
+'categories' => 'Categorías',
+'operatingsystems' => 'Sistemas operativos',
+'versions' => 'Versiones',
+'admintoolboxlong' => 'Herramientas de administración',
+'newproject' => 'Nuevo Proyecto',
+'delete' => 'Eliminar',
+'listdeletetip' => 'Eliminar este elemento de la lista',
+'lookandfeel' => 'Apariencia',
+'globaltheme' => 'Tema Global',
+'emailnotify' => 'Notificaciones a través del correo',
+'fromaddress' => 'Direccion del remitente',
+'smtpserver' => 'Servidor SMTP ',
+'smtpuser' => 'Usuario SMTP',
+'smtppass' => 'Contraseña SMTP ',
+'addrewrite' => 'Usar rescritura de dirección (address rewriting)',
+'usereminderdaemon' => 'Habilitar en 2do. plano el «demonio de recordatorios»',
+'tasksperpage' => 'Tareas por página',
+'addtoassignees' => 'Añadirme a los encargados ',
+'taskstatuses' => 'Estados de las tareas',
+'canvote' => 'Puede votar en las tareas',
+'loginsuccessful' => 'Ingreso satisfactorio.',
+'youareloggedout' => 'Ha sido desconectado',
+'waitwhiletransfer' => 'Por favor, espere mientras es transferido...',
+'clicknowait' => 'Pulse aquí si no quiere esperar.',
+'accountdisabled' => 'Su cuenta está deshabilitada. Contacte a un administrador.',
+'task' => 'Tarea',
+'edittask' => 'Editar tarea',
+'openedby' => 'Abierta por',
+'editedby' => 'Ultima edición por',
+'tasktype' => 'Tipo de Tarea',
+'category' => 'Categoría',
+'status' => 'Estado',
+'assignedto' => 'Asignada a',
+'operatingsystem' => 'Sistema operativo',
+'severity' => 'Importancia',
+'reportedversion' => 'Version reportada',
+'dueinversion' => 'Agendada para la versión',
+'undecided' => 'Sin decidir',
+'percentcomplete' => 'Porcentaje completo:',
+'details' => 'Detalles',
+'savedetails' => 'Guardar detalles',
+'anonymous' => 'Envío anónimo',
+'complete' => 'Completa',
+'closedby' => 'Cerrada por',
+'reasonforclosing' => 'La razon de cierre es:',
+'reopenthistask' => 'Reabrir esta tarea',
+'comments' => 'Comentarios',
+'attachments' => 'Adjuntos',
+'relatedtasks' => 'Tareas relacionadas',
+'edit' => 'Editar',
+'addcomment' => 'Agregar comentario',
+'fileuploadedby' => 'Archivo subido por',
+'uploadafile' => 'Subir archivo adjunto',
+'uploadnow' => 'Subir ahora!',
+'thesearerelated' => 'Estas tareas están relacionadas con la tarea actual',
+'remove' => 'Borrar',
+'addnewrelated' => 'Agregar nueva tarea relacionada',
+'add' => 'Agregar',
+'otherrelated' => 'Otras tareas relacionadas a esta tarea',
+'receivenotify' => 'Estos usuarios recibirán notificaciones detalladas cuando esta tarea cambie',
+'addusertolist' => 'Sumar usuario a la lista',
+'addtolist' => 'Sumar a la lista',
+'addmyself' => 'Sumarme a la lista',
+'removemyself' => 'Removerme de la lista',
+'theseusersnotify' => 'Estos usuarios recibiran notificaciones detalladas cuando esta tarea cambie',
+'attachedtoproject' => 'Pertenece al proyecto',
+'reminders' => 'Recordatorios',
+'system' => 'Sistema',
+'remindthisuser' => 'Recordar a este usuario',
+'thisoften' => 'Con esta continuidad',
+'startafter' => 'Esperar antes de comenzar los recordatorios',
+'hours' => 'Hora(s)',
+'days' => 'Día(s)',
+'weeks' => 'Semana(s)',
+'addreminder' => 'Sumar recordatorio',
+'defaultreminder' => 'Este recordatorio es para que mire la siguiente tarea en Flyspray:',
+'message' => 'Mensaje',
+'closed' => 'Cerradas',
+'filename' => 'Archivo:',
+'date' => 'Fecha',
+'filesize' => 'Tamaño:',
+'closurecomment' => 'Comentarios adicionales sobre el cierre:',
+'history' => 'Historial',
+'nohistory' => 'No hay historial disponible.',
+'eventdate' => 'Fecha',
+'user' => 'Usuario',
+'event' => 'Evento',
+'fieldchanged' => 'Campo modificado',
+'taskopened' => 'Tarea abierta',
+'taskreopened' => 'La siguiente tarea fue reabiera:',
+'taskclosed' => 'La siguiente tarea ahora se encuentra cerrada:',
+'commentadded' => 'La siguiente tarea tiene un nuevo comentario:',
+'commentedited' => 'Comentario editado',
+'commentdeleted' => 'Se eliminó el comentario.',
+'attachmentadded' => 'Un nuevo archivo se adjunto a la siguiente tarea:',
+'attachmentdeleted' => 'El archivo adjunto fue eliminado',
+'taskedited' => 'Detalle de la Tarea editado',
+'notificationadded' => 'Usuario sumado a la lista de notificaciones',
+'notificationdeleted' => 'Usuario borrado de la lista de notificaciones',
+'relatedadded' => 'Se agregó una tarea relacionada a la siguiente tarea:',
+'relateddeleted' => 'Tarea relacionada borrada',
+'taskassigned' => 'Tarea asignada a',
+'taskreassigned' => 'Tarea reasignada a',
+'assignmentremoved' => 'Asignacion removida',
+'summary' => 'Resumen',
+'addedasrelated' => 'Tarea sumada a la lista de relacionadas de',
+'deletedasrelated' => 'Tarea removida de la lista de relacionadas de',
+'reminderadded' => 'Se agregó su Recordatorio.',
+'reminderdeleted' => 'Se eliminó el recordatorio seleccionado.',
+'priority' => 'Prioridad',
+'previousvalue' => 'Valor previo',
+'newvalue' => 'Nuevo valor',
+'selectareason' => 'Seleccione una razon',
+'assigntome' => 'Asígnarme las tareas seleccionadas',
+'reopenrequest' => 'Requerir reapertura',
+'requestclose' => 'Requerir cierre',
+'ownershiptaken' => 'Usuario tomó propiedad',
+'closerequestmade' => 'Requerido el cierre de la tarea',
+'reopenrequestmade' => 'Requerida la reapertura de la tarea',
+'taskdependson' => 'Esta tarea depende de',
+'taskblocks' => 'Esta tarea bloquea el cierre de las siguientes tareas',
+'depadded' => 'La siguiente tarea tiene una nueva dependencia',
+'depaddedother' => 'Esta tarea se agregó como dependencia',
+'depremoved' => 'A la siguiente tarea se le removió una dependencia',
+'depremovedother' => 'Esta tarea fue removida de las dependencias de otras tareas',
+'showdetailserror' => 'Esa tarea no existe, o no tiene permiso para verla.',
+'makeprivate' => 'Hacerla Privada',
+'makepublic' => 'Hacerla Pública',
+'taskmadeprivate' => 'La tarea se hizo privada',
+'taskmadepublic' => 'La tarea se hizo pública nuevamente',
+'confirmdeletecomment' => '¿Seguro de que elimina este comentario? %s',
+'confirmdeleteattach' => '¿Seguro de que elimina este adjunto?',
+'selectedhistory' => 'Observando detalles del historial seleccionado',
+'showallhistory' => 'Mostrar nuevamente la pestaña completa del historial',
+'hidethis' => 'Ocultar nuevamente esta area',
+'mark100' => 'Marcar la tarea como 100 % completada',
+'watchtask' => 'Observar esta tarea',
+'stopwatching' => 'No monitorear',
+'commentlink' => 'Enlace a este comentario',
+'submitreq' => 'Enviar pedido',
+'reasonforreq' => 'Razón de esta petición',
+'pmreqdenied' => 'El pedido al administrador del proyecto fue rechazado',
+'taskpendingreq' => 'Acción pendiente del administrador del proyecto. Mire la solapa del historial para más detalles.',
+'previoustask' => 'Tarea previa',
+'nexttask' => 'Tarea proxima',
+'duedate' => 'Fecha de vencimiento',
+'attachnoperms' => 'Existen adjuntos en este comentario, pero no tiene permisos para verlos.',
+'open' => 'Abiertas',
+'depgraph' => 'Ver grafico de dependencia',
+'reset' => 'Reajustar',
+'selectusers' => 'Seleccione usuarios...',
+'addmetoassignees' => 'Agrégarme a los encargados',
+'addedtoassignees' => 'Usuario agregado a la lista de encargados',
+'dependencygraph' => 'Gráfico de dependencias',
+'attachanotherfile' => 'Adjuntar otro archivo',
+'OK' => 'Aceptar',
+'addvote' => 'Agregar voto',
+'notifyfromfs' => 'Notificación de Flyspray',
+'autogenerated' => 'ESTE ES UN MENSAJE GENERADO AUTOMÃTICAMENTE. NO LO RESPONDA.',
+'forward' => 'Adelante',
+'previous' => 'Previa',
+'next' => 'Próxima',
+'first' => 'Primero',
+'last' => 'Último',
+'page' => 'Página %d de %d',
+'search' => 'Buscar',
+'alltasktypes' => 'Todos los tipos de tareas',
+'allseverities' => 'Todas las importancias',
+'alldevelopers' => 'Todos los desarrolladores',
+'notyetassigned' => 'Sin asignar',
+'allcategories' => 'Todas las categorías',
+'allstatuses' => 'Todos los estados',
+'allopentasks' => 'Todas las tareas abiertas',
+'sortthiscolumn' => 'Ordenar por esta columna',
+'id' => 'Id',
+'project' => 'Proyecto:',
+'dateopened' => 'Abierta',
+'progress' => 'Progreso',
+'searchthisproject' => 'Buscar en este proyecto',
+'dueanyversion' => 'Agendada para cualquier versión',
+'anyversion' => 'Reportado en todas las versiones',
+'dueversion' => 'Agendada para',
+'lastedit' => 'Última edicion',
+'os' => 'Sistema operativo',
+'reportedin' => 'Reportada en',
+'taskrange' => 'Viendo tareas %d - %d de %d',
+'noresults' => 'No se encontraron resultados.',
+'takeaction' => 'Accion a tomar',
+'watchtasks' => 'Observar tareas seleccionadas',
+'stopwatchingtasks' => 'No continuar observando las siguientes tareas',
+'assigntaskstome' => 'Asignarme las siguientes tareas ',
+'dueby' => 'Vencen por',
+'dueanytime' => 'Vencen en cualquier momento',
+'selectduedate' => 'Elija la fecha de vencimiento',
+'toggleselected' => 'Invertir selección',
+'due' => 'Vence',
+'assignedtome' => 'Asignado a mí',
+'tasklist' => 'Lista de tareas',
+'dateclosed' => 'Fecha de cierre',
+'advanced' => 'Avanzado',
+'searchcomments' => 'Buscar en comentarios',
+'searchforall' => 'Buscar por todas las palabras',
+'anonusers' => 'Usuarios anónimos',
+'miscellaneous' => 'Misceláneos',
+'users' => 'Usuarios',
+'taskproperties' => 'Propiedades de la tarea',
+'selectsincedate' => 'Seleccionar cambiado desde',
+'changedsince' => 'Cambiado desde',
+'updatefs' => 'Por favor, actualize su versión de Flyspray.',
+'currentversion' => 'Su version es',
+'latestversion' => 'Y la última versión es',
+'hidemessage' => '(recordarme más tarde)',
+'saveas' => 'Guardar búsqueda como',
+'nosearches' => 'No hay búsquedas guardadas',
+'saving' => 'Guardando...',
+'votes' => 'Votos',
+'allclosedtasks' => 'Todas las tareas cerradas',
+'password' => 'Contraseña',
+'login' => 'Ingresar',
+'rememberme' => 'Recordarme',
+'lostpassword' => '¿Olvidó su contraseña?',
+'lostpwforfs' => 'Contraseñas perdidas para Flyspray',
+'lostpwmsg1' => 'Hola. He perdido mi contraseña para Flyspray en ',
+'lostpwmsg2' => ', por favor, asígneme una nueva contraseña. Usuario: ',
+'regards' => 'Disculpe las molestias,',
+'yourusername' => ' su usuario ',
+'locale' => 'es',
+'filenotexist' => 'El archivo no existe. Contacte al administrador de Flyspray de este proyecto.',
+'showtask' => 'Mostrar tarea',
+'now' => 'Ahora',
+'go' => 'Ir',
+'opentaskanon' => 'Abrir anónimanete una nueva tarea',
+'register' => 'Registrar nuevo usuario',
+'addnewtask' => 'Nueva tarea',
+'reports' => 'Reportes',
+'editmydetails' => 'Mi perfil',
+'logout' => 'Salir',
+'disabledaccount' => 'Su cuenta ha sido deshabilitada. La sesion finalizará de inmediato...',
+'poweredby' => 'Impulsado por Flyspray',
+'projects' => 'Proyectos',
+'allprojects' => 'Todos los proyectos',
+'selectproject' => 'para el proyecto:',
+'tasksall' => 'Todas las tareas',
+'tasksassigned' => 'Tareas asignadas a mí',
+'tasksreported' => 'Tareas reportadas por mí',
+'taskswatched' => 'Tareas que observo',
+'mysearch' => 'Mis búsquedas',
+'admintoolbox' => 'Herramientas de administrador',
+'manageproject' => 'Editar proyecto',
+'permissions' => 'Ver permisos',
+'hide' => 'Ocultar',
+'pendingreq' => 'Pedidos pendientes',
+'errorpage' => "Flyspray no puede mostrarle la página solicitada.\n Tal vez ha pedido una tarea que no existe,\n o no tiene permisos para ver la página solicitada.<br /><br />\n Tal vez haya intentado ingresar con una URL maliciosa para interactuar con la base de datos\n tratando de utilizar una inyección de SQL. Si esto es así, vaya hasta un rincón\n y medite sobre sus acciones. Al volver, por favor no lo intente nuevamente!",
+'permissionsforproject' => 'Permisos para',
+'switchto' => 'Cambiar a',
+'lastsearch' => 'Última búsqueda',
+'modify' => 'Modificar',
+'noticefrom' => 'Aviso de',
+'hasopened' => 'Ha abierto una nueva tarea en Flyspray y se la ha asignado a si mismo.:',
+'moreinfonew' => 'Puede encontrar más información sobre esta tarea en la página:',
+'newtaskcategory' => 'Una nueva tarea en Flyspray se ha abierto en esta categoría',
+'categoryowner' => 'Esta recibiendo este mensaje por ser el propietario de la categoría.',
+'tasksummary' => 'Detalle de la Tarea:',
+'newtaskadded' => 'Se agregó su nueva tarea.',
+'summaryanddetails' => 'Debe llenar el campo de resumen y detalle.',
+'goback' => 'Regresar.',
+'messagefrom' => 'Este es un mensaje generado por Flyspray en',
+'hasjustmodified' => 'Ha modificado la siguiente tarea.',
+'changedfields' => 'Los campos modificados tienen de prefijo de dos asteriscos (**)',
+'moreinfomodify' => 'Podrá obtener más información de esta tarea en la siguiente URL:',
+'nolongerassigned' => 'La siguiente tarea ya no está asignada a usted:',
+'hasassigned' => 'Le han asignado la siguiente tarea en Flyspray:',
+'taskupdated' => 'Se actualizó la tarea.',
+'hasclosedassigned' => 'Cerró la siguiente tarea en Flyspray que tenía asignada:',
+'unassigned' => 'Sin asignar',
+'hasclosed' => 'Cerro la siguiente tarea.',
+'youonnotify' => 'Está recibiendo este mensaje porque se encuentra en la lista de notificaciones.',
+'taskclosedmsg' => 'La tarea ha sido cerrada.',
+'returntotask' => 'Regresar a los detalles de la tarea',
+'backtoindex' => 'Regresar a la lista de tareas',
+'noclosereason' => 'No ha seleccionado una razón de cierre de esta tarea.',
+'hasreopened' => 'Ha reabierto la siguiente tarea que ya había cerrado:',
+'taskreopenedmsg' => 'La tarea ha sido reabierta.',
+'backtotask' => 'Regresar a la tarea.',
+'commentaddedmsg' => 'Se ha añadido un comentario.',
+'commenttoassigned' => 'Agregó un comentario a una tarea asignada a usted:',
+'commenttotask' => 'Agregó el siguiente comentario a esta tarea.',
+'nocommententered' => 'Se debe ingresar un comentario antes de hacer clic en el botón de envío.',
+'fillinfields' => 'No llenó todos los campos.',
+'notcurrentpass' => 'No es su contraseña actual',
+'passchanged' => 'Su contraseña ha cambiado.',
+'closewindow' => 'Ahora puede cerrar esta ventana...',
+'passnomatch' => 'Sus nuevas contraseñas no coinciden',
+'usernametaken' => 'Ese nombre de usuario ya esta utilizado. Deberá ingresar un nuevo nombre de usuario.',
+'newusercreated' => 'Se creó una nueva cuenta de usuario.',
+'accountcreated' => 'Se creó su cuenta.',
+'newuserwarning' => 'Note que las preferencias globales pueden requerir que su cuenta sea aprobada por un administrador. Si no puede ingresar al sistema, esa debe ser la razón.',
+'nomatchpass' => 'Las contaseñas no coincidieron.',
+'confirmwrong' => 'El código de confirmación es incorrecto',
+'formnotcomplete' => 'El formulario no se lleno completamente.',
+'formnotnumeric' => 'Los datos ingresados no son numéricos',
+'groupnametaken' => 'Ese nombre de grupo ya existe en la actualidad.',
+'newgroupadded' => 'Se agregó el nuevo grupo.',
+'optionssaved' => 'Opciones de Flyspray guardadas.',
+'hasuploaded' => 'Subió un archivo adjunto a una tarea asignada:',
+'hasattached' => 'adjuntó un archivo a la siguiente tarea.',
+'fileuploaded' => 'Se subió el archivo correctamente.',
+'fileerror' => 'Hubo un error subiendo su archivo. Tal vez los permisos en el directorio <i>adjunto/</i> no son correctos.',
+'contactadmin' => 'Contacte al administrador de este proyecto.',
+'selectfileerror' => 'No seleccionó un archivo.',
+'userupdated' => 'Se actualizaron los detalles del usuario',
+'realandemail' => 'No llenó los campos nombre real y correo.',
+'groupupdated' => 'La definicion del grupo fue actualizada.',
+'groupanddesc' => 'No completó el nombre del grupo y la descripción.',
+'fillallfields' => 'Complete todos los campos, por favor.',
+'listPmustN' => 'El «orden» de la entrada de ser numérico.',
+'listupdated' => 'Se actualizó el Listado.',
+'listitemadded' => 'Se agregó un elemento al listado.',
+'relatedaddedmsg' => 'Se agregaron tareas relacionadast.',
+'relatederror' => 'Esa tarea se encuentra actualmente en el listado de tareas relacionadas.',
+'relatedremoved' => 'Se removió del listado la tarea relacionada.',
+'notifyadded' => 'Se agregó usuario a la lista de notificaciones.',
+'notifyerror' => 'Ese usuario ya se encuentra en la lista de notificaciones.',
+'notifyremoved' => 'Se removió al usuario de la lista de notificaciones.',
+'editcommentsaved' => 'Se guardó el comentario actualizado.',
+'commentdeletedmsg' => 'El comentario ha sido eliminado.',
+'gotonewtask' => 'Ir a la tarea recien creada',
+'projectcreated' => 'Se creó el nuevo proyecto. Siga el enlace de abajo para poner a punto las categorías, sistemas operativos y el listado de versiones',
+'customiseproject' => 'Personalizar este proyecto',
+'projectupdated' => 'Se actualizó las preferencias del proyecto',
+'emptytitle' => 'Dejó en blanco el título del proyecto. Regrese para solucionarlo.',
+'loginbelow' => 'Ahora puede ingresar al sistema utilzando el formulario de abajo.',
+'attachmentdeletedmsg' => 'El adjunto ha sido eliminado.',
+'reminderaddedmsg' => 'Se ha añadido el recordatorio.',
+'reminderdeletedmsg' => 'El recordatorio seleccionado se ha eliminado.',
+'flyspraytask' => 'Tarea de Flyspray',
+'fieldsmissing' => 'Algunos campos estaban en blanco o contenian información no válida.',
+'relatedinvalid' => 'Esa tarea no existe.',
+'relatedproject' => 'La tarea pertenece a un proyecto diferente.',
+'addanyway' => 'Agregar de todas formas',
+'cancel' => 'Cancelar',
+'alreadyedited' => 'Esta tarea ha sido modificada por otra persona antes de que fuera guardada.',
+'saveanyway' => 'Guardar mis cambios de todas formas',
+'nouserselected' => 'No se seleccionó un usuario. Seleccione al menos uno antes de intentar nuevamente.',
+'groupswitchupdated' => 'Se modificaron satisfactoriamente los grupos de usuario.',
+'takenownershipmsg' => 'Esta tarea ahora esta asignada a ti.',
+'adminrequestmade' => 'Su pedido fue enviado a un administrador del proyecto.',
+'newdepnotify' => 'Se agregó una nueva dependencia a la siguiente tarea:',
+'dependadded' => 'Se agregó la dependencia de la tarea',
+'dependaddfailed' => 'Ahora no puede agregar esa tarea como dependencia',
+'depremovedmsg' => 'Se ha eliminado la dependencia de la tarea',
+'newdepis' => 'La nueva dependencia es',
+'magicurlsent' => 'Se envió un mensaje a su dirección de notificación. Contiene un enlace que lo llevará a una página para completar esta tarea.',
+'changefspass' => 'Cambiar la contraseña',
+'magicurlmessage' => 'Por favor, siga el siguiente enlace de abajo para cambiar su contraseña en Flyspray:',
+'erroronform' => 'Ocurrió un problema con el envío del formulario',
+'addressused' => 'Esta direccion ha sido utilizada para registrar una cuenta de Flyspray. Si no estaba esperando este mensaje, simplemente ignórelo y elimínelo. Su código de confirmación es: ',
+'confirmcodeis' => 'Su código de confirmación es:',
+'codesent' => 'Se envió el código de confirmación. Por favor, siga las instrucciones contenidas en el mensaje.',
+'codenotsent' => 'No fue posible enviar el código, intente más tarde.',
+'taskmadeprivatemsg' => 'Esta tarea se ha hecho privada',
+'taskmadepublicmsg' => 'Esta tarea se ha hecho pública de nuevo',
+'realandnotify' => 'Necesita rellenar los campos nombre real y alguno de los campos de correo o Id de Jabber.',
+'pmreqdeniedmsg' => 'Solicitud de administración del proyecto denegada.',
+'massopsuccess' => 'Las operaciones múltiples fueron satisfactorias',
+'usernotexist' => 'Ese nombre de usuario no existe en esta instalación de lyspray',
+'commentattachperms' => 'No puede eliminar ese comentario - No tiene permisos para eliminar adjuntos',
+'voterecorded' => 'Su voto ha sido almacenado',
+'votefailed' => 'Su voto no pudo ser almacenado en este momento.',
+'createnewgroup' => 'Crear nuevo grupo',
+'requiredfields' => 'Los campos requeridos están marcados con',
+'addthisgroup' => 'Agregar este grupo',
+'createnewproject' => 'Crear nuevo Proyecto',
+'addnewproject' => 'Agregar nuevo Proyecto',
+'htmlallowed' => 'Permitir código HTML',
+'createthisproject' => 'Crear este proyecto',
+'inlineimages' => 'Mostrar las imágenes adjuntas',
+'createnewtask' => 'Crear una nueva tarea en el proyecto:',
+'addanother' => 'Agregar otra tarea después de esta',
+'addthistask' => 'Agregar esta tarea',
+'notifyme' => 'Notificarme cuando esta tarea cambie',
+'newtask' => 'Nueva tarea',
+'attachafile' => 'Adjuntar un archivo',
+'registernewuser' => 'Registrar nuevo usuario',
+'none' => 'Ninguna',
+'registeraccount' => 'Registrar esta cuenta',
+'both' => 'Ambos',
+'notifyfrom' => 'Notificacion de ',
+'donotreply' => 'ESTE ES UN MENSAJE AUTOMÃTICO, NO LO RESPONDA.',
+'disclaimer' => 'Ha recibido este mensaje porque lo han requerido desde el sistema de seguimiento de errores de Flyspray. Puede ser removido de futuras notificaciones visitando la URL mostrada arriba.',
+'userwho' => 'Usuario que lo hizo:',
+'moreinfo' => 'Encontrará más informacion en la URL:',
+'newtaskopened' => 'Se abrió una nueva tarea en Flyspray. Los detalles son los siguientes',
+'notify.taskclosed' => 'La siguiente tarea ahora está cerrada:',
+'notify.taskreopened' => 'La siguiente tarea ha sido reabierta:',
+'newdep' => 'La siguiente tarea tiene una nueva dependencia:',
+'notify.depremoved' => 'Se ha eliminado una dependencia de la siguiente tarea:',
+'olddepwas' => 'La antigua dependencia era',
+'notify.commentadded' => 'La siguiente tarea tiene un comentario nuevo:',
+'commentis' => 'El texto del comentario está abajo.',
+'newattachment' => 'Se ha adjuntado un nuevo fichero a la siguiente tarea:',
+'detailsbelow' => 'Los detalles se muestran aquí abajo.',
+'notify.relatedadded' => 'Se ha añadido una nueva tarea relacionada a la siguiente tarea:',
+'relatedis' => 'La tarea relacionada es',
+'assignedtoyou' => 'Se le asignó la siguiente tarea:',
+'takenownership' => 'Se asignó la siguiente tarea:',
+'requiresaction' => 'La siguiente tarea requiere intervención de un Manager de proyecto:',
+'requiresactionnotify' => 'La tarea requiere intervención de un Manager de proyecto',
+'pmdeny' => 'Un administrador del proyecto ha denegado el pedido pendiente para esta tarea:',
+'pmdenynotify' => 'Un administrador del proyecto ha denegado la petición',
+'fileaddedtoo' => 'Había uno o más ficheros adjuntos a este comentario.',
+'taskwatching' => 'La siguiente tarea que estás vigilando',
+'isdepfor' => 'Es una nueva dependencia de',
+'denialreason' => 'Razón de la denegación',
+'taskchanged' => 'La siguiente tarea ha cambiado. Los nuevos detalles son los mostrados más abajo. Para conocer la información completa de lo que ha cambiado, visite la URL y haga clic en la pestaña del historial.',
+'useraddedtoassignees' => 'Un usuario se ha añadido a si mismo a la lista de usuarios asignados a esta tarea.',
+'removeddepis' => 'La dependencia eliminada es',
+'isnodepfor' => 'ha dejado de ser una dependencia de',
+'usergroups' => 'Grupos de usuario',
+'pmtoolbox' => 'Barra de herramientas del administrador del proyecto',
+'groupmanage' => 'Manejo de grupos',
+'pendingrequests' => 'Pedidos pendientes de administración',
+'reasongiven' => 'Razon dada',
+'nopendingreq' => 'No existen pedidos pendientes para el administrador del proyecto.',
+'givereason' => 'Ingrese una razón',
+'catlisted' => 'Editor de lista de categorías',
+'oslisted' => 'Editor de lista de sistemas operativos',
+'verlisted' => 'Editor de lista de versiones',
+'tasktypeed' => 'Editor de lista de tipos de tarea',
+'resed' => 'Editor de lista de resoluciones',
+'deny' => 'Denegar',
+'notifiedwhen' => 'Notificarme cuando',
+'onlynewtasks' => 'Se abren nuevas tareas',
+'allevents' => 'Ocurre algun evento en las tareas',
+'feeds' => 'RSS',
+'feeddescription' => 'Descripción del RSS',
+'feedimgurl' => 'Imagen del RSS (dejar en blanco para ninguna)',
+'notifysubject' => 'Asunto para las notificaciones',
+'notifysubjectinfo' => '(%p = título del proyecto, %s = sumario de la tarea, %t = id de la tarea, %a = acción, %u = nombre de usuario)',
+'priority6' => 'Al instante',
+'priority5' => 'Inmediata',
+'priority4' => 'Urgente',
+'priority3' => 'Alta',
+'priority2' => 'Normal',
+'priority1' => 'Baja',
+'sendcode' => 'Enviar código',
+'entercode' => 'Ingrese el código de confirmacion tal cual lo recibió. Además ingrese una contraseña de su agrado.',
+'confirmationcode' => 'Código de confirmación',
+'registererror' => 'Asegúrese de haber rellenado todos los campos requeridos y de haber ingresado correctamente el tipo de notificación deseada.',
+'validusername' => '(solamente están permitidos caracteres alfanuméricos y «-», «_», «.»)',
+'emailtaken' => 'Esa dirección de correo electrónico o Id de Jabber ya están en uso. Necesita elegir otro.',
+'note' => '<b>Nota:</b> Se le enviará un código de confirmación antes de crear su cuenta. El código se le enviará usando el metodo de notificacion seleccionado.<br />SI INGRESA INFORMACIÓN FALSA, NUNCA RECIBIRà SU CÓDIGO.',
+'changelog' => 'Registro de cambios',
+'changeloggen' => 'Generador de registro de cambios',
+'listfrom' => 'Listar ingresos al registro decambios desde',
+'to' => 'Hasta',
+'oldestfirst' => 'Viejos primero',
+'recentfirst' => 'Recientes primero',
+'severityrep' => 'Reporte de Importancia',
+'totalopen' => 'Total de tareas abiertas',
+'age' => 'Edad',
+'agerep' => 'Reporte de edad',
+'eventsrep' => 'Reporte de eventos',
+'events' => 'Eventos',
+'Tasks' => 'Tareas',
+'opened' => 'Abiertas',
+'edited' => 'Modificadas',
+'assigned' => 'Asignadas',
+'within' => 'Dentro de',
+'pastday' => 'El último dia',
+'pastweek' => 'La última semana',
+'pastmonth' => 'El mes pasado',
+'pastyear' => 'El año pasado',
+'nolimit' => 'Sin límite',
+'from' => 'De',
+'duein' => 'Vence en',
+'selectfromdate' => 'Seleccionar por fecha',
+'selecttodate' => 'Seleccionar hasta fecha',
+'showvoters' => 'Mostrar/ocultar votantes',
+'roadmap' => 'Mapa de ruta',
+'roadmapfor' => 'Mapa de ruta para la versión',
+'tasks' => 'Tareas',
+'completed' => 'Completada.',
+'opentasks' => 'Tareas abiertas',
+'of' => '% de',
+'severity5' => 'Crítica',
+'severity4' => 'Alta',
+'severity3' => 'Media',
+'severity2' => 'Baja',
+'severity1' => 'Mínima',
+'Redirect' => 'Redirigiendo',
+'redirectmsg' => 'Si su navegador no soporta redirección haga clic %sAQUÃ%s para ser redirigido',
+'allowclosedcomments' => 'Permitir comentarios en tareas cerradas',
+'comment' => 'Comentar',
+'editowncomments' => 'Editar los comentarios propios',
+'reopened' => 'Reabierta',
+'loading' => 'Cargando...',
+'notifyown' => 'Notificar los cambios propios',
+'youremail' => 'Su dirección de correo',
+'thankyouforbug' => 'Gracias por informar del problema. Puede ver las tareas y seguir su desarrollo en cualquier momento usando la URL:',
+'anonuser' => 'Usuario anónimo',
+'conflict' => 'Conflicto',
+'file' => 'Archivo',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Tamaño',
+'projectgroup' => 'Grupo de proyecto',
+'profile' => 'Perfil:',
+'viewprofile' => 'Ver perfil',
+'regdate' => 'Registrado desde',
+'tasksopened' => 'Tareas abiertas',
+'replyto' => 'Responder a',
+'notifytypes' => 'Tipos de notificación',
+'pm.taskchanged' => 'Tarea modificada',
+'pm.taskreopened' => 'Tarea reabierta',
+'pm.depadded' => 'Dependecia añadida',
+'pm.depremoved' => 'Dependencia eliminada',
+'pmrequest' => 'Solicitud de MP',
+'pmrequestdenied' => 'Solicitud de MP denegada',
+'newassignee' => 'Nuevo asignado',
+'revdepadded' => 'Dependencia inversa añadida',
+'revdepaddedremoved' => 'Dependencia inversa eliminada',
+'assigneeadded' => 'Asignado añadido',
+'addusergroup' => 'Añadir usuario a este grupo',
+'groupmembers' => 'Miembros del grupo',
+'deleteuser' => 'Eliminar este usuario',
+'userdeleted' => 'Usuario eliminado',
+'autoassign' => 'Autoasignar tarea al propietario de la categoría',
+'ssl' => 'SSL',
+'updatewrong' => "Tiene habilitada la verificacion de nuevas versiones, pero ha acurrido un error \n contactandose con el servidor de actualizaciones. Es probable que su servidor no \n permita conexiones hacia el exterior o que haya ocurrido un problema de red.\n Por favor visite el sitio web de flyspray para asegurarse que esta utilizando la \n version mas reciente",
+'deleteproject' => 'Eliminar este proyecto y mover sus contenidos a',
+'projectdeleted' => 'Proyecto eliminado satisfactoriamente',
+'feedforall' => 'Origen de todos los proyectos',
+'usercreated' => 'Usuario creado',
+'created' => 'Creado',
+'deleted' => 'Eliminado',
+'userid' => 'Id de usuario',
+'editassignments' => 'Editar asignaciones',
+'preview' => 'Vista previa',
+'anyprogress' => 'Cualquier progreso',
+'tasksrelated' => 'Tareas relacionadas con esta',
+'duplicatetasks' => 'Tareas duplicadas de esta',
+'databasemodfailed' => 'Modificación de la base de datos fallida. Posible motivo, la falta de permisos.',
+'frequency' => 'Frecuencia',
+'newuserregistered' => 'Se ha registrado un nuevo usuario en su instalación de Flyspray. Los detalles del usuario son:',
+'newuserregisterednotify' => 'Se ha registrado un nuevo usuario',
+'notify_registration' => 'Notificar a los administradores de los nuevos registros de usuario',
+'textversion' => 'Versión texto',
+'onlyprimary' => 'Tareas que no bloquean otras tareas',
+'switch' => 'Cambiar',
+'max' => 'Máx.',
+'dates' => 'Fechas',
+'selectduedatefrom' => 'A completar para',
+'selectduedateto' => 'A',
+'selectsincedatefrom' => 'Cambiado desde',
+'selectsincedateto' => 'A',
+'selectdate' => 'Seleccionar fecha',
+'selectopenedfrom' => 'Abierta desde',
+'selectopenedto' => 'A',
+'selectclosedfrom' => 'Cerrada desde',
+'selectclosedto' => 'A',
+'startat' => 'Comenzada a',
+'hasattachment' => 'Tiene adjuntos',
+'private' => 'Privada',
+'watching' => 'Monitoreando',
+'alreadyvotedthistask' => 'Votó por esta tarea',
+'alreadyvotedthisday' => 'Ya ha votado hoy',
+'visibility' => 'Visibilidad',
+'public' => 'Pública',
+'leaveemptyauto' => 'Deja los campos de contraseña en blanco si desea que se genere una automáticamente.',
+'novalidemail' => 'No escribió una dirección de correo válida.',
+'novalidjabber' => 'No escribió una dirección de Jabber válida.',
+'missingrequired' => 'No rellenó todos los campos requeridos.',
+'entervalidusername' => 'Por favor, introduzca un nombre de usuario y nombre real válidos.',
+'couldnotaddusernotif' => 'No se pudo añadir a este usuario a la lista de notificación.',
+'defaulttask' => 'Descripción por defecto de la tarea',
+'all' => 'Todos',
+'events.useraddedtoassignees'=> 'Usuario añadido a los asignados',
+'vote(s)' => 'Voto(s)',
+'eventlog' => 'Registro de eventos',
+'assignmentchanged' => 'Asignación cambiada',
+'detailedinfo' => 'Información detallada',
+'All' => 'Todos',
+'tasksireported' => 'Tareas de las que yo he informado',
+'recentlyopened' => 'Abierta recientemente',
+'stats' => 'Estadística',
+'totaltasks' => 'Tareas totales',
+'mostwanted' => 'Tareas más buscadas',
+'defaultentry' => 'Página inicial por defecto',
+'toplevel' => 'Vista de más alto nivel',
+'overview' => 'Vista general',
+'error#' => 'Número de error',
+'error1' => 'No tiene suficientes permisos para ver este adjunto.',
+'error3' => 'Acción repetida, redireccionando a la página principal.',
+'error4' => 'No tiene permisos de administrador.',
+'error5' => 'Ese usuario no existe en esta instalación.',
+'error6' => 'Area de administracíon no válida.',
+'error7' => 'Ingreso fallido (usuario o contraseña incorrecta)',
+'error8' => 'No escribió el nombre de usuario y la contraseña.',
+'error9' => 'La tarea no existe o no tiene permisos para verla.',
+'error10' => 'La tarea no existe o no tiene permisos para verla.',
+'error11' => 'No tiene permisos para editar este comentario.',
+'error12' => '¡No es una clave mágica válida! ¿Está seguro de que la recibió con el mensaje de notificación?',
+'error13' => 'Los usuarios anónimos no tienen perfil.',
+'error14' => 'No tienes permisos suficientes para crear un grupo.',
+'error15' => 'No tienes permisos suficientes para abrir una tarea.',
+'error16' => 'No eres manager de proyecto.',
+'error17' => 'Ãrea MP no válida.',
+'error18' => 'URL mágica no válida.',
+'error19' => 'Ese usuario no existe en esta instalación de Flyspray.',
+'error20' => 'Modificación a la base de datos no válida.',
+'error21' => 'No se pudieron enviar uno o más correos. Verifique su configuración.',
+'error22' => 'No está permitido el registro de nuevos usuarios.',
+'error23' => 'Usuario o grupo deshabilitado para iniciar sesión.',
+'error24' => 'No se ha configurado ni el ejecutable dot ni un servidor público de dot.',
+'error25' => 'El mapa de ruta solamente está disponible para un proyecto específico.',
+'error26' => 'Proveedor de código «oauth».',
+'error27' => 'No se pudo ingresar. Debe autorizarnos para ver su dirección de correo.',
+'error28' => 'No tiene permisos para acceder a esta área.',
+'done' => 'hecho',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'El proyecto no ha podido borrarse.',
+'GMT' => 'GMT',
+'timezone' => 'Zona Horaria',
+'accept' => 'Aceptar',
+'reasonfordeinal' => 'Razón de la denegación',
+'pruneclosedlinks' => 'Limpiar enlaces cerrados',
+'pruneclosedtasks' => 'Limpiar tareas cerradas',
+'pagegenerated' => 'Página e imagen generadas en %d segundos.',
+'pruninglevel' => 'Nivel de limpieza',
+'lastuser' => 'No se puede eliminar el último usuario.',
+'allprivate' => 'Todos los proyectos son privados.',
+'deletegroup' => 'Elimina este grupo y mueve sus usuarios a',
+'parent' => 'Padre',
+'ordertip' => 'El orden en que estos elementos aparecerán en la lista',
+'showtip' => 'Muestra este elemento en la lista',
+'deletetip' => 'Elimina este elemento de la lista',
+'del' => 'Eliminar',
+'allpriorities' => 'Todas las Prioridades',
+'request1' => 'Se ha solicitado cerrar una tarea.',
+'request2' => 'Una solicitud para reabrir una tarea ha sido efectuada',
+'expand' => 'Expandir',
+'collapse' => 'Contraer',
+'expandall' => 'Expandir todo',
+'collapseall' => 'Contraer todo',
+'noroadmap' => 'El mapa de ruta no está disponible (no existen versiones «futuras» específicas en este proyecto).',
+'minpwsize' => 'El largo mínimo de la contraseña es de 5 carácteres',
+'passwordtoosmall' => 'La contraseña es muy pequeña.',
+'accountwaslocked' => 'Su cuenta ha sido bloqueada debido a reitarados intentos de ingreso.',
+'failedattempts' => 'Han habido %d intentos fallidos de ingreso.',
+'groupnotexist' => 'El grupo seleccionado no existe en proyecto.',
+'searchindetails' => 'Detalles de búsqueda',
+'showasassignees' => 'Mostrar asignados',
+'find' => 'Buscar',
+'tls' => 'TLS',
+'isadmin' => 'Es administrador',
+'addvotes' => 'Añadir voto',
+'removevote' => 'Eliminar voto',
+'voteremoved' => 'Su voto ha sido eliminado.',
+'voteremovefailed' => 'Su voto no puede ser eliminado en este momento.',
+'novotes' => 'No tiene votos en esta tarea.',
+'connectedtasks' => 'Tareas conectadas:',
+'taskdependencies' => 'Tareas dependientes',
+'viewgraph' => 'ver gráfica',
+'notaskdependencies' => 'Esta tarea no depende de otras tareas.',
+'dependson' => 'Depende de',
+'blocks' => 'Bloquea',
+'newdependency' => 'Nueva dependencia:',
+'nouserstoadd' => 'Sin usuario a agregar. Asegúrese de definir un nombre real, nombre de usuario y correo para cada uno.',
+'dispintro' => 'Mostrar mensage introductorio principal',
+/* note only the English version for 'dispintro' is supported
+ * other languages should also be taken cared of in the future*/
+
+'setsupertask' => 'Ajustar el Id de la supertarea:',
+'supertaskmodified' => 'El Id de la supertarea ha sido modificado',
+'set' => 'Ajustar',
+'supertask' => 'Supertarea',
+'setparent' => 'Ajustar el Id de la tarea padre para esta tarea',
+'selfsupertasknotallowed' => 'El Id de la supertarea no puede ser el mismo que el de la misma tarea',
+'quickaction' => 'Acciones rápidas',
+'updateselectedtasks' => 'Actualizar tareas seleccionadas',
+'notspecified' => 'No especificado',
+'editselectedtasks' => 'Editar tareas seleccionadas',
+'information' => 'Información',
+'taskclosedisabled' => 'El cerre de la tarea ha sido deshabilitado debido a que hay tareas dependientes aún están abiertas:-',
+'daysleft' => 'días restantes',
+'dayoverdue' => 'días de entrega',
+'duetoday' => 'Se entrega hoy.',
+'daysbeforealert' => 'Días antes de la alerta',
+'associatedsubtask' => 'Éxito al enlazar la tarea #FS',
+'associatesubtask' => 'Asociar el Id de la subtarea a esta tarea',
+'subtaskid' => 'Id de la subtarea',
+'subtaskalreadyhasparent' => 'La subtarea que ingreso ya tiene una tarea padre, por favor, limpie los datos de esta antes de asociar.',
+'subtaskisparent' => 'La subtarea que ingresó es la tarea padre de esta misma tarea. Subtarea no asociada.',
+'subtasknotexist' => 'La subtarea ingresada no existe.',
+'subtaskremovedmsg' => 'La subtarea ha sido eliminada',
+'subtaskadded' => 'Subtarea añadida',
+'subtaskremoved' => 'Subtarea eliminada',
+'addnewsubtask' => 'Agregar nueva subtarea',
+'hidesubtasks' => 'Ocultar subtareas',
+'voteforthistask' => 'Votar por esta tarea',
+'watchthistask' => 'Vigilar esta tarea',
+'privatethistask' => 'Hacer esta tarea privada',
+'adddependenttask' => 'Asociar tarea dependiente',
+'associatetaskid' => 'Id de la subtarea',
+'parenttaskid' => 'Id de la tarea padre',
+'invalidsupertaskid' => 'Id de la tarea padre no válido.',
+'supertaskadded' => 'Añadir subtarea',
+'supertaskremoved' => 'Eliminar subtarea',
+'effort' => 'Esfuerzo',
+'efforttracking' => 'Seguimiento de esfuerzo',
+'useeffort' => 'Proyectos usando el seguimiento de esfuerzo',
+'estimatedeffort' => 'Esfuerzo estimado',
+'totalestimatedeffort' => 'Esfuerzo total estimado',
+'currenteffortdone' => 'Esfuerzo actual finalizado',
+'starteffort' => 'Iniciar seguimiento',
+'endeffort' => 'Finalizar seguimiento',
+'cleareffort' => 'Limpiar seguimiento',
+'addeffort' => 'Añadir esfuerzo',
+'manualeffort' => 'Añadir esfuero manualmente (H:M)',
+'efforttrackingstarted' => 'Seguimiento de esfuerzo concedido a esta tarea.',
+'efforttrackingnotstarted'=> 'No se puede iniciar el seguimiento a una tarea ya en seguimiento',
+'efforttrackingstopped' => 'El seguimiento de esfuerzo fue finalizado para esta tarea.',
+'efforttrackingcancelled' => 'El seguimiento de esfuerzo fue cancelado para esta tarea.',
+'efforttrackingadded' => 'Esfuerzo manual guardado para esta tarea.',
+'trackinginprogress' => 'Seguimiento en progreso',
+'viewestimatedeffort' => 'Puede ver el seguimiento de esfuerzo',
+'viewcurrenteffortdone' => 'Puede ver el seguimiento actual de esfuerzo',
+'trackeffort' => 'Puede seguir el esfuerzo',
+'invalideffort' => 'El esfuerzo ingresado no es válido, Debe ser un número',
+'showpass' => 'Mostrar contraseña',
+'chooseafile' => '¡Seleccione un archivo!',
+'incorrectfiletype' => 'Tipo de archivo incorrecto. Admitidos: .jpg, .jpeg, .gif, .png.',
+'oauthreqpass' => 'Sin contraseña a pedir. Se registró a travez de %s',
+'addmultipletasks' => 'Añadir múltiples tareas',
+'pendingnewuserrequest' => 'Hay peticiones de Nuevos usuarios pendientes',
+'adminrequestswaiting' => 'Hay peticiones de administrador pendientes',
+'clicktoedit' => 'Haga clic en cada campo para una edición rápida',
+'confirmedit' => 'confirmar',
+'canceledit' => 'cancelar',
+'regapprovedbyadmin' => 'Registración aprobadas por administradores (deshabilitar código de confirmación)',
+'activity' => 'Actividad',
+'myactivity' => 'Mi actividad',
+'emailverificationwrong' => 'El correo de confirmación no tiene la misma dirección que el correo registrado',
+'verifyemailaddress' => 'Confirmar la dirrecipon de correo',
+'hideemails' => 'Ocultar la dirección de correo',
+'hidemyemail' => 'Ocultar midirección de correo',
+'exporttasklist' => 'Exportar lista de tareas',
+'onedecimal' => 'un decimal',
+'manday' => 'hombres-día',
+'mandays' => 'hombres-días',
+'mandayabbrev' => 'h/d',
+'hourspermanday' => 'Horas por hombres-día (HH:mm)',
+'itemexists' => 'El objeto %s ya existe en la base de datos.',
+'categoryitemexists' => 'El objeto %s ya existe en la categoría %s ien la base de datos.',
+'pageswelcomemsg' => 'Pages where to show main intro message',
+'pagesintromsg' => 'Pages where to show intro message',
+'activeoauths' => 'Activar proveedores de Oauth',
+'onlyoauthreg' => 'Only allow Oauths registrations',
+'estimatedeffortformat' => 'Estimated effort display format',
+'currenteffortdoneformat' => 'Current effort done display format',
+'minute' => 'minuto',
+'minutes' => 'minutos',
+'minuteplural' => 'minutos',
+'minutesingular' => 'minuto',
+'minuteabbrev' => 'min',
+'hourplural' => 'horas',
+'hoursingular' => 'hora',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Estimación de esfuerzo en tareas abiertas',
+'currenteffortdoneopen' => 'Current effort spent in open tasks',
+'signinwith' => 'Sign in with %s',
+'canviewroadmap' => 'Can view roadmap',
+'enableavatars' => 'Activar avatares',
+'maxavatarsize' => 'Max avatar size in pixels',
+'taskhassubtask' => 'This task has the following sub-task',
+'taskhassubtasks' => 'This task has the following sub-tasks',
+'translations' => 'Traducciones',
+'translate' => 'Traducir',
+'taskdescription' => 'Descripción de la tarea',
+'notaskdescription' => 'tarea sin descripción',
+'pleaseselect' => 'Please select',
+'closeselectedtasks' => 'Cerrar tareas seleccionadas',
+'closetasks' => 'close tasks',
+'hintforbulkimport' => '<b>Trucos para importación masiva:</b>
+ <ol>
+ <li>Copy and paste from an excel spreadsheet or CSV by pasting one entire column.</li>
+ <li>Currently you can only paste Summary and Details.</li>
+ <li>There are suggestions when you assign to someone, and to no-one if there is no matched name.</li>
+ </ol>',
+'taskissubtaskof' => 'This task is a sub task of',
+'applyfirstline' => 'Apply first line',
+'addmorerows' => 'Añadir más filas',
+'addtasks' => 'Añadir tareas',
+'massopsdisabled' => 'Sorry, bulk editing is currently disabled for Flyspray 1.0. We plan to finish implementation for a later release of Flyspray. You can enable them in source code again at your own risk, but read the comments there before doing it.',
+'viewroadmap' => 'Can view roadmap',
+'nosuicide' => 'Dear user, my program doesn\'t allow you to destroy your access to Flyspray by disabling your own account or switching your own group. The empathic brother of HAL9000',
+'movingtodifferentproject' => 'Moving a task that has either a parent or subtasks to a different project is not allowed. You must break the connection between them first.',
+'musthavesameproject' => 'Parent and subtask must belong to the same project.',
+'defaultorderby' => 'Order tasklist by default by',
+'defaultorderby2' => 'then by',
+'viewowntasks' => 'Ver Mis Tareas',
+'viewgroupstasks' => 'View groups tasks',
+'urlrewriting' => 'Reescritura de urls (url amigables)',
+'enablehtaccess' => 'Por favor, habilita el uso de .htaccess en la carpeta de Flyspray antes de activar la reescritura de urls',
+'nomodrewrite' => 'Parece que mod-rewrite no esta habilitado en el servidor de Flyspray. Debe estar activarlo para activar urls amigables',
+'on' => 'habilitado',
+'off' => 'deshabilitado',
+'defaultorderbydirection' => 'Default order by direction',
+'ascending' => 'Ascendiente',
+'descending' => 'Descendiente',
+'myassignedtasks' => 'Mis tareas',
+'commentedon' => 'commented on',
+'maxvoteperday' => 'Maximum votes per day',
+'votesperproject' => 'User\'s limit of votes per project',
+'votelimitreached' => 'You reached your vote limit for this project. See your profile page for which tasks you currently vote. There you can also take back vote. So we can see what tasks are most important for you. Solved tasks get your vote back into your available voting limit.',
+'myvotes' => 'Mis Votos',
+'tag' => 'Etiqueta',
+'tags' => 'Etiquetas',
+'tagsinfo' => 'Free tagging/labeling of tasks in Flyspray: Separate tags by ; They aren\'t used yet for searching, sorting or filtering.',
+'novalues' => 'sin entradas',
+'youhaveregistered' => 'You have registered at Flyspray. Your details are as follows:',
+'youhaveregisterednotify' => 'Your registration at Flyspray has been accepted by Administrators.',
+'usedintasks' => 'Uso',
+'freetagging' => 'allow user defined tags',
+'keyboardshortcuts' => 'Atajos de teclado',
+'testmailsettings' => 'Test currently active email settings',
+'test' => 'Prueba',
+'testmailsettingsnotice' => 'And also check if you received the test email in the mail account of the current user (see \'myprofile\'-page)',
+'invalidinput' => 'Oh, there are some incompatible properties sent or missing.',
+'invalidprogress' => 'Please choose a valid progress value.',
+'invalidpriority' => 'Please choose a valid priority value.',
+'invalidseverity' => 'Please choose a valid severity value.',
+'invalidstatus' => 'Please choose a valid task status.',
+'invalidcategory' => 'Please choose a valid category.',
+'invalidreportedversion' => 'Please choose a valid report version.',
+'invaliddueversion' => 'Please choose a valid due version.',
+'invalidos' => 'Please choose a valid operating system.',
+'invalidtags' => 'Please choose only allowed tags.',
+'invalidassignees' => 'Please choose only allowed assignees.',
+'customize' => 'customize',
+'hidesubs' => 'ocultar subtareas',
+'hideprivate' => 'hide private tasks',
+'hideclosed' => 'ocultar tareas cerradas',
+'currentproject' => 'proyecto actual',
+'targetproject' => 'target project',
+'availablekeybshortcuts' => 'Available keyboard shortcuts',
+'logindialoglogout' => 'Login Dialog / Logout',
+'focustaskidsearch' => 'focus taskid search',
+'openselectedtask' => 'abrir tareas seleccionada',
+'movecursorup' => 'move cursor up',
+'movecursordown' => 'move cursor down',
+'taskdetails' => 'Detalle de la tarea',
+'taskediting' => 'Task Editing',
+'savetask' => 'guardar tarea',
+'generalintegration' => 'General Integration Strings',
+'generalintegrationdesc' => 'This content is put by default theme inside the body-tag, below main content and before footer tag. Where it really appears on screen depends on CSS. Use a custom_*.css for that.',
+'footerintegration' => 'Footer Integration Strings',
+'footerintegrationdesc' => 'This content is put by default theme into the logical footer area by default. Where it really appears on screen depends on CSS. Use a custom_*.css for that.',
+'editorbold' => 'B',
+'editorboldhint' => 'negrita',
+'editoritalic' => 'I',
+'editoritalichint' => 'cursiva',
+'editorunderline' => 'U',
+'editorunderlinehint' => 'subrayado',
+'editorstrikethrough' => 'S',
+'editorstrikethroughhint' => 'tachado',
+
+);
+?>
diff --git a/lang/fi.php b/lang/fi.php
new file mode 100644
index 0000000..41bbdf0
--- /dev/null
+++ b/lang/fi.php
@@ -0,0 +1,1058 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Muokkaa käyttäjää',
+'accountenabled' => 'Käyttäjätili voimassa',
+'editallusers' => 'Näytä kaikki käyttäjät',
+'username' => 'Käyttäjätunnus',
+'usersupdated' => 'Käyttäjät päivitetty',
+'realname' => 'Nimi',
+'emailaddress' => 'Sähköpostiosoite',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Kuva',
+'notifytype' => 'Ilmoitustapa',
+'group' => 'Ryhmä',
+'enableaccounts' => 'Ota käyttäjätilit käyttöön',
+'disableaccounts' => 'Poista käyttäjätilit käytöstä',
+'deleteaccounts' => 'Poista käyttäjätili',
+'updatedetails' => 'Päivitä tiedot',
+'setglobally' => 'Tämä on yleisasetus.',
+'usergroupmanage' => 'Käyttäjä- ja ryhmähallinta',
+'newuser' => 'Uusi käyttäjä',
+'newuserbulk' => 'Useita uusia käyttäjiä',
+'bulkuserstoadd' => 'Uudet käyttäjät',
+'optionsforallusers' => 'Oletusasetukset uusille käyttäjille',
+'newgroup' => 'Uusi ryhmä',
+'yes' => 'Kyllä',
+'no' => 'Ei',
+'editgroup' => 'Muokkaa ryhmää',
+'groupname' => 'Ryhmän nimi',
+'description' => 'Kuvaus',
+'admin' => 'Ylläpitäjä',
+'opennewtasks' => 'Avaa uusia tehtäviä',
+'modifytasks' => 'Muokkaa tehtäviä',
+'addcomments' => 'Lisää kommentteja',
+'attachfiles' => 'Liitä tiedostoja',
+'vote' => 'Äänestä',
+'groupenabled' => 'Jäsenet voivat kirjautua',
+'groupopen' => 'Jäsenet voivat kirjautua',
+'tasktypelist' => 'Tehtävätyypit',
+'categorylist' => 'Luokitukset',
+'oslist' => 'Käyttöjärjestelmät',
+'resolutionlist' => 'Päätökset',
+'versionlist' => 'Versiot',
+'severitylist' => 'Vakavuudet',
+'listnote' => 'Huom! Näytä-valintaruudun poisvalinta saattaa vaikuttaa tehtäviin muokkauksessa. Nimi-kentän muutos vaikuttaa kaikkiin samannimisiin tehtäviin. Kohteet, joiden poisto ei onnistu, ovat joko käytössä jossain tehtävässä tai niitä tarvitaan ohjelman oikein toimimiseksi.',
+'name' => 'Nimi',
+'order' => 'Järjestys',
+'back' => 'Takaisin',
+'text' => 'Teksti',
+'highlight' => 'Korosta',
+'show' => 'Näytä',
+'owner' => 'Omistaja',
+'selectowner' => 'Valitse omistaja',
+'update' => 'Päivitä',
+'addnew' => 'Lisää uusi',
+'flysprayprefs' => 'Flysprayn asetukset',
+'projecttitle' => 'Projektin otsikko',
+'baseurl' => 'Asennuksen URL-osoite',
+'replyaddress' => 'Ilmoitusten vastausosoite',
+'themestyle' => 'Teema / Tyyli',
+'customstyle' => 'Kustomoitu',
+'language' => 'Kieli',
+'anonview' => 'Salli tuntemattomien käyttäjien nähdä tehtävät',
+'allowanon' => 'Salli tuntemattomien käyttäjien avata uusia tehtäviä',
+'never' => 'Ei koskaan',
+'anonymously' => 'Anonyymisti',
+'afterregister' => 'Vasta rekisteröinnin jälkeen',
+'spamproof' => 'Vahvistuskoodi uusille käyttäjille',
+'anongroup' => 'Oletusryhmä uusille käyttäjille',
+'groupassigned' => 'Näiden ryhmien jäsenille voidaan osoittaa tehtäviä',
+'forcenotify' => 'Tehtäväilmoitusten pakotus',
+'neversend' => 'Älä lähetä koskaan',
+'userchoose' => 'Käyttäjän oma valinta',
+'email' => 'Sähköposti',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Luokituksen oletusomistaja',
+'noone' => 'Ei kukaan',
+'jabbernotify' => 'Jabber-ilmoitukset',
+'jabberserver' => 'Palvelin',
+'jabberport' => 'Portti',
+'jabberuser' => 'Käyttäjänimi',
+'jabberpass' => 'Salasana',
+'saveoptions' => 'Tallenna asetukset',
+'editcomment' => 'Muokkaa kommenttia',
+'commentby' => 'Kommentoija',
+'saveeditedcomment' => 'Tallenna kommentti',
+'projectprefs' => 'Projektin asetukset',
+'pagetitle' => 'Sivun otsikko',
+'defaultproject' => 'Oletusprojekti',
+'projectlists' => 'Projektilista',
+'showlogo' => 'Näytä logo',
+'showgravatars' => 'Näytä Gravatar (A Globally Recognized Avatar)',
+'emailNoHTML' => 'Sähköpostit tekstimuodossa',
+'intromessage' => 'Aloitusviesti',
+'active' => 'aktiivinen',
+'inactive' => 'epäaktiivinen',
+'isactive' => 'Aktiivinen',
+'showinactive' => 'Näytä epäaktiiset projektit',
+'hideinactive' => 'Piilota epäaktiiviset projektit',
+'createproject' => 'Uusi projekti',
+'nopermission' => 'Oikeutesi eivät riitä sivun näyttämiseen.',
+'listordertip' => 'Listan esitysjärjestys',
+'listshowtip' => 'Näytä listassa',
+'categoryownertip' => 'Henkilö, jolle ilmoitetaan luokituksen uudesta tehtävästä',
+'categoryparenttip' => 'Uuden luokituksen isäntäluokitus',
+'notsubcategory' => 'Ei mikään (päätason luokitus)',
+'showinlineimages' => 'Näytä kuvaliitteet tekstin joukossa',
+'dateformat' => 'Päivämäärän muoto',
+'dateformat_extended' => 'Laajennettu päivämäärän muoto',
+'cache_feeds' => 'Syötteen välimuistikäyttö',
+'no_cache' => 'Ei välimuistia',
+'cache_disk' => 'Välimuisti levyllä',
+'cache_db' => 'Välimuisti tietokannassa',
+'subcategoryof' => 'Alaluokitus',
+'visiblecolumns' => 'Tehtävälistan sarakkeet',
+'visiblefields' => 'Tehtävän kentät',
+'tense' => 'Aikamuoto',
+'listtensetip' => 'Menneisyys, nykyisyys tai tulevaisuus',
+'past' => 'Menneisyys',
+'present' => 'Nykyisyys',
+'future' => 'Tulevaisuus',
+'oldpass' => 'Aiempi salasana',
+'nooldpass' => 'Aiempaa salasanaa ei ollut asetettu',
+'oldpasswrong' => 'Aiempi salasana oli väärä',
+'changepass' => 'Vaihda salasana',
+'confirmpass' => 'Vahvista salasana',
+'projectmanager' => 'Projektin ylläpitäjä',
+'viewtasks' => 'Näytä tehtävät',
+'modifyowntasks' => 'Muokkaa tehtäviäsi',
+'modifyalltasks' => 'Muokkaa kaikkia tehtäviä',
+'viewcomments' => 'Näytä kommentit',
+'editcomments' => 'Muokkaa kommentteja',
+'deletecomments' => 'Poista kommentteja',
+'viewattachments' => 'Näytä liitteet',
+'createattachments' => 'Luo liitteitä',
+'deleteattachments' => 'Poista liitteitä',
+'viewhistory' => 'Näytä historia',
+'closeowntasks' => 'Sulje omat tehtäväni',
+'closeothertasks' => 'Sulje kaikki tehtävät',
+'assigntoself' => 'Osoita osoittamattomat tehtävät itsellesi',
+'assignotherstoself' => 'Osoita muille kuuluvia tehtäviä itsellesi',
+'viewreports' => 'Näytä tapahtumaloki',
+'othersview' => 'Salli kaikkien nähdä tämä projekti',
+'othersviewroadmap' => 'Salli kenen tahansa nähdä projektin etenemissuunnitelma',
+'usersandgroups' => 'Käyttäjät ja ryhmät',
+'globalgroup' => 'Yleisryhmä',
+'globalgroups' => 'Yleisryhmät',
+'defaultglobalgroup' => 'Oletusryhmä uusille käyttäjille',
+'addtogroup' => 'Lisää ryhmään',
+'moveuserstogroup' => 'Siirrä käyttäjät ryhmään',
+'nogroup' => 'Ei ryhmää - poista projektista',
+'eventdesc' => 'Tapahtuman kuvaus',
+'requestedby' => 'Pyytänyt',
+'daterequested' => 'Pyydetty',
+'closetask' => 'Sulje tehtävä',
+'reopentask' => 'Avaa tehtävä uudelleen',
+'applymember' => 'Hae jäsenyyttä projektissa',
+'forcurrentproj' => 'Valitulle projektille',
+'lostpw' => 'Uuden salasanan pyyntö',
+'lostpwexplain' => 'Anna käyttäjätunnuksesi salasanan muutoslinkkiä varten. Se lähetetään käyttäjäprofiilissasi olevaan sähköpostiosoitteeseen.',
+'sendlink' => 'Lähetä linkki',
+'savenewpass' => 'Tallenna uusi salasana',
+'anonreg' => 'Salli uudet käyttäjät',
+'allowanonopentask' => 'Salli tuntemattomien käyttäjien avata tehtäviä',
+'editglobalgroup' => 'Muokkaa yleisryhmää',
+'editgroupforproj' => 'Muokkaa ryhmää projektissa',
+'notshownforadmin' => 'Ylläpitäjien ryhmän oikeuksia ei näytetä. Niitä ei tarvitse muokata.',
+'general' => 'Yleinen',
+'userregistration' => 'Käyttäjäasetukset',
+'notifications' => 'Ilmoitukset',
+'resetoptions' => 'Palauta asetukset',
+'preferences' => 'Asetukset',
+'tasktypes' => 'Tehtävätyypit',
+'resolutions' => 'Päätökset',
+'categories' => 'Luokitukset',
+'operatingsystems' => 'Käyttöjärjestelmät',
+'versions' => 'Versiot',
+'admintoolboxlong' => 'Ylläpitotyökalut',
+'newproject' => 'Uusi projekti',
+'delete' => 'Poista',
+'link' => 'Linkki',
+'referencelinks' => 'Viitelinkit:',
+'listdeletetip' => 'Poista listasta',
+'lookandfeel' => 'Ulkoasu',
+'globaltheme' => 'Yleinen teema',
+'emailnotify' => 'Sähköposti-ilmoitukset',
+'fromaddress' => 'Lähettäjän osoite',
+'smtpserver' => 'SMTP-palvelin',
+'smtpuser' => 'SMTP-käyttäjätunnus',
+'smtppass' => 'SMTP-salasana',
+'addrewrite' => 'Käytä muokattuja URL-osoitteita.',
+'usereminderdaemon' => 'Käynnistä taustamuistutukset',
+'tasksperpage' => 'Tehtävälistan sivukohtainen tehtävämäärä',
+'addtoassignees' => 'Liitä minut tekijäksi',
+'taskstatuses' => 'Tehtävien tilat',
+'canvote' => 'Äänioikeus tehtävissä',
+'loginsuccessful' => 'Sisäänkirjautuminen onnistui.',
+'youareloggedout' => 'Olet kirjautunut ulos.',
+'waitwhiletransfer' => 'Odota hetki, kun sinut siirretään...',
+'clicknowait' => 'Klikkaa tätä, jos et halua odottaa.',
+'accountdisabled' => 'Käyttäjätilisi on jäädytetty. Ota yhteyttä ylläpitoon.',
+'task' => 'Tehtävä',
+'edittask' => 'Muokkaa tehtävää',
+'openedby' => 'Avannut',
+'editedby' => 'Viimeksi muokannut',
+'tasktype' => 'Tehtävätyyppi',
+'category' => 'Luokitus',
+'status' => 'Tila',
+'assignedto' => 'Osoitettu',
+'operatingsystem' => 'Käyttöjärjestelmä',
+'severity' => 'Vakavuus',
+'reportedversion' => 'Raportointiversio',
+'dueinversion' => 'Valmis versiossa',
+'defaultdueinversion' => 'Oletus valmistumisversio uusille tehtäville',
+'undecided' => 'Ei päätetty',
+'percentcomplete' => 'Valmistumisaste',
+'details' => 'Tiedot',
+'savedetails' => 'Tallenna tiedot',
+'canceledit' => 'peruuta',
+'anonymous' => 'Tuntematon ilmoittaja',
+'complete' => 'valmis',
+'closedby' => 'Sulkenut',
+'reasonforclosing' => 'Sulkemisen syy:',
+'reopenthistask' => 'Avaa tehtävä uudelleen',
+'comments' => 'Kommentit',
+'attachments' => 'Liitteet',
+'relatedtasks' => 'Liittyvät tehtävät',
+'edit' => 'Muokkaa',
+'addcomment' => 'Uusi kommentti',
+'fileuploadedby' => 'Tiedoston lisääjä',
+'uploadafile' => 'Uusi liite',
+'addalink' => 'Uusi linkki',
+'addanotherlink' => 'Lisää uusi linkki',
+'uploadnow' => 'Vie tiedosto!',
+'thesearerelated' => 'Nämä tehtävät liittyvät tähän tehtävään',
+'remove' => 'Poista',
+'addnewrelated' => 'Uusi liittyvä tehtävä',
+'add' => 'Lisää',
+'otherrelated' => 'Muut tähän tehtävään liittyvät tehtävät',
+'receivenotify' => 'Tehtävän muutoksista ilmoitetaan näille käyttäjille.',
+'addusertolist' => 'Lisää käyttäjä tähän listaan',
+'addtolist' => 'Lisää listaan',
+'addmyself' => 'Lisää minut tähän listaan',
+'removemyself' => 'Poista minut tästä listasta',
+'theseusersnotify' => 'Näille käyttäjille ilmoitetaan aina kun tämä tehtävä muuttuu.',
+'attachedtoproject' => 'Liitetty projektiin',
+'reminders' => 'Muistutukset',
+'system' => 'Järjestelmä',
+'systemvalues' => 'Järjestelmän yleiset arvot',
+'projectvalues' => 'Projektikohtaiset arvot',
+'remindthisuser' => 'Muistuta tätä käyttäjää',
+'thisoften' => 'Näin usein',
+'startafter' => 'Odota ennen muistutusten aloittamista',
+'hour' => 'tunti',
+'hours' => 'tuntia',
+'day' => 'päivä',
+'days' => 'päivää',
+'week' => 'viikko',
+'weeks' => 'viikkoa',
+'addreminder' => 'Lisää muistutus',
+'defaultreminder' => 'Tämä on muistutus. Katso Flyspray-tehtävä:',
+'message' => 'Viesti',
+'closed' => 'Suljettu',
+'filename' => 'Tiedoston nimi',
+'date' => 'Päiväys',
+'filesize' => 'Tiedoston koko:',
+'closurecomment' => 'Muita huomautuksia sulkemisesta:',
+'history' => 'Historia',
+'nohistory' => 'Historiaa ei ole saatavilla.',
+'eventdate' => 'Päiväys',
+'user' => 'Käyttäjä',
+'event' => 'Tapahtuma',
+'fieldchanged' => 'Muutettu kenttä',
+'taskopened' => 'Tehtävä avattu',
+'taskreopened' => 'Tehtävä avattu uudelleen',
+'taskclosed' => 'Tehtävä suljettu',
+'commentadded' => 'Kommentti lisätty',
+'commentedited' => 'Kommenttia muokattu',
+'commentdeleted' => 'Kommentti poistettu',
+'attachmentadded' => 'Liite lisätty',
+'attachmentdeleted' => 'Liite poistettu',
+'taskedited' => 'Tehtävän tietoja muokattu',
+'notificationadded' => 'Ilmoituslistaan lisätty',
+'notificationdeleted' => 'Poistettu ilmoituslistasta',
+'relatedadded' => 'Liittyvä tehtävä lisätty',
+'relateddeleted' => 'Liittyvä tehtävä poistettu',
+'taskassigned' => 'Tehtävä osoitettu',
+'taskreassigned' => 'Tehtävä uudelleenosoitettu',
+'assignmentremoved' => 'Tehtävän osoitus poistettu',
+'summary' => 'Yhteenveto',
+'addedasrelated' => 'Tehtävä lisätty liitettyjen tehtävien listalle',
+'deletedasrelated' => 'Tehtävä poistettu liitettyjen tehtävien listalta',
+'reminderadded' => 'Muistutus lisätty',
+'reminderdeleted' => 'Muistutus poistettu',
+'priority' => 'Kiireellisyys',
+'previousvalue' => 'Aiempi arvo',
+'newvalue' => 'Uusi arvo',
+'selectareason' => 'Valitse syy',
+'assigntome' => 'Osoita minulle',
+'reopenrequest' => 'Pyydä uudelleen avaamista',
+'requestclose' => 'Pyydä sulkemista',
+'ownershiptaken' => 'Käyttäjä otti omistukseensa',
+'closerequestmade' => 'Tehtävän sulkemista pyydetty',
+'reopenrequestmade' => 'Tehtävän uudelleen avaamista pyydetty',
+'taskdependson' => 'Tämä tehtävä riippuu',
+'taskdependsontask' => 'Tehtävä riippuu tehtävästä',
+'taskdependsontasks' => 'Tehtävä riippuu tehtävistä',
+'taskblock' => 'Tehtävä estää seuraavan sulkemisen',
+'taskblocks' => 'Tämä tehtävä estää sulkemasta näitä',
+'depadded' => 'Riippuvuussuhde lisätty',
+'depaddedother' => 'Tämä tehtävä lisätty riippuvuussuhteena',
+'depremoved' => 'Riippuvuussuhde poistettu',
+'depremovedother' => 'Tämä tehtävä poistettu toisen tehtävän riippuvuussuhteista',
+'showdetailserror' => 'Tehtävää ei ole tai oikeutesi eivät riitä sen näkemiseen.',
+'makeprivate' => 'Tee yksityiseksi',
+'makepublic' => 'Julkaise',
+'taskmadeprivate' => 'Tehtävä on muutettu yksityiseksi',
+'taskmadepublic' => 'Tehtävä julkaistu',
+'confirmdeletecomment' => 'Oletko varma, että haluat poistaa tämän kommentin? %s',
+'attachementswilldeleted' => 'Myös kaikki liitteet poistetaan!',
+'confirmdeleteattach' => 'Oletko varma, että haluat poistaa tämän liitteen?',
+'selectedhistory' => 'Näytetään valitut historiatiedot',
+'showallhistory' => 'Näytä historiavälilehti uudelleen',
+'hidethis' => 'Piilota uudestaan',
+'mark100' => 'Merkitse tehtävä valmiiksi',
+'watchtask' => 'tarkkaile tehtävää',
+'stopwatching' => 'lakkaa tarkkailemasta',
+'commentlink' => 'Linkki kommenttiin',
+'submitreq' => 'Lähetä muutospyyntö',
+'reasonforreq' => 'Muutospyynnön syy',
+'pmreqdenied' => 'Projektin ylläpitäjä kieltäytyi muutospyynnöstä',
+'taskpendingreq' => 'Odottaa projektin ylläpitäjän toimenpiteitä. Katso historiavälilehdeltä yksityiskohtia.',
+'previoustask' => 'Edellinen tehtävä',
+'nexttask' => 'Seuraava tehtävä',
+'duedate' => 'Valmistuu',
+'attachnoperms' => 'Kommenttiin sisältyy liitteitä, mutta oikeutesi eivät riitä niiden näkemiseen.',
+'linknoperms' => 'Kommenttiin sisältyy linkkejä, mutta oikeutesi eivät riitä niiden näkemiseen.',
+'open' => 'Avoimet',
+'depgraph' => 'Näytä riippuvuusgraafi',
+'reset' => 'Palauta',
+'selectusers' => 'Valitse käyttäjät...',
+'addmetoassignees' => 'Lisää minut tekijäksi',
+'addedtoassignees' => 'Käyttäjä lisätty tekijäksi',
+'dependencygraph' => 'Riippuvuusgraafi',
+'attachanotherfile' => 'Liitä toinen liite',
+'OK' => 'OK',
+'addvote' => 'Lisää ääni',
+'disable_lostpw' => 'Estä salasanan palautus',
+'disable_changepw' => 'Estä salasanan muokkaus',
+'notifyfromfs' => 'Ilmoitus Flyspraystä',
+'autogenerated' => 'TÄMÄ ON AUTOMAATTISESTI LUOTU VIESTI, ÄLÄ VASTAA SIIHEN',
+'forward' => 'Eteenpäin',
+'previous' => 'Edellinen',
+'next' => 'Seuraava',
+'first' => 'Ensimmäinen',
+'last' => 'Viimeinen',
+'page' => 'Sivu %d/%d.',
+'search' => 'Hae',
+'alltasktypes' => 'Kaikki',
+'allseverities' => 'Kaikki',
+'alldevelopers' => 'Kaikki',
+'notyetassigned' => 'Osoittamattomat',
+'allcategories' => 'Kaikki',
+'allstatuses' => 'Kaikki',
+'allopentasks' => 'Avoimet',
+'sortthiscolumn' => 'Lajittele tämän sarakkeen mukaan',
+'id' => 'ID',
+'project' => 'Projekti',
+'dateopened' => 'Avattu',
+'progress' => 'Edistyminen',
+'searchthisproject' => 'Hae tästä projektista',
+'dueanyversion' => 'Versioriippumaton',
+'anyversion' => 'Versioriippumaton',
+'dueversion' => 'Tavoiteversio',
+'lastedit' => 'Viimeksi muokattu',
+'os' => 'Käyttöjärjestelmä',
+'reportedin' => 'Raportoitu',
+'taskrange' => 'Näytetään tehtävät %d - %d / %d',
+'noresults' => 'Hakusi ei tuottanut tuloksia.',
+'takeaction' => 'Toimenpide',
+'watchtasks' => 'Tarkkaile valittuja tehtäviä',
+'stopwatchingtasks' => 'Lakkaa tarkkailemasta valittuja tehtäviä',
+'assigntaskstome' => 'Osoita valitut tehtävät minulle',
+'dueby' => 'Valmis',
+'dueanytime' => 'Ei valmistumisaikaa',
+'selectduedate' => 'Valitse valmistumisaika',
+'toggleselected' => 'Käännä valinta',
+'due' => 'Valmis',
+'assignedtome' => 'Omat tehtäväni',
+'tasklist' => 'Tehtävälista',
+'dateclosed' => 'Suljettu',
+'advanced' => 'Laajennettu',
+'searchcomments' => 'Hae kommenteista',
+'searchforall' => 'Hae kaikkia sanoja',
+'anonusers' => 'Tuntemattomat käyttäjät',
+'miscellaneous' => 'Sekalaista',
+'users' => 'Käyttäjät',
+'taskproperties' => 'Tehtävän ominaisuudet',
+'selectsincedate' => 'Valitse muuttunut jälkeen',
+'changedsince' => 'Muuttunut jälkeen',
+'updatefs' => 'Ole hyvä ja päivitä Flyspray.',
+'currentversion' => 'Nykyinen versiosi on',
+'latestversion' => 'ja viimeisin versio on',
+'hidemessage' => '(muistuta minua myöhemmin)',
+'saveas' => 'Tallenna haku nimellä',
+'nosearches' => 'Ei tallennettuja hakuja',
+'saving' => 'Tallennetaan...',
+'votes' => 'Ääniä',
+'tovote' => 'Äänestä',
+'allclosedtasks' => 'Kaikki suljetut',
+'password' => 'Salasana',
+'login' => 'Kirjaudu',
+'rememberme' => 'Muista minut',
+'lostpassword' => 'Salasana hukkunut?',
+'lostpwforfs' => 'Flysprayn kadonnut salasana',
+'lostpwmsg1' => "Terve.\n\nOlen kadottanut salasanani Flysprayhyn ",
+'lostpwmsg2' => ", olkaa hyvä ja antakaa minulle uusi salasana.\n\nKäyttäjätunnus: ",
+'regards' => "Terveisin,\n\n",
+'yourusername' => ' käyttäjätunnuksesi ',
+'locale' => 'fi-FI',
+'filenotexist' => 'Tiedostoa ei ole olemassa, tai oikeutesi eivät riitä,',
+'showtask' => 'Näytä tehtävä',
+'now' => 'Nyt',
+'go' => 'Mene!',
+'opentaskanon' => 'Uusi tehtävä nimettömänä',
+'register' => 'Rekisteröi',
+'addnewtask' => 'Uusi tehtävä',
+'reports' => 'Tapahtumaloki',
+'editmydetails' => 'Muokkaa tietojani',
+'logout' => 'Kirjaudu ulos',
+'disabledaccount' => 'Käyttäjätilisi on jäädytetty!<br>Sinut kirjataan ulos välittömästi...',
+'poweredby' => 'Palvelun tarjoaa Flyspray',
+'sponsoredby' => 'Flysprayta sponsoroi',
+'projects' => 'Projektit',
+'allprojects' => 'Kaikki projektit',
+'selectproject' => 'Projektille:',
+'tasksall' => 'Kaikki tehtävät',
+'tasksassigned' => 'Tehtäväni',
+'tasksreported' => 'Raportoimani tehtävät',
+'taskswatched' => 'Tarkkailemani tehtävät',
+'mysearch' => 'Hakuni',
+'admintoolbox' => 'Ylläpitotyökalut',
+'manageproject' => 'Projektinhallinta',
+'permissions' => 'Käyttöoikeudet',
+'hide' => 'Piilota',
+'pendingreq' => 'pyyntö projektin ylläpidolle',
+'errorpage' => 'Flyspray ei voi näyttää pyytämääsi sivua. Halusit ehkä nähdä tehtävän jota ei ole olemassa, tai sinulla ei ole oikeuksia nähdä haluamaasi sivua.<br/><br/>Olet saattanut myös yrittää tietomurtoa SQL-injektiota käyttämällä. Jos tämä pitää paikkansa, menee nurkkaan häpeämään ja miettimään toimintaasi. Älä yritä uudestaan palatessasi!',
+'permissionsforproject' => 'Käyttöoikeudet projektiin ',
+'switchto' => 'Vaihda',
+'lastsearch' => 'Viimeisin haku',
+'modify' => 'Muokkaa',
+'noticefrom' => 'Ilmoitus ',
+'hasopened' => ' on avannut uuden sinulle osoitetun Flyspray-tehtävän:',
+'moreinfonew' => 'Lisätietoja tästä bugista Flysprayn sivulta:',
+'newtaskcategory' => 'Flysprayhyn on avattu uusi tehtävä luokituksessa',
+'categoryowner' => 'Saat tämän ilmoituksen luokituksen omistajana.',
+'tasksummary' => 'Tehtäväyhteenveto:',
+'newtaskadded' => 'Uusi tehtäväsi on lisätty.',
+'summaryanddetails' => 'Täytä sekä yhteenveto että yksityiskohtainen selostus.',
+'summaryrequired' => 'Kirjoita lyhyt yhteenveto tehtävästä',
+'goback' => 'Palaa takaisin.',
+'messagefrom' => 'Tämä on automaattinen viesti Flyspraystä ',
+'hasjustmodified' => 'on juuri muokannut seuraavaa tehtävää.',
+'changedfields' => 'Muuttuneet kentät on merkitty **:lla.',
+'moreinfomodify' => 'Lisätietoja tehtävästä saat URL-osoitteesta:',
+'nolongerassigned' => 'Seuraava tehtävä ei ole enää osoitettu sinulle. Se on nyt osoitettu',
+'hasassigned' => 'osoitti sinulle seuraavan Flysprayn tehtävän:',
+'taskupdated' => 'Tehtävä päivitetty.',
+'tasksupdated' => 'Tehtävät päivitetty.',
+'hasclosedassigned' => 'sulki sinulle osoitetun Flyspray-tehtävän:',
+'unassigned' => 'Osoittamaton',
+'hasclosed' => 'sulki seuraavan tehtävän.',
+'youonnotify' => 'Tämä ilmoitus on lähetetty sinulle, koska olet ilmoituslistalla.',
+'taskclosedmsg' => 'Tehtävä on suljettu.',
+'returntotask' => 'Palaa tehtävän tietoihin',
+'backtoindex' => 'Palaa tehtävälistaan',
+'noclosereason' => 'Et valinnut tehtävän sulkemissyytä.',
+'hasreopened' => 'avasi uudelleen aiemmin sulkemasi tehtävän:',
+'taskreopenedmsg' => 'Tehtävä avattiin uudelleen.',
+'backtotask' => 'Palaa takaisin tehtävään.',
+'commentaddedmsg' => 'Kommentti lisätty.',
+'commenttoassigned' => 'lisäsi kommentin sinulle osoitettuun tehtävään.',
+'commenttotask' => 'lisäsi kommentin tähän tehtävään.',
+'nocommententered' => 'Sinun todellakin pitää kirjoittaa kommentti ennenkuin klikkaat \\"Lähetä\\".',
+'fillinfields' => 'Et täyttänyt kaikkia kenttiä.',
+'notcurrentpass' => 'Se ei ole nykyinen salasanasi!',
+'passchanged' => 'Salasanasi on muutettu.',
+'closewindow' => 'Voit nyt sulkea tämän ikkunan.',
+'passnomatch' => 'Salasanat eivät olleet samat!',
+'usernametaken' => 'Käyttäjätunnus on jo varattu. Sinun täytyy valita toinen.',
+'usernametakenbulk' => 'Käyttäjätunnus on jo varattu.',
+'newusercreated' => 'Uusi käyttäjätili on luotu.',
+'accountcreated' => 'Käyttäjätilisi on luotu.',
+'newuserwarning' => 'Huomioi, että ohjelman asetukset saattavat edellyttää käyttäjätilisi hyväksymistä ylläpitäjän taholta. Tämä voi olla syynä sisäänkirjautumisen epäonnistumiseen.',
+'nomatchpass' => 'Salasanat eivät täsmänneet keskenään.',
+'confirmwrong' => 'Vahvistuskoodi on virheellinen!',
+'formnotcomplete' => 'Lomaketta ei täytetty kokonaan.',
+'formnotnumeric' => 'Tieto ei ole numeerinen!',
+'groupnametaken' => 'Ryhmän nimi on jo varattu.',
+'newgroupadded' => 'Ryhmä lisätty.',
+'optionssaved' => 'Flysprayn asetukset tallennettu.',
+'hasuploaded' => 'on ladannut liitetiedoston tehtävään joka on osoitettu sinulle:',
+'hasattached' => 'on liittänyt tiedoston seuraavaan tehtävään.',
+'fileuploaded' => 'Tiedosto on ladattu',
+'fileerror' => 'Tiedoston tuonti epäonnistui. Tarkista <i>attachments/</i> hakemiston käyttöoikeudet.',
+'contactadmin' => 'Ota yhteyttä projektin ylläpitäjiin.',
+'selectfileerror' => 'Et valinnut tiedostoa.',
+'userupdated' => 'Käyttäjän tiedot on päivitetty',
+'realandemail' => 'Nimesi ja sähköpostiosoitteesi puuttuvat.',
+'groupupdated' => 'Ryhmän määrittely päivitetty.',
+'groupanddesc' => 'Ryhmän nimi puuttuu.',
+'fillallfields' => 'Ole hyvä ja täytä kaikki kentät.',
+'listPmustN' => 'Arvon "järjestys" tulee olla numero',
+'listupdated' => 'Lista on päivitetty.',
+'listitemadded' => 'Uusi rivi lisätty.',
+'relatedaddedmsg' => 'Uusi liittyvä tehtävä lisätty.',
+'relatederror' => 'Tämä tehtävä on jo liitettyjen tehtävien listalla.',
+'relatedremoved' => 'Liitetty tehtävä poistettu listalta.',
+'notifyadded' => 'Käyttäjä lisätty ilmoituslistaan.',
+'notifyerror' => 'Käyttäjä on jo tehtävän ilmoituslistalla.',
+'notifyremoved' => 'Käyttäjä poistettu ilmoituslistasta.',
+'editcommentsaved' => 'Kommentti päivitetty.',
+'commentdeletedmsg' => 'Kommentti poistettu.',
+'gotonewtask' => 'Siirry juuri luomaasi tehtävään',
+'projectcreated' => 'Uusi projektisi on luotu. Sen muokkaus onnistuu projektihallinnan avulla.',
+'customiseproject' => 'Muokkaa tätä projektia',
+'projectupdated' => 'Projektin asetukset päivitetty',
+'emptytitle' => 'Projektilla tulee olla nimi. Palaa korjaamaan puuttuva tieto.',
+'loginbelow' => 'Voit nyt yrittää kirjautua.',
+'attachmentdeletedmsg' => 'Liite on poistettu.',
+'reminderaddedmsg' => 'Muistutuksesi on lisätty.',
+'reminderdeletedmsg' => 'Valittu muistutus on poistettu.',
+'flyspraytask' => 'Flysprayn tehtävä',
+'fieldsmissing' => 'Jotkin kentät eivät sisältäneet tietoa tai tieto oli virheellistä.',
+'relatedinvalid' => 'Tehtävää ei ole olemassa.',
+'relatedproject' => 'Tehtävä on liitetty toiseen projektiin. Lisää liitos tästä huolimatta?',
+'addanyway' => 'Lisää joka tapauksessa',
+'cancel' => 'Peruuta',
+'alreadyedited' => 'Tätä tehtävää muokkasi toinen käyttäjä ennen tallennusyritystäsi. Haluatko tästä huolimatta tallentaa omat muutoksesi ja menettää hänen tekemänsä muutokset?',
+'saveanyway' => 'Tallenna muutokseni joka tapauksessa',
+'nouserselected' => 'Ei valittua käyttäjää. Valitse vähintään yksi ennen uudelleen yrittämistä.',
+'groupswitchupdated' => 'Käyttäjäryhmät muokattu onnistuneesti,',
+'takenownershipmsg' => 'Tämä tehtävä on nyt osoitettu sinulle,',
+'adminrequestmade' => 'Pyyntösi on lähetetty projektin ylläpitäjille.',
+'newdepnotify' => 'Uusi riippuvuussuhde on lisätty tehtävään:',
+'dependadded' => 'Tehtävään on lisätty riippuvuussuhde',
+'dependaddfailed' => 'Riippuvuussuhteen lisäyksessä tapahtui virhe. Tarkista, että tehtävä on olemassa eikä ole jo olemassa keskinäistä liitosta.',
+'depremovedmsg' => 'Tehtävän riippuvuussuhde on poistettu',
+'newdepis' => 'Uusi riippuvuussuhde on',
+'magicurlsent' => 'Sähköpostiosoitteeseesi on lähetetty viesti. Se sisältää linkin sivulle, josta voit täydentää tämän tehtävän tiedot.',
+'changefspass' => 'Vaihda Flysprayn salasana',
+'magicurlmessage' => 'Ole hyvä ja käytä annettua linkkiä vaihtaaksesi Flysprayn salasanan:',
+'erroronform' => 'Tietojen tallentamisessa ilmeni ongelma',
+'addressused' => 'Tätä osoitetta on käytetty Flyspray tilin rekisteröitymiseen. Jos sinä et ollut odottamassa mitään viestiä meiltä, niin elä välitä ja tuhoa viesti samantien. Mene tähän osoitteeseen jotta saat viimeisteltyä rekisteröitymisesi:',
+'confirmcodeis' => 'Vahvistuskoodisi on:',
+'codesent' => 'Vahvistuskoodisi on lähetetty. Ole hyvä ja seuraa viestissä olevia ohjeita,',
+'codenotsent' => 'Vahvistuskoodiasi ei voida lähettää, ole hyvä ja yritä myöhemmin uudelleen.',
+'taskmadeprivatemsg' => 'Tämä tehtävä on muutettu yksityiseksi',
+'taskmadepublicmsg' => 'Tämä tehtävä on muutettu taas julkiseksi',
+'realandnotify' => 'Syötä koko nimesi sekä joko sähköpostiosoitteesi tai Jabber ID.',
+'pmreqdeniedmsg' => 'Pyyntö projektin ylläpitäjille evätty',
+'massopsuccess' => 'Massaoperaatiot suoritettiin onnistuneesti - missä käyttöoikeudet riittivät',
+'usernotexist' => 'Käyttäjää ei olemassa',
+'commentattachperms' => 'Et voi poistaa kommenttia - oikeutesi eivät salli liitteiden poistoa.',
+'voterecorded' => 'Äänesi on rekisteröity',
+'votefailed' => 'Ääntäsi ei voitu rekisteröidä juuri nyt.',
+'createnewgroup' => 'Uusi ryhmä',
+'requiredfields' => 'Pakolliset kentät ovat merkitty',
+'addthisgroup' => 'Lisää tämä ryhmä',
+'createnewproject' => 'Uusi projekti',
+'addnewproject' => 'Lisää projekti',
+'htmlallowed' => 'HTML-koodi sallittu',
+'createthisproject' => 'Luo tämä projekti',
+'inlineimages' => 'Näytä kuvaliitteet tekstin joukossa',
+'createnewtask' => 'Luo uusi tehtävä tähän projektiin:',
+'addanother' => 'Lisää uusi tehtävä tämän jälkeen',
+'addthistask' => 'Lisää tämä tehtävä',
+'notifyme' => 'Ilmoita tehtävässä tapahtuvista muutoksista',
+'newtask' => 'Uusi tehtävä',
+'attachafile' => 'Liitä tiedosto',
+'registernewuser' => 'Rekisteröi uusi käyttäjä',
+'none' => 'Ei mikään',
+'registeraccount' => 'Rekisteröi tämä käyttäjätili',
+'registerbulkaccount' => 'Rekisteröi käyttäjätilit',
+'both' => 'Molemmat',
+'notifyfrom' => 'Ilmoitus ',
+'donotreply' => 'TÄMÄ ON AUTOMAATTINEN VIESTI, ÄLÄ VASTAA SIIHEN.',
+'disclaimer' => 'Sait tämän viestin koska sinut on merkitty vastaanottajaksi Flyspray vianseurantajärjestelmässä. Mikäli et osannut odottaa tätä viestiä tai et halua vastaanottaa sähköpostiviestejä jatkossa, niin voit muuttaa ilmoitusasetuksiasi yläpuolella mainitussa URL-osoitteessa.',
+'userwho' => 'Tekijä',
+'moreinfo' => 'Lisätietoja löydät seuraavasta osoitteesta:',
+'newtaskopened' => 'Flysprayssa on avattu uusi tehtävä. Yksityiskohdat alempana.',
+'notify.taskclosed' => 'Suljettu tehtävä:',
+'notify.taskreopened' => 'Uudelleen avattu tehtävä:',
+'newdep' => 'Uusi riippuvuussuhde on liitetty tehtävään:',
+'notify.depremoved' => 'Riippuvuussuhde on poistettu tehtävästä:',
+'olddepwas' => 'Vanha riippuvuussuhde oli',
+'notify.commentadded' => 'Uusi kommentti on lisätty tehtävään:',
+'commentis' => 'Kommentin teksti on alla',
+'newattachment' => 'Uusi tiedosto on liitetty tehtävään:',
+'detailsbelow' => 'Yksityiskohdat ovat alla.',
+'notify.relatedadded' => 'Uusi liittyvä tehtävä on lisätty tehtävään:',
+'relatedis' => 'Liittyvä tehtävä on',
+'assignedtoyou' => 'Sinulle on osoitettu seuraava tehtävä.',
+'takenownership' => 'on ottanut haltuunsa seuraavan tehtävän:',
+'requiresaction' => 'Seuraava tehtävä vaatii projektin ylläpitäjän toimenpidettä:',
+'requiresactionnotify' => 'Tehtävä vaatii projektin ylläpitäjän toimenpidettä',
+'pmdeny' => 'Projektin ylläpitäjä on evännyt pyynnön seuraavaan tehtävään:',
+'pmdenynotify' => 'Projektin ylläpitäjä on evännyt pyynnön',
+'fileaddedtoo' => 'Yksi tai useampia tiedostoja on liitetty',
+'taskwatching' => 'Seuraavalla tarkkailemallasi tehtävällä',
+'isdepfor' => 'on uusi riippuvuussuhde',
+'denialreason' => 'Eväämisen syy',
+'taskchanged' => 'Seuraavaa tehtävää on muutettu. Muutokset on listattu alla. Tarkemmat tiedot muutoksista saat menemällä seuraavaan osoitteeseen ja siellä historiavälilehteä klikkaamalla.',
+'useraddedtoassignees' => 'Käyttäjä on lisännyt nämä tehtävälle osoitettujen tekijöiden listaan.',
+'removeddepis' => 'Poistettu riippuvuussuhde on',
+'isnodepfor' => 'ei ole enää riippuvuussuhteessa tehtävään',
+'usergroups' => 'Käyttäjäryhmät',
+'pmtoolbox' => 'Projektin ylläpitäjän työkalut',
+'groupmanage' => 'Ryhmien hallinta',
+'pendingrequests' => 'Odottavat pyynnöt',
+'reasongiven' => 'Annettu syy',
+'nopendingreq' => 'Projektin ylläpitäjille ei ole odottavia pyyntöjä.',
+'givereason' => 'Anna syy',
+'catlisted' => 'Luokituseditori',
+'oslisted' => 'Käyttöjärjestelmäeditori',
+'verlisted' => 'Versioeditori',
+'tasktypeed' => 'Tehtävätyyppieditori',
+'resed' => 'Päätöseditori',
+'deny' => 'Evää',
+'notifiedwhen' => 'Ilmoita, kun',
+'onlynewtasks' => 'Uusia tehtäviä avataan',
+'allevents' => 'Kaikki tapahtumat tehtävissä',
+'feeds' => 'Syötteet',
+'feeddescription' => 'Syötteen kuvaus',
+'feedimgurl' => 'Syötteen kuvatiedoston URL (jätä tyhjäksi tarkoittaa ei kuvaa)',
+'notifysubject' => 'Ilmoitusten otsikko',
+'notifysubjectinfo' => '(%p = projektin nimi, %s = tehtävän yhteenveto, %t = tehtävän id, %a = toimenpide, %u = käyttäjä)',
+'priority6' => 'Erittäin korkea',
+'priority5' => 'Korkea',
+'priority4' => 'Keskinkertainen',
+'priority3' => 'Matala',
+'priority2' => 'Erittäin alhainen',
+'priority1' => 'Lykätty',
+'sendcode' => 'Lähetä vahvistuskoodi!',
+'entercode' => 'Syötä saamassasi viestissä ollut vahvistuskoodi. Syötä myös haluamasi salasana.',
+'confirmationcode' => 'Vahvistuskoodi',
+'registererror' => 'Varmista, että täytit kaikki tarvittavat tiedot sekä oikeat tiedot haluamaasi ilmoituskanavaan.',
+'validusername' => 'sallitut merkit ovat: alfanumeeriset merkit sekä -, _ ja .',
+'validemail' => 'Erota useat sähköpostiosoitteet toisistaan käyttämällä ;.',
+'emailtakenbulk' => 'Sähköpostiosoite on jo varattu.',
+'emailtaken' => 'Antamasi sähköpostiosoite tai Jabber-ID on jo käytössä. Ole hyvä, ja valitse toinen.',
+'note' => '<strong>Huom!</strong> Sinulle lähetetään vahvistuskoodi haluamasi kanavan kts. yllä kautta ennen tilisi luontia.<br />Jos lähetät vääriä tietoja, <strong>et saa koodiasi</strong>.',
+'changelog' => 'Muutosloki',
+'changeloggen' => 'Muutoslokin luoja',
+'listfrom' => 'Näytä muutoslokia lähtien',
+'to' => 'päättyen',
+'oldestfirst' => 'Vanhin ensin',
+'recentfirst' => 'Tuorein ensin',
+'severityrep' => 'Vakavuusraportti',
+'totalopen' => 'Avoimia tehtäviä yhteensä',
+'age' => 'Ikä',
+'agerep' => 'Ikäraportti',
+'eventsrep' => 'Tapahtumaraportti',
+'events' => 'Tapahtumat',
+'Tasks' => 'Tehtävät',
+'opened' => 'Avattu',
+'edited' => 'Muokattu',
+'assigned' => 'Osoitettu',
+'within' => 'Arvojen välillä',
+'pastday' => 'Viimeisin vuorokausi',
+'pastweek' => 'Viimeisin viikko',
+'pastmonth' => 'Viimeisin kuukausi',
+'pastyear' => 'Viimeisin vuosi',
+'nolimit' => 'Ei rajaa',
+'from' => 'Alkaen',
+'duein' => 'Valmistuu versioon',
+'selectfromdate' => 'Valitse alkupäivä',
+'selecttodate' => 'Valitse loppupäivä',
+'showvoters' => 'Näytä/piilota äänestäjät',
+'roadmap' => 'Etenemissuunnitelma',
+'roadmapfor' => 'Etenemissuunnitelma versiolle',
+'tasks' => 'tehtävät',
+'completed' => 'suoritettu.',
+'opentasks' => 'avointa tehtävää',
+'of' => '%',
+'severity5' => 'Kriittinen',
+'severity4' => 'Korkea',
+'severity3' => 'Keskinkertainen',
+'severity2' => 'Matala',
+'severity1' => 'Erittäin alhainen',
+'Redirect' => 'Uudelleenohjaa',
+'redirectmsg' => 'Jos selaimesi ei tue automaattista uudelleenohjausta klikkaa %sHERE%s jatkaaksesi',
+'allowclosedcomments' => 'Salli kommentit suljettuihin tehtäviin',
+'comment' => 'Kommentti',
+'editowncomments' => 'Muokkaa omia kommentteja',
+'reopened' => 'Avattu uudelleen',
+'loading' => 'Ladataan...',
+'notifyown' => 'Ilmoita omista muutoksista',
+'youremail' => 'Sähköpostiosoitteesi',
+'thankyouforbug' => 'Kiitos raportoimastasi ongelmasta. Näet tehtävän ja voit seurata sen etenemistä koska tahansa seuraavasta osoitteesta:',
+'anonuser' => 'Tuntematon käyttäjä',
+'conflict' => 'Konflikti',
+'file' => 'Tiedosto',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Koko',
+'projectgroup' => 'Projektiryhmä',
+'profile' => 'Profiili:',
+'viewprofile' => 'Näytä profiili',
+'regdate' => 'Rekisteröitynyt alkaen',
+'tasksopened' => 'Avattuja tehtäviä',
+'replyto' => 'Vastaa',
+'notifytypes' => 'Ilmoitustyypit',
+'pm.taskchanged' => 'Tehtävä muutettu',
+'pm.taskreopened' => 'Tehtävä avattu uudelleen',
+'pm.depadded' => 'Riippuvuussuhde lisätty',
+'pm.depremoved' => 'Riippuvuussuhde poistettu',
+'pmrequest' => 'Pyyntö projektin ylläpitäjille',
+'pmrequestdenied' => 'Pyyntö projektin ylläpitäjille evätty',
+'newassignee' => 'Uusi tekijä',
+'revdepadded' => 'Käänteinen riippuvuus lisätty',
+'revdepaddedremoved' => 'Käänteinen riippuvuus poistettu',
+'assigneeadded' => 'Tekijä lisätty',
+'addusergroup' => 'Lisää käyttäjä tähän ryhmään',
+'groupmembers' => 'Ryhmän jäsenet',
+'deleteuser' => 'Poista tämä käyttäjä',
+'userdeleted' => 'Käyttäjä poistettu',
+'autoassign' => 'Osoita tehtävä automaattisesti luokituksen omistajalle',
+'ssl' => 'SSL',
+'updatewrong' => 'Olet aktivoinut päivitysten tarkistuksen, mutta otettaessa yhteyttä päivityspalvelimeen tapahtui virhe. Joko palvelimesi ei salli ulossuuntautuvia yhteyksiä tai virheen syynä oli verkko-ongelma. Käy Flysprayn verkkosivuilla tarkistamassa että käytössäsi on viimeisin versio.',
+'deleteproject' => 'Poista tämä projekti ja siirrä sen sisältö',
+'projectdeleted' => 'Projekti poistettu onnistuneesti',
+'feedforall' => 'Kaikkien projektien syöte',
+'usercreated' => 'Käyttäjä luotu',
+'created' => 'Luotu',
+'deleted' => 'Poistettu',
+'userid' => 'Käyttäjätunnus',
+'editassignments' => 'Salli tehtävien osoitus',
+'preview' => 'Esikatselu',
+'anyprogress' => 'Kaikki',
+'tasksrelated' => 'Tähän tehtävään liittyvät tehtävät',
+'duplicatetasks' => 'Tämän tehtävän toisinto',
+'databasemodfailed' => 'Tietokannan muokkaus epäonnistui. Mahdollinen syy voi olla riittämättömät oikeudet.',
+'frequency' => 'Tiheys',
+'newuserregistered' => 'Uusi käyttäjä on rekisteröitynyt Flysprayhin. Hänen tietonsa ovat seuraavat:',
+'newuserregisterednotify' => 'Uusi käyttäjä on rekisteröitynyt',
+'notify_registration' => 'Ilmoita ylläpitäjille uuden käyttäjän rekisteröinnistä',
+'textversion' => 'Tekstiversio',
+'onlyprimary' => 'Muita tehtäviä sulkemasta estämättömät tehtävät',
+'onlyblocker' => 'Tehtävät jotka estävät muiden sulkemisen',
+'blockerornoblocker' => 'Molempien hakuvaihtoehtojen valitseminen yhtäaikaa ei ole kovin mielekästä.',
+'switch' => 'Vaihda',
+'max' => 'en.',
+'dates' => 'Päivät',
+'selectduedatefrom' => 'Valmistumisajasta',
+'selectduedateto' => 'Päättyen',
+'selectsincedatefrom' => 'Muutettu alkaen',
+'selectsincedateto' => 'Päättyen',
+'selectdate' => 'Valitse päivä',
+'selectopenedfrom' => 'Avattu alkaen',
+'selectopenedto' => 'Päättyen',
+'selectclosedfrom' => 'Suljettu alkaen',
+'selectclosedto' => 'Päättyen',
+'startat' => 'Alkaen',
+'hasattachment' => 'Sisältää liitetiedostoja',
+'private' => 'Yksityinen',
+'watching' => 'Tarkkailussa',
+'alreadyvotedthistask' => 'olet äänestänyt tätä tehtävää',
+'alreadyvotedthisday' => 'olet jo äänestänyt tänään',
+'visibility' => 'Näkyvyys',
+'public' => 'Julkinen',
+'leaveemptyauto' => 'Jätä salasanakentät tyhjiksi, jos haluat automaattisesti luodun salasanan.',
+'novalidemail' => 'Et antanut oikeaa sähköpostiosoitetta.',
+'novalidjabber' => 'Et antanut oikeaa Jabber-osoitetta',
+'missingrequired' => 'Et täyttänyt kaikkia vaadittuja kenttiä.',
+'entervalidusername' => 'Ole hyvä, ja anna pätevä käyttäjätunnus sekä oikea nimi.',
+'couldnotaddusernotif' => 'Käyttäjää ei voitu lisätä ilmoituslistaan.',
+'defaulttask' => 'Oletustehtäväkuvaus',
+'all' => 'kaikki',
+'events.useraddedtoassignees'=> 'Käyttäjä lisätty tekijöihin',
+'eventlog' => 'Tapahtumalogi',
+'assignmentchanged' => 'Osoitus muuttunut',
+'detailedinfo' => 'Yksityiskohtaiset tiedot',
+'All' => 'Kaikki',
+'tasksireported' => 'Raportoimani tehtävät',
+'recentlyopened' => 'Viime aikoina avatut',
+'stats' => 'Tilastot',
+'totaltasks' => 'tehtävää yhteensä',
+'mostwanted' => 'Halutuimmat tehtävät',
+'defaultentry' => 'Oletusalkusivu',
+'toplevel' => 'Päänäkymä',
+'overview' => 'Yleiskatsaus',
+'error#' => 'Virhe #',
+'error1' => 'Käyttöoikeutesi eivät riitä tämän liitteen avaamiseen.',
+'error3' => 'Toiminto toistettu, ohjataan pääsivulle.',
+'error4' => 'Sinulla ei ole ylläpitäjän oikeuksia.',
+'error5' => 'Tuntematon käyttäjä.',
+'error6' => 'Virheellinen ylläpitäjän alue.',
+'error7' => 'Kirjautuminen epäonnistui, virheellinen salasana!',
+'error71' => 'Käyttäjätili on lukittu %d minuutiksi liian monen epäonnistuneen kirjautumisyrityksen vuoksi!',
+'error8' => 'Et antanut sekä käyttäjätunnusta että salasanaa.',
+'error9' => 'Tehtävää ei ole tai käyttäjäoikeutesi eivät riitä sen katseluun.',
+'error10' => 'Tätä tehtävää ei ole olemassa.',
+'error101' => 'Oikeutesi eivät riitä tämän tehtävän katseluun.',
+'error102' => 'Oikeutesi eivät riitä tämän tehtävän katseluun, sisäänkirjautuminen saattaa auttaa.',
+'error11' => 'Ei oikeuksia muokata tätä kommenttia,',
+'error13' => 'Tuntemattomilla käyttäjillä ei ole profiilia.',
+'error14' => 'Sinulla ei ole riittäviä oikeuksia luoda uutta ryhmää.',
+'error15' => 'Sinulla ei ole riittäviä oikeuksia avata uutta tehtävää.',
+'error16' => 'Et ole projektin ylläpitäjä.',
+'error17' => 'Virheellinen projektin ylläpidon alue.',
+'error18' => 'Kirjautumiseen tarvittava URL-osoite puutteellinen.',
+'error19' => 'Tuntematon käyttäjä',
+'error20' => 'Virheellinen tietokantamuokkaus.',
+'error21' => 'Yhtä tai useampaa sähköpostia ei voitu lähettää. Tarkista asetukset.',
+'error22' => 'Uusien käyttäjien rekisteröinti ei ole sallittu.',
+'error23' => 'Käyttäjän tai ryhmän kirjautuminen ei ole sallittu.',
+'error25' => 'Etenemissuunnitelma on saatavilla vain tietyissä projekteissa.',
+'error26' => 'oauth tarjoaja ei ole tuettu',
+'error28' => 'Oikeutesi eivät riitä pääsyyn tälle alueelle.',
+'done' => 'suoritettu',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projektia ei voitu poistaa.',
+'GMT' => 'GMT',
+'timezone' => 'Aikavyöhyke',
+'accept' => 'Hyväksy',
+'reasonfordeinal' => 'Eväämisen syy',
+'pruneclosedlinks' => 'Karsi suljetut linkit',
+'pruneclosedtasks' => 'Karsi suljetut tehtävät',
+'pagegenerated' => 'Sivu ja kuva luotu %d sekunnissa.',
+'pruninglevel' => 'Karsintataso',
+'lastuser' => 'Viimeistä käyttäjää ei voi poistaa.',
+'allprivate' => 'Kaikki projektit ovat yksityisiä.',
+'deletegroup' => 'Poista ryhmä ja siirrä käyttäjät',
+'parent' => 'Emo',
+'ordertip' => 'Näytä listassa tässä järjestyksessä',
+'showtip' => 'Lisää listaan',
+'deletetip' => 'Poista listasta',
+'del' => 'del',
+'request1' => 'Tehtävän sulkemista pyydetty.',
+'request2' => 'Tehtävän uudelleen avaamista pyydetty.',
+'allpriorities' => 'Kaikki',
+'noroadmap' => 'Etenemissuunnitelmaa ei ole saatavilla (projektilla ei ole yhtäkään tulevaisuuteen määriteltyä versiota).',
+'expand' => 'Laajenna',
+'collapse' => 'Supista',
+'expandall' => 'Laajenna kaikki',
+'collapseall' => 'Supista kaikki',
+'minpwsize' => 'Salasanan vähimmäispituus on 5 merkkiä.',
+'passwordtoosmall' => 'Salasana on liian lyhyt.',
+'accountwaslocked' => 'Käyttäjätilisi on lukittu liian monen epäonnistuneen kirjautumisyrityksen vuoksi.',
+'failedattempts' => '% epäonnistunutta kirjautumisyritystä.',
+'groupnotexist' => 'Valittu ryhmä ei kuulu projektiin.',
+'searchindetails' => 'Hae kuvauksesta',
+'showasassignees' => 'Näytä tekijänä',
+'find' => 'Hae',
+'tls' => 'TLS',
+'isadmin' => 'On ylläpitäjä',
+'addvotes' => 'Anna ääniä',
+'removevote' => 'Poista ääni',
+'voteremoved' => 'Äänesi on poistettu',
+'voteremovefailed' => 'Ääntäsi ei voitu poistaa juuri nyt.',
+'novotes' => 'Sinulla ei ole tällä hetkellä yhtään annettua ääntä missään tehtävässä.',
+'connectedtasks' => 'Riippuvuussuhteessa olevat tehtävät:',
+'taskdependencies' => 'Riippuvuussuhteet',
+'viewgraph' => 'Näytä graafi',
+'notaskdependencies' => 'Tämä tehtävä on riippumaton muista tehtävistä.',
+'dependson' => 'Riippuu',
+'blocks' => 'Estää sulkemasta',
+'newdependency' => 'Uusi riippuvuussuhde:',
+'nouserstoadd' => 'Ei lisättäviä käyttäjiä. Ole hyvä, ja varmista, että kaikille käyttäjille on määritetty nimi, käyttäjänimi sekä sähköpostiosoite.',
+'dispintro' => 'Näytä tervetuloviesti',
+'mainmessage' => 'Tervetuloviesti',
+'setsupertask' => 'Aseta emotehtävän tunnus:',
+'supertaskmodified' => 'Emotehtävän tunnusta muokattu',
+'set' => 'Aseta',
+'supertask' => 'Emotehtävä',
+'setparent' => 'Aseta emotehtävän tunnus',
+'selfsupertasknotallowed' => 'Tehtävää ei voi asettaa itsensä emotehtäväksi.',
+'quickaction' => 'Pikatoiminnat',
+'updateselectedtasks' => 'Päivitä valitut tehtävät',
+'notspecified' => 'Ei määritelty',
+'editselectedtasks' => 'Muokkaa valittuja tehtäviä',
+'information' => 'Lisätietoja',
+'taskclosedisabled' => 'Tehtävän sulkeminen ei ole mahdollista seuraavien siitä riippuvien tehtävien ollessa avoimia:-',
+'daysleft' => 'päivää jäljellä',
+'dayoverdue' => 'päivää yli',
+'duetoday' => 'Valmistuu tänään.',
+'daysbeforealert' => 'Päivää ennen varoitusta',
+'associatedsubtask' => 'Liitetty onnistuneesti alitehtävä FS#',
+'associatesubtask' => 'Liitä alitehtävä',
+'subtaskid' => 'Alitehtävän ID',
+'subtaskalreadyhasparent' => 'Lisäämälläsi alitehtävällä on jo emotehtävä, poista se ensin.',
+'subtaskisparent' => 'Lisäämäsi alitehtävä on jo tämän tehtävän emotehtävä. Alitehtävää ei liitetty.',
+'subtasknotexist' => 'Annettu alitehtävä ei ole olemassa,',
+'subtaskremovedmsg' => 'Alitehtävä poistettu onnistuneesti.',
+'subtaskadded' => 'Alitehtävä lisätty',
+'subtaskremoved' => 'Alitehtävä poistettu',
+'addnewsubtask' => 'Lisää uusi alitehtävä',
+'hidesubtasks' => 'Piilota alitehtävät',
+'voteforthistask' => 'Äänestä tehtävää',
+'watchthistask' => 'Tarkkaile tehtävää',
+'privatethistask' => 'Muuta tehtävä yksityiseksi',
+'adddependenttask' => 'Lisää riippuvuus',
+'associatetaskid' => 'Alitehtävän tunnus',
+'parenttaskid' => 'Emotehtävän tunnus',
+'invalidsupertaskid' => 'Emotehtävän tunnus puutteellinen',
+'supertaskadded' => 'Emotehtävä lisätty',
+'supertaskremoved' => 'Emotehtävä poistettu',
+'effort' => 'Työaika',
+'efforttracking' => 'Työajanseuranta',
+'useeffort' => 'Projekti käyttää työajanseurantaa',
+'estimatedeffort' => 'Arvioitu työaika',
+'totalestimatedeffort' => 'Arvioitu työaika yhteensä',
+'currenteffortdone' => 'Toteutunut työaika tähän mennessä',
+'starteffort' => 'Aloita työajanseuranta',
+'endeffort' => 'Päätä työajanseuranta',
+'cleareffort' => 'Tyhjennä työajanseuranta',
+'addeffort' => 'Lisää työaika',
+'manualeffort' => 'Lisää käsin työaika (TT:MM)',
+'efforttrackingstarted' => 'Tehtävän työajanseuranta on käynnistetty.',
+'efforttrackingnotstarted'=> 'Työajanseuranta on jo käynnissä. Päätä edellinen ennen uuden aloittamista.',
+'efforttrackingstopped' => 'Tehtävän työajanseuranta on pysäytetetty.',
+'efforttrackingcancelled' => 'Tehtävän työajanseuranta on peruutettu.',
+'efforttrackingadded' => 'Manuaalinen työaika taltioitu tehtävälle,',
+'trackinginprogress' => 'Työajan seuranta käynnissä',
+'viewestimatedeffort' => 'Salli arvioidun työajan näkeminen',
+'viewcurrenteffortdone' => 'Salli toteutuneen työajan näkeminen',
+'trackeffort' => 'Salli työajanseuranta',
+'invalideffort' => 'Syöttämäsi työaika ei ollut tunnistettavassa muodossa.',
+'showpass' => 'Näytä salasana',
+'chooseafile' => 'Ole hyvä ja valitse tiedosto!',
+'incorrectfiletype' => 'Virheellinen tiedostotyyppi. Sallitut: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Ei salasanaa jota pyytää. Olet rekisteröitynyt käyttämällä %s',
+'addmultipletasks' => 'Lisää useita tehtäviä',
+'pendingnewuserrequest' => 'Liittymispyynnöt',
+'adminrequestswaiting' => 'pyyntöä ylläpitäjille',
+'clicktoedit' => 'Pikamuokkaa kenttiä klikkaamalla',
+'confirmedit' => 'vahvista',
+'regapprovedbyadmin' => 'Ylläpitäjät hyväksyvät rekisteröinnit (vahvistuskoodi ei ole käytössä)',
+'activity' => 'Aktiviteetti',
+'myactivity' => 'Oma aktiviteetti',
+'emailverificationwrong' => 'Sähköpostivahvistus ei täsmää annetun osoitteen kanssa',
+'verifyemailaddress' => 'Vahvista sähköpostiosoite',
+'hideemails' => 'Piilota käyttäjien sähköpostiosoitteet',
+'hidemyemail' => 'Piilota sähköpostiosoitteeni',
+'exporttasklist' => 'Vie tehtävälista',
+'onedecimal' => 'yksi desimaali',
+'manday' => 'työpäivä',
+'mandays' => 'työpäivää',
+'mandayabbrev' => 'tp',
+'hourspermanday' => 'Tuntia työpäivässä (TT:MM)',
+'itemexists' => '% on jo tietokannassa',
+'categoryitemexists' => '% on jo tietokannassa luokituksen % alla.',
+'pageswelcomemsg' => 'Tervetuloviesti näytetään sivuilla',
+'pagesintromsg' => 'Esittelyviesti näytetään sivuilla',
+'activeoauths' => 'Aktiiviset Oauth-tarjoajat',
+'onlyoauthreg' => 'Salli vain Oauth-rekisteröinnit',
+'estimatedeffortformat' => 'Arvioidun työmäärän esitystapa',
+'currenteffortdoneformat' => 'Toteutuneen työmäärän esitystapa',
+'minute' => 'minuutti',
+'minutes' => 'minuuttia',
+'minuteplural' => 'minuuttia',
+'minutesingular' => 'minuutti',
+'minuteabbrev' => 'm.',
+'hourplural' => 'tuntia',
+'hoursingular' => 'tunti',
+'hourabbrev' => 't.',
+'estimatedeffortopen' => 'Arvioitu työmäärä avoimille tehtäville',
+'currenteffortdoneopen' => 'Toteutunut työmäärä avoimille tehtäville',
+'signinwith' => 'Kirjaudu käyttämällä %s',
+'canviewroadmap' => 'Salli etenemissuunnitelman näyttäminen',
+'enableavatars' => 'Salli avatarien käyttö',
+'maxavatarsize' => 'Avatarin maksimikoko pikseleinä',
+'taskhassubtask' => 'Tehtävä sisältää seuraavan alitehtävän',
+'taskhassubtasks' => 'Tehtävä sisältää seuraavat alitehtävät',
+'translations' => 'Käännökset',
+'translate' => 'Käännä',
+'taskdescription' => 'Tehtävän kuvaus',
+'notaskdescription' => 'Ei tehtävän kuvausta',
+'pleaseselect' => 'Ole hyvä ja valitse',
+'closeselectedtasks' => 'Sulje valitut tehtävät',
+'closetasks' => 'sulje tehtävät',
+'hintforbulkimport' => "<b>Vihjeitä tietojen tuontiin:</b>\n<ol>\n<li>Kopioi ja liitä Excel-taulukosta tai csv-tiedostosta liittämällä kokonainen solu.</li>\n<li>Vain nimi- ja sisältötietojen liittäminen on mahdollista toistaiseksi.</li>\n<li>Tehtävien osoituksessa näytetään vihje, jos nimi täsmää. Muutoin näytetään tuntematon tekijä.</li>\n</ol>",
+'taskissubtaskof' => 'Tämä tehtävä on seuraavan tehtävän alitehtävä',
+'applyfirstline' => 'Hyväksy rivi',
+'addmorerows' => 'Lisää rivejä',
+'addtasks' => 'Lisää tehtäviä',
+'massopsdisabled' => 'Valitamme, massaoperaatiot eivät toistaiseksi ole käytettävissä. Toteutuksen viimeistely on suunnitelmissa jossakin Flysprayn myöhemmässä versiossa. Voit ottaa sen käyttöön omalla vastuulla muokkaamalla lähdekoodia, mutta lue kommentit ennen kuin teet sen.',
+'viewroadmap' => 'Salli etenemissuunnitelman näyttäminen',
+'nosuicide' => 'Rakas käyttäjä, ohjelmointini ei salli sinun tuhota kirjautumisoikeuksiasi Flysprayhin poistamalla käytöstä oman käyttäjätilisi tai vaihtamalla omaa ryhmääsi. Ystävällisin terveisin, HAL9000:n velipoika.',
+'movingtodifferentproject'=> 'Riippuvuussuhteessa olevien tehtävien siirto toiseen projektiin ei ole sallittu. Riippuvuussuhde tulee purkaa ensin.',
+'musthavesameproject' => 'Riippuvuussuhteessa olevien tehtävien tulee kuulua samaan projektiin.',
+'defaultorderby' => 'Tehtävälistan oletusjärjestys',
+'defaultorderby2' => 'sitten',
+'viewowntasks' => 'Näytä omat tehtäväni',
+'viewgroupstasks' => 'Ryhmän tehtävät',
+'urlrewriting' => 'Käytä muokattuja URL-osoitteita.',
+'nomodrewrite' => 'Mod_rewrite ei ole käytettävissä tällä palvelimella, valitan mutta muokattuja urleja ei voida ottaa käyttöön ',
+'on' => 'Päällä',
+'off' => 'Pois',
+'defaultorderbydirection' => 'Oletuslajittelu',
+'ascending' => 'Nouseva',
+'descending' => 'Laskeva',
+'myassignedtasks' => 'Tehtäväni',
+'commentedon' => 'kommentoi',
+'maxvoteperday' => 'Päiväkohtainen äänikiintiö',
+'votesperproject' => 'Käyttäjän projektikohtainen äänikiintiö',
+'votelimitreached' => 'Olet antanut projektikohtaisen raja-arvosi mukaisen määrän ääniä. Tarkista profiilisivultasi äänestystilanteesi. Voit tarvittaessa poistaa antamiasi ääniä ja siten vapauttaa niitä parempaan käyttöön. Näin voimme parhaiten seurata tehtäviä, jotka ovat sinulle tärkeimpiä. Suljetut tehtävät palauttavat äänikiintiötäsi.',
+'myvotes' => 'Äänestykseni',
+'tag' => 'Tägi',
+'tags' => 'Tägit',
+'tagsinfo' => 'Tehtävien vapaa tägitys Flysprayssa. Erota tägit \';\':llä (puolipiste) toisistaan. Niitä ei toistaiseksi käytetä hakuihin, lajitteluun tai suodatukseen.',
+'novalues' => 'Ei arvoja',
+'youhaveregistered' => 'Olet rekisteröitynyt käyttäjäksi Flysprayhin. Antamasi tiedot:',
+'youhaveregisterednotify' => 'Ylläpitäjät hyväksyivät sinun Flyspray rekisteröitymisen.',
+'usedintasks' => 'Käytetty tehtävissä',
+'freetagging' => 'Salli käyttäjien määrittelemät tagit',
+'keyboardshortcuts' => 'Näppäimistökomennot',
+'testmailsettings' => 'Kokeile aktiivisia sähköpostiasetuksia',
+'test' => 'Kokeile',
+'testmailsettingsnotice' => 'Ja varmista myös että olet vastaanottanut emailin käyttämääsi postilaatikkoon (tarkista sivulta \'myprofile\')',
+'invalidinput' => 'Tehtävällä on joitakin epäyhteensopivia ominaisuuksia jotka tulee ratkaista ennen sen siirtämistä toiseen projektiin.',
+'invalidstatus' => 'Valitse sopiva tehtävän tila siirtäessäsi tehtävää toiseen projektiin.',
+'invalidcategory' => 'Valitse sopiva luokitus siirtäessäsi tehtävää toiseen projektiin.',
+'invalidreportedversion' => 'Valitse sopiva raportointiversio siirtäessäsi tehtävää toiseen projektiin.',
+'invaliddueversion' => 'Valitse sopiva tavoiteversio siirtäessäsi tehtävää toiseen projektiin.',
+'invalidos' => 'Valitse sopiva käyttöjärjestelmä siirtäessäsi tehtävää toiseen projektiin.',
+'invalidtags' => 'Valitse vain sallittuja tägejä siirtäessäsi tehtävää toiseen projektiin.',
+'invalidassignees' => 'Valitse vain sallittuja suorittajia siirtäessäsi tehtävää toiseen projektiin.',
+'customize' => 'Kustomoi',
+'hidesubs' => 'Piilota alitehtävät',
+'hideprivate' => 'Piilota yksityiset tehtävät',
+'hideclosed' => 'Piilota suljetut tehtävät',
+'currentproject' => 'Nykyinen projekti',
+'targetproject' => 'Kohdeprojekti',
+'availablekeybshortcuts' => 'Käytettävissä olevat näppäinkomennot',
+'logindialoglogout' => 'Sisään-/uloskirjautuminen',
+'focustaskidsearch' => 'Siirry hakuun tehtävän id:llä',
+'openselectedtask' => 'Avaa valittu tehtävä',
+'movecursorup' => 'Siirrä kursoria ylöspäin',
+'movecursordown' => 'Siirrä kursoria alaspäin',
+'taskdetails' => 'Tehtävän yksityiskohdat',
+'taskediting' => 'Tehtävän muokkaus',
+'savetask' => 'Tallenna tehtävä',
+);
+
+?>
diff --git a/lang/fr.php b/lang/fr.php
new file mode 100644
index 0000000..fcf8429
--- /dev/null
+++ b/lang/fr.php
@@ -0,0 +1,1108 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Modifier l\'utilisateur',
+'accountenabled' => 'Compte actif',
+'editallusers' => 'Voir tous les utilisateurs',
+'username' => 'Nom d\'utilisateur',
+'usersupdated' => 'Utilisateur mis à jour',
+'realname' => 'Nom réel',
+'emailaddress' => 'Adresse e-mail',
+'jabberid' => 'ID Jabber',
+'profileimage' => 'Image de profil',
+'notifytype' => 'Type de notification',
+'group' => 'Groupe',
+'enableaccounts' => 'Activer les comptes',
+'disableaccounts' => 'Désactiver les comptes',
+'deleteaccounts' => 'Supprimer les comptes',
+'updatedetails' => 'Mettre les informations à jour',
+'setglobally' => 'Ces options ont été définies globalement.',
+'usergroupmanage' => 'Gestion des utilisateurs et des groupes',
+'newuser' => 'Enregistrer un nouvel utilisateur',
+'newuserbulk' => 'Enregistrer plusieurs nouveaux utilisateurs',
+'bulkuserstoadd' => 'Liste des nouveaux utilisateurs',
+'optionsforallusers' => 'Options pour tous les nouveaux utilisateurs',
+'newgroup' => 'Créer un nouveau groupe',
+'yes' => 'Oui',
+'no' => 'Non',
+'editgroup' => 'Modifier le groupe',
+'groupname' => 'Nom du groupe',
+'description' => 'Description',
+'admin' => 'Appartient au groupe Admin',
+'opennewtasks' => 'Ouvrir de nouvelles tâches',
+'modifytasks' => 'Modifier des tâches existantes',
+'addcomments' => 'Ajouter des commentaires',
+'attachfiles' => 'Joindre des fichiers',
+'vote' => 'Vote',
+'groupenabled' => 'Les membres peuvent se connecter',
+'groupopen' => 'Les membres peuvent se connecter',
+'tasktypelist' => 'Liste des types de tâche',
+'categorylist' => 'Liste des catégories',
+'oslist' => 'Liste des systèmes d\'exploitation',
+'resolutionlist' => 'Liste des raisons de fermeture',
+'versionlist' => 'Liste des versions',
+'severitylist' => 'Liste des sévérités',
+'listnote' => 'Note : décocher la case "Afficher" peut altérer certaines tâches en cours de modification. Modifier le champ "Nom" changera toutes les tâches associées à ce nom. Certaines options ne sont pas supprimables car déjà utilisées dans des tâches, ou bien nécessaires au bon fonctionnement de l\'outil.',
+'name' => 'Nom',
+'order' => 'Ordre',
+'back' => 'Précédent',
+'text' => 'Texte',
+'highlight' => 'Mettre en évidence',
+'show' => 'Afficher',
+'owner' => 'Responsable',
+'selectowner' => 'Sélectionner un responsable',
+'update' => 'Mettre à jour',
+'addnew' => 'Ajouter',
+'flysprayprefs' => 'Préférences de Flyspray',
+'projecttitle' => 'Nom du projet',
+'baseurl' => 'URL de base pour cette installation',
+'replyaddress' => 'Adresse e-mail de réponse pour les notifications',
+'themestyle' => 'Thème / Style',
+'language' => 'Langue',
+'anonview' => 'Permettre aux utilisateurs anonymes de consulter les tâches',
+'allowanon' => 'Permettre aux utilisateurs anonymes d\'ouvrir de nouvelles tâches',
+'never' => 'Jamais',
+'anonymously' => 'Anonymement',
+'afterregister' => 'Seulement après inscription',
+'spamproof' => 'Activer le code de confirmation pour l\'inscription de nouveaux utilisateurs',
+'anongroup' => 'Groupe des nouveaux utilisateurs',
+'groupassigned' => 'Des tâches peuvent être assignées aux membres des groupes',
+'forcenotify' => 'Mode de notification des tâches',
+'neversend' => 'Aucun envoi',
+'userchoose' => 'Laisser l\'utilisateur choisir',
+'email' => 'E-mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Responsable des catégories par défaut',
+'noone' => 'Personne',
+'jabbernotify' => 'Notifications par Jabber',
+'jabberserver' => 'Serveur',
+'jabberport' => 'Port',
+'jabberuser' => 'Nom d\'utilisateur Jabber',
+'jabberpass' => 'Mot de passe Jabber',
+'saveoptions' => 'Sauvegarder les options',
+'editcomment' => 'Modifier le commentaire',
+'commentby' => 'Commentaire de',
+'saveeditedcomment' => 'Sauvegarder le commentaire modifié',
+'projectprefs' => 'Préférences du projet',
+'pagetitle' => 'Titre de la page',
+'defaultproject' => 'Projet par défaut',
+'projectlists' => 'Listes du projet',
+'showlogo' => 'Afficher le logo',
+'showgravatars' => 'Afficher les gravatars',
+'emailNoHTML' => 'Désactiver HTML dans les emails',
+'intromessage' => 'Message d\'accueil',
+'isactive' => 'Le projet est actif',
+'createproject' => 'Créer un nouveau projet',
+'nopermission' => 'Vous n\'avez pas la permission d\'utiliser cette page',
+'listordertip' => 'L\'ordre d\'apparition des articles dans la liste',
+'listshowtip' => 'Montrer cet article dans la liste',
+'categoryownertip' => 'Cette personne reçoit une notification quand une tâche de cette catégorie est ouverte',
+'categoryparenttip' => 'La catégorie parent à laquelle cette nouvelle appartient',
+'notsubcategory' => 'Aucune - Catégorie de premier niveau',
+'showinlineimages' => 'Afficher les images jointes',
+'dateformat' => 'Format de date',
+'dateformat_extended' => 'Format de date détaillé',
+'cache_feeds' => 'Conserver les flux en cache',
+'no_cache' => 'Pas de cache',
+'cache_disk' => 'Cacher sur disque',
+'cache_db' => 'Cacher en base de données',
+'subcategoryof' => 'Sous-catégorie de',
+'visiblecolumns' => 'Colonnes affichées dans la liste des tâches',
+'visiblefields' => 'Champs visibles en ouverture/modification/consultation de tâche',
+'tense' => 'Temps',
+'listtensetip' => 'Passé, Présent ou Futur',
+'past' => 'Passé',
+'present' => 'Présent',
+'future' => 'Futur',
+'oldpass' => 'Ancien mot de passe',
+'nooldpass' => 'Veuillez saisir votre ancien mot de passe',
+'oldpasswrong' => 'Votre ancien mot de passe est faux',
+'changepass' => 'Changer mot de passe',
+'confirmpass' => 'Confirmer le mot de passe',
+'projectmanager' => 'Chef de projet',
+'viewtasks' => 'Consulter les tâches',
+'modifyowntasks' => 'Modifier ses propres tâches',
+'modifyalltasks' => 'Modifier les tâches des autres',
+'viewcomments' => 'Voir les commentaires',
+'editcomments' => 'Modifier les commentaires',
+'deletecomments' => 'Supprimer les commentaires',
+'viewattachments' => 'Voir les pièces jointes',
+'createattachments' => 'Ajouter des pièces jointes',
+'deleteattachments' => 'Supprimer les pièces jointes',
+'viewhistory' => 'Consulter l\'historique',
+'closeowntasks' => 'Fermer ses propres tâches',
+'closeothertasks' => 'Fermer les tâches des autres',
+'assigntoself' => 'S\'assigner des tâches',
+'assignotherstoself' => 'S\'assigner les tâches des autres',
+'viewreports' => 'Consulter les rapports',
+'othersview' => 'Tout le monde peut voir ce projet',
+'othersviewroadmap' => 'Tout le monde peut voir la feuille de route',
+'usersandgroups' => 'Utilisateurs & Groupes',
+'globalgroup' => 'Groupe global',
+'globalgroups' => 'Groupes globaux',
+'defaultglobalgroup' => 'Groupe global par défaut pour les nouveaux utilisateurs',
+'addtogroup' => 'Ajouter au groupe',
+'moveuserstogroup' => 'Déplacer dans le groupe',
+'nogroup' => 'Pas de groupe - Supprimer du projet',
+'eventdesc' => 'Description de l\'événement',
+'requestedby' => 'Demandé par',
+'daterequested' => 'Date demandée',
+'closetask' => 'Fermer cette tâche',
+'reopentask' => 'Rouvrir cette tâche',
+'applymember' => 'Postuler pour être membre du projet',
+'forcurrentproj' => 'Pour le projet courant',
+'lostpw' => 'Récupération du mot de passe oublié',
+'lostpwexplain' => 'Entrez votre identifiant pour obtenir un lien de modification du mot de passe. Ce lien sera envoyé à l\'adresse de notification spécifiée dans votre profil.',
+'sendlink' => 'Envoyer le lien',
+'savenewpass' => 'Enregistrer mon nouveau mot de passe',
+'anonreg' => 'Autoriser l\'inscription de nouveaux utilisateurs',
+'allowanonopentask' => 'Autoriser les utilisateurs anonymes à ouvrir des tâches',
+'editglobalgroup' => 'Modifier le groupe global',
+'editgroupforproj' => 'Modifier le groupe pour le projet',
+'notshownforadmin' => 'Les droits ne sont pas affichés pour le groupe Admin car il n\'est pas nécessaire de les modifier.',
+'general' => 'Général',
+'userregistration' => 'Inscription',
+'notifications' => 'Notifications',
+'resetoptions' => 'Réinitialiser les options',
+'preferences' => 'Préférences',
+'tasktypes' => 'Types de tâche',
+'resolutions' => 'Raisons de fermeture',
+'categories' => 'Catégories',
+'operatingsystems' => 'Systèmes d\'exploitation',
+'versions' => 'Versions',
+'admintoolboxlong' => 'Boîte à outils Administrateur',
+'newproject' => 'Nouveau projet',
+'delete' => 'Supprimer',
+'link' => 'Lien',
+'referencelinks' => 'Liens de référence',
+'listdeletetip' => 'Supprimer cet élément de la liste',
+'lookandfeel' => 'Apparence',
+'globaltheme' => 'Thème général',
+'emailnotify' => 'Notifications par e-mail',
+'fromaddress' => 'Adresse de l\'expéditeur',
+'smtpserver' => 'Serveur SMTP',
+'smtpuser' => 'Identifiant SMTP',
+'smtppass' => 'Mot de passe SMTP',
+'addrewrite' => 'Utiliser la réécriture d\'adresse',
+'usereminderdaemon' => 'Activer le démon de rappels',
+'tasksperpage' => 'Nombre des tâches par page',
+'addtoassignees' => 'S\'ajouter aux assignés',
+'taskstatuses' => 'Statuts des tâches',
+'canvote' => 'Voter pour des tâches',
+'loginsuccessful' => 'Connexion réussie.',
+'youareloggedout' => 'Vous avez été déconnecté.',
+'waitwhiletransfer' => 'Veuillez patienter pendant le transfert...',
+'clicknowait' => 'Cliquez ici si vous ne souhaitez pas attendre.',
+'accountdisabled' => 'Votre compte est désactivé. Contactez un administrateur',
+'task' => 'Tâche',
+'edittask' => 'Modifier cette tâche',
+'openedby' => 'Ouverte par',
+'editedby' => 'Dernière modification par',
+'tasktype' => 'Type',
+'category' => 'Catégorie',
+'status' => 'État',
+'assignedto' => 'Assignée à',
+'operatingsystem' => 'Système d\'exploitation',
+'severity' => 'Sévérité',
+'reportedversion' => 'Basée sur la version',
+'dueinversion' => 'Due pour la version',
+'defaultdueinversion' => 'Due pour la version par défaut pour les nouvelles tâches',
+'undecided' => 'Non décidée',
+'percentcomplete' => 'Pourcentage achevé',
+'details' => 'Détails',
+'savedetails' => 'Sauvegarder les informations',
+'canceledit' => 'Annuler la modification',
+'anonymous' => 'Auteur anonyme',
+'complete' => 'terminé',
+'closedby' => 'Fermée par',
+'reasonforclosing' => 'Raison de la fermeture :',
+'reopenthistask' => 'Rouvrir cette tâche',
+'comments' => 'Commentaires',
+'attachments' => 'Fichiers joints',
+'relatedtasks' => 'Tâches liées',
+'edit' => 'Modifier',
+'addcomment' => 'Ajouter un commentaire',
+'fileuploadedby' => 'Fichier téléchargé par',
+'uploadafile' => 'Joindre un fichier',
+'addalink' => 'Ajouter un lien',
+'addanotherlink' => 'Ajouter un autre lien',
+'uploadnow' => 'Envoyer maintenant !',
+'thesearerelated' => 'Ces tâches sont liées à cette autre tâche',
+'remove' => 'Supprimer',
+'addnewrelated' => 'Lier une tâche à celle-ci',
+'add' => 'Ajouter',
+'otherrelated' => 'Autres tâches auxquelles cette tâche est liée',
+'receivenotify' => 'Ces utilisateurs reçoivent une notification détaillée quand cette tâche est modifiée.',
+'addusertolist' => 'Ajouter un utilisateur à cette liste',
+'addtolist' => 'Ajouter à la liste',
+'addmyself' => 'M\'ajouter à cette liste',
+'removemyself' => 'Me supprimer de cette liste',
+'theseusersnotify' => 'Ces utilisateurs reçoivent une notification détaillée quand cette tâche est modifiée.',
+'attachedtoproject' => 'Concerne le projet',
+'reminders' => 'Rappels',
+'system' => 'Système',
+'systemvalues' => 'Liste des valeurs globales',
+'projectvalues' => 'Liste des valeurs spécifiques au projet',
+'remindthisuser' => 'Destinataire du rappel : ',
+'thisoften' => 'Période : ',
+'startafter' => 'Avant le premier rappel, attendre',
+'hour' => 'heure',
+'hours' => 'Heure(s)',
+'day' => 'jour',
+'days' => 'Jour(s)',
+'week' => 'semaine',
+'weeks' => 'Semaine(s)',
+'addreminder' => 'Créer un rappel',
+'defaultreminder' => 'Ceci est un rappel de surveillance de tâche :',
+'message' => 'Message',
+'closed' => 'Fermée',
+'filename' => 'Nom de fichier :',
+'date' => 'Date',
+'filesize' => 'Taille du fichier :',
+'closurecomment' => 'Commentaires de fermeture :',
+'history' => 'Historique',
+'nohistory' => 'Aucun historique disponible.',
+'eventdate' => 'Date',
+'user' => 'Utilisateur',
+'event' => 'Événement',
+'fieldchanged' => 'Champ modifié',
+'taskopened' => 'Tâche ouverte',
+'taskreopened' => 'La tâche a été rouverte.',
+'taskclosed' => 'La tâche a été fermée.',
+'commentadded' => 'Commentaire ajouté',
+'commentedited' => 'Commentaire modifié',
+'commentdeleted' => 'Le commentaire a été supprimé',
+'attachmentadded' => 'Fichier joint ajouté',
+'attachmentdeleted' => 'Fichier joint supprimé',
+'taskedited' => 'Détails de la tâche modifiée',
+'notificationadded' => 'Utilisateur ajouté à la liste de notifications',
+'notificationdeleted' => 'Utilisateur supprimé de la liste de notifications',
+'relatedadded' => 'Tâche liée ajoutée à la liste',
+'relateddeleted' => 'Tâche liée supprimée',
+'taskassigned' => 'Tâche assignée à',
+'taskreassigned' => 'Tâche ré-assignée à',
+'assignmentremoved' => 'Assignation supprimée',
+'summary' => 'Résumé',
+'addedasrelated' => 'Tâche ajoutée à la liste liée de',
+'deletedasrelated' => 'Tâche supprimée de la liste liée de',
+'reminderadded' => 'Rappel ajouté',
+'reminderdeleted' => 'Rappel supprimé',
+'priority' => 'Priorité',
+'previousvalue' => 'Valeur précédente',
+'newvalue' => 'Nouvelle valeur',
+'selectareason' => 'Choisissez une raison',
+'assigntome' => 'Me l\'assigner',
+'reopenrequest' => 'Rouvrir la demande',
+'requestclose' => 'Fermer la demande',
+'ownershiptaken' => 'Un utilisateur a pris la responsabilité',
+'closerequestmade' => 'Demande de fermeture de tâche',
+'reopenrequestmade' => 'Demande de réouverture de tâche',
+'taskdependson' => 'Cette tâche dépend de',
+'taskdependsontask' => 'Cette tâche dépend de',
+'taskdependsontasks' => 'Cette tâche dépend de',
+'taskblock' => 'Cette tâche bloque la fermeture',
+'taskblocks' => 'Cette tâche bloque la fermeture de ces autres tâches',
+'depadded' => 'Dépendance ajoutée',
+'depaddedother' => 'Cette tâche a été ajoutée en tant que dépendance',
+'depremoved' => 'Dépendance supprimée',
+'depremovedother' => 'Cette tâche a été supprimée de la liste des dépendances d\'une autre tâche',
+'showdetailserror' => 'Cette tâche n\'existe pas ou vous n\'avez pas la permission de la consulter',
+'makeprivate' => 'Rendre privée',
+'makepublic' => 'Rendre publique',
+'taskmadeprivate' => 'Cette tâche a été rendue privée',
+'taskmadepublic' => 'Cette tâche a été rendue publique',
+'confirmdeletecomment' => 'Voulez-vous vraiment supprimer ce commentaire ? %s',
+'attachementswilldeleted' => 'Toutes les pièces jointes seront supprimées !',
+'confirmdeleteattach' => 'Voulez-vous vraiment supprimer cette pièce jointe ?',
+'selectedhistory' => 'Historique des tâches sélectionnées',
+'showallhistory' => 'Réafficher l\'historique complet',
+'hidethis' => 'Cacher de nouveau cette zone',
+'mark100' => 'Marquer l\'achèvement à 100%',
+'watchtask' => 'Surveiller',
+'stopwatching' => 'Ne plus surveiller',
+'commentlink' => 'Lien vers ce commentaire',
+'submitreq' => 'Soumettre la demande',
+'reasonforreq' => 'Raison de cette demande',
+'pmreqdenied' => 'Le chef de projet a refusé la demande',
+'taskpendingreq' => 'En attente de décision du gestionnaire de projet. Consulter l\'historique pour plus de détails.',
+'previoustask' => 'Tâche précédente',
+'nexttask' => 'Tâche suivante',
+'duedate' => 'Échéance',
+'attachnoperms' => 'Ce commentaire contient des pièces jointes, mais vous n\'avez pas l\'autorisation de les consulter.',
+'linknoperms' => 'Il y a des liens avec ce commentaire mais vous n\'avez pas les permissions pour les consulter',
+'open' => 'Ouvertes',
+'depgraph' => 'Consulter le graphique des dépendances',
+'reset' => 'Réinitialiser',
+'selectusers' => 'Sélectionner les utilisateurs...',
+'addmetoassignees' => 'M\'ajouter aux destinataires',
+'addedtoassignees' => 'Utilisateur ajouté aux destinataires',
+'dependencygraph' => 'Graphique des dépendances',
+'attachanotherfile' => 'Joindre un autre fichier',
+'OK' => 'OK',
+'addvote' => 'Ajouter un vote',
+'disable_lostpw' => 'Désactiver la fonction de "Mot de passe perdu"',
+'disable_changepw' => 'Désactiver créer/modifier mot de passe',
+'notifyfromfs' => 'Notification de Flyspray',
+'autogenerated' => 'CE MESSAGE A ÉTÉ GÉNÉRÉ AUTOMATIQUEMENT, NE PAS RÉPONDRE',
+'forward' => 'Suivant',
+'previous' => 'Précédente',
+'next' => 'Suivante',
+'first' => 'Premier',
+'last' => 'Dernier',
+'page' => 'Page %d sur %d',
+'search' => 'Rechercher',
+'alltasktypes' => 'Tout type de tâche',
+'allseverities' => 'Toutes les sévérités',
+'alldevelopers' => 'Tous les développeurs',
+'notyetassigned' => 'Non assignées',
+'allcategories' => 'Toutes les catégories',
+'allstatuses' => 'Tous les états',
+'allopentasks' => 'Toutes les tâches ouvertes',
+'sortthiscolumn' => 'Trier selon cette colonne',
+'id' => 'ID',
+'project' => 'Projet',
+'dateopened' => 'Ouverte',
+'progress' => 'Progression',
+'searchthisproject' => 'Rechercher dans ce projet',
+'dueanyversion' => 'N\'importe quelle version',
+'anyversion' => 'Toutes versions',
+'dueversion' => 'Due pour la version',
+'lastedit' => 'Dernière modification',
+'os' => 'Système d\'exploitation',
+'reportedin' => 'Basée sur',
+'taskrange' => 'Tâches %d - %d sur %d',
+'noresults' => 'Aucun résultat ne correspond à vos critères de recherche.',
+'takeaction' => 'Valider',
+'watchtasks' => 'Surveiller les tâches sélectionnées',
+'stopwatchingtasks' => 'Ne plus surveiller les tâches sélectionnées',
+'assigntaskstome' => 'M\'assigner les tâches sélectionnées',
+'dueby' => 'Due pour',
+'dueanytime' => 'Due pour n\'importe quand',
+'selectduedate' => 'Choisir une date d\'échéance',
+'toggleselected' => 'Inverser la sélection',
+'due' => 'Due',
+'assignedtome' => 'Assignées à moi-même',
+'tasklist' => 'Liste des tâches',
+'dateclosed' => 'Date de fermeture',
+'advanced' => 'Avancé',
+'searchcomments' => 'Rechercher dans les commentaires',
+'searchforall' => 'Rechercher tous les mots',
+'anonusers' => 'Utilisateurs anonymes',
+'miscellaneous' => 'Divers',
+'users' => 'Utilisateurs',
+'taskproperties' => 'Propriétés de la tâche',
+'selectsincedate' => 'Sélectionner modifié depuis',
+'changedsince' => 'Modifié depuis',
+'updatefs' => 'Veuillez mettre à jour Flyspray',
+'currentversion' => 'Votre version actuelle est',
+'latestversion' => 'et la version la plus récente est',
+'hidemessage' => '(me rappeler plus tard)',
+'saveas' => 'Enregistrer la recherche sous',
+'nosearches' => 'Aucune recherche enregistrée',
+'saving' => 'Enregistrement...',
+'votes' => 'Votes',
+'tovote' => 'Voter',
+'allclosedtasks' => 'Toutes les tâches fermées',
+'password' => 'Mot de passe',
+'login' => 'Connexion',
+'rememberme' => 'Se souvenir de moi',
+'lostpassword' => 'Mot de passe perdu ?',
+'lostpwforfs' => 'Mot de passe perdu pour Flyspray',
+'lostpwmsg1' => "Bonjour,\n\nJ'ai perdu mon mot de passe pour Flyspray le",
+'lostpwmsg2' => ", merci de m'en fournir un nouveau s'il vous plaît.\n\nNom utilisateur :",
+'regards' => "\n\nCordialement,",
+'yourusername' => ' votre nom utilisateur ',
+'locale' => 'fr-FR',
+'filenotexist' => 'Le fichier n\'existe pas. Contacter l\'administrateur de Flyspray pour ce projet.',
+'showtask' => 'Afficher la tâche',
+'now' => 'Maintenant',
+'go' => 'Go !',
+'opentaskanon' => 'Ouvrir une nouvelle tâche anonymement',
+'register' => 'Inscription',
+'addnewtask' => 'Ouvrir une tâche',
+'reports' => 'Rapports',
+'editmydetails' => 'Modifier mes informations',
+'logout' => 'Déconnexion',
+'disabledaccount' => 'Votre compte a été désactivé !<br />Déconnexion immédiate...',
+'poweredby' => 'Propulsé par Flyspray',
+'sponsoredby' => 'Flyspray est fièrement sponsorisé par',
+'projects' => 'Projets',
+'allprojects' => 'Tous les projets',
+'selectproject' => 'du projet :',
+'tasksall' => 'Toutes les tâches',
+'tasksassigned' => 'Tâches qui me sont assignées',
+'tasksreported' => 'Tâches que j\'ai rapportées',
+'taskswatched' => 'Surveillées par moi',
+'mysearch' => 'Mes recherches',
+'admintoolbox' => 'Boîte à outils Administrateur',
+'manageproject' => 'Gérer le projet',
+'permissions' => 'Voir les permissions',
+'hide' => 'Masquer',
+'pendingreq' => 'Requêtes en attente de décision',
+'errorpage' => "Flyspray ne peut pas afficher la page demandée : soit vous avez demandé une tâche qui n'existe pas, soit vous n'avez pas la permission de consulter cette page.\nVous avez peut-être tenté d'utiliser une URL malveillante pour interagir avec la base de données via une injection SQL. Si c'est le cas, allez au coin et réfléchissez à vos actes. Quand vous reviendrez, ne recommencez pas !",
+'permissionsforproject' => 'Permissions pour ',
+'switchto' => 'Basculer vers',
+'lastsearch' => 'Ma dernière recherche',
+'modify' => 'Modifier',
+'noticefrom' => 'Note de',
+'hasopened' => 'a ouvert une nouvelle tâche, et vous l\'a assignée :',
+'moreinfonew' => 'Vous trouverez plus d\'information sur cette tâche à la page :',
+'newtaskcategory' => 'Une nouvelle tâche a été ouverte dans cette catégorie',
+'categoryowner' => 'Vous recevez ce message car vous figurez dans la liste des responsables de cette catégorie.',
+'tasksummary' => 'Résumé de la tâche :',
+'newtaskadded' => 'Votre nouvelle tâche a été ajoutée.',
+'summaryanddetails' => 'Vous devez remplir le résumé ET les détails.',
+'goback' => 'Retour.',
+'messagefrom' => 'Ceci est un message du système de gestion de tâches sur ',
+'hasjustmodified' => 'vient de modifier la tâche suivante.',
+'changedfields' => 'Les champs modifiés sont préfixés d\'astérisques (**)',
+'moreinfomodify' => 'Vous aurez plus d\'information sur cette tâche à cette adresse :',
+'nolongerassigned' => 'La tâche suivante ne vous est plus assignée. Elle est maintenant affectée à',
+'hasassigned' => 'vous a assigné la tâche suivante :',
+'taskupdated' => 'La tâche a été mise à jour.',
+'tasksupdated' => 'Les tâches ont été mises à jour',
+'hasclosedassigned' => 'a fermé cette tâche qui vous était assignée :',
+'unassigned' => 'Non assignée',
+'hasclosed' => 'a fermé la tâche suivante.',
+'youonnotify' => 'Vous recevez ce message car vous figurez dans la liste de notification.',
+'taskclosedmsg' => 'La tâche a été fermée',
+'returntotask' => 'Revenir au détail de la tâche',
+'backtoindex' => 'Revenir à la liste des tâches',
+'noclosereason' => 'Vous n\'avez pas choisi la raison de fermeture de cette tâche.',
+'hasreopened' => 'a rouvert la tâche suivante que vous aviez fermée :',
+'taskreopenedmsg' => 'La tâche a été rouverte',
+'backtotask' => 'Revenir sur la tâche.',
+'commentaddedmsg' => 'Le commentaire a été ajouté',
+'commenttoassigned' => 'a ajouté un commentaire à une tâche qui vous est assignée :',
+'commenttotask' => 'a ajouté le commentaire suivant à cette tâche.',
+'nocommententered' => 'Vous devez entrer un commentaire avant de cliquer le bouton d\'envoi.',
+'fillinfields' => 'Vous n\'avez pas rempli tous les champs.',
+'notcurrentpass' => 'Ce n\'est pas votre mot de passe actuel !',
+'passchanged' => 'Votre mot de passe a été modifié.',
+'closewindow' => 'Vous pouvez maintenant fermer cette fenêtre.',
+'passnomatch' => 'Vos nouveaux mots de passe ne sont pas identiques !',
+'usernametaken' => 'Ce nom d\'utilisateur est déjà enregistré. Veuillez en choisir un différent.',
+'usernametakenbulk' => 'Ce nom d\'utilisateur est déjà enregistré',
+'newusercreated' => 'Le nouveau compte utilisateur a été créé.',
+'accountcreated' => 'Votre compte a été créé.',
+'newuserwarning' => 'Notez que les options globales pourraient requérir l\'approbation de votre compte par un administrateur. Si vous ne pouvez pas vous connecter, c\'est sûrement pour cette raison.',
+'nomatchpass' => 'Les mots de passe ne correspondent pas.',
+'confirmwrong' => 'Le code de confirmation est incorrect !',
+'formnotcomplete' => 'Le formulaire n\'a pas été complètement rempli.',
+'formnotnumeric' => 'Les données renseignées ne sont pas numériques !',
+'groupnametaken' => 'Ce nom de groupe est déjà enregistré.',
+'newgroupadded' => 'Nouveau groupe ajouté.',
+'optionssaved' => 'Options Flyspray sauvegardées.',
+'hasuploaded' => 'a joint un fichier à une tâche qui vous est assignée :',
+'hasattached' => 'a joint un fichier à la tâche suivante.',
+'fileuploaded' => 'Le fichier a été envoyé.',
+'fileerror' => 'Erreur lors de l\'envoi du fichier : les droits sur le répertoire <i>attachments/</i> sont peut-être incorrects.',
+'contactadmin' => 'Contactez l\'administrateur de ce projet.',
+'selectfileerror' => 'Vous n\'avez pas sélectionné de fichier et/ou saisi de description.',
+'userupdated' => 'Les informations utilisateur ont été mises à jour',
+'realandemail' => 'Vous n\'avez pas saisi les champs Nom réel ET adresse e-mail.',
+'groupupdated' => 'Définition du groupe mise à jour.',
+'groupanddesc' => 'Vous n\'avez pas saisi les champs Nom du groupe ET Description.',
+'fillallfields' => 'Veuillez remplir tous les champs.',
+'listPmustN' => 'Le champ "ORDER" doit être numérique',
+'listupdated' => 'La liste a été mise à jour.',
+'listitemadded' => 'Nouvel élément ajouté dans la liste.',
+'relatedaddedmsg' => 'Tâche liée ajoutée à la liste',
+'relatederror' => 'Cette tâche est déjà dans la liste des tâches liées.',
+'relatedremoved' => 'Tâche liée retirée de la liste.',
+'notifyadded' => 'Utilisateur ajouté à la liste de notification.',
+'notifyerror' => 'Cet utilisateur est déjà dans la liste de notification pour cette tâche.',
+'notifyremoved' => 'Utilisateur retiré de la liste de notification.',
+'editcommentsaved' => 'Commentaire mis à jour sauvegardé.',
+'commentdeletedmsg' => 'Commentaire supprimé',
+'gotonewtask' => 'Aller à la nouvelle tâche nouvellement créée',
+'projectcreated' => 'Votre nouveau projet a été créé. Suivez le lien ci-dessous pour définir vos catégories, systèmes d\'exploitation et listes de versions',
+'customiseproject' => 'Personnaliser ce projet',
+'projectupdated' => 'Préférences du projet mises à jour',
+'emptytitle' => 'Vous n\'avez pas saisi le nom du projet.',
+'loginbelow' => 'Vous pouvez maintenant tenter de vous connecter via le formulaire ci-dessous.',
+'attachmentdeletedmsg' => 'La pièce jointe a été supprimée',
+'reminderaddedmsg' => 'Votre rappel a été ajouté',
+'reminderdeletedmsg' => 'Le rappel sélectionné a été supprimé',
+'flyspraytask' => 'Tâche Flyspray',
+'fieldsmissing' => 'Certains champs étaient vides ou invalides.',
+'relatedinvalid' => 'Il n\'y a pas de telle tâche.',
+'relatedproject' => 'La tâche appartient à un projet différent.',
+'addanyway' => 'Ajouter quand-même',
+'cancel' => 'Annuler',
+'alreadyedited' => 'Cette tâche a été modifiée par un autre utilisateur avant que vous la sauvegardiez.',
+'saveanyway' => 'Sauvegarder quand-même mes modifications',
+'nouserselected' => 'Aucun utilisateur sélectionné. Sélectionnez-en au moins un avant de recommencer',
+'groupswitchupdated' => 'Groupe d\'utilisateurs modifié avec succès.',
+'takenownershipmsg' => 'Vous êtes maintenant responsable de cette tâche',
+'adminrequestmade' => 'Votre demande a été envoyée au chef de projet',
+'newdepnotify' => 'Une dépendance a été ajoutée à la tâche suivante :',
+'dependadded' => 'Dépendance ajoutée',
+'dependaddfailed' => 'Erreur lors de l\'ajout de la dépendance : vérifiez que la tâche existe et qu\'il n\'y a pas d\'interblocage.',
+'depremovedmsg' => 'Dépendance supprimée',
+'newdepis' => 'La nouvelle dépendance est',
+'magicurlsent' => 'Un message a été envoyé à votre adresse de notification. Il contient un lien qui vous dirigera vers une page pour compléter cette tâche',
+'changefspass' => 'Modifier le mot de passe Flyspray',
+'magicurlmessage' => 'Veuillez suivre le lien ci-dessous pour modifier votre mot de passe :',
+'erroronform' => 'Il y a une erreur dans le formulaire',
+'addressused' => 'Cette adresse a déjà été utilisée pour créer un compte. Si vous recevez ce message par erreur, ignorez-le et effacez-le. Votre code de confirmation est :',
+'confirmcodeis' => 'Votre code de confirmation est :',
+'codesent' => 'Votre code de confirmation a été envoyé. Veuillez suivre les instructions contenues dans le message.',
+'codenotsent' => 'Échec d\'envoi de votre code. Réessayez plus tard.',
+'taskmadeprivatemsg' => 'Cette tâche a été rendue privée',
+'taskmadepublicmsg' => 'Cette tâche a été rendue publique',
+'realandnotify' => 'Vous devez remplir votre nom réel ainsi que votre adresse e-mail ou votre ID Jabber.',
+'pmreqdeniedmsg' => 'Demande de Chef de projet refusée',
+'massopsuccess' => 'Opérations en masse réussies - quand les permissions étaient bonnes',
+'usernotexist' => 'Cet utilisateur n\'existe pas dans cette installation de Flyspray',
+'commentattachperms' => 'Vous ne pouvez pas supprimer ce commentaire - vous n\'avez pas la permission de supprimer les pièces jointes',
+'voterecorded' => 'Votre vote a été enregistré',
+'votefailed' => 'Votre vote ne peut pas être enregistré pour le moment',
+'createnewgroup' => 'Créer un nouveau groupe',
+'requiredfields' => 'Les champs requis sont marqués d\'une',
+'addthisgroup' => 'Ajouter ce groupe',
+'createnewproject' => 'Créer un nouveau projet',
+'addnewproject' => 'Créer un projet',
+'htmlallowed' => 'Le code HTML est autorisé',
+'createthisproject' => 'Créer ce projet',
+'inlineimages' => 'Afficher les images jointes',
+'createnewtask' => 'Ouvrir une nouvelle tâche dans le projet :',
+'addanother' => 'Ouvrir une autre tâche après celle-ci',
+'addthistask' => 'Ouvrir cette tâche',
+'notifyme' => 'M\'avertir quand cette tâche est modifiée',
+'newtask' => 'Nouvelle tâche :',
+'attachafile' => 'Joindre un fichier',
+'registernewuser' => 'Enregistrer un nouvel utilisateur',
+'none' => 'Aucun(e)',
+'registeraccount' => 'Enregistrer ce compte',
+'registerbulkaccount' => 'Enregistrer comptes',
+'both' => 'Les deux',
+'notifyfrom' => 'Notification de ',
+'donotreply' => 'CECI EST UN MESSAGE AUTOMATIQUE, MERCI DE NE PAS RÉPONDRE.',
+'disclaimer' => 'Vous recevez ce message car vous en avez fait la demande sur le gestionnaire de tâches Flyspray. Vous pouvez stopper toute future notification en cliquant sur le lien ci-dessus.',
+'userwho' => 'Action effectuée par ',
+'moreinfo' => 'Plus d\'informations sont disponibles à cette adresse :',
+'newtaskopened' => 'Une nouvelle tâche a été ouverte. Voir les détails ci-dessous.',
+'notify.taskclosed' => 'La tâche suivante a été fermée :',
+'notify.taskreopened' => 'La tâche suivante a été rouverte :',
+'newdep' => 'La tâche suivante a une nouvelle dépendance :',
+'notify.depremoved' => 'Une dépendance a été supprimée sur la tâche suivante :',
+'olddepwas' => 'La dépendance était',
+'notify.commentadded' => 'Un nouveau commentaire a été ajouté dans la tâche suivante :',
+'commentis' => 'Le contenu du commentaire concerné est ci-dessous.',
+'newattachment' => 'Un nouveau fichier a été joint à la tâche suivante :',
+'detailsbelow' => 'Les détails sont ci-dessous.',
+'notify.relatedadded' => 'Une nouvelle tâche a été liée à la tâche suivante :',
+'relatedis' => 'La tâche liée est',
+'assignedtoyou' => 'La tâche suivante vous a été assignée :',
+'takenownership' => 'a pris la responsabilité de la tâche suivante :',
+'requiresaction' => 'La tâche suivante requière une intervention du chef de projet :',
+'requiresactionnotify' => 'Tâche requérant une intervention du chef de projet',
+'pmdeny' => 'Un chef de projet a refusé la demande en cours sur cette tâche :',
+'pmdenynotify' => 'Demande refusée par un chef de projet',
+'fileaddedtoo' => 'Il y avait un ou plusieurs fichiers joints au commentaire.',
+'taskwatching' => 'La tâche que vous surveillez',
+'isdepfor' => 'est une nouvelle dépendance pour',
+'denialreason' => 'Raison du refus',
+'taskchanged' => 'La tâche suivante a été modifiée. Les modifications effectuées sont décrites ci-dessous. Pour le détail de ce qui a changé depuis le début, suivez le lien vers la tâche concernée et consultez l\'historique.',
+'useraddedtoassignees' => 'Un utilisateur s\'est ajouté à la liste des destinataires de cette tâche.',
+'removeddepis' => 'La dépendance supprimée est',
+'isnodepfor' => 'n\'est plus une dépendance de',
+'usergroups' => 'Groupes d\'utilisateurs',
+'pmtoolbox' => 'Boîte à outils du Chef de projet',
+'groupmanage' => 'Gestion de groupe',
+'pendingrequests' => 'Demandes en attente',
+'reasongiven' => 'Raison invoquée',
+'nopendingreq' => 'Il n\'y a aucune demande de Chef de Projet en attente.',
+'givereason' => 'Donner une raison',
+'catlisted' => 'Éditeur de catégories',
+'oslisted' => 'Éditeur de systèmes d\'exploitation',
+'verlisted' => 'Éditeur de versions',
+'tasktypeed' => 'Éditeur de types de tâche',
+'resed' => 'Éditeur de résolutions',
+'deny' => 'Refus',
+'notifiedwhen' => 'Notifié quand',
+'onlynewtasks' => 'Des nouvelles tâches sont ouvertes',
+'allevents' => 'N\'importe quel événement se produit sur n\'importe quelle tâche',
+'feeds' => 'Flux',
+'feeddescription' => 'Description du flux',
+'feedimgurl' => 'URL de l\'image du flux (sinon laisser blanc)',
+'notifysubject' => 'Sujet des notifications',
+'notifysubjectinfo' => '(%p = nom du projet, %s = titre de la tâche, %t = ID de la tâche, %a = action, %u = nom d\'utilisateur)',
+'priority6' => 'Très haute',
+'priority5' => 'Haute',
+'priority4' => 'Moyenne',
+'priority3' => 'Basse',
+'priority2' => 'Très Basse',
+'priority1' => 'Ajourné',
+'sendcode' => 'Envoyer le code !',
+'entercode' => 'Votre code de confirmation a été envoyé. Une fois reçu, entrez le ci-dessous. Entrez également le mot de passe souhaité.',
+'confirmationcode' => 'Code de confirmation',
+'registererror' => 'Assurez-vous d\'avoir rempli tous les champs requis, et que vous avez entré des données correctes pour le type de notification souhaité.',
+'validusername' => '(seuls les caractères alphanumériques et - _ . sont autorisés',
+'validemail' => '(utilisez ; pour séparer des adresses email multiples',
+'emailtakenbulk' => 'Adresse email déjà enregistrée',
+'emailtaken' => 'Adresse e-mail ou ID Jabber déjà utilisé(e). Vous devez en choisir un(e) autre.',
+'note' => '<b>Note :</b> Vous recevrez un code de confirmation avant la création de votre compte. Le code vous sera envoyé selon la méthode de notification choisie ci-dessus.<br />EN CAS DE SAISIE ERRONÉE, VOUS NE POURREZ PAS RECEVOIR VOTRE CODE.',
+'changelog' => 'Journal des modifications',
+'changeloggen' => 'Génération du Journal',
+'listfrom' => 'Lister les entrées du journal du',
+'to' => 'au',
+'oldestfirst' => 'La plus ancienne en premier',
+'recentfirst' => 'La plus récente en premier',
+'severityrep' => 'Rapport de Sévérité',
+'totalopen' => 'Total des tâches ouvertes',
+'age' => 'Age',
+'agerep' => 'Rapport d\'âge',
+'eventsrep' => 'Rapport d\'événements',
+'events' => 'événements',
+'Tasks' => 'Tâches',
+'opened' => 'Ouverte',
+'edited' => 'Modifiée',
+'assigned' => 'Assignées',
+'within' => 'Dans',
+'pastday' => 'Le jour passé',
+'pastweek' => 'La semaine passée',
+'pastmonth' => 'Le mois passé',
+'pastyear' => 'L\'année passée',
+'nolimit' => 'Pas de limite',
+'from' => 'Du',
+'duein' => 'Échéance le',
+'selectfromdate' => 'Sélectionner date de début',
+'selecttodate' => 'Sélectionner date de fin',
+'showvoters' => 'Afficher/masquer les votants',
+'roadmap' => 'Feuille de route',
+'roadmapfor' => 'Feuille de route pour la version',
+'tasks' => 'tâches',
+'completed' => 'terminé.',
+'opentasks' => 'tâches ouvertes',
+'of' => '% de ',
+'severity5' => 'Critique',
+'severity4' => 'Haute',
+'severity3' => 'Moyenne',
+'severity2' => 'Basse',
+'severity1' => 'Très basse',
+'Redirect' => 'Redirection',
+'redirectmsg' => 'Si votre navigateur ne supporte pas la redirection automatique, %scliquez ici%s pour être redirigé ',
+'allowclosedcomments' => 'Autoriser les commentaires sur les tâches fermées',
+'comment' => 'Commentaire',
+'editowncomments' => 'Modifier ses propres commentaires',
+'reopened' => 'Rouverte',
+'loading' => 'Chargement...',
+'notifyown' => 'Notifier pour ses propres changements',
+'youremail' => 'Votre adresse e-mail',
+'thankyouforbug' => 'Merci d\'avoir signalé ce problème. Vous pouvez consulter la tâche et observer sa progression à tout moment à cette adresse :',
+'anonuser' => 'Utilisateur anonyme',
+'conflict' => 'Conflit',
+'file' => 'Fichier',
+'KiB' => 'ko',
+'MiB' => 'Mo',
+'size' => 'Taille',
+'projectgroup' => 'Groupe de projets',
+'profile' => 'Profil :',
+'viewprofile' => 'Voir le profil',
+'regdate' => 'Inscrit depuis',
+'tasksopened' => 'Tâches ouvertes',
+'replyto' => 'Répondre à',
+'notifytypes' => 'Type de notification',
+'pm.taskchanged' => 'Tâche modifiée',
+'pm.taskreopened' => 'Tâche rouverte',
+'pm.depadded' => 'Dépendance ajoutée',
+'pm.depremoved' => 'Dépendance supprimée',
+'pmrequest' => 'demande au Gestionnaire de Projet',
+'pmrequestdenied' => 'demande au Gestionaire de Projet refusée',
+'newassignee' => 'Nouveau destinataire',
+'revdepadded' => 'Dépendance inverse ajoutée',
+'revdepaddedremoved' => 'Dépendance inverse supprimée',
+'assigneeadded' => 'Destinataire ajouté',
+'addusergroup' => 'Ajouter l\'utilisateur suivant à ce groupe',
+'groupmembers' => 'Membres du groupe',
+'deleteuser' => 'Supprimer cet utilisateur',
+'userdeleted' => 'Utilisateur supprimé',
+'autoassign' => 'Assigner automatiquement une tâche au responsable de la catégorie',
+'ssl' => 'SSL',
+'updatewrong' => "L'option de recherche automatique de mises à jour est activée, mais une erreur s'est produite en essayant de contacter le serveur de mises à jour. Raisons probables : votre hôte n'autorise pas les connexions sortantes, ou un problème réseau est survenu.\nVeuillez visiter le site de Flyspray pour vous assurer que vous possédez la dernière version.",
+'deleteproject' => 'Supprimer ce projet et déplacer son contenu vers',
+'projectdeleted' => 'Projet supprimé avec succès',
+'feedforall' => 'Flux pour tous les projets',
+'usercreated' => 'Utilisateur créé',
+'created' => 'Créé',
+'deleted' => 'Supprimé',
+'userid' => 'ID de l\'utilisateur',
+'editassignments' => 'Modifier les assignations',
+'preview' => 'Prévisualisation',
+'anyprogress' => 'N\'importe',
+'tasksrelated' => 'Tâches associées à cette tâche',
+'duplicatetasks' => 'Tâches doublons de cette tâche',
+'databasemodfailed' => 'La modification de la base de données a échoué. Vous n\'avez peut-être pas les droits suffisants ?',
+'frequency' => 'Fréquence',
+'newuserregistered' => 'Un nouvel utilisateur s\'est inscrit sur votre installation Flyspray. Vous trouverez les détails ci-après :',
+'newuserregisterednotify' => 'Un nouvel utilisateur s\'est inscrit',
+'notify_registration' => 'Notifier les administrateurs à l\'inscription d\'un nouvel utilisateur',
+'textversion' => 'Version texte',
+'onlyprimary' => 'Tâches n\'en bloquant aucune autre',
+'onlyblocker' => 'Tâches bloquant d\'autres tâches',
+'blockerornoblocker' => 'La sélection simultanée des options "bloquant" et "non bloquant" n\'est pas cohérente.',
+'switch' => 'Basculer',
+'max' => 'max.',
+'dates' => 'Dates',
+'selectduedatefrom' => 'Échéance du',
+'selectduedateto' => 'au',
+'selectsincedatefrom' => 'Modifiée du',
+'selectsincedateto' => 'au',
+'selectdate' => 'Sélectionner la date',
+'selectopenedfrom' => 'Ouverte du',
+'selectopenedto' => 'au',
+'selectclosedfrom' => 'Fermée du',
+'selectclosedto' => 'au',
+'startat' => 'Démarre le : ',
+'hasattachment' => 'Avec pièce jointe',
+'private' => 'Privée',
+'watching' => 'Surveillée',
+'alreadyvotedthistask' => 'vous avez déjà voté pour cette tâche',
+'alreadyvotedthisday' => 'déjà voté aujourd\'hui',
+'visibility' => 'Visibilité',
+'public' => 'Publique',
+'leaveemptyauto' => 'Laissez ces 2 champs vides si vous voulez qu\'un mot de passe soit généré automatiquement.',
+'novalidemail' => 'L\'adresse e-mail que vous avez saisie n\'est pas valide.',
+'novalidjabber' => 'L\'ID Jabber que vous avez saisi n\'est pas valide.',
+'missingrequired' => 'Vous n\'avez pas rempli tous les champs obligatoires.',
+'entervalidusername' => 'Veuillez entrer un nom d\'utilisateur et un nom réel valide.',
+'couldnotaddusernotif' => 'Impossible d\'ajouter cet utilisateur à la liste des notifications.',
+'defaulttask' => 'Description par défaut des tâches',
+'all' => 'tous',
+'events.useraddedtoassignees'=> 'Utilisateur ajouté aux destinataires',
+'eventlog' => 'Journal des événements',
+'assignmentchanged' => 'Assignation modifiée',
+'detailedinfo' => 'Informations détaillées',
+'All' => 'Toutes',
+'tasksireported' => 'Ouvertes par moi',
+'recentlyopened' => 'Ouvertes récemment',
+'stats' => 'Statistiques',
+'totaltasks' => 'tâches au total',
+'mostwanted' => 'Tâches les plus voulues',
+'defaultentry' => 'Affichage par défaut',
+'toplevel' => 'Vue d\'ensemble',
+'overview' => 'Vue d\'ensemble',
+'error#' => 'Erreur n°',
+'error1' => 'Vous n\'avez pas les droits suffisants pour voir cette pièce jointe.',
+'error3' => 'Action répétée, redirection vers la page principale.',
+'error4' => 'Vous n\'êtes pas administrateur.',
+'error5' => 'Cet utilisateur n\'existe pas sur cette installation de Flyspray.',
+'error6' => 'Zone d\'administration invalide.',
+'error7' => 'Connexion échouée, mot de passe incorrect !',
+'error71' => 'Compte verrouillé pour %d minutes en raison de trop nombreux échecs de connexion !',
+'error8' => 'Vous n\'avez pas entré à la fois un nom d\'utilisateur et un mot de passe.',
+'error9' => 'Cette tâche n\'existe pas ou vous n\'avez pas la permission de la consulter.',
+'error10' => 'Cette tâche n\'existe pas.',
+'error101' => 'Vous n\'avez pas la permission de consulter cette tâche.',
+'error102' => 'Vous n\'avez pas la permission de consulter cette tâche : vous devez vous connecter.',
+'error11' => 'Vous n\'avez pas la permission de modifier ce commentaire.',
+'error12' => 'Clé magique non valide ! Êtes-vous sûr de l\'avoir obtenue depuis un message de notification ?',
+'error13' => 'Les utilisateurs anonymes ne disposent pas d\'un profil.',
+'error14' => 'Vous n\'avez pas les droits suffisants pour créer un groupe.',
+'error15' => 'Vous n\'avez pas les droits suffisants pour ouvrir une tâche.',
+'error16' => 'Vous n\'êtes pas un chef de projet.',
+'error17' => 'Zone de gestion de projet invalide.',
+'error18' => 'URL magique invalide.',
+'error19' => 'Cet utilisateur n\'existe pas sur cette installation de Flyspray.',
+'error20' => 'Modification de la base de données invalide.',
+'error21' => 'Un ou plusieurs e-mails n\'ont pas pu être envoyés. Vérifiez votre configuration.',
+'error22' => 'Les nouvelles inscriptions ne sont pas autorisées.',
+'error23' => 'Utilisateur ou groupe non autorisé à se connecter.',
+'error24' => 'Ni l\'exécutable dot ni le serveur public dot n\'ont été paramétrés.',
+'error25' => 'La feuille de route est uniquement disponible pour un projet donné.',
+'error26' => 'Fournisseur OAuth non supporté.',
+'error27' => 'Impossible de se connecter. Autorisez-nous à voir votre e-mail.',
+'error28' => 'Vous n\'avez pas les droits suffisants pour accéder à cette page',
+'done' => 'terminé',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Le projet n\'a pas pu être supprimé',
+'GMT' => 'GMT',
+'timezone' => 'Fuseau horaire',
+'accept' => 'Accepter',
+'reasonfordeinal' => 'Raison du refus',
+'pruneclosedlinks' => 'Purger les liens fermés',
+'pruneclosedtasks' => 'Purger les tâches fermées',
+'pagegenerated' => 'Page et image générées en %d secondes',
+'pruninglevel' => 'Niveau de purge',
+'lastuser' => 'Le dernier utilisateur ne peut pas être supprimé.',
+'allprivate' => 'Tous les projets sont privés.',
+'deletegroup' => 'Supprimer ce groupe et déplacer les utilisateurs vers',
+'parent' => 'Parent',
+'ordertip' => 'L\'ordre dans lequel ces éléments vont apparaitre dans la liste',
+'showtip' => 'Afficher cet élément dans la liste',
+'deletetip' => 'Supprimer cet élément de la liste',
+'del' => 'supprimer',
+'request1' => 'Une fermeture de tâche a été demandée',
+'request2' => 'Une réouverture de tâche a été demandée',
+'allpriorities' => 'Toutes les priorités',
+'noroadmap' => 'Aucune feuille de route disponible (le projet n\'a pas de future version définie)',
+'expand' => 'Dérouler',
+'collapse' => 'Replier',
+'expandall' => 'Tout dérouler',
+'collapseall' => 'Tout replier',
+'minpwsize' => 'Taille minimale : 5 caractères',
+'passwordtoosmall' => 'Mot de passe trop court.',
+'accountwaslocked' => 'Votre compte a été bloqué suite à un trop grand nombre d\'échecs de connexion.',
+'failedattempts' => 'Il y a eu %d échecs de connexion.',
+'groupnotexist' => 'Le groupe sélectionné n\'existe pas dans ce projet.',
+'searchindetails' => 'Rechercher dans les détails',
+'showasassignees' => 'Afficher dans les destinataires',
+'find' => 'Rechercher',
+'tls' => 'TLS',
+'isadmin' => 'Est administrateur',
+'addvotes' => 'Ajouter des votes',
+'removevote' => 'Supprimer le vote',
+'voteremoved' => 'Votre vote a été supprimé',
+'voteremovefailed' => 'Impossible de supprimer votre vote pour le moment.',
+'novotes' => 'Vous n\'avez actuellement voté pour aucune tâche.',
+'connectedtasks' => 'Tâches liées :',
+'taskdependencies' => 'Dépendances de tâche',
+'viewgraph' => 'voir le graphe',
+'notaskdependencies' => 'Cette tâche ne dépend d\'aucune autre.',
+'dependson' => 'Dépend de',
+'blocks' => 'bloque',
+'newdependency' => 'Nouvelle dépendance :',
+'nouserstoadd' => 'Aucun utilisateur à ajouter. Vérifiez que les noms, identifiants, et emails sont définis pour chaque utilisateur.',
+'dispintro' => 'Afficher le message d\'introduction principal',
+'mainmessage' => 'Message d\'introduction principal',
+'setsupertask' => 'Définir la tâche parente:',
+'supertaskmodified' => 'Identifiant de la tâche parente modifié',
+'set' => 'Définir',
+'supertask' => 'Tâche parente',
+'setparent' => 'Définir l\'identifiant de la tâche parente',
+'selfsupertasknotallowed' => 'L\'identifiant de tâche parente ne peut pas être le même que celui de la tâche elle-même',
+'quickaction' => 'Actions rapides',
+'updateselectedtasks' => 'Tâches sélectionnées mises à jour',
+'notspecified' => 'Non spécifié',
+'editselectedtasks' => 'Modification de cette tâche',
+'information' => 'Information',
+'taskclosedisabled' => 'La fermeture de cette tâche est temporairement suspendue, due aux tâches dépendantes encore ouvertes',
+'daysleft' => 'Jours restants',
+'dayoverdue' => 'Jours de retard',
+'duetoday' => 'Dernier jour.',
+'daysbeforealert' => 'Jours avant alerte',
+'associatedsubtask' => 'Sous-tâche associée',
+'associatesubtask' => 'Associer cet identifiant de sous-tâche avec cette tâche',
+'subtaskid' => 'Identifiant de sous-tâche',
+'subtaskalreadyhasparent' => 'Cette sous-tâche a déjà une tâche parente. Merci de la supprimer au préalable.',
+'subtaskisparent' => 'Cette sous-tâche est la tâche parente. Elle ne peut être associée.',
+'subtasknotexist' => 'Cette sous-tâche n\'existe pas.',
+'subtaskremovedmsg' => 'Cette sous-tâche a été supprimée',
+'subtaskadded' => 'Sous-tâche ajoutée',
+'subtaskremoved' => 'Sous-tâche retirée',
+'addnewsubtask' => 'Ajouter une Sous-tâche',
+'hidesubtasks' => 'Cacher une Sous-tâche',
+'voteforthistask' => 'Voter pour cette tâche',
+'watchthistask' => 'Consulter cette tâche',
+'privatethistask' => 'Marquer cette tâche comme privée',
+'adddependenttask' => 'Ajouter une tâche dépendante',
+'associatetaskid' => 'Identifiant de tâche associée',
+'parenttaskid' => 'Identifiant de tâche parente',
+'invalidsupertaskid' => 'Identifiant de tâche parente invalide.',
+'supertaskadded' => 'Tâche parente ajoutée',
+'supertaskremoved' => 'Tâche parente supprimée',
+'effort' => 'Effort',
+'efforttracking' => 'Suivi d\'effort',
+'useeffort' => 'Le projet utilise le suivi d\'effort',
+'estimatedeffort' => 'Effort estimé',
+'totalestimatedeffort' => 'Effort estimé total',
+'currenteffortdone' => 'Effort fourni',
+'starteffort' => 'Démarrer le suivi',
+'endeffort' => 'Arrêter le suivi',
+'cleareffort' => 'Effacer le suivi',
+'addeffort' => 'Ajouter effort',
+'manualeffort' => 'Ajouter effort manuellement (H:M)',
+'efforttrackingstarted' => 'Suivi d\'effort démarré pour cette tâche.',
+'efforttrackingnotstarted'=> 'Impossible de démarrer le suivi pour une tâche qui est déjà suivie',
+'efforttrackingstopped' => 'Le suivi d\'effort est terminé pour cette tâche.',
+'efforttrackingcancelled' => 'Suivi d\'effort annulé pour cette tâche.',
+'efforttrackingadded' => 'Effort manuel enregistré pour cette tâche.',
+'trackinginprogress' => 'Suivi en cours',
+'viewestimatedeffort' => 'Peut voir le suivi d\'effort',
+'viewcurrenteffortdone' => 'Peut voir l\'effort dépensé actuel',
+'trackeffort' => 'Peut suivre l\'effort',
+'invalideffort' => 'L\'effort entré est invalide. Ce doit être un nombre',
+'showpass' => 'Afficher le mot de passe',
+'chooseafile' => 'Merci de sélectionner un fichier !',
+'incorrectfiletype' => 'Type de fichier incorrect. Autorisé : jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Aucun mot de passe nécessaire. Enregistrement via %s',
+'addmultipletasks' => 'Ouvrir des tâches en lot',
+'pendingnewuserrequest' => 'Demande d\'utilisateur en attente',
+'adminrequestswaiting' => 'Demandes pour l\'administrateur en attente',
+'clicktoedit' => 'Cliquez sur les champs pour effectuer une modification rapide',
+'confirmedit' => 'confirmer',
+'regapprovedbyadmin' => 'Inscriptions approuvées par les administrateurs (code de confirmation inutile)',
+'activity' => 'Activité',
+'myactivity' => 'Mon activité',
+'emailverificationwrong' => 'La confirmation d\'e-mail ne correspond pas à l\'e-mail',
+'verifyemailaddress' => 'Confirmer l\'adresse e-mail',
+'hideemails' => 'Cacher adresses email',
+'hidemyemail' => 'Cacher mon adresse email',
+'exporttasklist' => 'Exporter la liste des tâches',
+'onedecimal' => 'Une décimale',
+'manday' => 'jour-homme',
+'mandays' => 'jours-hommes',
+'mandayabbrev' => 'JH',
+'hourspermanday' => 'Heures par jour-homme (HH:mm)',
+'itemexists' => 'L\'entrée %s existe déjà en base de données.',
+'categoryitemexists' => 'L\'entrée %s existe déjà dans la catégorie %s en base de données.',
+'pageswelcomemsg' => 'Pages sur lesquelles afficher le message de bienvenue',
+'pagesintromsg' => 'Pages sur lesquelles afficher le message d\'intro',
+'activeoauths' => 'Fournisseurs OAuth actifs',
+'onlyoauthreg' => 'Autoriser uniquement les inscriptions via OAuths',
+'estimatedeffortformat' => 'Format d\'affichage de l\'effort estimé',
+'currenteffortdoneformat' => 'Format actuel de l\'affichage de l\'effort',
+'minute' => 'minute',
+'minutes' => 'minutes',
+'minuteplural' => 'minutes',
+'minutesingular' => 'minute',
+'minuteabbrev' => 'm',
+'hourplural' => 'heures',
+'hoursingular' => 'heure',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Effort estimé pour les tâches ouvertes',
+'currenteffortdoneopen' => 'Effort fourni pour les tâches ouvertes',
+'signinwith' => 'Se connecter avec %s',
+'canviewroadmap' => 'Peut consulter la feuille de route',
+'enableavatars' => 'Activer les avatars',
+'maxavatarsize' => 'Taille maximum des avatars',
+'taskhassubtask' => 'Cette tâche a la sous-tâche suivante',
+'taskhassubtasks' => 'Cette tâche a les sous-tâches suivantes',
+'translations' => 'Traductions',
+'translate' => 'Traduire',
+'taskdescription' => 'Description de la tâche',
+'notaskdescription' => 'Aucune description',
+'pleaseselect' => 'Veuillez sélectionner',
+'closeselectedtasks' => 'Fermer les tâches sélectionnées',
+'closetasks' => 'Fermer les tâches',
+'hintforbulkimport' => '<b>Conseils pour l\'importation en lot :</b>
+ <ol>
+ <li>Copiez/collez d\'une feuille de calcul Excel ou CSV en collant une colonne entière</li>
+ <li>Pour le moment vous ne pouvez coller que "Résumé" et "Détails"</li>
+ <li>La colonne "Assignée à" est intelligente et vous suggère les utilisateurs en fonction de ce que vous saisissez</li>
+ </ol>',
+'taskissubtaskof' => 'Cette tâche est une sous-tâche de',
+'applyfirstline' => 'Appliquer la première ligne',
+'addmorerows' => 'Ajouter plus de lignes',
+'addtasks' => 'Ouvrir les tâches',
+'massopsdisabled' => 'Désolé, la modification par lot est désactivée pour Flyspray 1.0. Nous avons décidé de terminer son implémentation pour une future version de Flyspray. Vous pouvez activer cette fonction dans le code source à vos risques, mais lisez attentivement les commentaires avant de le faire.',
+'viewroadmap' => 'Peut consulter la feuille de route',
+'nosuicide' => 'Cher utilisateur, mon programme ne vous autorise pas à détruire votre accès à Flyspray en désactivant votre propre compte ou en changeant votre groupe d\'utilisateur. Signé le sympathique frère de HAL9000',
+'movingtodifferentproject'=> 'Déplacer une tâche ayant une tâche parente ou une sous-tâche dans un autre projet n\'est pas autorisé. Supprimer d\'abord les connexions entre elles.',
+'musthavesameproject' => 'La tâche parente et la sous-tâche doivent appartenir au même projet.',
+'defaultorderby' => 'Par défaut, ordonner les tâches par',
+'viewowntasks' => 'Voir ses propres tâches',
+'viewgroupstasks' => 'Voir les tâches de groupes',
+'urlrewriting' => 'Réécriture d\'url',
+'enablehtaccess' => 'Veuillez activer votre fichier .htaccess à la racine de Flyspray avant d\'activer la réécriture d\'url',
+'nomodrewrite' => 'Mod rewrite ne semble pas disponible sur ce serveur, désolé mais je ne peux pas activer la rééecriture d\'url',
+'on' => 'Activé',
+'off' => 'Désactivé',
+'defaultorderbydirection' => 'Ordre par défaut',
+'ascending' => 'Ascendant',
+'descending' => 'Descendant',
+'myassignedtasks' => 'Mes tâches',
+'commentedon' => 'a commenté le',
+'maxvoteperday' => 'Maximum de votes par jour',
+'votesperproject' => 'Nombre limite de votes utilisateur par projet',
+'votelimitreached' => 'Vous avez atteint la limite de votes pour ce projet. Consultez votre profil pour voir les tâches pour lesquelles vous avez voté. Vous pouvez également revenir sur vos votes. De cette façon, nous pouvons voir quelles tâches sont plus importantes pour vous. Les tâches résolues vous permettent de voter de nouveau.',
+'myvotes' => 'Mes Votes',
+'tag' => 'Tag',
+'tags' => 'Tags',
+'tagsinfo' => 'Tagguer librement des tâches dans Flyspray :
+Séparer les tags par ;
+Les Tags ne sont actuellement pas gérables et ne sont pas utilisés dans la recherche ou les filtres. Ils sont actuellement juste comme des post-it sur une fenêtre.',
+'novalues' => 'aucune entrée',
+'youhaveregistered' => 'Vous êtes inscrit sur Flyspray. Vous trouverez vos détails ci-après :',
+'youhaveregisterednotify' => 'Votre inscription sur Flyspray a été acceptée par les Administrateurs.',
+'usedintasks' => 'Utilisation',
+'active' => 'Actif(s)',
+'inactive' => 'Désactivé(s)',
+'showinactive' => 'Afficher les projets désactivés',
+'hideinactive' => 'Masquer les projets désactivés',
+'summaryrequired' => 'Vous devez renseigner un court résumé de la tâche',
+'defaultorderby2' => 'Ensuite par',
+'freetagging' => "Autoriser la création de tags par l'utilisateur",
+'customstyle' => 'personnalisé',
+'keyboardshortcuts' => 'Raccourcis clavier',
+'testmailsettings' => 'Tester l\'envoi d\'email avec la configuration actuellement active',
+'test' => 'Tester',
+'testmailsettingsnotice' => 'Et vérifier également que vous avez bien reçu l\'email de test dans la boîte mail de l\'utilisateur actuel (Voir la page "Mon profil")',
+'invalidinput' => 'Certaines propriétés sont incompatibles et doivent être modifiées avant de déplacer cette tâche vers un autre projet.',
+'invalidstatus' => 'Veuillez sélectionner un statut valide pour cette tâche lors d\'un déplacement vers un autre projet',
+'invalidcategory' => 'Veuillez sélectionner une catégorie valide pour cette tâche lors d\'un déplacement vers un autre projet',
+'invalidreportedversion' => 'Veuillez sélectionner une version de base valide pour cette tâche lors d\'un déplacement vers un autre projet',
+'invaliddueversion' => 'Veuillez sélectionner une version de livrable valide pour cette tâche lors d\'un déplacement vers un autre projet',
+'invalidos' => 'Veuillez sélectionner un système d\'exploitation valide pour cette tâche lors d\'un déplacement vers un autre projet',
+'invalidtags' => 'Veuillez ne sélectionner que des tags autorisés pour cette tâche lors d\'un déplacement vers un autre projet',
+'invalidassignees' => 'Veuillez ne sélectionner que des utilisateurs assignés autorisés pour cette tâche lors d\'un déplacement vers un autre projet',
+'customize' => 'Personnaliser',
+'hidesubs' => 'Masquer les sous-tâches',
+'hideprivate' => 'Masquer les tâches privées',
+'hideclosed' => 'Masquer les tâches closes',
+'currentproject' => 'Projet actuel',
+'targetproject' => 'Projet de destination',
+'invalidprogress' => 'Merci de choisir une valeur de progression correcte.',
+'invalidpriority' => 'Merci de choisir une valeur de priorité correcte.',
+'invalidseverity' => 'Merci de choisir une valeur de gravité correcte.',
+'availablekeybshortcuts' => 'Activer les raccourcis clavier',
+'logindialoglogout' => 'Se connecter/Se déconnecter',
+'focustaskidsearch' => 'Rechercher par ID de tâche',
+'openselectedtask' => 'Ouvrir la tâche sélectionnée',
+'movecursorup' => 'Déplacer le curseur vers le haut',
+'movecursordown' => 'Déplacer le curseur vers le bas',
+'taskdetails' => 'Détails de la tâche',
+'taskediting' => 'Édition de la tâche',
+'savetask' => 'Enregistrer la tâche',
+'generalintegration' => 'Chaînes de caractères globales',
+'generalintegrationdesc' => 'Dans le thème par défaut, ce contenu est positionné dans la balise "body", juste avant la balise "footer". Il est possible de modifier l\'endroit où il apparaît à l\'aide de CSS. Utilisez un fichier custom_*.css dans ce cas.',
+'footerintegration' => 'Texte du pied de page',
+'footerintegrationdesc' => 'Dans le thème par défaut, ce contenu est positionné dans le pied de page. Il est possible de modifier l\'endroit où il apparaît à l\'aide de CSS. Utilisez un fichier custom_*.css dans ce cas.',
+'editorbold' => 'G',
+'editorboldhint' => 'gras',
+'editoritalic' => 'I',
+'editoritalichint' => 'italique',
+'editorunderline' => 'S',
+'editorunderlinehint' => 'souligné',
+'editorstrikethrough' => 'B',
+'editorstrikethroughhint' => 'barré',
+'lastlogin' => 'Dernière connexion',
+'categoriesglobal' => 'Catégories Globales',
+'categoriesproject' => 'Catégories Projets',
+'categoriestarget' => 'Catégories Destination',
+
+'captchaerror' => 'CAPTCHA non résolu. Veuillez réessayer.',
+'registercaptcha' => 'Veuillez résoudre le CAPTCHA.',
+'regcaptcha' => 'Montrer un CAPTCHA à l\'enregistrement des nouveaux utilisateurs.',
+'invalidsecurimage' => 'CAPTCHA invalide',
+'invalidrecaptcha' => 'Google reCAPTCHA invalide',
+'antispam' => 'Antispam',
+'antispamprefsinfo' => 'Paramètres de l\'antispam. Actuellement, Securimage et Google reCAPTCHA sont utilisés pour l\'enregistrement des nouveaux utilisateurs',
+'securimageprefsinfo' => 'Un test CAPTCHA classique où les utilisateurs doivent reconnaitre un texte pour prouver qu\'ils sont humains.',
+'securimageenable' => 'Activer Securimage',
+'recaptchaprefsinfo' => 'Google reCAPTCHA est une alternative au CAPTCHA classique.
+Google décide selon les informations collectées, le comportement de l\'utilisateur et potentiellement un test de reconnaissance d\'image si vous êtes un humain ou un robot.
+Pour utiliser ceci, vous avez besoin d\'un compte Google et de configuer reCAPTCHA pour votre domaine depuis https://www.google.com/recaptcha en insérent "sitekey" et "secret" de cette configuration.
+Selon votre configuration - "reCAPTCHA V2" ou "Invisible reCAPTCHA" - il est possible que vous deviez informer vos utilisateurs à propos de la protection des données.',
+'recaptchaenable' => 'Activer Google reCAPTCHA',
+'adminchecks' => 'Vérification',
+'adminchecksinfo' => 'Information et vérification de l\'installation actuelle',
+'repeatpassword' => 'Répétez le mot de passe',
+'repeatemailaddress' => 'Répétez l\'adresse email',
+'tooltipshorttasktitle' => 'Ecrivez un titre résumant le problème et le rendant compréhensible d\'un coup d\'oeil',
+);
+
+?>
diff --git a/lang/he.php b/lang/he.php
new file mode 100644
index 0000000..57fb9ee
--- /dev/null
+++ b/lang/he.php
@@ -0,0 +1,1005 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'ערוך משתמש',
+'accountenabled' => 'חשבון פעיל',
+'editallusers' => 'הצג ×ת כל המשתמשי×',
+'username' => '×©× ×ž×©×ª×ž×©',
+'usersupdated' => 'המשתמש עודכן בהצלחה',
+'realname' => '×©× ×ž×œ×',
+'emailaddress' => 'כתובת דו×ל',
+'jabberid' => '×©× ×’\'×בר',
+'profileimage' => 'תמונת פרופיל',
+'notifytype' => 'שיטת הודעה',
+'group' => 'קבוצה',
+'enableaccounts' => 'הפעל חשבונות',
+'disableaccounts' => 'בטל חשבונות',
+'deleteaccounts' => 'מחק חשבונות',
+'updatedetails' => 'עדכן פרטי×',
+'setglobally' => 'העדפה זו הוגדרה בכל המערכת.',
+'usergroupmanage' => 'ניהול קבוצות ומשתמשי×',
+'newuser' => '×¨×™×©×•× ×ž×©×ª×ž×© חדש',
+'newuserbulk' => '×¨×™×©×•× ×ž×¡×¤×¨ משתמשי×',
+'bulkuserstoadd' => 'רשימת ×ž×©×ª×ž×©×™× ×—×“×©×™×',
+'optionsforallusers' => '×פשרויות לכל ×”×ž×©×ª×ž×©×™× ×”×—×“×©×™×',
+'newgroup' => 'יצירת קבוצה חדשה',
+'yes' => 'כן',
+'no' => 'ל×',
+'editgroup' => 'עריכת קבוצה',
+'groupname' => '×©× ×§×‘×•×¦×”',
+'description' => 'תי×ור',
+'admin' => 'קבוצת ניהול',
+'opennewtasks' => 'פתח משימות חדשות',
+'modifytasks' => 'עדכן משימות קיימות',
+'addcomments' => 'הוסף תגובות',
+'attachfiles' => 'הצמד קבצי×',
+'vote' => 'הצבע',
+'groupenabled' => '×—×‘×¨×™× ×™×›×•×œ×™× ×œ×”×›× ×¡',
+'tasktypelist' => 'רשימת סוגי משימות',
+'categorylist' => 'רשימת קטגוריות',
+'oslist' => 'רשימת מערכות הפעלה',
+'resolutionlist' => 'רשימת החלטות',
+'versionlist' => 'רשימת גרס×ות',
+'severitylist' => 'רשימת חומרות',
+'listnote' => 'הערה: הורדת סימון ״הצג״ עלולה לשנות חלק מהמשימות כשנמצ××™× ×‘×ž×¦×‘ עריכה. שינוי השדה ״ש××´ ישנה ×ת כל המשימות ×‘×©× ×–×”. משימות ×©×œ× ×™×›×•×œ×•×ª להמחק הן מוגנות כיוון שהן נדרשות לתפקוד תקין ×ו בשימוש במשימות.',
+'name' => 'ש×',
+'order' => 'סדר',
+'back' => 'חזרה',
+'text' => 'טקסט',
+'highlight' => 'הדגשה',
+'show' => 'הצג',
+'owner' => 'בעלי×',
+'selectowner' => 'בחר בעלי×',
+'update' => 'עדכן',
+'addnew' => 'הוסף חדש',
+'flysprayprefs' => 'הגדרות מערכת',
+'projecttitle' => 'כותרת פרויקט',
+'baseurl' => 'כתובת בסיס להתקנה זו',
+'replyaddress' => 'כתובת תשובה להודעות',
+'themestyle' => 'סגנון \\ עיצוב',
+'language' => 'שפה',
+'anonview' => '×פשר ×œ×ž×©×ª×ž×©×™× ×× ×•× ×™×ž×™×™× ×œ×¨×ות משימות',
+'allowanon' => '×פשר ×œ×ž×©×ª×ž×©×™× ×× ×•× ×™×ž×™×™× ×œ×¤×ª×•×— משימות חדשות',
+'never' => '××£ פע×',
+'anonymously' => 'ב×ופן ×נונימי',
+'afterregister' => 'רק ל×חר הרשמה',
+'spamproof' => '×פשר קוד ×ישור ×œ×¨×™×©×•× ×ž×©×ª×ž×©×™× ×—×“×©×™×',
+'anongroup' => 'קבוצה ×œ×¨×™×©×•× ×ž×©×ª×ž×©×™× ×—×“×©×™×',
+'groupassigned' => 'ניתן להגדיר משימות לחברי הקבוצות הב×ות',
+'forcenotify' => 'הכרח הודעות על משימות',
+'neversend' => '×œ×¢×•×œ× ×œ× ×œ×©×œ×•×—',
+'userchoose' => '×פשר לכל משתמש לבחור',
+'email' => 'דו×ר ×לקטרוני',
+'jabber' => '×’\'×בר ×ž×¡×¨×™× ×ž×™×™×“×™×™×',
+'defaultcatowner' => 'ברירת מחדל של בעל קטגוריה',
+'noone' => '××£ ×חד',
+'jabbernotify' => '×ž×¡×¨×™× ×ž×™×“×™×™× ×’\'×בר',
+'jabberserver' => 'שרת',
+'jabberport' => 'פורט',
+'jabberuser' => 'משתמש',
+'jabberpass' => 'סיסמה',
+'saveoptions' => 'שמור הגדרות',
+'editcomment' => 'עריכת הערה',
+'commentby' => 'הערה על ידי',
+'saveeditedcomment' => 'שמור הערה שנערכה',
+'projectprefs' => 'העדפות פרויקט',
+'pagetitle' => 'כותרת עמוד',
+'defaultproject' => 'פרויקט ברירת מחדל',
+'projectlists' => 'רשימות פרויקטי×',
+'showlogo' => 'הצג תמונת לוגו כותרת',
+'showgravatars' => 'הצג גר×וו×טרי×',
+'emailNoHTML' => 'דו××œ×™× ×œ×œ× HTML',
+'intromessage' => 'הודעת פתיחה',
+'isactive' => 'הפרויקט פעיל',
+'createproject' => 'צור פרויקט חדש',
+'nopermission' => '×ין לך הרש××” לשימוש בעמוד ×–×”.',
+'listordertip' => 'הסדר בו ×¤×¨×™×˜×™× ×לו יופיעו ברשימה',
+'listshowtip' => 'הצג פריט זה ברשימה',
+'categoryownertip' => 'משתמש ×–×” יקבל הודעה ×›×שר נפתחת משימה בקטגוריה זו',
+'categoryparenttip' => 'קטגוריית ×ב שהחדשה הזו תהיה תחתיה',
+'notsubcategory' => '×œ×œ× (קטגוריה ר×שית)',
+'showinlineimages' => 'הצג תמונות מצורפות בפני×',
+'dateformat' => 'מבנה ת×ריך',
+'dateformat_extended' => 'מבנה ת×ריך מפורט',
+'cache_feeds' => 'מטמון פידי×',
+'no_cache' => '×œ×œ× ×ž×˜×ž×•×Ÿ',
+'cache_disk' => 'מטמון בדיסק',
+'cache_db' => 'מטמון בבסיס הנתוני×',
+'subcategoryof' => 'תת קטגוריה של',
+'visiblecolumns' => 'עמודות להצגה ברשימת משימות',
+'visiblefields' => 'שדות בזמן הוספה/עריכה/צפייה במשימה',
+'tense' => 'זמן',
+'listtensetip' => 'עבר, הווה ×ו עתיד',
+'past' => 'עבר',
+'present' => 'הווה',
+'future' => 'עתיד',
+'oldpass' => '×¡×™×¡×ž× ×™×©× ×”',
+'nooldpass' => '×œ× ×”×•×’×“×¨×” ×¡×™×¡×ž× ×™×©× ×”',
+'oldpasswrong' => '×¡×™×¡×ž× ×™×©× ×” שגויה',
+'changepass' => 'שינוי סיסמ×',
+'confirmpass' => '×ישור שינוי',
+'projectmanager' => 'מנהל פרויקט',
+'viewtasks' => 'הצג משימות',
+'modifyowntasks' => 'עדכן ×ת משימותיך',
+'modifyalltasks' => 'עדכן משימות ש×ינן בבעלות המשתמש',
+'viewcomments' => 'הצג הערות',
+'editcomments' => 'ערוך הערות',
+'deletecomments' => 'מחק הערות',
+'viewattachments' => 'הצג ×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'createattachments' => 'צור ×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'deleteattachments' => 'מחק ×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'viewhistory' => 'הצג היסטוריה',
+'closeowntasks' => 'סגור משימות בבעלותך',
+'closeothertasks' => 'סגירת משימות ש×ינן בבעלות המשתמש',
+'assigntoself' => 'הקצה משימות לעצמי ×× ×¢×“×™×™×Ÿ ×œ× ×”×•×§×¦×•',
+'assignotherstoself' => 'הקצה משימות של ××—×¨×™× ×œ×¢×¦×ž×™',
+'viewreports' => 'הצגת רשימת ×ירועי×',
+'othersview' => '×פשר ×œ×›×•×œ× ×œ×¨×ות פרויקט ×–×”',
+'usersandgroups' => '×ž×©×ª×ž×©×™× ×•×§×‘×•×¦×•×ª',
+'globalgroup' => 'קבוצה גלובלית',
+'globalgroups' => 'קבוצות גלובליות',
+'defaultglobalgroup' => 'קבוצת ברירת מחדל גלובלית ×œ×ž×©×ª×ž×©×™× ×—×“×©×™×',
+'addtogroup' => 'הוסף לקבוצה',
+'moveuserstogroup' => 'העבר ×ž×©×ª×ž×©×™× ×œ×§×‘×•×¦×”',
+'nogroup' => '×œ×œ× ×§×‘×•×¦×” - הסר מהפרויקט',
+'eventdesc' => 'תי×ור ×ירוע',
+'requestedby' => 'נתבקש על ידי',
+'daterequested' => 'ת×ריך בקשה',
+'closetask' => 'סגור משימה',
+'reopentask' => 'פתח מחדש משימה',
+'applymember' => 'הגש בקשה לחברות בפרויקט',
+'forcurrentproj' => 'לפרויקט הקיי×',
+'lostpw' => 'שחזור ×¡×™×¡×ž× ×©×בדה',
+'lostpwexplain' => 'הזן ×ת ×©× ×”×ž×©×ª×ž×© לשליחת קישור לשינוי הסיסמ×. ההודעה תשלח לכתובת ההודעות שהוגדרה בפרופילך.',
+'sendlink' => 'שלח קישור',
+'savenewpass' => ' שמור סיסמה חדשה',
+'anonreg' => '×פשר ×¨×™×©×•× ×ž×©×ª×ž×©×™× ×—×“×©×™×',
+'allowanonopentask' => '×פשר ×œ×ž×©×ª×ž×©×™× ×× ×•× ×™×ž×™×™× ×œ×¤×ª×•×— משימות',
+'editglobalgroup' => 'ערוך קבוצה גלובלית',
+'editgroupforproj' => 'ערוך קבוצה לפרויקט',
+'notshownforadmin' => 'הרש×ות לקבוצת ניהול ×œ× ×ž×•×¦×’×•×ª. ×ין צורך לערוך ×ותן.',
+'general' => 'כללי',
+'userregistration' => '×¨×™×©×•× ×ž×©×ª×ž×©',
+'notifications' => 'הודעות',
+'resetoptions' => '×פס הגדרות',
+'preferences' => 'העדפות',
+'tasktypes' => 'סוגי משימות',
+'resolutions' => 'החלטות',
+'categories' => 'קטגוריות',
+'operatingsystems' => 'מערכות הפעלה',
+'versions' => 'גרס×ות',
+'admintoolboxlong' => 'תיבת ×›×œ×™× ×œ× ×™×”×•×œ',
+'newproject' => 'פרויקט חדש',
+'delete' => 'מחק',
+'link' => 'קשר',
+'referencelinks' => 'קישורי סימוכין:',
+'listdeletetip' => 'מחק פריט זה מהרשימה',
+'lookandfeel' => 'מר××” ו×ינטר×קציה',
+'globaltheme' => 'תערכת × ×•×©× ×’×œ×•×‘×œ×™',
+'emailnotify' => 'הודעות דו×ל',
+'fromaddress' => 'מכתובת',
+'smtpserver' => 'שרת SMTP',
+'smtpuser' => '×©× ×ž×©×ª×ž×© SMTP',
+'smtppass' => 'סיסמת SMTP',
+'addrewrite' => 'השתמש בשכתוב כתובת',
+'usereminderdaemon' => 'הפעל שירות תזכורות ברקע',
+'tasksperpage' => 'משימות לעמוד ברשימת המשימות',
+'addtoassignees' => 'הוסף ×ותי להקצ×ות',
+'taskstatuses' => 'מצבי משימה',
+'canvote' => 'יכול להצביע עבור משימות',
+'loginsuccessful' => 'כניסה הצליחה.',
+'youareloggedout' => 'יצ×ת מהמערכת.',
+'waitwhiletransfer' => '× × ×œ×”×ž×ª×™×Ÿ בעת ההעברה...',
+'clicknowait' => 'לחץ ×›×ן ×× ×ין ברצונך להמתין.',
+'accountdisabled' => 'חשבונך הושהה, יש ליצור קשר ×¢× ×ž× ×”×œ המערכת.',
+'task' => 'משימה',
+'edittask' => 'ערוך משימה זו',
+'openedby' => 'נפתחה על ידי',
+'editedby' => 'נערכה ל×חרונה על ידי',
+'tasktype' => 'סוג משימה',
+'category' => 'קטגוריה',
+'status' => 'סטטוס',
+'assignedto' => 'הוקצתה עבור',
+'operatingsystem' => 'מערכת הפעלה',
+'severity' => 'חומרה',
+'reportedversion' => 'דווח בגרסה',
+'dueinversion' => 'לביצוע בגרסה',
+'defaultdueinversion' => 'ברירת מחדל של ביצוע בגירסה למשימות חדשות',
+'undecided' => '×œ× ×”×•×—×œ×˜',
+'percentcomplete' => '×חוז השלמה',
+'details' => 'פרטי×',
+'savedetails' => 'שמור פרטי×',
+'canceledit' => 'ביטול',
+'anonymous' => '×ª×•×¨× ×נונימי',
+'complete' => 'הושל×',
+'closedby' => 'נסגרה על ידי',
+'reasonforclosing' => 'סיבת סגירה:',
+'reopenthistask' => 'פתח מחדש משימה זו',
+'comments' => 'הערות',
+'attachments' => '×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'relatedtasks' => 'משימות קשורות',
+'edit' => 'ערוך',
+'addcomment' => 'הוסף הערה',
+'fileuploadedby' => 'הקובץ הועלה על ידי',
+'uploadafile' => 'צרף קובץ',
+'addalink' => 'הוסף קישור',
+'addanotherlink' => 'הוסף קישור נוסף',
+'uploadnow' => 'העלה עכשיו!',
+'thesearerelated' => 'משימות ×לו קשורות למשימה זו',
+'remove' => 'הסר',
+'addnewrelated' => 'הוסף משימה קשורה חדשה',
+'add' => 'הוסף',
+'otherrelated' => 'משימות ×חרות שמשימה זו קשורה ×ליהן',
+'receivenotify' => '×ž×©×ª×ž×©×™× ×לו יקבלו הודעות מפורטות ×›×שר משימה זו מתעדכנת.',
+'addusertolist' => 'הוסף משתמש לרשימה זו',
+'addtolist' => 'הוסף לרשימה',
+'addmyself' => 'הוסף ×ת עצמי לרשימה זו',
+'removemyself' => 'הסר ×ת עצמי מרשימה זו',
+'theseusersnotify' => '×ž×©×ª×ž×©×™× ×לו יקבלו הודעות מפורטות ×›×שר משימה זו מתעדכנת.',
+'attachedtoproject' => 'צורף לפרויקט',
+'reminders' => 'תזכורות',
+'system' => 'מערכת',
+'remindthisuser' => 'הזכר למשתמש זה',
+'thisoften' => 'בתדירות זו',
+'startafter' => 'המתן לפני הפעלת תזכורות',
+'hour' => 'שעה',
+'hours' => 'שעות',
+'day' => 'יו×',
+'days' => 'ימי×',
+'week' => 'שבוע',
+'weeks' => 'שבועות',
+'addreminder' => 'הוסף תזכורת',
+'defaultreminder' => 'זוהי תזכורת להציץ במשימה הב××”:',
+'message' => 'הודעה',
+'closed' => 'נסגרה',
+'filename' => '×©× ×§×•×‘×¥:',
+'date' => 'ת×ריך',
+'filesize' => 'גודל קובץ:',
+'closurecomment' => 'הערות נוספות לסגירה:',
+'history' => 'היסטוריה',
+'nohistory' => '×ין היסטוריה.',
+'eventdate' => 'ת×ריך',
+'user' => 'משתמש',
+'event' => '×ירוע',
+'fieldchanged' => 'שדה שונה',
+'taskopened' => 'משימה נפתחה',
+'taskreopened' => 'משימה נפתחה מחדש',
+'taskclosed' => 'משימה נסגרה',
+'commentadded' => 'הערה נוספה',
+'commentedited' => 'הערה נערכה',
+'commentdeleted' => 'הערה נמחקה',
+'attachmentadded' => 'קובץ צורף',
+'attachmentdeleted' => 'צירוף קובץ נמחק',
+'taskedited' => 'פרטי משימה נערכו',
+'notificationadded' => 'משתמש התווסף לרשימת היידוע',
+'notificationdeleted' => 'משתמש הוסר מרשימת היידוע',
+'relatedadded' => 'משימה קשורה נוספה',
+'relateddeleted' => 'משימה קשורה הוסרה',
+'taskassigned' => 'משימה הוקצתה עבור',
+'taskreassigned' => 'משימה הוקצתה מחדש עבור',
+'assignmentremoved' => 'הקצ××” הוסרה',
+'summary' => 'סיכו×',
+'addedasrelated' => 'משימה התווספה לרשימת משימות קשורות של',
+'deletedasrelated' => 'משימה הוסרה מרשימת משימות קשורות של',
+'reminderadded' => 'תזכורת התווספה',
+'reminderdeleted' => 'תזכורת הוסרה',
+'priority' => 'עדיפות',
+'previousvalue' => 'ערך קוד×',
+'newvalue' => 'ערך חדש',
+'selectareason' => 'בחר סיבה',
+'assigntome' => 'הקצה לעצמי',
+'reopenrequest' => 'בקש פתיחה מחדש',
+'requestclose' => 'בקש סגירה',
+'ownershiptaken' => 'משתמש לקח בעלות',
+'closerequestmade' => 'בקשה הגיעה לסגירת המשימה',
+'reopenrequestmade' => 'בקשה הגיעה לפתיחה מחדש של המשימה',
+'taskdependson' => 'משימה זו תלויה במשימות',
+'taskblocks' => 'משימה זו חוסמת משימות ×לו מלהסגר',
+'depadded' => 'תלות נוספה',
+'depaddedother' => 'משימה זו הוגדרה כתלות',
+'depremoved' => 'תלות הוסרה',
+'depremovedother' => 'משימה זו הוסרה מרשימת התלויות של משימה ×חרת',
+'showdetailserror' => 'משימה זו ××™× ×” קיימת, ×ו ש×ין לך הרש×ות לצפות בה.',
+'makeprivate' => 'הגדר כפרטי',
+'makepublic' => 'הגדר כציבורי',
+'taskmadeprivate' => 'המשימה הוגדרה כפרטית',
+'taskmadepublic' => 'הפרטיות הוסרה - משימה הוגדרה כפומבית',
+'confirmdeletecomment' => '×תה משוכנע שברצונך למחוק הערה זו? %s',
+'attachementswilldeleted' => 'כל ×”×§×‘×¦×™× ×”×¦×•×¨×¤×™× ×™×™×ž×—×§×• ×’× ×›×Ÿ!',
+'confirmdeleteattach' => '×תה משוכנע שברצונך למחוק קובץ מצורף ×–×”?',
+'selectedhistory' => '×ž×¦×™×’×™× ×¤×¨×˜×™ היסטוריה שנבחרה',
+'showallhistory' => 'הצג לשונית היסטוריה מל××” שוב',
+'hidethis' => 'הסתר שטח זה שוב',
+'mark100' => 'סמן משימה כמושלמת ב-100%',
+'watchtask' => 'עקוב ×חר משימה',
+'stopwatching' => 'הפסק מעקב',
+'commentlink' => 'קשר לתגובה זו',
+'submitreq' => 'שלח בקשה',
+'reasonforreq' => 'סיבת הבקשה',
+'pmreqdenied' => 'מנהל פרויקט דחה ×ת הבקשה',
+'taskpendingreq' => '×ž×ž×ª×™× ×™× ×œ×¤×¢×•×œ×ª מנהל פרויקט. הבט בלשונית ההיסטוריה לפרטי×.',
+'previoustask' => 'המשימה הקודמת',
+'nexttask' => 'המשימה הב××”',
+'duedate' => 'ת×ריך יעד',
+'attachnoperms' => '×™×©× × ×§×‘×¦×™× ×ž×¦×•×¨×¤×™× ×œ×”×¢×¨×” זו, ×בל ×ין לך הרש×ות לצפות בה×.',
+'linknoperms' => '×™×©× × ×§×™×©×•×¨×™× ×‘×”×¢×¨×” זו, ×בל ×ין לך הרש×ות לצפות בה×.',
+'open' => 'פתח',
+'depgraph' => 'הצג גרף תלויות',
+'reset' => '×פס',
+'selectusers' => 'בחר משתמשי×...',
+'addmetoassignees' => 'הוסף ×ותי להקצ×ות',
+'addedtoassignees' => 'משתמש התווסף לרשימת ההקצ×ות',
+'dependencygraph' => 'גרף תלויות',
+'attachanotherfile' => 'צרף קובץ ×חר',
+'OK' => '×וקי',
+'addvote' => 'הוסף הצבעה',
+'disable_lostpw' => 'בטל ×יחזור ×¡×™×¡×ž× ×©×בדה',
+'disable_changepw' => 'בטל יצירת/עריכת סיסמ×',
+'notifyfromfs' => 'הודעה ממערכת Flyspray',
+'autogenerated' => 'זוהי הודעה המיוצרת ×וטומטית. ×ין להשיב!',
+'forward' => 'קדימה',
+'previous' => 'הקוד×',
+'next' => 'הב×',
+'first' => 'ר×שון',
+'last' => '×חרון',
+'page' => 'עמוד %d מתוך %d',
+'search' => 'חפש',
+'alltasktypes' => 'כל סוגי המשימות',
+'allseverities' => 'כל החומרות',
+'alldevelopers' => 'כל המפתחי×',
+'notyetassigned' => '×œ× ×”×•×§×¦×ª×” עדיין',
+'allcategories' => 'כל הקטגוריות',
+'allstatuses' => 'כל הסטטוסי×',
+'allopentasks' => 'כל המשימות הפתוחות',
+'sortthiscolumn' => 'מיין לפי עמודה זו',
+'id' => 'מזהה',
+'project' => 'פרויקט',
+'dateopened' => 'נפתח',
+'progress' => 'התקדמות',
+'searchthisproject' => 'חפש בפרויקט זה',
+'dueanyversion' => 'יעד בכל גרסה',
+'anyversion' => 'מדווח בכל גרסה',
+'dueversion' => 'יעד בגרסה',
+'lastedit' => 'נערך ל×חרונה',
+'os' => 'מערכת הפעלה',
+'reportedin' => 'דווח ב',
+'taskrange' => 'מציג משימות %d - %d מתוך %d',
+'noresults' => 'החיפוש ×œ× ×”×—×–×™×¨ תוצ×ות.',
+'takeaction' => 'בצע פעולה',
+'watchtasks' => 'עקוב ×חר משימות נבחרות',
+'stopwatchingtasks' => 'הפסק מעקב ×חר משימות נבחרות',
+'assigntaskstome' => 'הקצה משימות נבחרות עבורי',
+'dueby' => 'יעד',
+'dueanytime' => 'יעד בכל רגע',
+'selectduedate' => 'בחר ת×ריך יעד',
+'toggleselected' => 'הפוך בחירה',
+'due' => 'יעד',
+'assignedtome' => 'הוקצתה לעצמי',
+'tasklist' => 'רשימת משימות',
+'dateclosed' => 'ת×ריך סגירה',
+'advanced' => 'מתקד×',
+'searchcomments' => 'חפש בהערות',
+'searchforall' => 'חפש ×ת כל המילי×',
+'anonusers' => '×ž×©×ª×ž×©×™× ×נונימיי×',
+'miscellaneous' => 'שונות',
+'users' => 'משתמשי×',
+'taskproperties' => 'הגדרות משימה',
+'selectsincedate' => 'בחר השתנתה מ××–',
+'changedsince' => 'השתנתה מ××–',
+'updatefs' => '×× × ×¢×“×›×Ÿ ×ת Flyspray.',
+'currentversion' => 'הגרסה הנוכחית ×”×™×',
+'latestversion' => 'הגרסה החדשה ביותר ×”×™×',
+'hidemessage' => '(הזכירו לי בהמשך)',
+'saveas' => 'שמור חיפוש בש×',
+'nosearches' => '×ין ×—×™×¤×•×©×™× ×©×ž×•×¨×™×',
+'saving' => 'שומר...',
+'votes' => 'הצבעות',
+'tovote' => 'הצבע',
+'allclosedtasks' => 'כל המשימות הסגורות',
+'password' => 'סיסמ×',
+'login' => 'התחברות!',
+'rememberme' => 'זכור ×ותי',
+'lostpassword' => 'שכחת סיסמ×?',
+'lostpwforfs' => '×¡×™×¡×ž× ×בודה עבור Flyspray',
+'lostpwmsg1' => "שלו×.\n\nשכחתי ×ת סיסמתי ",
+'lostpwmsg2' => ",×× × ×”× ×¤×™×§×• לי סיסמה חדשה.\n\n×©× ×ž×©×ª×ž×©: ",
+'regards' => 'בתודה,',
+'yourusername' => ' ×©× ×”×ž×©×ª×ž×© שלך ',
+'locale' => 'he-IL',
+'filenotexist' => 'קובץ ×œ× ×§×™×™×, ×ו ש×ין לך הרש×ות גישה עבורו.',
+'showtask' => 'הצג משימה',
+'now' => 'עכשיו',
+'go' => 'בצע!',
+'opentaskanon' => 'פתח משימה חדשה ×›×נונימי',
+'register' => 'הרשמה',
+'addnewtask' => 'הוסף משימה חדשה',
+'reports' => 'יומן ×ירועי×',
+'editmydetails' => 'ערוך ×ת ×”×¤×¨×˜×™× ×©×œ×™',
+'logout' => 'התנתקות',
+'disabledaccount' => 'החשבון שלך הושהה! מנתק ×ותך מהמערכת...',
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray is proudly sponsored by',
+'projects' => 'פרויקטי×',
+'allprojects' => 'כל הפרויקטי×',
+'selectproject' => 'עבור פרויקט:',
+'tasksall' => 'כל המשימות',
+'tasksassigned' => 'משימות המוקצות לי',
+'tasksreported' => 'משימות שדיווחתי',
+'taskswatched' => 'משימות ש×× ×™ עוקב ×חריהן',
+'mysearch' => '×”×—×™×¤×•×©×™× ×©×œ×™',
+'admintoolbox' => 'תיבת ×›×œ×™× ×œ× ×™×”×•×œ',
+'manageproject' => 'ניהול פרויקט',
+'permissions' => 'הצג הרש×ות',
+'hide' => 'הסתר',
+'pendingreq' => 'מנהל פרוייקט מבקש המתנה',
+'errorpage' => 'הדף ×œ× × ×ž×¦×. ×ולי חיפשת משימה ×œ× ×§×™×™×ž×ª, ×ו ש×ין לך הרש×ות לצפות בדף שרצית.',
+'permissionsforproject' => 'הרש×ות עבור ',
+'switchto' => 'עבור ל',
+'lastsearch' => 'חיפוש ×חרון',
+'modify' => 'עדכן',
+'noticefrom' => 'הודעה מ×ת',
+'hasopened' => 'פתח משימת Fltspray חדשה ×•×”×™× ×”×•×§×¦×ª×” עבורך:',
+'moreinfonew' => 'ניתן לקבל עוד ×¤×¨×˜×™× ×¢×œ הב××’ ×”×–×” בעמוד Flyspray:',
+'newtaskcategory' => 'נפתחה משימת Flyspray חדשה בקטגוריה זו',
+'categoryowner' => '×תה מקבל ×ת ×–×” ×›×™ ×תה ×¨×©×•× ×›×ž× ×”×œ הקטגוריה.',
+'tasksummary' => '×¡×™×›×•× ×ž×©×™×ž×”:',
+'newtaskadded' => 'המשימה שלך התווספה.',
+'summaryanddetails' => 'יש ×œ×ž×œ× ×’× ×¡×™×›×•× ×•×’× ×¤×¨×˜×™×.',
+'goback' => 'חזור.',
+'messagefrom' => 'זו הודעה ממערכת מעקב ב××’×™× Flyspray ב ',
+'hasjustmodified' => 'עדכן כרגע ×ת המשימה הב××”.',
+'changedfields' => 'שדות ששונו ×ž×¡×•×ž× ×™× ×‘×›×•×›×‘×™×•×ª (**)',
+'moreinfomodify' => 'ניתן לקבל עוד מידע על המשימה הזו בקישור הב×:',
+'nolongerassigned' => 'משימה זו כבר ×œ× ×ž×•×§×¦×™×ª עבורך. ×”×™× ×ž×•×§×¦×™×ª כעת עבור',
+'hasassigned' => 'הקצה עבורך ×ת משימת Flyspray הב××”: ',
+'taskupdated' => 'המשימה עודכנה.',
+'tasksupdated' => 'המשימות עודכנו.',
+'hasclosedassigned' => 'סגר ×ת משימת Flyspray הב××” שהוקצתה עבורך:',
+'unassigned' => '×œ× ×ž×•×§×¦×”',
+'hasclosed' => 'סגר ×ת המשימה הב××”.',
+'youonnotify' => '×תה מקבל ×–×ת ×ž×©×•× ×©×תה ברשימת המיודעי×.',
+'taskclosedmsg' => 'משימה נסגרה.',
+'returntotask' => 'חזרה לפרטי המשימה',
+'backtoindex' => 'חזרה לרשימת המשימות',
+'noclosereason' => '×œ× ×‘×—×¨×ª סיבה לסגירת המשימה.',
+'hasreopened' => 'פתח מחדש ×ת המשימה הב××” ש×תה סגרת:',
+'taskreopenedmsg' => 'משימה נפתחה מחדש.',
+'backtotask' => 'חזרה למשימה.',
+'commentaddedmsg' => 'הערה התווספה.',
+'commenttoassigned' => 'הוסיף הודעה למשימה שהוקצתה ×ליך:',
+'commenttotask' => 'הוסיף ×ת ההערה הב××” למשימה.',
+'nocommententered' => 'ב×מת חובה להוסיף הערה לפני ×©×œ×•×—×¦×™× ×¢×œ כפתור השליחה.',
+'fillinfields' => '×œ× ×ž×™×œ×ת ×ת כל השדות.',
+'notcurrentpass' => 'זו ×œ× ×”×¡×™×¡×ž× ×”× ×•×›×—×™×ª שלך!',
+'passchanged' => 'ססמתך שונתה.',
+'closewindow' => 'ניתן לסגור ×ת החלון.',
+'passnomatch' => 'הסיסמ×ות ×ינן זהות!',
+'usernametaken' => '×©× ×ž×©×ª×ž×© ×–×” כבר תפוס, יש לבחור ×©× ×חר.',
+'usernametakenbulk' => '×©× ×ž×©×ª×ž×© כבר תפוס',
+'newusercreated' => 'חשבון משתמש חדש נוצר.',
+'accountcreated' => 'חשבונך נוצר.',
+'newuserwarning' => '×©×™× ×œ×‘ שההגדרות הגלובליות יכולות להזדקק לכך שחשבונך ×™×ושר על ידי מנהל. ×× ×ינך יכול להרש×, זו כנר××” הסיבה.',
+'nomatchpass' => 'הסיסמ×ות ×œ× ×ª×•×מות.',
+'confirmwrong' => 'קוד ×ישור ×œ× × ×›×•×Ÿ!',
+'formnotcomplete' => 'הטופס ×œ× ×”×•×–×Ÿ במלו×ו.',
+'formnotnumeric' => '×”× ×ª×•× ×™× ×©×”×•×›× ×¡×• ××™× × ×ž×¡×¤×¨×™×™×!',
+'groupnametaken' => '×©× ×§×‘×•×¦×” ×–×” כבר תפוס.',
+'newgroupadded' => 'קבוצה חדשה נוספה.',
+'optionssaved' => 'הגדרות נשמרו.',
+'hasuploaded' => 'צירף קובץ חדש למשימה שהוקצתה:',
+'hasattached' => 'צירף קובץ למשימה הב××”.',
+'fileuploaded' => 'הקובץ הועלה לשרת.',
+'fileerror' => 'הייתה בעייה בהעל×ת הקובץ לשרת. ×ולי ההרש×ות על ספרית attachments ×ינן נכונות.',
+'contactadmin' => 'צור קשר ×¢× ×”×ž× ×”×œ לפרויקט ×–×”.',
+'selectfileerror' => '×œ× ×‘×—×¨×ª קובץ.',
+'userupdated' => 'פרטי משתמש עודכנו',
+'realandemail' => '×œ× ×”×–× ×ª ×ת ×”×¤×¨×˜×™× ×‘×©×“×•×ª ×©× ×•×›×ª×•×‘×ª דו×ל.',
+'groupupdated' => 'הגדרת קבוצה עודכנה.',
+'groupanddesc' => '×œ× ×”×–× ×ª ×ת ×©× ×”×§×‘×•×¦×”.',
+'fillallfields' => '×× × ×”×–×Ÿ ×ת כל השדות.',
+'listPmustN' => '"סדר" חיב להיות מספרי.',
+'listupdated' => 'הרשימה עודכנה.',
+'listitemadded' => 'פריט חדש התווסף לרשימה.',
+'relatedaddedmsg' => 'משימה קשורה התווספה לרשימה.',
+'relatederror' => 'משימה זו כבר נמצ×ת ברשימת המשימות הקשורות.',
+'relatedremoved' => 'משימה קשורה נמחקה מהרשימה.',
+'notifyadded' => 'משתמש התווסף לרשימת היידוע.',
+'notifyerror' => 'משתמש ×–×” כבר × ×ž×¦× ×‘×¨×©×™×ž×ª היידוע עבור משימה זו.',
+'notifyremoved' => 'משתמש הוסר מרשימת היידוע.',
+'editcommentsaved' => 'הערה מעודכנת נשמרה.',
+'commentdeletedmsg' => 'הערה נמחקה.',
+'gotonewtask' => 'עבור למשימה החדשה שיצרת',
+'projectcreated' => 'הפרויקט החדש שלך נוצר. ניתן לערוך ×ותו ב×זור ניהול הפרויקטי×',
+'customiseproject' => 'הת×מה ×ישית של הפרוייקט',
+'projectupdated' => 'העדפות הפרויקט עודכנו',
+'emptytitle' => 'הש×רת ×ת שדה כותרת הפרויקט ריק. ×× × ×—×–×•×¨ ותקן ×–×ת.',
+'loginbelow' => 'ניתן לנסות להתחבר עכשיו.',
+'attachmentdeletedmsg' => 'קובץ מצורף זה נמחקה',
+'reminderaddedmsg' => 'התזכורת שלך נוספה.',
+'reminderdeletedmsg' => 'התזכורת שנבחרה נמחקה.',
+'flyspraytask' => 'משימת Flyspray',
+'fieldsmissing' => 'שדות ×ž×¡×•×™×™×ž×™× ××™× × ×ž×›×™×œ×™× ×¢×¨×š ×ו ×ž×›×™×œ×™× ×¢×¨×š ×œ× ×ª×§×™×Ÿ.',
+'relatedinvalid' => '×ין כזו משימה.',
+'relatedproject' => 'משימה זו קשורה לפרויקט ×חר. להוסיף בכל מקרה?',
+'addanyway' => 'הוסף בכל ×–×ת',
+'cancel' => 'ביטול',
+'alreadyedited' => 'משימה זו נערכה על ידי מישהו ×חר לפני ששמרת. ×”×× ×œ×©×ž×•×¨ בכל ×–×ת ×ת השינויי×?',
+'saveanyway' => 'שמור ×ת ×”×©×™× ×•×™×™× ×‘×›×œ ×–×ת',
+'nouserselected' => '×œ× × ×‘×—×¨ משתמש. יש לבחור לפחות משתמש ×חד לפני ניסיון נוסף.',
+'groupswitchupdated' => 'קבוצות ×ž×©×ª×ž×©×™× ×¢×•×“×›× ×• בהצלחה.',
+'takenownershipmsg' => 'משימה זו מוקצית ×ליך כעת.',
+'adminrequestmade' => 'בקשתך נשלחה למנהל פרויקט.',
+'newdepnotify' => 'תלות חדשה נוספה למשימה הב××”:',
+'dependadded' => 'תלות משימה נוספה',
+'dependaddfailed' => ' ×רעה שגי××” בזמן הוספת התלות. ×× × ×‘×“×•×§ שהמשימה קיימת ו×ין חסימה הדדית.',
+'depremovedmsg' => 'תלות משימה הוסרה',
+'newdepis' => 'התלות החדשה ×”×™×',
+'magicurlsent' => 'הודעה נשלחה לכתובת ההתר×ות שלך. ×”×™× ×›×•×œ×œ×ª קישור שיוביל ×ותך לדף בו ×ª×©×œ×™× ×ž×©×™×ž×” זו.',
+'changefspass' => 'שינוי סיסמה',
+'magicurlmessage' => 'יש ללחוץ על הקישור ×”×‘× ×œ×©×™× ×•×™ הסיסמה:',
+'erroronform' => '×רעה שגי××” בשליחת הקובץ',
+'addressused' => 'This address has been used to register a Flyspray account. If you were not expecting this message, please ignore and delete it. Go to the following URL to complete your registration:',
+'confirmcodeis' => 'קוד ×”×ישור שלך הו×:',
+'codesent' => 'קוד ×”×ישור שלך נשלח. ×× × ×ž×œ× ×חר ההור×ות בהודעה זו.',
+'codenotsent' => '×œ× ×ž×¦×œ×™×— לשלוח ×ת הקוד, ×× × × ×¡×” שוב מ×וחר יותר.',
+'taskmadeprivatemsg' => 'משימה זו הוגדרה כפרטית',
+'taskmadepublicmsg' => 'משימה זו הוגדרה שוב כפומבית.',
+'realandnotify' => 'עליך ×œ×ž×œ× ×ת שדה ×”×©× ×”×ž×œ×, ו×ת שדה כתובת הדו×ל טו ×”-Jabber ID.',
+'pmreqdeniedmsg' => 'בקשת מנהל פרוייקט נדחתה',
+'massopsuccess' => 'קבוצת פעולות הצליחה - היכן שההרש×ות ×פשרו',
+'usernotexist' => 'משתמש ×–×” ×œ× ×§×™×™×',
+'commentattachperms' => '×ין ב×פשרותך למחוק הערה זו - ×ין הרש×ות למחיקת ×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'voterecorded' => 'הצבעתך נרשמה',
+'votefailed' => 'הצבעתך ×œ× ×™×›×•×œ×” להתווסף כרגע.',
+'createnewgroup' => 'צור קבוצה חדשה',
+'requiredfields' => 'שדות חובה ×ž×¡×•×ž× ×™× ×¢×',
+'addthisgroup' => 'הוסף קבוצה זו',
+'createnewproject' => 'צור פרוייקט חדש',
+'addnewproject' => 'הוס,ף פרוייקט חדש',
+'htmlallowed' => '×ין להשתמש ב HTML',
+'createthisproject' => 'צור פרוייקט זה',
+'inlineimages' => 'הצג תמונות מצורפות בתוך ההודעה',
+'createnewtask' => 'צור משימה חדשה בפרויקט:',
+'addanother' => 'הוסף עוד משימה ×חרי זו',
+'addthistask' => 'הוסף משימה זו',
+'notifyme' => 'יידע ×ותי בכל ×¤×¢× ×©×ž×©×™×ž×” זו מתעדכנת',
+'newtask' => 'משימה חדשה',
+'attachafile' => 'צרף קובץ',
+'registernewuser' => '×¨×™×©×•× ×ž×©×ª×ž×© חדש',
+'none' => 'לל×',
+'registeraccount' => '×¨×©×•× ×—×©×‘×•×Ÿ ×–×”',
+'registerbulkaccount' => '×¨×©×•× ×—×©×‘×•× ×•×ª',
+'both' => 'שניה×',
+'notifyfrom' => 'הודעות מ',
+'donotreply' => 'זוהי הודעה ×וטומטית, ×ין להשיב!',
+'disclaimer' => 'קיבלת הודעה זו מכייון שביקשת כך ממערכת מעקב ב××’×™× Flyspray. ×× ×œ× ×¦×™×¤×™×ª להודעה זו ×ו ×ינך מעוניין לקבל דו×ל בעתיד, יש ב×פשרותך לשנות ×ת הגדרות היידוע בקישור המופיע למעלה.',
+'userwho' => 'משתמש שביצע ×–×ת',
+'moreinfo' => 'עוד מידע ניתן ×œ×ž×¦×•× ×‘×§×™×©×•×¨ הב×:',
+'newtaskopened' => 'משימת Flyspray חדשה נפתחה, ×¤×¨×˜×™× ×œ×ž×˜×”.',
+'notify.taskclosed' => 'המשימה הב××” כעת סגורה:',
+'notify.taskreopened' => 'המשימה הב××” נפתחה מחדש:',
+'newdep' => 'למשימה הב××” יש תלות חדשה:',
+'notify.depremoved' => 'הורדה תלות עבור המשימה הב××”:',
+'olddepwas' => 'התלות הקודמת הייתה',
+'notify.commentadded' => 'נוספה הערה למשימה הב××”:',
+'commentis' => 'תוכן ההערה בהמשך.',
+'newattachment' => 'קובץ חדש צורף למשימה הב××”:',
+'detailsbelow' => '×”×¤×¨×˜×™× ×‘×”×ž×©×š.',
+'notify.relatedadded' => 'משימה קשורה חדשה נוספה למשימה הב××”:',
+'relatedis' => 'המשימה הקשורה ×”×™×',
+'assignedtoyou' => 'המשימה הב××” הוקצתה ×ליך:',
+'takenownership' => 'קיבלת בעלות על המשימה הב××”:',
+'requiresaction' => 'המשימה הב××” דורשת פעולה של מנהל פרויקט:',
+'requiresactionnotify' => 'המשימה דורשת פעולה של מנהל פרויקט',
+'pmdeny' => 'מנהל פרוייקט סירב לבקשה שהמתינה עבור המשימה הב××”:',
+'pmdenynotify' => 'מנהל פרוייקט סירב לבקשה',
+'fileaddedtoo' => 'קובץ ×חד ×ו יותר צורפו.',
+'taskwatching' => 'המשימה הזו בה ×תה צופה',
+'isdepfor' => 'זו תלות חדשה עבור',
+'denialreason' => 'סיבת הסירוב',
+'taskchanged' => 'המשימה הב××” שונתה. ×”×©×™× ×•×™×™× ×ž×¤×•×¨×˜×™× ×‘×”×ž×©×š. למידע ×ž×œ× ×¢×œ כל מה שהשתנה, בקר בקישור ולחץ על לשונית ההיסטוריה.',
+'useraddedtoassignees' => 'משתמש הוסיף עצמו לרשימת ×”×ž×©×ª×ž×©×™× ×©×ž×©×™×ž×” זו מוקצית ×ליה×.',
+'removeddepis' => 'התלות שהוסרה ×”×™×',
+'isnodepfor' => '××™× ×” תלות יותר עבור',
+'usergroups' => 'קבוצות משתמשי×',
+'pmtoolbox' => 'תיבת ×›×œ×™× ×©×œ מנהל פרויקט',
+'groupmanage' => 'ניהול קבוצה',
+'pendingrequests' => 'בקשות ממתינות',
+'reasongiven' => 'סיבה שניתנה',
+'nopendingreq' => '×ין בקשות מנהל פרוייקט ממתינות',
+'givereason' => 'תן סיבה',
+'catlisted' => 'עורך רשימת קטגוריות',
+'oslisted' => 'עורך רשימת מערכות הפעלה',
+'verlisted' => 'עורך רשימת גרס×ות',
+'tasktypeed' => 'עורך רשימת סוגי משימות',
+'resed' => 'עורך רשימת החלטות',
+'deny' => 'דחה',
+'notifiedwhen' => 'ייודע ×›×שר',
+'onlynewtasks' => 'משימות חדשות נפתחו',
+'allevents' => 'כל ×ירוע בכל משימה',
+'feeds' => 'פידי×',
+'feeddescription' => 'תי×ור פיד',
+'feedimgurl' => 'קישור לתמונה עבור פיד (הש×ר ריק ×œ×œ× ×ª×ž×•× ×”)',
+'notifysubject' => '× ×•×©× ×œ×”×•×“×¢×•×ª',
+'notifysubjectinfo' => '(%p = כותרת פרויקט, %s = ×¡×™×›×•× ×”×•×“×¢×”, %t = מספר משימה, %a = פעולה, %u = משתמש)',
+'priority6' => 'גובה מ×ד',
+'priority5' => 'גבוה',
+'priority4' => 'בינוני',
+'priority3' => 'נמוך',
+'priority2' => 'נמוך מ×ד',
+'priority1' => '×œ× ×™×‘×•×¦×¢',
+'sendcode' => 'שלח קוד!',
+'entercode' => '×× × ×”×–×Ÿ ×ת קוד ×”×ישור שהתקבל בהודעת המייל. כמו כן הזן ×ת ×”×¡×™×¡×ž× ×”×¨×¦×•×™×”.',
+'confirmationcode' => 'קוד ×ישור',
+'registererror' => '×•×•×“× ×©×ž×™×œ×ת ×ת כל שדות החובה, וכן שמיל×ת מידע ×ž×“×•×™×™×§×™× ×œ×’×‘×™ סוג ההודעות שברצונך לקבל.',
+'validusername' => '(רק ×ª×•×™× ××œ×¤× ×•×ž×¨×™×™× ×•- _ מותרי×)',
+'validemail' => '(השתמש ב-; להפריד בין מספר כתובות דו×ל)',
+'emailtakenbulk' => 'כתובת דו×ל תפוסה',
+'emailtaken' => 'כתובת זו כבר כבר תפוסה, × × ×œ×‘×—×•×¨ ×חרת.',
+'note' => '<strong>הערה:</strong> לפני יצירת החשבון, קוד ×ישור יישלח בהת×× ×œ×¦×•×¨×ª היידוע שבחרת למעלה.</br>×× ×תה מזין מידע שגוי, ×œ× ×ª×§×‘×œ ×ת הקוד.',
+'changelog' => 'יומן שינויי×',
+'changeloggen' => 'מחולל יומן שינויי×',
+'listfrom' => 'הצג רשומות יומן ×©×™× ×•×™×™× ×”×—×œ מ-',
+'to' => 'עד',
+'oldestfirst' => 'הישן ביותר קוד×',
+'recentfirst' => 'העדכני ביותר קוד×',
+'severityrep' => 'דו״ח חומרה',
+'totalopen' => 'סך משימות פתוחות',
+'age' => 'גיל',
+'agerep' => 'דו״ח גיל',
+'eventsrep' => 'דו״ח ×ירועי×',
+'events' => '×ירועי×',
+'Tasks' => 'משימות',
+'opened' => 'פתוחות',
+'edited' => 'ערוכות',
+'assigned' => 'מוקצות',
+'within' => 'בתוך',
+'pastday' => '×”×™×•× ×”×חרון',
+'pastweek' => 'השבוע ×”×חרון',
+'pastmonth' => 'החודש ×”×חרון',
+'pastyear' => 'השנה ×”×חרונה',
+'nolimit' => '×œ×œ× ×”×’×‘×œ×”',
+'from' => 'מ-',
+'duein' => 'יעד',
+'selectfromdate' => 'בחר מת×ריך',
+'selecttodate' => 'בחר עד ת×ריך',
+'showvoters' => 'הצג/הסתר מצביעי×',
+'roadmap' => 'מפת דרכי×',
+'roadmapfor' => 'מפת ×“×¨×›×™× ×œ×’×¨×¡×”',
+'tasks' => 'משימות',
+'completed' => 'הושלמו.',
+'opentasks' => 'משימות פתוחות',
+'of' => '% מתוך',
+'severity5' => 'קריטי',
+'severity4' => 'גבוה',
+'severity3' => 'בינוני',
+'severity2' => 'נמוך',
+'severity1' => 'נמוך מ×ד',
+'Redirect' => 'להעביר ל',
+'redirectmsg' => 'If your browser does not support meta redirection please click %sHERE%s to be redirected',
+'allowclosedcomments' => '×פשר הערות על משימות סגורות',
+'comment' => 'הערה',
+'editowncomments' => 'עריכת הערות של עצמי',
+'reopened' => 'נפתחה מחדש',
+'loading' => 'טוען...',
+'notifyown' => 'יידוע על ×©×™× ×•×™×™× ×¢×¦×ž×™×™×',
+'youremail' => 'כתובת הדו×ל שלך',
+'thankyouforbug' => 'תודה על הדיווח. ניתן לר×ות ×ת המשימה ולעקוב ×חר ביצועה בקישור הב×:',
+'anonuser' => 'משתמש ×נונימי',
+'conflict' => 'התנגשות',
+'file' => 'קובץ',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'גודל',
+'projectgroup' => 'קבוצת פרויקט',
+'profile' => 'פרופיל:',
+'viewprofile' => 'הצג פרופיל',
+'regdate' => '× ×¨×©× ×ž×ª×ריך',
+'tasksopened' => 'משימות פתוחות',
+'replyto' => 'השב ×ל',
+'notifytypes' => 'סוגי הודעות',
+'pm.taskchanged' => 'משימה שונתה',
+'pm.taskreopened' => 'משימה נפתחה מחדש',
+'pm.depadded' => 'תלות נוספה',
+'pm.depremoved' => 'תלות הוסרה',
+'pmrequest' => 'בקשת PM',
+'pmrequestdenied' => 'בקשת PM נדחתה',
+'newassignee' => 'הקצ××” חדשה',
+'revdepadded' => 'תלות לכיוון השני התווספה',
+'revdepaddedremoved' => 'תלות לכיוון השני הוסרה',
+'assigneeadded' => 'הקצ××” התווספה',
+'addusergroup' => 'הוסף משתמש לקבוצה זו',
+'groupmembers' => 'חברי הקבוצה',
+'deleteuser' => 'מחק משתמש זה',
+'userdeleted' => 'משתמש נמחק',
+'autoassign' => 'הקצ×ת משימה ×וטומטית לבעל הקטגוריה',
+'ssl' => 'SSL',
+'updatewrong' => "You have the update check feature enabled, but an error ocurred while trying\n to contact the update server, either your host do not allow outbound connections\n or the error was caused by a network problem.\n Please visit the flyspray website to make sure you are running the latest version.",
+'deleteproject' => 'מחק ×ת הפרויקט והעבר ×ת תוכנו ל',
+'projectdeleted' => 'הפרויקט נמחק בהצלחה',
+'feedforall' => 'פיד עבור כל הפרויקטי×',
+'usercreated' => 'משתמש נוצר',
+'created' => 'נוצר',
+'deleted' => 'נמחק',
+'userid' => 'מזהה משתמש',
+'editassignments' => 'עריכת הקצ×ות',
+'preview' => 'תצוגה מוקדמת',
+'anyprogress' => 'התקדמות כלשהי',
+'tasksrelated' => 'משימות קשורות למשימה זו',
+'duplicatetasks' => 'כפילויות של משימה זו',
+'databasemodfailed' => 'עדכון בסיס × ×ª×•× ×™× × ×›×©×œ. סיבות ×פשריות הן הרש×ות ×œ× ×ž×¡×¤×§×•×ª.',
+'frequency' => 'תדירות',
+'newuserregistered' => 'משתמש חדש × ×¨×©× ×‘×ž×¢×¨×›×ª Flyspray. פרטי המשתמש ×”×:',
+'newuserregisterednotify' => 'משתמש חדש נרש×',
+'notify_registration' => 'הודע ×œ×ž× ×”×œ×™× ×¢×œ ×¨×™×©×•× ×ž×©×ª×ž×© חדש',
+'textversion' => 'גרסה טקסטו×לית',
+'onlyprimary' => 'משימות ש×ינן חוסמות משימות ×חרות',
+'switch' => 'מעבר',
+'max' => 'לכל היותר',
+'dates' => 'ת×ריכי×',
+'selectduedatefrom' => 'יעד מ-',
+'selectduedateto' => 'עד',
+'selectsincedatefrom' => 'השתנה מ-',
+'selectsincedateto' => 'עד',
+'selectdate' => 'בחר ת×ריך',
+'selectopenedfrom' => 'נפתח מ-',
+'selectopenedto' => 'עד',
+'selectclosedfrom' => 'נסגר מ-',
+'selectclosedto' => 'עד',
+'startat' => 'התחיל ב-',
+'hasattachment' => 'מכיל ×§×‘×¦×™× ×ž×¦×•×¨×¤×™×',
+'private' => 'פרטי',
+'watching' => 'עוקב',
+'alreadyvotedthistask' => 'הצבעת עבור המשימה',
+'alreadyvotedthisday' => 'כבר הצבעת היו×',
+'visibility' => 'נר×ות',
+'public' => 'פומבי',
+'leaveemptyauto' => 'הש×ר ×ת שדות ×”×¡×™×¡×ž× ×¨×™×§×™× ×× ×‘×¨×¦×•× ×š ×©×”×¡×¡×ž× ×ª×™×™×•×¦×¨ ×וטומטית.',
+'novalidemail' => '×œ× ×”×–× ×ª כתובת דו×ל תקינה.',
+'novalidjabber' => '×œ× ×”×–× ×ª כתובת Jabber תקינה.',
+'missingrequired' => '×œ× ×ž×•×œ×ו כל שדות החובה.',
+'entervalidusername' => '×× × ×”×–×Ÿ ×©× ×ž×©×ª×ž×© ×•×©× ×ž×œ× ×ª×§×™× ×™×.',
+'couldnotaddusernotif' => '×œ× × ×™×ª×Ÿ להוסיף משתמש ×–×” לרשימת היידוע.',
+'defaulttask' => 'תי×ור משימה ברירת מחדל',
+'all' => 'הכל',
+'events.useraddedtoassignees'=> 'משתמש התווסף להקצ×ות',
+'eventlog' => 'יומן ×ירועי×',
+'assignmentchanged' => 'הקצ××” שונתה',
+'detailedinfo' => 'מידע מפורט',
+'All' => 'הכל',
+'tasksireported' => 'משימות שדיווחתי',
+'recentlyopened' => 'נפתחו ל×חרונה',
+'stats' => 'סטטיסטיקות',
+'totaltasks' => 'סך כל המשימות',
+'mostwanted' => 'משימות מועדפות',
+'defaultentry' => 'עמוד נחיתה ברירת מחדל',
+'toplevel' => 'תצוגה עילית',
+'overview' => 'מבט כללי',
+'error#' => 'שגי××” #',
+'error1' => '×ין לך מספיק הרש×ות לר×ות ×ת הקובץ המצורף.',
+'error3' => 'פעולה חוזרת, מנווט לדף הבית.',
+'error4' => '×ין לך הרש×ות מנהל.',
+'error5' => 'משתמש ×–×” ×œ× ×§×™×™× ×‘×”×ª×§× ×”.',
+'error6' => '×יזור ניהול ×œ× ×ª×§×™×Ÿ.',
+'error7' => 'כניסה נכשלה, ×¡×™×¡×ž× ×©×’×•×™×”!',
+'error71' => 'החשבון ננעל למשך %d דקות בגלל נסיונות כניסה ×¨×‘×™× ×ž×“×™!',
+'error8' => '×œ× ×”×–× ×ª ×’× ×©× ×ž×©×ª×ž×© ×•×’× ×¡×™×¡×ž×.',
+'error9' => 'משימה ×œ× ×§×™×™×ž×ª ×ו ש×ין הרש×ות לר×ות ×ותה.',
+'error10' => 'משימה זו ×œ× ×§×™×™×ž×ª.',
+'error101' => '×ין לך הרש×ות לר×ות משימה זו.',
+'error102' => '×ין לך הרש×ות לר×ות משימה זו, כניסה למערכת יכולה לעזור.',
+'error11' => '×ין הרש×ות לערוך הערה זו.',
+'error12' => 'מפתח ×§×¡×ž×™× ×œ× ×ª×§×™×Ÿ! ×תה משוכנע שקיבלת ×ותו מהודעת ×”×ישור שלך?',
+'error13' => '×œ×ž×©×ª×ž×©×™× ×× ×•× ×™×ž×™× ×ין פרופיל.',
+'error14' => '×ין לך מספיק הרש×ות ליצור קבוצה חדשה.',
+'error15' => '×ין לך מספיק הרש×ות לפתוח משימה.',
+'error16' => '×ינך מנהל פרויקט.',
+'error17' => '×יזור PM ×œ× ×ª×§×™×Ÿ.',
+'error18' => 'קישור ×§×¡× ×©×’×•×™.',
+'error19' => 'משתמש ×–×” ×œ× ×§×™×™× ×‘×”×ª×§× ×”.',
+'error20' => 'עדכון מסד × ×ª×•× ×™× ×œ× ×ª×§×™×Ÿ.',
+'error21' => 'דו×ל ×חד ×ו יותר נכשלו בשליחה. בדוק ×ת ההגדרות שלך.',
+'error22' => 'הרשמת משתמש חדש ××™× ×” מ×ופשרת.',
+'error23' => 'משתמש ×ו קבוצה ×œ× ×ž××•×¤×©×¨×™× ×œ×›× ×™×¡×” למערכת.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'מפת ×“×¨×›×™× ×–×ž×™× ×” רק לפרוייקט מסויי×.',
+'error26' => 'ספק oauth ×œ× × ×ª×ž×š.',
+'error27' => 'כניסה למערכת נכשלה. ×פשר לנו לר×ות ×ת כתובת הדו×ל שלך.',
+'error28' => '×ין לך הרש×ות להכנס ל×יזור ×”×–×”.',
+'done' => 'בוצע',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => '×œ× × ×™×ª×Ÿ למחוק ×ת הפרויקט.',
+'GMT' => 'GMT',
+'timezone' => '×זור זמן',
+'accept' => '×שר',
+'reasonfordeinal' => 'סיבת דחיה',
+'pruneclosedlinks' => 'נפה ×§×™×©×•×¨×™× ×¡×’×•×¨×™×',
+'pruneclosedtasks' => 'נפה משימות סגורות',
+'pagegenerated' => 'דף ותמונה ×ž×™×•×¦×¨×™× ×ª×•×š %d שניות',
+'pruninglevel' => 'מנפה רמה',
+'lastuser' => '×œ× × ×™×ª×Ÿ למחוק ×ת המשתמש ×”×חרון.',
+'allprivate' => 'כל ×”×¤×¨×•×™×™×§×˜×™× ×¤×¨×˜×™×™×.',
+'deletegroup' => 'מחק קבוצה זו והעבר ×ž×©×ª×ž×©×™× ×ל',
+'parent' => '×ב',
+'ordertip' => 'הסדר בו יופיעו ×¤×¨×™×˜×™× ×לו ברשימה',
+'showtip' => 'הצג פריט זה ברשימה',
+'deletetip' => 'מחק פריט זה מהרשימה',
+'del' => 'מחק',
+'request1' => 'הוגשה בקשה לסגירת משימה.',
+'request2' => 'הוגשה בקשה לפתיחת המשימה מחדש.',
+'allpriorities' => 'כל הקדימויות',
+'noroadmap' => '×œ× ×§×™×™×ž×•×ª מפות ×“×¨×›×™× (×ין גרס×ות ״עתיד״ לפרוייקט)',
+'expand' => 'הרחב',
+'collapse' => 'סגור',
+'expandall' => 'הרחב הכל',
+'collapseall' => 'סגור הכל',
+'minpwsize' => '×ורך ×¡×™×¡×ž× ×œ×¤×—×•×ª 5 תוי×',
+'passwordtoosmall' => '×¡×™×¡×ž× ×§×¦×¨×” מדי.',
+'accountwaslocked' => 'חשבונך ננעל עקב נסיונות כניסה ×›×•×©×œ×™× ×œ×ž×¢×¨×›×ª ×¨×‘×™× ×ž×“×™.',
+'failedattempts' => 'היו %d נסיונות כניסה כושלי×.',
+'groupnotexist' => 'קבוצה שנבחרה ×œ× ×§×™×™×ž×ª בפרוייק ×–×”.',
+'searchindetails' => 'פרטי חיפוש',
+'showasassignees' => 'הצג כהקצ×ות',
+'find' => 'חפש',
+'tls' => 'TLS',
+'isadmin' => '×”×× ×ž× ×”×œ',
+'addvotes' => 'הוסף הצבעות',
+'removevote' => 'הסר הצבעה',
+'voteremoved' => 'הצבעתך הוסרה',
+'voteremovefailed' => '×œ× × ×™×ª×Ÿ להסיר הצבעתך כעת.',
+'connectedtasks' => 'משימות מקושרות:',
+'taskdependencies' => 'תלויות במשימה',
+'viewgraph' => 'הצגת גרף',
+'notaskdependencies' => 'משימה זו ××™× ×” תלויה במשימות ×חרות.',
+'dependson' => 'תלויה ב-',
+'blocks' => 'חוסמת',
+'newdependency' => 'תלות חדשה:',
+'nouserstoadd' => '×ין ×ž×©×ª×ž×©×™× ×œ×”×•×¡×¤×”. ×× × ×•×•×“× ×©×©×, ×©× ×ž×©×¦×ž×© ודו×ל ×ž×•×–× ×™× ×œ×›×œ משתמש.',
+'dispintro' => 'הצג הודעת פתיחה עיקרית',
+'mainmessage' => 'הודעת פתיחה עיקרית',
+'setsupertask' => 'הגדר מ×פיין של משימת-על:',
+'supertaskmodified' => 'מ×פיין של משימת-על עודכן',
+'set' => 'הגדר',
+'supertask' => 'משימת-על',
+'setparent' => 'הגדר מ×פיין משימת ×ב של משימה זו',
+'selfsupertasknotallowed' => 'מ×פיין משימת-על ×œ× ×™×›×•×œ להיות ×–×”×” למ×פיין משימה עצמית',
+'quickaction' => 'פעולות מהירות',
+'updateselectedtasks' => 'עדכן משימות בחורות',
+'notspecified' => '×œ× ×ž×¦×•×™×™×Ÿ',
+'editselectedtasks' => 'ערוך משימות בחורות',
+'information' => 'מידע',
+'taskclosedisabled' => 'סגירת משימה כרגע ×œ× × ×™×ª× ×ª בגלל שהמשימות התלויות הב×ות עדיין פתוחות:-',
+'daysleft' => '×™×ž×™× × ×•×ª×¨×•',
+'dayoverdue' => '×™×ž×™× ×ž×¢×‘×¨ ליעד',
+'duetoday' => 'מיועד להיו×.',
+'daysbeforealert' => '×™×ž×™× ×œ×¤× ×™ התר××”.',
+'associatedsubtask' => 'קישר תת-משימה #FS בהצלחה',
+'associatesubtask' => 'קשר מ×פיין תת-משימה למשימה זו',
+'subtaskid' => 'מ×פיין תת-משימה',
+'subtaskalreadyhasparent' => 'לתת-המשימה שהזנת כבר יש משימת ×ב, ×× × ×‘×˜×œ קישור ×–×” לפני הקישור החדש.',
+'subtaskisparent' => 'תת-המשימה שהזנת ×”×™× ×ž×©×™×ž×ª ×”×ב של משימה זו. תת-משימה ×œ× ×ž×§×•×©×¨×ª.',
+'subtasknotexist' => 'תת-המשימה שהזנת ×œ× ×§×™×™×ž×ª.',
+'subtaskremovedmsg' => 'תת-המשימה הוסרה בהצלחה',
+'subtaskadded' => 'תת-משימה התווספה',
+'subtaskremoved' => 'תת-משימה הוסרה',
+'addnewsubtask' => 'הוסף תת-משימה חדשה',
+'hidesubtasks' => '×”×—×‘× ×ª×ª×™-משימות',
+'voteforthistask' => 'הצבע למשימה זו',
+'watchthistask' => 'עקוב ×חר משימה זו',
+'privatethistask' => 'הפוך משימה זו לפרטית',
+'adddependenttask' => 'קשר משימה תלויה',
+'associatetaskid' => 'מ×פיין תת-משימה',
+'parenttaskid' => 'מ×פיין משימת ×ב',
+'invalidsupertaskid' => 'מ×פיין משימת ×ב ×œ× ×ª×§×™×Ÿ',
+'supertaskadded' => 'משימת-על התווספה',
+'supertaskremoved' => 'משימת-על הוסרה',
+'effort' => 'מ×מץ',
+'efforttracking' => 'מעקב מ×מץ',
+'useeffort' => 'הפרוייקט משתמש במעקב מ×מץ',
+'estimatedeffort' => 'הערכת מ×מץ',
+'totalestimatedeffort' => 'סך כל מ×מץ מוערך',
+'currenteffortdone' => 'מ×מץ עכשווי הושל×',
+'starteffort' => 'התחל מעקב',
+'endeffort' => 'הפסק מעקב',
+'cleareffort' => 'נקה מעקב',
+'addeffort' => 'הוסף מ×מץ',
+'manualeffort' => 'הוסף מ×מץ ידנית (H:M)',
+'efforttrackingstarted' => 'מעקב מ×מץ הומשך עבור משימה זו.',
+'efforttrackingnotstarted'=> '×œ× ×ž×¡×•×’×œ להתחיל מעקב על משימה שכבר יש בה מעקב.',
+'efforttrackingstopped' => 'מעקב מ×מץ ×”×¡×ª×™×™× ×œ×ž×©×™×ž×” זו.',
+'efforttrackingcancelled' => 'מעקב מ×מץ התבטל למשימה זו.',
+'efforttrackingadded' => 'מ×מץ ידני × ×¨×©× ×œ×ž×©×™×ž×” זו.',
+'trackinginprogress' => 'מעקב בתהליך',
+'viewestimatedeffort' => 'יכול לר×ות מעקב מ×מץ',
+'viewcurrenteffortdone' => 'יכול לר×ות מ×מץ ×©×”×•×©×œ× ×¢×“ ×›×”',
+'trackeffort' => 'יכול לעקוב ×חר מ×מץ.',
+'invalideffort' => 'המ×מץ שהוזן ×œ× ×ª×§×™×Ÿ. חייב להיות מספרי.',
+'showpass' => 'הצג סיסמ',
+'chooseafile' => '×× × ×‘×—×¨ קובץ!',
+'incorrectfiletype' => 'סוג קובץ ×œ× ×ª×§×™×Ÿ. סיומות מותרות: jpg, jpeg, gif, png.',
+'addmultipletasks' => 'הוסף מספר משימות',
+'pendingnewuserrequest' => 'בקשת משתמש חדש ממתינה',
+'adminrequestswaiting' => 'בקשות מנהל ממתינות',
+'clicktoedit' => 'הקלק על כל שדה לעריכה מהירה',
+'confirmedit' => '×שר',
+'regapprovedbyadmin' => 'הרשמות מ××•×©×¨×™× ×¢×œ ידי ×ž× ×”×œ×™× (ביטול קוד ×ישור)',
+'activity' => 'פעילות',
+'myactivity' => 'פעילות שלי',
+'emailverificationwrong' => '×ישור הדו×ל ×ינו תו×× ×œ×›×ª×•×‘×ª הדו×ל הרשומה',
+'verifyemailaddress' => '×שר כתובת דו×ל',
+'hideemails' => '×”×—×‘× ×›×ª×•×‘×•×ª דו×ל של משתמשי×',
+'hidemyemail' => '×”×—×‘× ×ת כתובת הדו×ל שלי',
+'exporttasklist' => '×™×™×¦× ×¨×©×™×ž×ª משימות',
+'onedecimal' => 'נקודה עשרונית ×חת',
+'manday' => '×™×•× ×ד×',
+'mandays' => 'ימי ×ד×',
+'mandayabbrev' => '×™×´×',
+'hourspermanday' => 'שעות ×œ×™×•× ××“× ×חד (HH:mm)',
+'itemexists' => 'פריט %s כבר ×§×™×™× ×‘×ž×¡×“ הנתוני×.',
+'categoryitemexists' => 'פריט %s כבר ×§×™×™× ×‘×ž×¡×“ ×”× ×ª×•× ×™× ×ª×—×ª קטגוריה %s.',
+'pageswelcomemsg' => '×“×¤×™× ×‘×”× ×™×© להר×ות הודעת פתיחה ר×שית',
+'pagesintromsg' => '×“×¤×™× ×‘×”× ×™×© להר×ות הודעת פיחה',
+'activeoauths' => 'ספקי oauth פעילי×',
+'onlyoauthreg' => '×שר רק הרשמות Oauth',
+'estimatedeffortformat' => 'פורמט תצוגת הערכת מ×מץ',
+'currenteffortdoneformat' => 'פורמט תצוגת מ×מץ שהושל×',
+'minute' => 'דקה',
+'minutes' => 'דקות',
+'minuteplural' => 'דקות',
+'minutesingular' => 'דקה',
+'minuteabbrev' => 'ד',
+'hourplural' => 'שעות',
+'hoursingular' => 'שעה',
+'hourabbrev' => 'ש',
+'estimatedeffortopen' => 'הערכת מ×מץ למשימות פתוחות',
+'currenteffortdoneopen' => 'מ×מץ שהושקע במשימות פתוחות',
+'signinwith' => 'הכנס ×¢× %s',
+'canviewroadmap' => 'יכול לר×ות מפת דרכי×',
+'enableavatars' => '×פשר ×וו×טרי×',
+'maxavatarsize' => 'גודל ×וו×טר מירבי בפיקסלי×',
+'taskhassubtask' => 'למשימה זו יש ×ת תת-המשימה הב××”',
+'taskhassubtasks' => 'למשימה זו יש ×ת תת-המשימות הב×ות',
+'translations' => 'תרגומי×',
+'translate' => 'תרג×',
+'taskdescription' => 'תי×ור משימה',
+'notaskdescription' => '×ין תי×ור משימה',
+'pleaseselect' => '×× × ×‘×—×¨',
+'closeselectedtasks' => 'סגור משימות בחורות',
+'closetasks' => 'סגור משימות',
+'hintforbulkimport' => "<b>Tips for bulk importing:</b>\n<ol>\n<li>Copy and paste from an excel spreadsheet or CSV by pasting one entire column.</li>\n<li>Currently you can only paste Summary and Details.</li>\n<li>There are suggestions when you assign to someone, and to no-one if there is no matched name.</li>\n</ol>",
+'taskissubtaskof' => 'משימה זו ×”×™× ×ª×ª-משימה של',
+'applyfirstline' => 'הפעל על שורה ר×שונה',
+'addmorerows' => 'הוסף עוד שורות',
+'addtasks' => 'הוסף משימות',
+'massopsdisabled' => 'Sorry, bulk editing is currently disabled for Flyspray 1.0. We plan to finish implementation for a later release of Flyspray. You can enable them in source code again at your own risk, but read the comments there before doing it. ',
+'viewroadmap' => 'יכול לר×ות ×ת מפת הדרכי×',
+'nosuicide' => 'Dear user, my program doesn\'t allow you to destroy your access to Flyspray by disabling your own account or switching your own group. The empathic brother of HAL9000 ',
+'movingtodifferentproject'=> 'Moving a task that has either a parent or subtasks to a different project is not allowed. You must break the connection between them first. ',
+'musthavesameproject' => '×ב ותת-משימה ×—×™×™×‘×™× ×œ×”×©×ª×™×™×š ל×ותו פרוייקט.',
+'defaultorderby' => 'מיין ×ת רשימת המשימות בברירת מחדל לפי',
+'viewowntasks' => 'ר××” ×ת המשימות שלך',
+'viewgroupstasks' => 'ר××” משימות קבוצות',
+'urlrewriting' => 'שיכתוב הכתובת',
+'enablehtaccess' => 'Please enable your .htaccess file at Flyspray root before turning url rewriting on ',
+'nomodrewrite' => 'Mod rewrite doesn\'t seem to be available on this server, sorry but I can\'t turn url rewriting on ',
+'on' => 'מופעל',
+'off' => 'מכובה',
+'defaultorderbydirection' => 'מיון ברירת מחדל לפי כיוון',
+'ascending' => 'עולה',
+'descending' => 'יורד',
+'myassignedtasks' => 'המשימות המוקצות לי',
+'commentedon' => 'העיר על',
+);
+
+?>
diff --git a/lang/hu.php b/lang/hu.php
new file mode 100644
index 0000000..73d8a18
--- /dev/null
+++ b/lang/hu.php
@@ -0,0 +1,851 @@
+<?php
+
+$translation = array(
+'edituser' => 'Felhasználó szerkesztése',
+'username' => 'Felhasználói név',
+'realname' => 'Teljes név',
+'emailaddress' => 'Email cím',
+'jabberid' => 'Jabber azonosító',
+'notifytype' => 'Értesítés típusa',
+'group' => 'Csoport',
+'accountenabled' => 'Fiók engedélyezve',
+'updatedetails' => 'Részletek frissítése',
+'setglobally' => 'Ezt a beállítást globálisan kell beállítani.',
+'usergroupmanage' => 'Felhasználó és csoport kezelése',
+'newuser' => 'Új felhasználó regisztrálása',
+'newgroup' => 'Új csoport regisztálása',
+'yes' => 'Igen',
+'no' => 'Nem',
+'editgroup' => 'Csoport szerkesztése',
+'groupname' => 'Csoport neve',
+'description' => 'Leírás',
+'admin' => 'Admin csoport',
+'opennewtasks' => 'Új hibajegy megnyitása',
+'modifytasks' => 'Meglévő hibajegy módosítása',
+'addcomments' => 'Megjegyzés hozzáadása',
+'attachfiles' => 'Fájl csatolása',
+'vote' => 'Szavazás',
+'groupenabled' => 'Tagok beléphetnek',
+'groupopen' => 'Tagok beléphetnek',
+'tasktypelist' => 'Hibajegy típusok listája',
+'categorylist' => 'Kategóriák listája',
+'oslist' => 'Rendszerek listája',
+'resolutionlist' => 'Megoldások listája',
+'versionlist' => 'Változatok listája',
+'severitylist' => 'Súlyosságok listája',
+'listnote' => 'Megjegyzés: A "megjelenít" kijelölőnégyzet törlése megszünteti az adott elem használatát, így ez a módosítás megváltoztathat pár hibajegyet, ahol ez használatban van. A "név" mező megváltozatása az összes olyan hibajegyben megváltozik, ahol ez használatban van. A nem törölhető bejegyzések vagy törlésre védettek, vagy pedig szükségesek a helyes működéshez, illetve már felhasználásra kerültek.',
+'name' => 'Név',
+'order' => 'Sorrend',
+'back' => 'Vissza',
+'text' => 'Szöveg',
+'highlight' => 'Megjelöl',
+'show' => 'Megjelenít',
+'owner' => 'Tulajdonos',
+'selectowner' => 'Tulajdonos kiválasztása',
+'update' => 'Alkalmaz',
+'addnew' => 'Új hozzáadása',
+'flysprayprefs' => 'Alkalmazás beállításai',
+'projecttitle' => 'Projekt címe',
+'baseurl' => 'Az alkalmazás Base URL-je',
+'replyaddress' => 'Válasz email-cím az értesítéshez',
+'themestyle' => 'Téma / Stílus',
+'language' => 'Nyelv',
+'anonview' => 'Névtelen felhasználók megnézhetik ezt a hibajegyet',
+'allowanon' => 'Névtelen felhasználók megnyithatják a hibajegyet',
+'never' => 'Soha',
+'anonymously' => 'Névtelenül',
+'afterregister' => 'Csak regisztráció után',
+'spamproof' => 'Megerősítési kód engedélyezése új felhasználó regisztrációjakor',
+'anongroup' => 'Az új felhasználó csoportja regisztrációjakor',
+'groupassigned' => 'E csoport tagjai rendelhetőek hozzá a hibajegyekhez',
+'forcenotify' => 'Hibajegy értesítés kikényszerítése:',
+'neversend' => 'Sose küld',
+'userchoose' => 'Felhasználó kiválaszthatja',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Az alapértelmezett kategória tulajdonosa',
+'noone' => 'Senki',
+'jabbernotify' => 'Jabber értesítés',
+'jabberserver' => 'Szerver',
+'jabberport' => 'Port',
+'jabberuser' => 'Felhasználói név',
+'jabberpass' => 'Jelszó',
+'saveoptions' => 'Alkalmaz',
+'editcomment' => 'Megjegyzés szerkesztése',
+'commentby' => 'Comment by',
+'saveeditedcomment' => 'Szerkesztett megjegyzés mentése',
+'projectprefs' => 'Projekt beállításai',
+'pagetitle' => 'Lap címe',
+'defaultproject' => 'Alapértelmezett projekt',
+'projectlists' => 'Projektek listája',
+'showlogo' => 'Logo mutatása',
+'intromessage' => 'Bemutató üzenet',
+'isactive' => 'A projekt aktív',
+'createproject' => 'Új projekt létrehozása',
+'nopermission' => 'Nincs hozzáférése ehhez az oldalhoz.',
+'listordertip' => 'The order these items will appear in the list',
+'listshowtip' => 'Az elem mutatása a listában',
+'categoryownertip' => 'Ez a személy fog értesítést kapni ha egy hibajegyet nyit ebben a kategóriában',
+'categoryparenttip' => 'The parent category this new one will fall under',
+'notsubcategory' => 'Nincs (legfelső szintű kategória)',
+'showinlineimages' => 'A képek mutatása beágyazva',
+'dateformat' => 'A dátum formátuma',
+'dateformat_extended' => 'A részletes dátum formátuma',
+'cache_feeds' => 'Gyorstár',
+'no_cache' => 'Nincs gyorstár',
+'cache_disk' => 'A gyorstár a lemezen',
+'cache_db' => 'A gyorstár az adatbázisban',
+'subcategoryof' => 'Al kategória a',
+'visiblecolumns' => 'Ezeket az oszlopokat mutasd a hibajegy-listában',
+'tense' => 'Idő',
+'listtensetip' => 'Múlt, jelen, jövő',
+'past' => 'Múlt',
+'present' => 'Jelen',
+'future' => 'Jövő',
+'oldpass' => 'Korábbi jelszó',
+'nooldpass' => 'Nincs korábbi jelszó beállítva',
+'oldpasswrong' => 'A korábbi jelszó helytelen',
+'changepass' => 'Jelszó megváltoztatása',
+'confirmpass' => 'Jelszó mégegyszer',
+'projectmanager' => 'Projekt Manager',
+'viewtasks' => 'Hibajegyek megjelenítése',
+'modifyowntasks' => 'Saját hibajegyek módosítása',
+'modifyalltasks' => 'Nem saját hibajegyek módosítása',
+'viewcomments' => 'Megjegyzések megjelenítése',
+'editcomments' => 'Megjegyzések szerkesztése',
+'deletecomments' => 'Megjegyzések törlése',
+'viewattachments' => 'Csatolt állományok megjelenítése',
+'createattachments' => 'Új csatolt állomány',
+'deleteattachments' => 'Csatolt állományok törlése',
+'viewhistory' => 'Történet megtekintése',
+'closeowntasks' => 'Saját hibajegyek lezárása',
+'closeothertasks' => 'Nem saját hibajegyek lezárása',
+'assigntoself' => 'Hibajegyek önmagához rendelése ha még nem történt meg',
+'assignotherstoself' => 'Mások hibajegyeinek hozzárendelése saját magához',
+'viewreports' => 'Eseménynapló megjelenítése',
+'othersview' => 'Bárki számára engedi ezt a projektet megtekinteni',
+'usersandgroups' => 'Felhasználók és csoportok',
+'globalgroup' => 'Csoport',
+'globalgroups' => 'Csoportok',
+'defaultglobalgroup' => 'Alapértelmezett globális csoport az új felhasználóhoz',
+'addtogroup' => 'Csoporthoz hozzáad',
+'moveuserstogroup' => 'Felhasználók mozgatása csoporthoz',
+'nogroup' => 'Nincs csoport - Törlése a projektból',
+'eventdesc' => 'Esemény leírása',
+'requestedby' => 'Kérte',
+'daterequested' => 'Kérés dátuma',
+'closetask' => 'Hibajegy lezárása',
+'reopentask' => 'Hibajegy újra nyitása',
+'applymember' => 'Projekt tagként hozzárendel',
+'forcurrentproj' => 'A jelenlegi projektnek',
+'lostpw' => 'Elveszett jelszó kérése',
+'lostpwexplain' => 'Ãrja be a felhasználói nevét a jelszó megváltoztatásához. A jelszó el lesz küldve a notifikáció szerinti email címre, amit a profilban beállított',
+'sendlink' => 'Hivatkozás küldése',
+'savenewpass' => 'Új jelszó mentése',
+'anonreg' => 'Új felhasználók regisztrálásának engedélyezése',
+'allowanonopentask' => 'Vendégek hozzáférése a projekt hibajegyeihez',
+'editglobalgroup' => 'Globális csoport szerkesztése',
+'editgroupforproj' => 'Globális csoport szerkesztése a projekthez',
+'notshownforadmin' => 'A jogosultságokat nem mutatja az adminisztrátor csoportjában. Nem szükséges ezeket szerkesztenie.',
+'general' => 'Ãltalános',
+'userregistration' => 'Felhaszáló regisztráció',
+'notifications' => 'Értesítések típusa',
+'resetoptions' => 'Visszaállít',
+'preferences' => 'Beállítások',
+'tasktypes' => 'Hibajegy típusok',
+'resolutions' => 'Megoldások',
+'categories' => 'Kategóriák',
+'operatingsystems' => 'Rendszerek',
+'versions' => 'Változatok',
+'admintoolboxlong' => 'Adminisztrátor eszközei',
+'newproject' => 'Új projekt',
+'delete' => 'Töröl',
+'listdeletetip' => 'Ez elem törlése a listából',
+'lookandfeel' => 'Kinézet',
+'globaltheme' => 'Globális téma',
+'emailnotify' => 'Email értesítések',
+'fromaddress' => 'Feladó cím',
+'smtpserver' => 'SMTP szerver',
+'smtpuser' => 'SMTP felhasználói név',
+'smtppass' => 'SMTP jelszó',
+'addrewrite' => 'Cím átírás használata',
+'usereminderdaemon' => 'Háttér figyelmeztető démon engedélyezése',
+'tasksperpage' => 'Megjelenített hibajegyek száma oldalanként',
+'addtoassignees' => 'Önmaga hozzáadása a hozzárendeltekhez',
+'taskstatuses' => 'Hibajegy állapotok',
+'canvote' => 'Szavazhat a hibajegyre',
+'loginsuccessful' => 'Belépés sikeres.',
+'youareloggedout' => 'Ön kilépett.',
+'waitwhiletransfer' => 'Kérem várjon amíg átirányítjuk...',
+'clicknowait' => 'Klikkeljen ide ha nem kíván várni.',
+'accountdisabled' => 'A felhasználói fiókja le van tiltva. Keresse fel az adminisztrátort.',
+'task' => 'Hibajegy',
+'edittask' => 'A hibajegy szerkesztése',
+'openedby' => 'Megnyitotta',
+'editedby' => 'Utoljára szerkesztette:',
+'tasktype' => 'Hibajegy típusa',
+'category' => 'Kategória',
+'status' => 'Státusz',
+'assignedto' => 'Felhasználó hozzárendelése',
+'operatingsystem' => 'Rendszer',
+'severity' => 'Súlyosság',
+'reportedversion' => 'Jelentett változat',
+'dueinversion' => 'Esedékesség',
+'undecided' => 'határozatlan',
+'percentcomplete' => '% kész',
+'details' => 'Részletes leírás',
+'savedetails' => 'Változtatások mentése',
+'canceledit' => 'Szerkesztés megszakítása',
+'anonymous' => 'Névtelen bejegyző',
+'complete' => 'kész',
+'closedby' => 'Lezárta',
+'reasonforclosing' => 'A lezárása indoka:',
+'reopenthistask' => 'A hibajegy újranyitása',
+'comments' => 'Megjegyzések',
+'attachments' => 'Csatolások',
+'relatedtasks' => 'Kapcsolódó hibajegyek',
+'edit' => 'Szerkeszt',
+'addcomment' => 'Megjegyzés hozzáadása',
+'fileuploadedby' => 'A fájlt feltöltötte',
+'uploadafile' => 'Fájl csatolása',
+'uploadnow' => 'Feltöltés most!',
+'thesearerelated' => 'Ezek a hibajegyek kapcsolódnak ehhez a hibajegyekhez',
+'remove' => 'Eltávolít',
+'addnewrelated' => 'Új kapcsolódó hibajegy hozzáadása',
+'add' => 'Hozzáad',
+'otherrelated' => 'Más hibajegyek melyek ehhez a hibajegyhez kapcsolódnak',
+'receivenotify' => 'Ezek a felhasználók kapnak részletes értesítést ha ez a hibajegy megváltozik.',
+'addusertolist' => 'Felhasználó hozzáadása az értesítési listához:',
+'addtolist' => 'Listához hozzáad',
+'addmyself' => 'Saját magam hozzáadása a listához',
+'removemyself' => 'Saját magam eltávolítása a listáról',
+'theseusersnotify' => 'Ezek a felhasználók kapnak értesítést ha a hibajegy megváltozik.',
+'attachedtoproject' => 'Projekt',
+'reminders' => 'Emlékeztetők',
+'system' => 'Rendszer',
+'remindthisuser' => 'Emlékeztesse ezt a felhasználót',
+'thisoften' => 'Ilyen gyakran',
+'startafter' => 'Várakozási idő az emlékeztetések indítása előtt',
+'hours' => 'Órák',
+'days' => 'Napokt',
+'weeks' => 'Hetek',
+'addreminder' => 'Emlékeztető hozzáadása',
+'defaultreminder' => 'Ez egy emlékeztető ehhez a hibajegyhez:',
+'message' => 'Ãœzenet',
+'closed' => 'Lezárva',
+'filename' => 'Fájlnév:',
+'date' => 'Dátum',
+'filesize' => 'Fájl méret:',
+'closurecomment' => 'További megjegyzések a lezárással kapcsolatban',
+'history' => 'Történet',
+'nohistory' => 'Nincs történet.',
+'eventdate' => 'Dátum',
+'user' => 'Felhasználó',
+'event' => 'Esemény',
+'fieldchanged' => 'Mező megváltoztatva',
+'taskopened' => 'Hibajegy megnyitva',
+'taskreopened' => 'Hibajegy újra megnyitva',
+'taskclosed' => 'Hibajegy lezárva',
+'commentadded' => 'Megjegyzés hozzáadva',
+'commentedited' => 'Megjegyzés szerkesztve',
+'commentdeleted' => 'Megjegyzés törölve',
+'attachmentadded' => 'Csatolás hozzáadva',
+'attachmentdeleted' => 'Csatolás törölve',
+'taskedited' => 'Hibajegy részletei szerkesztve',
+'notificationadded' => 'Felhasználó hozzáadva az értesítési listához',
+'notificationdeleted' => 'Felhasználó eltávolítva az értesítési listához',
+'relatedadded' => 'Kapcsolódó hibajegy hozzáadva',
+'relateddeleted' => 'Kapcsolódó hibajegy eltávolítva',
+'taskassigned' => 'Hibajegy hozzárendelve',
+'taskreassigned' => 'Hibajegy újra hozzárendelve',
+'assignmentremoved' => 'Hozzárendelés eltávolítva',
+'summary' => 'Összegzés',
+'addedasrelated' => 'Hibajegy hozzáadva a kapcsolódó listákhoz a',
+'deletedasrelated' => 'Hibajegy eltávolítva a kapcsolódó listákhoz a',
+'reminderadded' => 'Emlékeztető hozzáadva',
+'reminderdeleted' => 'Emlékeztető törölve',
+'priority' => 'Prioritás',
+'previousvalue' => 'Előző érték',
+'newvalue' => 'Új érték',
+'selectareason' => 'Lezárás indokának kiválasztása',
+'assigntome' => 'Önmagam hozzárendelése a hibajegyhez',
+'reopenrequest' => 'Újranyitás kérése',
+'requestclose' => 'Lezárás kérése',
+'ownershiptaken' => 'Felhasználó átvette a tulajdonjogot',
+'closerequestmade' => 'Hibajegy lezárás kérése',
+'reopenrequestmade' => 'Hibajegy újranyitás kérése',
+'taskdependson' => 'Ez a hibajegy függ',
+'taskblocks' => 'Ez a hibajegy zárolja a lezárástól ezeket',
+'depadded' => 'Függőség hozzáadva',
+'depaddedother' => 'Ez a hibajegy függőségként hozzáadva',
+'depremoved' => 'Függőség eltávolítva',
+'depremovedother' => 'Ez a hibajegy eltávolítva más hibajegyek függőségei közül',
+'showdetailserror' => 'Ez a hibajegy nem létezik vagy nincs jogosultsága megtekinteni.',
+'makeprivate' => 'privátnak beállít',
+'makepublic' => 'publikusnak beállít',
+'taskmadeprivate' => 'A hibajegy privátnak beállítva',
+'taskmadepublic' => 'A hibajegy publikusnak beállítva',
+'confirmdeletecomment' => 'Biztosan törli a megjegyzést? %s',
+'confirmdeleteattach' => 'Biztosan törli a csatolást?',
+'selectedhistory' => 'A kiválasztott történet részleteinek megjelenítése',
+'showallhistory' => 'Teljes történet fülének ismételt megjelenítése',
+'hidethis' => 'Ez a terület ismételt elrejtése',
+'mark100' => 'A hibajegy megjelölése 100% készre',
+'watchtask' => 'Hibajegy figyelése',
+'stopwatching' => 'Hibajegy figyelésének leállítása',
+'commentlink' => 'Hivatkozás ehhez a megjegyzéshez',
+'submitreq' => 'Kérés küldése',
+'reasonforreq' => 'A kérés oka',
+'pmreqdenied' => 'A projekt manager megtagadta a hozzáférést',
+'taskpendingreq' => 'Projekt manager művelet függőben. Tekintse meg a Történet fület a részles információért.',
+'previoustask' => 'Előző művelet',
+'nexttask' => 'Következő művelet',
+'duedate' => 'Esedékesség időpontja',
+'attachnoperms' => 'Vannak csatoltások a megjegyzéshez, de nincs joga megtekinteni őket.',
+'open' => 'Megnyit',
+'depgraph' => 'Függőségi gráf megtekintése',
+'reset' => 'Visszaállít',
+'selectusers' => 'Felhasználók kiválasztása...',
+'addmetoassignees' => 'Adjon hozzá engem',
+'addedtoassignees' => 'Felhasználó hozzáadva',
+'dependencygraph' => 'Függőségi gráf',
+'attachanotherfile' => 'További fájl csatolása',
+'OK' => 'OK',
+'addvote' => 'Szavazás hozzáadása',
+'notifyfromfs' => 'Notification from Flyspray',
+'autogenerated' => 'EZ EGY AUTOMATIKUSAN GENERÃLT ÃœZENET, NE VÃLASZOLJON RÃ',
+'forward' => 'Tovább',
+'previous' => 'Előző',
+'next' => 'Következő',
+'first' => 'Első',
+'last' => 'Utolsó',
+'page' => '%d.oldal (összesen %d)',
+'search' => 'Keresés',
+'alltasktypes' => 'Összes hibajegy típus',
+'allseverities' => 'Összes gyakoriság',
+'alldevelopers' => 'Összes fejlesztő',
+'notyetassigned' => 'Még nincs hozzárendelve',
+'allcategories' => 'Összes kategória',
+'allstatuses' => 'Összes státusz',
+'allopentasks' => 'Összes nyitott hibajegy',
+'sortthiscolumn' => 'Sorrendbe állítás az oszlop szerint',
+'id' => 'Azonosító',
+'project' => 'Projekt',
+'dateopened' => 'Nyitott',
+'progress' => 'Folyamat',
+'searchthisproject' => 'Keresés (az aktuális projektben)',
+'dueanyversion' => 'Esedékessék bármelyik változatban',
+'anyversion' => 'Jelentve bármelyik változatban',
+'dueversion' => 'Változat esedékesség',
+'lastedit' => 'Utoljára szerkesztve',
+'os' => 'Rendszer',
+'reportedin' => 'Jelentve ebben',
+'taskrange' => 'Hibajegy megjelenítése %d - %d (%d)',
+'noresults' => 'A keresének nem volt eredménye.',
+'takeaction' => 'Válasszon',
+'watchtasks' => 'A kiválasztott hibajegyek figyelése',
+'stopwatchingtasks' => 'A figyelés leállítása a kiválasztott hibajegyeken',
+'assigntaskstome' => 'A kiválasztott hibajegy hozzám rendelése',
+'dueby' => 'Due by',
+'dueanytime' => 'Due anytime',
+'selectduedate' => 'Select Due Date',
+'toggleselected' => 'Toggle selected',
+'due' => 'Due',
+'assignedtome' => 'Hozzárendelés saját magamhoz',
+'tasklist' => 'Hibajegy-lista',
+'dateclosed' => 'Lezárás dátuma',
+'advanced' => 'Részletes',
+'searchcomments' => 'Keresés a megjegyzésekben',
+'searchforall' => 'Keresés az összes szóra',
+'anonusers' => 'Névtelen felhasználó',
+'miscellaneous' => 'Egyéb',
+'users' => 'Felhasználók',
+'taskproperties' => 'Hibajegy tulajdonságai',
+'selectsincedate' => 'Select changes since',
+'changedsince' => 'Megváltoztatva ez óta',
+'updatefs' => 'Kérem frissítse az alkalmazást.',
+'currentversion' => 'Az Ön változata jelenleg',
+'latestversion' => 'és a legutóbbi változat',
+'hidemessage' => '(értesítsen később)',
+'saveas' => 'Keresés mentése mint',
+'nosearches' => 'Nincs mentett keresés',
+'saving' => 'Mentés...',
+'votes' => 'Szavazatok',
+'allclosedtasks' => 'Összes lezárt hibajegy',
+'password' => 'Jelszó',
+'login' => 'Belépés',
+'rememberme' => 'Emlékezzen rám',
+'lostpassword' => 'Elfelejtette jelszavát?',
+'lostpwforfs' => 'Elfelejtette jelszavát?',
+'lostpwmsg1' => 'Helló.
+
+Elfelejtettem a jelszavam ',
+'lostpwmsg2' => ', kérem adjon egy újat.
+
+Felhasználói név: ',
+'regards' => '
+
+Tisztelettel,',
+'yourusername' => ' az ön felhasználói neve ',
+'locale' => 'hu-HU',
+'filenotexist' => 'A fájl nem létezik vagy nincs jogosultsága elérni azt.',
+'showtask' => 'Ugrás hibajegyre',
+'now' => 'Most',
+'go' => 'Végrehajt!',
+'opentaskanon' => 'Új hibajegy nyitása névtelenül',
+'register' => 'Regisztrál',
+'addnewtask' => 'Új hibajegy hozzáadása',
+'reports' => 'Eseménynapló',
+'editmydetails' => 'Adataim szerkesztése',
+'logout' => 'Kijelentkezés',
+'disabledaccount' => 'Az Ön hozzáférése letiltva!<br />Kiléptetés azonnal...',
+'poweredby' => 'Powered by Flyspray',
+'projects' => 'Projektek',
+'allprojects' => 'Összes projekt',
+'selectproject' => 'Projekthez:',
+'tasksall' => 'Összes hibajegy',
+'tasksassigned' => 'Hozzám rendelt hibajegyek',
+'tasksreported' => 'Bejelentett hibajegyeim',
+'taskswatched' => 'Figyelt hibajegyek',
+'mysearch' => 'Kereséseim',
+'admintoolbox' => 'Admin eszközök',
+'manageproject' => 'Projekt kezelése',
+'permissions' => 'Hozzáférések megtekintése',
+'hide' => 'Elrejt',
+'pendingreq' => 'Projekt Manager várakozást kér',
+'errorpage' => 'A rendszer nem tudja szolgáltatni a kért lapot.
+ Lehetséges hogy elírta, vagy a lap nem létezik, vagy Önnek nincs
+ hozzáférése ehhez.<br /><br />
+ Lehetséges hogy hibás hivatkozással próbált meg SQL injekciós műveletet elvégezni,
+ hogy közvetlen kapcsolatot létesítsen az adatbázissal. Ha ez igaz, akkor ez a művelet
+ szankciókat fog maga után vonni.',
+'permissionsforproject' => 'Projektengedélyek: ',
+'switchto' => 'Ãtkapcsolás a ',
+'lastsearch' => 'Utolsó keresés',
+'modify' => 'Módosít',
+'noticefrom' => 'Értesítés a ',
+'hasopened' => 'egy új hibajegyet nyitott, és Önt rendelte hozzá!',
+'moreinfonew' => 'További információkat talál erről a hibajegyről itt:',
+'newtaskcategory' => 'Új hibajegy nyílt ebben a kategóriában',
+'categoryowner' => 'Ön azért kapta ezt azt, mert a hibajegy kategóriájának tulajdonosa.',
+'tasksummary' => 'Hibajegy összefoglalása:',
+'newtaskadded' => 'Az új hibajegy hozzáadva.',
+'summaryanddetails' => 'Ki kell töltenie az összegzést és a részletes leírást is. Továbbá anonym felhasználó esetén ellenőrizze hogy megadott-e e-mail címet!',
+'goback' => 'Vissza.',
+'messagefrom' => 'Ez egy üzenet a hibajegy rendszertől a ',
+'hasjustmodified' => 'módosította a következő hibajegyet.',
+'changedfields' => 'A megváltozott mezők előtt csillag szerepel (**)',
+'moreinfomodify' => 'További információkat láthat itt: ',
+'nolongerassigned' => 'Ez a hibajegy már nincs Önhöz rendelve. Most már hozzá van rendelve',
+'hasassigned' => 'hozzárendelte a következő hibajegyhez:',
+'taskupdated' => 'Hibajegy aktualizálva.',
+'hasclosedassigned' => 'lezárta a következő hibajegyet amihez Ön is hozzá volt rendelve:',
+'unassigned' => 'Nincs hozzárendelve',
+'hasclosed' => 'lezárta a következő hibajegyet.',
+'youonnotify' => 'Azért kapta ezt az üzenetet, mert Ön szerepel az értesítési listán.',
+'taskclosedmsg' => 'Hibajegy lezárva.',
+'returntotask' => 'Visszatérés a hibajegy részleteihez',
+'backtoindex' => 'Visszatérés a hibajegyek listájához',
+'noclosereason' => 'Nem válaszotta ki az indokot a hibajegy lezárásához',
+'hasreopened' => 'újranyitotta a következő hibajegyet, amit Ön már lezárt:',
+'taskreopenedmsg' => 'Hibajegy újranyitva.',
+'backtotask' => 'Vissza a hibajegyhez.',
+'commentaddedmsg' => 'Megjegyzés hozzáadva.',
+'commenttoassigned' => 'egy megjegyzést adott a következő hibajegyhez amihez Ön is hozzá van rendelve:',
+'commenttotask' => 'egy megjegyzést adott a következő hibajegyhez:.',
+'nocommententered' => 'Önnek mindenképpen kellene valami megjegyzést írnia, mielőtt az Elküld gombra kattint.',
+'fillinfields' => 'Nem töltötte ki az összes mezőt.',
+'notcurrentpass' => 'Ez nem az Ön jelszava!',
+'passchanged' => 'A jelszó megváltozott.',
+'closewindow' => 'Most már bezárhatja ezt az ablakot.',
+'passnomatch' => 'Az új jelszó és a megerősítés nem egyezik!',
+'usernametaken' => 'Ez a felhasználói név már használatban van, másikat kell választania.',
+'newusercreated' => 'Az új felhasználói fiókja létrejött.',
+'accountcreated' => 'Az új felhasználói fiókja létrejött.',
+'newuserwarning' => 'Figyelem: a globális paraméter megkövetelheti hogy Önnek adminisztrátori hozzáférése legyen, amit a szuper adminisztrátor oszthat ki. Ha nem tud belépni, valószínűleg emiatt lehet..',
+'nomatchpass' => 'A jelszavak nem egyeznek.',
+'confirmwrong' => 'Megerősítési kód érvénytelen!',
+'formnotcomplete' => 'Az űrlap teljesen ki lett töltve.',
+'formnotnumeric' => 'A beírt adat nem szám!',
+'groupnametaken' => 'Ez a csoportnév már létezik.',
+'newgroupadded' => 'Új csoport hozzáadva.',
+'optionssaved' => 'Opciók lementve.',
+'hasuploaded' => 'feltöltött egy új fájlt csatolásként, ami hozzá van rendelve:',
+'hasattached' => 'egy fájlt csatolt a következő hibajegyhez.',
+'fileuploaded' => 'Fájl feltöltve.',
+'fileerror' => 'Hiba történt a fájl feltöltése közben. Valószínűleg a jogok a <i>attachments/</i> könyvtáron nem elégségesek.',
+'contactadmin' => 'Lépjen kapcsolatba az adminisztrátorral a projekt miatt.',
+'selectfileerror' => 'Nem választott ki fájlt.',
+'userupdated' => 'Felhasználó részletei aktualizálva',
+'realandemail' => 'Nem töltötte ki a Teljes nevet és az Email címet.',
+'groupupdated' => 'Csoport definíció aktualizálva.',
+'groupanddesc' => 'Nem töltötte ki a csoport nevet.',
+'fillallfields' => 'Kérem töltse ki az összes mezőt.',
+'listPmustN' => '"Sorrend" bejegyzésnek számnak kell lennie.',
+'listupdated' => 'Lista aktualizálva.',
+'listitemadded' => 'Új listaelem hozzáadva.',
+'relatedaddedmsg' => 'Kapcsolódó hibajegy hozzáadva a listához.',
+'relatederror' => 'Ez a felaat már a kapcsolódó hibajegyek listájában van.',
+'relatedremoved' => 'Kapcsolódó hibajegy eltávolítva a listából.',
+'notifyadded' => 'Felhasználó hozzáava az értesítési listához.',
+'notifyerror' => 'Ez a felhasználó már szerepel az értesítési listán.',
+'notifyremoved' => 'Felhasználó eltávolítva az értesítési listáról.',
+'editcommentsaved' => 'Aktualizált megjegyzés mentve.',
+'commentdeletedmsg' => 'Megjegyzés törölve.',
+'gotonewtask' => 'Menjen az új hibajegyhez amit létrehozott',
+'projectcreated' => 'Az Ön új projektje létrehozva. Most már testreszabhatja a projektet a Projekt Kezelőben.',
+'customiseproject' => 'Projekt testreszabása',
+'projectupdated' => 'Projekt tulajdonságok aktualizálva',
+'emptytitle' => 'A projekt nevét nem töltötte ki. Menjen vissza és töltse ki.',
+'loginbelow' => 'Most megpróbálhat belépni.',
+'attachmentdeletedmsg' => 'A csatolás törölve.',
+'reminderaddedmsg' => 'Az emlékeztetője hozzáadva.',
+'reminderdeletedmsg' => 'A kiválaszott emlékeztető törölve.',
+'flyspraytask' => 'hibajegy',
+'fieldsmissing' => 'Egyes mező nem tartalmaz adatot, vagy érvénytelen adatot tartalmaz.',
+'relatedinvalid' => 'Itt nincs ilyen hibajegy.',
+'relatedproject' => 'Ez a hibajegy más projekthez van csatolva. Mindenképpen létre akarja hozni a kapcsolatot?',
+'addanyway' => 'Mindenképpen hozzáad',
+'cancel' => 'Mégse',
+'alreadyedited' => 'Ezt a hibajegyet valaki módosította mielÅ‘tt Ön ezt mentette volna. Ãgy is menteni akarja?',
+'saveanyway' => 'Mentés mindenképpen',
+'nouserselected' => 'Nincs felhasználó kiválasztva. Válasszon ki legalább egyet, majd próbálja újra.',
+'groupswitchupdated' => 'Felhasználói csoport sikeresen módosítva.',
+'takenownershipmsg' => 'Ez a hibajegy Önhöz rendelt.',
+'adminrequestmade' => 'A kérését elküldtök a projekt kezelőjének.',
+'newdepnotify' => 'Új függőség hozzáadva a következő hibajegyhez:',
+'dependadded' => 'Hibajegy függősége hozzáadva',
+'dependaddfailed' => 'Hiba történt a függőség hozzáadásakor. Ellenőrizze le hogy lézetik-e még a hibajegy vagy nincs-e zárolva.',
+'depremovedmsg' => 'Hibajegy függőség eltávolítva',
+'newdepis' => 'Az új függőség',
+'magicurlsent' => 'Az üzenet elküldve az értesítési címre. Az üzenet tartalmaz egy hivatkozást, amit megnyitva a teljes hibajegyről informálódhat.',
+'changefspass' => 'Jelszó változtatása',
+'magicurlmessage' => 'Kérem kövesse a lenti hivatkozást a jelszó megváltoztatásához:',
+'erroronform' => 'Hiba történt az űrlap elküldésekor',
+'addressused' => 'Ez a címet használva regisztrált. Ha nem tudja miért kapta ezt az üzenetet, törölje nyugodtan és ne foglalkozzon vele. Kattintson a hivatkozásra a regisztráció befejezéséhez:',
+'confirmcodeis' => 'A megerősítő kódja:',
+'codesent' => 'A megerősítő kódot emailben elküldtük. Kövesse az utasításokat az üzenetben.',
+'codenotsent' => 'Nem lehet elküldeni a kódot, kérem próálja később.',
+'taskmadeprivatemsg' => 'Ez a hibajegy már privát',
+'taskmadepublicmsg' => 'Ez a hibajegy ismét publikus',
+'realandnotify' => 'A teljes név kitöltése kötelező. Töltse ki szintén az Email cím vagy a Jabber Azonosító mező valamelyikét is.',
+'pmreqdeniedmsg' => 'Projekt Kezelő kérés megtagadva',
+'massopsuccess' => 'Tömeges művelet sikeres (ahol a jogosultságok megfelelő volt)',
+'usernotexist' => 'Ez a felhasználó nem szerepel az adatbázisban.',
+'commentattachperms' => 'Nem törölheti ezt a megjegyzést, mert jogosultsága törölni a csatolást',
+'voterecorded' => 'A szavazatát rögzítettük',
+'votefailed' => 'A szavazata nem rögzíthető jelenleg.',
+'createnewgroup' => 'Új csoport létrehozása',
+'requiredfields' => 'A szükséges mezők megvannak jelölve ',
+'addthisgroup' => 'Csoport létrehozása',
+'createnewproject' => 'Új projekt létrehozása',
+'addnewproject' => 'Új projekt hozzáadása',
+'htmlallowed' => 'HTML kód engedélyezett',
+'createthisproject' => 'A projekt létrehozása',
+'inlineimages' => 'Mutassa a képeket beágyazva',
+'createnewtask' => 'Új hibajegy létrehozása a projektben:',
+'addanother' => 'További hibajegyet adjon hozzá ezután',
+'addthistask' => 'A hibajegy hozzáadása',
+'notifyme' => 'Értesítsen ha ez a hibajegy megváltozik',
+'newtask' => 'Új hibajegy',
+'attachafile' => 'Fájl csatolása',
+'registernewuser' => 'Új felhasználó regisztálása',
+'none' => 'Nincs',
+'registeraccount' => 'A hozzáférés regisztrálása',
+'both' => 'Mindkettő',
+'notifyfrom' => 'Értesítés ',
+'donotreply' => 'EZ EGY AUTOMATA ÃœZENET, KÉREM NE VÃLASZOLJON RÃ.',
+'disclaimer' => 'Ezt az értesítést azért kapta, mert regisztrált felhasználója a rendszernek, és ezt kérte a rendszertől korábban. Ha nem tudja miért kapta ezt az üzenetet, és nem kíván a jövőben ilyen üzenetet kapni, beállíthatja az értesítési paramétereket ha ellátogat a fenti hivatkozásra, ahol tesztreszabhatja az értesítési üzenetek szabályát.',
+'userwho' => 'Felhasználók akik ezt tették',
+'moreinfo' => 'További információ található a következő hivatkozásra kattinva:',
+'newtaskopened' => 'Egy új hibajegy lett megnyitva. Részletek lejjebb.',
+'notify.taskclosed' => 'A következő hibajegy már lezárva:',
+'notify.taskreopened' => 'A következő hibajegy újra megnyitva:',
+'newdep' => 'A következő hibajegynek új függőségei vannak:',
+'notify.depremoved' => 'A következő hibajegy függőségei eltávolítva:',
+'olddepwas' => 'A régi függőség',
+'notify.commentadded' => 'A következő hibajegy új megjegyzést kapott:',
+'commentis' => 'A megjegyzés szövege az alábbi.',
+'newattachment' => 'Új fájl lett hozzácsatolva a következő hibajegyhez:',
+'detailsbelow' => 'A részletek lejjebb.',
+'notify.relatedadded' => 'Egy új kapcsolódó hibajegy lett hozzáadva a következő hibajegyhez:',
+'relatedis' => 'A kapcsolódó hibajegy',
+'assignedtoyou' => 'Ön az alábbi hibajegyhez lett hozzárendelve:',
+'takenownership' => 'átvette a tulajdonjogot a hibajegyen:',
+'requiresaction' => 'A következő hibajegy beavatkozást igényel a Projekt Kezelőben:',
+'requiresactionnotify' => 'A hibajegy beavatkozást igényel a Projekt Kezelőben.',
+'pmdeny' => 'A Projekt Kezelő megtagadta a függőben lévőségi kérést a következő hibajegyhez:',
+'pmdenynotify' => 'A Projekt Kezelő megtagadta a kérést',
+'fileaddedtoo' => 'Egy vagy több fájl csatolva.',
+'taskwatching' => 'A következő hibajegyet figyelteti',
+'isdepfor' => 'egy új függőség ehhez',
+'denialreason' => 'A megtagadás oka',
+'taskchanged' => 'A következő hibajegy megváltoztatva. A változások listája lejjebb látható. A változtatás teljes információjához kövesse a hivatkozást és válassza a Történet fület.',
+'useraddedtoassignees' => 'Egy felhasználó hozzáadta saját magát a hozzárendelt felhasználók listájához ebben a hibajegyben.',
+'removeddepis' => 'Az eltávolított függőség',
+'isnodepfor' => 'már nem függőség többé',
+'usergroups' => 'Felhasználói csoportok',
+'pmtoolbox' => 'Projekt Kezelő',
+'groupmanage' => 'Csoport Menedzsment',
+'pendingrequests' => 'Függőben lévő kérések',
+'reasongiven' => 'Az ok',
+'nopendingreq' => 'Nincs függőben lévő Projekt Kezelő kérés.',
+'givereason' => 'Adjon meg egy okot',
+'catlisted' => 'Kategórialista szerkesztő',
+'oslisted' => 'Rendszerlista szerkesztő',
+'verlisted' => 'Változatlista szerkesztő',
+'tasktypeed' => 'Hibajegytípus szerkesztő',
+'resed' => 'Megoldáslista szerkesztő',
+'deny' => 'Megtagad',
+'notifiedwhen' => 'Értesítsen ha',
+'onlynewtasks' => 'Új hibajegy nyitva',
+'allevents' => 'Bármilyen esemény bekövetkezése bármelyik hibajegyben',
+'feeds' => 'Lekérés',
+'feeddescription' => 'Lekérés leírása',
+'feedimgurl' => 'Kép hivatkozás lekérése (hagyja üresen ha nem akar képet)',
+'notifysubject' => 'Az értesítés tárgya',
+'notifysubjectinfo' => '(%p = projekt címe, %s = hibajegy összesítése, %t = hibajegy azonosító, %a = művelet, %u = felhasználó)',
+'priority6' => 'Azonnal',
+'priority5' => 'Gyorsan',
+'priority4' => 'Fontos',
+'priority3' => 'Magas',
+'priority2' => 'Normál',
+'priority1' => 'Alacsony',
+'sendcode' => 'Kód küldése!',
+'entercode' => 'Adja meg az ellenőrző kódot amit az értesítő üzenetben kapott. Az érintett fiók jelszavát is adja meg.',
+'confirmationcode' => 'Ellenőrző kód',
+'registererror' => 'Bizonyosodjon meg arról, hogy kitöltötte az összes szükséges mezőt, és hogy a helyes részleteket adta meg az értesítés típusához.',
+'validusername' => '(csak alfanumerikus betűk és a - _ . engedélyezett)',
+'emailtaken' => 'Ez az email cím vagy Jabber azonosító már használatban van. Kérem válasszon másikat.',
+'note' => '<strong>Megjegyzés:</strong> Egy ellenőrző kódot fog kapni mielőtt a fiók elkészül. A kód kézhezvételének módját Ön határozta meg. Javasolt, hogy ténylegesen érvényes elérhetőséget kapjon meg, ennek hiányában sajnos nem tudjuk eljuttatni ezt Önhöz.',
+'changelog' => 'Változások listája',
+'changeloggen' => 'Változások listájának generátora',
+'listfrom' => 'Változási lista bejegyzései a ',
+'to' => '-',
+'oldestfirst' => 'Régebiek előre',
+'recentfirst' => 'Legfrissebbek előre',
+'severityrep' => 'Súlyossági riport',
+'totalopen' => 'Összes nyitott hibajegy',
+'age' => 'Kor',
+'agerep' => 'Kor riport',
+'eventsrep' => 'Esemény riport',
+'events' => 'Esemény',
+'Tasks' => 'Hibajegyek',
+'opened' => 'Nyitva',
+'edited' => 'Szerkesztve',
+'assigned' => 'Hozzárendelve',
+'within' => 'Ebben',
+'pastday' => 'Tegnap',
+'pastweek' => 'Múlt héten',
+'pastmonth' => 'Múlt hónapban',
+'pastyear' => 'Tavaly',
+'nolimit' => 'Nincs határ',
+'from' => 'Dátum',
+'duein' => 'Due in',
+'selectfromdate' => 'Dátumtól',
+'selecttodate' => 'Dátumig',
+'showvoters' => 'Megjelenít/elrejt szavazók',
+'roadmap' => 'Roadmap',
+'roadmapfor' => 'Roadmap a változathoz',
+'tasks' => 'Hibajegyek',
+'completed' => 'kész:',
+'opentasks' => 'nyitott hibajegyek',
+'of' => '% ',
+'severity5' => 'kritikus',
+'severity4' => 'magas',
+'severity3' => 'közepes',
+'severity2' => 'alacsony',
+'severity1' => 'nagyon alacsony',
+'Redirect' => 'Ãtirányít',
+'redirectmsg' => 'Ha a böngészője nem támogat átirányítást kérem kattintson ide az átirányításhoz: %sHERE%s',
+'allowclosedcomments' => 'Megjegyzés engedélyezése lezárt hibajegyeken',
+'comment' => 'Megjegyzés',
+'editowncomments' => 'Saját megjegyzés szerkesztése',
+'reopened' => 'Újranyitva',
+'loading' => 'Betöltés...',
+'notifyown' => 'Értesít saját módosításokkor',
+'youremail' => 'Az Ön email címe',
+'thankyouforbug' => 'Köszönöm hogy jelentette a problémát. Láthatja a hibajegyet és figyelheti a megoldás folyamatát bármikor itt:',
+'anonuser' => 'Névtelen felhasználó',
+'conflict' => 'Konfliktus',
+'file' => 'Fájl',
+'KiB' => 'kilobájt',
+'MiB' => 'megabájt',
+'size' => 'Méret',
+'projectgroup' => 'Projekt csoport',
+'profile' => 'Profil:',
+'viewprofile' => 'Profile megtekintése',
+'regdate' => 'Regisztrált ekkor:',
+'tasksopened' => 'Hibajegy megnyitva',
+'replyto' => 'Feladó email cím',
+'notifytypes' => 'Értesítési típusok',
+'pm.taskchanged' => 'Hibajegy megváltozott',
+'pm.taskreopened' => 'Hibajegy újranyitva',
+'pm.depadded' => 'Függőség hozzáadva',
+'pm.depremoved' => 'Függőség eltávolítva',
+'pmrequest' => 'Projekt Kezelő kérés',
+'pmrequestdenied' => 'Projekt Kezelő kérés megtagadva',
+'newassignee' => 'Új hozzárendelt',
+'revdepadded' => 'Fordított függőség hozzáadva',
+'revdepaddedremoved' => 'Fordított függőség eltávolítva',
+'assigneeadded' => 'Hozzárendelt hozzáadva',
+'addusergroup' => 'Felhasználó hozzáadása csoporthoz',
+'groupmembers' => 'Csoport tagok',
+'deleteuser' => 'Felhasználó törlése',
+'userdeleted' => 'Felhasználó törölve',
+'autoassign' => 'Hibajegy automatikus hozzárendelése a kategória tulajdonosához',
+'ssl' => 'SSL',
+'updatewrong' => 'Ön a frissítés-értesítést engedélyezte, de nem sikerült a frissítő-szerverrel a kapcsolatfelvétel.
+ Lehetéges hogy a kiszolgáló szerver nem tud külső kapcsolatot létesíteni, vagy hálózati probléma lépett fel.
+ Kérem keresse fel a szerver karbantartóját ha a hiba tartósan fellép.',
+'deleteproject' => 'A projekt törlése és a tartalmak mozgatása ide',
+'projectdeleted' => 'Projekt sikeresen törölve',
+'feedforall' => 'Feliratkozás az összes projektre',
+'usercreated' => 'Felhasználó létrehozva',
+'userdeleted' => 'Felhasználó törölve',
+'created' => 'Létrehozva',
+'deleted' => 'Törölve',
+'userid' => 'Felhasználó azonosító szám',
+'editassignments' => 'Hozzárendelések szerkesztése',
+'preview' => 'Előnézet',
+'anyprogress' => 'Bármilyem előrehaladás',
+'tasksrelated' => 'Hibajegyek amik kapcsolódnak ehhez a hibajegyhez',
+'duplicatetasks' => 'Hibajegy lemásolása',
+'databasemodfailed' => 'Adatbázis módosítás sikertelen. Lehetséges okok: elégtelen jogosults.',
+'frequency' => 'Gyakoriság',
+'newuserregistered' => 'Új felhasználó regisztrált a rendszerhez. A felhasználó adatai:',
+'newuserregisterednotify' => 'Új felhasználó regisztrált',
+'notify_registration' => 'Adminisztrátorok értesítése új felhasználó regisztrálása esetén',
+'textversion' => 'Szöveges változat',
+'onlyprimary' => 'Hibajegyek, melyek nem blokkolnak más hibajegyeket',
+'switch' => 'Ãtvált',
+'max' => 'max.',
+'dates' => 'Dátumok',
+'selectduedatefrom' => 'Esedékesség',
+'selectduedateto' => '-',
+'selectsincedatefrom' => 'Változtatva',
+'selectsincedateto' => '-',
+'selectdate' => 'Select Date',
+'selectopenedfrom' => 'Megnyitva',
+'selectopenedto' => '-',
+'selectclosedfrom' => 'Lezárva',
+'selectclosedto' => '-',
+'startat' => 'Kezdeti időpont',
+'hasattachment' => 'Van csatolása',
+'private' => 'Privát',
+'watching' => 'Figyelt',
+'alreadyvotedthistask' => 'Ön szavazott erre a hibajegyre',
+'alreadyvotedthisday' => 'már szavazott ma',
+'visibility' => 'Láthatóság',
+'public' => 'Publikus',
+'leaveemptyauto' => 'Hagyja a jelszó mezőt üresen ha automatikusan generált jelszót szeretne.',
+'novalidemail' => 'Nem adott meg érvényes email címet.',
+'novalidjabber' => 'Nem adott meg érvényes Jabber azonosítót.',
+'missingrequired' => 'Nem töltötte ki az összes szükséges mezőt.',
+'entervalidusername' => 'Kérem adjon meg valós felhasználói és teljes nevet.',
+'couldnotaddusernotif' => 'A felhasználó felvétele az értesítési listába sikertelen.',
+'defaulttask' => 'Alapértelmezett hibajegy leírás',
+'all' => 'összes',
+'events.useraddedtoassignees' => 'Felhasználó hozzáadva a hozzárendeltekhez',
+'vote(s)' => 'szavazat(ok)',
+'eventlog' => 'Eseménynapló',
+'assignmentchanged' => 'Hozzárendelés megváltoztatva',
+'detailedinfo' => 'Részletes információ',
+'All' => 'Összes',
+'tasksireported' => 'Hibajegyek amiket én jelentettem',
+'recentlyopened' => 'Nemrég megnyitott',
+'stats' => 'Statisztika',
+'totaltasks' => 'összes hibajegy',
+'mostwanted' => 'Leggyakrabban keresett hibajegyek',
+'defaultentry' => 'Az alapértelmezett belépés utáni oldal',
+'toplevel' => 'Felső szintű nézet',
+'overview' => 'Ãttekintés',
+'error#' => 'Hiba #',
+'error1' => 'Nincs elegendő jogosultsága megnyitni a csatolást.',
+'error3' => 'IsmétlÅ‘dÅ‘ művelet. Ãtirányítás a fÅ‘oldalra.',
+'error4' => 'Nincs adminisztratív joga.',
+'error5' => 'Ez a felhasználó nem létezik a rendszerben.',
+'error6' => 'Érvénytelen adminisztrátoti terület.',
+'error7' => 'Sikertelen belépés (rossz felhasználói név vagy jelszó)!',
+'error71' => 'Fiók zárolva %d percig a többszöri sikertelen belépés miatt!',
+'error8' => 'Nem adta meg a felhasználói nevet és a jelszót.',
+'error9' => 'A hibajegy nem létezik vagy nincs jogosultsága megnézni azt.',
+'error10' => 'Ez a hibajegy nem létezik.',
+'error101' => 'Nincs jogosultága megnézni ezt a hibajegyet.',
+'error102' => 'Nincs jogosultsága megnézni ezt a hibajegyet, ha belép, lehet hogy sikerül.',
+'error11' => 'Nincs jogosultság a szerkeszteni a megjegyzést.',
+'error12' => 'Nincs érvényes kulcs. Biztos benne, hogy Ön ezt kapta az értesítési üzenetben?',
+'error13' => 'Névtelen felhasználóknak nincs profiljuk.',
+'error14' => 'Nincs elegendő jogosultsága új csoport létrehozására.',
+'error15' => 'Nincs elegentő jogosultsága új hibajegy létrehozására.',
+'error16' => 'Ön nem projekt kezelő.',
+'error17' => 'Érvénytelen projekt kezelő terület.',
+'error18' => 'Érvénytelen hivatkozás.',
+'error19' => 'Ez a felhasználó nem létezik a rendszerben.',
+'error20' => 'Érvénytlen adatbázis módosítás.',
+'error21' => 'Egy vagy több email nem küldhető el. Ellenőrizze a konfigurációt.',
+'error22' => 'Felhasználó regisztrációja nem megendegett.',
+'error23' => 'A felhasználó vagy csoportja nem léphet be.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Roadmap csak bizonyos projektekhez áll rendelkezésre.',
+'done' => 'kész',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'A projekt nem törölhető.',
+'GMT' => 'GMT',
+'timezone' => 'Időzóna',
+'accept' => 'Elfogad',
+'reasonfordeinal' => 'A megtagadás oka',
+'pruneclosedlinks' => 'Lezárt hivatkozások vágása',
+'pruneclosedtasks' => 'Lezárt hibajegyek vágása',
+'pagegenerated' => 'Oldal és kép generálva %d másodperc alatt.',
+'pruninglevel' => 'Vágási szint',
+'lastuser' => 'Az utolsó felhasználó nem törölhető.',
+'allprivate' => 'Az összes projekt privát.',
+'deletegroup' => 'Csoport törlése és a tagok mozgatása ide',
+'parent' => 'Szülő',
+'ordertip' => 'Az elemek sorrendje ebben a listában fog szerepelni',
+'showtip' => 'A bejegyzés megjelenítése ebben a listában',
+'deletetip' => 'A bejegyzés törlése a listából',
+'del' => 'töröl',
+'request1' => 'A hibajegy lezárás kérvényezve.',
+'request2' => 'A hibajegy újranyitásának kérelme megtörtént.',
+'allpriorities' => 'Összes prioritás',
+'noroadmap' => 'Nincs roadmap (nincs jövőbeni projekt definiálva).',
+'expand' => 'Kinyit',
+'collapse' => 'Összecsuk',
+'expandall' => 'Az össestet kinyitja',
+'collapseall' => 'Az összeset összecsuk',
+'minpwsize' => 'A jelszó minimális hossza 5 karakter',
+'passwordtoosmall' => 'Jelszó hossza túl rövid.',
+'accountwaslocked' => 'A hozzáférése zárolva túl sok sikertelen belépés miatt.',
+'failedattempts' => '%d sikertelen belépés történt.',
+'groupnotexist' => 'Kiválasztott csoport nem létezik ebben a projektben.',
+'searchindetails' => 'Keresés a részletes leírásban',
+
+'is_admin' => 'Adminisztrátor',
+'manage_project' => 'Projekt kezelő',
+'view_tasks' => 'Hibajegyek megtekintése',
+'edit_own_comments' => 'Saját megjegyzés szerkesztése',
+'open_new_tasks' => 'Új hibajegyek felvitele',
+'modify_own_tasks' => 'Saját hibajegyek módosítása',
+'modify_all_tasks' => 'Minden hibajegy módosítása',
+'view_comments' => 'Megjegyzések megtekintése',
+'add_comments' => 'Megjegyzés hozzáadása',
+'edit_comments' => 'Megjegyzés szerkesztése',
+'edit_assignments' => 'Hozzárendelések szerkesztése',
+'delete_comments' => 'Megjegyzések törlése',
+'create_attachments' => 'Csatolások hozzáadása',
+'delete_attachments' => 'Csatolások törlése',
+'view_history' => 'Történek megtekintése',
+'close_own_tasks' => 'Saját hibajegyek lezárása',
+'close_other_tasks' => 'Mások hibajegyeinek lezárása',
+'assign_to_self' => 'Önmagamhoz rendelés',
+'assign_others_to_self' => 'Mások hozzárendelése',
+'view_reports' => 'Riportok megtekintése',
+'add_votes' => 'Szavazat leadása',
+'showasassignees' => 'Hozzárendelhető személy',
+'find' => 'Keresés'
+);
+
+?>
diff --git a/lang/it.php b/lang/it.php
new file mode 100644
index 0000000..129d111
--- /dev/null
+++ b/lang/it.php
@@ -0,0 +1,1101 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Modifica utente',
+'accountenabled' => 'Utenza abilitata',
+'editallusers' => 'Visualizza tutti gli Utenti',
+'username' => 'Nome Utente',
+'usersupdated' => 'Utenti aggiornati correttamente',
+'realname' => 'Nome Reale',
+'emailaddress' => 'Indirizzo e-mail ',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Immagine del Profilo',
+'notifytype' => 'Tipo di notifica',
+'group' => 'Gruppo',
+'enableaccounts' => 'Abilita Account',
+'disableaccounts' => 'Disabilita Account',
+'deleteaccounts' => 'Elimina Account',
+'updatedetails' => 'Aggiorna le informazioni',
+'setglobally' => 'Questa opzione è stata impostata a livello globale.',
+'usergroupmanage' => 'Gestione Utenti e Gruppi',
+'newuser' => 'Registra un nuovo Utente',
+'newuserbulk' => 'Registra Nuovi Utenti',
+'bulkuserstoadd' => 'Elenco dei Nuovi Utenti',
+'optionsforallusers' => 'Opzioni per tutti i Nuovi Utenti',
+'newgroup' => 'Crea un nuovo Gruppo',
+'yes' => 'Si',
+'no' => 'No',
+'editgroup' => 'Modifica il Gruppo',
+'groupname' => 'Nome del Gruppo',
+'description' => 'Descrizione',
+'admin' => 'Amministra Gruppo',
+'opennewtasks' => 'Apri nuovo Task',
+'modifytasks' => 'Modifica Task esistenti',
+'addcomments' => 'Aggiungi commenti',
+'attachfiles' => 'Allega file',
+'vote' => 'I membri possono votare',
+'groupenabled' => 'I membri possono collegarsi',
+'groupopen' => 'I membri possono collegarsi',
+'tasktypelist' => 'Lista dei tipi di Task',
+'categorylist' => 'Lista delle Categorie',
+'oslist' => 'Lista dei Sistemi Operativi',
+'resolutionlist' => 'Lista delle Soluzioni',
+'versionlist' => 'Lista delle Versioni',
+'severitylist' => 'Lista delle Gravità',
+'listnote' => 'Nota: Disabilitare il box "Mostra" potrebbe alterare alcuni task quando in modalità modifica. La modifica del campo "Nome" cambierà tutti i task con quel nome.',
+'name' => 'Nome',
+'order' => 'Ordinamento',
+'back' => 'Indietro',
+'text' => 'Testo',
+'highlight' => 'In evidenza',
+'show' => 'Mostra',
+'owner' => 'Proprietario',
+'selectowner' => 'Seleziona il proprietario',
+'update' => 'Aggiorna',
+'addnew' => 'Aggiungi nuovo',
+'flysprayprefs' => 'Preferenze di Flyspray',
+'projecttitle' => 'Titolo del Progetto',
+'baseurl' => 'URL base per questa installazione',
+'replyaddress' => 'Indirizzo e-mail di risposta per le notifiche',
+'themestyle' => 'Tema / Stile',
+'customstyle' => 'personalizzato',
+'language' => 'Lingua',
+'anonview' => 'Consenti agli utenti anonimi di visualizzare i task',
+'allowanon' => 'Consenti agli utenti anonimi di creare nuovi task',
+'never' => 'Mai',
+'anonymously' => 'In modo anonimo',
+'afterregister' => 'Solo dopo essere stati registrati',
+'spamproof' => 'Abilita il codice di conferma per i nuovi Utenti registrati',
+'anongroup' => 'Gruppo per i nuovi Utenti',
+'groupassigned' => 'Ai Membri di questo Gruppo possono essere assegnati dei Task',
+'forcenotify' => 'Forza la notifica dei Task come',
+'neversend' => 'Non inviare mai',
+'userchoose' => 'Lascia scegliere l\'Utente',
+'email' => 'E-mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Proprietario predefinito della categoria',
+'noone' => 'Nessuno',
+'jabbernotify' => 'Notifiche Jabber',
+'jabberserver' => 'Server',
+'jabberport' => 'Porta',
+'jabberuser' => 'Nome Utente',
+'jabberpass' => 'Password',
+'saveoptions' => 'Salva le opzioni',
+'editcomment' => 'Modifica il Commento',
+'commentby' => 'Commento a cura di',
+'saveeditedcomment' => 'Salva il commento modificato',
+'projectprefs' => 'Preferenze del Progetto',
+'pagetitle' => 'Titolo Pagina',
+'defaultproject' => 'Progetto predefinito',
+'projectlists' => 'Lista dei Progetti',
+'showlogo' => 'Mostra mmagine del logo',
+'showgravatars' => 'Mostra i Gravatar',
+'emailNoHTML' => 'Disabilita HTML nelle E-Mail',
+'intromessage' => 'Messaggio Introduttivo',
+'active' => 'attivo',
+'inactive' => 'inattivo',
+'isactive' => 'Il progetto è attivo',
+'showinactive' => 'Mostra progetti inattivi',
+'hideinactive' => 'Nascondi progetti inattivi',
+'createproject' => 'Crea un nuovo Progetto',
+'nopermission' => 'Non hai il permesso di usare questa pagina',
+'listordertip' => 'l\'ordine con cui questi dati appariranno nella lista',
+'listshowtip' => 'Mostra questo dato nella lista',
+'categoryownertip' => 'Questa persona riceverà una notifica quando verrà aperto un Task in questa Categoria',
+'categoryparenttip' => 'La Categoria padre sotto cui cadrà questa nuova',
+'notsubcategory' => 'Nessuna - è una categoria primaria',
+'showinlineimages' => 'Mostra le immagini allegate inline',
+'dateformat' => 'Formato date',
+'dateformat_extended' => 'Formato date esteso',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'Niente caching',
+'cache_disk' => 'Cache su disco',
+'cache_db' => 'Cache nel DB',
+'subcategoryof' => 'Sottocategoria di',
+'visiblecolumns' => 'Colonne mostrate nella lista Task',
+'visiblefields' => 'Campi quando si aggiungono/modificano/visualizzano i Task',
+'tense' => 'Tempo',
+'listtensetip' => 'Passato, presente o futuro',
+'past' => 'Passato',
+'present' => 'Presente',
+'future' => 'Futuro',
+'oldpass' => 'Vecchia Password',
+'nooldpass' => 'Nessuna Password precedente impostata',
+'oldpasswrong' => 'Password precedente errata',
+'changepass' => 'Cambia password',
+'confirmpass' => 'Conferma Password',
+'projectmanager' => 'Amministratore del Progetto',
+'viewtasks' => 'Visualizza Task',
+'modifyowntasks' => 'Modifica un proprio Task',
+'modifyalltasks' => 'Modifica Task che non sono propri dell\'Utente',
+'viewcomments' => 'Visualizza commenti',
+'editcomments' => 'Modifica commenti',
+'deletecomments' => 'Cancella commenti',
+'viewattachments' => 'Visualizza allegati',
+'createattachments' => 'Crea allegati',
+'deleteattachments' => 'Cancella allegati',
+'viewhistory' => 'Visualizza lo storico',
+'closeowntasks' => 'Chiudi un Task che ti appartiene',
+'closeothertasks' => 'Chiudi Task che non sono propri dell\'utente',
+'assigntoself' => 'Assegnati i Task se non sono già assegnati',
+'assignotherstoself' => 'Assegnati i Task degli altri',
+'viewreports' => 'Visualizza i Report',
+'othersview' => 'Permetti a chiunque di visualizzare il Progetto',
+'othersviewroadmap' => 'Permetti a chiunque di visualizzare la roadmap del progetto',
+'usersandgroups' => 'Utenti e Gruppi',
+'globalgroup' => 'Gruppo globale',
+'globalgroups' => 'Gruppi globali',
+'defaultglobalgroup' => 'Gruppo globale predefinito per i nuovi utenti',
+'addtogroup' => 'Aggiungi al Gruppo',
+'moveuserstogroup' => 'Sposta gli utenti nel Gruppo',
+'nogroup' => 'Nessun Gruppo - elimina dal Progetto',
+'eventdesc' => 'Descrizione dell\'evento',
+'requestedby' => 'Richiesto da',
+'daterequested' => 'Data della richiesta',
+'closetask' => 'Chiudi Task',
+'reopentask' => 'Riapri Task',
+'applymember' => 'Richiedi l\'accesso al Progetto',
+'forcurrentproj' => 'Per il Progetto corrente',
+'lostpw' => 'Recupero Password perse',
+'lostpwexplain' => 'Inserisci il nome utente e ti verrà inviato un link per il cambio della password. Il link sarà inviato all\'indirizzo di notifica configurato nel tuo profilo.',
+'sendlink' => 'Invia link',
+'savenewpass' => 'Memorizza la nuova Password',
+'anonreg' => 'Permetti la registrazione di nuovi utenti',
+'allowanonopentask' => 'Permetti agli utenti anonimi di aprire dei task',
+'editglobalgroup' => 'Modifica i gruppi globali',
+'editgroupforproj' => 'Modifica i gruppi per progetto',
+'notshownforadmin' => 'I permessi del Gruppo Admin non vengono mostrati. Non hai bisogno di modificarli.',
+'general' => 'Generale',
+'userregistration' => 'Registrazione utenti',
+'notifications' => 'Notifiche',
+'resetoptions' => 'Annulla Opzioni',
+'preferences' => 'Preferenze',
+'tasktypes' => 'Tipi di Task',
+'resolutions' => 'Soluzioni',
+'categories' => 'Categorie',
+'categoriesglobal' => 'Categorie Globali',
+'categoriesproject' => 'Categorie specifiche del Progetto',
+'categoriestarget' => 'Categorie destinazione',
+'operatingsystems' => 'Sistemi Operativi',
+'versions' => 'Versioni',
+'admintoolboxlong' => 'Strumenti di Amministrazione',
+'newproject' => 'Nuovo Progetto',
+'delete' => 'Cancella',
+'link' => 'Collegamento',
+'referencelinks' => 'Riferimento Collegamenti',
+'listdeletetip' => 'Cancella questa voce dalla lista',
+'lookandfeel' => 'Aspetto Grafico',
+'globaltheme' => 'Tema Principale',
+'emailnotify' => 'Notifiche per Email',
+'fromaddress' => 'Indirizzo Da',
+'smtpserver' => 'Server SMTP',
+'smtpuser' => 'Nome Utente SMTP',
+'smtppass' => 'Password SMTP',
+'addrewrite' => 'Usa address rewriting',
+'usereminderdaemon' => 'Abilita il daemon per notifiche in background',
+'tasksperpage' => 'Task per pagina nella Task list',
+'addtoassignees' => 'Aggiungi me stesso tra gli assegnatari',
+'taskstatuses' => 'Stato Task',
+'canvote' => 'Può votare i Task',
+'loginsuccessful' => 'Login corretto.',
+'youareloggedout' => 'Sei stato disconnesso.',
+'waitwhiletransfer' => 'Aspetta mentre vieni trasferito...',
+'clicknowait' => 'Clicca qui se non vuoi aspettare.',
+'accountdisabled' => 'Il tuo account è disabilitato, Contatta un Amministratore.',
+'task' => 'Task',
+'edittask' => 'Modifica questo Task',
+'openedby' => 'Aperto da',
+'editedby' => 'Ultima modifica da parte di',
+'tasktype' => 'Tipo di Task',
+'category' => 'Categoria',
+'status' => 'Stato',
+'assignedto' => 'Assegnato a',
+'operatingsystem' => 'Sistema Operativo',
+'severity' => 'Gravità',
+'reportedversion' => 'Segnalato nella Versione',
+'dueinversion' => 'Previsto per la Versione',
+'defaultdueinversion' => 'Versione Prevista di default per i Task',
+'undecided' => 'Non precisata',
+'percentcomplete' => 'Percentuale di Completamento:',
+'details' => 'Dettagli',
+'savedetails' => 'Salva Dettagli',
+'canceledit' => 'Cancella Modifiche',
+'anonymous' => 'Origine anonima',
+'complete' => 'completati',
+'closedby' => 'Chiuso da',
+'reasonforclosing' => 'Il motivo della chiusura è:',
+'reopenthistask' => 'Riapri questo task',
+'comments' => 'Commenti',
+'attachments' => 'Allegati',
+'relatedtasks' => 'Task correlati',
+'edit' => 'Modifica',
+'addcomment' => 'Aggiungi commento',
+'fileuploadedby' => 'File caricato da',
+'uploadafile' => 'Carica un file come allegato',
+'addalink' => 'Aggiungi un collegamento',
+'addanotherlink' => 'Aggiungi un altro collegamento',
+'uploadnow' => 'Carica ora!',
+'thesearerelated' => 'Questi task sono correlati a questo task',
+'remove' => 'Rimuovi',
+'addnewrelated' => 'Aggiungi un nuovo Task correlato',
+'add' => 'Aggiungi!',
+'otherrelated' => 'Altri Task correlati',
+'receivenotify' => 'Questi Utenti riceveranno una notifica dettagliata quando questo Task viene modificato.',
+'addusertolist' => 'Aggiungi un Utente a questa lista',
+'addtolist' => 'Aggiungi alla lista',
+'addmyself' => 'Aggiungi me stesso a questa lista',
+'removemyself' => 'Rimuovi me stesso da questa lista',
+'theseusersnotify' => 'Questi utenti riceveranno una notifica dettagliata quando questo Task viene modificato.',
+'attachedtoproject' => 'Allegato al progetto',
+'reminders' => 'Promemoria',
+'system' => 'Sistema',
+'systemvalues' => 'Elenco valori per tutto il Sistema',
+'projectvalues' => 'Elenco valori per il progetto specifico',
+'remindthisuser' => 'Manda un promemoria a questo utente',
+'thisoften' => 'Quanto spesso',
+'startafter' => 'Aspetta prima di inviare promemoria',
+'hour' => 'ora',
+'hours' => 'Ora(e)',
+'day' => 'giorno',
+'days' => 'Giorno(i)',
+'week' => 'settimana',
+'weeks' => 'Settimana(e)',
+'addreminder' => 'Aggiungi promemoria',
+'defaultreminder' => 'Questo è un promemoria per controllare il seguente task di Flyspray:',
+'message' => 'Messaggio',
+'closed' => 'Chiusi',
+'filename' => 'Nome file:',
+'date' => 'Data',
+'filesize' => 'Dimensione File:',
+'closurecomment' => 'Commenti addizionali sulla chiusura:',
+'history' => 'Storico',
+'nohistory' => 'Lo storico non è disponibile',
+'eventdate' => 'Data',
+'user' => 'Utente',
+'event' => 'Evento',
+'fieldchanged' => 'Campo cambiato',
+'taskopened' => 'Task aperto',
+'taskreopened' => 'Il Task è stato riaperto.',
+'taskclosed' => 'Il Task è stato chiuso.',
+'commentadded' => 'Il Commento è stato aggiunto.',
+'commentedited' => 'Commento modificato',
+'commentdeleted' => 'Il commento è stato cancellato.',
+'attachmentadded' => 'Aggiunto allegato',
+'attachmentdeleted' => 'l\'allegato è stato rimosso',
+'taskedited' => 'Modificati i dettagli del Task',
+'notificationadded' => 'Aggiunto utente alla lista di notifica',
+'notificationdeleted' => 'Rimosso utente dalla lista di notifica',
+'relatedadded' => 'Task correlato aggiunto alla lista.',
+'relateddeleted' => 'Rimosso Task correlato',
+'taskassigned' => 'Task assegnato a',
+'taskreassigned' => 'Task riassegnato a',
+'assignmentremoved' => 'Assegnazione rimossa',
+'summary' => 'Riepilogo',
+'addedasrelated' => 'Aggiunto Task alla lista correlata a',
+'deletedasrelated' => 'Rimosso Task dalla lista correlata a',
+'reminderadded' => 'Il tuo promemoria è stato aggiunto.',
+'reminderdeleted' => 'Il promemoria selezionato è stato eliminato.',
+'priority' => 'Priorità',
+'previousvalue' => 'Valore precedente',
+'newvalue' => 'Nuovo valore',
+'selectareason' => 'Seleziona un motivo',
+'assigntome' => 'Assegna a me stesso',
+'reopenrequest' => 'Richiedi la riapertura',
+'requestclose' => 'Richiedi la chiusura',
+'ownershiptaken' => 'l\'utente ha preso possesso',
+'closerequestmade' => 'Richiesta di chiusura del Task effettuata',
+'reopenrequestmade' => 'Richiesta di riapertura del Task effettuata',
+'taskdependson' => 'Questo Task dipende da',
+'taskdependsontask' => 'Il task dipende da',
+'taskdependsontasks' => 'Il task dipende da',
+'taskblock' => 'Il task blocca impedisce la chiusura',
+'taskblocks' => 'Questo Task impedisce di chiudere questi altri',
+'depadded' => 'Dipendenza inserita',
+'depaddedother' => 'Questo Task aggiunto come dipendenza',
+'depremoved' => 'La dipendenza è stata eliminata',
+'depremovedother' => 'Questo Task è stato rimosso dalla lista di dipendenze dei Task',
+'showdetailserror' => 'Il Task non esiste o non hai i permessi per visualizzarlo.',
+'makeprivate' => 'Rendi privato',
+'makepublic' => 'Rendi pubblico',
+'taskmadeprivate' => 'Questo Task è stato reso privato',
+'taskmadepublic' => 'Questo Task è stato reso pubblico',
+'confirmdeletecomment' => 'Cancellare questo commento? %s',
+'attachementswilldeleted' => 'Verranno eliminati anche tutti gli allegati!',
+'confirmdeleteattach' => 'Cancellare questo allegato?',
+'selectedhistory' => 'Mostra i dettagli dello storico selezionati',
+'showallhistory' => 'Mostra tutto lo storico',
+'hidethis' => 'Nascondi quest`area',
+'mark100' => 'Segna il Task come Completo al 100%',
+'watchtask' => 'controlla Task',
+'stopwatching' => 'disabilita controllo',
+'commentlink' => 'Link a questo commento',
+'submitreq' => 'Invia richiesta',
+'reasonforreq' => 'Motivo della richiesta',
+'pmreqdenied' => 'l\'Amministratore del Progetto ha rifiutato la richiesta',
+'taskpendingreq' => 'In attesa di azione da parte dell\'Amministratore del Progetto. Visualizza lo Storico per i dettagli.',
+'previoustask' => 'Task precedente',
+'nexttask' => 'Prossimo task',
+'duedate' => 'Data prevista',
+'attachnoperms' => 'Questo commento contiene degli allegati ma non hai l\'autorizzazione a visualizzarli.',
+'linknoperms' => 'Sono presenti collegamenti in questo commento anche se non si hanno i permessi per vederli.',
+'open' => 'Aperti',
+'depgraph' => 'Visualizza il Grafico delle Dipendenze',
+'reset' => 'Reset',
+'selectusers' => 'Seleziona Utenti...',
+'addmetoassignees' => 'Aggiungimi agli assegnatari',
+'addedtoassignees' => 'Utente aggiunto alla lista degli assegnatari',
+'dependencygraph' => 'Grafico delle Dipendenze',
+'attachanotherfile' => 'Allega un altro file',
+'OK' => 'OK',
+'addvote' => 'Aggiungi Voto',
+'disable_lostpw' => 'Disabilita il recupero della password',
+'disable_changepw' => 'Disabilita la creazione/modifica password',
+'notifyfromfs' => 'Notifica da Flyspray',
+'autogenerated' => 'QUESTO è UN MESSAGGIO GENERATO AUTOMATICAMENTE, NON RISPONDERE',
+'forward' => 'Avanti',
+'previous' => 'Precedente',
+'next' => 'Successivo',
+'first' => 'Primo',
+'last' => 'Ultimo',
+'page' => 'Pagina %d di %d',
+'search' => 'Cerca',
+'alltasktypes' => 'Qualsiasi tipo di Task',
+'allseverities' => 'Qualsiasi Gravità',
+'alldevelopers' => 'Tutti gli Sviluppatori',
+'notyetassigned' => 'Non ancora assegnato',
+'allcategories' => 'Qualsiasi Categoria',
+'allstatuses' => 'Qualsiasi Stato',
+'allopentasks' => 'Tutti i Task aperti',
+'sortthiscolumn' => 'Ordina in base a questa colonna',
+'id' => 'ID',
+'project' => 'Progetto:',
+'dateopened' => 'Data di apertura',
+'progress' => 'Progresso',
+'searchthisproject' => 'Cerca in questo Progetto',
+'dueanyversion' => 'Previsto in ogni Versione',
+'anyversion' => 'Segnalato in Tutte le Versioni',
+'dueversion' => 'Previsto nella',
+'lastedit' => 'Ultima modifica',
+'os' => 'Sistema Operativo',
+'reportedin' => 'Segnalato nella',
+'taskrange' => 'Visualizzazione Task %d - %d di %d',
+'noresults' => 'La ricerca non ha prodotto risultati.',
+'takeaction' => 'Esegui',
+'watchtasks' => 'Controlla i Task selezionati',
+'stopwatchingtasks' => 'Disabilita controllo sui Task selezionati',
+'assigntaskstome' => 'Assegna Task a me stesso',
+'dueby' => 'Previsto il',
+'dueanytime' => 'Previsto senza scadenza',
+'selectduedate' => 'Seleziona scadenza',
+'toggleselected' => 'Inverti selezione',
+'due' => 'Scadenza',
+'assignedtome' => 'Assegnati a me stesso',
+'tasklist' => 'Elenco Task',
+'dateclosed' => 'Data chiusura',
+'advanced' => 'Avanzate',
+'searchcomments' => 'Cerca nei commenti',
+'searchforall' => 'Cerca tutte le parole',
+'anonusers' => 'Utenti Anonimi',
+'miscellaneous' => 'Varie',
+'users' => 'Utenti',
+'taskproperties' => 'Proprietà del Task',
+'selectsincedate' => 'Seleziona Modificato dal',
+'changedsince' => 'Modificato dal',
+'updatefs' => 'Per favore aggiorna Flyspray.',
+'currentversion' => 'La tua versione è',
+'latestversion' => 'la versione piu` recente è',
+'hidemessage' => '(ricordamelo piu` tardi)',
+'saveas' => 'Salva ricerca come',
+'nosearches' => 'Nessuna ricerca salvata',
+'saving' => 'Salvataggio in corso...',
+'votes' => 'Voti',
+'tovote' => 'Vota',
+'allclosedtasks' => 'Tutti i Task Chiusi',
+'password' => 'Password',
+'login' => 'Login!',
+'rememberme' => 'Ricordami',
+'lostpassword' => 'Hai perso la Password?',
+'lostpwforfs' => 'Smarrita la password per Flyspray',
+'lostpwmsg1' => "Salve.\nHo perso la mia password per Flyspray il ",
+'lostpwmsg2' => ", per favore inviatemene un'altra.\nNome Utente: ",
+'regards' => 'Saluti,',
+'yourusername' => ' il tuo Nome Utente ',
+'locale' => 'it-IT',
+'filenotexist' => 'Il file non esiste. Contatta l\'Amministratore di Flyspray per questo Progetto.',
+'showtask' => 'Mostra il Task',
+'now' => 'Ora',
+'go' => 'Vai!',
+'opentaskanon' => 'Apri un nuovo Task come anonimo',
+'register' => 'Registra come nuovo Utente',
+'addnewtask' => 'Aggiungi un nuovo Task',
+'reports' => 'Rapporti',
+'editmydetails' => 'Modifica i miei dati',
+'logout' => 'Logout',
+'disabledaccount' => 'La tua utenza è stata disabilitata!<br>Disconnessione immediata...',
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray è sponsorizzato da',
+'projects' => 'Progetti',
+'allprojects' => 'Tutti i Progetti',
+'selectproject' => 'per il Progetto:',
+'tasksall' => 'Tutti i Task',
+'tasksassigned' => 'Task a me assegnati',
+'tasksreported' => 'Task da me riportati',
+'taskswatched' => 'Task che controllo',
+'mysearch' => 'Le mie Ricerche',
+'admintoolbox' => 'Strumenti di Amministrazione',
+'manageproject' => 'Gestione Progetto',
+'permissions' => 'Visualizza Autorizzazioni',
+'hide' => 'Nascondi',
+'pendingreq' => 'Invia richieste in attesa come Messaggio Privato',
+'errorpage' => "Flyspray non puo' fornire la pagina richiesta.\n Forse hai richiesto un task inesistente oppure non hai\n l'autorizzazione per visualizzare la pagina richiesta.\n\n Potresti aver provato ad usare una URL modificata a mano per interagire col\n database e tentare un attacco SQL injection. Se questo e' il caso, vai all'angolino\n e rifletti sulle tue azioni. Quando torni, per favore, non farlo piu'!",
+'permissionsforproject' => 'Autorizzazioni per ',
+'switchto' => 'Passa a',
+'lastsearch' => 'Ultima ricerca',
+'modify' => 'Modifica',
+'noticefrom' => 'Informazione da',
+'hasopened' => 'ha aperto un nuovo Task su Flyspray, e l\'ha assegnato a te:',
+'moreinfonew' => 'Puoi trovare più informazioni su questo bug alla pagina FlySpray:',
+'newtaskcategory' => 'Un nuovo task è stato aperto in questa categoria',
+'categoryowner' => 'Stai ricevendo questo messaggio perchè sei il gestore della categoria.',
+'tasksummary' => 'Riassunto del Task:',
+'newtaskadded' => 'Il tuo nuovo Task è stato aggiunto.',
+'summaryanddetails' => 'Devi inserire sia il riassunto che i dettagli.',
+'summaryrequired' => 'Devi ',
+'goback' => 'Torna indietro.',
+'messagefrom' => 'Questo è un messaggio dal sistema di bug tracking Flyspray a',
+'hasjustmodified' => 'ha appena modificato il seguente task.',
+'changedfields' => 'I campi modificati sono contraddistinti da due asterischi (**)',
+'moreinfomodify' => 'Puoi avere maggiori informazioni su questo Task all\'URL seguente:',
+'nolongerassigned' => 'Il Task seguente non è piu` assegnato a te. Ora è assegnato a',
+'hasassigned' => 'ti ha assegnato il seguente task Flyspray:',
+'taskupdated' => 'Il task è stato aggiornato.',
+'tasksupdated' => 'I Task sono stati aggiornati.',
+'hasclosedassigned' => 'ha chiuso il seguente Task Flyspray che era assegnato a te:',
+'unassigned' => 'Non assegnato',
+'hasclosed' => 'ha chiuso il seguente Task.',
+'youonnotify' => 'Ricevi questo messaggio perchè sei sulla lista di notifica.',
+'taskclosedmsg' => 'Il Task è stato chiuso.',
+'returntotask' => 'Ritorna ai dettagli del Task',
+'backtoindex' => 'Torna alla lista dei Task',
+'noclosereason' => 'Non hai selezionato un motivo per chiudere questo Task.',
+'hasreopened' => 'ha riaperto il seguente Task Flyspray che avevi chiuso:',
+'taskreopenedmsg' => 'Il Task è stato riaperto.',
+'backtotask' => 'Torna indietro al Task.',
+'commentaddedmsg' => 'Il Commento è stato aggiunto.',
+'commenttoassigned' => 'ha aggiunto un commento a un Task che era stato assegnato:',
+'commenttotask' => 'ha aggiunto il seguente commento a questo Task.',
+'nocommententered' => 'Devi inserire un commento prima di cliccare il pulsante di invio.',
+'fillinfields' => 'Non hai riempito tutti i campi.',
+'notcurrentpass' => 'Non è la tua Password attuale!',
+'passchanged' => 'La tua Password è cambiata.',
+'closewindow' => 'Adesso puoi chiudere la finestra.',
+'passnomatch' => 'Le tue nuove password non coincidono!',
+'usernametaken' => 'Questo Nome Utente è già in uso. Devi sceglierne un altro.',
+'usernametakenbulk' => 'Nome Utente non disponibile',
+'newusercreated' => 'Un nuovo Utente è stato creato.',
+'accountcreated' => 'I tuo Utente è stato creato.',
+'newuserwarning' => 'Rircorda che l\'impostazione dell\'applicazione potrebbe richiedere che il tuo Utente venga approvato da un Amministratore. Se non riesci a collegarti, questa potrebbe essere la causa.',
+'nomatchpass' => 'Le Password non corrispondono.',
+'confirmwrong' => 'Il codice di conferma non è corretto!',
+'formnotcomplete' => 'Il modulo non è completo.',
+'formnotnumeric' => 'Il dato inserito non è numerico!',
+'groupnametaken' => 'Questo nome di Gruppo è già in uso.',
+'newgroupadded' => 'Aggiunto nuovo Gruppo.',
+'optionssaved' => 'Opzioni Flyspray salvate.',
+'hasuploaded' => 'ha caricato un allegato ad un Task che ti è assegnato a te:',
+'hasattached' => 'ha caricato un allegato al seguente Task.',
+'fileuploaded' => 'Il file è stato caricato.',
+'fileerror' => 'C`è stato un errore nel caricamento del file. Forse i permessi sulla directory degli allegati sono sbagliati.',
+'contactadmin' => 'Contatta l\'Amministratore di questo Progetto.',
+'selectfileerror' => 'Non hai selezionato alcun file.',
+'userupdated' => 'I dettagli Utente sono stati aggiornati',
+'realandemail' => 'Non hai riempito i campi Nome Reale e Indirizzo E-mail.',
+'groupupdated' => 'Definizione del Gruppo aggiornata.',
+'groupanddesc' => 'Non hai riempito i campi Nome Gruppo e Descrizione.',
+'fillallfields' => 'I campi vanno tutti inseriti tutti.',
+'listPmustN' => 'l\'"Ordine" deve essere numerico.',
+'listupdated' => 'La Lista è stata modificata.',
+'listitemadded' => 'Nuovo elemento aggiunto alla lista.',
+'relatedaddedmsg' => 'Task collegato aggiunto alla lista.',
+'relatederror' => 'Questo Task è gia nella lista dei Task correlati.',
+'relatedremoved' => 'Task correlato rimosso dalla lista.',
+'notifyadded' => 'Utente aggiunto alla lista di notifica.',
+'notifyerror' => 'l\'utente è gia nella lista di notifica di questo Task.',
+'notifyremoved' => 'Utente rimosso dalla lista di notifica.',
+'editcommentsaved' => 'Il commento è stato modificato.',
+'commentdeletedmsg' => 'Il Commento è stato eliminato.',
+'gotonewtask' => 'Vai al nuovo Task appena creato',
+'projectcreated' => 'Il tuo nuovo Progetto è stato creato. Segui il link sotto per definire le liste di Categorie, Versioni e Sistema Operativo',
+'customiseproject' => 'Personalizza questo Progetto',
+'projectupdated' => 'Le preferenze del Progetto sono state aggiornate',
+'emptytitle' => 'Hai lasciato vuoto il titolo del Progetto. Torna indietro e completa.',
+'loginbelow' => 'Prova a collegarti ora.',
+'attachmentdeletedmsg' => 'l\'Allegato è stato eliminato',
+'reminderaddedmsg' => 'Il tuo Promemoria è stato inserito.',
+'reminderdeletedmsg' => 'Il Promemoria selezionato è stato eliminato.',
+'flyspraytask' => 'Task Flyspray',
+'fieldsmissing' => 'Alcuni campi contenevano dati errati o mancanti.',
+'relatedinvalid' => 'Non c`è questo Task.',
+'relatedproject' => 'Il Task appartiene ad un Progetto differente.',
+'addanyway' => 'Aggiungi comunque',
+'cancel' => 'Cancella',
+'alreadyedited' => 'Il Task è stato modificato da qualcun altro prima che lo salvassi.',
+'saveanyway' => 'Salva comunque le mie modifiche',
+'nouserselected' => 'Nessun Utente selezionato. Seleziona almeno un Utente prima di riprovare.',
+'groupswitchupdated' => 'Gruppi utenti modificati con successo.',
+'takenownershipmsg' => 'Questo Task è ora assegnato a te.',
+'adminrequestmade' => 'La tua richiesta è stata trasmessa ad un Amministratore del Progetto.',
+'newdepnotify' => 'Una nuova dipendenza è stata aggiunta al seguente Task:',
+'dependadded' => 'La dipendenza èstata aggiunta',
+'dependaddfailed' => 'Non puoi aggiungere il Task come dipendenza in questo momento',
+'depremovedmsg' => 'La dipendenza del Task è stata eliminata',
+'newdepis' => 'La nuova dipendenza è',
+'magicurlsent' => 'Un messaggio è stato inviato al tuo indirizzo di notifica. Contiene un link che ti porterà ad una pagina per completare questo Task.',
+'changefspass' => 'Cambia la Password di Flyspray',
+'magicurlmessage' => 'Segui questo link per cambiare la tua password Flyspray:',
+'erroronform' => 'C`è stato un problema su questo modulo',
+'addressused' => 'Questo indirizzo è stato usato per registrare un account Flyspray. Se non stavi aspettando questo messaggio, ti prego di ignorarlo e di cancellarlo. Il tuo codice di conferma è:',
+'confirmcodeis' => 'Il tuo codice di conferma è:',
+'codesent' => 'Il tuo codice di conferma è stato inviato. Prego segui le istruzioni contenute nel messaggio.',
+'codenotsent' => 'Non posso inviarti il codice, per favore riprova piu` tardi',
+'taskmadeprivatemsg' => 'Questo Task è stato reso privato',
+'taskmadepublicmsg' => 'Questo Task è stato reso pubblico',
+'realandnotify' => 'Devi riempire il campo Nome Reale, ed uno tra i campi Indirizzo Email o Jabber ID.',
+'pmreqdeniedmsg' => 'Richiesta all\'Amministratore del Progetto rifiutata',
+'massopsenable' => 'Abilita la possibilità di modificare più task con una singola operazione.',
+'massopsuccess' => 'Operazione globale terminata con successo - ove consentito dalle autorizzazioni',
+'usernotexist' => 'l\'Utente non esiste in questa installazione di Flyspray',
+'commentattachperms' => 'Non puoi cancellare il commento - non hai l\'autorizzazione per cancellare gli allegati',
+'voterecorded' => 'Il tuo Voto è stato registrato',
+'votefailed' => 'Il tuo Voto non può essere inserito in questo momento.',
+'createnewgroup' => 'Crea un nuovo Gruppo',
+'requiredfields' => 'I campi obbligatori sono identificati con un',
+'addthisgroup' => 'Aggiungi questo Gruppo',
+'createnewproject' => 'Crea un nuovo Progetto',
+'addnewproject' => 'Aggiungi un nuovo Progetto',
+'htmlallowed' => 'è possibile inserire codice HTML',
+'createthisproject' => 'Crea questo Progetto',
+'inlineimages' => 'Mostra le immagini allegate inline',
+'createnewtask' => 'Crea un nuovo Task',
+'addanother' => 'Aggiungi un altro Task dopo questo',
+'addthistask' => 'Aggiungi questo Task',
+'notifyme' => 'Avvisami quando questo Task cambia',
+'newtask' => 'Nuovo Task:',
+'attachafile' => 'Allega un file',
+'registernewuser' => 'Registra nuovo Utente',
+'none' => 'Nessuna',
+'registeraccount' => 'Registra questo Account',
+'registerbulkaccount' => 'Registrati',
+'both' => 'Entrambi',
+'notifyfrom' => 'Notifica da',
+'donotreply' => 'QUESTO è UN MESSAGGIO AUTOMATICO, NON RISPONDERE.',
+'disclaimer' => 'Hai ricevuto questo messaggio perché lo hai richiesto al sistema di bugtracking Flyspray. Puoi disabilitare le notifiche future visitando l\'indirizzo qui sopra.',
+'userwho' => 'Utente che ha effettuato l\'operazione',
+'moreinfo' => 'Maggiori informazioni disponibili all\'indirizzo:',
+'newtaskopened' => 'Un nuovo Task è stato aperto su Flyspray. Maggiori dettagli di seguito.',
+'notify.taskclosed' => 'Il Task seguente è stato chiuso:',
+'notify.taskreopened' => 'Il Task seguente è stato riaperto:',
+'newdep' => 'Il Task seguente ha una nuova dipendenza:',
+'notify.depremoved' => 'Al Task seguente è stata rimossa una dipendenza:',
+'olddepwas' => 'La vecchia dipendenza era',
+'notify.commentadded' => 'Al Task seguente è stato aggiunto un commento:',
+'commentis' => 'Il commento è indicato di seguito.',
+'newattachment' => 'Un nuovo file è stato allegato al seguente Task:',
+'detailsbelow' => 'Di seguito i dettagli.',
+'notify.relatedadded' => 'Un nuovo Task collegato è stato aggiunto al seguente Task:',
+'relatedis' => 'Il Task collegato è',
+'assignedtoyou' => 'Ti è stato assegnato il seguente Task:',
+'takenownership' => 'Questo task non è assegnato a te.',
+'requiresaction' => 'Il seguente Task richiede l\'intervento di un Amministratore del Progetto:',
+'requiresactionnotify' => 'Il Task richiede l\'intervento di un Amministratore del Progetto',
+'pmdeny' => 'Un Amministratore del Progetto ha rifiutato la richiesta sottomessa per il seguente Task:',
+'pmdenynotify' => 'Un Amministratore del Progetto ha rifiutato la richiesta',
+'fileaddedtoo' => 'C`erano già uno o piu` file allegati a questo commento.',
+'taskwatching' => 'Il seguente Task che stai controllando',
+'isdepfor' => 'è una nuova dipendenza per',
+'denialreason' => 'Motivo del rifiuto',
+'taskchanged' => 'Il seguente Task è stato modificato. Le modifiche sono elencate qui` sotto. Per informazioni piu` dettagliate sulle modifiche visita l\'indirizzo seguente e clicca sullo Storico.',
+'useraddedtoassignees' => 'Un Utente ha aggiunto sè stesso alla lista degli utenti assegnati a questo Task.',
+'removeddepis' => 'La dipendenza rimossa è',
+'isnodepfor' => 'non è piu` una dipendenza per',
+'usergroups' => 'Utenti e Gruppi',
+'pmtoolbox' => 'Strumenti dell\'Amministratore Progetto',
+'groupmanage' => 'Gestione del Gruppo',
+'pendingrequests' => 'Richieste amministrative incomplete',
+'reasongiven' => 'Motivazione inserita',
+'nopendingreq' => 'Non ci sono richieste per l\'Amministratore del Progetto in sospeso.',
+'givereason' => 'Inserisci una motivazione',
+'catlisted' => 'Editor delle Categorie',
+'oslisted' => 'Editor dei Sistemi Operativi',
+'verlisted' => 'Editor delle Versioni',
+'tasktypeed' => 'Editor delle Tipologie di Task',
+'resed' => 'Editor delle Soluzioni',
+'deny' => 'Nega',
+'notifiedwhen' => 'Notificato quando',
+'onlynewtasks' => 'Vengono aperti nuovi Task',
+'allevents' => 'Un evento qualsiasi su qualsiasi Task',
+'feeds' => 'Feeds',
+'feeddescription' => 'Descrizione Feed',
+'feedimgurl' => 'Indirizzo dell\'immagine per il Feed (lasciare vuoto per selezionare nessuna immagine)',
+'notifysubject' => 'Oggetto delle notifiche',
+'notifysubjectinfo' => '(%p = titolo del Progetto, %s = riassunto del Task, %t = id del Task, %a = azione, %u = nome utente)',
+'priority6' => 'Molto Alta',
+'priority5' => 'Alta',
+'priority4' => 'Media',
+'priority3' => 'Bassa',
+'priority2' => 'Molto Bassa',
+'priority1' => 'Rinviare',
+'sendcode' => 'Invia il codice!',
+'entercode' => 'Il tuo codice di conferma è stato inviato usando il tuo metodo di notifica preferenziale. Quando lo ricevi, inseriscilo qui sotto. Inserisci anche la Password dell\'Account.',
+'confirmationcode' => 'Codice di conferma',
+'registererror' => 'Accertati di avere riempito tutti i campi obbligatori, e di aver inserito i giusti dettagli per il metodo di notifica preferenziale richiesto.',
+'validusername' => '(sono consentiti solo caratteri alfanumerici e - _ .)',
+'validemail' => '(utilizzare ; per separare più indirizzi email)',
+'emailtakenbulk' => 'Indirizzo email già utilizzato',
+'emailtaken' => 'l\'indirizzo Email o lo Jabber-ID sono già in uso. Devi selezionarne un altro.',
+'note' => "<b>Nota:</b> Ti sara' inviato un codice di conferma prima che il tuo account venga\ncreato. Il codice ti sara' inviato usando il tuo metodo di notifica preferenziale sopra indicato.<br>SE INSERISCI INFORMAZIONI FALSE, NON RICEVERAI IL CODICE.",
+'changelog' => 'Changelog',
+'changeloggen' => 'Generatore di Changelog',
+'listfrom' => 'Crea una lista di changelog da',
+'to' => 'a',
+'oldestfirst' => 'Prima i piu` vecchi',
+'recentfirst' => 'Prima i piu` nuovi',
+'severityrep' => 'Rapporto per Gravità',
+'totalopen' => 'totale Task aperti',
+'age' => 'Età',
+'agerep' => 'Rapporto per età',
+'eventsrep' => 'Rapporto eventi',
+'events' => 'Eventi',
+'Tasks' => 'Task',
+'opened' => 'Aperti',
+'edited' => 'Modificati',
+'assigned' => 'Assegnati',
+'within' => 'Entro',
+'pastday' => 'Ieri',
+'pastweek' => 'La scorsa settimana',
+'pastmonth' => 'Il mese scorso',
+'pastyear' => 'l\'anno scorso',
+'nolimit' => 'Nessun limite',
+'from' => 'Da',
+'duein' => 'Previsto entro',
+'selectfromdate' => 'Seleziona Data a Partire Da',
+'selecttodate' => 'Seleziona Data Fino A',
+'showvoters' => 'Mostra/nascondi votanti',
+'roadmap' => 'Roadmap',
+'roadmapfor' => 'Roadmap per la Versione',
+'tasks' => 'Task',
+'completed' => 'completato.',
+'opentasks' => 'Task aperti',
+'of' => '% di',
+'severity5' => 'Critica',
+'severity4' => 'Alta',
+'severity3' => 'Media',
+'severity2' => 'Bassa',
+'severity1' => 'Molto Bassa',
+'Redirect' => 'Redireziona',
+'redirectmsg' => 'Se il tuo browser non supporta la meta-redirezione per favore clicca qui %sHERE%s per essere rediretto',
+'allowclosedcomments' => 'Consenti commenti sui Task chiusi',
+'comment' => 'Commento',
+'editowncomments' => 'Modifica i propri commenti',
+'reopened' => 'Riaperto',
+'loading' => 'Caricamento in corso...',
+'notifyown' => 'Notifica per le modifiche fatte da me stesso',
+'youremail' => 'Il tuo indirizzo E-mail',
+'thankyouforbug' => 'Grazie per aver segnalato i problemi che hai riscontrato. Puoi vedere il task e seguirne l\'avanzamento in ogni momento tramite il seguente indirizzo:',
+'anonuser' => 'Utente anonimo',
+'conflict' => 'Conflitto',
+'file' => 'File',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Dimensioni',
+'projectgroup' => 'Gruppo',
+'profile' => 'Profilo:',
+'viewprofile' => 'Visualizza Profilo',
+'regdate' => 'Registrato dal',
+'tasksopened' => 'Task aperti',
+'replyto' => 'Rispondi a',
+'notifytypes' => 'Tipi di notifica',
+'pm.taskchanged' => 'Task modificato',
+'pm.taskreopened' => 'Task riaperto',
+'pm.depadded' => 'Dipendenza inserita',
+'pm.depremoved' => 'Dipendenza eliminata',
+'pmrequest' => 'Richiesta PM',
+'pmrequestdenied' => 'Richiesta PM respinta',
+'newassignee' => 'Nuovo assegnatario',
+'revdepadded' => 'Dipendenza inversa aggiunta',
+'revdepaddedremoved' => 'Dipendenza inversa eliminata',
+'assigneeadded' => 'Assegnatario aggiunto',
+'addusergroup' => 'Aggiungi Utente a questo Gruppo',
+'groupmembers' => 'Membri del Gruppo',
+'deleteuser' => 'Elimina questo Utente',
+'userdeleted' => 'Utente eliminato',
+'autoassign' => 'Assegna automaticamente un Task al proprietario della Categoria',
+'ssl' => 'SSL',
+'updatewrong' => "Hai selezionato l'opzione di aggiornamento ma si e' verificato un errore nel\n collegamento al server, il tuo host non consente connessioni all'esterno\n oppure c'e' un problema di rete.\n Per favore visita il sito di Flyspray e assicurati di avere installata la versione piu' recente.",
+'deleteproject' => 'Elimina questo Progetto e sposta il contenuto in',
+'projectdeleted' => 'Progetto eliminato con successo',
+'feedforall' => 'Feed per tutti i progetti',
+'usercreated' => 'Utente creato',
+'created' => 'Creato',
+'deleted' => 'Eliminato',
+'userid' => 'ID Utente',
+'editassignments' => 'Modifica assegnazioni',
+'preview' => 'Anteprima',
+'anyprogress' => 'Qualsiasi Avanzamento',
+'tasksrelated' => 'Task collegati a questo Task',
+'duplicatetasks' => 'Task duplicati di questo Task',
+'databasemodfailed' => 'Modifica al Database fallita. Una delle possibili cause sono autorizzazioni insufficienti.',
+'frequency' => 'Frequenza',
+'newuserregistered' => 'Un nuovo Utente si è registrato. Di seguito i dettagli:',
+'newuserregisterednotify' => 'Un nuovo Utente si è registrato',
+'notify_registration' => 'Avvisa gli Amministratori sui nuovi utenti registrati',
+'textversion' => 'Versione solo testo',
+'onlyprimary' => 'Task che non bloccano altri Task',
+'onlyblocker' => 'Task che bloccano altri Task',
+'blockerornoblocker' => 'Bloccante o non-bloccante. Scegliere entrambi non ha senso.',
+'switch' => 'Vai a',
+'max' => 'massimo',
+'dates' => 'Date',
+'selectduedatefrom' => 'Data Scadenza dal',
+'selectduedateto' => 'al',
+'selectsincedatefrom' => 'Modificato dal',
+'selectsincedateto' => 'al',
+'selectdate' => 'Seleziona Data',
+'selectopenedfrom' => 'Aperto dal',
+'selectopenedto' => 'al',
+'selectclosedfrom' => 'Chiuso dal',
+'selectclosedto' => 'al',
+'startat' => 'Inizia dal',
+'hasattachment' => 'Con allegati',
+'private' => 'Privato',
+'watching' => 'In osservazione',
+'alreadyvotedthistask' => 'hai già votato per questo Task',
+'alreadyvotedthisday' => 'hai già votato oggi',
+'visibility' => 'Visibilità',
+'public' => 'Pubblico',
+'leaveemptyauto' => 'Lascia vuoti i campi della password se vuoi che una password sia generata automaticamente per te.',
+'novalidemail' => 'l\'indirizzo Email inserito non è valido.',
+'novalidjabber' => 'l\'ID Jabber inserito non è valido.',
+'missingrequired' => 'Non hai inserito tutti i campi richiesti.',
+'entervalidusername' => 'Inserisci un Utente valido ed un Nome Reale.',
+'couldnotaddusernotif' => 'Impossibile aggiungere questo Utente alla lista delle notifiche.',
+'defaulttask' => 'Descrizione di default dei Task',
+'all' => 'tutti',
+'events.useraddedtoassignees'=> 'Utente aggiunto agli assegnatari',
+'eventlog' => 'Log degli eventi',
+'assignmentchanged' => 'Modificata assegnazione',
+'detailedinfo' => 'Informazioni dettagliate',
+'All' => 'Tutti',
+'tasksireported' => 'Task riportati da me',
+'recentlyopened' => 'Aperti recentemente',
+'stats' => 'Statistiche',
+'totaltasks' => 'task totali',
+'mostwanted' => 'Task piu` cercati',
+'defaultentry' => 'Pagina iniziale predefinita',
+'toplevel' => 'Vista superiore',
+'overview' => 'Riepilogo',
+'error#' => 'Errore #',
+'error1' => 'Non hai privilegi sufficienti per vedere questo allegato.',
+'error3' => 'Azione ripetuta, redirezione alla pagina principale.',
+'error4' => 'Non hai i privilegi di amministrazione.',
+'error5' => 'Utente sconosciuto a questa installazione di Flyspray.',
+'error6' => 'Area di amministrazione non valida.',
+'error7' => 'Login fallito (Nome Utente o Password errati)!',
+'error71' => 'Account bloccato per %d minuto a causa dei troppi tentativi falliti!',
+'error8' => 'Non hai inserito il Nome Utente o la Password.',
+'error9' => 'Il Task non esiste o non sei autorizzato a vederlo.',
+'error10' => 'Il Task non esiste o non sei autorizzato a vederlo.',
+'error101' => 'Non hai i permessi per guardare questo task.',
+'error102' => 'Non hai i permessi per guardare questo task, fare il login potrebbe aiutare.',
+'error11' => 'Non hai l\'autorizzazione per modificare questo commento.',
+'error12' => 'Magic key non valida! Sicuro di averla ricevuta dal tuo messaggio di notifica?',
+'error13' => 'Gli utenti anonimi non hanno un profilo.',
+'error14' => 'Non hai privilegi sufficienti per creare nuovi Gruppi.',
+'error15' => 'Non hai privilegi sufficienti per arprie nuovi Task.',
+'error16' => 'Non sei un Amministratore del Progetto.',
+'error17' => 'Area PM non valida.',
+'error18' => 'Indirizzo non valido.',
+'error19' => 'Utente sconosciuto a questa installazione di Flyspray.',
+'error20' => 'Modifica al database non valida.',
+'error21' => 'Errore nell\'invio di una o piu` Email. Verifica la tua configurazione.',
+'error22' => 'Non è possibile registrare nuovi Utenti.',
+'error23' => 'Utente o Gruppo non abilitato.',
+'error24' => 'Un eseguibile di Dot o un server pubblico Dot non sono stati configurati.',
+'error25' => 'La Roadmap è disponibile solo per un Progetto specifico.',
+'error26' => 'Provider OAuth non supportato.',
+'error27' => 'Impossibile collegarsi. Autorizzaci a visualizzare il tuo indirizzo email.',
+'error28' => 'Non si hanno i permessi per accedere a questa area.',
+'done' => 'completato',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Il Progetto non può essere eliminato.',
+'GMT' => 'GMT',
+'timezone' => 'Fuso Orario',
+'accept' => 'Accetto',
+'reasonfordeinal' => 'Ragione del rifiuto',
+'pruneclosedlinks' => 'Ripulisci i Link Chiusi',
+'pruneclosedtasks' => 'Ripulisci i Task Chiusi',
+'pagegenerated' => 'Pagina e immagine generate in %d secondi.',
+'pruninglevel' => 'Livello di Pulizia',
+'lastuser' => 'l\'ultimo Utente non può essere eliminato.',
+'allprivate' => 'Tutti i Progetti sono privati.',
+'deletegroup' => 'Elimina questo Gruppo e sposta gli Utenti in',
+'parent' => 'Categoria superiore',
+'ordertip' => 'Ordine in cui queste voci compariranno nella lista',
+'showtip' => 'Mostra questa voce nella lsita',
+'deletetip' => 'Cancella questa voce dalla lista',
+'del' => 'cancella',
+'request1' => 'è stata richiesta la chiusura di un task',
+'request2' => 'è stata richiesta la riapertura del task',
+'allpriorities' => 'Tutte le Priorità',
+'noroadmap' => 'Nessuna roadmap disponibile (non ci sono versioni "future" previste per questo progetto).',
+'expand' => 'Espandi',
+'collapse' => 'Contrai',
+'expandall' => 'Espandi tutto',
+'collapseall' => 'Contrai tutto',
+'minpwsize' => 'La lunghezza minima della password è di 5 caratteri',
+'passwordtoosmall' => 'La password è troppo corta.',
+'accountwaslocked' => 'Il tuo account è stato bloccato a causa di troppi tentativi di login falliti.',
+'failedattempts' => 'Ci sono stati %d tentativi di login falliti.',
+'groupnotexist' => 'Il gruppo selezionato non esiste in questo progetto.',
+'searchindetails' => 'Cerca nei dettagli',
+'showasassignees' => 'Mostra come Assegnato',
+'find' => 'Trova',
+'tls' => 'TLS',
+'isadmin' => 'È admin',
+'addvotes' => 'Aggiungi Voti',
+'removevote' => 'Rimuovi Voto',
+'voteremoved' => 'Il tuo voto è stato rimosso',
+'voteremovefailed' => 'Il voto non può essere rimosso in questo momento.',
+'novotes' => 'Non hai voti su nessun task.',
+'connectedtasks' => 'Task connessi',
+'taskdependencies' => 'Dipendenze task',
+'viewgraph' => 'Vedi Grafico',
+'notaskdependencies' => 'Questo task non dipende su nessun task',
+'dependson' => 'Dipende da',
+'blocks' => 'Blocchi',
+'newdependency' => 'Nuove dipendenze',
+'nouserstoadd' => 'Nessun utente da aggiungere. Assicurarsi che Nome, Cognome e Email siano definite per ogni utente.',
+'dispintro' => 'Mostra messaggio introduttivo principale',
+'mainmessage' => 'Messaggio introduttivo principale',
+'setsupertask' => 'Imposta ID Super-Task:',
+'supertaskmodified' => 'ID Super-Task è stato modificato',
+'set' => 'Imposta',
+'supertask' => 'Super-Task',
+'setparent' => 'Imposta id del task genitore per questo task',
+'selfsupertasknotallowed' => 'L\'ID del super-task non può essere uguale all\'ID dell\'attuale Task',
+'quickaction' => 'Azioni veloci',
+'updateselectedtasks' => 'Aggiorna i Task Selezionati',
+'notspecified' => 'Non specificato',
+'editselectedtasks' => 'Modifica i Task Specificati',
+'information' => 'Informazione',
+'taskclosedisabled' => 'La Chiusura Task è attualmente disabilitata in quanto i seguenti task dipendenti sono ancora aperti:-',
+'daysleft' => 'Giorni mancanti',
+'dayoverdue' => 'Giorni di Ritardo',
+'duetoday' => 'Da fare oggi.',
+'daysbeforealert' => 'Giorni prima dell\'avviso',
+'associatedsubtask' => 'Sotto-task associato correttamente',
+'associatesubtask' => 'Associa l\'ID task con questo task',
+'subtaskid' => 'ID Sotto-task',
+'subtaskalreadyhasparent' => 'Il sotto task inserito ha già un task genitore. Si prega di correggere prima di associare.',
+'subtaskisparent' => 'Il sotto-task inserito è il task genitore di questo task. Sotto-task non associato.',
+'subtasknotexist' => 'Il sotto task inserito non esiste.',
+'subtaskremovedmsg' => 'Il sotto task è stato rimosso con successo',
+'subtaskadded' => 'Sotto task aggiunto',
+'subtaskremoved' => 'Sotto task rimosso',
+'addnewsubtask' => 'Aggiungi un sotto-task',
+'hidesubtasks' => 'Nascondi Sotto-task',
+'voteforthistask' => 'Vota per questo task',
+'watchthistask' => 'Visualizza questo task',
+'privatethistask' => 'Rendi il task privato',
+'adddependenttask' => 'Associa task dipendente',
+'associatetaskid' => 'ID Sotto-task',
+'parenttaskid' => 'ID Task Genitore',
+'invalidsupertaskid' => 'L\'id del Task genitore non è valido',
+'supertaskadded' => 'Supertask aggiunto',
+'supertaskremoved' => 'Supertask rimosso',
+'effort' => 'Impegno',
+'efforttracking' => 'Tracciamento Impegno',
+'useeffort' => 'Il progetto utilizza il Tracciamento dell\'Impegno',
+'estimatedeffort' => 'Impegno Stimato',
+'totalestimatedeffort' => 'Totale impegno stimato',
+'currenteffortdone' => 'Attuale impegno impiegato',
+'starteffort' => 'Avvia Tracciamento',
+'endeffort' => 'Ferma Tracciamento',
+'cleareffort' => 'Pulisci Tracciamento',
+'addeffort' => 'Aggiungi Impegno',
+'manualeffort' => 'Aggiungi manualmente Impegno (H:M)',
+'efforttrackingstarted' => 'Monitoraggio impegno avviato per questo task ',
+'efforttrackingnotstarted'=> 'Impossibile iniziare a monitorare su di un problema già in monitoraggio.',
+'efforttrackingstopped' => 'Monitoraggio Impegno terminato per questo task',
+'efforttrackingcancelled' => 'Monitoraggio Impegno cancellato per questo task',
+'efforttrackingadded' => 'Impegno registrato manualmente per il task',
+'trackinginprogress' => 'Monitoraggio in corso',
+'viewestimatedeffort' => 'Può visualizzare il monitoraggio dell\'Impegno',
+'viewcurrenteffortdone' => 'Può visualizzare l\'attuale Impegno impiegato',
+'trackeffort' => 'Può monitorare l\'Impegno',
+'invalideffort' => 'L\'Impegno inserito non è valido. Devono essere solo numeri',
+'showpass' => 'Mostra Password',
+'chooseafile' => 'Si prega di scegliere un file!',
+'incorrectfiletype' => 'Tipo di file non corretto. Sono permessi: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Impossibile richiedere la password. Ti sei registrato tramite %s',
+'addmultipletasks' => 'Aggiungi più task',
+'pendingnewuserrequest' => 'Richieste di Nuovi Utenti in Attesa',
+'adminrequestswaiting' => 'Richieste admin in attesa',
+'clicktoedit' => 'Clicca su un campo per modificarlo velocemente',
+'confirmedit' => 'Conferma',
+'regapprovedbyadmin' => 'Registrazioni approvate dagli admin (disabilita codice di conferma)',
+'activity' => 'Attività',
+'myactivity' => 'Le mie attività',
+'emailverificationwrong' => 'L\'email di conferma non corrisponde con l\'indirizzo email fornito',
+'verifyemailaddress' => 'Conferma indirizzo email',
+'hideemails' => 'Nascondi l\'indirizzo email degli utenti',
+'hidemyemail' => 'Nascondi il mio indirizzo email',
+'exporttasklist' => 'Esporta elenco Task',
+'onedecimal' => 'Un decimale',
+'manday' => 'uomo-giornata',
+'mandays' => 'uomo-giornate',
+'mandayabbrev' => 'md',
+'hourspermanday' => 'Ore per Uomo a Giornata (HH:mm)',
+'itemexists' => 'L\'elemento %s già esiste nel database.',
+'categoryitemexists' => 'L\'elemento %s già esiste nella categoria %s nel database.',
+'pageswelcomemsg' => 'Pagine dove mostrare il messaggio introduttivo principale',
+'pagesintromsg' => 'Pagine dove mostrare il messaggio introduttivo',
+'activeoauths' => 'Provider OAuth attivi',
+'onlyoauthreg' => 'Permetti solo registrazioni tramite OAuth',
+'estimatedeffortformat' => 'Formato di visualizzazione Impegno stimato',
+'currenteffortdoneformat' => 'Formato di visualizzazione Impegno attuale',
+'minute' => 'minuto',
+'minutes' => 'minuti',
+'minuteplural' => 'minuti',
+'minutesingular' => 'minuto',
+'minuteabbrev' => 'min',
+'hourplural' => 'ore',
+'hoursingular' => 'ora',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Impegno stimato per i task aperti',
+'currenteffortdoneopen' => 'Impegno speso nei task aperti',
+'signinwith' => 'Accedi con %s',
+'canviewroadmap' => 'Può vedere la roadmap',
+'enableavatars' => 'Abilita gli avatar',
+'maxavatarsize' => 'Dimensione massima avatar in pixel',
+'taskhassubtask' => 'Questo task presenta il seguente sotto-task',
+'taskhassubtasks' => 'Questo task presenta i seguenti sotto-task',
+'translations' => 'Traduzioni',
+'translate' => 'Traduci',
+'taskdescription' => 'Descrizione Task',
+'notaskdescription' => 'Nessuna descrizione per il Task',
+'pleaseselect' => 'Scegliere',
+'closeselectedtasks' => 'Chiudi i task selezionati',
+'closetasks' => 'Chiudi Task',
+'hintforbulkimport' => "<b>Consigli per l'importazione in blocco:</b>\n<ol>\n<li>Copia e incolla da un file Excel o CSV un'intera colonna.</li>\n<li>Attualmente è possibile incollare solo Riepilogo e Dettagli.</li>\n<li>Ci sono suggerimenti quando assegni a qualcuno e a \"nessuno\" se non vengono trovate corrispondenze.</li>\n</ol>",
+'taskissubtaskof' => 'Questo task è un Sotto-task di',
+'applyfirstline' => 'Applica la prima linea',
+'addmorerows' => 'Aggiungi più righe',
+'addtasks' => 'Aggiungi Task',
+'massopsdisabled' => 'Spiacenti ma la modifica multipla è attualmente disabilitata, abbiamo pianificato l\'implementazione in una versione successiva ma nel frattempo puoi abilitarla dal codice sorgente a tuo rischio. Ti consigliamo comunque di leggere i commenti nel codice prima di farlo.',
+'viewroadmap' => 'Può visualizzare la roadmap',
+'nosuicide' => "Gentile utente, il programma non permette di disabilitare il tuo account o il tuo gruppo.\nIl fratello empatico di HAL9000.",
+'movingtodifferentproject'=> 'Non è permesso spostare task che hanno un task padre o sotto-task in un altro progetto. E\' necessario slegarli dai relativi task nel progetto originario.',
+'musthavesameproject' => 'I task genitore e sotto-task devono appartenere allo stesso progetto',
+'defaultorderby' => 'Ordina l\'elenco dei task per',
+'defaultorderby2' => 'e poi per',
+'viewowntasks' => 'Visualizza i tuoi task',
+'viewgroupstasks' => 'Visualizza i task dei gruppi',
+'urlrewriting' => 'Riscrittura URL',
+'enablehtaccess' => 'Abilita il tuo file .htaccess nella cartella principale prima di abilitare l\'url rewriting',
+'nomodrewrite' => 'Mod rewrite non sembra essere disponibile su questo server. Impossibile abilitare url rewriting.',
+'on' => 'Abilita',
+'off' => 'Disabilita',
+'defaultorderbydirection' => 'Ordine predefinito',
+'ascending' => 'Ascendente',
+'descending' => 'Discendente',
+'myassignedtasks' => 'I miei task',
+'commentedon' => 'Commentato il',
+'maxvoteperday' => 'Numero Massimo di voti per giorno',
+'votesperproject' => 'Limite di voti utente per progetto',
+'votelimitreached' => "Hai raggiunto il tuo limite di voti per questo progetto. Verifica la pagina del tuo profilo per vedere per quali task stai votando attuanlmente.\n",
+'myvotes' => 'I miei voti',
+'tag' => 'Tag',
+'tags' => 'Tag',
+'tagsinfo' => 'Inserisci liberamente tag per i task. Separa i tag con ; (punto e virgola). Non vengono attualmente usati per ricerca, ordinamento o filtro.',
+'novalues' => 'Nessun elemento',
+'youhaveregistered' => "Ti seri registrato.\nLe tue informazioni sono le seguenti:",
+'youhaveregisterednotify' => 'La tua registrazione è stata accettata dagli amministratori.',
+'usedintasks' => 'Utilizzo',
+'freetagging' => 'Permetti agli utenti di definire i tag',
+'keyboardshortcuts' => 'Scorciatoie da tastiera',
+'testmailsettings' => 'Prova le impostazioni email',
+'test' => 'Prova',
+'testmailsettingsnotice' => 'Verifica anche di aver ricevuto l\'email di prova all\'indirizzo dell\'account corrente (indirizzo impostato nel profilo)',
+'invalidinput' => 'Ci sono alcuni valori mancanti o erroneamente inviati',
+'invalidprogress' => 'Seleziona un valore valido per il Progresso',
+'invalidpriority' => 'Seleziona un valore valido per la Priorità',
+'invalidseverity' => 'Seleziona un valore valido per la Gravità',
+'invalidstatus' => 'Seleziona uno stato valido per il Task',
+'invalidtasktype' => 'Seleziona un tipo di task valido',
+'invalidcategory' => 'Seleziona una Categoria valida',
+'invalidreportedversion' => 'Seleziona una versione valida per il Report',
+'invaliddueversion' => 'Seleziona una Versione valida come versione prevista',
+'invalidos' => 'Seleziona un Sistema Operativo valido',
+'invalidtags' => 'Seleziona solo i tag consentiti',
+'invalidassignees' => 'Seleziona solo Assegnatari validi',
+'invalidtargetproject' => 'Seleziona un progetto di destinazione valido',
+'customize' => 'Personalizza',
+'hidesubs' => 'Nascondi task collegati',
+'hideprivate' => 'Nascondi task privati',
+'hideclosed' => 'Nascondi task chiusi',
+'globaloptions' => 'opzioni globali',
+'currentproject' => 'Progetto corrente',
+'targetproject' => 'Progetto destinazione',
+'availablekeybshortcuts' => 'Combinazioni disponibili',
+'logindialoglogout' => 'Dialog di Login / Logout',
+'focustaskidsearch' => 'Attiva campo ricerca Task ID',
+'openselectedtask' => 'Apri il Task selezionato',
+'movecursorup' => 'Muovi il cursore giù',
+'movecursordown' => 'Muovi il cursore su',
+'taskdetails' => 'Dettagli Task',
+'taskediting' => 'Modifiche Task',
+'savetask' => 'Salva Task',
+'generalintegration' => 'Stringhe da integrare',
+'generalintegrationdesc' => 'Il contenuto viene inserito nel body dal tema di default, sotto il contenuto principale e prima del footer. Dove sarà effettivamente visualizzato sullo schermo dipende dal CSS. Usa un tema personalizzato custom_*.css per il posizionamento.',
+'footerintegration' => 'Stringhe da integrare nel footer',
+'footerintegrationdesc' => 'Il contenuto viene inserito nel footer dal tema di default. Dove sarà effettivamente visualizzato sullo schermo dipende dal CSS. Usa un tema personalizzato custom_*.css per il posizionamento.',
+'editorbold' => 'G',
+'editorboldhint' => 'Grassetto',
+'editoritalic' => 'C',
+'editoritalichint' => 'Corsivo',
+'editorunderline' => 'S',
+'editorunderlinehint' => 'Sottolineato',
+'editorstrikethrough' => 'B',
+'editorstrikethroughhint' => 'Barrato',
+'captchaerror' => 'CAPTCHA errato. Riprova.',
+'registercaptcha' => 'Risolvi il CAPTCHA.',
+'regcaptcha' => 'Visualizza il CAPTCHA per le nuove registrazioni utente.',
+'invalidsecurimage' => 'CAPTCHA non valido.',
+'invalidrecaptcha' => 'Google reCAPTCHA non valido.',
+'antispam' => 'Prevenzione SPAM',
+'antispamprefsinfo' => 'Impostazioni per le contromisure anti spambots. Attualmente vengono utilizzati Securimage e Google reCAPTCHA per le nuove registrazioni utente.',
+'securimageprefsinfo' => 'Un CAPTCHA di tipo classico dove gli utenti devono riconoscere una sequenza di testo per dimostrare di essere umani.',
+'securimageenable' => 'Attiva Securimage.',
+'recaptchaprefsinfo' => "Google reCAPTCHA è un'alternativa al classico CAPTCHA.\nIn base ai dati registrati, al comportamento e altre tipologie di domande basate su immagini, Google decide se sei un umano o un bot.\nPer usarlo hai bisogno di un account Google e di configurare reCAPTCHA per il tuo dominio all'indirizzo https://www.google.com/recaptcha\ndopodiché inserire \"sitekey\" e \"secret\".\nIn base alla configurazione su Google (\"reCAPTCHA V2\" oppure \"Invisible reCAPTCHA\") potresti dover informare i tuoi utenti sulle policy di protezione dati.",
+'recaptchaenable' => 'Abilita Google reCAPTCHA',
+'adminchecks' => 'Verifiche',
+'adminchecksinfo' => 'Informazioni e verifiche sulla tua installazione',
+'repeatpassword' => 'Ripeti Password',
+'repeatemailaddress' => 'Ripeti Indirizzo Email',
+'tooltipshorttasktitle' => 'Scrivi una sintesi che renda comprensibile il task a colpo d\'occhio',
+'lastlogin' => 'Ultimo accesso',
+);
+
+?>
diff --git a/lang/ja.php b/lang/ja.php
new file mode 100644
index 0000000..9792fb1
--- /dev/null
+++ b/lang/ja.php
@@ -0,0 +1,829 @@
+<?php
+
+$translation = array(
+'edituser' => 'ユーザ情報ã®å¤‰æ›´',
+'username' => 'ユーザå',
+'realname' => 'æ°å',
+'emailaddress' => 'E-mailアドレス',
+'jabberid' => 'Jabber ID',
+'notifytype' => '通知方法',
+'group' => 'グループ',
+'accountenabled' => '有効ãªã‚¢ã‚«ã‚¦ãƒ³ãƒˆ',
+'updatedetails' => '詳細更新',
+'setglobally' => 'ã“ã®è¨­å®šã¯ã‚°ãƒ­ãƒ¼ãƒãƒ«ã«è¨­å®šã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'usergroupmanage' => 'ユーザã¨ã‚°ãƒ«ãƒ¼ãƒ—ã®ç®¡ç†',
+'newuser' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ã®ç™»éŒ²',
+'newgroup' => 'æ–°è¦ã‚°ãƒ«ãƒ¼ãƒ—ã®ç™»éŒ²',
+'yes' => 'ã¯ã„',
+'no' => 'ã„ã„ãˆ',
+'editgroup' => 'グループ情報ã®å¤‰æ›´',
+'groupname' => 'グループå',
+'description' => '説明',
+'admin' => 'Admin group',
+'opennewtasks' => 'æ–°è¦ã‚¿ã‚¹ã‚¯ã‚’登録',
+'modifytasks' => '既存タスクを修正',
+'addcomments' => 'コメントを追加',
+'attachfiles' => 'ファイル添付',
+'vote' => '投票',
+'groupenabled' => 'メンãƒã®ãƒ­ã‚°ã‚¤ãƒ³',
+'tasktypelist' => 'タスク種別一覧',
+'categorylist' => 'カテゴリ一覧',
+'oslist' => 'OS一覧',
+'resolutionlist' => 'クローズç†ç”±ä¸€è¦§',
+'versionlist' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³ä¸€è¦§',
+'severitylist' => 'é‡å¤§åº¦ä¸€è¦§',
+'listnote' => '注æ„: 「表示ã€ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ã‚’解除ã™ã‚‹ã¨ã€ãã®è¨­å®šå€¤ã‚’æŒã£ãŸã‚¿ã‚¹ã‚¯ã®ä¿®æ­£ç”»é¢ã§ãã®å€¤ã‚’変更ã—ã¦ã—ã¾ã†ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“。「å称ã€æ¬„を変更ã™ã‚‹ã¨ã€ãã®å称ã®è¨­å®šå€¤ã ã£ãŸã™ã¹ã¦ã®ã‚¿ã‚¹ã‚¯ã‚’変更ã™ã‚‹ã“ã¨ã«ãªã‚Šã¾ã™ã€‚動作ã«å¿…è¦ãªé …目やタスクã«ä½¿ç”¨ã•ã‚Œã¦ã„ã‚‹é …ç›®ã¯å‰Šé™¤ã§ãã¾ã›ã‚“。',
+'name' => 'å称',
+'order' => '順番',
+'back' => 'Back',
+'text' => 'Text',
+'highlight' => 'Highlight',
+'show' => '表示',
+'owner' => '所有者',
+'selectowner' => '所有者をé¸æŠž',
+'update' => 'æ›´æ–°',
+'addnew' => '追加',
+'flysprayprefs' => 'Flyspray preferences',
+'projecttitle' => 'プロジェクトå',
+'baseurl' => 'Base URL for this installation',
+'replyaddress' => 'Reply email address for notifications',
+'themestyle' => 'テーマ/スタイル',
+'language' => '言語',
+'anonview' => '未ログインユーザã«ã‚¿ã‚¹ã‚¯ã®å‚照を許å¯',
+'allowanon' => '未ログインユーザã«ã‚¿ã‚¹ã‚¯ã®ç™»éŒ²ã‚’許å¯',
+'never' => 'Never',
+'anonymously' => 'Anonymously',
+'afterregister' => 'Only after registering',
+'spamproof' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ç™»éŒ²ã§ç¢ºèªã‚³ãƒ¼ãƒ‰ã‚’使用',
+'anongroup' => 'Group for new user registrations',
+'groupassigned' => 'タスクã®æ‹…当者ã¨ãªã‚Œã‚‹ãƒ¦ãƒ¼ã‚¶ã®ã‚°ãƒ«ãƒ¼ãƒ—',
+'forcenotify' => 'タスク通知方法を固定',
+'neversend' => 'é€ä¿¡ã—ãªã„',
+'userchoose' => 'ユーザãŒé¸æŠž',
+'email' => 'E-mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'デフォルトã®ã‚«ãƒ†ã‚´ãƒªæ‰€æœ‰è€…',
+'noone' => 'ä¸åœ¨',
+'jabbernotify' => 'Jabber通知',
+'jabberserver' => 'サーãƒ',
+'jabberport' => 'ãƒãƒ¼ãƒˆ',
+'jabberuser' => 'アカウントユーザå',
+'jabberpass' => 'アカウントパスワード',
+'saveoptions' => '設定ä¿å­˜',
+'editcomment' => 'コメント修正',
+'commentby' => 'コメント記述者',
+'saveeditedcomment' => 'コメントä¿å­˜',
+'projectprefs' => 'Project Preferences',
+'pagetitle' => 'ページタイトル',
+'defaultproject' => 'デフォルトプロジェクト',
+'projectlists' => 'プロジェクト一覧',
+'showlogo' => 'Show title logo image',
+'intromessage' => '紹介文',
+'isactive' => 'プロジェクトã¯ã‚¢ã‚¯ãƒ†ã‚£ãƒ–',
+'createproject' => 'プロジェクト作æˆ',
+'nopermission' => 'You don\'t have permission to use this page.',
+'listordertip' => 'ã“ã®é …目を一覧ã«è¡¨ç¤ºã™ã‚‹ã¨ãã®é †ç•ª',
+'listshowtip' => 'ã“ã®é …目を一覧ã«è¡¨ç¤º',
+'categoryownertip' => 'ã“ã®ã‚«ãƒ†ã‚´ãƒªã®ã‚¿ã‚¹ã‚¯ãŒã‚ªãƒ¼ãƒ—ンã•ã‚ŒãŸã¨ãã«é€šçŸ¥ã™ã‚‹ãƒ¦ãƒ¼ã‚¶',
+'categoryparenttip' => 'ã“ã®æ–°ã—ã„カテゴリã®è¦ªã¨ãªã‚‹ã‚«ãƒ†ã‚´ãƒª',
+'notsubcategory' => 'ç„¡ã— (最上ä½ã‚«ãƒ†ã‚´ãƒª)',
+'showinlineimages' => '添付画åƒã‚’インラインã§è¡¨ç¤º',
+'dateformat' => '日付形å¼',
+'dateformat_extended' => '詳細日付形å¼',
+'cache_feeds' => 'フィードã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥',
+'no_cache' => 'キャッシュã—ãªã„',
+'cache_disk' => 'ディスクã«ã‚­ãƒ£ãƒƒã‚·ãƒ¥',
+'cache_db' => 'データベースã«ã‚­ãƒ£ãƒƒã‚·ãƒ¥',
+'subcategoryof' => 'Sub-category of',
+'visiblecolumns' => 'タスク一覧ã«è¡¨ç¤ºã™ã‚‹æ¬„',
+'tense' => '時期',
+'listtensetip' => 'éŽåŽ»ãƒ»ç¾åœ¨ãƒ»æœªæ¥',
+'past' => 'éŽåŽ»',
+'present' => 'ç¾åœ¨',
+'future' => '未æ¥',
+'oldpass' => '旧パスワード',
+'nooldpass' => '旧パスワードãŒè¨­å®šã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'oldpasswrong' => '旧パスワードã«èª¤ã‚ŠãŒã‚ã‚Šã¾ã™ã€‚',
+'changepass' => 'パスワード',
+'confirmpass' => 'パスワード(確èª)',
+'projectmanager' => 'プロジェクトã®ç®¡ç†è€…',
+'viewtasks' => 'タスクを表示',
+'modifyowntasks' => '自分ã®ã‚¿ã‚¹ã‚¯ã‚’修正',
+'modifyalltasks' => '自分以外ã®ã‚¿ã‚¹ã‚¯ã‚’修正',
+'viewcomments' => 'コメントを表示',
+'editcomments' => 'コメントを修正',
+'deletecomments' => 'コメントを削除',
+'viewattachments' => '添付ファイルを表示',
+'createattachments' => '添付ファイルを作æˆ',
+'deleteattachments' => '添付ファイルを削除',
+'viewhistory' => '履歴を表示',
+'closeowntasks' => '自分ã®ã‚¿ã‚¹ã‚¯ã‚’クローズ',
+'closeothertasks' => '自分以外ã®ã‚¿ã‚¹ã‚¯ã‚’クローズ',
+'assigntoself' => '未割り当ã¦ã®ã‚¿ã‚¹ã‚¯ã‚’自分ã«å‰²ã‚Šå½“ã¦',
+'assignotherstoself' => '他人ã®ã‚¿ã‚¹ã‚¯ã‚’自分ã«å‰²ã‚Šå½“ã¦',
+'viewreports' => 'イベントログを表示',
+'othersview' => '誰ã§ã‚‚ã“ã®ãƒ—ロジェクトをå‚ç…§å¯èƒ½',
+'usersandgroups' => 'ユーザã¨ã‚°ãƒ«ãƒ¼ãƒ—',
+'globalgroup' => 'グローãƒãƒ«ã‚°ãƒ«ãƒ¼ãƒ—',
+'globalgroups' => 'グローãƒãƒ«ã‚°ãƒ«ãƒ¼ãƒ—',
+'defaultglobalgroup' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ã®ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã‚°ãƒ­ãƒ¼ãƒãƒ«ã‚°ãƒ«ãƒ¼ãƒ—',
+'addtogroup' => 'グループã«è¿½åŠ ',
+'moveuserstogroup' => 'ユーザを他ã®ã‚°ãƒ«ãƒ¼ãƒ—ã«ç§»å‹•',
+'nogroup' => 'グループ無㗠- プロジェクトã‹ã‚‰å‰Šé™¤',
+'eventdesc' => 'è¦æœ›äº‹é …',
+'requestedby' => 'è¦æœ›è€…',
+'daterequested' => 'è¦æœ›æ—¥',
+'closetask' => 'タスクをクローズ',
+'reopentask' => 'タスクをå†ã‚ªãƒ¼ãƒ—ン',
+'applymember' => 'プロジェクトメンãƒã«å¿œå‹Ÿ',
+'forcurrentproj' => 'For current project',
+'lostpw' => '忘れãŸãƒ‘スワードã®å¾©æ—§',
+'lostpwexplain' => 'ã‚ãªãŸã®ãƒ¦ãƒ¼ã‚¶åを入力ã—ã¦ãã ã•ã„。ã‚ãªãŸã®ãƒ—ロフィールã«ç™»éŒ²ã•ã‚ŒãŸé€šçŸ¥ã‚¢ãƒ‰ãƒ¬ã‚¹ã«ã€ãƒ‘スワード変更画é¢ã¸ã®ãƒªãƒ³ã‚¯ã‚’é€ä¿¡ã—ã¾ã™ã€‚',
+'sendlink' => 'リンクé€ä¿¡',
+'savenewpass' => '新パスワードをä¿å­˜',
+'anonreg' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ç™»éŒ²ã‚’許å¯',
+'allowanonopentask' => '未ログインユーザã«ã‚¿ã‚¹ã‚¯ã®ç™»éŒ²ã‚’許å¯',
+'editglobalgroup' => 'Edit Global Group',
+'editgroupforproj' => 'Edit group for Project',
+'notshownforadmin' => '管ç†è€…グループã«å¯¾ã™ã‚‹è¨±å¯è¨­å®šã¯å¤‰æ›´ã™ã‚‹å¿…è¦ãŒãªã„ãŸã‚表示ã•ã‚Œã¾ã›ã‚“。',
+'general' => '一般',
+'userregistration' => 'ユーザ登録',
+'notifications' => '通知',
+'resetoptions' => '設定リセット',
+'preferences' => '設定',
+'tasktypes' => 'タスク種別',
+'resolutions' => 'クローズç†ç”±',
+'categories' => 'カテゴリ',
+'operatingsystems' => 'OS',
+'versions' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'admintoolboxlong' => 'é‹ç”¨ç®¡ç†',
+'newproject' => 'æ–°è¦ãƒ—ロジェクト',
+'delete' => '削除',
+'listdeletetip' => 'ã“ã®é …目を削除',
+'lookandfeel' => '外観',
+'globaltheme' => 'グローãƒãƒ«ãƒ†ãƒ¼ãƒž',
+'emailnotify' => 'E-mail通知',
+'fromaddress' => 'Fromアドレス',
+'smtpserver' => 'SMTPサーãƒ',
+'smtpuser' => 'SMTPユーザå',
+'smtppass' => 'SMTPパスワード',
+'addrewrite' => 'Use address rewriting',
+'usereminderdaemon' => 'Enable background reminder daemon',
+'tasksperpage' => 'タスク一覧ã§ã®ãƒšãƒ¼ã‚¸æ¯Žã‚¿ã‚¹ã‚¯æ•°',
+'addtoassignees' => '自分を担当者ã«è¿½åŠ ',
+'taskstatuses' => 'タスク状態',
+'canvote' => 'タスクã«æŠ•ç¥¨',
+'loginsuccessful' => 'ログインæˆåŠŸ.',
+'youareloggedout' => 'ログアウトã—ã¾ã—ãŸã€‚',
+'waitwhiletransfer' => '転é€ä¸­ãŠå¾…ã¡ãã ã•ã„...',
+'clicknowait' => 'Click here if you do not wish to wait.',
+'accountdisabled' => 'Your account is disabled. Contact a full admin.',
+'task' => 'タスク',
+'edittask' => 'タスクを修正',
+'openedby' => 'オープン者',
+'editedby' => '最終修正者',
+'tasktype' => 'タスク種別',
+'category' => 'カテゴリ',
+'status' => '状態',
+'assignedto' => '担当者',
+'operatingsystem' => 'OS',
+'severity' => 'é‡å¤§åº¦',
+'reportedversion' => '報告ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'dueinversion' => '対応予定ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'undecided' => '未決定',
+'percentcomplete' => '進æ—',
+'details' => '詳細',
+'savedetails' => '設定ä¿å­˜',
+'canceledit' => 'Cancel Edit',
+'anonymous' => '匿å登録者',
+'complete' => '完了',
+'closedby' => 'クローズ者',
+'reasonforclosing' => 'クローズã®ç†ç”±:',
+'reopenthistask' => 'タスクをå†ã‚ªãƒ¼ãƒ—ン',
+'comments' => 'コメント',
+'attachments' => '添付ファイル',
+'relatedtasks' => '関連タスク',
+'edit' => '変更',
+'addcomment' => 'コメント追加',
+'fileuploadedby' => 'ファイルアップロード者',
+'uploadafile' => 'ファイルを添付',
+'uploadnow' => 'Upload now!',
+'thesearerelated' => 'These tasks are related to this task',
+'remove' => '削除',
+'addnewrelated' => '関連タスクを追加',
+'add' => '追加',
+'otherrelated' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã«é–¢é€£ã™ã‚‹ä»–ã®ã‚¿ã‚¹ã‚¯',
+'receivenotify' => 'These users will receive detailed notifications when this task changes.',
+'addusertolist' => 'ユーザをã“ã®ä¸€è¦§ã«è¿½åŠ ',
+'addtolist' => '一覧ã«è¿½åŠ ',
+'addmyself' => '自分をã“ã®ä¸€è¦§ã«è¿½åŠ ',
+'removemyself' => '自分をã“ã®ä¸€è¦§ã‹ã‚‰å‰Šé™¤',
+'theseusersnotify' => 'ã“れらã®ãƒ¦ãƒ¼ã‚¶ã¯ã“ã®ã‚¿ã‚¹ã‚¯ãŒå¤‰æ›´ã•ã‚ŒãŸã¨ãã«è©³ç´°æƒ…å ±ã®é€šçŸ¥ã‚’å—ã‘å–ã‚Šã¾ã™ã€‚',
+'attachedtoproject' => '割り当ã¦ã‚‰ã‚ŒãŸãƒ—ロジェクト',
+'reminders' => '催促',
+'system' => 'System',
+'remindthisuser' => '催促ã™ã‚‹ãƒ¦ãƒ¼ã‚¶',
+'thisoften' => '通知間隔',
+'startafter' => '最åˆã®å‚¬ä¿ƒã¾ã§ã®å¾…ã¡',
+'hours' => '時間',
+'days' => 'æ—¥',
+'weeks' => '週間',
+'addreminder' => '催促登録',
+'defaultreminder' => 'ã“ã‚Œã¯ä»¥ä¸‹ã®Flysprayタスクã«å¯¾ã™ã‚‹å‚¬ä¿ƒã§ã™:',
+'message' => 'メッセージ',
+'closed' => 'クローズ',
+'filename' => 'ファイルå:',
+'date' => '日付',
+'filesize' => 'ファイルサイズ:',
+'closurecomment' => 'クローズã«å¯¾ã™ã‚‹è¿½åŠ ã‚³ãƒ¡ãƒ³ãƒˆ:',
+'history' => '履歴',
+'nohistory' => '履歴ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'eventdate' => '日付',
+'user' => 'ユーザ',
+'event' => 'イベント',
+'fieldchanged' => 'フィールド変更',
+'taskopened' => 'タスクオープン',
+'taskreopened' => 'タスクå†ã‚ªãƒ¼ãƒ—ン',
+'taskclosed' => 'タスククローズ',
+'commentadded' => 'コメント追加',
+'commentedited' => 'コメント修正',
+'commentdeleted' => 'コメント削除',
+'attachmentadded' => '添付ファイル追加',
+'attachmentdeleted' => '添付ファイル削除',
+'taskedited' => 'タスク詳細修正',
+'notificationadded' => '通知一覧ã«ãƒ¦ãƒ¼ã‚¶è¿½åŠ ',
+'notificationdeleted' => '通知一覧ã‹ã‚‰ãƒ¦ãƒ¼ã‚¶å‰Šé™¤',
+'relatedadded' => '関連タスク追加',
+'relateddeleted' => '関連タスク削除',
+'taskassigned' => 'タスク割り当ã¦',
+'taskreassigned' => 'タスクå†å‰²ã‚Šå½“ã¦',
+'assignmentremoved' => 'タスク割り当ã¦è§£é™¤',
+'summary' => 'è¦ç´„',
+'addedasrelated' => '関連タスクã¨ã—ã¦è¿½åŠ ',
+'deletedasrelated' => '関連タスクã‹ã‚‰å‰Šé™¤',
+'reminderadded' => '通知追加',
+'reminderdeleted' => '通知削除',
+'priority' => '優先度',
+'previousvalue' => '以å‰ã®å€¤',
+'newvalue' => 'æ–°ã—ã„値',
+'selectareason' => 'ç†ç”±ã‚’é¸æŠž',
+'assigntome' => '自分ã«å‰²ã‚Šå½“ã¦',
+'reopenrequest' => 'å†ã‚ªãƒ¼ãƒ—ンをè¦æœ›',
+'requestclose' => 'クローズをè¦æœ›',
+'ownershiptaken' => '担当者割り当ã¦',
+'closerequestmade' => 'タスククローズè¦æœ›',
+'reopenrequestmade' => 'タスクå†ã‚ªãƒ¼ãƒ—ンè¦æœ›',
+'taskdependson' => 'ã“ã®ã‚¿ã‚¹ã‚¯ãŒä¾å­˜ã—ã¦ã„るタスク',
+'taskblocks' => 'ã“ã®ã‚¿ã‚¹ã‚¯ãŒã‚¯ãƒ­ãƒ¼ã‚ºã®å¦¨ã’ã¨ãªã£ã¦ã„るタスク',
+'depadded' => 'ä¾å­˜ã«è¿½åŠ ',
+'depaddedother' => 'ä»–ã®ã‚¿ã‚¹ã‚¯ã®ä¾å­˜ã¨ã—ã¦è¿½åŠ ',
+'depremoved' => 'ä¾å­˜ã‹ã‚‰å‰Šé™¤',
+'depremovedother' => 'ä»–ã®ã‚¿ã‚¹ã‚¯ã®ä¾å­˜ã‹ã‚‰å‰Šé™¤',
+'showdetailserror' => 'ãã®ã‚¿ã‚¹ã‚¯ã¯å­˜åœ¨ã—ãªã„ã‹ã€ãã®ã‚¿ã‚¹ã‚¯ã‚’å‚ç…§ã™ã‚‹è¨±å¯ãŒã‚ã‚Šã¾ã›ã‚“。',
+'makeprivate' => 'éžå…¬é–‹ã«å¤‰æ›´',
+'makepublic' => '公開ã«å¤‰æ›´',
+'taskmadeprivate' => 'éžå…¬é–‹ã«å¤‰æ›´',
+'taskmadepublic' => 'プライãƒã‚·ãƒ¼ã®é™¤åŽ» - 公開ã«å¤‰æ›´',
+'confirmdeletecomment' => '本当ã«ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’削除ã—ã¦ã‚‚ã„ã„ã§ã™ã‹ï¼Ÿ%s',
+'confirmdeleteattach' => '本当ã«ã“ã®æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚’削除ã—ã¦ã‚‚ã„ã„ã§ã™ã‹ï¼Ÿ',
+'selectedhistory' => 'Showing selected history details',
+'showallhistory' => 'Show full history tab again',
+'hidethis' => 'Hide this area again',
+'mark100' => 'タスクを 100% 完了ã«ã™ã‚‹',
+'watchtask' => '監視',
+'stopwatching' => '監視を解除',
+'commentlink' => 'ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã¸ã®ãƒªãƒ³ã‚¯',
+'submitreq' => 'è¦æœ›ã‚’é€ä¿¡',
+'reasonforreq' => 'è¦æœ›ã®ç†ç”±',
+'pmreqdenied' => 'プロジェクト管ç†è€…ãŒè¦æœ›ã‚’æ‹’å¦',
+'taskpendingreq' => 'プロジェクト管ç†è€…ã«ã‚ˆã‚‹å‡¦ç†å¾…ã¡ã§ã™ã€‚詳細ã¯å±¥æ­´ã‚¿ãƒ–を見ã¦ãã ã•ã„。',
+'previoustask' => 'å‰ã®ã‚¿ã‚¹ã‚¯',
+'nexttask' => '次ã®ã‚¿ã‚¹ã‚¯',
+'duedate' => '対応予定日',
+'attachnoperms' => 'ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã«ã¯æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ãŒã‚ã‚Šã¾ã™ãŒã€æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚’å‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'open' => 'オープン',
+'depgraph' => 'ä¾å­˜é–¢ä¿‚グラフを表示',
+'reset' => 'リセット',
+'selectusers' => 'ユーザをé¸æŠž',
+'addmetoassignees' => '自分を担当者ã«è¿½åŠ ',
+'addedtoassignees' => 'ユーザを担当者ã«è¿½åŠ ',
+'dependencygraph' => 'ä¾å­˜é–¢ä¿‚グラフ',
+'attachanotherfile' => 'ã‚‚ã†1ã¤ãƒ•ã‚¡ã‚¤ãƒ«ã‚’添付',
+'OK' => 'OK',
+'addvote' => '投票',
+'notifyfromfs' => 'Flysprayã‹ã‚‰ã®é€šçŸ¥',
+'autogenerated' => 'THIS IS AN AUTOMATICALLY GENERATED MESSAGE, DO NOT REPLY',
+'forward' => '次',
+'previous' => 'å‰',
+'next' => '次',
+'first' => '最åˆ',
+'last' => '最後',
+'page' => '表示中ページ %d / %d',
+'search' => 'Search',
+'alltasktypes' => '全タスク種別',
+'allseverities' => 'å…¨é‡å¤§åº¦',
+'alldevelopers' => '全開発者',
+'notyetassigned' => '未割り当ã¦',
+'allcategories' => '全カテゴリ',
+'allstatuses' => '全状態',
+'allopentasks' => '全オープンタスク',
+'sortthiscolumn' => 'ã“ã®æ¬„ã§ã‚½ãƒ¼ãƒˆ',
+'id' => 'ID',
+'project' => 'プロジェクト',
+'dateopened' => 'オープン日',
+'progress' => '進æ—',
+'searchthisproject' => 'ã“ã®ãƒ—ロジェクトを検索',
+'dueanyversion' => '全対応予定ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'anyversion' => '全報告ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'dueversion' => '対応予定ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'lastedit' => '最終更新日',
+'os' => 'OS',
+'reportedin' => '報告ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'taskrange' => '表示中タスク %d - %d / %d',
+'noresults' => '検索æ¡ä»¶ã«ä½•ã‚‚該当ã—ã¾ã›ã‚“ã§ã—ãŸ',
+'takeaction' => '実行',
+'watchtasks' => 'é¸æŠžã—ãŸã‚¿ã‚¹ã‚¯ã‚’監視',
+'stopwatchingtasks' => 'é¸æŠžã—ãŸã‚¿ã‚¹ã‚¯ã®ç›£è¦–を解除',
+'assigntaskstome' => 'é¸æŠžã—ãŸã‚¿ã‚¹ã‚¯ã‚’自分ã«å‰²ã‚Šå½“ã¦',
+'dueby' => 'Due by',
+'dueanytime' => 'Due anytime',
+'selectduedate' => '対応予定日をé¸æŠž',
+'toggleselected' => 'é¸æŠžçŠ¶æ…‹ã‚’å転',
+'due' => '対応予定日',
+'assignedtome' => '自分ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸã‚¿ã‚¹ã‚¯',
+'tasklist' => 'タスク一覧',
+'dateclosed' => 'クローズ日',
+'advanced' => '詳細',
+'searchcomments' => 'コメント内も検索',
+'searchforall' => 'ã™ã¹ã¦ã®å˜èªžã§AND検索',
+'anonusers' => '未ログインユーザ',
+'miscellaneous' => '検索範囲',
+'users' => 'ユーザ',
+'taskproperties' => 'タスク属性',
+'selectsincedate' => '最終更新日をé¸æŠž',
+'changedsince' => '最終更新日',
+'updatefs' => 'Flysprayã‚’æ›´æ–°ã—ã¦ãã ã•ã„。',
+'currentversion' => 'ç¾åœ¨ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯',
+'latestversion' => 'ã€æœ€æ–°ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯',
+'hidemessage' => '(remind me later)',
+'saveas' => '検索æ¡ä»¶ã«åå‰ã‚’付ã‘ã¦ä¿å­˜',
+'nosearches' => 'ä¿å­˜ã•ã‚ŒãŸæ¤œç´¢æ¡ä»¶ã¯ã‚ã‚Šã¾ã›ã‚“',
+'saving' => 'ä¿å­˜ä¸­...',
+'votes' => '投票',
+'allclosedtasks' => '全クローズタスク',
+'password' => 'パスワード',
+'login' => 'ログイン!',
+'rememberme' => '記憶ã™ã‚‹',
+'lostpassword' => 'パスワードを忘れã¾ã—ãŸã‹?',
+'lostpwforfs' => 'Lost password for Flyspray',
+'lostpwmsg1' => 'Hi.
+
+I have lost my password for Flyspray on ',
+'lostpwmsg2' => ', please provide me with a new password.
+
+Username: ',
+'regards' => '
+
+Regards,',
+'yourusername' => ' your username ',
+'locale' => 'ja',
+'filenotexist' => 'File does not exist, or you do not have permission to access it.',
+'showtask' => 'タスク表示',
+'now' => 'Now',
+'go' => 'Go!',
+'opentaskanon' => '匿åã§ã‚¿ã‚¹ã‚¯ç™»éŒ²',
+'register' => 'ユーザ登録',
+'addnewtask' => 'æ–°è¦ã‚¿ã‚¹ã‚¯ç™»éŒ²',
+'reports' => 'イベントログ',
+'editmydetails' => 'プロフィールã®å¤‰æ›´',
+'logout' => 'ログアウト',
+'disabledaccount' => 'ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ç„¡åŠ¹ã«ãªã‚Šã¾ã—ãŸã€‚<br />ログアウトã—ã¾ã™ã€‚',
+'poweredby' => 'Powered by Flyspray',
+'projects' => 'プロジェクト',
+'allprojects' => '全プロジェクト',
+'selectproject' => 'for Project:',
+'tasksall' => '全タスク',
+'tasksassigned' => '自分ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸã‚¿ã‚¹ã‚¯',
+'tasksreported' => '自分ãŒå ±å‘Šã—ãŸã‚¿ã‚¹ã‚¯',
+'taskswatched' => '監視ã—ã¦ã„るタスク',
+'mysearch' => 'ãŠå¥½ã¿æ¤œç´¢',
+'admintoolbox' => 'é‹ç”¨ç®¡ç†',
+'manageproject' => 'プロジェクト管ç†',
+'permissions' => 'View Permissions',
+'hide' => 'Hide',
+'pendingreq' => '個ã®è¦æœ›ãŒãƒ—ロジェクト管ç†è€…å¾…ã¡',
+'errorpage' => 'è¦æ±‚ã•ã‚ŒãŸãƒšãƒ¼ã‚¸ã‚’æä¾›ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“。<br />
+ ãŠãらãã€è¦æ±‚ã•ã‚ŒãŸã‚¿ã‚¹ã‚¯ãŒå­˜åœ¨ã—ãªã„ã‹ã€è¦æ±‚ã•ã‚ŒãŸãƒšãƒ¼ã‚¸ã‚’å‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ãªãŸã«ã‚ã‚Šã¾ã›ã‚“。<br /><br />
+ You may have tried to use a naughty URL to interact with the database
+ backend using SQL injection. If this is true, go to the corner and think
+ about your actions. When you return, please do not do it again!',
+'permissionsforproject' => 'è¨±å¯ for ',
+'switchto' => 'プロジェクト切り替ãˆ',
+'lastsearch' => '一番最後ã«è¡Œã£ãŸæ¤œç´¢',
+'modify' => 'Modify',
+'noticefrom' => '通知 from',
+'hasopened' => 'has OPENED A NEW Flyspray task, and assigned it to you:',
+'moreinfonew' => 'You can find more information about this bug at the Flyspray page:',
+'newtaskcategory' => 'A new Flyspray task has been opened in this category',
+'categoryowner' => 'You are receiving this because you are listed as the category owner.',
+'tasksummary' => 'Task summary:',
+'newtaskadded' => 'æ–°ã—ã„タスクãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'summaryanddetails' => 'è¦ç´„ã¨è©³ç´°ã®ä¸¡æ–¹ã‚’埋ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚',
+'goback' => 'Go back.',
+'messagefrom' => 'Flyspray障害管ç†ã‚·ã‚¹ãƒ†ãƒ ã‹ã‚‰ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã§ã™ã€‚ ',
+'hasjustmodified' => 'has just modified the following task.',
+'changedfields' => 'Changed fields are prefixed with asterisks (**)',
+'moreinfomodify' => 'You can get more information about this task at the following URL:',
+'nolongerassigned' => 'The following task is no longer assigned to you. It is now assigned to',
+'hasassigned' => 'has assigned to you the following Flyspray task:',
+'taskupdated' => 'タスクãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚',
+'hasclosedassigned' => 'has closed the following Flyspray task that you were assigned:',
+'unassigned' => '未決定',
+'hasclosed' => 'has closed the following task.',
+'youonnotify' => 'You are receiving this because you are on the notification list.',
+'taskclosedmsg' => 'タスクãŒã‚¯ãƒ­ãƒ¼ã‚ºã•ã‚Œã¾ã—ãŸã€‚',
+'returntotask' => 'Return to the task details',
+'backtoindex' => 'Go back to the task list',
+'noclosereason' => 'タスクã®ã‚¯ãƒ­ãƒ¼ã‚ºç†ç”±ãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'hasreopened' => 'has re-opened the following Flyspray task that you closed:',
+'taskreopenedmsg' => 'タスクãŒå†ã‚ªãƒ¼ãƒ—ンã•ã‚Œã¾ã—ãŸã€‚',
+'backtotask' => 'Go back to the task.',
+'commentaddedmsg' => 'コメントãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'commenttoassigned' => 'has added a comment to a task that you have been assigned:',
+'commenttotask' => 'has added the following comment to this task.',
+'nocommententered' => 'コメントを入力ã—ã¦ãã ã•ã„。',
+'fillinfields' => 'You didn\'t fill in all the fields.',
+'notcurrentpass' => 'That\'s not your current password!',
+'passchanged' => 'パスワードãŒå¤‰æ›´ã•ã‚Œã¾ã—ãŸã€‚',
+'closewindow' => 'You can now close this window.',
+'passnomatch' => '新パスワードãŒåŒã˜ã§ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'usernametaken' => 'ãã®ãƒ¦ãƒ¼ã‚¶åã¯ã™ã§ã«å–å¾—ã•ã‚Œã¦ã„ã¾ã™ã€‚é•ã†ã‚‚ã®ã‚’指定ã—ã¦ãã ã•ã„。',
+'newusercreated' => 'æ–°ã—ã„ユーザãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'accountcreated' => 'アカウントãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'newuserwarning' => 'æ–°ã—ã„アカウントã«ã¯ç®¡ç†è€…ã«ã‚ˆã‚‹æ‰¿èªã‚’å¿…è¦ã¨ã™ã‚‹é‹ç”¨è¨­å®šã«ãªã£ã¦ã„ã‚‹ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“。ログインã§ããªã„å ´åˆã¯ã€ãŠãらããã‚ŒãŒåŽŸå› ã§ã™ã€‚',
+'nomatchpass' => 'パスワードãŒä¸€è‡´ã—ã¾ã›ã‚“。',
+'confirmwrong' => '確èªã‚³ãƒ¼ãƒ‰ãŒé–“é•ã£ã¦ã„ã¾ã™!',
+'formnotcomplete' => 'フォームãŒå®Œå…¨ã«åŸ‹ã‚られã¦ã„ã¾ã›ã‚“。',
+'formnotnumeric' => '入力ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿ãŒæ•°å­—ã§ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'groupnametaken' => 'ãã®ã‚°ãƒ«ãƒ¼ãƒ—åã¯ã™ã§ã«å–å¾—ã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'newgroupadded' => 'æ–°ã—ã„グループãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'optionssaved' => 'Flyspray設定ãŒä¿å­˜ã•ã‚Œã¾ã—ãŸã€‚',
+'hasuploaded' => 'has uploaded a file attachment to a task that have been assigned:',
+'hasattached' => 'has attached a file to the following task.',
+'fileuploaded' => 'File has been uploaded.',
+'fileerror' => 'There was an error uploading your file. Perhaps the permissions on the <i>attachments/</i> directory are incorrect.',
+'contactadmin' => 'Contact the Administrator for this project.',
+'selectfileerror' => 'You didn\'t select a file.',
+'userupdated' => 'ユーザ情報ãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚',
+'realandemail' => 'You didn\'t fill in both the Real Name and Email Address fields.',
+'groupupdated' => 'グループ情報ãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚',
+'groupanddesc' => 'グループåãŒåŸ‹ã‚られã¦ã„ã¾ã›ã‚“。',
+'fillallfields' => 'ã™ã¹ã¦ã®å…¥åŠ›æ¬„を埋ã‚ã¦ãã ã•ã„。',
+'listPmustN' => '「順番ã€æ¬„ã¯æ•°å­—ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚',
+'listupdated' => '一覧ãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚',
+'listitemadded' => 'æ–°ã—ã„一覧項目ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'relatedaddedmsg' => '関連タスクãŒä¸€è¦§ã«è¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'relatederror' => 'ãã®ã‚¿ã‚¹ã‚¯ã¯ã™ã§ã«ã“ã®é–¢é€£ã‚¿ã‚¹ã‚¯ä¸€è¦§ã«è¨­å®šã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'relatedremoved' => '関連タスクãŒä¸€è¦§ã‹ã‚‰å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'notifyadded' => 'ユーザãŒé€šçŸ¥ä¸€è¦§ã«è¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'notifyerror' => 'That user is already on the notification list for that task.',
+'notifyremoved' => 'ユーザãŒé€šçŸ¥ä¸€è¦§ã‹ã‚‰å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'editcommentsaved' => '更新コメントãŒä¿å­˜ã•ã‚Œã¾ã—ãŸã€‚',
+'commentdeletedmsg' => 'コメントãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'gotonewtask' => 'Go to the new task you just created',
+'projectcreated' => 'æ–°ã—ã„プロジェクトãŒä½œæˆã•ã‚Œã¾ã—ãŸã€‚プロジェクト管ç†é ˜åŸŸã§è¨­å®šã‚’è¡Œã†ã“ã¨ãŒã§ãã¾ã™ã€‚',
+'customiseproject' => 'Customise this project',
+'projectupdated' => 'プロジェクトã®è¨­å®šãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚',
+'emptytitle' => 'プロジェクトåãŒç©ºæ¬„ã§ã™ã€‚戻ã£ã¦ä¿®æ­£ã—ã¦ãã ã•ã„。',
+'loginbelow' => 'ログインã®æº–å‚™ãŒã§ãã¾ã—ãŸã€‚',
+'attachmentdeletedmsg' => 'The attachment has been deleted',
+'reminderaddedmsg' => '催促ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'reminderdeletedmsg' => 'é¸æŠžã•ã‚ŒãŸå‚¬ä¿ƒãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'flyspraytask' => 'Flyspray task',
+'fieldsmissing' => '入力ã•ã‚Œã¦ã„ãªã„ã¾ãŸã¯ç„¡åŠ¹ãªå…¥åŠ›æ¬„ãŒã‚ã‚Šã¾ã™ã€‚',
+'relatedinvalid' => 'ãã®ã‚ˆã†ãªã‚¿ã‚¹ã‚¯ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'relatedproject' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã¯åˆ¥ã®ãƒ—ロジェクトã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã¾ã™ã€‚関連タスクã¨ã—ã¦è¿½åŠ ã—ã¾ã™ã‹ï¼Ÿ',
+'addanyway' => 'Add anyway',
+'cancel' => 'Cancel',
+'alreadyedited' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã¯ã‚ãªãŸãŒä¿å­˜ã™ã‚‹å‰ã«åˆ¥ã®ãƒ¦ãƒ¼ã‚¶ã«ã‚ˆã£ã¦å¤‰æ›´ã•ã‚Œã¦ã„ã¾ã™ã€‚変更をä¿å­˜ã—ã¾ã™ã‹ï¼Ÿ',
+'saveanyway' => 'Save my changes anyway',
+'nouserselected' => 'No user selected. Select at least one user before trying again.',
+'groupswitchupdated' => 'ユーザã®ã‚°ãƒ«ãƒ¼ãƒ—を変更ã—ã¾ã—ãŸã€‚',
+'takenownershipmsg' => 'ã“ã®ã‚¿ã‚¹ã‚¯ãŒã‚ãªãŸã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚',
+'adminrequestmade' => 'プロジェクト管ç†è€…ã«è¦æœ›ãŒé€ä¿¡ã•ã‚Œã¾ã—ãŸã€‚',
+'newdepnotify' => 'A new dependency has been added to the following task:',
+'dependadded' => 'タスクã®ä¾å­˜ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'dependaddfailed' => 'ä¾å­˜ã®è¿½åŠ ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ãã®ã‚¿ã‚¹ã‚¯ãŒå­˜åœ¨ã™ã‚‹ã‹ã€ãã®ã‚¿ã‚¹ã‚¯ã‹ã‚‰é€†ã«ä¾å­˜ã•ã‚Œã¦ã„ãªã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。',
+'depremovedmsg' => 'タスクã®ä¾å­˜ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'newdepis' => 'æ–°ã—ã„ä¾å­˜ã¯',
+'magicurlsent' => 'ã‚ãªãŸã®é€šçŸ¥ã‚¢ãƒ‰ãƒ¬ã‚¹ã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ãã®ä¸­ã«ç›®çš„を完了ã™ã‚‹ãŸã‚ã®ãƒšãƒ¼ã‚¸ã¸ã®ãƒªãƒ³ã‚¯ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚',
+'changefspass' => 'Flysprayã®ãƒ‘スワードを変更',
+'magicurlmessage' => 'Flysprayã®ãƒ‘スワードを変更ã™ã‚‹ã«ã¯ä»¥ä¸‹ã®URLã«è¡Œã£ã¦ãã ã•ã„。',
+'erroronform' => 'フォームã®é€ä¿¡ã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚',
+'addressused' => 'ã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯Flysprayã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’登録ã™ã‚‹ãŸã‚ã«ä½¿ç”¨ã•ã‚Œã¾ã—ãŸã€‚
+ã‚‚ã—ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’予期ã—ã¦ã„ãªã‹ã£ãŸã®ã§ã‚ã‚Œã°ã€ç„¡è¦–ã—ã¦å‰Šé™¤ã—ã¦ãã ã•ã„。
+ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ç™»éŒ²ã‚’完了ã™ã‚‹ã«ã¯ã€ä»¥ä¸‹ã®URLã«è¡Œã£ã¦ãã ã•ã„。',
+'confirmcodeis' => '確èªã‚³ãƒ¼ãƒ‰:',
+'codesent' => '確èªã‚³ãƒ¼ãƒ‰ãŒé€ä¿¡ã•ã‚Œã¾ã—ãŸã€‚ãã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã«æ›¸ã‹ã‚ŒãŸæ‰‹é †ã«ã—ãŸãŒã£ã¦ãã ã•ã„。',
+'codenotsent' => '確èªã‚³ãƒ¼ãƒ‰ã‚’é€ä¿¡ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦è©¦ã—ã¦ã¿ã¦ãã ã•ã„。',
+'taskmadeprivatemsg' => 'タスクãŒéžå…¬é–‹ã«ãªã‚Šã¾ã—ãŸã€‚',
+'taskmadepublicmsg' => 'タスクãŒå…¬é–‹ã«ãªã‚Šã¾ã—ãŸã€‚',
+'realandnotify' => 'æ°åã¨ã€E-mailアドレスã¾ãŸã¯Jabber IDã®å…¥åŠ›æ¬„を埋ã‚ã¦ãã ã•ã„。',
+'pmreqdeniedmsg' => 'プロジェクト管ç†è€…ãŒè¦æœ›ã‚’æ‹’å¦ã—ã¾ã—ãŸ',
+'massopsuccess' => 'Mass operations successful - where permissions allowed',
+'usernotexist' => 'ãã®ãƒ¦ãƒ¼ã‚¶ã¯ã“ã®Flyspray上ã«å­˜åœ¨ã—ã¾ã›ã‚“。',
+'commentattachperms' => 'コメントを削除ã§ãã¾ã›ã‚“。- 添付ファイルを削除ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'voterecorded' => '投票ãŒè¨˜éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'votefailed' => '投票ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
+'createnewgroup' => 'æ–°è¦ã‚°ãƒ«ãƒ¼ãƒ—ã®ç™»éŒ²',
+'requiredfields' => '入力必須欄:',
+'addthisgroup' => 'ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—を登録',
+'createnewproject' => 'æ–°è¦ãƒ—ロジェクトã®ä½œæˆ',
+'addnewproject' => 'æ–°è¦ãƒ—ロジェクトを登録',
+'htmlallowed' => 'HTML code is allowed',
+'createthisproject' => 'ã“ã®ãƒ—ロジェクトを登録',
+'inlineimages' => '添付画åƒã‚’インラインã§è¡¨ç¤º',
+'createnewtask' => 'Create a new task in project:',
+'addanother' => 'Add another task after this one',
+'addthistask' => 'タスク登録',
+'notifyme' => 'ã“ã®ã‚¿ã‚¹ã‚¯ãŒå¤‰æ›´ã•ã‚ŒãŸã‚‰è‡ªåˆ†ã«é€šçŸ¥ã™ã‚‹',
+'newtask' => 'æ–°è¦ã‚¿ã‚¹ã‚¯',
+'attachafile' => 'Attach a file',
+'registernewuser' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ã®ç™»éŒ²',
+'none' => 'ç„¡ã—',
+'registeraccount' => 'ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’登録',
+'both' => '両方',
+'notifyfrom' => '通知 from ',
+'donotreply' => 'ã“ã‚Œã¯è‡ªå‹•çš„ã«é€ä¿¡ã•ã‚ŒãŸãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã§ã™ã€‚返信ã—ãªã„ã§ãã ã•ã„。',
+'disclaimer' => 'ã‚ãªãŸãŒã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’å—ä¿¡ã—ãŸã®ã¯ã€Flyspray障害管ç†ã‚·ã‚¹ãƒ†ãƒ ä¸Šã§ã‚ãªãŸãŒè¦æ±‚ã—ãŸãŸã‚ã§ã™ã€‚ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’予期ã—ã¦ã„ãªã‹ã£ãŸã‹ã€ä»Šå¾Œå—ä¿¡ã—ãŸããªã„å ´åˆã¯ã€ä¸Šè¨˜URLã§ã‚ãªãŸã®é€šçŸ¥è¨­å®šã‚’変更ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚',
+'userwho' => 'æ“作ユーザ',
+'moreinfo' => '詳細ã¯ä»¥ä¸‹ã®URLã‹ã‚‰å‚ç…§ã§ãã¾ã™ã€‚',
+'newtaskopened' => 'Flysprayã«æ–°ã—ã„タスクãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚詳細ã¯ä»¥ä¸‹ã®é€šã‚Šã§ã™ã€‚',
+'notify.taskclosed' => '以下ã®ã‚¿ã‚¹ã‚¯ãŒã‚¯ãƒ­ãƒ¼ã‚ºã•ã‚Œã¾ã—ãŸã€‚',
+'notify.taskreopened' => '以下ã®ã‚¿ã‚¹ã‚¯ãŒå†ã‚ªãƒ¼ãƒ—ンã•ã‚Œã¾ã—ãŸã€‚',
+'newdep' => '以下ã®ã‚¿ã‚¹ã‚¯ã«æ–°ã—ã„ä¾å­˜ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'notify.depremoved' => '以下ã®ã‚¿ã‚¹ã‚¯ã‹ã‚‰ä¾å­˜ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'olddepwas' => 'The old dependency was',
+'notify.commentadded' => '以下ã®ã‚¿ã‚¹ã‚¯ã«æ–°ã—ã„コメントãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'commentis' => 'コメント文ã¯ä»¥ä¸‹ã§ã™ã€‚',
+'newattachment' => '以下ã®ã‚¿ã‚¹ã‚¯ã«æ–°ã—ã„添付ファイルãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸã€‚',
+'detailsbelow' => '詳細ã¯ä»¥ä¸‹ã®é€šã‚Šã§ã™ã€‚',
+'notify.relatedadded' => '以下ã®ã‚¿ã‚¹ã‚¯ã«æ–°ã—ã„関連タスクãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚',
+'relatedis' => '関連タスクã¯ä»¥ä¸‹ã§ã™ã€‚',
+'assignedtoyou' => '以下ã®ã‚¿ã‚¹ã‚¯ãŒã‚ãªãŸã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚',
+'takenownership' => 'ã«ä»¥ä¸‹ã®ã‚¿ã‚¹ã‚¯ãŒå‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¾ã—ãŸã€‚',
+'requiresaction' => '以下ã®ã‚¿ã‚¹ã‚¯ã¯ãƒ—ロジェクト管ç†è€…ã«ã‚ˆã‚‹å‡¦ç†ãŒå¿…è¦ã§ã™ã€‚',
+'requiresactionnotify' => 'Task requires action by a Project Manager',
+'pmdeny' => 'プロジェクト管ç†è€…ãŒä»¥ä¸‹ã®ã‚¿ã‚¹ã‚¯ã«å¯¾ã™ã‚‹è¦æœ›ã‚’æ‹’å¦ã—ã¾ã—ãŸã€‚',
+'pmdenynotify' => 'A Project Manager has denied the request',
+'fileaddedtoo' => 'ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã«ã¯1ã¤ä»¥ä¸Šã®æ·»ä»˜ãƒ•ã‚¡ã‚¤ãƒ«ãŒã‚ã‚Šã¾ã™ã€‚',
+'taskwatching' => 'ã‚ãªãŸã¯ä»¥ä¸‹ã®ã‚¿ã‚¹ã‚¯ã‚’監視ã—ã¦ã„ã¾ã™ã€‚',
+'isdepfor' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã¯ä»¥ä¸‹ã®ã‚¿ã‚¹ã‚¯ã‹ã‚‰ä¾å­˜ã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'denialreason' => 'æ‹’å¦ã®ç†ç”±',
+'taskchanged' => '以下ã®ã‚¿ã‚¹ã‚¯ã¯ä¿®æ­£ã•ã‚Œã¾ã—ãŸã€‚修正内容ã¯ä»¥ä¸‹ã®ä¸€è¦§ã®é€šã‚Šã§ã™ã€‚修正内容ã®è©³ç´°æƒ…報を見るã«ã¯ã€ä¸‹è¨˜ã®URLã«è¡Œã履歴タブをクリックã—ã¦ãã ã•ã„。',
+'useraddedtoassignees' => 'ユーザãŒä»¥ä¸‹ã®ã‚¿ã‚¹ã‚¯ã®æ‹…当者一覧ã«è‡ªåˆ†è‡ªèº«ã‚’追加ã—ã¾ã—ãŸã€‚',
+'removeddepis' => '削除ã•ã‚ŒãŸä¾å­˜',
+'isnodepfor' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã¯ã‚‚ã¯ã‚„以下ã®ã‚¿ã‚¹ã‚¯ã®ä¾å­˜ã§ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'usergroups' => 'ユーザã¨ã‚°ãƒ«ãƒ¼ãƒ—',
+'pmtoolbox' => 'プロジェクト管ç†',
+'groupmanage' => 'グループ管ç†',
+'pendingrequests' => '未解決è¦æœ›',
+'reasongiven' => 'ç†ç”±',
+'nopendingreq' => '未解決ã®è¦æœ›ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'givereason' => 'Give a reason',
+'catlisted' => 'カテゴリ一覧',
+'oslisted' => 'OS一覧',
+'verlisted' => 'ãƒãƒ¼ã‚¸ãƒ§ãƒ³ä¸€è¦§',
+'tasktypeed' => 'タスク種別一覧',
+'resed' => 'クローズç†ç”±ä¸€è¦§',
+'deny' => 'æ‹’å¦',
+'notifiedwhen' => 'Notified when',
+'onlynewtasks' => 'New tasks are opened',
+'allevents' => 'Any event occurs in any task',
+'feeds' => 'フィード',
+'feeddescription' => 'フィードã®èª¬æ˜Ž',
+'feedimgurl' => 'フィードã®ç”»åƒURL (ç”»åƒç„¡ã—ã¯ç©ºæ¬„)',
+'notifysubject' => '通知ã®é¡Œå',
+'notifysubjectinfo' => '(%p = プロジェクトå, %s = タスクè¦ç´„, %t = タスクID, %a = イベント内容, %u = ユーザå)',
+'priority6' => 'å³åˆ»',
+'priority5' => '大至急',
+'priority4' => '至急',
+'priority3' => '高',
+'priority2' => '通常',
+'priority1' => '低',
+'sendcode' => 'コードé€ä¿¡!',
+'entercode' => '通知メッセージã®ä¸­ã«è¨˜è¼‰ã•ã‚ŒãŸç¢ºèªã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„。ã¾ãŸå¸Œæœ›ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードを入力ã—ã¦ãã ã•ã„。',
+'confirmationcode' => '確èªã‚³ãƒ¼ãƒ‰',
+'registererror' => 'å¿…é ˆã®å…¥åŠ›æ¬„ãŒã™ã¹ã¦åŸ‹ã¾ã£ã¦ã„ã‚‹ã‹ã€å¸Œæœ›ã™ã‚‹é€šçŸ¥å½¢å¼ç”¨ã®å…¥åŠ›æ¬„ãŒæ­£ã—ã入力ã•ã‚Œã¦ã„ã‚‹ã‹ã€ç¢ºèªã—ã¦ãã ã•ã„。',
+'validusername' => '(アルファベット・数字㨠- _ . ãŒä½¿ç”¨å¯èƒ½)',
+'emailtaken' => 'ãã®E-mailアドレスã¾ãŸã¯Jabber IDã¯ã™ã§ã«å–å¾—ã•ã‚Œã¦ã„ã¾ã™ã€‚é•ã†ã‚‚ã®ã‚’指定ã—ã¦ãã ã•ã„。',
+'note' => '<strong>注æ„:</strong> アカウントを作æˆã™ã‚‹å‰ã«ã€ã‚ãªãŸå®›ã«ç¢ºèªã‚³ãƒ¼ãƒ‰ãŒé€ä¿¡ã•ã‚Œã¾ã™ã€‚ã“ã®ç¢ºèªã‚³ãƒ¼ãƒ‰ã¯ä¸Šè¨˜ã®å¸Œæœ›ã™ã‚‹é€šçŸ¥æ–¹æ³•ã§é€ä¿¡ã•ã‚Œã¾ã™ã€‚<br />入力ã«é–“é•ã„ãŒã‚ã£ãŸå ´åˆã€<strong>ã‚ãªãŸã¯ç¢ºèªã‚³ãƒ¼ãƒ‰ã‚’å—ä¿¡ã§ãã¾ã›ã‚“。</strong>',
+'changelog' => '変更履歴',
+'changeloggen' => '変更履歴生æˆ',
+'listfrom' => 'List changelog entries from',
+'to' => '終了日',
+'oldestfirst' => 'Oldest first',
+'recentfirst' => 'Most recent first',
+'severityrep' => 'Severity Report',
+'totalopen' => 'Total open tasks',
+'age' => 'Age',
+'agerep' => 'Age Report',
+'eventsrep' => 'Events Report',
+'events' => 'イベント',
+'Tasks' => 'タスク',
+'opened' => 'オープン',
+'edited' => '修正',
+'assigned' => '割り当ã¦',
+'within' => '最近',
+'pastday' => 'éŽåŽ»1æ—¥',
+'pastweek' => 'éŽåŽ»1週間',
+'pastmonth' => 'éŽåŽ»1ヶ月間',
+'pastyear' => 'éŽåŽ»1å¹´é–“',
+'nolimit' => '無制é™',
+'from' => '開始日',
+'duein' => '対応予定',
+'selectfromdate' => '開始日をé¸æŠž',
+'selecttodate' => '終了日をé¸æŠž',
+'showvoters' => '投票者ã®è¡¨ç¤º/éžè¡¨ç¤º',
+'roadmap' => 'ロードマップ',
+'roadmapfor' => 'ロードマップ for ãƒãƒ¼ã‚¸ãƒ§ãƒ³',
+'tasks' => 'タスク',
+'completed' => 'ãŒå®Œäº†ã€‚',
+'opentasks' => 'オープンタスク',
+'of' => '% of',
+'severity5' => 'é‡å¤§',
+'severity4' => '高',
+'severity3' => '普通',
+'severity2' => '低',
+'severity1' => '極低',
+'Redirect' => '転é€',
+'redirectmsg' => 'If your browser does not support meta redirection please click %sHERE%s to be redirected',
+'allowclosedcomments' => 'クローズã•ã‚ŒãŸã‚¿ã‚¹ã‚¯ã«ã‚³ãƒ¡ãƒ³ãƒˆã‚’許å¯',
+'comment' => 'コメント',
+'editowncomments' => '自分ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’修正',
+'reopened' => 'å†ã‚ªãƒ¼ãƒ—ン',
+'loading' => '読ã¿è¾¼ã¿ä¸­...',
+'notifyown' => '自分自身ã«ã‚ˆã‚‹å¤‰æ›´ã‚’通知ã™ã‚‹',
+'youremail' => 'ã‚ãªãŸã®E-mailアドレス',
+'thankyouforbug' => 'å•é¡Œã®æŒ‡æ‘˜ã‚’ã‚ã‚ŠãŒã¨ã†ã”ã–ã„ã¾ã™ã€‚以下ã®URLã‹ã‚‰ã„ã¤ã§ã‚‚ã“ã®ã‚¿ã‚¹ã‚¯ã‚’å‚ç…§ã—ãã®é€²æ—を監視ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚',
+'anonuser' => '未ログインユーザ',
+'conflict' => 'Conflict',
+'file' => 'ファイル',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'サイズ',
+'projectgroup' => 'プロジェクトグループ',
+'profile' => 'プロフィール:',
+'viewprofile' => 'プロフィールã®å‚ç…§',
+'regdate' => '登録日',
+'tasksopened' => 'オープンã—ãŸã‚¿ã‚¹ã‚¯',
+'replyto' => 'Reply-To',
+'notifytypes' => '通知ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆ',
+'pm.taskchanged' => 'タスク修正',
+'pm.taskreopened' => 'タスクå†ã‚ªãƒ¼ãƒ—ン',
+'pm.depadded' => 'ä¾å­˜è¿½åŠ ',
+'pm.depremoved' => 'ä¾å­˜å‰Šé™¤',
+'pmrequest' => 'プロジェクト管ç†è€…ã¸ã®è¦æœ›',
+'pmrequestdenied' => 'プロジェクト管ç†è€…ã¸ã®è¦æœ›ã®æ‹’å¦',
+'newassignee' => 'æ–°ã—ã„担当者',
+'revdepadded' => '逆ä¾å­˜è¿½åŠ ',
+'revdepaddedremoved' => '逆ä¾å­˜å‰Šé™¤',
+'assigneeadded' => '担当者追加',
+'addusergroup' => '指定ユーザをã“ã®ã‚°ãƒ«ãƒ¼ãƒ—ã«è¿½åŠ ',
+'groupmembers' => 'グループメンãƒ',
+'deleteuser' => 'ã“ã®ãƒ¦ãƒ¼ã‚¶ã‚’削除',
+'userdeleted' => 'ユーザãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'autoassign' => 'カテゴリ所有者ã«ã‚¿ã‚¹ã‚¯ã‚’自動割り当ã¦',
+'ssl' => 'SSL',
+'updatewrong' => '更新確èªæ©Ÿèƒ½ãŒæœ‰åŠ¹ã«è¨­å®šã•ã‚Œã¦ã„ã¾ã™ãŒã€æ›´æ–°ã‚µãƒ¼ãƒã«æŽ¥ç¶šä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚
+ã‚ãªãŸã®ãƒ›ã‚¹ãƒˆãŒå¤–ã¸ã®æŽ¥ç¶šã‚’許å¯ã—ã¦ã„ãªã„ã‹ã€ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®å•é¡Œã«ã‚ˆã‚‹ã‚¨ãƒ©ãƒ¼ã§ã™ã€‚
+Flysprayã®Webサイトã«è¡Œãã€ç¾åœ¨ç¨¼åƒã—ã¦ã„ã‚‹ã®ãŒæœ€æ–°ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‹ã©ã†ã‹ç¢ºèªã—ã¦ãã ã•ã„。',
+'deleteproject' => 'ã“ã®ãƒ—ロジェクトを削除ã—ã¦å†…容を指定プロジェクトã«ç§»å‹•',
+'projectdeleted' => 'プロジェクトãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚',
+'feedforall' => '全プロジェクトã«å¯¾ã™ã‚‹ãƒ•ã‚£ãƒ¼ãƒ‰',
+'usercreated' => 'ユーザを登録',
+'userdeleted' => 'ユーザを削除',
+'created' => '登録',
+'deleted' => '削除',
+'userid' => 'ユーザID',
+'editassignments' => '割り当ã¦ã‚’変更',
+'preview' => 'プレビュー',
+'anyprogress' => '全進æ—',
+'tasksrelated' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã«é–¢é€£ã—ã¦ã„るタスク',
+'duplicatetasks' => 'ã“ã®ã‚¿ã‚¹ã‚¯ã¨é‡è¤‡ã—ã¦ã„るタスク',
+'databasemodfailed' => 'データベースã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚権é™ãŒãªã„ã‹ã‚‰ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“。',
+'frequency' => '頻度',
+'newuserregistered' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ãŒFlysprayã«ç™»éŒ²ã•ã‚Œã¾ã—ãŸã€‚登録ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ã®è©³ç´°ã¯ä»¥ä¸‹ã®é€šã‚Šã§ã™ã€‚',
+'newuserregisterednotify' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ãŒç™»éŒ²ã•ã‚Œã¾ã—ãŸ',
+'notify_registration' => 'æ–°è¦ãƒ¦ãƒ¼ã‚¶ç™»éŒ²ã‚’管ç†è€…ã«é€šçŸ¥',
+'textversion' => 'テキスト版',
+'onlyprimary' => 'ä¾å­˜ã•ã‚Œã¦ã„ãªã„タスク',
+'switch' => '切り替ãˆ',
+'max' => '最大',
+'dates' => '日付',
+'selectduedatefrom' => '対応予定日',
+'selectduedateto' => 'ã‹ã‚‰',
+'selectsincedatefrom' => 'æ›´æ–°æ—¥',
+'selectsincedateto' => 'ã‹ã‚‰',
+'selectdate' => '日付é¸æŠž',
+'selectopenedfrom' => 'オープン日',
+'selectopenedto' => 'ã‹ã‚‰',
+'selectclosedfrom' => 'クローズ日',
+'selectclosedto' => 'ã‹ã‚‰',
+'startat' => '開始日',
+'hasattachment' => '添付ファイルãŒã‚るタスク',
+'private' => 'éžå…¬é–‹',
+'watching' => '監視中',
+'alreadyvotedthistask' => '投票済ã¿',
+'alreadyvotedthisday' => '本日投票済ã¿',
+'visibility' => '公開/éžå…¬é–‹',
+'public' => '公開',
+'leaveemptyauto' => 'パスワードを自動生æˆã™ã‚‹å ´åˆã¯ç©ºæ¬„ã«ã—ã¦ãã ã•ã„。',
+'novalidemail' => '有効ãªE-mailアドレスãŒå…¥åŠ›ã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'novalidjabber' => '有効ãªJabber IDãŒå…¥åŠ›ã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'missingrequired' => '入力ã•ã‚Œã¦ã„ãªã„必須入力欄ãŒã‚ã‚Šã¾ã™ã€‚',
+'entervalidusername' => '有効ãªãƒ¦ãƒ¼ã‚¶åã¨æ°åを入力ã—ã¦ãã ã•ã„。',
+'couldnotaddusernotif' => 'ユーザを通知一覧ã«è¿½åŠ ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
+'defaulttask' => 'デフォルトã®ã‚¿ã‚¹ã‚¯è©³ç´°',
+'all' => 'ã™ã¹ã¦',
+'events.useraddedtoassignees' => '担当者追加',
+'vote(s)' => '票',
+'eventlog' => 'イベントログ',
+'assignmentchanged' => '担当者変更',
+'detailedinfo' => '詳細情報',
+'All' => 'ã™ã¹ã¦',
+'tasksireported' => '自分ãŒå ±å‘Šã—ãŸã‚¿ã‚¹ã‚¯',
+'recentlyopened' => '最近登録ã•ã‚ŒãŸã‚¿ã‚¹ã‚¯',
+'stats' => '統計',
+'totaltasks' => '全タスク',
+'mostwanted' => '最è¦æœ›ã‚¿ã‚¹ã‚¯',
+'defaultentry' => 'åˆæœŸè¡¨ç¤ºç”»é¢',
+'toplevel' => '概è¦',
+'overview' => '概è¦',
+'error#' => 'エラー番å·',
+'error1' => '添付ファイルをå‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error3' => 'ç¹°ã‚Šè¿”ã—æ“作ã§ã™ã€‚メインページã«è»¢é€ã—ã¾ã™ã€‚',
+'error4' => '管ç†ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error5' => 'ãã®ãƒ¦ãƒ¼ã‚¶ã¯ã“ã®Flyspray上ã«å­˜åœ¨ã—ã¾ã›ã‚“。',
+'error6' => '無効ãªé‹ç”¨ç®¡ç†é ˜åŸŸã§ã™.',
+'error7' => 'ログインã«å¤±æ•—ã—ã¾ã—ãŸã€‚ユーザåã¾ãŸã¯ãƒ‘スワードãŒé–“é•ã£ã¦ã„ã¾ã™ã€‚',
+'error71' => 'ç¹°ã‚Šè¿”ã—ログインã«å¤±æ•—ã—ãŸã®ã§ã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ %d 分間ロックã•ã‚Œã¾ã™ã€‚',
+'error8' => 'ユーザåã¾ãŸã¯ãƒ‘スワードãŒå…¥åŠ›ã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'error9' => 'タスクãŒå­˜åœ¨ã—ãªã„ã‹ã€ãã®ã‚¿ã‚¹ã‚¯ã‚’å‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error10' => 'タスクãŒå­˜åœ¨ã—ã¾ã›ã‚“。',
+'error101' => 'タスクをå‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error102' => 'タスクをå‚ç…§ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。ログインã™ã‚Œã°å‚ç…§ã§ãã‚‹ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“。',
+'error11' => 'ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’修正ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error12' => 'マジックキーãŒæ­£ã—ãã‚ã‚Šã¾ã›ã‚“。通知メッセージã«æ›¸ã‹ã‚ŒãŸå†…容を確èªã—ã¦ãã ã•ã„。',
+'error13' => '未ログインユーザã«ãƒ—ロフィールã¯ã‚ã‚Šã¾ã›ã‚“。',
+'error14' => 'æ–°è¦ã‚°ãƒ«ãƒ¼ãƒ—を登録ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error15' => 'æ–°è¦ã‚¿ã‚¹ã‚¯ã‚’登録ã™ã‚‹æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error16' => 'ã‚ãªãŸã¯ãƒ—ロジェクト管ç†è€…ã§ã¯ã‚ã‚Šã¾ã›ã‚“。',
+'error17' => '無効ãªãƒ—ロジェクト管ç†é ˜åŸŸã§ã™ã€‚',
+'error18' => '無効ãªãƒžã‚¸ãƒƒã‚¯URLã§ã™ã€‚',
+'error19' => 'ãã®ãƒ¦ãƒ¼ã‚¶ã¯ã“ã®Flyspray上ã«å­˜åœ¨ã—ã¾ã›ã‚“。',
+'error20' => '無効ãªãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹å¤‰æ›´ã§ã™ã€‚',
+'error21' => '1通もã—ãã¯è¤‡æ•°ã®E-mailãŒé€ä¿¡ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚設定を確èªã—ã¦ãã ã•ã„。',
+'error22' => 'ユーザ登録ãŒè¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'error23' => 'ユーザã¾ãŸã¯ã‚°ãƒ«ãƒ¼ãƒ—ã«ãƒ­ã‚°ã‚¤ãƒ³è¨±å¯ãŒã‚ã‚Šã¾ã›ã‚“。',
+'error24' => 'dot 実行環境も dot 公開サーãƒã‚‚設定ã•ã‚Œã¦ã„ã¾ã›ã‚“。',
+'error25' => 'ロードマップã¯ç‰¹å®šã®ãƒ—ロジェクトã«å¯¾ã—ã¦ã ã‘存在ã—ã¾ã™ã€‚',
+'done' => '完了',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'プロジェクトを削除ã§ãã¾ã›ã‚“。',
+'GMT' => 'GMT',
+'timezone' => '時間帯',
+'accept' => '承諾',
+'reasonfordeinal' => 'æ‹’å¦ã®ç†ç”±',
+'pruneclosedlinks' => '切れãŸãƒªãƒ³ã‚¯ã‚’除去',
+'pruneclosedtasks' => 'クローズタスクを除去',
+'pagegenerated' => 'ページã¨ç”»åƒã®ç”Ÿæˆã« %d 秒ã‹ã‹ã‚Šã¾ã—ãŸã€‚',
+'pruninglevel' => '除去レベル',
+'lastuser' => '最後ã®ãƒ¦ãƒ¼ã‚¶ã¯å‰Šé™¤ã§ãã¾ã›ã‚“。',
+'allprivate' => 'ã™ã¹ã¦ã®ãƒ—ロジェクトãŒéžå…¬é–‹ã«ãªã£ã¦ã„ã¾ã™ã€‚',
+'deletegroup' => 'ã“ã®ã‚°ãƒ«ãƒ¼ãƒ—を削除ã—ã¦ãƒ¦ãƒ¼ã‚¶ã‚’指定グループã«ç§»å‹•',
+'parent' => '親カテゴリ',
+'ordertip' => 'ã“ã®é …目を一覧ã«è¡¨ç¤ºã™ã‚‹ã¨ãã®é †ç•ª',
+'showtip' => 'ã“ã®é …目を一覧ã«è¡¨ç¤º',
+'deletetip' => 'ã“ã®é …目を削除',
+'del' => '削除',
+'request1' => 'タスクã®ã‚¯ãƒ­ãƒ¼ã‚ºã®è¦æœ›ãŒå‡ºã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'request2' => 'タスクã®å†ã‚ªãƒ¼ãƒ—ンã®è¦æœ›ãŒå‡ºã•ã‚Œã¦ã„ã¾ã™ã€‚',
+'allpriorities' => '全優先度',
+'noroadmap' => 'ロードマップã¯ã‚ã‚Šã¾ã›ã‚“(「未æ¥ã€ã¨è¨­å®šã•ã‚ŒãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒãƒ—ロジェクトã«ã‚ã‚Šã¾ã›ã‚“)。',
+'expand' => '詳細を開ã',
+'collapse' => '詳細を閉ã˜ã‚‹',
+'expandall' => 'ã™ã¹ã¦ã®è©³ç´°ã‚’é–‹ã',
+'collapseall' => 'ã™ã¹ã¦ã®è©³ç´°ã‚’é–‰ã˜ã‚‹',
+'minpwsize' => 'パスワードã¯5文字以上ã§ã™ã€‚',
+'passwordtoosmall' => 'パスワードãŒçŸ­ã™ãŽã¾ã™ã€‚',
+'accountwaslocked' => 'ç¹°ã‚Šè¿”ã—ログインã«å¤±æ•—ã—ãŸã®ã§ã€ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ãƒ­ãƒƒã‚¯ã•ã‚Œã¦ã„ã¾ã—ãŸã€‚',
+'failedattempts' => '%d 回ã®ãƒ­ã‚°ã‚¤ãƒ³å¤±æ•—ãŒã‚ã‚Šã¾ã—ãŸã€‚',
+'groupnotexist' => 'é¸æŠžã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ã¯ã“ã®ãƒ—ロジェクトã«å­˜åœ¨ã—ã¾ã›ã‚“。',
+'searchindetails' => '詳細内も検索',
+'showasassignees' => '担当者欄ã«è¡¨ç¤º',
+'find' => '検索',
+);
+
+?>
diff --git a/lang/mk.php b/lang/mk.php
new file mode 100644
index 0000000..5ef3c1a
--- /dev/null
+++ b/lang/mk.php
@@ -0,0 +1,831 @@
+<?php
+
+$translation = array(
+'edituser' => 'Уреди кориÑник',
+'username' => 'КориÑничко име',
+'realname' => 'ВиÑтинÑко име',
+'emailaddress' => 'ÐдреÑа за е-пошта',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Тип на извеÑтување',
+'group' => 'Група',
+'accountenabled' => 'Сметката е вклучена',
+'updatedetails' => 'Ðжурирај детали',
+'setglobally' => 'Оваа преференца е поÑтавена глобално.',
+'usergroupmanage' => 'Менаџмент на групи и кориÑници',
+'newuser' => 'РегиÑтрирај нов кориÑник',
+'newgroup' => 'Креирај нова група',
+'yes' => 'Да',
+'no' => 'Ðе',
+'editgroup' => 'Уреди група',
+'groupname' => 'Име на група',
+'description' => 'ОпиÑ',
+'admin' => 'ÐдминиÑтраторÑка група',
+'opennewtasks' => 'Отвори нови задачи',
+'modifytasks' => 'Измени поÑтоечки задачи',
+'addcomments' => 'Додај коментари',
+'attachfiles' => 'Закачи датотеки',
+'vote' => 'ГлаÑ',
+'groupenabled' => 'Членовите можат да Ñе најавуваат',
+'groupopen' => 'Членовите можат да Ñе најавуваат',
+'tasktypelist' => 'ЛиÑта на типови на задачи',
+'categorylist' => 'ЛиÑта на категории',
+'oslist' => 'ЛиÑта на оперативни ÑиÑтеми',
+'resolutionlist' => 'Resolutions list',
+'versionlist' => 'ЛиÑта на верзии',
+'severitylist' => 'ЛиÑта на ÑериозноÑÑ‚',
+'listnote' => 'Note: Unchecking the "show" box may alter some tasks when in edit mode. Changing the "Name" field will change all tasks with that name. Items that can not be deleted are either protected because they are needed for correct functioning or used in tasks.',
+'name' => 'Име',
+'order' => 'РедоÑлед',
+'back' => 'Ðазад',
+'text' => 'ТекÑÑ‚',
+'highlight' => 'ОÑветли',
+'show' => 'Покажи',
+'owner' => 'СопÑтвеник',
+'selectowner' => 'Избери ÑопÑтвеник',
+'update' => 'Ðжурирај',
+'addnew' => 'Додај нова',
+'flysprayprefs' => 'Преференци за Flyspray',
+'projecttitle' => 'ÐаÑлов на проект',
+'baseurl' => 'ОÑновно URL за оваа инÑталација',
+'replyaddress' => 'ÐдреÑа за извеÑтувања',
+'themestyle' => 'Тема / Стил',
+'language' => 'Јазик',
+'anonview' => 'Allow anonymous users to view tasks',
+'allowanon' => 'Allow anonymous users to open new tasks',
+'never' => 'Ðикогаш',
+'anonymously' => 'Ðнонимно',
+'afterregister' => 'Само по региÑтрација',
+'spamproof' => 'Enable confirmation code for new user registrations',
+'anongroup' => 'Group for new user registrations',
+'groupassigned' => 'Members of these groups can be assigned tasks',
+'forcenotify' => 'Force task notifications as',
+'neversend' => 'Ðикогаш не иÑпраќај',
+'userchoose' => 'Секој кориÑник да може да избира',
+'email' => 'Е-пошта',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Default category owner',
+'noone' => 'Ðикој',
+'jabbernotify' => 'ИзвеÑтувања преку Jabber',
+'jabberserver' => 'Сервер',
+'jabberport' => 'Порта',
+'jabberuser' => 'КориÑничко име',
+'jabberpass' => 'Лозинка',
+'saveoptions' => 'Зачувај опции',
+'editcomment' => 'Уреди коментар',
+'commentby' => 'Коментар од',
+'saveeditedcomment' => 'Зачувај уреден коментар',
+'projectprefs' => 'Преференци за проектот',
+'pagetitle' => 'ÐаÑлов на Ñтраницата',
+'defaultproject' => 'Стандарден проект',
+'projectlists' => 'ЛиÑти на проекти',
+'showlogo' => 'Show title logo image',
+'intromessage' => 'Порака за вовед',
+'isactive' => 'Проектот е активен',
+'createproject' => 'Креирај нов проект',
+'nopermission' => 'You don\'t have permission to use this page.',
+'listordertip' => 'The order these items will appear in the list',
+'listshowtip' => 'Покажи го овој предмет во лиÑтата',
+'categoryownertip' => 'This person will receive notifications when a task in this category is opened',
+'categoryparenttip' => 'The parent category this new one will fall under',
+'notsubcategory' => 'None (top-level category)',
+'showinlineimages' => 'Show image attachments inline',
+'dateformat' => 'Формат на датум',
+'dateformat_extended' => 'Детален формат на датум',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'Без кеширање',
+'cache_disk' => 'Кеширај на диÑк',
+'cache_db' => 'Кеширај во база',
+'subcategoryof' => 'Sub-category of',
+'visiblecolumns' => 'Columns displayed in task list',
+'tense' => 'Време',
+'listtensetip' => 'Старо, Сегашно или Идно',
+'past' => 'Стари',
+'present' => 'Сегашни',
+'future' => 'Идни',
+'oldpass' => 'Стара лозинка',
+'nooldpass' => 'Ðе е поÑтавена Ñтарата лозинка',
+'oldpasswrong' => 'Старата лозинка е погрешна',
+'changepass' => 'Смени лозинка',
+'confirmpass' => 'Потврди лозинка',
+'projectmanager' => 'Менаџер на проектот',
+'viewtasks' => 'Види задачи',
+'modifyowntasks' => 'Измени ÑопÑтвени задачи',
+'modifyalltasks' => 'Modify tasks that are not user\'s own',
+'viewcomments' => 'Види коментари',
+'editcomments' => 'Уреди коментари',
+'deletecomments' => 'Избриши коментари',
+'viewattachments' => 'Види привезоци',
+'createattachments' => 'Креирај привезок',
+'deleteattachments' => 'Избриши привезоци',
+'viewhistory' => 'Види иÑторија',
+'closeowntasks' => 'Затвори ја ÑопÑтвената задача',
+'closeothertasks' => 'Close tasks that are not user\'s own',
+'assigntoself' => 'Assign tasks to self if not already assigned',
+'assignotherstoself' => 'Assign others\' tasks to self',
+'viewreports' => 'Види го логот на наÑтани',
+'othersview' => 'Allow anyone to view this project',
+'usersandgroups' => 'КориÑници и групи',
+'globalgroup' => 'Глобална група',
+'globalgroups' => 'Глобални групи',
+'defaultglobalgroup' => 'Default global group for new users',
+'addtogroup' => 'Додај во групата',
+'moveuserstogroup' => 'ПремеÑти ги кориÑниците во групата',
+'nogroup' => 'ОтÑтрани од проектот',
+'eventdesc' => 'ÐžÐ¿Ð¸Ñ Ð½Ð° наÑтанот',
+'requestedby' => 'Побарано од',
+'daterequested' => 'Датум на барањето',
+'closetask' => 'Затвори задача',
+'reopentask' => 'Повторно отвори задача',
+'applymember' => 'Ðплицирај за членÑтво во проектот',
+'forcurrentproj' => 'За тековниот проект',
+'lostpw' => 'Враќање на изгубена лозинка',
+'lostpwexplain' => 'ВнеÑи кориÑничко име за да ти биде иÑпратена врÑка за промена на лозинката. Ова ќе биде иÑпратено на адреÑата за извеÑтување твојот профил.',
+'sendlink' => 'ИÑпрати врÑка',
+'savenewpass' => 'Зачувај нова лозинка',
+'anonreg' => 'Дозволи региÑтрација на нови кориÑници',
+'allowanonopentask' => 'Дозволи анонимни кориÑници да отвораат задачи',
+'editglobalgroup' => 'Уреди глобална група',
+'editgroupforproj' => 'Уреди група за проект',
+'notshownforadmin' => 'ПермиÑиите за админиÑтраторÑката група не Ñе прикажани. Ðе треба да ги уредуваш.',
+'general' => 'Општо',
+'userregistration' => 'РегиÑтрација на кориÑници',
+'notifications' => 'ИзвеÑтувања',
+'resetoptions' => 'РеÑетирај опции',
+'preferences' => 'Преференци',
+'tasktypes' => 'Типови на задачи',
+'resolutions' => 'Решенија',
+'categories' => 'Категории',
+'operatingsystems' => 'Оперативни ÑиÑтеми',
+'versions' => 'Верзии',
+'admintoolboxlong' => 'ÐдминиÑтраторÑки алатник',
+'newproject' => 'Ðов проект',
+'delete' => 'Избриши',
+'listdeletetip' => 'Избриши го овој предмет од поштенÑката лиÑта',
+'lookandfeel' => 'Изглед и чувÑтво',
+'globaltheme' => 'Глобална тема',
+'emailnotify' => 'ИзвеÑтувања преку е-пошта',
+'fromaddress' => 'Од адреÑа',
+'smtpserver' => 'SMTP Ñервер',
+'smtpuser' => 'SMTP кориÑничко име',
+'smtppass' => 'SMTP лозинка',
+'addrewrite' => 'Use address rewriting',
+'usereminderdaemon' => 'Вклучи даемон за извеÑтување во позадина',
+'tasksperpage' => 'Задачи по Ñтраница од лиÑтата на задачи',
+'addtoassignees' => 'Додај Ñе ÑебеÑи',
+'taskstatuses' => 'СтатуÑи на задачи',
+'canvote' => 'Може да Ñе глаÑа за задачата',
+'loginsuccessful' => 'Ðајавата е уÑпешна.',
+'youareloggedout' => 'Одјавени Ñте.',
+'waitwhiletransfer' => 'Те молам почекај...',
+'clicknowait' => 'Кликнете овде ако не Ñакате да чекате.',
+'accountdisabled' => 'Твојата Ñметка е иÑклучена. Контактирај го админиÑтраторот.',
+'task' => 'Задача',
+'edittask' => 'Уреди ја оваа задача',
+'openedby' => 'Отворено од',
+'editedby' => 'ПоÑледен пат изменето од',
+'tasktype' => 'Тип на задача',
+'category' => 'Категорија',
+'status' => 'СтатуÑ',
+'assignedto' => 'Доделено на',
+'operatingsystem' => 'Оперативни ÑиÑтеми',
+'severity' => 'СериозноÑÑ‚',
+'reportedversion' => 'Пријавена верзија',
+'dueinversion' => 'Доаѓа во верзија',
+'undecided' => 'Ðерешено',
+'percentcomplete' => 'Завршено во проценти',
+'details' => 'Детали',
+'savedetails' => 'Зачувај детали',
+'canceledit' => 'Откажи уредување',
+'anonymous' => 'Ðнонимен',
+'complete' => 'завршено',
+'closedby' => 'Затворено од',
+'reasonforclosing' => 'Причина за затворање:',
+'reopenthistask' => 'Повторно отвори задача',
+'comments' => 'Коментари',
+'attachments' => 'Привезоци',
+'relatedtasks' => 'Поврзани задачи',
+'edit' => 'Уреди',
+'addcomment' => 'Додај коментар',
+'fileuploadedby' => 'Датотека качена од',
+'uploadafile' => 'Закачи датотека',
+'uploadnow' => 'Качи Ñега!',
+'thesearerelated' => 'Овие задачи Ñе поврзани Ñо оваа задача',
+'remove' => 'ОтÑтрани',
+'addnewrelated' => 'Додај нова поврзана задача',
+'add' => 'Додај!',
+'otherrelated' => 'Други задачи Ñо кои што е поврзана оваа задача',
+'receivenotify' => 'These users will receive detailed notifications when this task changes.',
+'addusertolist' => 'Додај го кориÑникот во оваа лиÑта',
+'addtolist' => 'Додај во лиÑта',
+'addmyself' => 'Додај ме во оваа лиÑта',
+'removemyself' => 'Тргни ме од оваа лиÑта',
+'theseusersnotify' => 'Овие кориÑници ќе добиваат детални извеÑтувања Ñекој пат кога ќе Ñе измени задачата.',
+'attachedtoproject' => 'Закачено за проектот',
+'reminders' => 'ПотÑетници',
+'system' => 'СиÑтем',
+'remindthisuser' => 'ПотÑети го кориÑникот',
+'thisoften' => 'Волку чеÑто',
+'startafter' => 'Чекај пред да иÑпратиш потÑетници',
+'hours' => 'ЧаÑ(а)',
+'days' => 'Ден(а)',
+'weeks' => 'Ðедел(а)',
+'addreminder' => 'Додај потÑетник',
+'defaultreminder' => 'Ова е потÑетник за да ја провериш Ñледнава задача:',
+'message' => 'Порака',
+'closed' => 'Затворено',
+'filename' => 'Име на датотека:',
+'date' => 'Датум',
+'filesize' => 'Големина на датотеката:',
+'closurecomment' => 'Additional comments about closing:',
+'history' => 'ИÑторија',
+'nohistory' => 'Ðе е пронајдена иÑторија.',
+'eventdate' => 'Датум',
+'user' => 'КориÑник',
+'event' => 'ÐаÑтан',
+'fieldchanged' => 'Полето е променето',
+'taskopened' => 'Задачата е отворена',
+'taskreopened' => 'Задачате е повторно отворена',
+'taskclosed' => 'Задачата е затворена',
+'commentadded' => 'Коментарот е додаден',
+'commentedited' => 'Коментарот е уреден',
+'commentdeleted' => 'Коментарот е избришан',
+'attachmentadded' => 'Привезокот е додаден',
+'attachmentdeleted' => 'Привезокот е избришан',
+'taskedited' => 'Деталите за задачата Ñе Ñменети',
+'notificationadded' => 'КориÑникот е додаден во лиÑтата за извеÑтување',
+'notificationdeleted' => 'КориÑникот е отÑтранет од лиÑтата за извеÑтување',
+'relatedadded' => 'Пповрзаната задача е додадена',
+'relateddeleted' => 'Поврзаната задаче отÑтранета',
+'taskassigned' => 'Задачата е доделена на',
+'taskreassigned' => 'Задачата е предоделена на',
+'assignmentremoved' => 'Assignment removed',
+'summary' => 'Ðа кратко',
+'addedasrelated' => 'Task added to the related list of',
+'deletedasrelated' => 'Task removed from the related list of',
+'reminderadded' => 'Reminder added',
+'reminderdeleted' => 'Reminder removed',
+'priority' => 'Приоритет',
+'previousvalue' => 'Претходна вредноÑÑ‚',
+'newvalue' => 'Ðова вредноÑÑ‚',
+'selectareason' => 'Изберете причина',
+'assigntome' => 'Додели ми ја задачата',
+'reopenrequest' => 'Побарај повторно отворање',
+'requestclose' => 'Побарај затворање',
+'ownershiptaken' => 'КориÑникот ја зема задачата',
+'closerequestmade' => 'Requested task closure',
+'reopenrequestmade' => 'Requested task to be re-opened',
+'taskdependson' => 'Оваа задача завиÑи од',
+'taskblocks' => 'Поради оваа задача не можат да бидат затворени',
+'depadded' => 'ЗавиÑноÑта е додадена',
+'depaddedother' => 'Оваа задача е додадена како завиÑноÑÑ‚',
+'depremoved' => 'ЗавиÑноÑта е отÑтранета',
+'depremovedother' => 'This task removed from other task\'s dependency list',
+'showdetailserror' => 'That task does not exist, or you don\'t have permission to view it.',
+'makeprivate' => 'направи приватно',
+'makepublic' => 'направи јавно',
+'taskmadeprivate' => 'Задачата е Ñега приватна',
+'taskmadepublic' => 'Задачата е Ñега јавна',
+'confirmdeletecomment' => 'Really delete this comment? %s',
+'confirmdeleteattach' => 'Really delete this attachment?',
+'selectedhistory' => 'Showing selected history details',
+'showallhistory' => 'Show full history tab again',
+'hidethis' => 'Hide this area again',
+'mark100' => 'Mark task 100% complete',
+'watchtask' => 'прати задача',
+'stopwatching' => 'преÑтани Ñо пратење',
+'commentlink' => 'Ð’Ñ€Ñка до овој коментар',
+'submitreq' => 'ПоднеÑи барање',
+'reasonforreq' => 'Причина за барање',
+'pmreqdenied' => 'Менаџерот на прокетот го одби барањето',
+'taskpendingreq' => 'Project Manager action pending. See the History tab for details.',
+'previoustask' => 'Претходна задача',
+'nexttask' => 'Следна задача',
+'duedate' => 'До датум',
+'attachnoperms' => 'There are attachments with this comment, but you have no permission to view them.',
+'open' => 'Отворени',
+'depgraph' => 'Види графа на завиÑноÑти',
+'reset' => 'РеÑетирај',
+'selectusers' => 'Избери кориÑници...',
+'addmetoassignees' => 'Add me to assignees',
+'addedtoassignees' => 'User added to assignees list',
+'dependencygraph' => 'Графа на завиÑноÑти',
+'attachanotherfile' => 'Закачи друга датотека',
+'OK' => 'Во ред',
+'addvote' => 'Додај глаÑ',
+'notifyfromfs' => 'ИзвеÑтување од Flyspray',
+'autogenerated' => 'ОВРЕ ÐВТОМÐТСКИ ГЕÐЕРИРÐÐРПОРÐКÐ, ТЕ МОЛÐÐœ ÐЕ ОДГОВÐРÐЈ.',
+'forward' => 'Ðапред',
+'previous' => 'Претходна',
+'next' => 'Следна',
+'first' => 'Прва',
+'last' => 'ПоÑледна',
+'page' => 'Страница %d од %d',
+'search' => 'Барај',
+'alltasktypes' => 'Сите типови на задачи',
+'allseverities' => 'Сите нивоа на ÑериозноÑÑ‚',
+'alldevelopers' => 'Сите програмери',
+'notyetassigned' => 'Сеуште недоделени',
+'allcategories' => 'Сите категории',
+'allstatuses' => 'Сите ÑтатуÑи',
+'allopentasks' => 'Сите отворени задачи',
+'sortthiscolumn' => 'Подреди по оваа колона',
+'id' => 'ID',
+'project' => 'Проект',
+'dateopened' => 'Отворена на',
+'progress' => 'ПрогреÑ',
+'searchthisproject' => 'Пребарај го проектот за',
+'dueanyversion' => 'Доаѓа во Ñите верзии',
+'anyversion' => 'Пријавено во Ñите верзии',
+'dueversion' => 'Доаѓа во верзија',
+'lastedit' => 'ПоÑледен пат изменето',
+'os' => 'Оперативен ÑиÑтем',
+'reportedin' => 'Пријавено во',
+'taskrange' => 'Покажувам задачи %d - %d од %d',
+'noresults' => 'Твоето пребарување не најде задачи.',
+'takeaction' => 'Превземи дејÑтво',
+'watchtasks' => 'Прати ги избраните задачи',
+'stopwatchingtasks' => 'ПреÑтани да ги Ñледиш избраните задачи',
+'assigntaskstome' => 'Префрли ми ги избраните задачи на мене',
+'dueby' => 'До',
+'dueanytime' => 'Due anytime',
+'selectduedate' => 'Select Due Date',
+'toggleselected' => 'Toggle Selected',
+'due' => 'Due',
+'assignedtome' => 'Доделено на мене',
+'tasklist' => 'ЛиÑта на задачи',
+'dateclosed' => 'Датум на затворање',
+'advanced' => 'Ðапредно',
+'searchcomments' => 'Барај во коментарите',
+'searchforall' => 'Барај ги Ñите зборови',
+'anonusers' => 'Ðнонимни кориÑници',
+'miscellaneous' => 'Разно',
+'users' => 'КориÑници',
+'taskproperties' => 'СвојÑтва на задачата',
+'selectsincedate' => 'Select Changed since',
+'changedsince' => 'Сменето од',
+'updatefs' => 'Ðадгради го Flyspray.',
+'currentversion' => 'Тековната верзија е',
+'latestversion' => 'а најновата верзија е',
+'hidemessage' => '(потÑети ме подоцна)',
+'saveas' => 'Зачувај го пребарувањето како',
+'nosearches' => 'Ðема зачувани пребарувања',
+'saving' => 'Зачувувам...',
+'votes' => 'ГлаÑови',
+'allclosedtasks' => 'Сите затворени задачи',
+'password' => 'Лозинка',
+'login' => 'Ðајави ме!',
+'rememberme' => 'Запомни ме',
+'lostpassword' => 'Ја изгуби лозинката?',
+'lostpwforfs' => 'Изгубена лозинка за Flyspray',
+'lostpwmsg1' => 'Здраво.
+
+Ја изгубив лозинката за Flyspray на ',
+'lostpwmsg2' => ', ве молам пратете ми .
+
+кориÑничко име: ',
+'regards' => '
+
+Поздрав,',
+'yourusername' => ' твоето кориÑничко име ',
+'locale' => 'mk-MK',
+'filenotexist' => 'File does not exist, or you do not have permission to access it.',
+'showtask' => 'Покажи задача',
+'now' => 'Сега',
+'go' => 'Оди!',
+'opentaskanon' => 'Отвори нова задача анонимно',
+'register' => 'РегиÑтрирај Ñе',
+'addnewtask' => 'Додај нова задача',
+'reports' => 'Лог на наÑтани',
+'editmydetails' => 'Уреди ги моите детали',
+'logout' => 'Одјави ме',
+'disabledaccount' => 'Your account has been disabled!<br />Immediately logging you out...',
+'poweredby' => 'Работи на Flyspray',
+'projects' => 'Проекти',
+'allprojects' => 'Сите прокети',
+'selectproject' => 'за проектот:',
+'tasksall' => 'Сите задачи',
+'tasksassigned' => 'Задачи доделени на мене',
+'tasksreported' => 'Задачи иÑпратени од мене',
+'taskswatched' => 'Задачи кои што ги Ñледам',
+'mysearch' => 'Мои пребарувања',
+'admintoolbox' => 'ÐдминиÑтраторÑки алатник',
+'manageproject' => 'Менаџирај проект',
+'permissions' => 'Види пермиÑии',
+'hide' => 'Скриј',
+'pendingreq' => 'PM барањето чека',
+'errorpage' => 'Flyspray cannot provide the page you requested.
+ Perhaps you requested a task that does not exist, or you
+ do not have permission to view the page you wanted.<br /><br />
+ You may have tried to use a naughty URL to interact with the database
+ backend using SQL injection. If this is true, go to the corner and think
+ about your actions. When you return, please do not do it again!',
+'permissionsforproject' => 'ПермиÑии за ',
+'switchto' => 'Смени на',
+'lastsearch' => 'ПоÑледно пребарување',
+'modify' => 'Измени',
+'noticefrom' => 'Порака од',
+'hasopened' => 'отвори нова задача на Flyspray и ти ја додели на тебе:',
+'moreinfonew' => 'Повеќе информации за задачата можеш да најдеш на Ñтраницата:',
+'newtaskcategory' => 'Отворена е нова задача во оваа категорија',
+'categoryowner' => 'Го примаш ова бидејќи Ñи ÑопÑтвеник на оваа категорија.',
+'tasksummary' => 'Ðа кратко за задачата:',
+'newtaskadded' => 'Твојата нова задаче додадена.',
+'summaryanddetails' => 'Треба да ги пополниш и краткиот Ð¾Ð¿Ð¸Ñ Ð¸ деталите.',
+'goback' => 'Оди назад.',
+'messagefrom' => 'Ова е порака од ÑиÑтемот за пријавување и пратење на грешки дека некој ',
+'hasjustmodified' => 'Ñамо што ја изменил Ñледнава задача.',
+'changedfields' => 'Changed fields are prefixed with asterisks (**)',
+'moreinfomodify' => 'You can get more information about this task at the following URL:',
+'nolongerassigned' => 'The following task is no longer assigned to you. It is now assigned to',
+'hasassigned' => 'has assigned to you the following Flyspray task:',
+'taskupdated' => 'Задачата е ажурирана.',
+'hasclosedassigned' => 'has closed the following Flyspray task that you were assigned:',
+'unassigned' => 'Ðедоделено',
+'hasclosed' => 'ја затвори Ñледнава задача.',
+'youonnotify' => 'You are receiving this because you are on the notification list.',
+'taskclosedmsg' => 'Задачата е затворена.',
+'returntotask' => 'Врати ме на деталите за задачата',
+'backtoindex' => 'Врати ме на лиÑтата Ñо задачи',
+'noclosereason' => 'Ðе избра причина за затворање на задачата.',
+'hasreopened' => 'ја отвори Ñледнава задача која што ти претходно ја затвори:',
+'taskreopenedmsg' => 'Задачата е повторно отворена.',
+'backtotask' => 'Врати ме назад на задачата.',
+'commentaddedmsg' => 'Коментарот е додаден.',
+'commenttoassigned' => 'додаде коментар на задачата која што ти е доделена:',
+'commenttotask' => 'го додаде Ñледниов коментар на задачата.',
+'nocommententered' => 'You really should enter a comment before clicking the submit button.',
+'fillinfields' => 'You didn\'t fill in all the fields.',
+'notcurrentpass' => 'Тоа не е твојата тековна лозинка!',
+'passchanged' => 'Твојата лозинка е Ñменета!.',
+'closewindow' => 'Може да го затвориш прозорецов Ñега.',
+'passnomatch' => 'Ðовите лозинки не Ñе иÑти!',
+'usernametaken' => 'Тоа кориÑничко име веќе поÑтои. Треба да избереш друго.',
+'newusercreated' => 'Креирана е нова кориÑничка Ñметка.',
+'accountcreated' => 'Твојата Ñметка е креирана.',
+'newuserwarning' => 'Note that the global preferences might require your account to be approved by an admin. If you cannot login, this is probably why.',
+'nomatchpass' => 'Лозинките не Ñе иÑти.',
+'confirmwrong' => 'Кодот за потврда не е точен!',
+'formnotcomplete' => 'Формуларот не е целоÑно пополнет.',
+'formnotnumeric' => 'ВнеÑените податоци не Ñе броеви!',
+'groupnametaken' => 'Тоа име на група веќе поÑтои.',
+'newgroupadded' => 'Ðовата група е додадена.',
+'optionssaved' => 'Опциите за Flyspray Ñе зачувани.',
+'hasuploaded' => 'has uploaded a file attachment to a task that have been assigned:',
+'hasattached' => 'has attached a file to the following task.',
+'fileuploaded' => 'Датотеката е качена.',
+'fileerror' => 'There was an error uploading your file. Perhaps the permissions on the <i>attachments/</i> directory are incorrect.',
+'contactadmin' => 'Контактирај го админиÑтраторот за овој проект.',
+'selectfileerror' => 'Ðе избра датотека.',
+'userupdated' => 'КориÑничките детали Ñе ажурирани',
+'realandemail' => 'Ðе ги пополни полињата за виÑтинÑко име и е-пошта.',
+'groupupdated' => 'Дефиницијата за групата е ажурирана.',
+'groupanddesc' => 'Ðе го пополни полето за името на групата.',
+'fillallfields' => 'Те молам пополни ги Ñите полиња.',
+'listPmustN' => '"Order" entry has to be numeric.',
+'listupdated' => 'ЛиÑтата е ажурирана.',
+'listitemadded' => 'Ðова задача е додадена во лиÑтата.',
+'relatedaddedmsg' => 'Поврзаната задача е додадена во лиÑтата.',
+'relatederror' => 'That task is already on this related task list.',
+'relatedremoved' => 'Related task removed from list.',
+'notifyadded' => 'КориÑникот е додаден на лиÑтата за извеÑтување.',
+'notifyerror' => 'That user is already on the notification list for that task.',
+'notifyremoved' => 'КориÑникот е отÑтранет од лиÑтата за извеÑтување.',
+'editcommentsaved' => 'Ðжурираниот коментар е зачуван.',
+'commentdeletedmsg' => 'Коментарот е избришан.',
+'gotonewtask' => 'Оди до новата задача која што ја креираше Ñега',
+'projectcreated' => 'Ðовиот проект е креиран. Можеш да ги менуваш деталите за проектот во делот за менаџирање на проекти.',
+'customiseproject' => 'Смени детали за проектот',
+'projectupdated' => 'Преференците за проектот Ñе ажурирани',
+'emptytitle' => 'You left the Project Title field blank. Go back and fix it.',
+'loginbelow' => 'Сега можеш да пробаш да Ñе најавиш.',
+'attachmentdeletedmsg' => 'Привезокот е избришан',
+'reminderaddedmsg' => 'Твојот потÑетник е додаден.',
+'reminderdeletedmsg' => 'The selected reminder has been deleted.',
+'flyspraytask' => 'Flyspray задача',
+'fieldsmissing' => 'Some fields contained no or invalid data.',
+'relatedinvalid' => 'Ðе поÑтои таква задача.',
+'relatedproject' => 'This task is attached to a different project. Add relation anyway?',
+'addanyway' => 'Додај Ñепак',
+'cancel' => 'Откажи',
+'alreadyedited' => 'This task was edited by someone else before you saved. Do you still want to save your changes?',
+'saveanyway' => 'Зачувај ги моите промени Ñекако',
+'nouserselected' => 'No user selected. Select at least one user before trying again.',
+'groupswitchupdated' => 'КориÑничките групи Ñе уÑпешно изменети.',
+'takenownershipmsg' => 'Оваа задача ти е доделена на тебе.',
+'adminrequestmade' => 'Your request has been sent to a Project Manager.',
+'newdepnotify' => 'A new dependency has been added to the following task:',
+'dependadded' => 'ЗавиÑноÑта е додадена',
+'dependaddfailed' => 'There was an error when adding the dependency. Check that the task exists and there is no mutual block.',
+'depremovedmsg' => 'ЗавиÑноÑта е отÑтранета',
+'newdepis' => 'Ðовата завиÑноÑÑ‚ е',
+'magicurlsent' => 'A message has been sent to your notification address. It contains a link that will take you to a page to complete this task.',
+'changefspass' => 'Промени лозинка за Flyspray',
+'magicurlmessage' => 'Please follow the link below to change your Flyspray password:',
+'erroronform' => 'There was an problem with your form submission',
+'addressused' => 'This address has been used to register a Flyspray account. If you were not expecting this message, please ignore and delete it. Go to the following URL to complete your registration:',
+'confirmcodeis' => 'Твојот код за потврда е:',
+'codesent' => 'Твојот код за потврда е иÑпратен. Следи ги упатÑтвата во пораката.',
+'codenotsent' => 'Ðе можам да го иÑпратам кодот, те молам пробај пак подоцна.',
+'taskmadeprivatemsg' => 'Оваа задача е Ñега приватна',
+'taskmadepublicmsg' => 'Оваа задача е Ñега јавна',
+'realandnotify' => 'You need to fill in the Real Name field, and either the Email Address or Jabber ID field.',
+'pmreqdeniedmsg' => 'Project Manager request denied',
+'massopsuccess' => 'Mass operations successful - where permissions allowed',
+'usernotexist' => 'That user does not exist on this Flyspray installation',
+'commentattachperms' => 'You cannot delete that comment - no permission to delete attachments',
+'voterecorded' => 'Твојот Ð³Ð»Ð°Ñ Ðµ запишан',
+'votefailed' => 'Твојот Ð³Ð»Ð°Ñ Ð½Ðµ можеше да биде запишан.',
+'createnewgroup' => 'Креирај нова група',
+'requiredfields' => 'Required fields are marked with a',
+'addthisgroup' => 'Додај ја оваа група',
+'createnewproject' => 'Креирај нов проект',
+'addnewproject' => 'Додај нов проект',
+'htmlallowed' => 'HTML код е дозволен',
+'createthisproject' => 'Креирај го овој проект',
+'inlineimages' => 'Show image attachments inline',
+'createnewtask' => 'Креирај нова задача во проектот:',
+'addanother' => 'Додај уште една задача по оваа',
+'addthistask' => 'Додај ја оваа задача',
+'notifyme' => 'ИзвеÑти ме Ñекој пат кога ќе Ñе промени нешто',
+'newtask' => 'Ðова задача',
+'attachafile' => 'Закачи датотека',
+'registernewuser' => 'РегиÑтрирај нов кориÑник',
+'none' => 'Ðишто',
+'registeraccount' => 'РегиÑтрирај ја оваа Ñметка',
+'both' => 'И двете',
+'notifyfrom' => 'ИзвеÑтување за ',
+'donotreply' => 'ОВРЕ ÐВТОМÐТСКРПОРÐКÐ, ТЕ МОЛÐÐœ ÐЕ ОДГОВÐРÐЈ.',
+'disclaimer' => 'You are receiving this message because you have requested it from the Flyspray bugtracking system. If you did not expect this message or don\'t want to receive mails in future, you can change your notification settings at the URL shown above.',
+'userwho' => 'КориÑникот кој го направил ова',
+'moreinfo' => 'Повеќе информации на Ñледнова адреÑа:',
+'newtaskopened' => 'Отворена е нова задача. Деталите Ñе подолу.',
+'notify.taskclosed' => 'Следнава задача е Ñега затворена:',
+'notify.taskreopened' => 'Следнава задача е повторно отворена:',
+'newdep' => 'Следнава задача Ñега има завиÑноÑÑ‚:',
+'notify.depremoved' => 'The following task has had a dependency removed:',
+'olddepwas' => 'Старата завиÑноÑÑ‚ беше',
+'notify.commentadded' => 'Следнава задача има нов коментар:',
+'commentis' => 'Коментарот за текÑтот е подолу.',
+'newattachment' => 'Следнава задача има нов привезок:',
+'detailsbelow' => 'Деталите Ñе подолу.',
+'notify.relatedadded' => 'A new related task has been added to the following task:',
+'relatedis' => 'Поврзаната задача е',
+'assignedtoyou' => 'Доделена ти е Ñледнава задача:',
+'takenownership' => 'ја зема Ñледнава задача:',
+'requiresaction' => 'The following task requires action by a Project Manager:',
+'requiresactionnotify' => 'Task requires action by a Project Manager',
+'pmdeny' => 'A Project Manager has denied the request pending for the following task:',
+'pmdenynotify' => 'A Project Manager has denied the request',
+'fileaddedtoo' => 'One or more files have been attached.',
+'taskwatching' => 'The following task you are watching',
+'isdepfor' => 'is a new dependency for',
+'denialreason' => 'Reason for denial',
+'taskchanged' => 'The following task has been changed. The changes are listed below. For full information about what has changed, visit the URL and click the History tab.',
+'useraddedtoassignees' => 'A user has added themself to the list of users assigned to this task.',
+'removeddepis' => 'The removed dependency is',
+'isnodepfor' => 'is no dependency anymore for',
+'usergroups' => 'КориÑнички групи',
+'pmtoolbox' => 'Project Manager\'s Toolbox',
+'groupmanage' => 'Менаџмент на групи',
+'pendingrequests' => 'Барања што чекаат',
+'reasongiven' => 'Причина',
+'nopendingreq' => 'There are no pending Project Manager requests.',
+'givereason' => 'Додај причина',
+'catlisted' => 'Уредувач на лиÑтата на категории',
+'oslisted' => 'Уредувач на лиÑтата на оперативни ÑиÑтеми',
+'verlisted' => 'Уредувач на лиÑтата на верзии',
+'tasktypeed' => 'Уредувач на лиÑтата на типови на задачи',
+'resed' => 'Уредувач на лиÑтата на решенија',
+'deny' => 'Одбиј',
+'notifiedwhen' => 'ИзвеÑти кога',
+'onlynewtasks' => 'Се отвораат нови задачи',
+'allevents' => 'Се Ñлучи било каков наÑтан во било која задача',
+'feeds' => 'Фидови',
+'feeddescription' => 'ÐžÐ¿Ð¸Ñ Ð½Ð° фид',
+'feedimgurl' => 'Feed image URL (leave blank for no image)',
+'notifysubject' => 'ÐаÑлов за извеÑтувања',
+'notifysubjectinfo' => '(%p = наÑлов на проектот, %s = Ð¾Ð¿Ð¸Ñ Ð½Ð° задачата, %t = id на задачата, %a = дејÑтво, %u = кориÑник)',
+'priority6' => 'Флеш',
+'priority5' => 'Веднаш',
+'priority4' => 'Итно',
+'priority3' => 'ВиÑко',
+'priority2' => 'Ðормално',
+'priority1' => 'ÐиÑко',
+'sendcode' => 'ИÑпрати код!',
+'entercode' => 'ВнеÑи го кодот што го прими во пораката за извеÑтување. ИÑто така внеÑи ја и лозинката за твојата Ñметка.',
+'confirmationcode' => 'Код за потврда',
+'registererror' => 'Ensure that you have filled in all required fields, and that you have entered correct details for your desired notification type.',
+'validusername' => '(only alphanumeric chars and - _ . are allowed)',
+'emailtaken' => 'That email address or Jabber-ID is already taken. You will need to choose another one.',
+'note' => '<strong>Забелешка:</strong> Ќе ти биде иÑпратен код за потврда пред да ти биде креирана Ñметката. Кодот ќе ти биде иÑпратен преку преферираниот метод за извеÑтување.<br />Ðко внеÑеш лажни детали, <strong>нема да го добиеш кодот</strong>.',
+'changelog' => 'Changelog',
+'changeloggen' => 'Генератор на Changelog',
+'listfrom' => 'List changelog entries from',
+'to' => 'до',
+'oldestfirst' => 'Прво најÑтарите',
+'recentfirst' => 'Прво најновите',
+'severityrep' => 'Извештај за ÑериозноÑÑ‚',
+'totalopen' => 'Вкупно отворени задачи',
+'age' => 'СтароÑÑ‚',
+'agerep' => 'Извештај за ÑтароÑÑ‚',
+'eventsrep' => 'Извештај за наÑтани',
+'events' => 'ÐаÑтани',
+'Tasks' => 'Задачи',
+'opened' => 'Отворени',
+'edited' => 'Уредени',
+'assigned' => 'Доделени',
+'within' => 'Внатре во',
+'pastday' => 'ПоÑледниот ден',
+'pastweek' => 'ПоÑледната недела',
+'pastmonth' => 'ПоÑледниот меÑец',
+'pastyear' => 'ПоÑледната година',
+'nolimit' => 'Без лимит',
+'from' => 'Од',
+'duein' => 'Доаѓа во',
+'selectfromdate' => 'Избери од датум',
+'selecttodate' => 'Избери до датум',
+'showvoters' => 'Покажи/Ñкриј глаÑови',
+'roadmap' => 'Мапа',
+'roadmapfor' => 'Мапа за верзијата',
+'tasks' => 'задачи',
+'completed' => 'завршени.',
+'opentasks' => 'отворени задачи',
+'of' => '% од',
+'severity5' => 'Критично',
+'severity4' => 'ВиÑоко',
+'severity3' => 'Средно',
+'severity2' => 'ÐиÑко',
+'severity1' => 'Многу ниÑко',
+'Redirect' => 'Префрли',
+'redirectmsg' => 'If your browser does not support meta redirection please click %sHERE%s to be redirected',
+'allowclosedcomments' => 'Дозволувај коментари на затворени задачи',
+'comment' => 'Коментар',
+'editowncomments' => 'Уредувај Ñвои коментари',
+'reopened' => 'Повторно отворено',
+'loading' => 'Вчитувам...',
+'notifyown' => 'ИзвеÑтувај ме за ÑопÑтвени промени',
+'youremail' => 'Твојата адреÑа за е-пошта',
+'thankyouforbug' => 'Ти благодариме што го пријави проблемот. Можеш да ја пратиш задачата и нејзиниот Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑ Ð½Ð° Ñледнава адреÑа:',
+'anonuser' => 'Ðнонимен кориÑник',
+'conflict' => 'Конфликт',
+'file' => 'Датотека',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Големина',
+'projectgroup' => 'Група на проектот',
+'profile' => 'Профил:',
+'viewprofile' => 'Види профил',
+'regdate' => 'РегиÑтриран од',
+'tasksopened' => 'Отворени задачи',
+'replyto' => 'Одговори на',
+'notifytypes' => 'Типови на извеÑтувања',
+'pm.taskchanged' => 'Задачата е променета',
+'pm.taskreopened' => 'Task reopened',
+'pm.depadded' => 'Dependency added',
+'pm.depremoved' => 'Dependency removed',
+'pmrequest' => 'PM request',
+'pmrequestdenied' => 'PM request denied',
+'newassignee' => 'New assignee',
+'revdepadded' => 'Reverse dependency added',
+'revdepaddedremoved' => 'Reverse dependency removed',
+'assigneeadded' => 'Assignee added',
+'addusergroup' => 'Додај го кориÑникот во групава',
+'groupmembers' => 'Членови на групата',
+'deleteuser' => 'Избриши го овој кориÑник',
+'userdeleted' => 'КориÑникот е избришан',
+'autoassign' => 'Auto-assign a task to the category owner',
+'ssl' => 'SSL',
+'updatewrong' => 'You have the update check feature enabled, but an error ocurred while trying
+ to contact the update server, either your host do not allow outbound connections
+ or the error was caused by a network problem.
+ Please visit the flyspray website to make sure you are running the latest version.',
+'deleteproject' => 'Delete this project and move contents to',
+'projectdeleted' => 'Project deleted successfully',
+'feedforall' => 'Feed for all projects',
+'usercreated' => 'КориÑникот е креиран',
+'userdeleted' => 'КориÑникот е избришан',
+'created' => 'Креиран',
+'deleted' => 'Избришан',
+'userid' => 'КориÑничка ID',
+'editassignments' => 'Уреди доделени задачи',
+'preview' => 'Преглед',
+'anyprogress' => 'Било каков прогреÑ',
+'tasksrelated' => 'Задачи поврзани Ñо оваа задача',
+'duplicatetasks' => 'Duplicate tasks of this task',
+'databasemodfailed' => 'Database modification failed. Possible reasons are insufficient permissions.',
+'frequency' => 'РедовноÑÑ‚',
+'newuserregistered' => 'A new user has registered at your Flyspray installation. The user details are as follows:',
+'newuserregisterednotify' => 'Се региÑтрираше нов кориÑник',
+'notify_registration' => 'Notify admins on new user registration',
+'textversion' => 'Text Version',
+'onlyprimary' => 'Tasks not blocking other tasks',
+'switch' => 'Смени',
+'max' => 'макÑ.',
+'dates' => 'Датуми',
+'selectduedatefrom' => 'Доаѓа од',
+'selectduedateto' => 'до',
+'selectsincedatefrom' => 'Сменето од',
+'selectsincedateto' => 'до',
+'selectdate' => 'Избери датум',
+'selectopenedfrom' => 'Отворено од',
+'selectopenedto' => 'до',
+'selectclosedfrom' => 'Затворено од',
+'selectclosedto' => 'до',
+'startat' => 'Почнува на',
+'hasattachment' => 'Has attachment',
+'private' => 'Приватно',
+'watching' => 'Се Ñледи',
+'alreadyvotedthistask' => 'веќе глаÑаше за оваа задача',
+'alreadyvotedthisday' => 'веќе глаÑаше денеÑ',
+'visibility' => 'ВидливоÑÑ‚',
+'public' => 'Јавно',
+'leaveemptyauto' => 'Leave password fields empty if you want the password to be automatically generated.',
+'novalidemail' => 'You did not enter a valid email address.',
+'novalidjabber' => 'You did not enter a valid Jabber address.',
+'missingrequired' => 'You did not fill in all required fields.',
+'entervalidusername' => 'Please enter a valid user and real name.',
+'couldnotaddusernotif' => 'Could not add this user to the notification list.',
+'defaulttask' => 'Default task description',
+'all' => 'all',
+'events.useraddedtoassignees' => 'User added to assignees',
+'vote(s)' => 'vote(s)',
+'eventlog' => 'Лог на наÑтани',
+'assignmentchanged' => 'СопÑтвеноÑта е променета',
+'detailedinfo' => 'Детални информации',
+'All' => 'Сите',
+'tasksireported' => 'Задачи кои што Ñум ги пријавил',
+'recentlyopened' => 'Скоро отворени',
+'stats' => 'СтатиÑтики',
+'totaltasks' => 'вкупно задачи',
+'mostwanted' => 'Ðајважни задачи',
+'defaultentry' => 'Default entry page',
+'toplevel' => 'Top level view',
+'overview' => 'Преглед',
+'error#' => 'Грешка #',
+'error1' => 'You don\'t have sufficient permissions to view this attachment.',
+'error3' => 'Repeated action, redirecting to main page.',
+'error4' => 'You don\'t have administrative rights.',
+'error5' => 'That user does not exist on this Flyspray installation.',
+'error6' => 'Invalid admin area.',
+'error7' => 'Login failed, paassword incorrect!',
+'error71' => 'Account locked for %d minutes due to too many failed login attempts!',
+'error8' => 'You didn\'t enter both a username and password.',
+'error9' => 'Task does not exist or no permissions to view this task.',
+'error10' => 'This task does not exist.',
+'error101' => 'You have no permission to view this task.',
+'error102' => 'You have no permission to view this task, logging in might help.',
+'error11' => 'No permission to edit this comment.',
+'error12' => 'No valid magic key! Are you sure that you got that from your notification message?',
+'error13' => 'Anonymous users don\'t have a profile.',
+'error14' => 'You don\'t have sufficient permissions to create a new group.',
+'error15' => 'You don\'t have sufficient permissions to open a task.',
+'error16' => 'You are not a project manager.',
+'error17' => 'Invalid PM area.',
+'error18' => 'Invalid magic URL.',
+'error19' => 'That user does not exist on this Flyspray installation.',
+'error20' => 'Invalid database modification.',
+'error21' => 'One or more emails could not be sent. Check your config.',
+'error22' => 'No new user registration allowed.',
+'error23' => 'User or group not enabled for login.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Roadmap only available for a specific project.',
+'done' => 'готово',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Project couldn\'t be deleted.',
+'GMT' => 'GMT',
+'timezone' => 'ВременÑка зона',
+'accept' => 'Прифати',
+'reasonfordeinal' => 'Reason for denial',
+'pruneclosedlinks' => 'Prune Closed Links',
+'pruneclosedtasks' => 'Prune Closed Tasks',
+'pagegenerated' => 'Page and image generated in %d seconds.',
+'pruninglevel' => 'Pruning Level',
+'lastuser' => 'The last user cannot be deleted.',
+'allprivate' => 'All projects are private.',
+'deletegroup' => 'Delete this group and move users to',
+'parent' => 'Parent',
+'ordertip' => 'The order these items will appear in the list',
+'showtip' => 'Show this item in the list',
+'deletetip' => 'Delete this item from the list',
+'del' => 'del',
+'request1' => 'A task closure has been requested.',
+'request2' => 'A request to re-open the task has been made.',
+'allpriorities' => 'All Priorities',
+'noroadmap' => 'No roadmap available (no project specific "future" versions exist).',
+'expand' => 'Прошири',
+'collapse' => 'Собери',
+'expandall' => 'Прошири Ñè',
+'collapseall' => 'Собери Ñè',
+'minpwsize' => 'Минималната должина за лозинката е 5 знаци',
+'passwordtoosmall' => 'Лозинката е прекратка.',
+'accountwaslocked' => 'Your account had been locked due to too many failed login attempts.',
+'failedattempts' => 'Се Ñлучија %d неуÑпешни обиди за најава.',
+'groupnotexist' => 'Selected group does not exist in this project.',
+'searchindetails' => 'Детали за пребарување',
+'showasassignees' => 'Show as assignees',
+'find' => 'Ðајди',
+'tls' => 'TLS',
+);
+
+?>
diff --git a/lang/nl.php b/lang/nl.php
new file mode 100644
index 0000000..6cf7117
--- /dev/null
+++ b/lang/nl.php
@@ -0,0 +1,823 @@
+<?php
+//
+// This file is auto generated with .langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the .langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Bewerk gebruiker',
+'username' => 'Gebruikersnaam',
+'realname' => 'Echte naam',
+'emailaddress' => 'E-mailadres',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Notificatietype',
+'group' => 'Groep',
+'accountenabled' => 'Account ingeschakeld',
+'updatedetails' => 'Details bijwerken',
+'setglobally' => 'Deze voorkeur is globaal ingesteld',
+'usergroupmanage' => 'Gebruiker- en groepsbeheer',
+'newuser' => 'Registeer nieuwe gebruiker',
+'newgroup' => 'Maak nieuwe groep',
+'yes' => 'Ja',
+'no' => 'Nee',
+'editgroup' => 'Bewerk groep',
+'groupname' => 'Groepsnaam',
+'description' => 'Beschrijving',
+'admin' => 'Beheerdersgroep',
+'opennewtasks' => 'Open nieuwe taken',
+'modifytasks' => 'Pas bestaande taken aan',
+'addcomments' => 'Voeg commentaar toe',
+'attachfiles' => 'Voeg bijlagen toe',
+'vote' => 'Leden kunnen inloggen',
+'groupenabled' => 'Leden kunnen inloggen',
+'groupopen' => 'Leden kunnen inloggen',
+'tasktypelist' => 'Lijst met taaktypes',
+'categorylist' => 'Categorielijst',
+'oslist' => 'Besturingssysteemlijst',
+'resolutionlist' => 'Lijst met oplossingen',
+'versionlist' => 'Versielijst',
+'severitylist' => 'Lijst met ernstigheden',
+'listnote' => 'Let op: Het uitvinken van de toonoptie in de bewerkmodus kan een paar taken wijzigen. Veranderen van het naamveld verandert alle taken met die naam',
+'name' => 'Naam',
+'order' => 'Volgorde',
+'back' => 'Terug',
+'text' => 'Tekst',
+'highlight' => 'Markeer',
+'show' => 'Toon',
+'owner' => 'Eigenaar',
+'selectowner' => 'Selecteer eigenaar',
+'update' => 'Aanpassen',
+'addnew' => 'Nieuwe toevoegen',
+'flysprayprefs' => 'Flyspray voorkeuren',
+'projecttitle' => 'Projecttitel',
+'baseurl' => 'Basis-URL voor deze installatie',
+'replyaddress' => 'Antwoordadres voor notificaties',
+'themestyle' => 'Thema / Stijl',
+'language' => 'Taal',
+'anonview' => 'Laat anonieme gebruikers de taken zien',
+'allowanon' => 'Laat anonieme gebuikers nieuwe taken openen',
+'never' => 'Nooit',
+'anonymously' => 'Anoniem',
+'afterregister' => 'Enkel na registratie',
+'spamproof' => 'Schakel bevestigingscode in voor nieuwe gebruikers',
+'anongroup' => 'Groep voor nieuwe gebruikersregistraties',
+'groupassigned' => 'Leden van deze groep kunnen taken toegewezen krijgen',
+'forcenotify' => 'Forceer taaknotificaties als',
+'neversend' => 'Verstuur nooit',
+'userchoose' => 'Laat elke gebruiker kiezen',
+'email' => 'E-mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Standaard categorie-eigenaar',
+'noone' => 'Niemand',
+'jabbernotify' => 'Jabber notificaties',
+
+'jabberserver' => 'Server',
+'jabberport' => 'Poort',
+'jabberuser' => 'Account gebruikersnaam',
+'jabberpass' => 'Account wachtwoord',
+'saveoptions' => 'Opties opslaan',
+'editcomment' => 'Bewerk commentaar',
+'commentby' => 'Commentaar door',
+'saveeditedcomment' => 'Sla bewerkte commentaar op',
+'projectprefs' => 'Projecteigenschappen',
+'pagetitle' => 'Paginatitel',
+'defaultproject' => 'Standaard Project',
+'projectlists' => 'Projectlijsten',
+'showlogo' => 'Toon titellogo',
+'intromessage' => 'Welkomstbericht',
+'isactive' => 'Project is actief',
+'createproject' => 'Maak een nieuw project',
+'nopermission' => 'U hebt geen toestemming om deze pagina te gebruiken',
+'listordertip' => 'De volgorde van deze items zal verschijnen in de lijst',
+'listshowtip' => 'Toon dit item in de lijst',
+'categoryownertip' => 'Deze persoon ontvangt notificaties als een taak in deze categorie geopend wordt',
+'categoryparenttip' => 'Bovenliggende categorie waaronder deze nieuwe valt',
+'notsubcategory' => 'Geen - top-niveau categorie',
+'showinlineimages' => 'Toon afbeeldingbijlagen in het bericht',
+'dateformat' => 'Datumformaat',
+'dateformat_extended' => 'Gedetailleerd datum formaat',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'Geen caching',
+'cache_disk' => 'Cache op schijf',
+'cache_db' => 'Cache in DB',
+'subcategoryof' => 'Sub-categorie van',
+'visiblecolumns' => 'Kolommen getoond in takenlijst',
+'tense' => 'Tijd',
+'listtensetip' => 'Verleden, Nu of Toekomst',
+'past' => 'Verleden',
+'present' => 'Nu',
+'future' => 'Toekomst',
+'oldpass' => 'Oude wachtwoord',
+'nooldpass' => 'Er was geen oud wachtwoord opgeslagen',
+'oldpasswrong' => 'Oude wachtwoord was niet correct',
+'changepass' => 'Wijzig wachtwoord',
+'confirmpass' => 'Bevestig wachtwoord',
+'projectmanager' => 'Projectleider',
+'viewtasks' => 'Bekijk taken',
+'modifyowntasks' => 'Bewerk eigen taken',
+'modifyalltasks' => 'Bewerk taken die niet van de gebruiker zelf zijn',
+'viewcomments' => 'Bekijk opmerkingen',
+'editcomments' => 'Bewerk opmerkingen',
+'deletecomments' => 'Verwijder opmerkingen',
+'viewattachments' => 'Bekijk bijlagen',
+'createattachments' => 'Maak bijlagen',
+'deleteattachments' => 'Verwijder bijlagen',
+'viewhistory' => 'Bekijk geschiedenis',
+'closeowntasks' => 'Sluit eigen taken',
+'closeothertasks' => 'Sluit taken die niet van de gebruiker zelf zijn',
+'assigntoself' => 'Taak aan uzelf toewijzen als deze nog niet toegewezen is',
+'assignotherstoself' => 'Wijs andermans taak aan uzelf toe',
+'viewreports' => 'Bekijk rapporten',
+'othersview' => 'Iedereen toestaan dit project te bekijken',
+'usersandgroups' => 'Gebruikers en Groepen',
+'globalgroup' => 'Globale groep',
+'globalgroups' => 'Globale Groepen',
+'defaultglobalgroup' => 'Standaard globale groep voor nieuwe gebruikers',
+'addtogroup' => 'Toevoegen aan groep',
+'moveuserstogroup' => 'Verplaats gebruikers naar groep',
+'nogroup' => 'Geen groep - verwijder uit project',
+'eventdesc' => 'Gebeurtenis omschrijving',
+'requestedby' => 'Aangevraagd door',
+'daterequested' => 'Datum aanvraag',
+'closetask' => 'Sluit taak',
+'reopentask' => 'Heropen taak',
+'applymember' => 'Projectlidmaatschap aanvragen',
+'forcurrentproj' => 'Voor huidige project',
+'lostpw' => 'Verloren wachtwoord opvragen',
+'lostpwexplain' => 'Voer u gebruikersnaam in om uw wachtwoord op te vragen. Er wordt een e-mail gestuurd naar het notificatieadres dat u in uw profiel ingesteld hebt.',
+'sendlink' => 'Verstuur link',
+'savenewpass' => 'Nieuwe wachtwoord opslaan',
+'anonreg' => 'Nieuwe gebruikersregistraties toestaan',
+'allowanonopentask' => 'Anonieme gebruikers toestaan taken te openen',
+'editglobalgroup' => 'Bewerk globale groep',
+'editgroupforproj' => 'Bewerk groep voor project',
+'notshownforadmin' => 'Machtigingen worden niet getoond voor de beheerdersgroep. U hoeft deze niet te bewerken.',
+'general' => 'Algemeen',
+'userregistration' => 'Gebruikersregistratie',
+'notifications' => 'Notificaties',
+'resetoptions' => 'Reset opties naar beginwaarden',
+'preferences' => 'Voorkeuren',
+'tasktypes' => 'Taaktypen',
+'resolutions' => 'Oplossingen',
+'categories' => 'Categoriën',
+'operatingsystems' => 'Besturingssystemen',
+'versions' => 'Versies',
+'admintoolboxlong' => 'Beheerders gereedschap',
+'newproject' => 'Nieuw Project',
+'delete' => 'Verwijder',
+'listdeletetip' => 'Verwijder dit item uit de lijst',
+'lookandfeel' => 'Uiterlijk en gedrag',
+'globaltheme' => 'Globaal Uiterlijk',
+'emailnotify' => 'E-mailnotificatie',
+'fromaddress' => 'Van het adres',
+'smtpserver' => 'SMTP Server',
+'smtpuser' => 'SMTP Gebruikersnaam',
+'smtppass' => 'SMTP Wachtwoord',
+'addrewrite' => 'Gebruik adresherschrijving',
+'usereminderdaemon' => 'Enable background reminder daemon',
+'tasksperpage' => 'Taken per pagina van de takenlijst',
+'addtoassignees' => 'Voeg mijzelf toe als toegewezen persoon',
+'taskstatuses' => 'Taakstatussen',
+'canvote' => 'Kan op taken stemmen',
+'loginsuccessful' => 'Login succesvol.',
+'youareloggedout' => 'U bent uitgelogd',
+'waitwhiletransfer' => 'Wacht alstublieft terwijl u doorverwezen wordt...',
+'clicknowait' => 'Klik hier als u niet wenst te wachten',
+'accountdisabled' => 'Uw account is uitgeschakeld. Neem contact op met een beheerder.',
+'task' => 'Taak',
+'edittask' => 'Bewerk deze taak',
+'openedby' => 'Geopend door',
+'editedby' => 'Laatst bewerkt door',
+'tasktype' => 'Taaktype',
+'category' => 'Categorie',
+'status' => 'Status',
+'assignedto' => 'Toegekend aan',
+'operatingsystem' => 'Besturingssysteem',
+'severity' => 'Ernstigheid',
+'reportedversion' => 'Gerapporteerde versie',
+'dueinversion' => 'Toe te schrijven aan versie',
+'undecided' => 'Onbeslist',
+'percentcomplete' => 'Percentage voltooid:',
+'details' => 'Details',
+'savedetails' => 'Sla details op',
+'canceledit' => 'Annuleer bewerking',
+'anonymous' => 'Anonieme toevoeger',
+'complete' => 'voltooid',
+'closedby' => 'Gesloten door',
+'reasonforclosing' => 'De reden voor sluiting is:',
+'reopenthistask' => 'Heropen deze taak',
+'comments' => 'Commentaar',
+'attachments' => 'Bijlagen',
+'relatedtasks' => 'Gerelateerde taken',
+'edit' => 'Bewerk',
+'addcomment' => 'Voeg commentaar toe',
+'fileuploadedby' => 'Bestand geüpload door',
+'uploadafile' => 'Voeg een bestand toe',
+'uploadnow' => 'Upload nu!',
+'thesearerelated' => 'Deze taken zijn verwant aan deze taak',
+'remove' => 'Verwijder',
+'addnewrelated' => 'Voeg een nieuwe verwante taak toe',
+'add' => 'Voeg toe!',
+'otherrelated' => 'Andere verwante taken van deze taak',
+'receivenotify' => 'Deze gebruikers krijgen gedetailleerde berichten wanneer deze taak verandert.',
+'addusertolist' => 'Voeg gebruiker toe aan deze lijst',
+'addtolist' => 'Voeg toe aan de lijst',
+'addmyself' => 'Voeg mijzelf toe aan deze lijst',
+'removemyself' => 'Verwijder mijzelf van deze lijst',
+'theseusersnotify' => 'Deze gebruikers krijgen gedetailleerde berichten wanneer deze taak verandert.',
+'attachedtoproject' => 'Toegevoegd aan project',
+'reminders' => 'Herinneringen',
+'system' => 'Systeem',
+'remindthisuser' => 'Herinner deze gebruiker',
+'thisoften' => 'Zo vaak',
+'startafter' => 'Wachten voordat herinneringen gestart worden',
+'hours' => 'Uren',
+'days' => 'Dag(en)',
+'weeks' => 'Week(en)',
+'addreminder' => 'Herinnering toevoegen',
+'defaultreminder' => 'Dit is een herinnering om naar de volgende Flyspray taak te kijken:',
+'message' => 'Bericht',
+'closed' => 'Gesloten',
+'filename' => 'Bestandsnaam:',
+'date' => 'Datum',
+'filesize' => 'Bestandsgrootte:',
+'closurecomment' => 'Meer commentaar over de sluiting:',
+'history' => 'Geschiedenis',
+'nohistory' => 'Geen geschiedenis beschikbaar.',
+'eventdate' => 'Datum',
+'user' => 'Gebruiker',
+'event' => 'Gebeurtenis',
+'fieldchanged' => 'Veld veranderd',
+'taskopened' => 'Taak geopend',
+'taskreopened' => 'Taak is heropend.',
+'taskclosed' => 'Taak is gesloten',
+'commentadded' => 'Commentaar is toegevoegd',
+'commentedited' => 'Commentaar gewijzigd',
+'commentdeleted' => 'Commentaar is verwijderd.',
+'attachmentadded' => 'Bijlage toegevoegd',
+'attachmentdeleted' => 'De bijlage is verwijderd',
+'taskedited' => 'Taakdetails gewijzigd',
+'notificationadded' => 'Gebruiker toegevoegd aan de notificatielijst',
+'notificationdeleted' => 'Gebruiker verwijderd van de notificatielijst',
+'relatedadded' => 'Gerelateerde taak toegevoegd aan lijst.',
+'relateddeleted' => 'Gerelateerde taak verwijderd',
+'taskassigned' => 'Taak toegekend aan',
+'taskreassigned' => 'Taak opnieuw toegekend aan',
+'assignmentremoved' => 'Toewijzing verwijderd',
+'summary' => 'Samenvatting',
+'addedasrelated' => 'Taak toegevoegd aan de gerelateerde lijst van',
+'deletedasrelated' => 'Taak verwijderd van de gerelateerde lijst van',
+'reminderadded' => 'Uw herinnering is toegevoegd.',
+'reminderdeleted' => 'De geselecteerde herinnering is verwijderd.',
+'priority' => 'Prioriteit',
+'previousvalue' => 'Vorige waarde',
+'newvalue' => 'Nieuwe waarde',
+'selectareason' => 'Selecteer een reden',
+'assigntome' => 'Aan mijzelf toewijzen',
+'reopenrequest' => 'Heropening aanvragen',
+'requestclose' => 'Sluiting aanvragen',
+'ownershiptaken' => 'Gebruiker nam lidmaatschap',
+'closerequestmade' => 'Aanvragen taak sluiting',
+'reopenrequestmade' => 'Aanvragen taak heropening',
+'taskdependson' => 'Deze taak is afhankelijk van',
+'taskblocks' => 'Deze taak blokkeert de volgende taken tot sluiting',
+'depadded' => 'Afhankelijkheid toegevoegd',
+'depaddedother' => 'Deze taak heeft als afhankelijkheden',
+'depremoved' => 'Taakafhankelijkheid is verwijderd',
+'depremovedother' => 'Deze taak is verwijderd uit de afhankelijkhedenlijst van een andere taak',
+'showdetailserror' => 'Deze taak bestaat niet, of u hebt geen rechten om de taak te bekijken.',
+'makeprivate' => 'Maak Privé',
+'makepublic' => 'Maak Publiekelijk',
+'taskmadeprivate' => 'Deze taak is omgezet naar een privétaak',
+'taskmadepublic' => 'Deze taak is weer publiekelijk gemaakt',
+'confirmdeletecomment' => 'Weet u zeker dat u dit commentaar wilt verwijderen? %s',
+'confirmdeleteattach' => 'Weet u zeker dat u dit toegevoegde bestand wilt verwijderen?',
+'selectedhistory' => 'Toon geselecteerde geschiedenisdetails',
+'showallhistory' => 'Toon opnieuw de volledige Geschiedenistab',
+'hidethis' => 'Verberg dit gebied',
+'mark100' => 'Markeer taak als 100% af',
+'watchtask' => 'Houd deze taak in de gaten',
+'stopwatching' => 'Stop met het in de gaten houden van deze taak',
+'commentlink' => 'Link naar dit commentaar',
+'submitreq' => 'Stuur verzoek op',
+'reasonforreq' => 'Reden voor deze aanvraag',
+'pmreqdenied' => 'Projectleider heeft het verzoek afgewezen',
+'taskpendingreq' => 'Wachtende acties van projectleider. Bekijk de geschiedenistab voor details.',
+'previoustask' => 'Vorige taak',
+'nexttask' => 'Volgende taak',
+'duedate' => 'Beoogde datum',
+'attachnoperms' => 'Er zijn bestanden aan dit commentaar toegevoegd, maar u heeft niet genoeg rechten om deze te bekijken.',
+'open' => 'Open',
+'depgraph' => 'Bekijk afhangeklijkheidsgraaf',
+'reset' => 'Reset',
+'selectusers' => 'Selecteer gebruikers...',
+'addmetoassignees' => 'Voeg mij toe als toegewezen persoon',
+'addedtoassignees' => 'Gebruiker toegevoegd aan lijst toegekende personen',
+'dependencygraph' => 'Afhankelijkheidsgraaf',
+'attachanotherfile' => 'Voeg nog een bestand toe',
+'OK' => 'Oké',
+'addvote' => 'Voeg stem toe',
+'notifyfromfs' => 'Notificatie van Flyspray',
+'autogenerated' => 'DIT IS EEN AUTOMATISCH BERICHT, NIET ANTWOORDEN AUB',
+'forward' => 'Vooruit',
+'previous' => 'Vorige',
+'next' => 'Volgende',
+'first' => 'Eerste',
+'last' => 'Laatste',
+'page' => 'Pagina %d van %d',
+'search' => 'Zoeken',
+'alltasktypes' => 'Alle taaktypen',
+'allseverities' => 'Alle ernstigheden',
+'alldevelopers' => 'Alle ontwikkelaars',
+'notyetassigned' => 'Nog niet toegekend',
+'allcategories' => 'Alle categoriën',
+'allstatuses' => 'Alle statussen',
+'allopentasks' => 'Alle open taken',
+'sortthiscolumn' => 'Sorteer op deze kolom',
+'id' => 'ID',
+'project' => 'Project:',
+'dateopened' => 'Openingsdatum',
+'progress' => 'Vooruitgang',
+'searchthisproject' => 'Zoek in dit project',
+'dueanyversion' => 'Iedere versie',
+'anyversion' => 'Geraporteerd in Elke Versie',
+'dueversion' => 'Toe te schrijven aan',
+'lastedit' => 'Laatst gewijzigd',
+'os' => 'Besturingssysteem',
+'reportedin' => 'Gemeld in',
+'taskrange' => 'Taken %d - %d van %d',
+'noresults' => 'Geen resultaten gevonden.',
+'takeaction' => 'Onderneem actie',
+'watchtasks' => 'Houd geselecteerde taken in de gaten',
+'stopwatchingtasks' => 'Stop met het in de gaten houden van de geselecteerde taken',
+'assigntaskstome' => 'Ken geselecteerde taken aan mij toe',
+'dueby' => 'Beoogt voor ',
+'dueanytime' => 'Elk moment',
+'selectduedate' => 'Selecteer beoogde datum',
+'toggleselected' => 'Keer selectie om',
+'due' => 'Beoogd voor',
+'assignedtome' => 'Aan mijzelf toegekend',
+'tasklist' => 'Takenlijst',
+'dateclosed' => 'Sluitdatum',
+'advanced' => 'Geavanceerd',
+'searchcomments' => 'Zoek binnen commentaren',
+'searchforall' => 'Zoek voor alle woorden',
+'anonusers' => 'Anonieme gebruikers',
+'miscellaneous' => 'Overige',
+'users' => 'Gebruikers',
+'taskproperties' => 'Taakeigenschappen',
+'selectsincedate' => 'Selecteer Aangepast sinds',
+'changedsince' => 'Aangepast sinds',
+'updatefs' => 'Gaarna update Flyspray.',
+'currentversion' => 'Uw huidige versie is',
+'latestversion' => 'en de nieuwste versie is',
+'hidemessage' => '(herinner me op een later tijdstip)',
+'saveas' => 'Sla zoektocht op als',
+'nosearches' => 'Geen opgeslagen zoekopdrachten',
+'saving' => 'Bezig met oplaan...',
+'votes' => 'Stemmen',
+'allclosedtasks' => 'Alle gesloten taken',
+'password' => 'Wachtwoord',
+'login' => 'Login!',
+'rememberme' => 'Onthoud login',
+'lostpassword' => 'Wachtwoord verloren?',
+'lostpwforfs' => 'Verloren wachtwoord voor Flyspray',
+'lostpwmsg1' => "Gegroet,\n\nHet wachtwoord ben ik kwijtgeraakt voor ",
+'lostpwmsg2' => ", voorzie mij gaarne van een nieuw wachtwoord.\n\nGebruikersnaam: ",
+'regards' => "\n\nMet vriendelijke groeten,",
+'yourusername' => ' uw gebruikersnaam ',
+'locale' => 'nl',
+'filenotexist' => 'Bestand bestaat niet, of u heeft niet genoeg rechten het te bekijken.',
+'showtask' => 'Toon taak',
+'now' => 'Nu',
+'go' => 'Toon',
+'opentaskanon' => 'Open anoniem een nieuwe taak',
+'register' => 'Registreer als een nieuwe gebruiker',
+'addnewtask' => 'Voeg nieuwe taak toe',
+'reports' => 'Rapporten',
+'editmydetails' => 'Bewerk mijn details',
+'logout' => 'Uitloggen',
+'disabledaccount' => 'Uw account is uitgeschakeld.<br />U wordt onmiddellijk uitgelogd!',
+'poweredby' => 'Aangedreven door Flyspray',
+'projects' => 'Projecten',
+'allprojects' => 'Alle projecten',
+'selectproject' => 'voor project:',
+'tasksall' => 'Alle taken',
+'tasksassigned' => 'Taken toegewezen aan mijzelf',
+'tasksreported' => 'Taken door mij gerapporteerd ',
+'taskswatched' => 'Taken die u in de gaten houdt',
+'mysearch' => 'Mijn zoekopdrachten',
+'admintoolbox' => 'Beheerders gereedschap',
+'manageproject' => 'Beheer project',
+'permissions' => 'Bekijk permission',
+'hide' => 'Verberg',
+'pendingreq' => 'Verzoeken aan projectleider wachtende',
+'errorpage' => "Flyspray kan de opgevraagde pagina niet tonen. Wellicht heeft u een taak proberen op te vragen die niet bestaat, of heeft u geen toegangsrechten om de pagina te bekijken.<br /><br />\nMisschien heeft u geprobeerd de URL aan te passen om het database aan te sturen door middel van een SQL Injection. In dat geval, ga in de hoek staan en schaam u !",
+'permissionsforproject' => 'Permissies voor ',
+'switchto' => 'Schakel over op',
+'lastsearch' => 'Mijn laatste zoekopdracht',
+'modify' => 'Aanpassen',
+'noticefrom' => 'Bericht van',
+'hasopened' => 'heeft een NIEUWE Flyspray taak GEOPEND en aan u toegekend:',
+'moreinfonew' => 'U kunt meer informatie over deze bug vinden op de Flyspray pagina:',
+'newtaskcategory' => 'Een nieuwe Flayspray taak is geopend in deze categorie',
+'categoryowner' => 'U ontvangt dit omdat u op de lijst staat als categorie-eigenaar',
+'tasksummary' => 'Taak samenvatting:',
+'newtaskadded' => 'Uw nieuwe taak is toegevoegd',
+'summaryanddetails' => 'U moet de samenvatting en de details invullen.',
+'goback' => 'Ga terug en voer uit.',
+'messagefrom' => 'Dit is een bericht van het Flyspray Bug Tracking System op',
+'hasjustmodified' => 'Heeft juist de volgende taak aangepast',
+'changedfields' => 'Aangepaste velden woorden voorgegaan door asterisken (***)',
+'moreinfomodify' => 'U kunt meer informatie krijgen over deze taak op de volgende URL:',
+'nolongerassigned' => 'De volgende taak is niet langer aan u toegekend. Het is nu toegekend aan',
+'hasassigned' => 'heeft aan u de volgende Flyspray taak toegekend:',
+'taskupdated' => 'Taak is bijgewerkt',
+'hasclosedassigned' => 'heeft de volgende, aan u toegekende, taak gesloten:',
+'unassigned' => 'Niet toegekend',
+'hasclosed' => 'heeft de volgende taak gesloten.',
+'youonnotify' => 'U ontvangt dit omdat u op de notificatielijst staat.',
+'taskclosedmsg' => 'Taak is gesloten.',
+'returntotask' => 'Ga terug naar de taakdetails',
+'backtoindex' => 'Ga terug naar de takenlijst',
+'noclosereason' => 'U selecteerde geen reden voor het sluiten van deze taak.',
+'hasreopened' => 'heeft de volgende Flyspray taak heropend die door u gesloten was:',
+'taskreopenedmsg' => 'Taak is heropend.',
+'backtotask' => 'Ga terug naar de taak.',
+'commentaddedmsg' => 'Commentaar is toegevoegd.',
+'commenttoassigned' => 'heeft commentaar toegevoegd aan de aan u toegekende taak:',
+'commenttotask' => 'heeft de volgende commentaar toegevoegd aan deze taak.',
+'nocommententered' => 'U moet echt een commentaar toevoegen voor u op de \'toevoegen\'-knop drukt.',
+'fillinfields' => 'U heeft niet alle velden ingevuld.',
+'notcurrentpass' => 'Dat is niet uw huidige wachtwoord!',
+'passchanged' => 'Uw wachtwoord is veranderd.',
+'closewindow' => 'U kunt nu dit venster sluiten.',
+'passnomatch' => 'Uw nieuwe wachtwoorden kwamen niet overeen!',
+'usernametaken' => 'Die gebruikersnaam is reeds in gebruik. U zal een andere naam moeten kiezen.',
+'newusercreated' => 'Nieuw gebruikersaccount is aangemaakt.',
+'accountcreated' => 'Uw account is aangemaakt.',
+'newuserwarning' => 'Het kan zijn dat de beheerder uw account moet goedkeuren. Dit kan de reden zijn waarom u niet kunt inloggen.',
+'nomatchpass' => 'Wachtwoorden kwamen niet overeen.',
+'confirmwrong' => 'Bevestigingscode is incorrect!',
+'formnotcomplete' => 'Formulier was niet volledig ingevuld.',
+'formnotnumeric' => 'Ingevoegde data was niet numeriek!',
+'groupnametaken' => 'Die groepsnaam is reeds in gebruik.',
+'newgroupadded' => 'Nieuwe groep toegevoegd.',
+'optionssaved' => 'Flyspray opties opgeslagen.',
+'hasuploaded' => 'heeft een bijlage geüpload naar een taak die toegekend is:',
+'hasattached' => 'heeft een bestand toegevoegd aan de volgende taak.',
+'fileuploaded' => 'Bestand is geüpload.',
+'fileerror' => 'Er is fout opgetreden tijdens het uploaden van het bestand. Misschien zijn de machtigingen van de <i>attachments</i>/map incorrect.',
+'contactadmin' => 'Neem contact op met de beheerder van dit project.',
+'selectfileerror' => 'U selecteerde geen bestand en/of gaf geen beschrijving op.',
+'userupdated' => 'Gebruikersdetails zijn bijgewerkt',
+'realandemail' => 'U heeft velden \'Echte naam\' en \'E-mailadres\' niet ingevuld.',
+'groupupdated' => 'Groepsdefinities bijgewerkt.',
+'groupanddesc' => 'U vulde de Groepsnaam en de Beschrijving niet in.',
+'fillallfields' => 'Vult u alstublieft alle velden in.',
+'listPmustN' => '"Volgorde"-veld moet numeriek zijn.',
+'listupdated' => 'Lijst is bijgewerkt.',
+'listitemadded' => 'Nieuw lijst item toegevoegd.',
+'relatedaddedmsg' => 'Gerelateerde taak toegevoegd aan de lijst.',
+'relatederror' => 'Deze taak staat reeds op de lijst met gerelateerde taken.',
+'relatedremoved' => 'Gerelateerde taak verwijdert van de lijst.',
+'notifyadded' => 'Gebruiker toegevoegd aan de notificatielijst',
+'notifyerror' => 'De gebruiker staat reeds op de notificatielijst van deze taak.',
+'notifyremoved' => 'Gebruiker verwijdert van de notificatielijst.',
+'editcommentsaved' => 'Bijgewerkte commentaar opgeslagen.',
+'commentdeletedmsg' => 'Commentaar is verwijderd.',
+'gotonewtask' => 'Ga direct naar de taak die u net aangemaakt hebt',
+'projectcreated' => 'Uw nieuwe project is toegevoegd. Volg onderstaande link om de categorie, besturingssystemen en versielijsten in te stellen',
+'customiseproject' => 'Pas dit project aan',
+'projectupdated' => 'Projectvoorkeuren aangepast',
+'emptytitle' => 'U liet het projecttitelveld leeg. Ga terug en pas dit aan.',
+'loginbelow' => 'U kunt nu proberen in te loggen.',
+'attachmentdeletedmsg' => 'De toegevoegde bestanden zijn verwijderd',
+'reminderaddedmsg' => 'Uw herinnering is toegevoegd.',
+'reminderdeletedmsg' => 'De geselecteerde herinnering is verwijderd.',
+'flyspraytask' => 'Flyspray taak',
+'fieldsmissing' => 'Sommige velden bevatten geen of foute gegevens.',
+'relatedinvalid' => 'Taak is niet gevonden.',
+'relatedproject' => 'Taak is aan een ander project gekoppeld.',
+'addanyway' => 'Toch toevoegen',
+'cancel' => 'Annuleren',
+'alreadyedited' => 'Deze taak is door iemand anders gewijzigd voordat u hem opgeslagen had.',
+'saveanyway' => 'Sla de wijzigingen toch op',
+'nouserselected' => 'Geen gebruiker geselecteerd. Gebruik de terugknop van uw browser en selecteer er minstens één.',
+'groupswitchupdated' => 'Gebruiker groepen succesvol gewijzigd.',
+'takenownershipmsg' => 'Deze taak is nu aan u toegekend.',
+'adminrequestmade' => 'Uw aanvraag is verzonden naar een projectleider.',
+'newdepnotify' => 'Een nieuwe afhankelijkheid is toegevoegd aan de volgende taak:',
+'dependadded' => 'Taakafhankelijkheid is toegevoegd',
+'dependaddfailed' => 'U kunt op het moment geen taak als afhankelijkheid toevoegen',
+'depremovedmsg' => 'Taakafhankelijkheid is verwijderd',
+'newdepis' => 'De nieuwe afhankelijkheid is',
+'magicurlsent' => 'Er is een bericht verzonden naar uw notificatieadres. Hierin staat een link waarmee u de taak kunt afronden.',
+'changefspass' => 'Wijzig Flyspray wachtwoord',
+'magicurlmessage' => 'Gebruik onderstaande link om uw Flyspray wachtwoord te wijzigen:',
+'erroronform' => 'Er is een probleem met het versturen van het formulier',
+'addressused' => 'Op dit adres is zojuist een Flyspray account geregisteerd. Als u dit bericht niet verwachtte, gelieve het dan te negeren. Uw bevestigingscode is:',
+'confirmcodeis' => 'Uw bevestingings code is:',
+'codesent' => 'Uw bevestingingscode is verzonden. Volg de instructies in het bericht op.',
+'codenotsent' => 'De code kon niet verstuurd worden, gaarne probeer het later opnieuw.',
+'taskmadeprivatemsg' => 'Deze taak is privé gemaakt',
+'taskmadepublicmsg' => 'Deze taak is weer publiekelijk gemaakt',
+'realandnotify' => 'Het is verplicht uw volledige naam in te vullen, en uw E-mailadres of uw Jabber-ID veld.',
+'pmreqdeniedmsg' => 'Verzoek aan projectleider afgewezen',
+'massopsuccess' => 'Massaopdrachten succesvol waar er genoeg rechten waren',
+'usernotexist' => 'Die gebruikersnaam bestaat niet binnen deze Flyspray installatie',
+'commentattachperms' => 'U kunt dat commentaar niet verwijderen - niet genoeg rechten om bijgevoegde bestanden te verwijderen',
+'voterecorded' => 'Uw stem is geregistreerd',
+'votefailed' => 'Uw stem kon niet worden geregistreerd',
+'createnewgroup' => 'Maak nieuwe groep aan',
+'requiredfields' => 'Vereiste velden zijn gemarkeerd met een a',
+'addthisgroup' => 'Voeg deze groep to',
+'createnewproject' => 'Maak een nieuw project aan',
+'addnewproject' => 'Nieuw project toevoegen',
+'htmlallowed' => 'HTML-code is toegestaan',
+'createthisproject' => 'Maak dit project',
+'inlineimages' => 'Toon afbeeldingbijlagen inline',
+'createnewtask' => 'Maak een nieuwe taak aan',
+'addanother' => 'Voeg nog een andere taak toe',
+'addthistask' => 'Voeg deze taak toe',
+'notifyme' => 'Houd mij op de hoogte als deze taak veranderd',
+'newtask' => 'Nieuwe taak',
+'attachafile' => 'Voeg toe als bestand',
+'registernewuser' => 'Registreer nieuwe gebruiker',
+'none' => 'Geen',
+'registeraccount' => 'Registreer dit account',
+'both' => 'Beide',
+'notifyfrom' => 'Notificatie van ',
+'donotreply' => 'DIT IS EEN AUTOMATISCH VERSTUURD BERICHT, REAGEER ER NIET OP.',
+'disclaimer' => 'U ontvangt dit bericht omdat u het heeft aangevraagd bij het Flyspray bug-volgsysteem. U kunt toekomstige notificaties uitzetten door de URL hierboven weergegeven te bezoeken.',
+'userwho' => 'Gebruiker die dit gedaan heeft',
+'moreinfo' => 'Meer informatie kan op de volgende URL gevonden worden:',
+'newtaskopened' => 'Een nieuwe Flyspray-taak is geopend. Details volgend hieronder.',
+'notify.taskclosed' => 'De volgende taak is nu gesloten:',
+'notify.taskreopened' => 'De volgende taak is heropend:',
+'newdep' => 'De volgende taak heeft een nieuwe afhankelijkheid:',
+'notify.depremoved' => 'Een afhankelijkheid is verwijderd van de volgende taak:',
+'olddepwas' => 'De oude afhankelijkheid was',
+'notify.commentadded' => 'Nieuw commentaar is aan de volgende taak toegevoegd:',
+'commentis' => 'Het commentaar volgt hieronder.',
+'newattachment' => 'Een nieuw bestand is toegevoegd aan de volgende taak:',
+'detailsbelow' => 'De details volgen hieronder.',
+'notify.relatedadded' => 'Een nieuwe gerelateerde taak is toegevoegd aan de volgende taak:',
+'relatedis' => 'De gerelateerde taak is',
+'assignedtoyou' => 'U is de volgende taak toegekend:',
+'takenownership' => 'Deze taak is nu aan u toegewezen.',
+'requiresaction' => 'De volgende taken wachten op een actie van een projectbeheerder:',
+'requiresactionnotify' => 'Taak vereist actie van een projectleider',
+'pmdeny' => 'Een projectleider heeft het verzoek afgewezen aanhangig de volgende taak:',
+'pmdenynotify' => 'Een projectleider heeft het verzoek afgewezen',
+'fileaddedtoo' => 'Één of meerdere bestanden zijn toegevoegd aan dit commentaar.',
+'taskwatching' => 'De volgende taken houdt u in de gaten',
+'isdepfor' => 'is een nieuwe afhankelijkheid voor',
+'denialreason' => 'Reden voor afwijzing',
+'taskchanged' => 'De volgende taken zijn veranderd. De veranderingen zijn hieronder weergegeven. Voor de volledige informatie over wat er veranderd is, bezoek de URL en klik op de Geschiedenistab.',
+'useraddedtoassignees' => 'Een gebruiker heeft zichzelf toegevoegd aan de lijst van gebruikers die aan deze taak zijn toegewezen',
+'removeddepis' => 'De verwijderde afhankelijkheid is',
+'isnodepfor' => 'is geen afhankelijkheid meer voor',
+'usergroups' => 'Gebruikers en groepen',
+'pmtoolbox' => 'Projectleiders gereedschap',
+'groupmanage' => 'Groepsbeheer',
+'pendingrequests' => 'Openstaande aanvragen',
+'reasongiven' => 'Reden opgegeven',
+'nopendingreq' => 'Er zijn geen onafgehandelde projectleiderverzoeken.',
+'givereason' => 'Geef een reden op',
+'catlisted' => 'Categoriënlijsteditor',
+'oslisted' => 'Besturingssysteemlijsteditor',
+'verlisted' => 'Versielijsteditor',
+'tasktypeed' => 'Taaktypelijsteditor',
+'resed' => 'Oplossingenlijsteditor',
+'deny' => 'Wijs af',
+'notifiedwhen' => 'Stuur notificatie als',
+'onlynewtasks' => 'Niuewe taken zijn geopend',
+'allevents' => 'Elk mogelijke gebeurtenis van elke taak',
+'feeds' => 'Feeds',
+'feeddescription' => 'Feed omschrijving',
+'feedimgurl' => 'Feedafbeelding URL (laat leeg voor geen afbeeldingen)',
+'notifysubject' => 'Onderwerp voor notificaties',
+'notifysubjectinfo' => '(%p = projectnaam, %s = taaksamenvatting, %t = taak-ID, %a = actie, %u = gebruikersnaam)',
+'priority6' => 'Kritiek',
+'priority5' => 'Onmiddelijk',
+'priority4' => 'Dringend',
+'priority3' => 'Hoog',
+'priority2' => 'Normaal',
+'priority1' => 'Laag',
+'sendcode' => 'Verzend code!',
+'entercode' => 'Voer de bevestigingscode in, zoals u die ontvangen heeft in het notificatiebericht. Typ ook uw gewenste wachtwoord in.',
+'confirmationcode' => 'Bevestigingscode',
+'registererror' => 'Verzeker u ervan dat u alle vereiste velden ingevuld hebt en dat u de correcte details voor uw gewenste notificatietype ingevuld hebt.',
+'validusername' => '(alleen letters en - _ . zijn toegestaan)',
+'emailtaken' => 'Dat e-mailadres of Jabber-ID is al bezet. Kies een andere.',
+'note' => '<b>Opmerking:</b> U krijgt een bevestigingscode voordat uw account aangemaakt wordt. Deze code wordt verzonden naar uw gekozen notificatietype hierboven. ALS U VALSE GEGEVENS INVULT, ONTVANGT U UW CODE NIET.',
+'changelog' => 'Changelog',
+'changeloggen' => 'Changelog generator',
+'listfrom' => 'Toon changelog toevoegingen van',
+'to' => 'naar',
+'oldestfirst' => 'Oudste eerst',
+'recentfirst' => 'Meest recente eerst',
+'severityrep' => 'Overzicht ernstigheden',
+'totalopen' => 'Totaal open taken',
+'age' => 'Leeftijd',
+'agerep' => 'Leeftijdrapport',
+'eventsrep' => 'Gebeurtenissenrapport',
+'events' => 'Gebeurtenissen',
+'Tasks' => 'Taken',
+'opened' => 'Geopend',
+'edited' => 'Bewerkt',
+'assigned' => 'Toegekend',
+'within' => 'Periode',
+'pastday' => 'Vorige dag',
+'pastweek' => 'Vorige week',
+'pastmonth' => 'Vorige maand',
+'pastyear' => 'Vorig jaar',
+'nolimit' => 'Geen limiet',
+'from' => 'Van',
+'duein' => 'Geimplementeerd in',
+'selectfromdate' => 'Selecteer van-datum',
+'selecttodate' => 'Selecteer naar-datum',
+'showvoters' => 'Toon/verberg stemmers',
+'roadmap' => 'Planning',
+'roadmapfor' => 'Planning voor versie',
+'tasks' => 'Taken',
+'completed' => 'kompleet.',
+'opentasks' => 'open taken:',
+'of' => '% van',
+'severity5' => 'Kritisch',
+'severity4' => 'Hoog',
+'severity3' => 'Gemiddeld',
+'severity2' => 'Laag',
+'severity1' => 'Zeer laag',
+'Redirect' => 'Doorsturen',
+'redirectmsg' => 'In het geval uw browser meta-redirections niet ondersteunt klik %sHERE%s om doorgestuurd te worden',
+'allowclosedcomments' => 'Sta commentaar aan gesloten taken toe',
+'comment' => 'Commentaar',
+'editowncomments' => 'Bewerk eigen commentaar',
+'reopened' => 'Heropend',
+'loading' => 'Bezig met laden...',
+'notifyown' => 'Stuur een notificatie voor eigen aanpassingen',
+'youremail' => 'Uw e-mailadres',
+'thankyouforbug' => 'Bedankt voor het rapporteren van uw probleem. U kunt de taak bekijken en de voortgang bestuderen met de volgende URL:',
+'anonuser' => 'Anonieme gebruiker',
+'conflict' => 'Bosting',
+'file' => 'Bestand',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Grootte',
+'projectgroup' => 'Projectgroep',
+'profile' => 'Profiel:',
+'viewprofile' => 'Bekijk profiel',
+'regdate' => 'Geregisteerd sinds',
+'tasksopened' => 'Geopende taken',
+'replyto' => 'Antwoord naar',
+'notifytypes' => 'Notificatietypen',
+'pm.taskchanged' => 'Taak veranderd',
+'pm.taskreopened' => 'Taak heropend',
+'pm.depadded' => 'Afhankelijkheid toegevoegd',
+'pm.depremoved' => 'Afhankelijkheid verwijderd',
+'pmrequest' => 'Verzoek aan projectleider',
+'pmrequestdenied' => 'Projectmaangerverzoek afgewezen',
+'newassignee' => 'Nieuwe persoon voor toegewezen personen',
+'revdepadded' => 'Omgekeerde afhankelijkheid toegevoegd',
+'revdepaddedremoved' => 'Omgekeerde afhankelijkheid verwijderd',
+'assigneeadded' => 'Persoon toegevoegd',
+'addusergroup' => 'Voeg gebruiker aan deze groep toe',
+'groupmembers' => 'Groepleden',
+'deleteuser' => 'Verwijder deze gebruiker',
+'userdeleted' => 'Gebruiker verwijderd',
+'autoassign' => 'Ken taken automatisch aan categorie-eigenaar aan',
+'ssl' => 'SSL',
+'updatewrong' => 'De versiecontroleoptie is actief, maar er is een fout opgetreden bij het benaderen van de updateserver. Wellicht mag uw host geen uitgaande verbindingen aanmaken, of er zijn problemen met het netwerk. Bezoek de Flyspray webstek om er van verzekerd te zijn dat u de laatste versie draait.',
+'deleteproject' => 'Verwijder dit project en verplaats de inhoud naar',
+'projectdeleted' => 'Project succesvol verwijderd',
+'feedforall' => 'Feed voor alle projecten',
+'usercreated' => 'Gebruiker aangemaakt',
+'created' => 'Gecreëerd',
+'deleted' => 'Verwijderd',
+'userid' => 'Gebruikers-ID',
+'editassignments' => 'Bewerk toegekende taken',
+'preview' => 'Voorbeeld',
+'anyprogress' => 'Elke voortgang',
+'tasksrelated' => 'Taken gerelateerd aan deze taak',
+'duplicatetasks' => 'Dubbele versies van deze taak',
+'databasemodfailed' => 'Database-aanpassing mislukt. Mogelijke oorzaak is het hebben van onvoldoende rechten.',
+'frequency' => 'Frequentie',
+'newuserregistered' => 'Een nieuwe gebruiker heeft zich geregisteerd bij uw Flyspray installation. De gebruikersdetails zijn als volgt:',
+'newuserregisterednotify' => 'Een nieuwe gebruiker heeft zich geregisteerd',
+'notify_registration' => 'Licht beheerders in bij een nieuwe gebruikersregistratie',
+'textversion' => 'Tekstversie',
+'onlyprimary' => 'Taken die geen andere taken blokkeren',
+'switch' => 'Schakel over',
+'max' => 'Max.',
+'dates' => 'Data',
+'selectduedatefrom' => 'Beoogd vanaf',
+'selectduedateto' => 'tot',
+'selectsincedatefrom' => 'Veranderd vanaf',
+'selectsincedateto' => 'tot',
+'selectdate' => 'Selecteer datum',
+'selectopenedfrom' => 'Geopend vanaf',
+'selectopenedto' => 'tot',
+'selectclosedfrom' => 'Gesloten vanaf',
+'selectclosedto' => 'tot',
+'startat' => 'Begin vanaf',
+'hasattachment' => 'Heeft een bijlage',
+'private' => 'Privé',
+'watching' => 'In de gaten houden',
+'alreadyvotedthistask' => 'U heeft reeds voor deze taak gestemd',
+'alreadyvotedthisday' => 'U heeft reeds vandaag gestemd',
+'visibility' => 'Zichtbaarheid',
+'public' => 'Publiekelijk',
+'leaveemptyauto' => 'Laat het wachtwoordveld leeg als u een wachtwoord toegewezen wilt krijgen.',
+'novalidemail' => 'U heeft geen geldig e-mailadres ingevuld.',
+'novalidjabber' => 'U heeft geen geldig Jabberadres ingevuld.',
+'missingrequired' => 'U heeft niet alle benodigde velden ingevuld.',
+'entervalidusername' => 'Gaarne vul een geldig gebruikers- en echte naam in.',
+'couldnotaddusernotif' => 'Kon de gebruiker niet toevoegen aan de ',
+'defaulttask' => 'Standaard taakomschrijving',
+'all' => 'Alle',
+'events.useraddedtoassignees'=> 'Gebruiker toegevoegd aan toegewezen personen',
+'vote(s)' => 'Stem(men)',
+'eventlog' => 'Logboek',
+'assignmentchanged' => 'Toekenning aangepast',
+'detailedinfo' => 'Gedetailleerde informatie',
+'All' => 'Alle',
+'tasksireported' => 'Taken die u heeft aangemeld',
+'recentlyopened' => 'Recentelijk geopend',
+'stats' => 'Statistieken',
+'totaltasks' => 'Totaal aantal taken',
+'mostwanted' => 'Meest gewilde taken',
+'defaultentry' => 'Standaard opgavepagina',
+'toplevel' => 'Topniveau-overzicht',
+'overview' => 'Overzicht',
+'error#' => 'Fout #',
+'error1' => 'U heeft niet genoeg rechten om deze bijlage te bekijken.',
+'error3' => 'Herhaalde actie, u wordt doorverwezen naar de hoofdpagina.',
+'error4' => 'U heeft geen beheerdersrechten.',
+'error5' => 'Die gebruiker bestaat niet.',
+'error6' => 'Ongeldig beheerdersgebied.',
+'error7' => 'Inloggen mislukt, verkeerde gebruikersnaam of wachtwoord.',
+'error71' => 'Account voor %d minuten bevroren omdat er te veel mislukte inlogpogingen gedaan zijn.',
+'error8' => 'U moet zowel een gebruikersnaam als een wachtwoord opgeven.',
+'error9' => 'Onbekende taak, of u heeft niet genoeg rechten om de taak te bekijken.',
+'error10' => 'Onbekende taak, of u heeft niet genoeg rechten om de taak te bekijken.',
+'error101' => 'U heeft niet genoeg rechten om deze taak te bekijken.',
+'error102' => 'U heeft niet genoeg rechten op deze taak te bekijken, wellicht helpt het om in te loggen.',
+'error11' => 'U heeft geen toestemming om het commentaar te bewerken.',
+'error12' => 'Dit is een ongeldige magische sleutel! Weet u zeker dat deze sleutel in uw notificatiebericht stond?',
+'error13' => 'Anonieme gebruikers hebben geen profiel.',
+'error14' => 'U heeft niet genoeg rechten om een nieuwe groep aan te maken.',
+'error15' => 'U heeft niet genoeg rechten om een taak te openen.',
+'error16' => 'U bent geen projectleider.',
+'error17' => 'Ongeldig PM-gebied.',
+'error18' => 'Ongeldige magische URL.',
+'error19' => 'Die gebruiker bestaat niet.',
+'error20' => 'Ongeldige databaseaanpassing.',
+'error21' => 'Één of meer e-mails konden niet verstuurd worden. Controleer uw configuratiebestand.',
+'error22' => 'Nieuwe gebruikersregistraties niet toegestaan.',
+'error23' => 'Gebruiker of groep heeft geen toestemming om in te loggen.',
+'error24' => 'Er is noch een dotprogramma, noch een publiekelijk toegankelijke dotserver ingesteld.',
+'error25' => 'Roadmap alleen beschikbaar voor specifieke projecten.',
+'done' => 'Klaar',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Project kon niet verwijderd worden.',
+'GMT' => 'GMT',
+'timezone' => 'Tijdzone',
+'accept' => 'Accepteer',
+'reasonfordeinal' => 'Reden voor weigering',
+'pruneclosedlinks' => 'Schoon gesloten links op',
+'pruneclosedtasks' => 'Schoon gesloten taken op',
+'pagegenerated' => 'Pagina en afbeeldig gegenereerd in %d seconden.',
+'pruninglevel' => 'Opschoonniveau',
+'lastuser' => 'De laatste gebruiker kan niet verwijderd worden.',
+'allprivate' => 'Alle projecten zijn privé.',
+'deletegroup' => 'Verwijder deze groep en verplaats de gebruikers naar',
+'parent' => 'Ouder',
+'ordertip' => 'Op welke manier items geordend worden',
+'showtip' => 'Toon dit item in de lijst',
+'deletetip' => 'Verwijder dit item van de lijst',
+'del' => 'Verwijder',
+'request1' => 'Er is een verzoek gedaan om een taak te sluiten.',
+'request2' => 'Er is een verzoek gedaan om een taak te heropenen.',
+'allpriorities' => 'Alle prioriteiten',
+'noroadmap' => 'Geen planning beschikbaar, omdat er geen toekomstige versies bestaan binnen dit project.',
+'expand' => 'Uitklappen',
+'collapse' => 'Inklappen',
+'expandall' => 'Alles uitklappen',
+'collapseall' => 'Alles inklappen',
+'minpwsize' => 'Minimale wachtwoordlengte is 5 karakters',
+'passwordtoosmall' => 'Wachtwoordlength is te klein',
+'accountwaslocked' => 'Uw account is uitgeschakeld, omdat er te veel mislukte inlogpogingen gedaan zijn.',
+'failedattempts' => 'Er hebben %d mislukte inlogpogingen plaatsgevonden.',
+'groupnotexist' => 'Geselecteerde groep bestaat niet in dit project.',
+);
+
+?>
diff --git a/lang/no.php b/lang/no.php
new file mode 100644
index 0000000..2941cc6
--- /dev/null
+++ b/lang/no.php
@@ -0,0 +1,1007 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Endre bruker',
+'accountenabled' => 'Konto aktivert',
+'editallusers' => 'Vis alle brukere',
+'username' => 'Brukernavn',
+'usersupdated' => 'Brukere ble oppdatert',
+'realname' => 'Virkelig navn',
+'emailaddress' => 'Epostadresse',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Profilbilde',
+'notifytype' => 'Varslingstype',
+'group' => 'Gruppe',
+'enableaccounts' => 'Aktiver kontoer',
+'disableaccounts' => 'Deaktiver kontoer',
+'deleteaccounts' => 'Slett kontoer',
+'updatedetails' => 'Oppdater innhold',
+'setglobally' => 'Denne preferansen er satt til alle brukere.',
+'usergroupmanage' => 'Bruker- og gruppeinnstillinger',
+'newuser' => 'Registrer ny bruker',
+'newuserbulk' => 'Registrer flere nye brukere',
+'bulkuserstoadd' => 'Liste på nye brukere',
+'optionsforallusers' => 'Valg for alle nye brukere',
+'newgroup' => 'Opprett ny gruppe',
+'yes' => 'Ja',
+'no' => 'Nei',
+'editgroup' => 'Endre gruppe',
+'groupname' => 'Gruppenavn',
+'description' => 'Beskrivelse',
+'admin' => 'Administratorgruppe',
+'opennewtasks' => 'Ny oppgave',
+'modifytasks' => 'Endre eksisterende oppgave',
+'addcomments' => 'Legg til kommentarer',
+'attachfiles' => 'Legg til filer',
+'vote' => 'Stemme',
+'groupenabled' => 'Medlemmer kan logge inn',
+'groupopen' => 'Medlemmer kan logge inn',
+'tasktypelist' => 'Oppgavetyper',
+'categorylist' => 'Kategorier',
+'oslist' => 'Operativsystemer',
+'resolutionlist' => 'Løsningstyper',
+'versionlist' => 'Versjoner',
+'severitylist' => 'PÃ¥virkninger',
+'listnote' => 'NB: Dersom "Vis" tas bort kan det være at noen oppgaver endres eller ikke vises slik de skal. Endring av verdiene i feltene vil også endre verdiene i de oppgavene som allerede har de feltene. Grunnen til at noen felter ikke kan slettes kan være beskyttet eller allerede brukt i oppgaver.',
+'name' => 'Navn',
+'order' => 'Rekkefølge',
+'back' => 'Tilbake',
+'text' => 'Tekst',
+'highlight' => 'Fremheving',
+'show' => 'Vis',
+'owner' => 'Eier',
+'selectowner' => 'Valgt eier',
+'update' => 'Oppdater',
+'addnew' => 'Legg til',
+'flysprayprefs' => 'Preferanser for Endringsportal',
+'projecttitle' => 'Applikasjonstittel',
+'baseurl' => 'Base URL for denne installasjonen',
+'replyaddress' => 'Svar e-postadresse for varsling',
+'themestyle' => 'Stil',
+'language' => 'Språk',
+'anonview' => 'Tillate anonyme brukere å vise oppgaver',
+'allowanon' => 'Tillat anonyme brukere til å åpne nye oppgaver',
+'never' => 'Aldri',
+'anonymously' => 'Anonymt',
+'afterregister' => 'Kun etter registrering',
+'spamproof' => 'Aktiver bekreftelseskode for nye brukerregistreringer',
+'anongroup' => 'Gruppe for nye registrerte brukere',
+'groupassigned' => 'Medlemmer i denne gruppen kan tildeles oppgaver',
+'forcenotify' => 'Tving oppgave varslinger som',
+'neversend' => 'Aldri send',
+'userchoose' => 'La hver bruker velge',
+'email' => 'Epost',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Standard kategorieier',
+'noone' => 'Ingen',
+'jabbernotify' => 'Jabber PÃ¥minnelser',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Konto brukernavn',
+'jabberpass' => 'Kontopassord',
+'saveoptions' => 'Lagre',
+'editcomment' => 'Endre kommentar',
+'commentby' => 'Kommentert av',
+'saveeditedcomment' => 'Lagre endret kommentar',
+'projectprefs' => 'Applikasjonspreferanser',
+'pagetitle' => 'Sidetittel',
+'defaultproject' => 'Standard applikasjon',
+'projectlists' => 'Applikasjonsliste',
+'showlogo' => 'Vis tittel logo bilde',
+'showgravatars' => 'Vis avatars',
+'emailNoHTML' => 'Ingen HTML i e-post',
+'intromessage' => 'Innledende melding',
+'isactive' => 'Applikasjonen er aktiv',
+'createproject' => 'Ny applikasjon',
+'nopermission' => 'Du har ikke tilgang til å se denne siden.',
+'listordertip' => 'Sorteringsrekkefølge i listen',
+'listshowtip' => 'Vis denne i listen',
+'categoryownertip' => "Denne personen vil motta varslinger når en oppgave i denne kategorien er åpnet\n",
+'categoryparenttip' => 'Overkategori som denne skal ligge under',
+'notsubcategory' => 'Hovedkategori',
+'showinlineimages' => 'Vis bilde vedlegg in felt',
+'dateformat' => 'Datoformat',
+'dateformat_extended' => 'Datoformat på innhold',
+'cache_feeds' => 'Buffre feeden',
+'no_cache' => 'Ingen buffer',
+'cache_disk' => 'Buffer på disk',
+'cache_db' => 'Buffer i DB',
+'subcategoryof' => 'Underkategori av',
+'visiblecolumns' => 'Kolonner som vises i oppgavelisten',
+'visiblefields' => 'Felt når legge til / endre / vis oppgave',
+'tense' => 'Spent',
+'listtensetip' => 'Fortid, nåtid eller fremtid',
+'past' => 'Fortid',
+'present' => 'NÃ¥tid',
+'future' => 'Fremtid',
+'oldpass' => 'Gammelt passord',
+'nooldpass' => 'Gammelt passord ble ikke tastet inn',
+'oldpasswrong' => 'Gammelt passord var feil',
+'changepass' => 'Endre passord',
+'confirmpass' => 'Bekreft passord',
+'projectmanager' => 'Applikasjonsansvarlig',
+'viewtasks' => 'Vis oppgaver',
+'modifyowntasks' => 'Endre egne oppgaver',
+'modifyalltasks' => 'Endre oppgaver som ikke er brukerens egen',
+'viewcomments' => 'Vis kommentarer',
+'editcomments' => 'Endre kommentarer',
+'deletecomments' => 'Slett kommentarer',
+'viewattachments' => 'Vis vedlegg',
+'createattachments' => 'Sett inn vedlegg',
+'deleteattachments' => 'Slett vedlegg',
+'viewhistory' => 'Se historikk',
+'closeowntasks' => 'Stenge egne oppgaver',
+'closeothertasks' => 'Lukk oppgaver som ikke er brukerens egen',
+'assigntoself' => 'Tildele oppgaver til seg selv hvis det ikke allerede er tildelt',
+'assignotherstoself' => 'Tildele andres oppgaver til seg selv',
+'viewreports' => 'Vis aktivitetslogg',
+'othersview' => 'Tillat alle å se dette prosjektet',
+'usersandgroups' => 'Brukere og grupper',
+'globalgroup' => 'Global gruppe',
+'globalgroups' => 'Globale grupper',
+'defaultglobalgroup' => 'Standard global gruppe for nye brukere',
+'addtogroup' => 'Legg til i gruppe',
+'moveuserstogroup' => 'Flytt brukere til gruppe',
+'nogroup' => 'Ingen Group - Fjern fra prosjektet',
+'eventdesc' => 'Aktivitetsbeskrivelse',
+'requestedby' => 'Spurt av',
+'daterequested' => 'Dato Spurt',
+'closetask' => 'Steng oppgaven',
+'reopentask' => 'Re-Ã¥pne oppgave',
+'applymember' => 'Søk om Prosjekt medlemskap',
+'forcurrentproj' => 'For aktuelt prosjekt',
+'lostpw' => 'Mistet passord gjen oppreting',
+'lostpwexplain' => "Skriv inn ditt brukernavn, for å sende en link for å endre passordet ditt. Dette vil bli sendt til varsling adresse i profilen din.\n",
+'sendlink' => 'Send lenke',
+'savenewpass' => 'Lagre nytt passord',
+'anonreg' => 'Tillat registrering for nye brukere',
+'allowanonopentask' => 'Tillat at anonyme brukere oppretter oppgaver',
+'editglobalgroup' => 'Endre globale grupper',
+'editgroupforproj' => 'Rediger gruppe for Prosjekt',
+'notshownforadmin' => "Tillatelser er ikke vist for admin-gruppen. Du trenger ikke å redigere dem.\n",
+'general' => 'Generelt',
+'userregistration' => 'Brukerregistrering',
+'notifications' => 'Varslinger',
+'resetoptions' => 'Tilbakestill',
+'preferences' => 'Preferanser',
+'tasktypes' => 'Oppgavetyper',
+'resolutions' => 'Løsningstyper',
+'categories' => 'Kategorier',
+'operatingsystems' => 'Operativsystemer',
+'versions' => 'Versjoner',
+'admintoolboxlong' => 'Adminverktøy',
+'newproject' => 'Ny applikasjon',
+'delete' => 'Slett',
+'link' => 'Lenke',
+'referencelinks' => 'Referanselink',
+'listdeletetip' => 'Slett denne fra listen',
+'lookandfeel' => 'Utseende',
+'globaltheme' => 'Globalt tema',
+'emailnotify' => 'Epostvarslinger',
+'fromaddress' => 'Fra adresse',
+'smtpserver' => 'SMTP-server',
+'smtpuser' => 'SMTP Brukernavn',
+'smtppass' => 'SMTP Passord',
+'addrewrite' => 'Bruk adressen omskriving',
+'usereminderdaemon' => 'Aktiver bakgrunns påminnelse daemon',
+'tasksperpage' => 'Oppgaver pr. side på oppgavelisten',
+'addtoassignees' => 'Legg selv til følgere',
+'taskstatuses' => 'Oppgavestatuser',
+'canvote' => 'Kan gi stemmer',
+'loginsuccessful' => 'Innlogging vellykket',
+'youareloggedout' => 'Du har blitt logget ut.',
+'waitwhiletransfer' => 'Vennligst vent mens du blir overført ...',
+'clicknowait' => 'Klikk her hvis du ikke ønsker å vente.',
+'accountdisabled' => 'Din konto er deaktivert. Kontakt administrator.',
+'task' => 'Oppgave',
+'edittask' => 'Endre oppgaven',
+'openedby' => 'Innmeldt av',
+'editedby' => 'Sist endret av',
+'tasktype' => 'Type',
+'category' => 'Kategori',
+'status' => 'Status',
+'assignedto' => 'Tildelt til',
+'operatingsystem' => 'Operativsystem',
+'severity' => 'PÃ¥virkning',
+'reportedversion' => 'Rapportert versjon',
+'dueinversion' => 'Frist i versjon',
+'defaultdueinversion' => 'Standard utløp i versjon for nye oppgaver',
+'undecided' => 'Ubestemt',
+'percentcomplete' => 'Prosent ferdig',
+'details' => 'Innhold',
+'savedetails' => 'Lagre',
+'canceledit' => 'Avbryt',
+'anonymous' => 'Anonym Innsender',
+'complete' => 'Løst',
+'closedby' => 'Stengt av',
+'reasonforclosing' => 'Ã…rsak til stenging:',
+'reopenthistask' => 'Gjenåpne denne oppgaven',
+'comments' => 'Kommentarer',
+'attachments' => 'Vedlegg',
+'relatedtasks' => 'Relaterte oppgaver',
+'edit' => 'Endre',
+'addcomment' => 'Send inn kommentar',
+'fileuploadedby' => 'Filer lastet opp av',
+'uploadafile' => 'Legg ved en fil',
+'addalink' => 'Relater til en oppgave',
+'addanotherlink' => 'Legg til enda en relasjon',
+'uploadnow' => 'Last opp nå',
+'thesearerelated' => 'Disse oppgavene er relatert til denne oppgaven',
+'remove' => 'Slett',
+'addnewrelated' => 'Relater oppgave #',
+'add' => 'Legg til',
+'otherrelated' => 'Andre oppgaver som denne oppgaven er knyttet til',
+'receivenotify' => "Disse brukerne vil motta detaljvarsler når denne oppgaven endres.\n",
+'addusertolist' => 'Legg til bruker i denne listen',
+'addtolist' => 'Legg til i listen',
+'addmyself' => 'Legg meg til denne listen',
+'removemyself' => 'Fjern meg fra denne listen',
+'theseusersnotify' => "Disse brukerne vil motta detaljerte varsler når denne oppgaven endres.\n",
+'attachedtoproject' => 'Tiknyttet applikasjon',
+'reminders' => 'PÃ¥minnelser',
+'system' => 'System',
+'remindthisuser' => 'PÃ¥minn denne brukeren',
+'thisoften' => 'så ofte',
+'startafter' => 'Vent før du starter påminnelser',
+'hour' => 'time',
+'hours' => 'timer',
+'day' => 'dag',
+'days' => 'dager',
+'week' => 'uke',
+'weeks' => 'uker',
+'addreminder' => 'Legg til påminnelse',
+'defaultreminder' => 'Dette er en påminnelse om å se på følgende oppgave:',
+'message' => 'Beskjed',
+'closed' => 'Stengt',
+'filename' => 'Filnavn:',
+'date' => 'Dato',
+'filesize' => 'Filstørrelse:',
+'closurecomment' => 'Ekstra kommentarer om lukking:',
+'history' => 'Historikk',
+'nohistory' => 'Ingen historikk tilgjengelig.',
+'eventdate' => 'Dato',
+'user' => 'Bruker',
+'event' => 'Aktivitet',
+'fieldchanged' => 'Felt endret',
+'taskopened' => 'Oppgave åpnet',
+'taskreopened' => 'Oppgave gjenåpnet',
+'taskclosed' => 'Oppgave stengt',
+'commentadded' => 'Kommentar lagret',
+'commentedited' => 'Kommentar endret',
+'commentdeleted' => 'Kommentar slettet',
+'attachmentadded' => 'Vedlegg lagt til',
+'attachmentdeleted' => 'Vedlegg slettet',
+'taskedited' => 'Oppgaveinnhold endret',
+'notificationadded' => 'Bruker lagt til varslingsliste',
+'notificationdeleted' => 'Bruker fjernet fra varslingsliste',
+'relatedadded' => 'Relatert oppgave lagret',
+'relateddeleted' => 'Relatert oppgave slettet',
+'taskassigned' => 'Oppgave tildelt til',
+'taskreassigned' => 'Oppgave tildelt på nytt til',
+'assignmentremoved' => 'Tildeling fjernet',
+'summary' => 'Tittel',
+'addedasrelated' => 'Oppgave lagt til den relaterte listen',
+'deletedasrelated' => 'Oppgave fjernet fra den aktuelle listen',
+'reminderadded' => 'PÃ¥minnelse lagt til',
+'reminderdeleted' => 'PÃ¥minnelse fjernet',
+'priority' => 'Prioritet',
+'previousvalue' => 'Forrige verdi',
+'newvalue' => 'Ny verdi',
+'selectareason' => 'Velg årsak',
+'assigntome' => 'Tildel oppgaven til meg',
+'reopenrequest' => 'Be om gjenåpning',
+'requestclose' => 'Be om stenging',
+'ownershiptaken' => 'Bruker tok eierskap',
+'closerequestmade' => 'Forespurte oppgaven lukkes',
+'reopenrequestmade' => 'Bedt om oppgaven som skal gjenåpnes',
+'taskdependson' => 'Denne oppgaven er avhengig av',
+'taskblocks' => 'Denne oppgaven blokkerer disse fra stenging',
+'depadded' => 'Avhengighet lagt til',
+'depaddedother' => 'Denne oppgaven lagt til som avhengighet',
+'depremoved' => 'Avhengighet fjernet',
+'depremovedother' => 'Denne oppgaven fjernet fra annen oppgave avhengighet liste',
+'showdetailserror' => 'At oppgaven ikke finnes, eller du har ikke tillatelse til å vise det.',
+'makeprivate' => 'Gjør personlig',
+'makepublic' => 'offentliggjøre',
+'taskmadeprivate' => 'Oppgaven gjort personlig',
+'taskmadepublic' => 'Personvern fjernet - oppgave offentliggjort',
+'confirmdeletecomment' => 'Vil du slette kommentar?',
+'attachementswilldeleted' => 'Alle vedlegg vil også bli slettet!',
+'confirmdeleteattach' => 'Er du sikker på at du vil slette dette vedlegget?',
+'selectedhistory' => 'Viser valgte historie detaljer',
+'showallhistory' => 'Vis full historikk tab igjen',
+'hidethis' => 'Skjule dette området igjen',
+'mark100' => 'Marker oppgave som 100% fullført',
+'watchtask' => 'overvåk oppgave',
+'stopwatching' => 'Stopp overvåking',
+'commentlink' => 'Lenke til denne kommentaren',
+'submitreq' => 'Send forespørsel',
+'reasonforreq' => 'Årsak til forespørsel',
+'pmreqdenied' => 'Prosjektleder nektet forespørsel',
+'taskpendingreq' => "Prosjektleder handling venter. Se fanen Historikk for detaljer.\n",
+'previoustask' => 'Forrige oppgave',
+'nexttask' => 'Neste oppgave',
+'duedate' => 'Frist',
+'attachnoperms' => "Det er vedlegg med denne kommentarer, men du har ingen tillatelse til å vise dem.\n",
+'linknoperms' => "Det er linker med denne kommentarer, men du har ingen tillatelse til å vise dem.\n",
+'open' => 'Ã…pen',
+'depgraph' => 'Vis avhengigheter',
+'reset' => 'Tilbakestill',
+'selectusers' => 'Valgte brukere...',
+'addmetoassignees' => 'Legg meg også i listen "Tildelt til"',
+'addedtoassignees' => 'Bruker lagt til agent liste',
+'dependencygraph' => 'Avhengigheter',
+'attachanotherfile' => 'Legg ved en fil til',
+'OK' => 'OK',
+'addvote' => 'Legg inn stemme',
+'disable_lostpw' => 'Deaktiver tapte passord henting',
+'disable_changepw' => 'Deaktiver opprette / redigere passord',
+'notifyfromfs' => 'Varsel fra Endringsportal',
+'autogenerated' => 'Dette er en automatisk melding, vennligst ikke svar.',
+'forward' => 'Forover',
+'previous' => 'Forrige',
+'next' => 'Neste',
+'first' => 'Første',
+'last' => 'Siste',
+'page' => 'Side %d av %d',
+'search' => 'Søk',
+'alltasktypes' => 'Alle typer oppgaver',
+'allseverities' => 'Alle alvorlighetsgrader',
+'alldevelopers' => 'Alle utviklere',
+'notyetassigned' => 'Ennå ikke tildelt',
+'allcategories' => 'Alle kategorier',
+'allstatuses' => 'Alle statuser',
+'allopentasks' => 'Alle åpne oppgaver',
+'sortthiscolumn' => 'Sorter etter denne kolonnen',
+'id' => 'ID#',
+'project' => 'Applikasjon',
+'dateopened' => 'Ã…pnet',
+'progress' => 'Fremdrift',
+'searchthisproject' => 'Søk i applikasjon',
+'dueanyversion' => 'Ferdig i vilken versjon',
+'anyversion' => 'Rapportert i noen versjon',
+'dueversion' => 'Frist i versjon',
+'lastedit' => 'Siste endret',
+'os' => 'Operativsystem',
+'reportedin' => 'Rapportert I',
+'taskrange' => 'Viser oppgaver %d - %d av %d',
+'noresults' => 'Ingen funnet.',
+'takeaction' => 'Gjer tiltak',
+'watchtasks' => 'Overvåk valgte oppgaver',
+'stopwatchingtasks' => 'Ikke overvåk valgte oppgaver',
+'assigntaskstome' => 'Tildel valgte oppgaver til meg',
+'dueby' => 'Ferdig av ',
+'dueanytime' => 'Ferdig kortid ',
+'selectduedate' => 'Velg frist',
+'toggleselected' => 'Toggle Valgt',
+'due' => 'Frist',
+'assignedtome' => 'Tildelt til meg',
+'tasklist' => 'Oppgaveliste',
+'dateclosed' => 'Dato stengt',
+'advanced' => 'Avansert',
+'searchcomments' => 'Søk i kommentarer',
+'searchforall' => 'Søk etter alle ord',
+'anonusers' => 'Anonyme brukere',
+'miscellaneous' => 'Diverse',
+'users' => 'Brukere',
+'taskproperties' => 'Oppgaveegenskaper',
+'selectsincedate' => 'Velg Endret siden',
+'changedsince' => 'Endret siden',
+'updatefs' => 'Vennligst oppdater flyspray.',
+'currentversion' => 'Din nåværende versjon er',
+'latestversion' => 'og den siste versjonen er',
+'hidemessage' => '(Minn meg på det senere)',
+'saveas' => 'Lagre søk som',
+'nosearches' => 'Ingen lagrede søk',
+'saving' => 'Lagrer ...',
+'votes' => 'Stemmer',
+'tovote' => 'Stem',
+'allclosedtasks' => 'Alle stengte oppgaver',
+'password' => 'Passord',
+'login' => 'Logg inn',
+'rememberme' => 'Husk meg',
+'lostpassword' => 'Mistet passord?',
+'lostpwforfs' => 'Mistet passord for flyspray',
+'lostpwmsg1' => "Hei\n\nJeg har mistet passordet mitt for flyspray på _\n",
+'lostpwmsg2' => "Vennligst gi meg med et nytt passord.\n\nBrukernavn: _",
+'regards' => "\n\nHilsen,",
+'yourusername' => '_ ditt brukernavn _',
+'locale' => 'no-NO',
+'filenotexist' => "Filen ikke finnes, eller du har ikke tillatelse til å få tilgang til det.\n",
+'showtask' => 'Vis oppgave',
+'now' => 'NÃ¥',
+'go' => 'GÃ¥!',
+'opentaskanon' => 'Ny oppgave anonymt',
+'register' => 'Registrer',
+'addnewtask' => 'Ny oppgave',
+'reports' => 'Aktivitetslogg',
+'editmydetails' => 'Redigere mine opplysninger',
+'logout' => 'Logg ut',
+'disabledaccount' => "Din konto har blitt deaktivert! <br /> Umiddelbart logger deg ut ...\n",
+'poweredby' => 'Powered by flyspray',
+'sponsoredby' => ' ',
+'projects' => 'Applikasjoner',
+'allprojects' => 'Alle applikasjoner',
+'selectproject' => 'for applikasjon:',
+'tasksall' => 'Alle oppgaver',
+'tasksassigned' => 'Oppgaver tildelt meg',
+'tasksreported' => 'Oppgaver jeg har innmeldt',
+'taskswatched' => 'Oppgaver jeg overvåker',
+'mysearch' => 'Mine søk',
+'admintoolbox' => 'Admin',
+'manageproject' => 'Applikasjonsinnstillinger',
+'permissions' => 'Se Tillatelser',
+'hide' => 'Skjul',
+'pendingreq' => 'PM ber om venting',
+'errorpage' => "Flyspray kan ikke gi den siden du ba om.\nKanskje du har bedt om en oppgave som ikke finnes, eller du\nhar ikke tillatelse til å vise den siden du ønsket. <br /> <br />\nDu har kanskje prøvd å bruke en slem URL til å samhandle med databasen\nbackend bruker SQL-injeksjon. Hvis dette er sant, kan du gå til hjørnet og tror\nom dine handlinger. Når du kommer tilbake, må du ikke gjøre det igjen!",
+'permissionsforproject' => 'Tillatelser for ',
+'switchto' => 'Bytte til',
+'lastsearch' => 'Siste søk',
+'modify' => 'Endre',
+'noticefrom' => 'Varsel fra',
+'hasopened' => 'har åpnet en ny flyspray oppgave, og tildelt den til deg:',
+'moreinfonew' => 'Du kan finne mer informasjon om denne feilen på flyspray side:',
+'newtaskcategory' => 'En ny flyspray oppgave har blitt åpnet i denne kategorien',
+'categoryowner' => 'Du mottar dette fordi du er oppført som kategori eieren.',
+'tasksummary' => 'Oppgavesammendrag:',
+'newtaskadded' => 'Din nye oppgaven er lagt til.',
+'summaryanddetails' => 'Du må fylle inn både sammendraget og detaljene.',
+'goback' => 'GÃ¥ tilbake.',
+'messagefrom' => 'Dette er en melding fra flyspray bug tracking system på _',
+'hasjustmodified' => 'har nettopp endret følgende oppgave.',
+'changedfields' => 'Endrede felt er prefikset med stjerner (**)',
+'moreinfomodify' => "Du kan få mer informasjon om denne oppgaven på følgende nettadresse:\n",
+'nolongerassigned' => "Følgende oppgave er ikke lenger tildelt deg. Det er nå tildelt\n",
+'hasassigned' => 'har tildelt deg følgende flyspray oppgave:',
+'taskupdated' => 'Oppgaven er oppdatert',
+'tasksupdated' => 'Oppgaver har blitt oppdatert.',
+'hasclosedassigned' => "har stengt følgende flyspray oppgaven du ble tildelt:\n",
+'unassigned' => 'Ikke tildelt',
+'hasclosed' => 'har stengt følgende oppgave.',
+'youonnotify' => "Du mottar dette fordi du er på varslingslisten.\n",
+'taskclosedmsg' => 'Oppgaven har blitt stengt',
+'returntotask' => 'Tilbake til detaljer om oppgaven',
+'backtoindex' => 'GÃ¥ tilbake til oppgavelisten',
+'noclosereason' => 'Du har ikke valgt en grunn for å stenge denne oppgaven.',
+'hasreopened' => 'har gjenåpnet følgende Flyspray oppgave som du lukket:',
+'taskreopenedmsg' => 'Oppgaven har blitt gjenåpnet.',
+'backtotask' => 'GÃ¥ tilbake til oppgaven.',
+'commentaddedmsg' => 'Kommentar har blitt lagt til.',
+'commenttoassigned' => 'har lagt til en kommentar til en oppgave du har fått tildelt:',
+'commenttotask' => 'har lagt følgende kommentar til denne oppgaven.',
+'nocommententered' => "Du bør legge inn en kommentar før du klikker på send-knappen.\n",
+'fillinfields' => 'Du gjorde ikke fylle inn alle feltene.',
+'notcurrentpass' => 'Det er ikke ditt riktige passord!',
+'passchanged' => 'Ditt passord er endret.',
+'closewindow' => 'NÃ¥ kan du lukke dette vinduet.',
+'passnomatch' => 'Dine nye passord var ikke det samme!',
+'usernametaken' => "Dette brukernavnet er allerede i bruk. Du må velge en annen.\n",
+'usernametakenbulk' => 'Brukernavn er allerede tatt',
+'newusercreated' => 'Ny brukerkonto er opprettet',
+'accountcreated' => 'Din brukerkonto er opprettet',
+'newuserwarning' => "Legg merke til at de globale preferanser kan kreve kontoen din for å bli godkjent av en administrator. Hvis du ikke kan logge inn, dette er nok derfor.\n",
+'nomatchpass' => 'Passordene er ulike.',
+'confirmwrong' => 'Bekreftelseskoden er feil!',
+'formnotcomplete' => 'Skjemaet ble ikke ferdig utfylt.',
+'formnotnumeric' => 'Satt inn data var ikke numerisk!',
+'groupnametaken' => 'De gruppenavnet er allerede tatt.',
+'newgroupadded' => 'Ny gruppe opprettet.',
+'optionssaved' => 'Endringer lagret.',
+'hasuploaded' => "har lastet opp en fil, vedlegg til en oppgave som har blitt tildelt til:\n",
+'hasattached' => 'har festet en fil til følgende oppgave.',
+'fileuploaded' => 'Filen er lastet opp.',
+'fileerror' => "Det oppstod en feil ved opplasting av filen. Kanskje tillatelsene på <i> vedlegg / </ i> katalogen er feil.\n",
+'contactadmin' => 'Kontakt ansvarlig for denne applikasjonen',
+'selectfileerror' => 'Du har ikke valgt en fil.',
+'userupdated' => 'Bruker detaljer har blitt oppdatert',
+'realandemail' => "Du fylte ikke inn både virkelig navn og e-postadresse feltene.\n",
+'groupupdated' => 'Gruppe definisjon oppdatert.',
+'groupanddesc' => 'Du fylte ikke inn gruppenavnet.',
+'fillallfields' => 'Vennligst fyll ut alle feltene.',
+'listPmustN' => '"Order" må være numerisk.',
+'listupdated' => 'Listen er oppdatert.',
+'listitemadded' => 'Ny liste element lagt.',
+'relatedaddedmsg' => 'Relatert oppgave lagt til listen.',
+'relatederror' => 'At oppgaven er allerede på dette relatert oppgaveliste.',
+'relatedremoved' => 'Relatert oppgave fjernet fra listen.',
+'notifyadded' => 'Bruker lagt til varslingsliste.',
+'notifyerror' => 'At brukeren er allerede på varslingsliste for denne oppgaven.',
+'notifyremoved' => 'Bruker fjernet fra varslingsliste.',
+'editcommentsaved' => 'Oppdatert kommentar er lagret.',
+'commentdeletedmsg' => 'Kommentar er slettet.',
+'gotonewtask' => 'GÃ¥ til oppgaven du opprettet',
+'projectcreated' => "Din nye prosjektet er opprettet. Du kan nå tilpasse prosjektet i prosjektledelse området.\n",
+'customiseproject' => 'Tilpass dette prosjektet',
+'projectupdated' => 'Preferanser prosjekt oppdatert',
+'emptytitle' => 'Du forlot Prosjekttittel-feltet stå tomt. Gå tilbake og rette den.',
+'loginbelow' => 'Du kan nå prøve å logge inn!',
+'attachmentdeletedmsg' => 'Vedlegget er slettet',
+'reminderaddedmsg' => 'Din påminnelse er lagt til.',
+'reminderdeletedmsg' => 'Den valgte påminnelse har blitt slettet.',
+'flyspraytask' => 'Flyspray oppgave',
+'fieldsmissing' => 'Noen felt inneholdt ingen eller ugyldige data.',
+'relatedinvalid' => 'Oppgaven finnes ikke.',
+'relatedproject' => "Denne oppgaven er festet til et annet prosjekt. Legg til forhold likevel?\n",
+'addanyway' => 'Legg til likevel',
+'cancel' => 'Avbryt',
+'alreadyedited' => "Denne oppgaven ble redigert av noen andre før du lagret. Har du fortsatt ønsker å lagre endringene?\n",
+'saveanyway' => 'Lagre endringene mine uansett',
+'nouserselected' => "Ingen bruker valgt. Velg minst en bruker før du prøver igjen.\n",
+'groupswitchupdated' => 'Brukergrupper endret vellykket.',
+'takenownershipmsg' => 'Denne oppgaven er nå tildelt deg.',
+'adminrequestmade' => 'Din forespørsel er sendt til Applikasjonsansvarlig',
+'newdepnotify' => 'Ny avhengighet er lagt inn for følgende oppgave:',
+'dependadded' => 'Oppgaveavhengighet er lagt til',
+'dependaddfailed' => "Det oppsto en feil når du legger til avhengighet. Kontroller at oppgaven finnes, og det er ingen gjensidig blokken.\n",
+'depremovedmsg' => 'Oppgaveavhengighet er fjernet',
+'newdepis' => 'Den nye avhengighet er',
+'magicurlsent' => "En melding har blitt sendt til din varsling adresse. Den inneholder en link som tar deg til en side for å fullføre denne oppgaven.\n",
+'changefspass' => 'Endre flyspray passord',
+'magicurlmessage' => "Følg linken nedenfor for å endre flyspray passord:\n",
+'erroronform' => 'Det var et problem med innsending av skjemaet',
+'addressused' => "Denne adressen har blitt brukt til å registrere et flyspray konto. Hvis du ikke var ventet denne meldingen, kan ignorere og slette den. Gå til følgende URL for å fullføre registreringen:\n",
+'confirmcodeis' => 'Din bekreftelseskode er:',
+'codesent' => "Din bekreftelseskode har blitt sendt. Følg instruksjonene i meldingen.\n",
+'codenotsent' => 'Kan ikke sende koden din, vennligst prøv igjen senere.',
+'taskmadeprivatemsg' => 'Denne oppgaven er gjort personlig',
+'taskmadepublicmsg' => 'Denne oppgaven har blitt gjort offentlig igjen',
+'realandnotify' => "Du må fylle ut ditt virklige Name-feltet, og enten e-postadresse eller Jabber ID-feltet.\n",
+'pmreqdeniedmsg' => 'Forespørsel Prosjektleder nektet',
+'massopsuccess' => 'Mass operasjoner vellykkede - hvor tillatelser tillatt',
+'usernotexist' => 'Dennebrukeren finnes ikke på denne flyspray installasjon',
+'commentattachperms' => "Du kan ikke slette denne kommentaren - ingen tillatelse til å slette vedlegg\n",
+'voterecorded' => 'Din stemme er registrert',
+'votefailed' => 'Din stemme kan ikke registreres',
+'createnewgroup' => 'Opprett ny gruppe',
+'requiredfields' => 'Obligatoriske felt er merket med en',
+'addthisgroup' => 'Legg til denne gruppen',
+'createnewproject' => 'Opprett ny applikasjon',
+'addnewproject' => 'Opprett ny Applikasjon',
+'htmlallowed' => 'HTML-kode er tillatt',
+'createthisproject' => 'Opprett applikasjon',
+'inlineimages' => 'Vis bilde vedlegg inline',
+'createnewtask' => 'Opprett en ny oppgave i prosjektet:',
+'addanother' => 'Legg til en annen oppgave etter dette',
+'addthistask' => 'Opprett oppgaven',
+'notifyme' => 'Varsle meg ved endringer av denne oppgaven',
+'newtask' => 'Ny oppgave',
+'attachafile' => 'Legg til en fil',
+'registernewuser' => 'Registrer ny bruker',
+'none' => 'Ingen',
+'registeraccount' => 'Registrer denne kontoen',
+'registerbulkaccount' => 'Registrere kontoer',
+'both' => 'Begge',
+'notifyfrom' => 'Melding fra _',
+'donotreply' => 'Dette er en automatisk melding, vennligst ikke svar.',
+'disclaimer' => "Du mottar denne meldingen fordi du har bedt om det fra flyspray bugtracking system. Hvis du ikke forvente denne meldingen eller ikke ønsker å motta post i fremtiden, kan du endre varslingsinnstillingene på nettadressen vist ovenfor.\n",
+'userwho' => 'Brukeren som gjorde dette',
+'moreinfo' => 'Mer informasjon finner du på følgende nettadresse:',
+'newtaskopened' => 'En ny flyspray oppgave er blitt åpnet. Detaljer er nedenfor.',
+'notify.taskclosed' => 'Følgende oppgave er nå stengt:',
+'notify.taskreopened' => 'Følgende oppgave har blitt gjenåpnet:',
+'newdep' => 'Følgende oppgave har en ny avhengighet:',
+'notify.depremoved' => 'Følgende oppgave har hatt en avhengighet fjernet:',
+'olddepwas' => 'Den gamle avhengighet var',
+'notify.commentadded' => 'Følgende oppgave har en ny kommentar til:',
+'commentis' => 'Kommentar tekst er nedenfor.',
+'newattachment' => 'En ny fil er knyttet til følgende oppgave:',
+'detailsbelow' => 'Detaljene er under.',
+'notify.relatedadded' => 'En ny beslektet oppgave er lagt til følgende oppgave:',
+'relatedis' => 'Den relatert oppgave er',
+'assignedtoyou' => 'Du er tildelt følgende oppgave',
+'takenownership' => 'har tatt eierskap for følgende oppgave:',
+'requiresaction' => 'Følgende oppgave krever handling av en prosjektleder:',
+'requiresactionnotify' => 'Oppgaven krever handling av en prosjektleder',
+'pmdeny' => "En prosjektleder har avslått forespørselen påvente for følgende oppgave:\n",
+'pmdenynotify' => 'En prosjektleder har avslått forespørselen',
+'fileaddedtoo' => 'En eller flere filer har blitt festet.',
+'taskwatching' => 'Du overvåker følgende oppgaver',
+'isdepfor' => 'er en ny avhengighet for',
+'denialreason' => 'Grunn for avvisning',
+'taskchanged' => "Følgende oppgave er endret. Endringene er listet opp nedenfor. For full informasjon om hva som er endret, kan du gå til URL og klikk på fanen History.\n",
+'useraddedtoassignees' => "En bruker har lagt seg selv til i listen over brukere som er tildelt denne oppgaven.\n",
+'removeddepis' => 'Den fjernet avhengigheten er',
+'isnodepfor' => 'er ingen avhengighet lenger for',
+'usergroups' => 'Brukergrupper',
+'pmtoolbox' => 'Applikasjonsansvarligs verktøykasse',
+'groupmanage' => 'Gruppeinnstillinger',
+'pendingrequests' => 'Avventende forespørsler',
+'reasongiven' => 'Grunn',
+'nopendingreq' => 'Det er ingen avventende forespørsler fra Applikasjonsansvarlig',
+'givereason' => 'Gi en grunn',
+'catlisted' => 'Kategorier',
+'oslisted' => 'Operativsystemer',
+'verlisted' => 'Versjoner',
+'tasktypeed' => 'Oppgavetyper',
+'resed' => 'Løsningstyper',
+'deny' => 'Avvis',
+'notifiedwhen' => 'Varslet når',
+'onlynewtasks' => 'Nye oppgaver er åpnet',
+'allevents' => 'Enhver hendelse skjer i enhver oppgave',
+'feeds' => 'Feeds',
+'feeddescription' => 'Feed beskrivelse',
+'feedimgurl' => 'Feed bilde URL (la stå tom for ingen bilde)',
+'notifysubject' => 'Varslingsnavn',
+'notifysubjectinfo' => '(%p = prosjekttittel, %s = oppgave Oppsummert, %t = oppgave id, %a = action, %u = user)',
+'priority6' => 'Veldig høy',
+'priority5' => 'Høy',
+'priority4' => 'Medium',
+'priority3' => 'Lav',
+'priority2' => 'Veldig lav',
+'priority1' => 'Utsatt',
+'sendcode' => 'Send kode!',
+'entercode' => "Skriv inn bekreftelseskoden du mottok på varselmelding. Skriv også ønsket konto passord.\n",
+'confirmationcode' => 'Bekreftelseskode',
+'registererror' => "Sørg for at du har fylt ut alle obligatoriske felter, og at du har lagt inn riktige opplysninger for ønsket varslingstype.\n",
+'validusername' => '(bare alfanumeriske tegn og -. _ er tillatt)',
+'validemail' => '(bruk ; for å skille flere epost-adresser)',
+'emailtakenbulk' => 'Epostadresse er allerede i bruk',
+'emailtaken' => "Den e-postadressen eller Jabber-ID er allerede tatt. Du må velge en annen.\n",
+'note' => '<strong>NB:</strong> Du vil motta en bekreftelseskode før kontoen blir opprettet. Koden sendes dit du valgte som varslingsmåte ovenfor.<br />Hvis du legger inne feil informasjon vil du <strong>ikke</strong> motta bekreftelseskoden.',
+'changelog' => 'Endringslogg',
+'changeloggen' => 'Changelog Generator',
+'listfrom' => 'Liste changelog-oppføringer fra',
+'to' => 'til',
+'oldestfirst' => 'Eldste først',
+'recentfirst' => 'Nyeste først',
+'severityrep' => 'PÃ¥virkningsrapport',
+'totalopen' => 'Totalt åpne oppgaver',
+'age' => 'Alder',
+'agerep' => 'Alder Rapport',
+'eventsrep' => 'Aktivitetsrapport',
+'events' => 'Aktiviteter',
+'Tasks' => 'Oppgaver',
+'opened' => 'Ã…pnet',
+'edited' => 'Endret',
+'assigned' => 'Tildelt',
+'within' => 'Innen',
+'pastday' => 'Den siste dagen',
+'pastweek' => 'Den siste uken',
+'pastmonth' => 'Den siste måneden',
+'pastyear' => 'Det siste året',
+'nolimit' => 'Ingen grense',
+'from' => 'Fra',
+'duein' => 'Frist innen',
+'selectfromdate' => 'Velg fra dato',
+'selecttodate' => 'Velg til dato',
+'showvoters' => 'Vis/skjul stemmere',
+'roadmap' => 'Veikart',
+'roadmapfor' => 'Veikart for versjon',
+'tasks' => 'oppgaver',
+'completed' => 'fullført.',
+'opentasks' => 'Ã¥pne oppgaver',
+'of' => '% av',
+'severity5' => 'Kritisk',
+'severity4' => 'Høy',
+'severity3' => 'Medium',
+'severity2' => 'Lav',
+'severity1' => 'Veldig lav',
+'Redirect' => 'Omdirigere',
+'redirectmsg' => "Hvis nettleseren ikke støtter meta omdirigering vennligst klikk %sHERE%s for å bli omdirigert\n",
+'allowclosedcomments' => 'Tillat kommentarer til lukkede oppgaver',
+'comment' => 'Kommentar',
+'editowncomments' => 'Endre egne kommentarer',
+'reopened' => 'Gjenåpnet',
+'loading' => 'Laster...',
+'notifyown' => 'Varsel for egne endringer',
+'youremail' => 'Din epostadresse',
+'thankyouforbug' => "Takk for rapportering av problemet. Du kan se oppgaven og observere dens fremgang som helst ved hjelp av følgende URL:\n",
+'anonuser' => 'Anonym bruker',
+'conflict' => 'Konflikt',
+'file' => 'Fil',
+'KiB' => 'Kb',
+'MiB' => 'Mb',
+'size' => 'Størrelse',
+'projectgroup' => 'Applikasjonsgruppe',
+'profile' => 'Profil:',
+'viewprofile' => 'Vis profil',
+'regdate' => 'Registrert siden',
+'tasksopened' => 'Oppgave åpnet',
+'replyto' => 'Svar til',
+'notifytypes' => 'Varslingstyper',
+'pm.taskchanged' => 'Oppgave endret',
+'pm.taskreopened' => 'Oppgave gjenåpnet',
+'pm.depadded' => 'Avhengighet lagt til',
+'pm.depremoved' => 'Avhengighet fjernet',
+'pmrequest' => 'PM forespørsel',
+'pmrequestdenied' => 'PM forespørsel avvist',
+'newassignee' => 'New oppdragstakeren',
+'revdepadded' => 'Omvendt avhengighet lagt',
+'revdepaddedremoved' => 'Omvendt avhengighet fjernet',
+'assigneeadded' => 'Oppdragstakeren lagt',
+'addusergroup' => 'Legg til bruker i denne gruppen',
+'groupmembers' => 'Gruppemedlemmer',
+'deleteuser' => 'Slett denne brukeren',
+'userdeleted' => 'Bruker slettet',
+'autoassign' => 'Autotildel sak til kategorieier',
+'ssl' => 'SSL',
+'updatewrong' => "Du har oppdateringen kontrollfunksjonen er aktivert, men en feil oppstod under forsøk\nå kontakte oppdateringsserveren, enten verten tillater ikke utgående tilkoblinger\neller feilen ble forårsaket av et nettverksproblem.\nVennligst besøk flyspray nettsted for å sikre at du kjører den nyeste versjonen.\n",
+'deleteproject' => 'Slett dette prosjektet og flytte innholdet til',
+'projectdeleted' => 'Applikasjonen er slettet',
+'feedforall' => 'Feed for alle prosjekter',
+'usercreated' => 'Bruker opprettet',
+'created' => 'Opprettet',
+'deleted' => 'Slettet',
+'userid' => 'Bruker ID',
+'editassignments' => 'Rediger oppdrag',
+'preview' => 'Forhåndsvisning',
+'anyprogress' => 'Enhver Progress',
+'tasksrelated' => 'Oppgaver relatert til denne oppgaven',
+'duplicatetasks' => 'Kopier av denne oppgaven',
+'databasemodfailed' => "Database modifisering mislyktes. Mulige årsaker er tilstrekkelige tillatelser.\n",
+'frequency' => 'Frekvens',
+'newuserregistered' => "En ny bruker har registrert på din flyspray installasjon. Bruker detaljene er som følger:\n\n",
+'newuserregisterednotify' => 'En ny bruker er registrert',
+'notify_registration' => 'Varsle administratorer om ny bruker',
+'textversion' => 'Vis i tekstformat',
+'onlyprimary' => 'Oppgaver ikke blokkere andre oppgaver',
+'switch' => 'Switch',
+'max' => 'maks.',
+'dates' => 'Datoer',
+'selectduedatefrom' => 'Frist fra',
+'selectduedateto' => 'til',
+'selectsincedatefrom' => 'Endret fra',
+'selectsincedateto' => 'til',
+'selectdate' => 'Velg dato',
+'selectopenedfrom' => 'Ã…pnet fra',
+'selectopenedto' => 'til',
+'selectclosedfrom' => 'Stengt fra',
+'selectclosedto' => 'til',
+'startat' => 'Start ved',
+'hasattachment' => 'Har vedlegg',
+'private' => 'Personlig',
+'watching' => 'Overvåking',
+'alreadyvotedthistask' => 'du stemte for denne oppgaven',
+'alreadyvotedthisday' => 'har stemt i dag',
+'visibility' => 'Sikt',
+'public' => 'Offentlig',
+'leaveemptyauto' => "La passordfeltene tomt hvis du vil at passordet skal genereres automatisk.\n",
+'novalidemail' => 'Du har ikke skrevet inn en gyldig e-postadresse.',
+'novalidjabber' => 'Du har ikke skrevet inn en gyldig Jabber adresse.',
+'missingrequired' => 'Du gjorde ikke fylle ut alle obligatoriske felter.',
+'entervalidusername' => 'Vennligst skriv inn et gyldig brukernavn og virkelige navn.',
+'couldnotaddusernotif' => 'Kunne ikke legge brukeren til varslingsliste.',
+'defaulttask' => 'Standard oppgavebeskrivelse',
+'all' => 'alle',
+'events.useraddedtoassignees'=> 'Bruker lagt til agenter ',
+'eventlog' => 'Aktivitetslogg',
+'assignmentchanged' => 'Oppdraget endret',
+'detailedinfo' => 'Detaljert informasjon',
+'All' => 'Alle',
+'tasksireported' => 'Oppgaver jeg har innmeldt',
+'recentlyopened' => 'Ã…pnet nylig',
+'stats' => 'Status',
+'totaltasks' => 'oppgaver totalt',
+'mostwanted' => 'Mest ettersøkte oppgaver',
+'defaultentry' => 'Standard oppføring side',
+'toplevel' => 'Toppnivå visning',
+'overview' => 'Oversikt',
+'error#' => 'Feil #',
+'error1' => "Du har ikke tilstrekkelige tillatelser til å vise dette vedlegget.\n",
+'error3' => 'Gjentok handlingen, omdirigere til hovedsiden.',
+'error4' => 'Du har ikke adminrettigheter.',
+'error5' => 'At brukeren finnes ikke på denne flyspray installasjon.',
+'error6' => 'Ugyldig adminområde.',
+'error7' => 'Feil passord!',
+'error71' => "Kontoen er låst for %d minutter på grunn av for mange mislykkede innloggingsforsøk!\n",
+'error8' => 'Du skrev ikke inn brukernavn og passord.',
+'error9' => 'Oppgaven finnes ikke eller ingen rettigheter til å vise denne oppgaven.',
+'error10' => 'Denne oppgaven finnes ikke.',
+'error101' => 'Du har ingen tillatelse til å vise denne oppgaven.',
+'error102' => "Du har ingen tillatelse til å vise denne oppgaven, logge inn kan hjelpe.\n",
+'error11' => 'Ingen tilgang til å endre denne kommentaren',
+'error12' => "Ingen gyldig magiske nøkkelen! Er du sikker på at du fikk den frå meldingen?\n",
+'error13' => 'Anonyme brukere har ikkje en profil.',
+'error14' => 'Du har ikkje tilstrekkelige tillatelser til å opprette en ny gruppe.',
+'error15' => 'Du har ikkje tilstrekkelige tillatelser til å åpne en oppgave.',
+'error16' => 'Du er ikke en prosjektleder.',
+'error17' => 'Ugyldig PM området.',
+'error18' => 'Ugyldig magi URL.',
+'error19' => 'Den brukeren finnes ikke på denne flyspray installasjon.',
+'error20' => 'Ugyldig database modifikasjon.',
+'error21' => 'En eller flere e-poster ble ikke sendt. Sjekk din config.',
+'error22' => 'Ingen nye brukerregistrering tillatt.',
+'error23' => 'Bruker eller gruppe ikke aktivert for innlogging.',
+'error24' => "Verken dot kjørbar eller ein offentlig dot-server er blitt satt.\n",
+'error25' => 'Veikart bare tilgjengelig for et bestemt prosjekt.',
+'error26' => 'Uegnet OAuth leverandør.',
+'error27' => 'Kan ikkje logge inn. Gje oss tillatelse til å vise e-postadressen din.',
+'error28' => 'Du har ingen tilgang til dette området.',
+'done' => 'gjort',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Prosjektet kunne ikke bli sletta.',
+'GMT' => 'GMT',
+'timezone' => 'Tidssone',
+'accept' => 'Aksepter',
+'reasonfordeinal' => 'Grunnen til fornektelse',
+'pruneclosedlinks' => 'Fjern Stengte relasjoner',
+'pruneclosedtasks' => 'Fjern Stengte oppgaver',
+'pagegenerated' => 'Side og bilde generert i %d sekunder.',
+'pruninglevel' => 'Reduksjonsnivå',
+'lastuser' => 'Den siste brukeren kan ikkje slettes.',
+'allprivate' => 'Alle applikasjon er personlige',
+'deletegroup' => 'Slett denne gruppen og flytte brukere til',
+'parent' => 'Overordnet oppgave',
+'ordertip' => 'Rekkefølgen disse elementene vil vises i listen',
+'showtip' => 'Vis element i listen',
+'deletetip' => 'Slett dette elementet fra listen',
+'del' => 'del',
+'request1' => 'En oppgave nedleggelse har blitt forespurt.',
+'request2' => 'En forespørsel om å re-åpne oppgaven har blitt gjort.',
+'allpriorities' => 'Alle prioriteter',
+'noroadmap' => 'Veikart eksisterer ikke (den finnes ingen "fremtidsversjoner" for denne applikasjonen).',
+'expand' => 'Utvid',
+'collapse' => 'Kollaps',
+'expandall' => 'Utvid alle',
+'collapseall' => 'Skjul alle',
+'minpwsize' => 'Minimum passord størrelse er 5 tegn',
+'passwordtoosmall' => 'Passordet er for kort.',
+'accountwaslocked' => "Kontoen hadde blitt låst på grunn av for mange mislykkede innloggingsforsøk.\n",
+'failedattempts' => 'Det var %d mislykkede innloggingsforsøk.',
+'groupnotexist' => 'Valgte gruppen finnes ikke i dette prosjektet.',
+'searchindetails' => 'Søke detaljer',
+'showasassignees' => 'Vis som agenter',
+'find' => 'Finn',
+'tls' => 'TLS',
+'isadmin' => 'er admin',
+'addvotes' => 'Legg til stemmer',
+'removevote' => 'Fjern stemme',
+'voteremoved' => 'Din stemme er fjernet',
+'voteremovefailed' => 'Din stemme kan ikke fjernes.',
+'connectedtasks' => 'Tilknyttede oppgaver:',
+'taskdependencies' => 'Oppgaveavhengigheter',
+'viewgraph' => 'Vis graf',
+'notaskdependencies' => 'Denne oppgaven er ikke avhengig av andre oppgaver',
+'dependson' => 'Er avhengig av',
+'blocks' => 'Blokkerer',
+'newdependency' => 'Ny avhengighet:',
+'nouserstoadd' => "Ingen brukere å legge til. Vennligst sikre navn, brukernavn og e-post er definert for hver bruker.\n",
+'dispintro' => 'Vise hoved innledende melding',
+'mainmessage' => 'Hoved innledende melding',
+'setsupertask' => 'Still Super-Task ID:',
+'supertaskmodified' => 'Super-Task ID er endret',
+'set' => 'Sett',
+'supertask' => 'Super-Task',
+'setparent' => 'Legg inn ID# for overordnet oppgave',
+'selfsupertasknotallowed' => 'Super-Task ID kan ikke være det samme som selv Oppgåve ID',
+'quickaction' => 'Hurtigvalg',
+'updateselectedtasks' => 'Oppdater valgte oppgaver',
+'notspecified' => 'Ikke spesifisert',
+'editselectedtasks' => 'Endre valgte oppgaver',
+'information' => 'Informasjon',
+'taskclosedisabled' => "Lukk Task er deaktivert som følgende avhengige oppgaver er fortsatt åpen: -\n",
+'daysleft' => 'dager igjen',
+'dayoverdue' => 'dager over fristen',
+'duetoday' => 'Frist i dag.',
+'daysbeforealert' => 'Dager før varsel',
+'associatedsubtask' => 'Tilknyttet deloppgave #',
+'associatesubtask' => 'Knytt deloppgave ID# med denne oppgaven',
+'subtaskid' => 'Deloppgave ID#',
+'subtaskalreadyhasparent' => "Sub oppgaven du skrev allerede har en overordnet oppgave, må du fjerne dette før assosiere.\n",
+'subtaskisparent' => "Sub oppgaven du skrev inn er den overordnede oppgaven med denne oppgaven. Sub oppgave ikke forbundet.\n",
+'subtasknotexist' => 'Sub oppgaven du har angitt finnes ikke.',
+'subtaskremovedmsg' => 'Under oppgave har fulført fjernet',
+'subtaskadded' => 'Sub oppgave lagt',
+'subtaskremoved' => 'Sub oppgave fjernet',
+'addnewsubtask' => 'Opprett ny deloppgave',
+'hidesubtasks' => 'Skjul deloppgaver',
+'voteforthistask' => 'Stem på denne oppgaven',
+'watchthistask' => 'Se denne oppgaven',
+'privatethistask' => 'Gjør denne oppgaven personlig',
+'adddependenttask' => 'Knytt avhengig oppgave',
+'associatetaskid' => 'Sub oppgave id',
+'parenttaskid' => 'Overordnet ID#',
+'invalidsupertaskid' => 'Overordnede oppgave id er ikke gyldig.',
+'supertaskadded' => 'Supertask lagt',
+'supertaskremoved' => 'Supertask fjernet',
+'effort' => 'Innsats',
+'efforttracking' => 'Innsatsovervåking',
+'useeffort' => 'Prosjektet benytter innsats sporing',
+'estimatedeffort' => 'Beregnet innsats',
+'totalestimatedeffort' => 'Totalt beregnet innsats',
+'currenteffortdone' => 'Innsats utført hittil',
+'starteffort' => 'Start overvåking',
+'endeffort' => 'Stopp overvåking',
+'cleareffort' => 'Fjern overvåking',
+'addeffort' => 'Legg til innsats',
+'manualeffort' => 'Manuelt Legg til innsats (H: M)',
+'efforttrackingstarted' => 'Innsats Sporing Startet for denne oppgaven.',
+'efforttrackingnotstarted'=> "Kan ikke begynne å spore på en sak som allerede spores\n",
+'efforttrackingstopped' => 'Innsats Sporing Endte for denne oppgaven.',
+'efforttrackingcancelled' => 'Innsats Sporing Avlyst for denne oppgaven.',
+'efforttrackingadded' => 'Manuell innsats registrert for oppgaven.',
+'trackinginprogress' => 'Sporing i Progress',
+'viewestimatedeffort' => 'Kan sjå innsats sporing.',
+'viewcurrenteffortdone' => 'Kan sjå nåværende innsats gjort',
+'trackeffort' => 'Kan spore innsats',
+'invalideffort' => 'Innsatsen er ugyldig. Må være et tall',
+'showpass' => 'Vis passord',
+'chooseafile' => 'Velg en fil!',
+'incorrectfiletype' => 'Feil filtype. Tillatt: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Ingen passord for å be. Du registrert gjennom %s',
+'addmultipletasks' => 'Importer oppgaveliste',
+'pendingnewuserrequest' => 'Avventende nye innmeldte forespørsler',
+'adminrequestswaiting' => 'Adminforespørsler venter',
+'clicktoedit' => 'Klikk på hvert felt for å raskt redigere',
+'confirmedit' => 'OK',
+'regapprovedbyadmin' => 'Registrering godkjent av admin (deaktiver bekreftelseskode)',
+'activity' => 'Aktivitet',
+'myactivity' => 'Min aktivitet',
+'emailverificationwrong' => 'Bekreftelsen stemmer ikke overens med den gitte e-postadresse',
+'verifyemailaddress' => 'Bekreft epost',
+'hideemails' => 'Skjul brukeres e-postadresser',
+'hidemyemail' => 'Skjule min e-postadresse',
+'exporttasklist' => 'Eksporter liste',
+'onedecimal' => 'ein desimal',
+'manday' => 'man-dag',
+'mandays' => 'man-dager',
+'mandayabbrev' => 'dag',
+'hourspermanday' => 'Timer per ein day (HH: mm)',
+'itemexists' => 'Sak %s finnes allerede i databasen.',
+'categoryitemexists' => 'Sak %s eksisterer allerede i kategorien %s i databasen.',
+'pageswelcomemsg' => 'Sider for å vise hoved intro melding',
+'pagesintromsg' => 'Sider for å vise intro melding',
+'activeoauths' => 'Aktive OAuth tilbydere',
+'onlyoauthreg' => 'Bare tillate Oauths registreringer',
+'estimatedeffortformat' => 'Estimert innsats visningsformat',
+'currenteffortdoneformat' => 'Nåværende innsats gjort visningsformat',
+'minute' => 'minutt',
+'minutes' => 'minutter',
+'minuteplural' => 'minutter',
+'minutesingular' => 'minutt',
+'minuteabbrev' => 'min',
+'hourplural' => 'timer',
+'hoursingular' => 'time',
+'hourabbrev' => 't',
+'estimatedeffortopen' => 'Estimert innsats for åpne oppgaver',
+'currenteffortdoneopen' => 'Nåværende innsats brukt i åpne oppgaver',
+'signinwith' => 'Logg på med %s',
+'canviewroadmap' => 'Kan se veikart',
+'enableavatars' => 'Aktiver avatarer',
+'maxavatarsize' => 'Maksimal avatarstørrelse i piksler',
+'taskhassubtask' => 'Denne oppgaven har følgende sub-oppgave',
+'taskhassubtasks' => 'Denne oppgaven har følgende deloppgaver',
+'translations' => 'Oversettelser',
+'translate' => 'Oversett',
+'taskdescription' => 'Oppgavebeskrivelse',
+'notaskdescription' => 'ingen oppgavebeskrivelse',
+'pleaseselect' => 'Velg',
+'closeselectedtasks' => 'Steng valgte oppgaver',
+'closetasks' => 'Steng oppgaver',
+'hintforbulkimport' => "<b>Tips for bulk import:</b>\n<ol>\n<li>Kopier og lim inn fra et excel-ark eller CSV ved å lime den inn som en hel kolonne.</li>\n<li>Du kan bare lime inn tittel og detaljer.</li>\n</ol>",
+'taskissubtaskof' => 'Denne oppgaven er en under oppgave',
+'applyfirstline' => 'På før første linje',
+'addmorerows' => 'Legg til flere rader',
+'addtasks' => 'Legg til oppgaver',
+'massopsdisabled' => "Beklager, masseredigering for øyeblikket deaktivert for flyspray 1.0. Vi planlegger å fullføre gjennomføringen for en senere utgave av flyspray. Du kan aktivere dem i kildekoden på nytt på egen risiko, men leste kommentarene der før du gjør det.\n",
+'viewroadmap' => 'Kan se veikart',
+'nosuicide' => "Kjære bruker, ikke mitt program ikke tillate deg å ødelegge din tilgang til flyspray ved å deaktivere din egen konto eller bytter din egen gruppe. Den empatiske bror av HAL9000\n",
+'movingtodifferentproject'=> "Flytte en oppgave som enten har en forelder eller deloppgaver til et annet prosjekt er ikke tillatt. Du må bryte forbindelsen mellom dem først.\n",
+'musthavesameproject' => 'Forelder og deloppgave må tilhøre samme prosjekt.',
+'defaultorderby' => 'Ordne tasklisten som standard av',
+'viewowntasks' => 'Vis mine oppgaver',
+'viewgroupstasks' => 'Vis gruppens oppgaver',
+'urlrewriting' => 'Url omskriving',
+'enablehtaccess' => "Vennligst aktiver .htaccess fil i flyspray root før du slår url omskriving på\n",
+'nomodrewrite' => "Mod omskriving ser ikke ut til å være tilgjengelig på denne serveren, beklager, men jeg kan ikke slå url omskriving på\n",
+'on' => 'PÃ¥',
+'off' => 'Av',
+'defaultorderbydirection' => 'Standard rekkefølge av retning',
+'ascending' => 'Stigende',
+'descending' => 'Synkende',
+'myassignedtasks' => 'Mine tildelte saker',
+'commentedon' => 'kommenterte',
+);
+
+?>
diff --git a/lang/pl.php b/lang/pl.php
new file mode 100644
index 0000000..cb8fc91
--- /dev/null
+++ b/lang/pl.php
@@ -0,0 +1,926 @@
+<?php
+
+$translation = array(
+'edituser' => 'Edycja danych użytkownika',
+'username' => 'Nazwa',
+'realname' => 'ImiÄ™ i nazwisko',
+'emailaddress' => 'Adres e-mail',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Rodzaj powiadomienia',
+'group' => 'Grupa',
+'accountenabled' => 'Konto aktywne?',
+'updatedetails' => 'Zapisz zmiany',
+'setglobally' => 'Właściwość zdefiniowana globalnie.',
+'usergroupmanage' => 'Zarządzanie użytkownikami i grupami',
+'newuser' => 'Zarejestruj nowego użytkownika',
+'newgroup' => 'Utwórz nową grupę',
+'yes' => 'Tak',
+'no' => 'Nie',
+'editgroup' => 'Edytuj grupÄ™',
+'groupname' => 'Nazwa grupy',
+'description' => 'Opis',
+'admin' => 'Admin grupy',
+'opennewtasks' => 'Otwieranie nowych zgłoszeń',
+'modifytasks' => 'Edycja istniejących zgłoszeń',
+'addcomments' => 'Dodawanie komentarzy',
+'attachfiles' => 'Dołączanie plików',
+'vote' => 'GÅ‚osowanie',
+'groupenabled' => 'Członkowie mogą się logować',
+'groupopen' => 'Członkowie mogą się logować',
+'tasktypelist' => 'Lista typów zgłoszeń',
+'categorylist' => 'Lista kategorii',
+'oslist' => 'Lista systemów operacyjnych',
+'resolutionlist' => 'Lista decyzji',
+'versionlist' => 'Lista wersji',
+'severitylist' => 'Lista typów ważkości',
+'listnote' => 'Uwaga: Odznaczenie opcji "Pokaż" może zmienić niektóre obecnie edytowane zgłoszenia. Zmiana pola "Nazwa" spowoduje zmianę we wszystkich zgłoszeniach, w których dana nazwa występuje. Niektórych pozycji nie można skasować, ponieważ ich obecność jest niezbędna do prawidłowego funkcjonowania lub są one obecnie w użyciu.',
+'name' => 'Nazwa',
+'order' => 'Lp.',
+'back' => 'Wróć',
+'text' => 'Tekst',
+'highlight' => 'Wyróżnij',
+'show' => 'Pokaż',
+'owner' => 'Właściciel',
+'selectowner' => 'Wybierz właściciela',
+'update' => 'Zapisz',
+'addnew' => 'Dodaj nowe',
+'flysprayprefs' => 'Ustawienia Flyspraya',
+'projecttitle' => 'Nazwa projektu',
+'baseurl' => 'Adres bazowy Flyspraya',
+'replyaddress' => 'E-mail zwrotny dla powiadomień',
+'themestyle' => 'Styl',
+'language' => 'Język',
+'anonview' => 'Pozwól oglądać zgłoszenia niezalogowanym',
+'allowanon' => 'Pozwól tworzyć nowe zgłoszenia niezalogowanym',
+'never' => 'Nigdy',
+'anonymously' => 'Anonimowo',
+'afterregister' => 'Tylko po rejestracji',
+'spamproof' => 'Użyj kodu potwierdzającego przy rejestracji',
+'anongroup' => 'Grupa dla nowo zarejestrowanych',
+'groupassigned' => 'Grupy, którym można przydzielać prace',
+'forcenotify' => 'Powiadamiaj poprzez',
+'neversend' => 'Nigdy nie wysyłaj',
+'userchoose' => 'Pozwól wybrać użytkownikowi',
+'email' => 'E-mail',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Domyślny właściciel kategorii',
+'noone' => 'Nikogo',
+'jabbernotify' => 'Powiadomienia Jabbera',
+'jabberserver' => 'Serwer',
+'jabberport' => 'Port',
+'jabberuser' => 'Nazwa konta',
+'jabberpass' => 'Hasło konta',
+'saveoptions' => 'Zapisz ustawienia',
+'editcomment' => 'Edytuj komentarz',
+'commentby' => 'Komentarz od',
+'saveeditedcomment' => 'Zapisz edytowany komentarz',
+'projectprefs' => 'Ustawienia projektu',
+'pagetitle' => 'Tytuł strony',
+'defaultproject' => 'Projekt domyślny',
+'projectlists' => 'Lista projektów',
+'showlogo' => 'Pokazuj logo',
+'intromessage' => 'Tekst wprowadzajÄ…cy',
+'isactive' => 'Projekt jest aktywny',
+'createproject' => 'Utwórz projekt',
+'nopermission' => 'Nie masz uprawnień do oglądania tej strony',
+'listordertip' => 'Kolejność, w której pozycje pojawią się na liście',
+'listshowtip' => 'Pokazuj pozycję na liście',
+'categoryownertip' => 'Ten użytkownik będzie otrzymywał powiadomienia o zgłoszeniach otwieranych w tej kategorii',
+'categoryparenttip' => 'Kategoria nadrzędna w stosunku do tej kategorii',
+'notsubcategory' => 'brak (główna kategoria)',
+'showinlineimages' => 'Pokazuj załączniki graficzne na stronie',
+'dateformat' => 'Format daty',
+'dateformat_extended' => 'Rozszerzony format daty',
+'cache_feeds' => 'Cache\'owanie kanałów',
+'no_cache' => 'Bez cache\'owania',
+'cache_disk' => 'Cache na dysku',
+'cache_db' => 'Cache w bazie',
+'subcategoryof' => 'Kategoria podrzędna',
+'visiblecolumns' => 'Kolumny widoczne na liście zgłoszeń',
+'tense' => 'Czas',
+'listtensetip' => 'Przeszłe, obecne lub przyszłe',
+'past' => 'Przeszła',
+'present' => 'Obecna',
+'future' => 'Przyszła',
+'oldpass' => 'Stare hasło',
+'nooldpass' => 'Stare hasło nie zostało ustawione',
+'oldpasswrong' => 'Stare hasło jest niepoprawne',
+'changepass' => 'Zmień hasło',
+'confirmpass' => 'Potwierdź hasło',
+'projectmanager' => 'ZarzÄ…dzanie projektem',
+'viewtasks' => 'Podgląd zgłoszeń',
+'modifyowntasks' => 'Zmiana własnych zgłoszeń',
+'modifyalltasks' => 'Zmiana cudzych zgłoszeń',
+'viewcomments' => 'PodglÄ…d komentarzy',
+'editcomments' => 'Edycja komentarzy',
+'deletecomments' => 'Usuwanie komentarzy',
+'viewattachments' => 'Podgląd załączników',
+'createattachments' => 'Dodawanie załączników',
+'deleteattachments' => 'Usuwanie załączników',
+'viewhistory' => 'PodglÄ…d historii',
+'closeowntasks' => 'Zamykanie własnych zgłoszeń',
+'closeothertasks' => 'Zamykanie cudzych zgłoszeń',
+'assigntoself' => 'Przydzielanie sobie nieprzydzielonych zgłoszeń',
+'assignotherstoself' => 'Przydzielanie sobie już przydzielonych zgłoszeń',
+'viewreports' => 'Przeglądanie logu zdarzeń',
+'othersview' => 'Zezwalaj wszystkim na przeglÄ…danie tego projektu',
+'usersandgroups' => 'Użytkownicy i grupy',
+'globalgroup' => 'Grupa globalna',
+'globalgroups' => 'Grupy globalne',
+'defaultglobalgroup' => 'Domyślna grupa globalna dla nowych użytkowników',
+'addtogroup' => 'Dodaj do grupy',
+'moveuserstogroup' => 'PrzenieÅ› zaznaczonych do grupy',
+'nogroup' => 'Brak grupy - usuń z projektu',
+'eventdesc' => 'Opis zdarzenia',
+'requestedby' => 'Zgłoszone przez',
+'daterequested' => 'Data zgłoszenia',
+'closetask' => 'Zamknij zgłoszenie',
+'reopentask' => 'Otwórz ponownie zgłoszenie',
+'applymember' => 'PoproÅ› o uczestnictwo w projekcie',
+'forcurrentproj' => 'Dla bieżącego projektu',
+'lostpw' => 'Odzyskiwanie utraconego hasła',
+'lostpwexplain' => 'Aby odzyskać hasło, wprowadź swoją nazwę użytkownika. Link umożliwiający zmianę hasła zostanie wysłany na adres podany w Twoim profilu.',
+'sendlink' => 'Wyślij link',
+'savenewpass' => 'Zapisz nowe hasło',
+'anonreg' => 'Zezwalaj na rejestrację nowych użytkowników',
+'allowanonopentask' => 'Zezwalaj na anonimowe dodawanie zgłoszeń',
+'editglobalgroup' => 'Edytuj grupÄ™ globalnÄ…',
+'editgroupforproj' => 'Edytuj grupę bieżącego projektu',
+'notshownforadmin' => 'Uprawnienia grupy administratorów nie są wyświetlane. Nie ma potrzeby wprowadzania zmian w tej grupie.',
+'general' => 'Ogólne',
+'userregistration' => 'Rejestracja użytkowników',
+'notifications' => 'Powiadomienia',
+'resetoptions' => 'Przywróć ustawienia',
+'preferences' => 'Ustawienia',
+'tasktypes' => 'Typy zgłoszeń',
+'resolutions' => 'Powody zamknięcia',
+'categories' => 'Kategorie',
+'operatingsystems' => 'Systemy operacyjne',
+'versions' => 'Wersje',
+'admintoolboxlong' => 'Narzędzia administratora',
+'newproject' => 'Nowy projekt',
+'delete' => 'Kasuj',
+'listdeletetip' => 'Usuń element',
+'lookandfeel' => 'WyglÄ…d i zachowanie',
+'globaltheme' => 'Temat globalny',
+'emailnotify' => 'Powiadomienia na e-mail',
+'fromaddress' => 'Adres nadawcy',
+'smtpserver' => 'Serwer SMTP',
+'smtpuser' => 'Nazwa użytkownika SMTP',
+'smtppass' => 'Hasło SMTP',
+'addrewrite' => 'Używaj przyjaznych adresów',
+'usereminderdaemon' => 'Włącz demona przypomnień',
+'tasksperpage' => 'Liczba zgłoszeń na stronie',
+'addtoassignees' => 'Przydziel do siebie',
+'taskstatuses' => 'Statusy zgłoszeń',
+'canvote' => 'Możesz głosować na zgłoszenie',
+'loginsuccessful' => 'Logowanie udane.',
+'youareloggedout' => 'Zostałeś wylogowany.',
+'waitwhiletransfer' => 'Proszę czekać na przekierowanie...',
+'clicknowait' => 'Kliknij tutaj, jeśli nie chcesz czekać.',
+'accountdisabled' => 'Twoje konto jest nieaktywne. Skontaktuj siÄ™ z administratorem.',
+'task' => 'Zgłoszenie',
+'edittask' => 'Edytuj zgłoszenie',
+
+'copytask' => 'Kopiuj zgłoszenie',
+
+'openedby' => 'Zgłoszone przez',
+'editedby' => 'Ostatnio edytował',
+'tasktype' => 'Typ zgłoszenia',
+'category' => 'Kategoria',
+'status' => 'Status',
+'assignedto' => 'Przydzielone do',
+'operatingsystem' => 'System operacyjny',
+'severity' => 'Ważkość',
+'reportedversion' => 'Rozpoznane w wersji',
+'dueinversion' => 'Do realizacji w wersji',
+'undecided' => 'Nie zdecydowano',
+'percentcomplete' => 'Procent zaawansowania:',
+'details' => 'Opis',
+'savedetails' => 'Zapisz',
+'canceledit' => 'Anuluj',
+'anonymous' => 'Anonimowy zgłaszający',
+'complete' => 'gotowe',
+'closedby' => 'Zamknięte przez',
+'reasonforclosing' => 'Powód zamknięcia:',
+'reopenthistask' => 'Ponownie otwórz zgłoszenie',
+'comments' => 'Komentarze',
+'attachments' => 'Załączniki',
+'relatedtasks' => 'Powiązane zgłoszenia',
+'edit' => 'Edycja',
+'addcomment' => 'Dodaj komentarz',
+'fileuploadedby' => 'Plik przysłany przez',
+'uploadafile' => 'Wyślij załącznik',
+'uploadnow' => 'Wyślij teraz!',
+'thesearerelated' => 'Te zgłoszenia są powiązane z oglądanym',
+'remove' => 'Usuń',
+'addnewrelated' => 'Dodaj nowe powiÄ…zanie',
+'add' => 'Dodaj!',
+'otherrelated' => 'Inne zgłoszenia powiązane z obecnym',
+'receivenotify' => 'Ci użytkownicy zostaną powiadomieni o zmianach w tym zgłoszeniu.',
+'addusertolist' => 'Dodaj użytkownika do listy',
+'addtolist' => 'Dodaj do listy',
+'addmyself' => 'Dodaj mnie do listy',
+'removemyself' => 'Usuń mnie z listy',
+'theseusersnotify' => 'Ci użytkownicy będą powiadamiani o zmianach w tym zgłoszeniu.',
+'attachedtoproject' => 'Przydzielony do projektu',
+'reminders' => 'Przypomnienia',
+'system' => 'System',
+'remindthisuser' => 'Przypomnij użytkownikowi',
+'thisoften' => 'Z częstotliwością',
+'startafter' => 'Odczekaj z przypominaniem',
+'hours' => 'Godzin',
+'days' => 'Dni',
+'weeks' => 'Tygodni',
+'addreminder' => 'Dodaj przypomnienie',
+'defaultreminder' => 'To jest przypomnienie, by zająć się następującym zgłoszeniem Flyspraya: ',
+'message' => 'Wiadomość',
+'closed' => 'Zamknięte',
+'filename' => 'Nazwa pliku:',
+'date' => 'Data',
+'filesize' => 'Rozmiar pliku:',
+'closurecomment' => 'Dodatkowe uwagi dotyczące zamknięcia:',
+'history' => 'Historia',
+'nohistory' => 'To zgłoszenie nie ma historii.',
+'eventdate' => 'Data',
+'user' => 'Użytkownik',
+'event' => 'Zdarzenie',
+'fieldchanged' => 'Zmiana pola',
+'taskopened' => 'Otwarcie zgłoszenia',
+'taskreopened' => 'Zgłoszenie ponownie otwarte',
+'taskclosed' => 'Zgłoszenie zamknięte',
+'commentadded' => 'Dodano komentarz',
+'commentedited' => 'Zmieniono komentarza',
+'commentdeleted' => 'Usunięto komentarz',
+'attachmentadded' => 'Dodano załącznik',
+'attachmentdeleted' => 'Usunięto załącznik',
+'taskedited' => 'Zmieniono opis zgłoszenia',
+'notificationadded' => 'Dodanie użytkownika do listy powiadomień',
+'notificationdeleted' => 'Usunięcie użytkownika z listy powiadomień',
+'relatedadded' => 'Dodano powiązane zgłoszenie',
+'relateddeleted' => 'Usunięto powiązane zgłoszenie',
+'taskassigned' => 'Zgłoszenie przydzielone do',
+'taskreassigned' => 'Zmiana przydziału zgłoszenia do',
+'assignmentremoved' => 'Usunięcie przydziału zgłoszenia',
+'summary' => 'Tytuł',
+'addedasrelated' => 'Zgłoszenie powiązane ze zgłoszeniem',
+'deletedasrelated' => 'Usunięcie powiązania ze zgłoszeniem',
+'reminderadded' => 'Dodano przypomnienie',
+'reminderdeleted' => 'Usunięto przypomnienie',
+'priority' => 'Priorytet',
+'previousvalue' => 'Poprzednia wartość',
+'newvalue' => 'Nowa wartość',
+'selectareason' => 'Wybierz powód',
+'assigntome' => 'Przejmij zgłoszenie',
+'reopenrequest' => 'PoproÅ› o ponowne otwarcie',
+'requestclose' => 'Poproś o zamknięcie',
+'ownershiptaken' => 'Użytkownik przejął zgłoszenie',
+'closerequestmade' => 'Prośba o zamknięcie zgłoszenia',
+'reopenrequestmade' => 'Prośba o ponowne otwarcie zgłoszenia',
+'taskdependson' => 'Zgłoszenie zależy od',
+'taskblocks' => 'Zgłoszenie blokuje następujące zgłoszenia',
+'depadded' => 'Zgłoszenie ma nową zależność',
+'depaddedother' => 'Dodano zgłoszenie zależne',
+'depremoved' => 'Usunięto zależność',
+'depremovedother' => 'Zgłoszenie usunięte z listy zależności innego zgłoszenia',
+'showdetailserror' => 'Zgłoszenie nie istnieje lub nie masz uprawnień, by je oglądać.',
+'makeprivate' => 'Zmień na prywatne',
+'makepublic' => 'Zmień na publiczne',
+'taskmadeprivate' => 'Zgłoszenie ustawione jako prywatne',
+'taskmadepublic' => 'Zgłoszenie ponownie ustawione jako publiczne',
+'confirmdeletecomment' => 'Czy na pewno chcesz usunąć komentarz? %s',
+'confirmdeleteattach' => 'Czy na pewno chcesz usunąć załącznik?',
+'selectedhistory' => 'Pokazane zostały wybrane szczegóły historii.',
+'showallhistory' => 'Ponownie pokaż pełną historię',
+'hidethis' => 'Ponownie ukryj obszar',
+'mark100' => 'Oznacz zgłoszenie jako wykonane w 100%',
+'watchtask' => 'Obserwuj zgłoszenie',
+'stopwatching' => 'Przestań obserwować',
+'commentlink' => 'Link do komentarza',
+'submitreq' => 'Wyślij prośbę',
+'reasonforreq' => 'Powód tego żądania',
+'pmreqdenied' => 'Menadżer projektu odrzucił prośbę',
+'taskpendingreq' => 'Prośba zgłoszona menadżerowi projektu. Więcej szczegółów w zakładce <i>Historia</i>.',
+'previoustask' => 'Poprzednie zgłoszenie',
+'nexttask' => 'Następne zgłoszenie',
+'duedate' => 'Do realizacji do dnia',
+'attachnoperms' => 'Komentarz posiada załączniki, ale nie masz uprawnień, by je zobaczyć.',
+'open' => 'Otwarte',
+'depgraph' => 'Pokaż graf zależności',
+'reset' => 'Przywróć',
+'selectusers' => 'Zaznacz użytkowników...',
+'addmetoassignees' => 'Przydziel do mnie',
+'addedtoassignees' => 'Użytkownik przydzielony do zadania',
+'dependencygraph' => 'Wykres zależności',
+'attachanotherfile' => 'Załącz inny plik',
+'OK' => 'OK',
+'addvote' => 'Dodaj głos',
+'notifyfromfs' => 'Powiadomienie z Flyspraya',
+'autogenerated' => 'WIADOMOŚĆ WYGENEROWANA AUTOMATYCZNIE, NIE ODPOWIADAJ NA NIĄ',
+'forward' => 'Prześlij',
+'previous' => 'Poprzedni',
+'next' => 'Następny',
+'first' => 'Pierwsza',
+'last' => 'Ostatnia',
+'page' => 'Strona %d z %d',
+'search' => 'Szukaj',
+'alltasktypes' => 'Dowolny typ',
+'allseverities' => 'Dowolna ważkość',
+'alldevelopers' => 'Wszyscy programiści',
+'notyetassigned' => 'Zgłoszenia nieprzydzielone',
+'allcategories' => 'Dowolna kategoria',
+'allstatuses' => 'Dowolny status',
+'allopentasks' => 'Zgłoszenia otwarte',
+'sortthiscolumn' => 'Sortuj według kolumny',
+'id' => 'ID',
+'project' => 'Projekt:',
+'dateopened' => 'Data otwarcia',
+'progress' => 'Postęp',
+'searchthisproject' => 'Szukaj w bieżącym projekcie',
+'dueanyversion' => 'Realizacji w dowolnej wersji',
+'anyversion' => 'Rozpoznane w dowolnej wersji',
+'dueversion' => 'Realizacji w wersji',
+'lastedit' => 'Data ostatniej edycji',
+'os' => 'System operacyjny',
+'reportedin' => 'Rozpoznane w',
+'taskrange' => 'Pokazane zgłoszenia %d - %d z %d',
+'noresults' => 'Żadne zgłoszenie nie spełnia warunków wyszukiwania.',
+'takeaction' => 'Wybierz czynność',
+'watchtasks' => 'Obserwuj zaznaczone',
+'stopwatchingtasks' => 'Przestań obserwować zgłoszenie',
+'assigntaskstome' => 'Przyporządkuj zaznaczone zgłoszenia do mnie.',
+'dueby' => 'Realizacja do',
+'dueanytime' => 'Dowolna data',
+'selectduedate' => 'Wybierz datÄ™ realizacji',
+'toggleselected' => 'Przełącz zaznaczone',
+'due' => 'Realizacja',
+'assignedtome' => 'PrzyporzÄ…dkowane do mnie',
+'tasklist' => 'Lista zgłoszeń',
+'dateclosed' => 'Data zamknięcia',
+'advanced' => 'Zaawansowane',
+'searchcomments' => 'Szukaj w komentarzach',
+'searchforall' => 'Szukaj wszystkich słów',
+'anonusers' => 'Użytkownik anonimowy',
+'miscellaneous' => 'Różne',
+'users' => 'Użytkownicy',
+'taskproperties' => 'Właściwości zgłoszenia',
+'selectsincedate' => 'Wybierz ZmianÄ™ od',
+'changedsince' => 'Zmienione od',
+'updatefs' => 'Proszę zaktualizować Flyspraya.',
+'currentversion' => 'Twoja obecna wersja to',
+'latestversion' => 'najbardziej aktualna wersja to',
+'hidemessage' => '(przypomnij później)',
+'saveas' => 'Zapisz wynik wyszukania jako',
+'nosearches' => 'Brak zapisanych wyszukiwań',
+'saving' => 'Zapisywanie...',
+'votes' => 'GÅ‚osy',
+'allclosedtasks' => 'Wszystkie zamknięte zgłoszenia',
+'password' => 'Hasło',
+'login' => 'Zaloguj!',
+'rememberme' => 'Zapamiętaj mnie',
+'lostpassword' => 'Nie pamiętasz hasła?',
+'lostpwforfs' => 'Zapomniałem hasła do Flyspraya',
+'lostpwmsg1' => "Cześć.
+
+Zapomniałem mojego hasła do Flyspraya na ",
+'lostpwmsg2' => ", proszę o przesłanie nowego hasła.
+
+Użytkownik: ",
+'regards' => "
+
+Pozdrowienia,",
+'yourusername' => ' twoja nazwa użytkownika ',
+'locale' => 'pl-PL',
+'filenotexist' => 'Plik nie istnieje lub nie masz uprawnień aby go pobrać.',
+'showtask' => 'Pokaż zgłoszenie',
+'now' => 'Teraz',
+'go' => 'Idź!',
+'opentaskanon' => 'Utwórz anonimowo nowe zgłoszenie',
+'register' => 'Zarejestruj nowe konto',
+'addnewtask' => 'Dodaj nowe zgłoszenie',
+'reports' => 'Log zgłoszeń',
+'editmydetails' => 'Edytuj moje dane',
+'logout' => 'Wyloguj siÄ™',
+'disabledaccount' => 'Twoje konto jest nieaktywne!<br />Zostaniesz wylogowany...',
+'poweredby' => 'Napędzane przez Flyspray',
+'projects' => 'Projekty',
+'allprojects' => 'wszystkie',
+'selectproject' => 'dla projektu:',
+'tasksall' => 'Wszystkie zgłoszenia',
+'tasksassigned' => 'Przydzielone do mnie',
+'tasksreported' => 'Zgłoszone przeze mnie',
+'taskswatched' => 'Obserwowane przeze mnie',
+'mysearch' => 'Moje wyszukiwania',
+'admintoolbox' => 'Narzędzia administratora',
+'manageproject' => 'ZarzÄ…dzaj projektem',
+'permissions' => 'Pokaż uprawnienia',
+'hide' => 'Ukryj',
+'pendingreq' => 'Oczekujące prośby',
+'errorpage' => "Flyspray nie znalazł żądanej strony.
+ Być może zostało wybrane nieistniejące zgłoszenie
+ lub nie masz uprawnień, by zobaczyć żądaną stronę.<br /><br />
+ A może próbujesz w dziwny sposób zmusić bazę danych
+ do wykonania twojego kodu SQL? Jeśli tak, to
+ idź do kąta i zastanów się nad swoim postępowaniem. I nie rób tego więcej!",
+'permissionsforproject' => 'Uprawnienia dla ',
+'switchto' => 'Przełącz do',
+'lastsearch' => 'Ostatnio wyszukiwane',
+'modify' => 'Zmień',
+'noticefrom' => 'Powiadomienie od',
+'hasopened' => ' OTWORZYÅ(A) NOWE ZGÅOSZENIE, które zostaÅ‚o Ci przydzielone:',
+'moreinfonew' => 'Więcej informacji o zgłoszeniu znajdziesz na stronie:',
+'newtaskcategory' => 'Dodano nowe zgłoszenie w tej kategorii',
+'categoryowner' => 'Otrzymałeś wiadomość, bo jesteś wymieniony jako opiekun kategorii.',
+'tasksummary' => 'Tytuł zgłoszenia:',
+'newtaskadded' => 'Twoje nowe zgłoszenie zostało dodane.',
+'summaryanddetails' => 'Musisz wypełnić zarówno tytuł jak i podać opis zgłoszenia.',
+'goback' => 'Powrót.',
+'messagefrom' => 'To jest wiadomość z systemu śledzenia błędów Flyspray na ',
+'hasjustmodified' => 'właśnie zmienił(-a) zgłoszenie.',
+'changedfields' => 'Pola, które zmienione zostały oznaczone gwiazdkami (**)',
+'moreinfomodify' => 'Więcej informacji o zgłoszeniu znajdziesz na stronie:',
+'nolongerassigned' => 'Poniższe zgłoszenie nie jest już przydzielone do Ciebie. Zostało przydzielone do',
+'hasassigned' => 'przydzielił(-a) Ci następujące zgłoszenie:',
+'taskupdated' => 'Zgłoszenie zostało zaktualizowane.',
+'hasclosedassigned' => 'zamknął(-ęła) zgłoszenie które było przypisane do Ciebie:',
+'unassigned' => 'Nieprzydzielone',
+'hasclosed' => 'zamknął(-ęła) poniższe zgłoszenie.',
+'youonnotify' => 'Otrzymujesz wiadomość, bo jesteś na liście powiadomień.',
+'taskclosedmsg' => 'Zgłoszenie zostało zamknięte.',
+'returntotask' => 'Wróć do szczegółowego opisu zgłoszenia',
+'backtoindex' => 'Wróć do listy zgłoszeń',
+'noclosereason' => 'Nie został wybrany powód zamknięcia zgłoszenia.',
+'hasreopened' => 'ponownie otworzył(-a) zgłoszenie, które zamknąłeś(-aś):',
+'taskreopenedmsg' => 'Zgłoszenia ponownie otwarte.',
+'backtotask' => 'Wróć do zgłoszenia.',
+'commentaddedmsg' => 'Komentarz został dodany.',
+'commenttoassigned' => 'dodał(-a) następujący komentarz do zgłoszenia przydzielonego do:',
+'commenttotask' => 'dodał(-a) następujący komentarz do tego zgłoszenia.',
+'nocommententered' => 'Przed zatwierdzeniem formularza wpisz komentarz.',
+'fillinfields' => 'Nie zostały wypełnione wszystkie pola.',
+'notcurrentpass' => 'To nie jest twoje bieżące hasło!',
+'passchanged' => 'Twoje hasło zostało zmienione.',
+'closewindow' => 'Możesz już zamknąć to okno.',
+'passnomatch' => 'Źle powtórzone nowe hasło!',
+'usernametaken' => 'Ta nazwa użytkownika jest już zajęta. Wybierz inną.',
+'newusercreated' => 'Nowe konto zostało utworzone.',
+'accountcreated' => 'Twoje konto zostało utworzone.',
+'newuserwarning' => 'Jeśli nie możesz się zalogować, prawdopodobnie administrator ustawił wymóg akceptacji konta.',
+'nomatchpass' => 'Źle powtórzono hasło.',
+'confirmwrong' => 'Nieprawidłowy kod potwierdzający!',
+'formnotcomplete' => 'Nie został wypełniony cały formularz.',
+'formnotnumeric' => 'Podane dane nie sÄ… liczbowe!',
+'groupnametaken' => 'Grupa o takiej nazwie już istnieje.',
+'newgroupadded' => 'Grupa została dodana.',
+'optionssaved' => 'Opcje programu zostały zapisane.',
+'hasuploaded' => 'załączył(-a) plik do zgłoszenia przydzielonego do:',
+'hasattached' => 'załączył(-a) plik do następującego zgłoszenia.',
+'fileuploaded' => 'Plik został wysłany.',
+'fileerror' => 'Podczas wysyłania pliku wystąpił błąd. Być może jest to problem z niewłaściwymi prawami dostępu do katalogu załączników - <i>attachments/</i>.',
+'contactadmin' => 'Skontaktuj siÄ™ z administratorem tego projektu.',
+'selectfileerror' => 'Pliku nie został wybrany.',
+'userupdated' => 'Dane użytkownika zostały zaktualizowane',
+'realandemail' => 'Pola imienia i nazwiska oraz adresu e-mail nie zostały podane.',
+'groupupdated' => 'Ustawienia grupy zostały zaktualizowane.',
+'groupanddesc' => 'Nazwa i opis grupy nie zostały podane.',
+'fillallfields' => 'Proszę wypełnić wszystkie pola.',
+'listPmustN' => 'W polu "Lp." należy wpisać liczbę.',
+'listupdated' => 'Lista została zaktualizowana.',
+'listitemadded' => 'Element listy został dodany.',
+'relatedaddedmsg' => 'Zgłoszenie zostało dodane do listy powiązanych.',
+'relatederror' => 'To zgłoszenie jest już na liście zgłoszeń powiązanych.',
+'relatedremoved' => 'Zgłoszenie zostało usunięte z listy powiązanych.',
+'notifyadded' => 'Użytkownik został dodany do listy powiadomień.',
+'notifyerror' => 'Użytkownik jest już na liście powiadomień tego zgłoszenia.',
+'notifyremoved' => 'Użytkownik został usunięty z listy powiadomień.',
+'editcommentsaved' => 'Zmieniony komentarz został zachowany.',
+'commentdeletedmsg' => 'Komentarz został usunięty.',
+'gotonewtask' => 'Przejdź do nowo utworzonego zgłoszenia',
+'projectcreated' => 'Twój nowy projekt został utworzony. Przejdź do zarządzania projektem, aby ustawić odpowiednie opcje.',
+'customiseproject' => 'Dostosuj projekt',
+'projectupdated' => 'Ustawienia projektu zostały zaktualizowane.',
+'emptytitle' => 'Tytuł projektu jest pusty. Wróć i popraw.',
+'loginbelow' => 'Możesz teraz spróbować zalogować się za pomocą formularza logowania.',
+'attachmentdeletedmsg' => 'Załącznik został usunięty',
+'reminderaddedmsg' => 'Twoje przypomnienie zostało dodane.',
+'reminderdeletedmsg' => 'Zaznaczone przypomnienie zostało usunięte.',
+'flyspraytask' => 'Zgłoszenie',
+'fieldsmissing' => 'Niektóre pola były puste lub zawierały błędne dane.',
+'relatedinvalid' => 'Zgłoszenie nie istnieje.',
+'relatedproject' => 'To zgłoszenie jest przypisane do innego projektu. Czy mimo to dodać powiązanie?',
+'addanyway' => 'Dodaj mimo tego',
+'cancel' => 'Rezygnuj',
+'alreadyedited' => 'Zgłoszenie zostało w międzyczasie zmienione przez inną osobę. Czy mimo to chcesz zachować zmiany?',
+'saveanyway' => 'Mimo tego, zapisz moje zmiany.',
+'nouserselected' => 'Żaden użytkownik nie został wybrany. Wybierz przynajmniej jednego użytkownika i spróbuj ponownie.',
+'groupswitchupdated' => 'Grupy zostały zmodyfikowane.',
+'takenownershipmsg' => 'To zgłoszenie jest teraz przydzielone do Ciebie.',
+'adminrequestmade' => 'Twoja prośba została przesłana osobie zarządzającej projektem.',
+'newdepnotify' => 'Nowa zależność została dodana do poniższego zgłoszenia:',
+'dependadded' => 'Zależność dodana.',
+'dependaddfailed' => 'Nie możesz dodać tego zgłoszenia jako zależności',
+'depremovedmsg' => 'Zależność zgłoszeń została przeniesiona',
+'newdepis' => 'Nowa zależność',
+'magicurlsent' => 'Wiadomość została wysłana na Twój adres. Zawiera ona odnośnik do strony umożliwiającej dokończenie procedury.',
+'changefspass' => 'Zmień hasło',
+'magicurlmessage' => 'Wybierz poniższy odnośnik by zmienić hasło:',
+'erroronform' => 'Wystąpił problem podczas przetwarzania formularza',
+'addressused' => 'Twój adres został użyty do utworzenia konta we Flysprayu. Jeśli ta wiadomość jest nieoczekiwana, to zignoruj ją i skasuj. Aby potwierdzić rejestrację przejdź pod podany poniżej adres URL:',
+'confirmcodeis' => 'Kod weryfikujÄ…cy:',
+'codesent' => 'Kod weryfikujący został wysłany wraz z dalszymi instrukcjami.',
+'codenotsent' => 'Nie udało się wysłać kodu weryfikującego, proszę spróbować później.',
+'taskmadeprivatemsg' => 'To zgłoszenie uzyskało status prywatnego',
+'taskmadepublicmsg' => 'To zgłoszenie odzyskało status publiczny',
+'realandnotify' => 'Musisz wypełnić pola \'Imię i nazwisko\' oraz \'Adres e-mail\' lub \'Jabber ID\'.',
+'pmreqdeniedmsg' => 'Prośba odrzucona przez Menadżera Projektu',
+'massopsuccess' => 'Operacja zbiorowa udana (tam, gdzie pozwalały na to uprawnienia)',
+'usernotexist' => 'Użytkownik nie istnieje',
+'commentattachperms' => 'Nie możesz usunąć komentarza - brak uprawnień do usuwania załączników',
+'voterecorded' => 'Twój głos został zarejestrowany',
+'votefailed' => 'Twój głos nie może zostać dodany w tej chwili.',
+'createnewgroup' => 'Utwórz nową grupę',
+'requiredfields' => 'Wymagane pola sÄ… oznaczone znakiem',
+'addthisgroup' => 'Dodaj grupÄ™',
+'createnewproject' => 'Utwórz nowy projekt',
+'addnewproject' => 'Dodaj projekt',
+'htmlallowed' => 'Zezwalaj na kod HTML',
+'createthisproject' => 'Utwórz projekt',
+'inlineimages' => 'Wyświetl załączniki w treści strony',
+'createnewtask' => 'Utwórz nowe zgłoszenie dla projektu',
+'addanother' => 'Dodaj kolejne zgłoszenie',
+'addthistask' => 'Dodaj to zgłoszenie',
+'notifyme' => 'Powiadom mnie, gdy zgłoszenie zostanie zmienione',
+'newtask' => 'Nowe zgłoszenie',
+'attachafile' => 'Załącz plik',
+'registernewuser' => 'Rejestracja nowego użytkownika',
+'none' => 'Brak',
+'registeraccount' => 'Utwórz konto',
+'both' => 'Obydwa',
+'notifyfrom' => 'Powiadomienie od ',
+'donotreply' => 'WIADOMOŚĆ WYGENEROWANA AUTOMATYCZNIE. NIE ODPOWIADAJ NA NIĄ.',
+'disclaimer' => 'Otrzymujesz tę wiadomość, ponieważ została ona zamówiona ją w aplikacji do śledzenia błędów - Flyspray. Jeśli nie chcesz otrzymywać podobnych wiadomości w przyszłości, to zmień ustawienia dla swoich powiadomień na stronie o adresie widocznym powyżej.',
+'userwho' => 'Zmiana dokonana przez użytkownika:',
+'moreinfo' => 'Więcej informacji pod poniższym adresem URL:',
+'newtaskopened' => 'Pojawiło się nowe zgłoszenie. Szczegóły poniżej.',
+'notify.taskclosed' => 'Następujące zgłoszenie zostało zamknięte:',
+'notify.taskreopened' => 'Następujące zgłoszenie zostało ponownie otwarte:',
+'newdep' => 'Następujące zgłoszenie posiada nową zależność:',
+'notify.depremoved' => 'Z następującego zgłoszenia została usunięta zależność:',
+'olddepwas' => 'Stara zależność',
+'notify.commentadded' => 'Do następującego zgłoszenia został dodany komentarz:',
+'commentis' => 'Treść komentarza poniżej.',
+'newattachment' => 'Nowy plik został załączony do następującego zgłoszenia:',
+'detailsbelow' => 'Szczegóły poniżej.',
+'notify.relatedadded' => 'Nowe powiązanie zostało dodane do następującego zgłoszenia:',
+'relatedis' => 'Powiązane zgłoszenie to',
+'assignedtoyou' => 'Następujące zgłoszenie zostało przydzielone do Ciebie:',
+'takenownership' => 'przejął zgłoszenie:',
+'requiresaction' => 'Następujące zgłoszenie wymaga akcji ze strony Menadżera Projektu:',
+'requiresactionnotify' => 'Zgłoszenie wymaga akcji ze strony Menadżera Projektu:',
+'pmdeny' => 'Menadżer Projektu odrzucił prośbę dotyczącą zgłoszenia:',
+'pmdenynotify' => 'Menadżer Projektu odrzucił prośbę',
+'fileaddedtoo' => 'Co najmniej jeden plik został dołączony do komentarza.',
+'taskwatching' => 'Obserwujesz następujące zgłoszenia',
+'isdepfor' => 'zależy od',
+'denialreason' => 'Powód odmowy',
+'taskchanged' => 'Zgłoszenie uległo zmianie. Zmiany są pokazane poniżej. Aby dowiedzieć się więcej, otwórz stronę zgłoszenia i wybierz zakładkę Historia.',
+'useraddedtoassignees' => 'Użytkownik dodał siebie do listy użytkowników przydzielonych do tego zgłoszenia.',
+'removeddepis' => 'Usunięta zależność to',
+'isnodepfor' => 'już nie jest zależny od',
+'usergroups' => 'Grupy użytkowników',
+'pmtoolbox' => 'Narzędzia Menadżera Projektu',
+'groupmanage' => 'ZarzÄ…dzanie grupÄ…',
+'pendingrequests' => 'Oczekujące zgłoszenia administratorów',
+'reasongiven' => 'Podana przyczyna',
+'nopendingreq' => 'Brak oczekujących próśb.',
+'givereason' => 'Podaj przyczynÄ™',
+'catlisted' => 'Edytor kategorii',
+'oslisted' => 'Edytor listy systemów operacyjnych',
+'verlisted' => 'Edytor listy wersji',
+'tasktypeed' => 'Edytor listy typów zgłoszeń',
+'resed' => 'Edytor listy powodów zamknięcia',
+'deny' => 'Odrzuć',
+'notifiedwhen' => 'Powiadomienia gdy',
+'onlynewtasks' => 'Nowe zgłoszenia są otwarte',
+'allevents' => 'Dowolne zdarzenie dla dowolnego zgłoszenia',
+'feeds' => 'Kanały',
+'feeddescription' => 'Opis kanału',
+'feedimgurl' => 'Adres URL grafiki kanału (pozostaw puste jeśli brak)',
+'notifysubject' => 'Temat powiadomień',
+'notifysubjectinfo' => '(%p = tytuł projektu, %s = opis zgłoszenia, %t = identyfikator zgłoszenia, %a = akcja, %u = użytkownik)',
+'priority6' => 'BÅ‚yskawicznie',
+'priority5' => 'Natychmiast',
+'priority4' => 'Pilne',
+'priority3' => 'Wysoki',
+'priority2' => 'Normalny',
+'priority1' => 'Niski',
+'sendcode' => 'Wyślij kod!',
+'entercode' => 'Podaj kod, który znajduje się w wysłanej do Ciebie wiadomości. Podaj też swoje nowe hasło.',
+'confirmationcode' => 'Kod potwierdzajÄ…cy',
+'registererror' => 'Upewnij się, że wszystkie wymagane pola zostały wypełnione oraz że podałeś odpowiednie dane co do wybranej metody komunikacji.',
+'validusername' => '(dozwolone są wyłącznie znaki alfanumeryczne oraz - _ .)',
+'emailtaken' => 'Ten adres e-mail lub Jabber ID jest już zajęty. Wybierz inny.',
+'note' => '<b>Uwaga:</b> Zanim Twoje konto zostanie otwarte, wyślemy do Ciebie kod potwierdzający. Kod ten zostanie wysłany w sposób wybrany przez Ciebie powyżej.<br />Jeśli podasz fałszywe dane, <strong>nie otrzymasz kodu</strong>.',
+'changelog' => 'Lista zmian',
+'changeloggen' => 'Generator listy zmian',
+'listfrom' => 'Pokaż zmiany od',
+'to' => 'do',
+'oldestfirst' => 'Sortuj od najstarszych',
+'recentfirst' => 'Sortuj od najnowszych',
+'severityrep' => 'Raport ważkości',
+'totalopen' => 'Całkowita liczba otwartych zgłoszeń',
+'age' => 'Wiek',
+'agerep' => 'Raport wieku',
+'eventsrep' => 'Raport zdarzeń',
+'events' => 'Zdarzenia',
+'Tasks' => 'Zgłoszenia',
+'opened' => 'Otwarte',
+'edited' => 'Zmieniane',
+'assigned' => 'Przydzielone',
+'within' => 'Okres',
+'pastday' => 'wczoraj',
+'pastweek' => 'w zeszłym tygodniu',
+'pastmonth' => 'w zeszłym miesiącu',
+'pastyear' => 'w zeszłym roku',
+'nolimit' => 'bez ograniczeń',
+'from' => 'Zakres dni',
+'duein' => 'Do wersji',
+'selectfromdate' => 'Wybierz datę rozpoczęcia',
+'selecttodate' => 'Wybierz datę zakończenia',
+'showvoters' => 'Wyświetl/ukryj głosujących',
+'roadmap' => 'Plan działań',
+'roadmapfor' => 'Plan działań dla wersji',
+'tasks' => 'Zgłoszenia',
+'completed' => 'zakończono.',
+'opentasks' => 'otwarte zgłoszenia',
+'of' => '% z',
+'severity5' => 'Krytyczna',
+'severity4' => 'Wysoka',
+'severity3' => 'Åšrednia',
+'severity2' => 'Niska',
+'severity1' => 'Bardzo niska',
+'Redirect' => 'Przekierowanie',
+'redirectmsg' => 'Jeśli Twoja przeglądarka nie obsługuje meta-przekierowań, to kliknij %sTUTAJ%s aby zostać przeniesionym do odpowiedniej strony',
+'allowclosedcomments' => 'Wszystkie komentarze z zamkniętych zgłoszeń',
+'comment' => 'Komentarz',
+'editowncomments' => 'Edytuj własne komentarze',
+'reopened' => 'Ponownie otwarty',
+'loading' => 'Wczytywanie...',
+'notifyown' => 'Powiadom o własnych zmianach',
+'youremail' => 'Twój adres e-mail',
+'thankyouforbug' => 'Dziękujemy za zgłoszenie problemu. Możesz obejrzeć zgłoszenie i obserwować postęp używając poniższego adresu URL:',
+'anonuser' => 'użytkownik anonimowy',
+'conflict' => 'Konflikt',
+'file' => 'Plik',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Rozmiar',
+'projectgroup' => 'Grupa Projektu',
+'profile' => 'Profil:',
+'viewprofile' => 'Obejrzyj profil',
+'regdate' => 'Zarejestrowany od',
+'tasksopened' => 'Zgłoszenia otwarte',
+'replyto' => 'Odpowiedź do',
+'notifytypes' => 'Rodzaje powiadomień',
+'pm.taskchanged' => 'Zgłoszenie zmienione',
+'pm.taskreopened' => 'Zgłoszenie ponownie otwarte',
+'pm.depadded' => 'Zależność dodana',
+'pm.depremoved' => 'Zależność usunięta',
+'pmrequest' => 'Prośba przez PW ',
+'pmrequestdenied' => 'Prośba odrzucona',
+'newassignee' => 'Nowy przydział',
+'revdepadded' => 'Odwrotna zależność dodana',
+'revdepaddedremoved' => 'Odwrotna zależność usunięta',
+'assigneeadded' => 'Przyporządkowanie zostało dodane',
+'addusergroup' => 'Dodaj użytkownika do grupy',
+'groupmembers' => 'Użytkownicy grupy',
+'deleteuser' => 'Usuń użytkownika',
+'userdeleted' => 'Usunięto użytkownika',
+'autoassign' => 'Automatycznie przydziel zgłoszenia do właściciela kategorii',
+'ssl' => 'SSL',
+'updatewrong' => 'Funkcja sprawdzania dostępności aktualizacji jest włączona,
+ lecz próba kontaktu z serwerem aktualizacji nie powiodła się. Twój serwer nie zezwala na zewnętrzne połączenia
+ lub wystąpiły problemy z siecią.
+ Aby upewnić się, że posiadasz najnowszą wersję Flyspraya, wejdź na stronę http://www.flyspray.org/.',
+'deleteproject' => 'Usuń ten projekt i przenieś zawartość do',
+'projectdeleted' => 'Projekt został pomyślnie usunięty',
+'feedforall' => 'Kanał dla wszystkich projektów',
+'usercreated' => 'Dodano użytkownika',
+'userdeleted' => 'Skasowano użytkownika',
+'created' => 'Utworzono',
+'deleted' => 'Usunięto',
+'userid' => 'ID użytkownika',
+'editassignments' => 'Zmień przydziały',
+'preview' => 'PodglÄ…d',
+'anyprogress' => 'dowolny postęp',
+'tasksrelated' => 'Zgłoszenia powiązane z tym zgłoszeniem',
+'duplicatetasks' => 'Duplikaty tego zgłoszenia',
+'databasemodfailed' => 'Błąd podczas zmian w bazie danych. Prawdopodobną przyczyną jest brak wystarczających uprawnień.',
+'frequency' => 'Częstotliwość',
+'newuserregistered' => 'Nowy użytkownik został zarejestrowany w Twojej instalacji Flyspraya. Szczegóły użytkownika:',
+'newuserregisterednotify' => 'Nowy użytkownik został zarejestrowany',
+'notify_registration' => 'Powiadom administratorów o nowo zarejestrowanym użytkowniku',
+'textversion' => 'Wersja tekstowa',
+'onlyprimary' => 'Zgłoszenia nie blokujące innych zgłoszeń',
+'switch' => 'Przełącz',
+'max' => 'maks',
+'dates' => 'Daty',
+'selectduedatefrom' => 'Realizacja od',
+'selectduedateto' => 'do',
+'selectsincedatefrom' => 'Zmieniane od',
+'selectsincedateto' => 'do',
+'selectdate' => 'Wybierz datÄ™',
+'selectopenedfrom' => 'Otwarte od',
+'selectopenedto' => 'do',
+'selectclosedfrom' => 'Zamknięte od',
+'selectclosedto' => 'do',
+'startat' => 'Zacznij od',
+'hasattachment' => 'Posiada załącznik',
+'private' => 'Prywatne',
+'watching' => 'Obserwowanie',
+'alreadyvotedthistask' => 'już głosowałeś na to zgłoszenie',
+'alreadyvotedthisday' => 'już dziś głosowałeś',
+'visibility' => 'Widoczność',
+'public' => 'Publiczne',
+'leaveemptyauto' => 'Pozostaw pola hasła puste, jeśli chcesz aby zostało ono automatycznie wygenerowane.',
+'novalidemail' => 'Podaj poprawny adres email.',
+'novalidjabber' => 'Podaj poprawny identyfikator Jabbera.',
+'missingrequired' => 'Nie wszystkie wymagane pola zostały wypełnione.',
+'entervalidusername' => 'Proszę podać prawidłową nazwę użytkownika oraz imię i nazwisko.',
+'couldnotaddusernotif' => 'Nie udało się dodać tego użytkownika do listy powiadomień.',
+'defaulttask' => 'Standardowy opis zgłoszenia',
+'all' => 'Wszystkie',
+'events.useraddedtoassignees'=> 'Użytkownik został przydzielony',
+'vote(s)' => 'głos(y)',
+'eventlog' => 'Log zdarzeń',
+'assignmentchanged' => 'Przydział został zmieniony',
+'detailedinfo' => 'Szczegółowe informacje',
+'All' => 'Wszystkie',
+'tasksireported' => 'Zgłoszone przeze mnie',
+'recentlyopened' => 'Ostatnio otwarte',
+'stats' => 'Statystyki',
+'totaltasks' => 'wszystkie zgłoszenia',
+'mostwanted' => 'Najbardziej oczekiwane zgłoszenia',
+'defaultentry' => 'Domyślna strona',
+'toplevel' => 'Widok główny',
+'overview' => 'PrzeglÄ…d',
+'error#' => 'BÅ‚Ä…d #',
+'error1' => 'Nie posiadasz wymaganych uprawnień aby móc obejrzeć ten załącznik.',
+'error3' => 'Powtórzona akcja, przekierowuję do strony głównej.',
+'error4' => 'Nie posiadasz uprawnień administratora.',
+'error5' => 'Taki użytkownik nie istnieje w tej instalacji Flyspraya.',
+'error6' => 'Nieprawidłowa strefa administratora.',
+'error7' => 'Logowanie nie powiodło się - nieprawidłowe hasło!',
+'error71' => 'Konto zostało zablokowane na %d minut (zbyt dużo nieudanych prób logowania)!',
+'error8' => 'Niewypełniona nazwa użytkownika oraz hasło.',
+'error9' => 'Zgłoszenie nie istnieje lub nie posiadasz wystarczających uprawnień aby je obejrzeć.',
+'error10' => 'Zgłoszenie nie istnieje lub nie posiadasz uprawnień aby je obejrzeć.',
+'error101' => 'Nie posiadasz uprawnień do oglądania tego zgłoszenia.',
+'error102' => 'Nie posiadasz uprawnień do oglądania tego zgłoszenia - być może wystarczy się zalogować.',
+'error11' => 'Brak uprawnień do zmiany tego komentarz.',
+'error12' => 'Brak prawidłowego magicznego klucza! Czy jesteś pewien, że taki właśnie otrzymałeś w powiadomieniu?',
+'error13' => 'Użytkownicy anonimowi nie posiadają profilu.',
+'error14' => 'Nie posiadasz wystarczających uprawnień aby utworzyć nową grupę.',
+'error15' => 'Nie posiadasz wystarczających uprawnień aby otworzyć zgłoszenie.',
+'error16' => 'Nie jesteś menadżerem projektu.',
+'error17' => 'Nieprawidłowy obszar prywatnych wiadomości.',
+'error18' => 'Nieprawidłowy magiczny URL.',
+'error19' => 'Taki użytkownik nie istnieje w tej instalacji Flyspraya.',
+'error20' => 'Nieprawidłowa zmiana bazy danych.',
+'error21' => 'Nie udało się wysłać jednej lub więcej wiadomości e-mail. Sprawdź swoje ustawienia.',
+'error22' => 'Rejestracja nowych użytkowników jest niedozwolona.',
+'error23' => 'Nieaktywny użytkownik lub grupa.',
+'error24' => 'Brak ustawionego dostępu do programu "dot" lub publicznego serwera "dot" do generowania grafów.',
+'error25' => 'Plan działań jest dostępny jedynie dla wybranych projektów.',
+'error26' => 'Nieznany klucz OAuth.',
+'error27' => 'Nie można zalogować. Pozwól nam zobaczyć Twój adres e-mail.',
+'done' => 'wykonano',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projekt nie mógł zostać usunięty.',
+'GMT' => 'GMT',
+'timezone' => 'Strefa czasowa',
+'accept' => 'Akceptuj',
+'reasonfordeinal' => 'Powód odmowy',
+'pruneclosedlinks' => 'Przytnij zamknięte odnośniki',
+'pruneclosedtasks' => 'Przytnij zamknięte zgłoszenia',
+'pagegenerated' => 'Strona i grafika wygenerowane w %d sekund.',
+'pruninglevel' => 'Stopień przycinania',
+'lastuser' => 'Ostatni użytkownik nie może zostać usunięty.',
+'allprivate' => 'Wszystkie projekty sÄ… prywatne.',
+'deletegroup' => 'Usuń tę grupę i przenieś użytkowników do',
+'parent' => 'Nadrzędny',
+'ordertip' => 'Kolejność tych pozycji będzie widoczna na liście',
+'showtip' => 'Wyświetl to zgłoszenie na liście',
+'deletetip' => 'Usuń tę wartość z listy',
+'del' => 'kasuj',
+'request1' => 'Poproszono o zamknięcie zgłoszenia.',
+'request2' => 'Poproszono o ponowne otwarcie zgłoszenia.',
+'allpriorities' => 'Wszystkie priorytety',
+'noroadmap' => 'Plan działań jest niedostępny (w tym projekcie nie ma przyszłej wersji).',
+'expand' => 'Rozwiń',
+'collapse' => 'Zwiń',
+'expandall' => 'Rozwiń wszystkie',
+'collapseall' => 'Zwiń wszystkie',
+'minpwsize' => 'Minimalna długość hasła to 5 znaków',
+'passwordtoosmall' => 'Hasło jest za krótkie.',
+'accountwaslocked' => 'Twoje konto zostało zablokowane z powodu zbyt wielu nieudanych prób zalogowania się.',
+'failedattempts' => 'Liczba nieudanych prób zalogowania się: %d.',
+'groupnotexist' => 'W tym projekcie nie ma wybranej grupy.',
+'searchindetails' => 'Szukaj w opisach',
+'showasassignees' => 'Pokaż przy przydzielaniu',
+'find' => 'wyszukaj',
+'tls' => 'TLS',
+'isadmin' => 'Jest administratorem',
+'addvotes' => 'Dodaj głosy',
+'removevote' => 'Usuń głos',
+'voteremoved' => 'Głos został usunięty',
+'voteremovefailed' => 'Błąd podczas usuwania głosu.',
+'connectedtasks' => 'Połączone zgłoszenia:',
+'taskdependencies' => 'Zależności',
+'viewgraph' => 'wyświetl graf',
+'notaskdependencies' => 'To zgłoszenie nie jest zależne od żadnego innego zgłoszenia.',
+'dependson' => 'Zależny od',
+'blocks' => 'Bloki',
+'newdependency' => 'Nowa zależność:',
+'nouserstoadd' => 'Brak użytkowników do dodania. Upewnij się, że Imię, Nazwa użytkownika oraz e-mail są zdefiniowane dla każdego użytkownika.',
+'dispintro' => 'Pokaż wstępną wiadomość',
+'mainmessage' => 'Wiadomość wstępna',
+/* note only the English version for 'dispintro' is supported
+ * other languages should also be taken cared of in the future*/
+
+'setsupertask' => 'Ustaw ID Super-Zgłoszenia:',
+'supertaskmodified' => 'ID Super-Zgłoszenia został zmieniony',
+'set' => 'Ustaw',
+'supertask' => 'Super-Zgłoszenie',
+'setparent' => 'Ustaw ID zgłoszenia nadrzędnego dla tego zgłoszenia',
+'selfsupertasknotallowed' => 'ID dla Super-Zgłoszenia nie może być taki sam jak ID dla własnego zgłoszenia',
+'quickaction' => 'Szybkie działania',
+'updateselectedtasks' => 'Aktualizuj wybrane zadania',
+'notspecified' => 'Nic nie zaznaczono',
+'editselectedtasks' => 'Edytuj wybrane zadania',
+'information' => 'Informacja',
+'taskclosedisabled' => 'Zamknięcie zgłoszenia jest niemożliwe, ponieważ następujące zgłoszenia zależne są otwarte:-',
+'daysleft' => 'Dni pozostałych',
+'dayoverdue' => 'spóźnionych dni',
+'duetoday' => 'Na dziÅ›.',
+'daysbeforealert' => 'Liczba dni przed ostrzeżeniem',
+'associatedsubtask' => 'Pomyślnie przypisano zadanie podrzędne #FS',
+'associatesubtask' => 'Przypisz ID zadania podrzędnego do tego zadania',
+'subtaskid' => 'ID zadania podrzędnego',
+'subtaskalreadyhasparent' => 'Zgłoszenie podrzędne może mieć tylko jedno zgłoszenie nadrzędne. Aby ustawić inne, usuń poprzednie.',
+'subtaskisparent' => 'Nie można ustawić zgłoszenia podrzędnego, ponieważ jest ono ustawione jako nadrzędne.',
+'subtasknotexist' => 'Wybrane zgłoszenie podrzędne nie istnieje.',
+'subtaskremovedmsg' => 'Zgłoszenie podrzędne zostało pomyślnie usunięte',
+'subtaskadded' => 'Zadanie podrzędne dodane',
+'subtaskremoved' => 'Zadanie podrzędne usunięte',
+'addnewsubtask' => 'Dodaj zgłoszenie podrzędne',
+'hidesubtasks' => 'Ukryj zgłoszenie podrzędne',
+'voteforthistask' => 'Głosuj na to zgłoszenie',
+'watchthistask' => 'Śledź to zgłoszenie',
+'privatethistask' => 'Oznacz jako zgłoszenie prywatne',
+'adddependenttask' => 'Przypisz zgłoszenie zależne',
+'associatetaskid' => 'ID zgłoszenia podrzędnego',
+'parenttaskid' => 'ID zgłoszenia nadrzędnego',
+'invalidsupertaskid' => 'ID Super-Zgłoszenia nieprawidłowy.',
+'supertaskadded' => 'Super-Zgłoszenie dodane',
+'effort' => 'Czas pracy',
+'efforttracking' => 'Åšledzenie czasu pracy',
+'useeffort' => 'Projekt używa śledzenia czasu prac',
+'estimated_effort' => 'Przewidziany czas pracy',
+'totalestimatedeffort' => 'Całkowity przewidziany czas prac',
+'actualeffort' => 'Zrealizowany czas pracy',
+'starteffort' => 'Zacznij śledzenie',
+'endeffort' => 'Zakończ śledzenie',
+'cleareffort' => 'Wyczyść śledzenie',
+'addeffort' => 'Dodaj czas pracy',
+'manualeffort' => 'Dodaj czas pracy ręcznie (H:M)',
+'efforttrackingstarted' => 'Śledzenie czasu prac zlecone dla tego zgłoszenia.',
+'efforttrackingnotstarted'=> 'Śledzenia czasu prac niemożliwe dla zgłoszeń już śledzonych',
+'efforttrackingstopped' => 'Śledzenie czasu prac zakończone dla tego zgłoszenia.',
+'efforttrackingcancelled' => 'Śledzenie czasu prac anulowane dla tego zgłoszenia.',
+'efforttrackingadded' => 'Ręczny czas pracy dodany do zgłoszenia.',
+'trackinginprogress' => 'Åšledzenie czasu pracy w toku',
+'vieweffort' => 'Można przeglądać śledzenie czasu pracy',
+'trackeffort' => 'Można śledzić czasu pracy',
+'invalideffort' => 'Błędnie wybrany czas pracy. Wpisz liczbę.',
+'showpass' => 'Pokaż hasło',
+'chooseafile' => 'Proszę wybrać plik!',
+'incorrectfiletype' => 'Niewłaściwy typ pliku. Dozwolone: JPG, JPEG, GIF, PNG.',
+'oauthreqpass' => 'Nie potrzeba hasła. Zarejestrowano przez %s',
+'addmultipletasks' => 'Dodaj wiele zgłoszeń',
+'pendingnewuserrequest' => 'OczekujÄ…ce rejestracje',
+'adminrequestswaiting' => 'OczekujÄ…ce rejestracje administratora',
+'clicktoedit' => 'Kliknij pola dla szybkiej edycji',
+'confirmedit' => 'zatwierdź',
+'canceledit' => 'anuluj',
+'regapprovedbyadmin' => 'Rejestracja za akceptacją administratora (brak kodów weryfikacyjnych)',
+'activity' => 'Aktywność',
+'myactivity' => 'Moja aktywność',
+'emailverificationwrong' => 'Nieprawidłowo powtórzony adres e-mail',
+'verifyemailaddress' => 'Potwierdź adres e-mail',
+'hideemails' => 'Ukryj adresy e-mail użytkowników',
+'hidemyemail' => 'Ukryj mój adres e-mail',
+'exporttasklist' => 'Eksportuj listę zgłoszeń'
+);
+?>
diff --git a/lang/pt_br.php b/lang/pt_br.php
new file mode 100644
index 0000000..a307176
--- /dev/null
+++ b/lang/pt_br.php
@@ -0,0 +1,840 @@
+<?php
+/**
+* Esta tradução é atualmente mantida por
+* Felipe Vargas Rigo - felipevr@linuxmail.org
+* Envie um e-mail com suas sugestões!
+* Contribuições:
+* José Ricardo da Silva
+* Nicolás Curti
+* Assegure-se de usar um editor que salve e leia este arquivo em utf-8!
+*/
+
+$translation = array(
+'edituser' => 'Editar usuário',
+'username' => 'Usuário',
+'realname' => 'Nome Verdadeiro',
+'emailaddress' => 'Endereço de Email',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Tipo de Notificação',
+'group' => 'Grupo',
+'accountenabled' => 'Conta Ativada',
+'updatedetails' => 'Atualizar detalhes',
+'setglobally' => 'Esta preferência foi setada globalmente.',
+'usergroupmanage' => 'Gerenciamento de Grupos e Usuários',
+'newuser' => 'Registrar Novo Usuário',
+'newgroup' => 'Criar Novo Grupo',
+'yes' => 'Sim',
+'no' => 'Não',
+'editgroup' => 'Editar Grupo',
+'groupname' => 'Nome do Grupo',
+'description' => 'Descrição',
+'admin' => 'Grupo Admin',
+'opennewtasks' => 'Abrir novas tarefas',
+'modifytasks' => 'Modificar tarefas existentes',
+'addcomments' => 'Adicionar comentários',
+'attachfiles' => 'Anexar arquivos',
+'vote' => 'Votar',
+'groupenabled' => 'Membros podem logar',
+'groupopen' => 'Membros podem logar',
+'tasktypelist' => 'Lista de Tipos de Tarefas',
+'categorylist' => 'Lista de Categorias',
+'oslist' => 'Lista de Sistemas Operacionais',
+'resolutionlist' => 'Lista de Resoluções',
+'versionlist' => 'Lista de Versões',
+'severitylist' => 'Lista de Severidades',
+'listnote' => 'Nota: Desmarcando a caixa "exibir" pode modificar algumas tarefas quando no modo de edição. Alterando o campo "Nome" modificará todas as tarefas com esse nome. Itens que não podem ser apagados ou são protegidos para o correto funcionamento ou estão sendo usados em tarefas.',
+'name' => 'Nome',
+'order' => 'Ordem',
+'back' => 'Voltar',
+'text' => 'Texto',
+'highlight' => 'Destaque',
+'show' => 'Exibir',
+'owner' => 'Proprietário',
+'selectowner' => 'Selecionar Proprietário',
+'update' => 'Atualizar',
+'addnew' => 'Adicionar novo',
+'flysprayprefs' => 'Preferências do Flyspray',
+'projecttitle' => 'Título do Projeto',
+'baseurl' => 'URL base da instalação',
+'replyaddress' => 'Responder ao endereço de e-mail para notificações',
+'themestyle' => 'Tema / Estilo',
+'language' => 'Idioma',
+'anonview' => 'Permitir usuários anônimos ver as tarefas',
+'allowanon' => 'Permitir usuários anônimos criar novas tarefas',
+'never' => 'Nunca',
+'anonymously' => 'Anonimamente',
+'afterregister' => 'Somente após cadastro',
+'spamproof' => 'Ativar código de confirmação para registro de novos usuários',
+'anongroup' => 'Grupo para registro de novos usuários',
+'groupassigned' => 'Membros destes grupos podem receber tarefas',
+'forcenotify' => 'Forçar notificações de tarefa como',
+'neversend' => 'Nunca enviar',
+'userchoose' => 'Deixe cada usuário escolher',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Proprietário de categoria padrão',
+'noone' => 'Ninguém',
+'jabbernotify' => 'Notificações do Jabber',
+'jabberserver' => 'Servidor',
+'jabberport' => 'Porta',
+'jabberuser' => 'Usuário da conta',
+'jabberpass' => 'Senha da conta',
+'saveoptions' => 'Salvar Opções',
+'editcomment' => 'Editar Comentário',
+'commentby' => 'Comentário por',
+'saveeditedcomment' => 'Salvar comentário editado',
+'projectprefs' => 'Preferências do Projeto',
+'pagetitle' => 'Título da Página',
+'defaultproject' => 'Projeto Padrão',
+'projectlists' => 'Listas de Projeto',
+'showlogo' => 'Exibir a imagem do logo do título',
+'intromessage' => 'Mensagem introdutória',
+'isactive' => 'Projeto está ativo',
+'createproject' => 'Criar um novo Projeto',
+'nopermission' => 'Você não tem permissão para usar essa página.',
+'listordertip' => 'A ordem em que esses itens irão aparecer na lista',
+'listshowtip' => 'Exibir este item na lista',
+'categoryownertip' => 'Esta pessoa irá receber notificações quando uma tarefa nesta categoria abrir',
+'categoryparenttip' => 'Categoria pai a qual esta nova irá pertencer',
+'notsubcategory' => 'Nenhuma (categoria principal)',
+'showinlineimages' => 'Exibir imagens anexadas na mesma tela',
+'dateformat' => 'Formato da Data',
+'dateformat_extended' => 'Formato da Data por extenso',
+'cache_feeds' => 'Cache de feeds',
+'no_cache' => 'Sem fazer cache',
+'cache_disk' => 'Cache no disco',
+'cache_db' => 'Cache no BD',
+'subcategoryof' => 'Subcategoria de',
+'visiblecolumns' => 'Colunas exibidas na lista de tarefas',
+'tense' => 'Tempo',
+'listtensetip' => 'Passado, Presente ou Futuro',
+'past' => 'Passado',
+'present' => 'Presente',
+'future' => 'Futuro',
+'oldpass' => 'Senha antiga',
+'nooldpass' => 'Nenhuma senha antiga foi definida',
+'oldpasswrong' => 'Senha antiga está errada',
+'changepass' => 'Alterar senha',
+'confirmpass' => 'Confirmar senha',
+'projectmanager' => 'Gerente de Projeto',
+'viewtasks' => 'Ver tarefas',
+'modifyowntasks' => 'Modificar suas tarefas',
+'modifyalltasks' => 'Modificar tarefas que não são do usuário',
+'viewcomments' => 'Ver comentários',
+'editcomments' => 'Editar comentários',
+'deletecomments' => 'Apagar comentários',
+'viewattachments' => 'Ver anexos',
+'createattachments' => 'Criar anexos',
+'deleteattachments' => 'Apagar anexos',
+'viewhistory' => 'Ver histórico',
+'closeowntasks' => 'Fechar suas tarefas',
+'closeothertasks' => 'Fechar tarefas que não são do usuário',
+'assigntoself' => 'Atribuir as tarefas para si caso ainda não tenham sido atribuidas',
+'assignotherstoself' => 'Atribuir outras tarefas para si',
+'viewreports' => 'Ver log de eventos',
+'othersview' => 'Permitir a qualquer um ver este projeto',
+'usersandgroups' => 'Usuários e Grupos',
+'globalgroup' => 'Grupo Global',
+'globalgroups' => 'Grupos Globais',
+'defaultglobalgroup' => 'Grupo global padrão para novos usuários',
+'addtogroup' => 'Adicionar ao grupo',
+'moveuserstogroup' => 'Mover usuários para o grupo',
+'nogroup' => 'Nenhum Grupo - Remover do projeto',
+'eventdesc' => 'Descrição do Evento',
+'requestedby' => 'Requisitado por',
+'daterequested' => 'Data de requisição',
+'closetask' => 'Fechar Tarefa',
+'reopentask' => 'Re-abrir Tarefa',
+'applymember' => 'Aplicar para membros do Projeto',
+'forcurrentproj' => 'Para o projeto atual',
+'lostpw' => 'Recuperar senha perdida',
+'lostpwexplain' => 'Entre com seu Login para enviar um atalho para alterar sua senha. Será enviado para o endereço de notificação do seu perfil.',
+'sendlink' => 'Enviar atalho',
+'savenewpass' => 'Salvar nova senha',
+'anonreg' => 'Permitir cadastro de novos usuários',
+'allowanonopentask' => 'Permitir usuários anônimos abrir novas tarefas',
+'editglobalgroup' => 'Editar Grupo Global',
+'editgroupforproj' => 'Editar grupo para o Projeto',
+'notshownforadmin' => 'Permissões não são exibidas para o grupo admin. Você não precisa editar elas.',
+'general' => 'Geral',
+'userregistration' => 'Cadastro de Usuário',
+'notifications' => 'Notificações',
+'resetoptions' => 'Redefinir Opções',
+'preferences' => 'Preferências',
+'tasktypes' => 'Tipos de Tarefa',
+'resolutions' => 'Resoluções',
+'categories' => 'Categorias',
+'operatingsystems' => 'Sistemas Operacionais',
+'versions' => 'Versões',
+'admintoolboxlong' => 'Ferramentas administrativas',
+'newproject' => 'Novo Projeto',
+'delete' => 'Apagar',
+'listdeletetip' => 'Apagar este item da lista',
+'lookandfeel' => 'Aparência',
+'globaltheme' => 'Tema Global',
+'emailnotify' => 'Notificações por Email',
+'fromaddress' => 'Do Endereço',
+'smtpserver' => 'Servidor SMTP',
+'smtpuser' => 'Usuário SMTP',
+'smtppass' => 'Senha SMTP',
+'addrewrite' => 'Use reescrita de endereço',
+'usereminderdaemon' => 'Ativar lembretes',
+'tasksperpage' => 'Tarefas por página da lista de tarefas',
+'addtoassignees' => 'Atribuir a mim',
+'taskstatuses' => 'Estados das Tarefas',
+'canvote' => 'Pode votar para tarefas',
+'loginsuccessful' => 'Conectado com sucesso.',
+'youareloggedout' => 'Você foi desconectado.',
+'waitwhiletransfer' => 'Por favor espere enquanto é transferido...',
+'clicknowait' => 'Clique aqui se não quiser esperar.',
+'accountdisabled' => 'Sua conta está desativada. Contate um administrador.',
+'task' => 'Tarefa',
+'edittask' => 'Editar esta tarefa',
+'openedby' => 'Aberto por',
+'editedby' => 'último editor',
+'tasktype' => 'Tipo de Tarefa',
+'category' => 'Categoria',
+'status' => 'Estado',
+'assignedto' => 'Atribuido a',
+'operatingsystem' => 'Sistema Operacional',
+'severity' => 'Severidade',
+'reportedversion' => 'Reportado na Versão',
+'dueinversion' => 'Devido a Versão', //REVIEW - REVER
+'undecided' => 'Não decidido',
+'percentcomplete' => 'Percentual Completo',
+'details' => 'Detalhes',
+'savedetails' => 'Salvar Detalhes',
+'canceledit' => 'Cancelar Edição',
+'anonymous' => 'Envio Anônimo',
+'complete' => 'completo',
+'closedby' => 'Fechado por',
+'reasonforclosing' => 'Motivo de fechamento:',
+'reopenthistask' => 'Re-abrir esta tarefa',
+'comments' => 'Comentários',
+'attachments' => 'Anexos',
+'relatedtasks' => 'Tarefas Relacionadas',
+'edit' => 'Editar',
+'addcomment' => 'Adicionar comentário',
+'fileuploadedby' => 'Arquivo enviado por',
+'uploadafile' => 'Anexar arquivo',
+'uploadnow' => 'Enviar agora!',
+'thesearerelated' => 'Estas tarefas são relacionadas a essa tarefa',
+'remove' => 'Remover',
+'addnewrelated' => 'Adicionar uma nova tarefa relacionada',
+'add' => 'Adicionar!',
+'otherrelated' => 'Outras tarefas que estão relacionadas a esta',
+'receivenotify' => 'Estes usuários receberão informações detalhadas quando esta tarefa for alterada.',
+'addusertolist' => 'Adicionar usuário para esta lista',
+'addtolist' => 'Adicionar a lista',
+'addmyself' => 'Me adicionar a lista',
+'removemyself' => 'Me Remover da lista',
+'theseusersnotify' => 'Estes usuários receberão informações detalhadas quando esta tarefa for alterada.',
+'attachedtoproject' => 'Anexado ao Projeto',
+'reminders' => 'Lembretes',
+'system' => 'Sistema',
+'remindthisuser' => 'Lembrar este usuário',
+'thisoften' => 'Freqüência',
+'startafter' => 'Espere antes de iniciar os lembretes',
+'hours' => 'Hora(s)',
+'days' => 'Dia(s)',
+'weeks' => 'Semana(s)',
+'addreminder' => 'Adicionar Lembrete',
+'defaultreminder' => 'Este é um lembrete a seguinte tarefa do Flyspray:',
+'message' => 'Mensagem',
+'closed' => 'Fechado',
+'filename' => 'Nome do arquivo:',
+'date' => 'Data',
+'filesize' => 'Tamanho do arquivo:',
+'closurecomment' => 'Comentários adicionais sobre o fechamento:',
+'history' => 'Histórico',
+'nohistory' => 'Sem histórico disponível.',
+'eventdate' => 'Data',
+'user' => 'Usuário',
+'event' => 'Evento',
+'fieldchanged' => 'Campo modificar',
+'taskopened' => 'Tarefa aberta',
+'taskreopened' => 'Tarefa re-aberta',
+'taskclosed' => 'Tarefa fechada',
+'commentadded' => 'Comentário adicionado',
+'commentedited' => 'Comentário editado',
+'commentdeleted' => 'Comentário apagado',
+'attachmentadded' => 'Anexo adicionado',
+'attachmentdeleted' => 'Anexo apagado',
+'taskedited' => 'Detalhes da tarefa editados',
+'notificationadded' => 'Usuário adicionado a lista de notificação',
+'notificationdeleted' => 'Usuário removido da lista de notificação',
+'relatedadded' => 'Tarefa relacionada adicionada',
+'relateddeleted' => 'Tarefa relacionada removida',
+'taskassigned' => 'Tarefa atribuida a',
+'taskreassigned' => 'Tarefa reatribuida a',
+'assignmentremoved' => 'Atribuição removida',
+'summary' => 'Resumo',
+'addedasrelated' => 'Tarefa adicionada a lista relacionada a',
+'deletedasrelated' => 'Tarefa removida a lista relacionada a',
+'reminderadded' => 'Lembrete adicionar',
+'reminderdeleted' => 'Lembrete removido',
+'priority' => 'Prioridade',
+'previousvalue' => 'Valor anterior',
+'newvalue' => 'Novo valor',
+'selectareason' => 'Selecionar um motivo',
+'assigntome' => 'Atribuir a mim',
+'reopenrequest' => 'Requisitar re-abertura',
+'requestclose' => 'Requisitar fechamento',
+'ownershiptaken' => 'Usuário se apropriou',
+'closerequestmade' => 'Requisitado fechamento da tarefa',
+'reopenrequestmade' => 'Requisitado re-abertura da tarefa',
+'taskdependson' => 'Esta tarefa depende de',
+'taskblocks' => 'Esta tarefa bloqueia estes de fechar',
+'depadded' => 'Adicionado dependência',
+'depaddedother' => 'Esta tarefa foi adicionada como dependência de',
+'depremoved' => 'Dependência removida',
+'depremovedother' => 'Esta tarefa foi removida da lista de dependências de outra tarefa',
+'showdetailserror' => 'Esta tarefa não existe ou você não tem permissão para vê-la.',
+'makeprivate' => 'tornar privado',
+'makepublic' => 'tornar publico',
+'taskmadeprivate' => 'Tarefa tornada privada',
+'taskmadepublic' => 'Removido privacidade - tarefa tornada pública',
+'confirmdeletecomment' => 'Realmente apagar este comentário? %s',
+'confirmdeleteattach' => 'Realmente apagar este anexo?',
+'selectedhistory' => 'Exibindo os detalhes do histórico selecionado',
+'showallhistory' => 'Exibir aba de histórico completo novamente',
+'hidethis' => 'Esconder novamente',
+'mark100' => 'Marcar tarefa como 100% completa',
+'watchtask' => 'Acompanhar tarefa',
+'stopwatching' => 'Parar de acompanhar',
+'commentlink' => 'Atalho para este comentário',
+'submitreq' => 'Enviar pedido',
+'reasonforreq' => 'Motivo para o pedido',
+'pmreqdenied' => 'Pedido de Gerencia de Projeto negado',
+'taskpendingreq' => 'Ação pendente ao Gerente de Projeto. Veja a aba Histórico para detalhes.',
+'previoustask' => 'Tarefa Anterior',
+'nexttask' => 'Próxima tarefa',
+'duedate' => 'Data de Término',
+'attachnoperms' => 'Há anexos com este comentário, mas você não tem permissão para vê-los.',
+'open' => 'Abrir',
+'depgraph' => 'Ver gráfico de Dependência',
+'reset' => 'Desfazer',
+'selectusers' => 'Selecionar Usuários...',
+'addmetoassignees' => 'Adicionar-me à lista de atribuidos', //review - rever
+'addedtoassignees' => 'Usuário adicionado a lista de atribuidos', //review - rever
+'dependencygraph' => 'Gráfico de Dependência',
+'attachanotherfile' => 'Anexar outro arquivo',
+'OK' => 'OK',
+'addvote' => 'Adicionar Voto',
+'notifyfromfs' => 'Notificação do Flyspray',
+'autogenerated' => 'ESTA É UMA MENSAGEM AUTOMÃTICA, NÃO RESPONDA',
+'forward' => 'Repassar',
+'previous' => 'Anterior',
+'next' => 'Próximo',
+'first' => 'Primeiro',
+'last' => 'Último',
+'page' => 'Página %d de %d',
+'search' => 'Procurar',
+'alltasktypes' => 'Todos os tipos de tarefas',
+'allseverities' => 'Todas as Severidades',
+'alldevelopers' => 'Todos Desenvolvedores',
+'notyetassigned' => 'Ainda não atribuido',
+'allcategories' => 'Todas Categorias',
+'allstatuses' => 'Todos Estados',
+'allopentasks' => 'Todas as tarefas abertas',
+'sortthiscolumn' => 'Ordenar por esta coluna',
+'id' => 'ID',
+'project' => 'Projeto',
+'dateopened' => 'Aberto',
+'progress' => 'Progresso',
+'searchthisproject' => 'Pesquisar no projeto por',
+'dueanyversion' => 'Terminar em qualquer Versão',
+'anyversion' => 'Reportado em qualquer Versão',
+'dueversion' => 'Terminar na Versão',
+'lastedit' => 'Última Edição',
+'os' => 'Sistema Operacional',
+'reportedin' => 'Reportado Em',
+'taskrange' => 'Exibindo tarefas %d - %d de %d',
+'noresults' => 'Sua pesquisa não retornou resultados.',
+'takeaction' => 'Agir',
+'watchtasks' => 'Acompanhar as tarefas selecionadas',
+'stopwatchingtasks' => 'Parar de acompanhar as tarefas selecionadas',
+'assigntaskstome' => 'Atribuir a tarefa selecionada para mim',
+'dueby' => 'Terminado por',
+'dueanytime' => 'Terminado qualquer hora',
+'selectduedate' => 'Seleciona Data de Término',
+'toggleselected' => 'Toggle Selecionado', //review - rever
+'due' => 'Terminado',
+'assignedtome' => 'Atribuido a mim',
+'tasklist' => 'Lista de tarefas',
+'dateclosed' => 'Fechado Data',
+'advanced' => 'Avançado',
+'searchcomments' => 'Procurar nos comentários',
+'searchforall' => 'Procurar por todas palavras',
+'anonusers' => 'Usuários anônimos',
+'miscellaneous' => 'Variados',
+'users' => 'Usuários',
+'taskproperties' => 'Propriedades da Tarefa',
+'selectsincedate' => 'Selecionar Alterado desde',
+'changedsince' => 'Alterado desde',
+'updatefs' => 'Por favor atualize o Flyspray.',
+'currentversion' => 'Sua versão atual é',
+'latestversion' => 'e a última versão é',
+'hidemessage' => '(lembrar mais tarde)',
+'saveas' => 'Salvar pesquisa como',
+'nosearches' => 'Sem pesquisas salvas',
+'saving' => 'Salvando...',
+'votes' => 'Votos',
+'allclosedtasks' => 'Todas as Tarefas Fechadas',
+'password' => 'Senha',
+'login' => 'Conectar-se!',
+'rememberme' => 'Me lembrar',
+'lostpassword' => 'Esqueceu a senha?',
+'lostpwforfs' => 'Senha esquecida para o Flyspray',
+'lostpwmsg1' => 'Ola.
+
+Eu perdi minha senha do Flyspray em ',
+'lostpwmsg2' => ', por favor me envie uma nova senha.
+
+Usuário: ',
+'regards' => '
+
+Atenciosamente,',
+'yourusername' => ' seu nome de usuário ',
+'locale' => 'pt-BR',
+'filenotexist' => 'Arquivo não existe, ou você não tem permissão para acessa-lo.',
+'showtask' => 'Exibir Tarefa',
+'now' => 'Agora',
+'go' => 'Vai!',
+'opentaskanon' => 'Abrir uma nova Tarefa anonimamente',
+'register' => 'Registrar',
+'addnewtask' => 'Adicionar nova tarefa',
+'reports' => 'Log de Eventos',
+'editmydetails' => 'Editar meus detalhes',
+'logout' => 'Sair',
+'disabledaccount' => 'Sua conta foi desativada!<br>Desconectando você...',
+'poweredby' => 'Turbinado por Flyspray',
+'projects' => 'Projetos',
+'allprojects' => 'Todos Projetos',
+'selectproject' => 'para Projeto:',
+'tasksall' => 'Todas as tarefas',
+'tasksassigned' => 'Tarefas atribuidas a mim',
+'tasksreported' => 'Tarefas reportadas a mim',
+'taskswatched' => 'Tarefas acompanhadas',
+'mysearch' => 'Minhas Buscas',
+'admintoolbox' => 'Opções administrativas',
+'manageproject' => 'Gerenciar Projeto',
+'permissions' => 'Ver Permissões',
+'hide' => 'Esconder',
+'pendingreq' => 'Pedidos em espera',
+'errorpage' => 'Erro tentando acessar a pagina.
+ Talvez você tenha pedido uma tarefa que não existe,
+ ou você não tem permissão para ver esta pagina.<br /><br />
+ Você pode estar tentando usar uma URL inválida
+ ou fazendo o que não deveria.',
+
+'permissionsforproject' => 'Permissiões para ',
+'switchto' => 'Alternar para',
+'lastsearch' => 'Ultima busca',
+'modify' => 'Alterar',
+'noticefrom' => 'Notificação de',
+'hasopened' => 'ABRIU uma tarefa NOVA e atribuiu-lhe a você:',
+'moreinfonew' => 'Você pode encontrar mais informações na página do FlySpray:',
+'newtaskcategory' => 'Uma nova Tarefa do FlySpray foi aberta nesta categoria',
+'categoryowner' => 'Você está recebendo esta mensagem porque está listado como dono da categoria.',
+'tasksummary' => 'Sumário de tarefas:',
+'newtaskadded' => 'Sua nova Tarefa foi adicionada.',
+'summaryanddetails' => 'Você precisa preencher o sumario e os detalhes.',
+'goback' => 'Voltar.',
+'messagefrom' => 'Esta é uma mensagem do sistema de rastreamento de bugs FlySpray',
+'hasjustmodified' => 'modificou a seguinte Tarefa.',
+'changedfields' => 'campos alterados estão precedidos por asteriscos (**)',
+'moreinfomodify' => 'Mais informações sobre esta Tarefa na URL:',
+'nolongerassigned' => 'A seguinte Tarefa não está mais atribuida a você. Ela agora está atribuida a mim',
+'hasassigned' => 'Atribuiu a você a seguinte Tarefa do FlySpray:',
+'taskupdated' => 'Tarefa atualizada.',
+'hasclosedassigned' => 'fechou a seguinte tarefa Flyspray que foi atribuida a você:',
+'unassigned' => 'não atribuida',
+'hasclosed' => 'fechou a seguinte tarefa.',
+'youonnotify' => 'Você está recebendo este porque você está na lista da notificação.',
+'taskclosed' => 'Tarefa foi fechada.',
+'returntotask' => 'Voltar para detalhes da tarefa',
+'backtoindex' => 'Voltar para a lista de tarefas',
+'noclosereason' => 'Você não selecionou nenhum motivo para fechar esta tarefa.',
+'hasreopened' => 'reabriu a seguinte tarefa do Notes que você fechou:',
+'taskreopened' => 'Tarefa foi reaberta.',
+'backtotask' => 'Voltar para a tarefa.',
+'commentaddedmsg' => 'O comentario foi adicionado.',
+'commenttoassigned' => 'adicionou um comentário a uma tarefa que foi atribuído:',
+'commenttotask' => 'adicionou o seguinte comentário a esta tarefa.',
+'nocommententered' => 'Você realmente deve inserir um comentário antes de clicar a tecla enviar.',
+'fillinfields' => 'Você não preencheu todos os campos.',
+'notcurrentpass' => 'Aquela não é sua senha atual!',
+'passchanged' => 'Sua senha foi alterada.',
+'closewindow' => 'Agora você já pode fechar esta janela.',
+'passnomatch' => 'nossas senhas novas não eram as mesmas!',
+'usernametaken' => 'Esse usuário já está cadastrado. Você precisa escolher outro nome para acesso.',
+'newusercreated' => 'Foi criada nova conta de usuário.',
+'accountcreated' => 'Sua conta foi criada.',
+'newuserwarning' => 'Algumas funções são desabilitadas para usuários comuns e as mesmas, requerem que seu usuário seja aprovado por um dos administradores. Se você não puder iníciar uma sessão, provavelmente é por que você ainda não possui estes direitos de acesso liberado por um dos administradores.',
+'nomatchpass' => 'As senhas não combinaram.',
+'confirmwrong' => 'Código de confirmação está incorreto!',
+'formnotcomplete' => 'Dê forma que não foi preenchido completamente.',
+'formnotnumeric' => 'O dado inserido não é numérico!',
+'groupnametaken' => 'O Nome do grupo já está em uso.',
+'newgroupadded' => 'Novo grupo adicionado.',
+'optionssaved' => 'Notes Opções salvas.',
+'hasuploaded' => 'enviou um arquivo em anexo a uma tarefa que lhe foi atribuída:',
+'hasattached' => 'enviou um arquivo à seguinte tarefa.',
+'filename' => 'Arquivo:',
+'fileuploaded' => 'Arquivo foi enviado.',
+'contactadmin' => 'Contacte o Administrador deste projeto.',
+'selectfileerror' => 'Você não selecionou um arquivo e/ou não incorporou uma descrição.',
+'userupdated' => 'Detalhes do usuário foi atualizado',
+'realandemail' => 'Você não preencheu os campos reais do nome e do endereço de email.',
+'groupupdated' => 'Definição de grupo atualizada.',
+'groupanddesc' => 'Você não preencheu os campos de nome e de descrição de grupo.',
+'fillallfields' => 'Preencha todos os campos.',
+'listPmustN' => 'O campo "Ordem" deve ser numérico.',
+'listupdated' => 'A lista foi atualizada.',
+'listitemadded' => 'O artigo novo da lista adicionou.',
+'relatedaddedmsg' => 'Tarefa relacionada adicionada à lista.',
+'relatederror' => 'Esse usuário está já na lista da notificação para essa tarefa.',
+'relatedremoved' => 'Tarefa relacionada removida da lista.',
+'notifyadded' => 'Usuário adicionado para lista de notificações.',
+'notifyerror' => 'Esse usuário está já na lista da notificação para essa tarefa.',
+'notifyremoved' => 'Usuário removido da lista de notificação.',
+'editcommentsaved' => 'O comentário foi salvo e atualizado.',
+'commentdeletedmsg' => 'O comentário foi deletado.',
+'gotonewtask' => 'Ir para a nova tarefa que você acabou de criar',
+'projectcreated' => 'nosso novo Projeto foi criado. Siga o link abaixo para configurar categoria, sistema operacional e listas de versões.',
+'customiseproject' => 'Customisar este projeto',
+'projectupdated' => 'Preferências do projeto atualizadas',
+'emptytitle' => 'Você deixou em branco o campo do título do projeto. Volte e adicione um título para este projeto.',
+'loginbelow' => 'Seu cadastro foi concluido com sucesso. Agora, você já pode acessar o site usando o formulário abaixo.',
+'attachmentdeletedmsg' => 'O arquivo foi excluído',
+'reminderaddedmsg' => 'Seu lembrete foi adicionado.',
+'reminderdeletedmsg' => 'O lembrete selecionado foi apagado.',
+'flyspraytask' => 'Tarefa do Flyspray',
+'fieldsmissing' => 'Alguns campos estão vazios ou possuem dados inválidos.',
+'relatedinvalid' => 'Esta tarefa não existe.',
+'relatedproject' => 'Esta tarefa pertence a outro projeto. Relacioná-las mesmo assim?',
+'addanyway' => 'Adicionar de qualquer jeito',
+'cancel' => 'Cancelar',
+'alreadyedited' => 'Esta tarefa foi editada por outra pessoa antes de você salvar. Você deseja sobreescrever as alterações?',
+'saveanyway' => 'Salve minhas alterações mesmo assim',
+'nouserselected' => 'Nenhum usuário selecionado. Selecione pelo menos um usuário antes de tentar novamente.',
+'groupswitchupdated' => 'Grupos de usuário alterados com sucesso.',
+'takenownershipmsg' => 'Esta tarefa agora está atribuida a você.',
+'adminrequestmade' => 'Sua requisição foi enviada para um Gerente de Projeto.',
+'newdepnotify' => 'Uma nova depenência foi adicionada à seguinte tarefa:',
+'dependadded' => 'Dependência entre tarefas adicionada.',
+'dependaddfailed' => 'Ocorreu um erro ao tentar adicionar a dependência. Verifique se a tarefa existe e não há bloqueio mútuo.',
+'depremovedmsg' => 'Dependência entre tarefas removida',
+'newdepis' => 'A nova dependência é',
+'magicurlsent' => 'Uma mensagem foi enviada para seu endereço de notificação. Ela contém um atalho que levará você a pagina que completa esta tarefa.',
+'changefspass' => 'Alterar senha do Flyspray',
+'magicurlmessage' => 'Por favor abre o seguinte atalho para alterar a sua senha do Flyspray:',
+'erroronform' => 'Ocorreu um problema com o envio do seu formulário',
+'addressused' => 'Este endereço foi usado para cadastro numa conta no Flyspray. Se você não esperava esta mensagem, por favor ignore e apague. Vá para o seguinte endereço para completar seu cadastro:',
+'confirmcodeis' => 'Seu código de confirmação é:',
+'codesent' => 'Seu código de confirmação foi enviado. Por favor siga as instruções na mensagem.',
+'codenotsent' => 'Não foi possível enviar seu código, por favor tente novamente',
+'taskmadeprivatemsg' => 'Esta tarefa tornou-se privada',
+'taskmadepublicmsg' => 'Esta tarefa tornou-se publica novamente',
+'realandnotify' => 'Você precisa preencher seu nome verdadeiro, e o endereço de E-mail ou seu Jabber ID.',
+'pmreqdeniedmsg' => 'Gerência de Projeto negada',
+'massopsuccess' => 'Operações executadas com sucesso - onde havia permissão',
+'usernotexist' => 'Esse usuário não existe neste Flyspray',
+'commentattachperms' => 'Você não pode apagar este comentário - sem permissão para apagar anexos',
+'voterecorded' => 'Seu voto foi registrado',
+'votefailed' => 'Seu voto não pode ser registrado neste momento.',
+'createnewgroup' => 'Criar novo grupo',
+'requiredfields' => 'Todos os campos assinalados são requeridos',
+'addthisgroup' => 'Adicionar ao grupo',
+'createnewproject' => 'Criar um projeto novo',
+'addnewproject' => 'Adicionar um novo projeto',
+'htmlallowed' => 'Código do HTML é reservado',
+'createthisproject' => 'Criar este projeto',
+'inlineimages' => 'Mostrar as imagens em anexo na mesma janela',
+'createnewtask' => 'Criar uma nova tarefa no projeto:',
+'addanother' => 'Adicionar outra tarefa depois desta',
+'addthistask' => 'Adicionar esta tarefa',
+'notifyme' => 'Notificar-me sempre que esta tarefa mudar',
+'newtask' => 'Nova Tarefa',
+'attachafile' => 'Anexar arquivo',
+'registernewuser' => 'Registrar novo usuário',
+'none' => 'Nenhum',
+'registeraccount' => 'Registrar esta conta',
+'both' => 'Ambos',
+'notifyfrom' => 'Notificação de ',
+'donotreply' => 'ESTA é UMA MENSAGEM AUTOMáTICA, NãO RESPONDA.',
+'disclaimer' => 'Você está recebendo esta mensagem porque você requisitou do Sistema de Bugtracking Flyspray. Se você não esperava esta mensagem ou não quer receber e-mails no futuro, você pode alterar suas definições de notificação na URL abaixo.',
+'userwho' => 'Usuário que fez isso',
+'moreinfo' => 'Mais informações podem ser encontradas na seguinte URL:',
+'newtaskopened' => 'Uma nova tarefa do Flyspray foi aberta. Detalhes abaixo.',
+'notify.taskclosed' => 'A seguinte tarefa foi fechada:',
+'notify.taskreopened' => 'A seguinte tarefa foi re-aberta:',
+'newdep' => 'A seguinte tarefa tem uma nova dependência:',
+'notify.depremoved' => 'A seguinte tarefa teve uma dependência removida:',
+'olddepwas' => 'A antiga dependência era',
+'notify.commentadded' => 'A seguinte tarefa tem um novo comentário:',
+'commentis' => 'O comentário está abaixo.',
+'newattachment' => 'Um novo arquivo foi anexado para a seguinte tarefa:',
+'detailsbelow' => 'Os detalhes estão abaixo.',
+'notify.relatedadded' => 'Uma nova relação entre tarefas foi adicionada a seguinte tarefa:',
+'relatedis' => 'A tarefa relacionada é',
+'assignedtoyou' => 'A seguinte tarefa foi atribuida a você:',
+'takenownership' => 'foi designado para a seguinte tarefa:',
+'requiresaction' => 'A seguinte tarefa precisa de moderação pelo Gerente de Projeto:',
+'requiresactionnotify' => 'A tarefa precisa ser moderada por um Gerente de Projeto',
+'pmdeny' => 'Um Gerente de Projeto negou o pedido pendente para a seguinte tarefa:',
+'pmdenynotify' => 'Um Gerente de Projeto negou o pedido',
+'fileaddedtoo' => 'Há um ou mais arquivos anexados a este comentário.',
+'taskwatching' => 'A seguinte tarefa que você estava acompanhando',
+'isdepfor' => 'é uma nova dependência para',
+'denialreason' => 'Motivo para negação',
+'taskchanged' => 'A seguinte tarefa foi alterada. As alterações estão listadas abaixo. Para ver sobre tudo que foi alterado, visite a seguinte URL e clique na aba Histórico.',
+'useraddedtoassignees' => 'Um usuário se adicionou para a lista de usuários atribuidos para esta tarefa.',
+'removeddepis' => 'A dependência removida foi',
+'isnodepfor' => 'não há mais dependência para',
+'usergroups' => 'Grupos de Usuário',
+'pmtoolbox' => 'Ferramentas do Gerente de Projeto',
+'groupmanage' => 'Gerenciar dos Grupos',
+'pendingrequests' => 'Pedidos Pendentes',
+'reasongiven' => 'Motivos dadas',
+'nopendingreq' => 'Não há pedidos pendentes para Gerentes de Projeto.',
+'givereason' => 'Dê uma motivo',
+'catlisted' => 'Editor da Lista de Categorias',
+'oslisted' => 'Editor da Lista de Sistemas Operacionais',
+'verlisted' => 'Editor da Lista de Versões',
+'tasktypeed' => 'Editor da Lista de Tipos de Tarefa',
+'resed' => 'Editor da Lista de Resoluções',
+'deny' => 'Negar',
+'notifiedwhen' => 'Notificar quando',
+'onlynewtasks' => 'Novas tarefas estão abertas',
+'allevents' => 'Qualquer evento ocorrido em qualquer tarefa',
+'feeds' => 'Feeds',
+'feeddescription' => 'Descrição de Feed',
+'feedimgurl' => 'Imagem da URL do Feed (deixe em branco para nenhuma imagem)',
+'notifysubject' => 'Assunto para notificações',
+'notifysubjectinfo' => '(%p = título do projeto, %s = resumo da tarefa, %t = id da tarefa, %a = ação, %u = usuário)',
+'priority6' => 'Altíssima',
+'priority5' => 'Imediatamente',
+'priority4' => 'Urgente',
+'priority3' => 'Alta',
+'priority2' => 'Normal',
+'priority1' => 'Baixa',
+'sendcode' => 'Envie o código!',
+'entercode' => 'Digite o código de confirmação abaixo em sua mensagem de notificação. Também digite a sua senha de usuário desejada.',
+'confirmationcode' => 'Código de confirmação',
+'registererror' => 'Certifique-se de que você preencheu todos os campos obrigatórios e de que você especificou os detalhes corretos para o tipo de notificação desejada.',
+'validusername' => '(apenas caracteres alfanuméricos e - _ . são permitidos)',
+'emailtaken' => 'O email ou ID Jabber já estão em uso. Você terá que escolher um outro.',
+'note' => '<strong>Nota:</strong> Você receberá um código de confirmação antes da criação da sua conta. O código será enviado através do seu método de notificação especificado acima. <br />Se você fornecer dados falso, você <strong>não receberá o seu código</strong>.',
+'changelog' => 'Registro de mudanças',
+'changeloggen' => 'Gerador de registro de mudanças',
+'listfrom' => 'Listar registro de mudanças de',
+'to' => 'para',
+'oldestfirst' => 'Mais antigos antes',
+'recentfirst' => 'Mais novos antes',
+'severityrep' => 'Relatório de severidade',
+'totalopen' => 'Total de tarefas a realizar',
+'age' => 'Idade',
+'agerep' => 'Relatório de idade',
+'eventsrep' => 'Relatório de eventos',
+'events' => 'Eventos',
+'Tasks' => 'Tarefas',
+'opened' => 'Aberto',
+'edited' => 'Modificado',
+'assigned' => 'Atribuído',
+'within' => 'Dentro de',
+'pastday' => 'Ontem',
+'pastweek' => 'Semana passada',
+'pastmonth' => 'Mês passado',
+'pastyear' => 'Ano passado',
+'nolimit' => 'Sem limite',
+'from' => 'Início',
+'duein' => 'Término',
+'selectfromdate' => 'Selecione a data de início',
+'selecttodate' => 'Selecione a data de término',
+'showvoters' => 'Mostrar/esconder eleitores',
+'roadmap' => 'Plano de execução',
+'roadmapfor' => 'Plano de execução para a versão',
+'tasks' => 'tarefas',
+'completed' => 'completas.',
+'opentasks' => 'tarefas em aberto',
+'of' => '% de',
+'severity5' => 'Crítica',
+'severity4' => 'Alta',
+'severity3' => 'Média',
+'severity2' => 'Baixa',
+'severity1' => 'Muito baixa',
+'Redirect' => 'Redirecionar',
+'redirectmsg' => 'Se o seu browser não suporta redirecionamento, clique %sAQUI%s para ser redirecionado.',
+'allowclosedcomments' => 'Permitir comentários em tarefas já executadas',
+'comment' => 'Comente',
+'editowncomments' => 'Editar os seus comentários',
+'reopened' => 'Reabertos',
+'loading' => 'Carregando...',
+'notifyown' => 'Notificar sobre suas modificações',
+'youremail' => 'O seu email',
+'thankyouforbug' => 'Obrigado por reportar o seu problema. Você pode ver a tarefa e observer o seu progresso a qualquer hora através da seguinte URL:',
+'anonuser' => 'Usuário anônimo',
+'conflict' => 'Conflito',
+'file' => 'Arquivo',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Tamanho',
+'projectgroup' => 'Grupo do projeto',
+'profile' => 'Perfil:',
+'viewprofile' => 'Ver perfil',
+'regdate' => 'Registrado desde',
+'tasksopened' => 'Tarefas em aberto',
+'replyto' => 'Responder para',
+'notifytypes' => 'Tipos de notificação',
+'pm.taskchanged' => 'Tarefa modificada',
+'pm.taskreopened' => 'Tarefa reaberta',
+'pm.depadded' => 'Dependência adicionada',
+'pm.depremoved' => 'Dependência removida',
+'pmrequest' => 'Requisição de Gerência de Projeto',
+'pmrequestdenied' => 'Requisição de Gerência de Projeto negada',
+'newassignee' => 'Nova atribuição',
+'revdepadded' => 'Dependência reversa adicionada',
+'revdepaddedremoved' => 'Dependência reversa removida',
+'assigneeadded' => 'Atribuição adicionada',
+'addusergroup' => 'Adicionar usuário a este grupo',
+'groupmembers' => 'Membros do grupo',
+'deleteuser' => 'Apagar este usuário',
+'userdeleted' => 'Usuário apagado',
+'autoassign' => 'Auto atribuir uma tarefa ao dono da categoria',
+'ssl' => 'SSL',
+'updatewrong' => 'Você tem a verificação de atualizações habilitada, mas um erro ocorroeu ao tentar
+ acessar o servidor de atualizações, ou o seu host não permite acesso a conexões externas,
+ ou o erro foi causado por uma falha na rede.
+ Por gentileza, visite o site do FlySpray para assegurar-se de que está utilizando a última versão.',
+'deleteproject' => 'Apagar este projeto e mover seus dados para',
+'projectdeleted' => 'Projeto apagado com sucesso',
+'feedforall' => 'Fornecido para todos os projetos',
+'usercreated' => 'Usuário criado',
+'userdeleted' => 'Usuário apagado',
+'created' => 'Criado',
+'deleted' => 'Apagado',
+'userid' => 'Identificador de usuário',
+'editassignments' => 'Editar atribuições',
+'preview' => 'Pré-visualizar',
+'anyprogress' => 'Qualquer progresso',
+'tasksrelated' => 'Tarefas relacionadas a esta',
+'duplicatetasks' => 'Tarefas duplicadas desta',
+'databasemodfailed' => 'A modificação na base de dados falhou. Uma das possíveis razões é falta de permissão.',
+'frequency' => 'Frequência',
+'newuserregistered' => 'Um novo usuário se registrou na sua instalação do FlySpray. O detalhes do usuário são os seguintes:',
+'newuserregisterednotify' => 'Um novo usuário se registrou',
+'notify_registration' => 'Notificar administradores sobre o registro de novos usuários',
+'textversion' => 'Versão texto',
+'onlyprimary' => 'Tarefas não bloqueiam outras tarefas',
+'switch' => 'Trocar',
+'max' => 'máx.',
+'dates' => 'Datas',
+'selectduedatefrom' => 'Terminam a partir de',
+'selectduedateto' => 'até',
+'selectsincedatefrom' => 'Modificadas a partir de',
+'selectsincedateto' => 'até',
+'selectdate' => 'Selecione a data',
+'selectopenedfrom' => 'Abertas a partir de',
+'selectopenedto' => 'até',
+'selectclosedfrom' => 'Fechadas a partir de',
+'selectclosedto' => 'até',
+'startat' => 'Inicia-se em',
+'hasattachment' => 'Possui anexo',
+'private' => 'Privada',
+'watching' => 'Acompanhando',
+'alreadyvotedthistask' => 'você votou para esta tarefa',
+'alreadyvotedthisday' => 'já votada hoje',
+'visibility' => 'Visibilidade',
+'public' => 'Pública',
+'leaveemptyauto' => 'Deixe os campos de senha em branco se desejar que a senha seja gerada automaticamente.',
+'novalidemail' => 'Você não forneceu um email válido.',
+'novalidjabber' => 'Você não forneceu um endereço Jabber válido.',
+'missingrequired' => 'Você não preencheu todos os campos obrigatórios.',
+'entervalidusername' => 'Por gentileza, especifique um usuário válido em um nome real.',
+'couldnotaddusernotif' => 'Não foi possível adicionar este usuário à lista de notificações.',
+'defaulttask' => 'Descrição de tarefa padrão',
+'all' => 'todas',
+'events.useraddedtoassignees' => 'Usuário adicionado aos atribuidos',
+'vote(s)' => 'voto(s)',
+'eventlog' => 'Histórico de eventos',
+'assignmentchanged' => 'Atribuição modificada',
+'detailedinfo' => 'Informações detalhadas',
+'All' => 'Todas',
+'tasksireported' => 'Tarefas que você reportou',
+'recentlyopened' => 'Abertas recentemente',
+'stats' => 'Estatísticas',
+'totaltasks' => 'total de tarefas',
+'mostwanted' => 'Tarefas mais requisitadas',
+'defaultentry' => 'Página de entrada padrão',
+'toplevel' => 'Visão do nível mais alto',
+'overview' => 'Resumo',
+'error#' => 'Erro #',
+'error1' => 'Você não possui permissões suficientes para visualizar este anexo.',
+'error3' => 'Ação repetida, redirecionando para a página principal.',
+'error4' => 'Você não possui privilégios administrativos.',
+'error5' => 'O usuário não existe nesta instalação do FlySpray.',
+'error6' => 'área de administração inválida.',
+'error7' => 'Autenticação falhou (Nome de usuário ou senha incorreto)!',
+'error71' => 'Conta travada por %d minutos devido a muitas tentativas de login!',
+'error8' => 'Você não especificou o seu nome de usuário e sua senha.',
+'error9' => 'A tarefa não existe ou você não tem permissão para ver esta tarefa.',
+'error10' => 'A tarefa não existe ou você não tem permissão para ver esta tarefa.',
+'error101' => 'Você não tem permissão para ver esta tarefa.',
+'error102' => 'Você não tem permissão para ver esta tarefa, autenticar-se pode ajudar.',
+'error11' => 'Sem permissões para editar este documento.',
+'error12' => 'Sem chave mágica! Você está certo de que a recebeu em sua mensagem de notificação?',
+'error13' => 'Usuários anônimos não possuem perfil',
+'error14' => 'Você não possui permissões suficientes para criar um grupo novo.',
+'error15' => 'Você não possui permissões para criar uma tarefa.',
+'error16' => 'Você não é um administrador de projeto.',
+'error17' => 'Inválido, área de PM.',
+'error18' => 'URL mágica inválida.',
+'error19' => 'O usuário não existe nesta instalação do FlySpray.',
+'error20' => 'Modificação na base de dados inválida.',
+'error21' => 'Um ou mais emails não puderam ser enviados. Verifique a sua configuração.',
+'error22' => 'Não é permitida a criação de novos usuários.',
+'error23' => 'Usuário ou grupo não habilitados para autenticar-se.',
+'error24' => 'Nem o ponto executável, nem um ponto público foi definido.',
+'error25' => 'Plano de execução disponível apenas para um projeto específico.',
+'done' => 'feito',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projeto não pôde ser apagado.',
+'GMT' => 'GMT',
+'timezone' => 'Zona de tempo',
+'accept' => 'Aceitar',
+'reasonfordeinal' => 'Motivo para recusar',
+'pruneclosedlinks' => 'Remover links fechados',
+'pruneclosedtasks' => 'Remover tarefas concluídas',
+'pagegenerated' => 'Página e imagem geradas em %d segundos.',
+'pruninglevel' => 'Nível de poda',
+'lastuser' => 'O último usuário não pode ser apagado.',
+'allprivate' => 'Todos os projetos são privados.',
+'deletegroup' => 'Apagar este grupo e mover usuários para',
+'parent' => 'Pai',
+'ordertip' => 'A ordem em que estes itens irão aparecer na lista',
+'showtip' => 'Mostrar este item na lista',
+'deletetip' => 'Apagar este item da lista',
+'del' => 'apagar',
+'request1' => 'O fechamento de uma tarefa foi solicitado.',
+'request2' => 'Um pedido para reabertura da tarefa foi feito.',
+'allpriorities' => 'Todas Prioridades',
+'noroadmap' => 'Não há um roadmap disponível (não existe versões "futuras" específicas do projeto).',
+'expand' => 'Expandir',
+'collapse' => 'Recolher',
+'expandall' => 'Expandir Tudo',
+'collapseall' => 'Recolher Tudo',
+'minpwsize' => 'O tamanho mínimo para a senha é de 5 caracteres',
+'passwordtoosmall' => 'O tamanho da senha é muito pequeno.',
+'accountwaslocked' => 'Sua conta foi bloqueada devido a muitas tentativas sem sucesso de entrar.',
+'failedattempts' => 'Foram %d tentativas de entrar sem sucesso.',
+'groupnotexist' => 'Grupo selecionado não existe neste projeto.',
+'searchindetails' => 'Detalhes da Busca',
+'showasassignees' => 'Exibir como atribuido',
+'find' => 'Encontrar',
+'tls' => 'TLS',
+);
+
+?>
diff --git a/lang/pt_pt.php b/lang/pt_pt.php
new file mode 100644
index 0000000..f1a78b5
--- /dev/null
+++ b/lang/pt_pt.php
@@ -0,0 +1,1051 @@
+<?php
+/**
+* Adaptado do pt_br para pt_pt por André Nunes - andre.garcia.nunes at gmail dot com
+* Respeita o novo Acordo Ortográfico de 1990.
+* Não esquecer: Editar e gravar em utf-8...
+*/
+
+$translation = array(
+'edituser' => 'Editar Utilizador',
+'accountenabled' => 'Conta ativa',
+'editallusers' => 'Ver todos os utilizadores',
+'username' => 'Nome de Utilizador',
+'usersupdated' => 'Utilizadores atualizados com sucesso',
+'realname' => 'Nome Verdadeiro',
+'emailaddress' => 'Endereço de Email',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Imagem de perfil',
+'notifytype' => 'Tipo de Notificação',
+'group' => 'Grupo',
+'enableaccounts' => 'Ativar Contas',
+'disableaccounts' => 'Desativar Contas',
+'deleteaccounts' => 'Apagar Contas',
+'updatedetails' => 'Atualizar detalhes',
+'setglobally' => 'Esta preferência foi estabelecida globalmente.',
+'usergroupmanage' => 'Gestão de Grupos e Utilizadores',
+'newuser' => 'Registar Novo Utilizador',
+'newuserbulk' => 'Registar múltiplos Novos Utilizadores',
+'bulkuserstoadd' => 'Lista de Novos Utilizadores',
+'optionsforallusers' => 'Opções para todos os Novos Utilizadores',
+'newgroup' => 'Criar Novo Grupo',
+'yes' => 'Sim',
+'no' => 'Não',
+'editgroup' => 'Editar Grupo',
+'groupname' => 'Nome do Grupo',
+'description' => 'Descrição',
+'admin' => 'Grupo Admin',
+'opennewtasks' => 'Abrir novas tarefas',
+'modifytasks' => 'Modificar tarefas existentes',
+'addcomments' => 'Adicionar comentários',
+'attachfiles' => 'Anexar ficheiros',
+'vote' => 'Votar',
+'groupenabled' => 'Membros podem-se conectar',
+'groupopen' => 'Membros podem-se conectar',
+'tasktypelist' => 'Lista de Tipos de Tarefas',
+'categorylist' => 'Lista de Categorias',
+'oslist' => 'Lista de Sistemas Operativos',
+'resolutionlist' => 'Lista de Resoluções',
+'versionlist' => 'Lista de Versões',
+'severitylist' => 'Lista de Gravidades',
+'listnote' => 'Nota: Desmarcar a caixa "exibir" pode modificar algumas tarefas quando no modo de edição. Alterar o campo "Nome" modificará todas as tarefas com esse nome. Itens que não podem ser apagados ou estão protegidos para garantir o funcionamento correto ou estão a ser usados em tarefas.',
+'name' => 'Nome',
+'order' => 'Ordem',
+'back' => 'Retroceder',
+'text' => 'Texto',
+'highlight' => 'Destacar',
+'show' => 'Exibir',
+'owner' => 'Proprietário',
+'selectowner' => 'Selecionar Proprietário',
+'update' => 'Atualizar',
+'addnew' => 'Adicionar novo',
+'flysprayprefs' => 'Preferências do Flyspray',
+'projecttitle' => 'Título do Projeto',
+'baseurl' => 'URL base da instalação',
+'replyaddress' => 'Responder ao endereço de e-mail para notificações',
+'themestyle' => 'Tema / Estilo',
+'language' => 'Idioma',
+'anonview' => 'Permitir que utilizadores anónimos vejam as tarefas',
+'allowanon' => 'Permitir que utilizadores anónimos criem novas tarefas',
+'never' => 'Nunca',
+'anonymously' => 'Anonimamente',
+'afterregister' => 'Somente após registo',
+'spamproof' => 'Ativar código de confirmação em registos de novos utilizadores',
+'anongroup' => 'Grupo para registo de novos utilizadores',
+'groupassigned' => 'Membros destes grupos podem receber tarefas',
+'forcenotify' => 'Forçar notificações de tarefa como',
+'neversend' => 'Nunca enviar',
+'userchoose' => 'Deixar cada utilizador escolher',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Proprietário padrão da categoria',
+'noone' => 'Ninguém',
+'jabbernotify' => 'Notificações do Jabber',
+'jabberserver' => 'Servidor',
+'jabberport' => 'Port',
+'jabberuser' => 'Nome de utilizador da conta',
+'jabberpass' => 'Password da conta',
+'saveoptions' => 'Guardar opções',
+'editcomment' => 'Editar Comentário',
+'commentby' => 'Comentário por',
+'saveeditedcomment' => 'Guardar comentário editado',
+'projectprefs' => 'Preferências do Projeto',
+'pagetitle' => 'Título da Página',
+'defaultproject' => 'Projeto Padrão',
+'projectlists' => 'Listas de Projeto',
+'showlogo' => 'Exibir a imagem do logo do título',
+'showgravatars' => 'Mostrar Gravatars',
+'emailNoHTML' => 'não íncluir HTML nos E-Mails',
+'intromessage' => 'Mensagem introdutória',
+'active' => 'ativo',
+'inactive' => 'inativo',
+'isactive' => 'Projeto está ativo',
+'showinactive' => 'Mostrar projetos inativos',
+'hideinactive' => 'Esconder projetos inativos',
+'createproject' => 'Criar um novo Projeto',
+'nopermission' => 'Não tem permissão para usar essa página.',
+'listordertip' => 'A ordem em que estes itens irão aparecer na lista',
+'listshowtip' => 'Exibir este item na lista',
+'categoryownertip' => 'Esta pessoa irá receber notificações quando uma tarefa nesta categoria abrir',
+'categoryparenttip' => 'Categoria pai a qual esta nova irá pertencer',
+'notsubcategory' => 'Nenhuma (categoria principal)',
+'showinlineimages' => 'Exibir imagens anexadas na mesma página',
+'dateformat' => 'Formato da Data',
+'dateformat_extended' => 'Formato da Data por extenso',
+'cache_feeds' => 'Cache de feeds',
+'no_cache' => 'Sem fazer cache',
+'cache_disk' => 'Cache no disco',
+'cache_db' => 'Cache na BD',
+'subcategoryof' => 'Subcategoria de',
+'visiblecolumns' => 'Colunas exibidas na lista de tarefas',
+'visiblefields' => 'Campos enquanto se adiciona/edita/vê uma tarefa',
+'tense' => 'Tempo',
+'listtensetip' => 'Passado, Presente ou Futuro',
+'past' => 'Passado',
+'present' => 'Presente',
+'future' => 'Futuro',
+'oldpass' => 'Password antiga',
+'nooldpass' => 'Não foi definida uma password antiga',
+'oldpasswrong' => 'Passowrd antiga está errada',
+'changepass' => 'Alterar password',
+'confirmpass' => 'Confirmar password',
+'projectmanager' => 'Gerente do Projeto',
+'viewtasks' => 'Ver tarefas',
+'modifyowntasks' => 'Modificar suas tarefas',
+'modifyalltasks' => 'Modificar tarefas que não são do utilizador',
+'viewcomments' => 'Ver comentários',
+'editcomments' => 'Editar comentários',
+'deletecomments' => 'Apagar comentários',
+'viewattachments' => 'Ver anexos',
+'createattachments' => 'Criar anexos',
+'deleteattachments' => 'Apagar anexos',
+'viewhistory' => 'Ver histórico',
+'closeowntasks' => 'Fechar suas tarefas',
+'closeothertasks' => 'Fechar tarefas que não são do utilizador',
+'assigntoself' => 'Atribuir as tarefas a si caso ainda não tenham sido atribuidas',
+'assignotherstoself' => 'Atribuir outras tarefas a si',
+'viewreports' => 'Ver log de eventos',
+'othersview' => 'Permitir a qualquer um ver este projeto',
+'othersviewroadmap' => 'Permitir a qualquer um ver o roteiro deste projeto',
+'usersandgroups' => 'Utlizadores e Grupos',
+'globalgroup' => 'Grupo Global',
+'globalgroups' => 'Grupos Globais',
+'defaultglobalgroup' => 'Grupo global padrão para novos utilizadores',
+'addtogroup' => 'Adicionar ao grupo',
+'moveuserstogroup' => 'Mover utilizador para o grupo',
+'nogroup' => 'Nenhum Grupo - Remover do projeto',
+'eventdesc' => 'Descrição do Evento',
+'requestedby' => 'Pedido por',
+'daterequested' => 'Data do Pedido',
+'closetask' => 'Fechar Tarefa',
+'reopentask' => 'Re-abrir Tarefa',
+'applymember' => 'Candidatar-se a membro do Projeto',
+'forcurrentproj' => 'Para o projeto atual',
+'lostpw' => 'Recuperar password perdida',
+'lostpwexplain' => 'Escreva o seu nome de utilizador para receber um link para recuperação da password perdida. Será enviado para o endereço de notificação do seu perfil.',
+'sendlink' => 'Enviar link',
+'savenewpass' => 'Guardar nova password',
+'anonreg' => 'Permitir registo de novos utilizadores',
+'allowanonopentask' => 'Permitir quem utilizadores anónimos abram novas tarefas',
+'editglobalgroup' => 'Editar Grupo Global',
+'editgroupforproj' => 'Editar grupo para o Projeto',
+'notshownforadmin' => 'Permissões não são exibidas para o grupo admin. Não precisa de as editar.',
+'general' => 'Geral',
+'userregistration' => 'Registode utilizador',
+'notifications' => 'Notificações',
+'resetoptions' => 'Redefinir Opções',
+'preferences' => 'Preferências',
+'tasktypes' => 'Tipos de Tarefa',
+'resolutions' => 'Resoluções',
+'categories' => 'Categorias',
+'operatingsystems' => 'Sistemas Operativos',
+'versions' => 'Versões',
+'admintoolboxlong' => 'Ferramentas administrativas',
+'newproject' => 'Novo Projeto',
+'delete' => 'Apagar',
+'link' => 'Link',
+'referencelinks' => 'Links de referência:',
+'listdeletetip' => 'Apagar este item da lista',
+'lookandfeel' => 'Aparência',
+'globaltheme' => 'Tema Global',
+'emailnotify' => 'Notificações por Email',
+'fromaddress' => 'Endereço de origem',
+'smtpserver' => 'Servidor SMTP',
+'smtpuser' => 'Utilizador SMTP',
+'smtppass' => 'Password SMTP',
+'addrewrite' => 'Usar reescrita do endereço',
+'usereminderdaemon' => 'Ativar avisos',
+'tasksperpage' => 'Tarefas por página da lista de tarefas',
+'addtoassignees' => 'Atribuir-me',
+'taskstatuses' => 'Estados das Tarefas',
+'canvote' => 'Pode votar',
+'loginsuccessful' => 'Ligado com sucesso.',
+'youareloggedout' => 'Foi desligado.',
+'waitwhiletransfer' => 'Por favor espere enquanto é transferido...',
+'clicknowait' => 'Clique aqui se não quiser esperar.',
+'accountdisabled' => 'Sua conta está desativada. Contate um administrador.',
+'task' => 'Tarefa',
+'edittask' => 'Editar esta tarefa',
+'openedby' => 'Aberto por',
+'editedby' => 'Editado por',
+'tasktype' => 'Tipo de Tarefa',
+'category' => 'Categoria',
+'status' => 'Estado',
+'assignedto' => 'Atribuido a',
+'operatingsystem' => 'Sistema Operativo',
+'severity' => 'Gravidade',
+'reportedversion' => 'Reportado na Versão',
+'dueinversion' => 'Conclusão esperada na versão',
+'defaultdueinversion' => 'Conclusão esperada por defeito para novas tarefas',
+'undecided' => 'Não decidido',
+'percentcomplete' => 'Percentagem Completa',
+'details' => 'Detalhes',
+'savedetails' => 'Guardar Detalhes',
+'canceledit' => 'Cancelar Edição',
+'anonymous' => 'Envio Anónimo',
+'complete' => 'Completo',
+'closedby' => 'Fechado por',
+'reasonforclosing' => 'Motivo de fecho:',
+'reopenthistask' => 'Re-abrir esta tarefa',
+'comments' => 'Comentários',
+'attachments' => 'Anexos',
+'relatedtasks' => 'Tarefas Relacionadas',
+'edit' => 'Editar',
+'addcomment' => 'Adicionar comentário',
+'fileuploadedby' => 'Arquivo enviado por',
+'uploadafile' => 'Anexar arquivo',
+'addalink' => 'Adicionar um link',
+'addanotherlink' => 'Adicionar outro link',
+'uploadnow' => 'Enviar agora!',
+'thesearerelated' => 'Estas tarefas estão relacionadas com esta tarefa',
+'remove' => 'Remover',
+'addnewrelated' => 'Adicionar uma nova tarefa relacionada',
+'add' => 'Adicionar!',
+'otherrelated' => 'Outras tarefas que estão relacionadas com esta',
+'receivenotify' => 'Estes utilizadores receberão informações detalhadas quando esta tarefa for alterada.',
+'addusertolist' => 'Adicionar utilizador a esta lista',
+'addtolist' => 'Adicionar à lista',
+'addmyself' => 'Adicionar-me à lista',
+'removemyself' => 'Remover-me da lista',
+'theseusersnotify' => 'Estes utilizadores receberão informações detalhadas quando esta tarefa for alterada.',
+'attachedtoproject' => 'Anexado ao Projeto',
+'reminders' => 'Avisos',
+'system' => 'Sistema',
+'systemvalues' => 'Valores de todo o sistema',
+'projectvalues' => 'Valores específicos ao projeto',
+'remindthisuser' => 'Lembrar este utilizador',
+'thisoften' => 'Frequência',
+'startafter' => 'Esperar antes de iniciar os avisos',
+'hour' => 'hora',
+'hours' => 'horas',
+'day' => 'dia',
+'days' => 'dias',
+'week' => 'semana',
+'weeks' => 'semanas',
+'addreminder' => 'Adicionar Aviso',
+'defaultreminder' => 'Este é um aviso da seguinte tarefa do Flyspray:',
+'message' => 'Mensagem',
+'closed' => 'Fechado',
+'filename' => 'Nome do ficheiro:',
+'date' => 'Data',
+'filesize' => 'Tamanho do ficheiro:',
+'closurecomment' => 'Comentários adicionais sobre o fecho:',
+'history' => 'Histórico',
+'nohistory' => 'Sem histórico disponível.',
+'eventdate' => 'Data',
+'user' => 'Utilizador',
+'event' => 'Evento',
+'fieldchanged' => 'Campo foi modificado',
+'taskopened' => 'Tarefa aberta',
+'taskreopened' => 'Tarefa re-aberta',
+'taskclosed' => 'Tarefa fechada',
+'commentadded' => 'Comentário adicionado',
+'commentedited' => 'Comentário editado',
+'commentdeleted' => 'Comentário apagado',
+'attachmentadded' => 'Anexo adicionado',
+'attachmentdeleted' => 'Anexo apagado',
+'taskedited' => 'Detalhes da tarefa editados',
+'notificationadded' => 'Utilizador adicionado a lista de notificação',
+'notificationdeleted' => 'Utilizador removido da lista de notificação',
+'relatedadded' => 'Tarefa relacionada adicionada',
+'relateddeleted' => 'Tarefa relacionada removida',
+'taskassigned' => 'Tarefa atribuida a',
+'taskreassigned' => 'Tarefa reatribuida a',
+'assignmentremoved' => 'Atribuição removida',
+'summary' => 'Resumo',
+'addedasrelated' => 'Tarefa adicionada à lista relacionada com',
+'deletedasrelated' => 'Tarefa removida da lista relacionada com',
+'reminderadded' => 'Aviso adicionado',
+'reminderdeleted' => 'Aviso removido',
+'priority' => 'Prioridade',
+'previousvalue' => 'Valor anterior',
+'newvalue' => 'Novo valor',
+'selectareason' => 'Selecionar um motivo',
+'assigntome' => 'Atribuir a mim',
+'reopenrequest' => 'Pedir re-abertura',
+'requestclose' => 'Pedir fecho',
+'ownershiptaken' => 'Utilizador apropriou-se',
+'closerequestmade' => 'Requisitado fecho da tarefa',
+'reopenrequestmade' => 'Requisitado re-abertura da tarefa',
+'taskdependson' => 'Esta tarefa depende de',
+'taskdependsontask' => 'Esta tarefa depende de',
+'taskdependsontasks' => 'Esta tarefa depende de',
+'taskblock' => 'Esta tarefa impede esta de fechar',
+'taskblocks' => 'Esta tarefa impede estas de fechar',
+'depadded' => 'Adicionada dependência',
+'depaddedother' => 'Esta tarefa foi adicionada como dependência de',
+'depremoved' => 'Dependência removida',
+'depremovedother' => 'Esta tarefa foi removida da lista de dependências de outra tarefa',
+'showdetailserror' => 'Esta tarefa não existe ou não tem permissão para vê-la.',
+'makeprivate' => 'tornar privado',
+'makepublic' => 'tornar publico',
+'taskmadeprivate' => 'Tarefa foi tornada privada',
+'taskmadepublic' => 'Privacidade removida - tarefa tornada pública',
+'confirmdeletecomment' => 'Quer mesmo apagar este comentário? %s',
+'attachementswilldeleted' => 'Os ficheiros anexados também serão apagados!',
+'confirmdeleteattach' => 'Quer mesmo apagar este anexo?',
+'selectedhistory' => 'A exibir os detalhes do histórico selecionado',
+'showallhistory' => 'Exibir aba de histórico completo novamente',
+'hidethis' => 'Esconder novamente',
+'mark100' => 'Marcar tarefa como 100% completa',
+'watchtask' => 'Seguir tarefa',
+'stopwatching' => 'Parar de seguir',
+'commentlink' => 'Link para este comentário',
+'submitreq' => 'Enviar pedido',
+'reasonforreq' => 'Motivo para o pedido',
+'pmreqdenied' => 'Pedido de Gerência de Projeto negado',
+'taskpendingreq' => 'Ação pendente ao Gerente de Projeto. Veja a aba Histórico para detalhes.',
+'previoustask' => 'Tarefa Anterior',
+'nexttask' => 'Próxima tarefa',
+'duedate' => 'Prazo limite',
+'attachnoperms' => 'Há anexos com este comentário, mas não tem permissão para vê-los.',
+'linknoperms' => 'Há links neste comentário mas não tem permissões para os ver.',
+'open' => 'Abrir',
+'depgraph' => 'Ver gráfico de Dependência',
+'reset' => 'Desfazer',
+'selectusers' => 'Selecionar utilizadores...',
+'addmetoassignees' => 'Adicionar-me à lista de atribuidos',
+'addedtoassignees' => 'utilizador adicionado à lista de atribuidos',
+'dependencygraph' => 'Gráfico de Dependência',
+'attachanotherfile' => 'Anexar outro ficheiro',
+'OK' => 'OK',
+'addvote' => 'Adicionar Voto',
+'disable_lostpw' => 'Desativar recuperação da password',
+'disable_changepw' => 'Desativar criar/modificar password',
+'notifyfromfs' => 'Notificação do Flyspray',
+'autogenerated' => 'ESTA É UMA MENSAGEM AUTOMÃTICA, NÃO RESPONDA',
+'forward' => 'Reencaminhar',
+'previous' => 'Anterior',
+'next' => 'Próximo',
+'first' => 'Primeiro',
+'last' => 'Último',
+'page' => 'Página %d de %d',
+'search' => 'Procurar',
+'alltasktypes' => 'Todos os tipos de tarefas',
+'allseverities' => 'Todas as Gravidades',
+'alldevelopers' => 'Todos Developers',
+'notyetassigned' => 'Ainda não atribuido',
+'allcategories' => 'Todas Categorias',
+'allstatuses' => 'Todos Estados',
+'allopentasks' => 'Todas as tarefas abertas',
+'sortthiscolumn' => 'Ordenar por esta coluna',
+'id' => 'ID',
+'project' => 'Projeto',
+'dateopened' => 'Aberto',
+'progress' => 'Progresso',
+'searchthisproject' => 'Pesquisar no projeto por',
+'dueanyversion' => 'Terminar em qualquer Versão',
+'anyversion' => 'Reportado em qualquer Versão',
+'dueversion' => 'Terminar na Versão',
+'lastedit' => 'Última Edição',
+'os' => 'Sistema Operativo',
+'reportedin' => 'Reportado Em',
+'taskrange' => 'Exibindo tarefas %d - %d de %d',
+'noresults' => 'Sua pesquisa não retornou resultados.',
+'takeaction' => 'Agir',
+'watchtasks' => 'Acompanhar as tarefas selecionadas',
+'stopwatchingtasks' => 'Parar de acompanhar as tarefas selecionadas',
+'assigntaskstome' => 'Atribuir a tarefa selecionada para mim',
+'dueby' => 'Terminado por',
+'dueanytime' => 'Terminado qualquer hora',
+'selectduedate' => 'Seleciona Data de Término',
+'toggleselected' => 'Selecionar',
+'due' => 'Terminado',
+'assignedtome' => 'Atribuido a mim',
+'tasklist' => 'Lista de tarefas',
+'dateclosed' => 'Fechado Data',
+'advanced' => 'Avançado',
+'searchcomments' => 'Procurar nos comentários',
+'searchforall' => 'Procurar por todas palavras',
+'anonusers' => 'Utilizadores anónimos',
+'miscellaneous' => 'Variados',
+'users' => 'Utilizadores',
+'taskproperties' => 'Propriedades da Tarefa',
+'selectsincedate' => 'Selecionar Alterado desde',
+'changedsince' => 'Alterado desde',
+'updatefs' => 'Por favor atualize o Flyspray.',
+'currentversion' => 'Sua versão atual é',
+'latestversion' => 'e a última versão é',
+'hidemessage' => '(lembrar mais tarde)',
+'saveas' => 'Guardar pesquisa como',
+'nosearches' => 'Sem pesquisas guardadas',
+'saving' => 'A guardar...',
+'votes' => 'Votos',
+'tovote' => 'Votar',
+'allclosedtasks' => 'Todas as Tarefas Fechadas',
+'password' => 'Password',
+'login' => 'Conectar-se!',
+'rememberme' => 'Lembrar-me',
+'lostpassword' => 'Esqueceu a password?',
+'lostpwforfs' => 'Senha esquecida para o Flyspray',
+'lostpwmsg1' => 'Olá.
+
+Eu perdi minha senha do Flyspray em ',
+'lostpwmsg2' => ', por favor envie-me uma nova password.
+
+utilizador: ',
+'regards' => '
+
+Atenciosamente,',
+'yourusername' => ' seu nome de utilizador ',
+'locale' => 'pt-PT',
+'filenotexist' => 'Ficheiro não existe, ou não tem permissão para o ver.',
+'showtask' => 'Exibir Tarefa',
+'now' => 'Agora',
+'go' => 'Vai!',
+'opentaskanon' => 'Abrir uma nova Tarefa anonimamente',
+'register' => 'Registar',
+'addnewtask' => 'Adicionar nova tarefa',
+'reports' => 'Log de Eventos',
+'editmydetails' => 'Editar meus detalhes',
+'logout' => 'Sair',
+'disabledaccount' => 'Sua conta foi desativada!<br>A desligar...',
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray é orgulhosamente patrocinado por',
+'projects' => 'Projetos',
+'allprojects' => 'Todos Projetos',
+'selectproject' => 'para Projeto:',
+'tasksall' => 'Todas as tarefas',
+'tasksassigned' => 'Tarefas atribuidas a mim',
+'tasksreported' => 'Tarefas reportadas a mim',
+'taskswatched' => 'Tarefas acompanhadas',
+'mysearch' => 'Minhas Procuras',
+'admintoolbox' => 'Opções administrativas',
+'manageproject' => 'Gerir Projeto',
+'permissions' => 'Ver Permissões',
+'hide' => 'Esconder',
+'pendingreq' => 'Pedidos em espera',
+'errorpage' => 'Erro a tentar aceder à página.
+ Talvez tenha pedido uma tarefa que não existe,
+ ou não tem permissão para ver esta página.<br /><br />
+ Pode estar a tentar usar um URL inválido
+ ou a fazer o que não devia.',
+
+'permissionsforproject' => 'Permissiões para ',
+'switchto' => 'Alternar para',
+'lastsearch' => 'Ultima Procura',
+'modify' => 'Alterar',
+'noticefrom' => 'Notificação de',
+'hasopened' => 'ABRIU uma tarefa NOVA e esta foi atribuída a si:',
+'moreinfonew' => 'Pode encontrar mais informações na página do FlySpray:',
+'newtaskcategory' => 'Uma nova Tarefa do FlySpray foi aberta nesta categoria',
+'categoryowner' => 'Recebeu esta mensagem porque está listado como dono da categoria.',
+'tasksummary' => 'Descrição da tarefa:',
+'newtaskadded' => 'Sua nova Tarefa foi adicionada.',
+'summaryanddetails' => 'Precisa de preencher a descrição e os detalhes.',
+'summaryrequired' => 'Precisa de preencher uma pequena descrição da tarefa.',
+'goback' => 'Voltar.',
+'messagefrom' => 'Esta é uma mensagem do sistema de bug tracking FlySpray',
+'hasjustmodified' => 'modificou a seguinte Tarefa.',
+'changedfields' => 'campos alterados estão precedidos por asteriscos (**)',
+'moreinfomodify' => 'Mais informações sobre esta Tarefa no URL:',
+'nolongerassigned' => 'A seguinte Tarefa não está mais atribuida a si. Ela agora está atribuida a',
+'hasassigned' => 'Atribuiu a si a seguinte Tarefa do FlySpray:',
+'taskupdated' => 'Tarefa atualizada.',
+'tasksupdated' => 'Tarefas atualizadas.',
+'hasclosedassigned' => 'fechou a seguinte tarefa Flyspray que foi atribuida a si:',
+'unassigned' => 'não atribuida',
+'hasclosed' => 'fechou a seguinte tarefa.',
+'youonnotify' => 'Recebeu isto porque está na lista de notificação.',
+'taskclosedmsg' => 'Tarefa foi fechada.',
+'returntotask' => 'Voltar para detalhes da tarefa',
+'backtoindex' => 'Voltar para a lista de tarefas',
+'noclosereason' => 'Não selecionou nenhum motivo para fechar esta tarefa.',
+'hasreopened' => 'reabriu a seguinte tarefa do Notes que fechou:',
+'taskreopenedmsg' => 'Tarefa foi reaberta.',
+'backtotask' => 'Voltar para a tarefa.',
+'commentaddedmsg' => 'O comentario foi adicionado.',
+'commenttoassigned' => 'adicionou um comentário a uma tarefa que foi atribuído:',
+'commenttotask' => 'adicionou o seguinte comentário a esta tarefa.',
+'nocommententered' => 'Deve inserir um comentário antes de clicar a tecla enviar.',
+'fillinfields' => 'Não preencheu todos os campos.',
+'notcurrentpass' => 'Esta não é sua password atual!',
+'passchanged' => 'Sua senha foi alterada.',
+'closewindow' => 'Agora já pode fechar esta janela.',
+'passnomatch' => 'As suas novas passwords não eram as mesmas!',
+'usernametaken' => 'Este utilizador já está registadoo. Precisa de escolher outro nome para acesso.',
+'usernametakenbulk' => 'Nome de Utilizador já utilizado',
+'newusercreated' => 'Foi criada uma nova conta de utilizador.',
+'accountcreated' => 'Sua conta foi criada.',
+'newuserwarning' => 'Algumas funções são desabilitadas para utilizadores comuns e as mesmas, requerem que seu utilizador seja aprovado por um dos administradores. Se não puder iníciar uma sessão, provavelmente é por que ainda não possui estes direitos de acesso dados por um dos administradores.',
+'nomatchpass' => 'As passwords não combinam.',
+'confirmwrong' => 'Código de confirmação está incorreto!',
+'formnotcomplete' => 'O formulário não foi preenchido completamente.',
+'formnotnumeric' => 'O dado inserido não é numérico!',
+'groupnametaken' => 'O Nome do grupo já está em uso.',
+'newgroupadded' => 'Novo grupo adicionado.',
+'optionssaved' => 'Opções do Flyspray guardadas.',
+'hasuploaded' => 'enviou um ficheiro em anexo a uma tarefa que lhe foi atribuída:',
+'hasattached' => 'enviou um ficheiro à seguinte tarefa.',
+'fileuploaded' => 'Ficheiro foi carregado.',
+'fileerror' => 'Houve um erro no carregamento do ficheiro. Talvez as permissões na pasta <i>attachments/</i> estão incorrectas.',
+'contactadmin' => 'Contacte o Administrador deste projeto.',
+'selectfileerror' => 'Não selecionou um ficheiro e/ou não incorporou uma descrição.',
+'userupdated' => 'Detalhes do utilizador foram atualizados',
+'realandemail' => 'Não preencheu os campos do Nome Real e do Endereço de Email.',
+'groupupdated' => 'Definição de grupo atualizada.',
+'groupanddesc' => 'Não preencheu os campos de nome e de descrição de grupo.',
+'fillallfields' => 'Preencha todos os campos.',
+'listPmustN' => 'O campo "Ordem" deve ser numérico.',
+'listupdated' => 'A lista foi atualizada.',
+'listitemadded' => 'O artigo novo da lista adicionou.',
+'relatedaddedmsg' => 'Tarefa relacionada adicionada à lista.',
+'relatederror' => 'Esse utilizador está já na lista da notificação para essa tarefa.',
+'relatedremoved' => 'Tarefa relacionada removida da lista.',
+'notifyadded' => 'utilizador adicionado para lista de notificações.',
+'notifyerror' => 'Este utilizador está já na lista da notificação para essa tarefa.',
+'notifyremoved' => 'utilizador removido da lista de notificação.',
+'editcommentsaved' => 'O comentário foi guardado e atualizado.',
+'commentdeletedmsg' => 'O comentário foi apagado.',
+'gotonewtask' => 'Ir para a nova tarefa que acabou de criar',
+'projectcreated' => 'nosso novo Projeto foi criado. Siga o link abaixo para configurar categoria, sistema operativo e listas de versões.',
+'customiseproject' => 'Customisar este projeto',
+'projectupdated' => 'Preferências do projeto atualizadas',
+'emptytitle' => 'VDeixou em branco o campo do título do projeto. Volte e adicione um título para este projeto.',
+'loginbelow' => 'Seu registo foi concluido com sucesso. Agora, já pode aceder o site usando o formulário abaixo.',
+'attachmentdeletedmsg' => 'O ficheiro foi excluído',
+'reminderaddedmsg' => 'Seu aviso foi adicionado.',
+'reminderdeletedmsg' => 'O aviso selecionado foi apagado.',
+'flyspraytask' => 'Tarefa do Flyspray',
+'fieldsmissing' => 'Alguns campos estão vazios ou possuem dados inválidos.',
+'relatedinvalid' => 'Esta tarefa não existe.',
+'relatedproject' => 'Esta tarefa pertence a outro projeto. Relacioná-las mesmo assim?',
+'addanyway' => 'Adicionar de qualquer jeito',
+'cancel' => 'Cancelar',
+'alreadyedited' => 'Esta tarefa foi editada por outra pessoa antes de a tentar guardar. Deseja sobreescrever as alterações?',
+'saveanyway' => 'Guardar minhas alterações mesmo assim',
+'nouserselected' => 'Nenhum utilizador selecionado. Selecione pelo menos um utilizador antes de tentar novamente.',
+'groupswitchupdated' => 'Grupos de utilizadores alterados com sucesso.',
+'takenownershipmsg' => 'Esta tarefa está agora atribuida a si.',
+'adminrequestmade' => 'Seu pedido foi enviado para um Gerente de Projeto.',
+'newdepnotify' => 'Uma nova dependência foi adicionada à seguinte tarefa:',
+'dependadded' => 'Dependência entre tarefas adicionada.',
+'dependaddfailed' => 'Ocorreu um erro ao tentar adicionar a dependência. Verifique se a tarefa existe e não há bloqueio mútuo.',
+'depremovedmsg' => 'Dependência entre tarefas removida',
+'newdepis' => 'A nova dependência é',
+'magicurlsent' => 'Uma mensagem foi enviada para seu endereço de notificação. Ela contém um link que o levará à pagina que completa esta tarefa.',
+'changefspass' => 'Alterar password do Flyspray',
+'magicurlmessage' => 'Por favor abra o seguinte link para alterar a sua password do Flyspray:',
+'erroronform' => 'Ocorreu um problema com o envio do seu formulário',
+'addressused' => 'Este endereço foi usado para registo numa conta no Flyspray. Se não esperava esta mensagem, por favor ignore e apague. Vá para o seguinte endereço para completar seu registo:',
+'confirmcodeis' => 'Seu código de confirmação é:',
+'codesent' => 'Seu código de confirmação foi enviado. Por favor siga as instruções na mensagem.',
+'codenotsent' => 'Não foi possível enviar seu código, por favor tente novamente',
+'taskmadeprivatemsg' => 'Esta tarefa tornou-se privada',
+'taskmadepublicmsg' => 'Esta tarefa tornou-se publica novamente',
+'realandnotify' => 'Precisa de preencher seu nome verdadeiro, e o endereço de E-mail ou seu Jabber ID.',
+'pmreqdeniedmsg' => 'Gerência de Projeto negada',
+'massopsuccess' => 'Operações executadas com sucesso - onde havia permissão',
+'usernotexist' => 'Este utilizador não existe neste Flyspray',
+'commentattachperms' => 'Não pode apagar este comentário - sem permissão para apagar anexos',
+'voterecorded' => 'Seu voto foi registado',
+'votefailed' => 'Seu voto não pode ser registado neste momento.',
+'createnewgroup' => 'Criar novo grupo',
+'requiredfields' => 'Todos os campos assinalados são requeridos',
+'addthisgroup' => 'Adicionar ao grupo',
+'createnewproject' => 'Criar um projeto novo',
+'addnewproject' => 'Adicionar um novo projeto',
+'htmlallowed' => 'Código do HTML é reservado',
+'createthisproject' => 'Criar este projeto',
+'inlineimages' => 'Mostrar as imagens em anexo na mesma janela',
+'createnewtask' => 'Criar uma nova tarefa no projeto:',
+'addanother' => 'Adicionar outra tarefa depois desta',
+'addthistask' => 'Adicionar esta tarefa',
+'notifyme' => 'Notificar-me sempre que esta tarefa mudar',
+'newtask' => 'Nova Tarefa',
+'attachafile' => 'Anexar arquivo',
+'registernewuser' => 'Registar novo utilizador',
+'none' => 'Nenhum',
+'registeraccount' => 'Registar esta conta',
+'registerbulkaccount' => 'Registar contas',
+'both' => 'Ambos',
+'notifyfrom' => 'Notificação de ',
+'donotreply' => 'ESTA é UMA MENSAGEM AUTOMáTICA, NãO RESPONDA.',
+'disclaimer' => 'Está recebendo esta mensagem porque o pediu do Sistema de Bugtracking Flyspray. Se não esperava esta mensagem ou não quer receber e-mails no futuro, pode alterar suas definições de notificação no URL abaixo.',
+'userwho' => 'utilizador que fez isso',
+'moreinfo' => 'Mais informações podem ser encontradas no seguinte URL:',
+'newtaskopened' => 'Uma nova tarefa do Flyspray foi aberta. Detalhes abaixo.',
+'notify.taskclosed' => 'A seguinte tarefa foi fechada:',
+'notify.taskreopened' => 'A seguinte tarefa foi re-aberta:',
+'newdep' => 'A seguinte tarefa tem uma nova dependência:',
+'notify.depremoved' => 'A seguinte tarefa teve uma dependência removida:',
+'olddepwas' => 'A antiga dependência era',
+'notify.commentadded' => 'A seguinte tarefa tem um novo comentário:',
+'commentis' => 'O comentário está abaixo.',
+'newattachment' => 'Um novo ficheiro foi anexado para a seguinte tarefa:',
+'detailsbelow' => 'Os detalhes estão abaixo.',
+'notify.relatedadded' => 'Uma nova relação entre tarefas foi adicionada a seguinte tarefa:',
+'relatedis' => 'A tarefa relacionada é',
+'assignedtoyou' => 'A seguinte tarefa foi atribuida a si:',
+'takenownership' => 'foi designado para a seguinte tarefa:',
+'requiresaction' => 'A seguinte tarefa precisa da moderação do Gerente de Projeto:',
+'requiresactionnotify' => 'A tarefa precisa ser moderada por um Gerente de Projeto',
+'pmdeny' => 'Um Gerente de Projeto negou o pedido pendente para a seguinte tarefa:',
+'pmdenynotify' => 'Um Gerente de Projeto negou o pedido',
+'fileaddedtoo' => 'Há um ou mais ficheiros anexados a este comentário.',
+'taskwatching' => 'A seguinte tarefa que estava acompanhando',
+'isdepfor' => 'é uma nova dependência para',
+'denialreason' => 'Motivo da negação',
+'taskchanged' => 'A seguinte tarefa foi alterada. As alterações estão listadas abaixo. Para ver sobre tudo que foi alterado, visite o seguinte URL e clique na aba Histórico.',
+'useraddedtoassignees' => 'Um utilizador adicionou-se à lista de utilizadores atribuidos para esta tarefa.',
+'removeddepis' => 'A dependência removida foi',
+'isnodepfor' => 'não há mais dependência para',
+'usergroups' => 'Grupos de utilizador',
+'pmtoolbox' => 'Ferramentas do Gerente de Projeto',
+'groupmanage' => 'Gerir os Grupos',
+'pendingrequests' => 'Pedidos Pendentes',
+'reasongiven' => 'Motivos dadas',
+'nopendingreq' => 'Não há pedidos pendentes para Gestores de Projeto.',
+'givereason' => 'Dê uma motivo',
+'catlisted' => 'Editor da Lista de Categorias',
+'oslisted' => 'Editor da Lista de Sistemas Operativos',
+'verlisted' => 'Editor da Lista de Versões',
+'tasktypeed' => 'Editor da Lista de Tipos de Tarefa',
+'resed' => 'Editor da Lista de Resoluções',
+'deny' => 'Negar',
+'notifiedwhen' => 'Notificar quando',
+'onlynewtasks' => 'Novas tarefas estão abertas',
+'allevents' => 'Qualquer evento ocorrido em qualquer tarefa',
+'feeds' => 'Feeds',
+'feeddescription' => 'Descrição de Feed',
+'feedimgurl' => 'Imagem do URL do Feed (deixe em branco para nenhuma imagem)',
+'notifysubject' => 'Assunto para notificações',
+'notifysubjectinfo' => '(%p = título do projeto, %s = resumo da tarefa, %t = id da tarefa, %a = ação, %u = Nome de Utilizador)',
+'priority6' => 'Altíssima',
+'priority5' => 'Imediatamente',
+'priority4' => 'Urgente',
+'priority3' => 'Alta',
+'priority2' => 'Normal',
+'priority1' => 'Baixa',
+'sendcode' => 'Envie o código!',
+'entercode' => 'Digite o código de confirmação que recebeu na mensagem de notificação. Também digite a password que deseja.',
+'confirmationcode' => 'Código de confirmação',
+'registererror' => 'Certifique-se de que preencheu todos os campos obrigatórios e de que specificou os detalhes corretos para o tipo de notificação desejada.',
+'validusername' => '(apenas caracteres alfanuméricos e - _ . são permitidos)',
+'validemail' => '(use ; para separar vários endereços de email)',
+'emailtakenbulk' => 'Endereço de Email já foi utilizado',
+'emailtaken' => 'O email ou ID Jabber já estão em uso. Terá que escolher um outro.',
+'note' => '<strong>Nota:</strong> Receberá um código de confirmação antes da criação da sua conta. O código será enviado através do seu método de notificação especificado acima. <br />Se fornecer dados falso, <strong>não receberá o seu código</strong>.',
+'changelog' => 'Registo de mudanças',
+'changeloggen' => 'Gerador de registo de mudanças',
+'listfrom' => 'Listar registo de mudanças de',
+'to' => 'para',
+'oldestfirst' => 'Mais antigos antes',
+'recentfirst' => 'Mais novos antes',
+'severityrep' => 'Relatório de gravidade',
+'totalopen' => 'Total de tarefas a realizar',
+'age' => 'Idade',
+'agerep' => 'Relatório de idade',
+'eventsrep' => 'Relatório de eventos',
+'events' => 'Eventos',
+'Tasks' => 'Tarefas',
+'opened' => 'Aberto',
+'edited' => 'Modificado',
+'assigned' => 'Atribuído',
+'within' => 'Dentro de',
+'pastday' => 'Ontem',
+'pastweek' => 'Semana passada',
+'pastmonth' => 'Mês passado',
+'pastyear' => 'Ano passado',
+'nolimit' => 'Sem limite',
+'from' => 'Início',
+'duein' => 'Término',
+'selectfromdate' => 'Selecione a data de início',
+'selecttodate' => 'Selecione a data de término',
+'showvoters' => 'Mostrar/esconder votantes',
+'roadmap' => 'Plano de execução',
+'roadmapfor' => 'Plano de execução para a versão',
+'tasks' => 'tarefas',
+'completed' => 'completas.',
+'opentasks' => 'tarefas em aberto',
+'of' => '% de',
+'severity5' => 'Crítica',
+'severity4' => 'Alta',
+'severity3' => 'Média',
+'severity2' => 'Baixa',
+'severity1' => 'Muito baixa',
+'Redirect' => 'Redirecionar',
+'redirectmsg' => 'Se o seu browser não suporta redirecionamento, clique %sAQUI%s para ser redirecionado.',
+'allowclosedcomments' => 'Permitir comentários em tarefas já executadas',
+'comment' => 'Comentar',
+'editowncomments' => 'Editar os seus comentários',
+'reopened' => 'Reabertos',
+'loading' => 'A caregar...',
+'notifyown' => 'Notificar sobre as suas modificações',
+'youremail' => 'O seu email',
+'thankyouforbug' => 'Obrigado por reportar o seu problema. Pode ver a tarefa e observer o seu progresso a qualquer hora através do seguinte URL:',
+'anonuser' => 'utilizador anónimo',
+'conflict' => 'Conflito',
+'file' => 'Ficheiro',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Tamanho',
+'projectgroup' => 'Grupo do projeto',
+'profile' => 'Perfil:',
+'viewprofile' => 'Ver perfil',
+'regdate' => 'Registado desde',
+'tasksopened' => 'Tarefas em aberto',
+'replyto' => 'Responder para',
+'notifytypes' => 'Tipos de notificação',
+'pm.taskchanged' => 'Tarefa modificada',
+'pm.taskreopened' => 'Tarefa reaberta',
+'pm.depadded' => 'Dependência adicionada',
+'pm.depremoved' => 'Dependência removida',
+'pmrequest' => 'Requisição de Gestão do Projeto',
+'pmrequestdenied' => 'Requisição de Gestão de Projeto negada',
+'newassignee' => 'Nova atribuição',
+'revdepadded' => 'Dependência reversa adicionada',
+'revdepaddedremoved' => 'Dependência reversa removida',
+'assigneeadded' => 'Atribuição adicionada',
+'addusergroup' => 'Adicionar utilizador a este grupo',
+'groupmembers' => 'Membros do grupo',
+'deleteuser' => 'Apagar este utilizador',
+'userdeleted' => 'utilizador apagado',
+'autoassign' => 'Auto atribuir uma tarefa ao dono da categoria',
+'ssl' => 'SSL',
+'updatewrong' => 'Tem a verificação de atualizações habilitada, mas um erro ocorreu ao tentar
+ acessar o servidor de atualizações, ou o seu host não permite acesso a conexões externas,
+ ou o erro foi causado por uma falha na rede.
+ Por gentileza, visite o site do FlySpray para assegurar-se de que está utilizando a última versão.',
+'deleteproject' => 'Apagar este projeto e mover seus dados para',
+'projectdeleted' => 'Projeto apagado com sucesso',
+'feedforall' => 'Fornecido para todos os projetos',
+'usercreated' => 'Utilizador criado',
+'userdeleted' => 'Utilizador apagado',
+'created' => 'Criado',
+'deleted' => 'Apagado',
+'userid' => 'Identificador do utilizador',
+'editassignments' => 'Editar atribuições',
+'preview' => 'Pré-visualizar',
+'anyprogress' => 'Qualquer progresso',
+'tasksrelated' => 'Tarefas relacionadas a esta',
+'duplicatetasks' => 'Tarefas duplicadas desta',
+'databasemodfailed' => 'A modificação na base de dados falhou. Uma das possíveis razões é falta de permissões.',
+'frequency' => 'Frequência',
+'newuserregistered' => 'Um novo utilizador se registou na instalação do FlySpray. O detalhes do utilizador são os seguintes:',
+'newuserregisterednotify' => 'Um novo utilizador se registou',
+'notify_registration' => 'Notificar administradores sobre o registo de novos utilizadores',
+'textversion' => 'Versão texto',
+'onlyprimary' => 'Tarefas não bloqueiam outras tarefas',
+'onlyblocker' => 'Tarefas bloqueando outras tarefas',
+'blockerornoblocker' => 'Tarefas que bloqueiam ou não bloqueiam, não faz sentido selecionar ambas opções',
+'switch' => 'Trocar',
+'max' => 'máx.',
+'dates' => 'Datas',
+'selectduedatefrom' => 'Terminam a partir de',
+'selectduedateto' => 'até',
+'selectsincedatefrom' => 'Modificadas a partir de',
+'selectsincedateto' => 'até',
+'selectdate' => 'Selecione a data',
+'selectopenedfrom' => 'Abertas a partir de',
+'selectopenedto' => 'até',
+'selectclosedfrom' => 'Fechadas a partir de',
+'selectclosedto' => 'até',
+'startat' => 'Inicia-se em',
+'hasattachment' => 'Possui anexo',
+'private' => 'Privada',
+'watching' => 'Acompanhando',
+'alreadyvotedthistask' => 'votou para esta tarefa',
+'alreadyvotedthisday' => 'já votada hoje',
+'visibility' => 'Visibilidade',
+'public' => 'Pública',
+'leaveemptyauto' => 'Deixe os campos de senha em branco se desejar que a senha seja gerada automaticamente.',
+'novalidemail' => 'Não forneceu um email válido.',
+'novalidjabber' => 'Não forneceu um endereço Jabber válido.',
+'missingrequired' => 'Não preencheu todos os campos obrigatórios.',
+'entervalidusername' => 'Por gentileza, especifique um utilizador válido com um nome real.',
+'couldnotaddusernotif' => 'Não foi possível adicionar este utilizador à lista de notificações.',
+'defaulttask' => 'Descrição de tarefa padrão',
+'all' => 'todas',
+'events.useraddedtoassignees' => 'utilizador adicionado aos atribuidos',
+'eventlog' => 'Histórico de eventos',
+'assignmentchanged' => 'Atribuição modificada',
+'detailedinfo' => 'Informações detalhadas',
+'All' => 'Todas',
+'tasksireported' => 'Tarefas que reportou',
+'recentlyopened' => 'Abertas recentemente',
+'stats' => 'Estatísticas',
+'totaltasks' => 'total de tarefas',
+'mostwanted' => 'Tarefas mais requisitadas',
+'defaultentry' => 'Página de entrada padrão',
+'toplevel' => 'Visão do nível mais alto',
+'overview' => 'Resumo',
+'error#' => 'Erro #',
+'error1' => 'Não possui permissões suficientes para visualizar este anexo.',
+'error3' => 'Ação repetida, redirecionando para a página principal.',
+'error4' => 'Não possui privilégios administrativos.',
+'error5' => 'O utilizador não existe nesta instalação do FlySpray.',
+'error6' => 'área de administração inválida.',
+'error7' => 'Autenticação falhou (Nome de utilizador ou senha incorreto)!',
+'error71' => 'Conta travada por %d minutos devido a muitas tentativas de login!',
+'error8' => 'Não especificou o seu nome de utilizador e sua senha.',
+'error9' => 'A tarefa não existe ou não tem permissão para ver esta tarefa.',
+'error10' => 'A tarefa não existe ou não tem permissão para ver esta tarefa.',
+'error101' => 'Não tem permissão para ver esta tarefa.',
+'error102' => 'Não tem permissão para ver esta tarefa, autenticar-se pode ajudar.',
+'error11' => 'Sem permissões para editar este documento.',
+'error12' => 'Sem chave mágica! Tem a certeza que recebeu a mensagem de notificação?',
+'error13' => 'Utilizadores anónimos não possuem perfil',
+'error14' => 'Não possui permissões suficientes para criar um grupo novo.',
+'error15' => 'Não possui permissões para criar uma tarefa.',
+'error16' => 'Não é um administrador de projeto.',
+'error17' => 'Inválido, área de PM.',
+'error18' => 'URL inválido.',
+'error19' => 'O utilizador não existe nesta instalação do FlySpray.',
+'error20' => 'Modificação na base de dados inválida.',
+'error21' => 'Um ou mais emails não puderam ser enviados. Verifique a sua configuração.',
+'error22' => 'Não é permitida a criação de novos utilizadores.',
+'error23' => 'utilizador ou grupo não habilitados para autenticar-se.',
+'error24' => 'Nem o ponto executável, nem um ponto público foi definido.',
+'error25' => 'Plano de execução disponível apenas para um projeto específico.',
+'error26' => 'Fornecedor de "oauth" não suportado.',
+'error27' => 'Impossível fazer login. Autorize-nos a ver o seu email.',
+'error28' => 'Não tem permissões para aceder a esta área.',
+'done' => 'feito',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projeto não pode ser apagado.',
+'GMT' => 'GMT',
+'timezone' => 'Fuso horário',
+'accept' => 'Aceitar',
+'reasonfordeinal' => 'Motivo para recusar',
+'pruneclosedlinks' => 'Remover links fechados',
+'pruneclosedtasks' => 'Remover tarefas concluídas',
+'pagegenerated' => 'Página e imagem geradas em %d segundos.',
+'pruninglevel' => 'Nível de ramificação',
+'lastuser' => 'O último utilizador não pode ser apagado.',
+'allprivate' => 'Todos os projetos são privados.',
+'deletegroup' => 'Apagar este grupo e mover usuários para',
+'parent' => 'Pai',
+'ordertip' => 'A ordem em que estes itens irão aparecer na lista',
+'showtip' => 'Mostrar este item na lista',
+'deletetip' => 'Apagar este item da lista',
+'del' => 'apagar',
+'request1' => 'O fecho de uma tarefa foi solicitado.',
+'request2' => 'Um pedido para reabertura da tarefa foi feito.',
+'allpriorities' => 'Todas Prioridades',
+'noroadmap' => 'Não há um roadmap disponível (não existem versões "futuras" específicas do projeto).',
+'expand' => 'Expandir',
+'collapse' => 'Recolher',
+'expandall' => 'Expandir Tudo',
+'collapseall' => 'Recolher Tudo',
+'minpwsize' => 'O tamanho mínimo para a password é de 5 caracteres',
+'passwordtoosmall' => 'O tamanho da password é muito pequeno.',
+'accountwaslocked' => 'Sua conta foi bloqueada devido a muitas tentativas sem sucesso de entrar.',
+'failedattempts' => 'Foram %d tentativas de entrar sem sucesso.',
+'groupnotexist' => 'Grupo selecionado não existe neste projeto.',
+'searchindetails' => 'Detalhes da Procura',
+'showasassignees' => 'Exibir como atribuido',
+'find' => 'Encontrar',
+'tls' => 'TLS',
+'isadmin' => 'É administrador',
+'addvotes' => 'Adiciona votos',
+'removevote' => 'Remove voto',
+'voteremoved' => 'O seu voto foi removido',
+'voteremovefailed' => 'O seu voto não pode ser removido neste momento.',
+'novotes' => 'Não há votações.',
+'connectedtasks' => 'Tarefas relacionadas:',
+'taskdependencies' => 'Dependências',
+'viewgraph' => 'ver gráfico',
+'notaskdependencies' => 'Esta tarefa não depende de outras.',
+'dependson' => 'Depende de',
+'blocks' => 'Bloqueia',
+'newdependency' => 'Nova dependência:',
+'nouserstoadd' => 'Não há utilizadores para adicionar. Por favor confirme que o Nome, o Nome de Utilizador e o Email estão definidos para cada utilizador.',
+'dispintro' => 'Mostrar mensagem introdutória',
+'mainmessage' => 'Mensagem introdutória principal',
+/* note only the English version for 'dispintro' is supported
+ * other languages should also be taken cared of in the future*/
+
+'setsupertask' => 'Mudar ID da Super-Tarefa:',
+'supertaskmodified' => 'ID da Super-Tarefa foi modificado',
+'set' => 'Modificar',
+'supertask' => 'Super-Tarefa',
+'setparent' => 'Modificar o id da tarefa mãe desta tarefa',
+'selfsupertasknotallowed' => 'ID da Super-Tarefa não pode ser igual ao ID da própria tarefa',
+'quickaction' => 'Acções rápidas',
+'updateselectedtasks' => 'Atualizar tarefas selecionadas',
+'notspecified' => 'Não especificado',
+'editselectedtasks' => 'Editar tarefas selecionadas',
+'information' => 'Informação',
+'taskclosedisabled' => 'Não é possível fechar Tarefa pois ainda existem tarefas abertas que dependem desta:-',
+'daysleft' => 'dias que faltam',
+'dayoverdue' => 'dias de atraso',
+'duetoday' => 'Prazo termina hoje.',
+'daysbeforealert' => 'Dias antes do alerta',
+'associatedsubtask' => 'Associada com sucesso à subtarefa #FS',
+'associatesubtask' => 'Associar ID da sub tarefa com esta tarefa',
+'subtaskid' => 'ID da sub tarefa',
+'subtaskalreadyhasparent' => 'A sub tarefa que inseriu já tem uma tarefa mãe, por favor corrija isto antes de associar.',
+'subtaskisparent' => 'A sub tarefa que inseriu é a tarefa mãe desta tarefa. Sub tarefa não associada.',
+'subtasknotexist' => 'A sub tarefa que inseriu não existe.',
+'subtaskremovedmsg' => 'A sub tarefa foi removida com sucesso',
+'subtaskadded' => 'Adicionada sub tarefa',
+'subtaskremoved' => 'Removida sub tarefa',
+'addnewsubtask' => 'Adicionada nova sub tarefa',
+'hidesubtasks' => 'Esconder sub tarefas',
+'voteforthistask' => 'Votar nesta tarefa',
+'watchthistask' => 'Seguir esta tarefa',
+'privatethistask' => 'Tornar esta tarefa privada',
+'adddependenttask' => 'Associar dependência',
+'associatetaskid' => 'ID da sub tarefa',
+'parenttaskid' => 'ID da tarefa mãe',
+'invalidsupertaskid' => 'ID da tarefa mãe é inválido.',
+'supertaskadded' => 'Adicionada Super-tarefa',
+'supertaskremoved' => 'Removida Super-tarefa',
+'effort' => 'Esforço',
+'efforttracking' => 'Seguir Esforço',
+'useeffort' => 'Projeto usa controlo de Esforço',
+'estimatedeffort' => 'Esforço estimado',
+'totalestimatedeffort' => 'Total de Esforço estimado',
+'actualeffort' => 'Esforço realizado',
+'starteffort' => 'Começar a seguir',
+'endeffort' => 'Parar de seguir',
+'cleareffort' => 'Limpar',
+'addeffort' => 'Adicionar esforço',
+'manualeffort' => 'Adicionar esforço manualmente(H:M)',
+'efforttrackingstarted' => 'Começou a seguir Esforço nesta tarefa.',
+'efforttrackingnotstarted'=> 'Não é possível seguir esforço de uma tarefa que já está a ser seguida',
+'efforttrackingstopped' => 'Seguir Esforço terminado.',
+'efforttrackingcancelled' => 'Seguir Esforço cancelado para esta tarefa.',
+'efforttrackingadded' => 'Esforço manual guardado.',
+'trackinginprogress' => 'A seguir esforço',
+'viewestimatedeffort' => 'Pode seguir esforço',
+'viewcurrenteffortdone' => 'Pode ver esforço despendido',
+'trackeffort' => 'Pode ver esforço',
+'invalideffort' => 'O Esforço é inválido. Tem de inserir um número.',
+'showpass' => 'Mostrar password',
+'chooseafile' => 'Por favor escolha um ficheiro!',
+'incorrectfiletype' => 'Formato de ficheiro incorreto. Permitidos: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Não há password a pedir. Registou-se através de %s',
+'addmultipletasks' => 'Adicionar tarefas múltiplas',
+'pendingnewuserrequest' => 'Pedido de Novo Utilizador pendente',
+'adminrequestswaiting' => 'Administrador pede que espere',
+'clicktoedit' => 'Clique em cada campo para editar rapidamente',
+'confirmedit' => 'confirmar',
+'canceledit' => 'cancelar',
+'regapprovedbyadmin' => 'Registos aprovados por admins (desligar código de confirmação)',
+'activity' => 'Atividade',
+'myactivity' => 'A minha Atividade',
+'emailverificationwrong' => 'A confirmação de email não coincide com o email dado',
+'verifyemailaddress' => 'Confirmar endereço de email',
+'hideemails' => 'Esconder endereço de email dos utilizadores',
+'hidemyemail' => 'Esconder o meu endereço de email',
+'exporttasklist' => 'Exportar lista de Tarefas',
+'onedecimal' => 'um décimal',
+'manday' => 'dia por pessoa',
+'mandays' => 'dias por pessoa',
+'mandayabbrev' => 'dpp',
+'hourspermanday' => 'Horas por dia por pessoa (HH:mm)',
+'itemexists' => 'Item %s já existe na base de dados.',
+'categoryitemexists' => 'Item %s já existe na categoria %s da base de dados.',
+'pageswelcomemsg' => 'Páginas onde mostrar mensagem principal de boas vindas',
+'pagesintromsg' => 'Páginas onde mostrar mensagem de boas vindas',
+'activeoauths' => 'Provedores Oauth ativos',
+'onlyoauthreg' => 'Apenas permitir registos por Oauths',
+'estimatedeffortformat' => 'Formato para mostrar esforço estimado',
+'currenteffortdoneformat' => 'Formato para mostrar esforço atual',
+'minute' => 'minuto',
+'minutes' => 'minutos',
+'minuteplural' => 'minutos',
+'minutesingular' => 'minuto',
+'minuteabbrev' => 'min',
+'hourplural' => 'horas',
+'hoursingular' => 'hora',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Esforço estimado para tarefas abertas',
+'currenteffortdoneopen' => 'Esforço dispendido em tarefas abertas',
+'signinwith' => 'Entrar com %s',
+'canviewroadmap' => 'Pode ver roteiro',
+'enableavatars' => 'Permitir avatars',
+'maxavatarsize' => 'Tamanho máximo de avatar em pixeis',
+'taskhassubtask' => 'Esta tarefa tem as seguintes sub-tarefa',
+'taskhassubtasks' => 'Esta tarefa tem as seguintes sub-tarefas',
+'translations' => 'Traduções',
+'translate' => 'Traduzir',
+'taskdescription' => 'Descrição da Tarefa',
+'notaskdescription' => 'sem descrição',
+'pleaseselect' => 'Por favor selecionar',
+'closeselectedtasks' => 'Fechar tarefas selecionadas',
+'closetasks' => 'fechar tarefas',
+'hintforbulkimport' => '<b>Dicas de importação:</b>
+ <ol>
+ <li>Copiar e colar a partir de uma folha de cálculo excel ou CSV colando uma coluna inteira.</li>
+ <li>Atualmente só pode colar Descrição e Detalhes.</li>
+ <li>Há sugestões quando nomeia alguém, e ninguém quando os nomes não batem certo.</li>
+ </ol>',
+'taskissubtaskof' => 'Esta tarefa é sub-tarefa de',
+'applyfirstline' => 'Aplicar primeira linha',
+'addmorerows' => 'Adicionar mais linhas',
+'addtasks' => 'Adicionar tarefas',
+'massopsdisabled' => 'Pedimos desculpa, edião em massa está desabilidata no Flyspray 1.0. Planeamos acabar a implementação para uma versão posterior do Flyspray. Pode ativar no condigo fonte por sua conta e risco, mas leia os comentários lá antes de o fazer.',
+'viewroadmap' => 'Pode ver rodeito',
+'nosuicide' => 'Estimado utilizado, o meu programa não permite que destrua o seu acesso ao Flyspray desativando a sua conta ou mudando o seu grupo',
+'movingtodifferentproject' => 'Mover uma tarefa que não tem parente nem sub-taregas para um projeto diferente não é permitido. Primeiro tem de apagar a ligação entre eles.',
+'musthavesameproject' => 'Parente e sub-tarefa devem pertencer ao mesmo projeto.',
+'defaultorderby' => 'Ordenar tarefas por',
+'viewowntasks' => 'Ver próprias tarefas',
+'viewgroupstasks' => 'Ver tarefas do grupo',
+'urlrewriting' => 'Reescrever Url',
+'enablehtaccess' => 'Ative o ficheiro .htaccess na raiz da sua instalação Flyspray antes de permitir que urls sejam reescritos',
+'nomodrewrite' => 'Parece que neste momento não dá para reescrever urls deste servidor',
+'on' => 'Ligado',
+'off' => 'Desligado',
+'defaultorderbydirection' => 'Ordem por defeito',
+'ascending' => 'Ordem crescente',
+'descending' => 'Ordem decrescente',
+'myassignedtasks' => 'Tarefas atribuídas a mim',
+'commentedon' => 'comentou em',
+'maxvoteperday' => 'Máximo de votos por dia',
+'votesperproject' => 'Limite de votos do utilizador por projeto',
+'votelimitreached' => 'Chegou ao limite de votos para este projeto. Veja na sua página de perfil em que tarefas está a votar. Lá pode também retirar votos. Veja quais as suas tarefas mais importantes no momento. Tarefas resolvidas não contam para o limite de votos.',
+'myvotes' => 'Meus votos',
+'tag' => 'Etiquetas',
+'tags' => 'Etiquetas',
+'tagsinfo' => 'Etiquetar tarefas no Flyspray: Separar etiquetas por ; Neste momento ainda não são usadas para pesquisar, ordenar e filtrar.',
+'novalues' => 'sem entradas',
+'youhaveregistered' => 'Registou-se no Flyspray. Os seus detalhes são os seguintes:',
+'youhaveregisterednotify' => 'O seu registo no Flyspray foi aprovada pelos Administradores.',
+'usedintasks' => 'Utilização',
+'freetagging' => 'permitir etiquetas definidas por utilizadores',
+);
+?>
diff --git a/lang/ro.php b/lang/ro.php
new file mode 100644
index 0000000..66128a9
--- /dev/null
+++ b/lang/ro.php
@@ -0,0 +1,1067 @@
+<?php
+
+$translation = array(
+'edituser' => 'Modifica utilizator',
+'accountenabled' => 'Cont activat',
+'editallusers' => 'Vezi toti utilizatorii',
+'username' => 'Utilizator',
+'usersupdated' => 'Utilizatori modificati cu succes',
+'realname' => 'Nume real',
+'emailaddress' => 'Adresa email',
+'jabberid' => 'ID Jabber',
+'profileimage' => 'Imagine profil',
+'notifytype' => 'Tip notificare',
+'group' => 'Grup',
+'enableaccounts' => 'Activeaza conturi',
+'disableaccounts' => 'Dezactiveaza conturi',
+'deleteaccounts' => 'Sterge conturi',
+'updatedetails' => 'Modifica detalii',
+'setglobally' => 'Aceasta preferinta a fost setata global.',
+'usergroupmanage' => 'Management utilizatori si grupuri de utilizatori',
+'newuser' => 'Inregistrare utilizator nou',
+'newuserbulk' => 'Inregistrare utilizatori multipli',
+'bulkuserstoadd' => 'Lista utilizatorilor noi',
+'optionsforallusers' => 'Optiuni pentru toti utilizatorii',
+'newgroup' => 'Creare grup nou',
+'yes' => 'Da',
+'no' => 'Nu',
+'editgroup' => 'Modifica grup',
+'groupname' => 'Nume grup',
+'description' => 'Descriere',
+'admin' => 'Grup administrator',
+'opennewtasks' => 'Deschide tichet nou',
+'modifytasks' => 'Modifica tichete existente',
+'addcomments' => 'Adauga comentarii',
+'attachfiles' => 'Ataseaza fisiere',
+'vote' => 'Vot',
+'groupenabled' => 'Membrii se pot autentifica',
+'groupopen' => 'Membrii se pot autentifica',
+'tasktypelist' => 'Lista tipuri tichete',
+'categorylist' => 'Lista categorii',
+'oslist' => 'Lista Sisteme de Operare',
+'resolutionlist' => 'Lista rezolvari',
+'versionlist' => 'Lista versiuni',
+'severitylist' => 'Lista severitati',
+'listnote' => 'Observatie: Debifand "arata" se pot altera anumite tichete in modul de editare. Schimband campul "Nume" va schimba toate tichetele cu acest nume. Cele care nu se pot sterge sunt fie protejate pentru o functionare corecta, fie sunt folosite deja in tichete.',
+'name' => 'Nume',
+'order' => 'Ordine',
+'back' => 'Inapoi',
+'text' => 'Text',
+'highlight' => 'Evidentiaza',
+'show' => 'Afiseaza',
+'owner' => 'Titular',
+'selectowner' => 'Selecteaza titular',
+'update' => 'Actualizeaza',
+'addnew' => 'Adaugare',
+'flysprayprefs' => 'Preferinte Flyspray',
+'projecttitle' => 'Titlu proiect',
+'baseurl' => 'URL de baza pentru aceasta instalare',
+'replyaddress' => 'Adresa de email pentru notificari',
+'themestyle' => 'Tema / Stil',
+'customstyle' => 'personalizat',
+'language' => 'Limba',
+'anonview' => 'Permite utilizatorilor anonimi sa vizualizeze tichetele',
+'allowanon' => 'Permite utilizatorilor anonimi sa adauge tichete noi',
+'never' => 'Niciodata',
+'anonymously' => 'Anonim',
+'afterregister' => 'Numai dupa inregistrare',
+'spamproof' => 'Activeaza codul de confirmare pentru inregistrarile noi',
+'anongroup' => 'Grup implicit pentru noile inregistrari de conturi',
+'groupassigned' => 'Membrilor din aceste grupuri li se pot atribui tichete',
+'forcenotify' => 'Forteaza notificarile ca si',
+'neversend' => 'Nu trimite niciodata',
+'userchoose' => 'Permite utilizatorului sa isi aleaga',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Titularul implicit pentru categorie',
+'noone' => 'Nimeni',
+'jabbernotify' => 'Notificari Jabber',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Nume utilizator',
+'jabberpass' => 'Parola',
+'saveoptions' => 'Salveaza optiuni',
+'editcomment' => 'Modifica comentariu',
+'commentby' => 'Comentariu adaugat de',
+'saveeditedcomment' => 'Salveaza',
+'projectprefs' => 'Preferinte proiect',
+'pagetitle' => 'Titlu pagina',
+'defaultproject' => 'Proiect implicit',
+'projectlists' => 'Lista proiecte',
+'showlogo' => 'Afiseaza logo',
+'showgravatars' => 'Afiseaza Gravatar',
+'emailNoHTML' => 'Fara HTML in e-mailuri',
+'intromessage' => 'Mesaj introductiv',
+'active' => 'activ',
+'inactive' => 'inactiv',
+'isactive' => 'Proiect activ',
+'showinactive' => 'Afiseaza proiecte inactive',
+'hideinactive' => 'Ascunde proiecte inactive',
+'createproject' => 'Creaza proiect nou',
+'nopermission' => 'Nu ai permisiunea sa folosesti aceasta pagina.',
+'listordertip' => 'Ordinea listarii',
+'listshowtip' => 'Afiseaza in lista',
+'categoryownertip' => 'Aceasta persoana va primi notificari cand un tichet va fi deschis in categoria aceasta',
+'categoryparenttip' => 'Va fi sub categoria',
+'notsubcategory' => 'Niciuna (categorie tip parinte)',
+'showinlineimages' => 'Afiiseaza fisierele atasate de tip imagine in linie',
+'dateformat' => 'Formatul datei',
+'dateformat_extended' => 'Formatul datei (detaliat)',
+'cache_feeds' => 'Feeduri cache',
+'no_cache' => 'Fara stocare',
+'cache_disk' => 'Stocare pe disc',
+'cache_db' => 'Stocare in DB',
+'subcategoryof' => 'Sub-categoria lui',
+'visiblecolumns' => 'Coloanele afisate in lista de tichete',
+'visiblefields' => 'Campuri la adaugarea, editarea si vizualizarea unui tichet',
+'tense' => 'Timp',
+'listtensetip' => 'Trecut, prezent sau viitor',
+'past' => 'Trecut',
+'present' => 'Prezent',
+'future' => 'Viitor',
+'oldpass' => 'Parola veche',
+'nooldpass' => 'Nu a fost completata parola veche',
+'oldpasswrong' => 'Parola veche a fost scrisa gresit',
+'changepass' => 'Schimba parola',
+'confirmpass' => 'Confirma parola',
+'projectmanager' => 'Manager Proiect',
+'viewtasks' => 'Vizualizare tichete',
+'modifyowntasks' => 'Modifica tichetele proprii',
+'modifyalltasks' => 'Modifica tichetele altora',
+'viewcomments' => 'Vizualizare comentarii',
+'editcomments' => 'Modifica comentarii',
+'deletecomments' => 'Sterge comentarii',
+'viewattachments' => 'Vizualizeaza fisiere atasate',
+'createattachments' => 'Poate atasa fisiere',
+'deleteattachments' => 'Poate sterge fisiere',
+'viewhistory' => 'Vizualizare istoric',
+'closeowntasks' => 'Inchide tichetele proprii',
+'closeothertasks' => 'Inchide tichetele altora',
+'assigntoself' => 'Isi poate atribui tichetele, daca nu sunt deja atribuite cuiva',
+'assignotherstoself' => 'Isi poate atribui tichetele altora',
+'viewreports' => 'Poate vizualiza log-ul de evenimente',
+'othersview' => 'Permite oricui sa vizualizeze tichetele din acest proiect',
+'othersviewroadmap' => 'Permite oricui sa vizualizeze harta de parcurs a acestui proiect',
+'usersandgroups' => 'Utilizatori si grupuri de utilizatori',
+'globalgroup' => 'Grup Global',
+'globalgroups' => 'Grupuri Globale',
+'defaultglobalgroup' => 'Grup global implicit pentru utilizatorii noi',
+'addtogroup' => 'Adauga in grup',
+'moveuserstogroup' => 'Muta utilizatorii in grup',
+'nogroup' => 'Fara grup - Elimina din proiect',
+'eventdesc' => 'Descriere eveniment',
+'requestedby' => 'Persoana',
+'daterequested' => 'Data',
+'closetask' => 'Inchide tichet',
+'reopentask' => 'Redeschide tichet',
+'applymember' => 'Cere sa fie membru al proiectului',
+'forcurrentproj' => 'Pentru proiectul curent',
+'lostpw' => 'Recuperare parola',
+'lostpwexplain' => 'Completeaza numele de utilizator pentru trimiterea unui link in vederea schimbarii parolei. Acest link va fi trimis la adresa de email din profilul tau.',
+'sendlink' => 'Trimite link',
+'savenewpass' => 'Salveaza noua parola',
+'anonreg' => 'Permite inregistrarea de utilizatori noi',
+'allowanonopentask' => 'Permite utilizatorilor anonimi sa adauge tichete noi',
+'editglobalgroup' => 'Modifica grup global',
+'editgroupforproj' => 'Modifica grup proiect',
+'notshownforadmin' => 'Permisiunile nu sunt afisate pentru grupul de administratori. Nu este necesar sa faceti modificari.',
+'general' => 'General',
+'userregistration' => 'Inregistrare utilizatori noi',
+'notifications' => 'Notificari',
+'resetoptions' => 'Anuleaza modificari',
+'preferences' => 'Preferinte',
+'tasktypes' => 'Tipuri de tichete',
+'resolutions' => 'Rezolvari',
+'categories' => 'Categorii',
+'operatingsystems' => 'Sisteme de Operare',
+'versions' => 'Versiuni',
+'admintoolboxlong' => 'Panoul administratorului',
+'newproject' => 'Proiect nou',
+'delete' => 'Sterse',
+'link' => 'Link',
+'referencelinks' => 'Linkuri de referinta:',
+'listdeletetip' => 'Sterge din lista',
+'lookandfeel' => 'Aspect',
+'globaltheme' => 'Tema globala',
+'emailnotify' => 'Notificari email',
+'fromaddress' => 'De la adresa email',
+'smtpserver' => 'Server SMTP',
+'smtpuser' => 'Utilizator SMTP',
+'smtppass' => 'Parola SMTP',
+'addrewrite' => 'Rescrie adresele',
+'usereminderdaemon' => 'Porneste serviciul de tip reminder',
+'tasksperpage' => 'Nr. de tichete in lista de tichete',
+'addtoassignees' => 'Adauga la lista de persoane atribuite',
+'taskstatuses' => 'Statusuri tichete',
+'canvote' => 'Poate vota in tichete',
+'loginsuccessful' => 'Autentificare reusita.',
+'youareloggedout' => 'Deautentificare reusita.',
+'waitwhiletransfer' => 'Te rog sa astepti cat timp esti transferat...',
+'clicknowait' => 'Click aici daca nu doresti sa astepti.',
+'accountdisabled' => 'Contul tau e dezactivat. Contacteaza un administrator de sistem.',
+'task' => 'Tichet',
+'edittask' => 'Modifica tichet',
+'openedby' => 'Deschis de',
+'editedby' => 'Ultima modificare',
+'tasktype' => 'Tip tichet',
+'category' => 'Categorie',
+'status' => 'Status',
+'assignedto' => 'Persoana atribuita',
+'operatingsystem' => 'Sistem Operare',
+'severity' => 'Severitate',
+'reportedversion' => 'Raportat in versiunea',
+'dueinversion' => 'De rezolvat pana in versiunea',
+'defaultdueinversion' => 'Versiunea implicita de rezolvat a noilor tichete',
+'undecided' => 'Nedecis',
+'percentcomplete' => 'Procentaj',
+'details' => 'Descriere',
+'savedetails' => 'Salveaza',
+'canceledit' => 'Renunta la modificari',
+'anonymous' => 'Trimis de anonim',
+'complete' => 'complet',
+'closedby' => 'Inchis de',
+'reasonforclosing' => 'Motiv inchidere:',
+'reopenthistask' => 'Redeschide tichetul',
+'comments' => 'Comentarii',
+'attachments' => 'Fisiere atasate',
+'relatedtasks' => 'Tichete similare',
+'edit' => 'Modifica',
+'addcomment' => 'Adauga raspuns',
+'fileuploadedby' => 'Fisier atasat de',
+'uploadafile' => 'Ataseaza fisier',
+'addalink' => 'Adauga un link',
+'addanotherlink' => 'Adauga alt link',
+'uploadnow' => 'Incarca',
+'thesearerelated' => 'Aceste tichete sunt inrudite cu acest tichet',
+'remove' => 'Elimina',
+'addnewrelated' => 'Noteaza un tichet similar cu acesta sau un duplicat al acestuia',
+'add' => 'Adauga',
+'otherrelated' => 'Alte tichete cu care acesta este inrudit',
+'receivenotify' => 'Acesti utilizatori vor primi notificari detaliate cand acest tichet sufera modificari.',
+'addusertolist' => 'Adauga persoana la lista de notificari',
+'addtolist' => 'Adauga in lista',
+'addmyself' => 'Adauga-ma in lista',
+'removemyself' => 'Elimina-ma din lista',
+'theseusersnotify' => 'Urmatoarele persoane vor primi informatii detaliate pe mail atunci cand apar noutati la acest tichet.',
+'attachedtoproject' => 'Postat in',
+'reminders' => 'Atentionari',
+'system' => 'Sistem',
+'systemvalues' => 'Valori comune sistem',
+'projectvalues' => 'Valori specifice proiectului',
+'remindthisuser' => 'Reaminteste-i acestui utilizator',
+'thisoften' => 'Des',
+'startafter' => 'Timpul de asteptare inainte de a trimite atentionare',
+'hour' => 'ora',
+'hours' => 'ore',
+'day' => 'zi',
+'days' => 'zile',
+'week' => 'saptamana',
+'weeks' => 'saptamani',
+'addreminder' => 'Adauga atentionare',
+'defaultreminder' => 'Aceasta este o atentionare pentru a verifica urmatorul tichet:',
+'message' => 'Mesaj',
+'closed' => 'Inchis',
+'filename' => 'Fisier:',
+'date' => 'Data',
+'filesize' => 'Marime fisier:',
+'closurecomment' => 'Comentarii aditionale cu privire la solutionare:',
+'history' => 'Istoric',
+'nohistory' => 'Nu este disponibil istoricul.',
+'eventdate' => 'Data',
+'user' => 'Utilizator',
+'event' => 'Eveniment',
+'fieldchanged' => 'Camp modificat',
+'taskopened' => 'Tichet deschis',
+'taskreopened' => 'Tichet redeschis',
+'taskclosed' => 'Tichet inchis',
+'commentadded' => 'Comentariu adaugat',
+'commentedited' => 'Commentariu editat',
+'commentdeleted' => 'Comentariu sters',
+'attachmentadded' => 'Fisier atasat',
+'attachmentdeleted' => 'Fisier sters',
+'taskedited' => 'Detaliile tichetului au fost modificate',
+'notificationadded' => 'Persoana adaugata in lista de notificari',
+'notificationdeleted' => 'Persoana eliminata din lista de notificari',
+'relatedadded' => 'Tichet similar - dependenta adaugata',
+'relateddeleted' => 'Tichet similar - dependenta eliminata',
+'taskassigned' => 'Tichetul a fost alocat ',
+'taskreassigned' => 'Tichetul a fost realocat',
+'assignmentremoved' => 'Atribuirea a fost eliminata',
+'summary' => 'Sumar',
+'addedasrelated' => 'Tichetul este similar cu',
+'deletedasrelated' => 'Tichetul este nu mai e similar cu',
+'reminderadded' => 'Atentionare adaugata',
+'reminderdeleted' => 'Atentionare eliminata',
+'priority' => 'Prioritate',
+'previousvalue' => 'Valoare anterioara',
+'newvalue' => 'Valoare noua',
+'selectareason' => 'Selectare motiv',
+'assigntome' => 'Atribuie-ma',
+'reopenrequest' => 'Redeschide tichet',
+'requestclose' => 'Inchide tichet',
+'ownershiptaken' => 'Tichetul este preluat de',
+'closerequestmade' => 'Cere inchidere tichet',
+'reopenrequestmade' => 'Cere redeschidere tichet',
+'taskdependson' => 'Tichetul are urmatoarele dependente',
+'taskdependsontask' => 'Tichetul are urmatoarele dependente',
+'taskdependsontasks' => 'Tichetul are urmatoarele dependente',
+'taskblock' => 'Acest tichet nu permite inchiderea urmatoarelor tichete:',
+'taskblocks' => 'Acest tichet nu permite inchiderea urmatoarelor tichete:',
+'depadded' => 'Dependenta adaugata',
+'depaddedother' => 'Tichetul a fost adaugat ca dependenta de tichetul',
+'depremoved' => 'Dependenta eliminata',
+'depremovedother' => 'Tichet eliminat din alta lista de tichete',
+'showdetailserror' => 'Acest tichet nu exista sau nu ai permisiunea de a il vizualiza.',
+'makeprivate' => 'fa privat',
+'makepublic' => 'fa public',
+'taskmadeprivate' => 'Tichet facut privat',
+'taskmadepublic' => 'Tichetul a fost facut public',
+'confirmdeletecomment' => 'Esti sigur ca doresti sa stergi acest comentariu? %s',
+'attachementswilldeleted' => 'Toate fisierele atasate vor fi sterse!',
+'confirmdeleteattach' => 'Esti sigur ca doresti sa stergi fisierul atasat?',
+'selectedhistory' => 'Afiseaza detaliile istoricului',
+'showallhistory' => 'Afiseaza istoricul complet din nou',
+'hidethis' => 'Ascunde aceasta zona',
+'mark100' => 'Marcheaza tichetul cu procentaj 100%',
+'watchtask' => 'urmareste tichet',
+'stopwatching' => 'Nu mai urmari',
+'commentlink' => 'Link catre comentariu',
+'submitreq' => 'Trimite',
+'reasonforreq' => 'Comentarii rezolvare',
+'pmreqdenied' => 'Nu s-a permis modificarea tichetului',
+'taskpendingreq' => 'Se asteapta o actiune din partea Managerului de Proiect. Vezi istoricul pentru detalii',
+'previoustask' => '< Tichetul anterior ',
+'nexttask' => 'Tichetul urmator >',
+'duedate' => 'Pana la data',
+'attachnoperms' => 'Sunt fisiere atasate acestui comentariu, dar nu ai permisiunea de a le vizualiza.',
+'linknoperms' => 'Sunt linkuri in acest comentariu, dar nu ai permisiunea de a le vizualiza.',
+'open' => 'Deschise',
+'depgraph' => 'Trasabilitate',
+'reset' => 'Reseteaza',
+'selectusers' => 'Selecteaza persoane...',
+'addmetoassignees' => 'Atribuie-ma (+1)',
+'addedtoassignees' => 'Persoana a preluat tichetul',
+'dependencygraph' => 'Grafic dependente',
+'attachanotherfile' => 'Ataseaza alt fisier',
+'OK' => 'OK',
+'addvote' => 'Adauga vot',
+'disable_lostpw' => 'Dezactiveaza formularul de recuperare parola',
+'disable_changepw' => 'Dezactiveaza posibilitatea de a crea sau edita o parola',
+'notifyfromfs' => 'Notificare de la FlySpray',
+'autogenerated' => 'ACESTA ESTE UN MESAJ GENERAT AUTOMAT DE SISTEM. VA RUGAM SA NU RASPUNDETI.',
+'forward' => 'Inainte',
+'previous' => 'Anterior',
+'next' => 'Urmator',
+'first' => 'Primul',
+'last' => 'Ultimul',
+'page' => 'Pagina %d din %d',
+'search' => 'Cauta',
+'alltasktypes' => 'Toate',
+'allseverities' => 'Toate severitatile',
+'alldevelopers' => 'Toti dezvoltatorii',
+'notyetassigned' => 'Fara atribuire',
+'allcategories' => 'Toate categoriile',
+'allstatuses' => 'Toate statusurile',
+'allopentasks' => 'Toate tichetele deschise',
+'sortthiscolumn' => 'Sorteaza dupa aceasta coloana',
+'id' => 'ID',
+'project' => 'Proiect',
+'dateopened' => 'Data deschidere',
+'progress' => 'Progres',
+'searchthisproject' => 'Cauta',
+'dueanyversion' => 'Pana in orice versiune',
+'anyversion' => 'Raportat in orice versiune',
+'dueversion' => 'Pana in versiunea',
+'lastedit' => 'Ultimul raspuns',
+'os' => 'Sistem operare',
+'reportedin' => 'Raportat in',
+'taskrange' => 'Arata %d - %d din %d tichete.',
+'noresults' => 'Cautarea dvs. nu a returnat rezultate.',
+'takeaction' => 'Trimite',
+'watchtasks' => 'Urmareste tichetele selectate',
+'stopwatchingtasks' => 'Nu mai urmari tichetele selectate',
+'assigntaskstome' => 'Atribuie-mi tichetele selectate',
+'dueby' => 'Pana la',
+'dueanytime' => 'Oricand',
+'selectduedate' => 'Selecteaza data de final',
+'toggleselected' => 'Mod selectie activat',
+'due' => 'Pana',
+'assignedtome' => 'Atribuite mie',
+'tasklist' => 'Tichete',
+'dateclosed' => 'Data inchiderii',
+'advanced' => 'Cautare avansata',
+'searchcomments' => 'Cauta in comentarii',
+'searchforall' => 'Cauta dupa cuvinte',
+'anonusers' => 'Utilizatori anonimi',
+'miscellaneous' => 'Diverse',
+'users' => 'Utilizatori',
+'taskproperties' => 'Proprietati tichet',
+'selectsincedate' => 'Selecteaza Schimbat de la',
+'changedsince' => 'Schimbat de la',
+'updatefs' => 'Te rugam sa actualizezi FlySpray.',
+'currentversion' => 'Versiunea folosita este',
+'latestversion' => 'iar cea mai recenta versiune este',
+'hidemessage' => '(aminteste-mi mai tarziu)',
+'saveas' => 'Salveaza template cautare',
+'nosearches' => 'Nu exista template-uri',
+'saving' => 'Salvez...',
+'votes' => 'Voturi',
+'tovote' => 'Voteaza',
+'allclosedtasks' => 'Toate tichetele inchise',
+'password' => 'Parola',
+'login' => 'Autentificare',
+'rememberme' => 'Tine minte',
+'lostpassword' => 'Parola uitata?',
+'lostpwforfs' => 'Parola uitata pentru FlySprayLost password for Flyspray',
+'lostpwmsg1' => 'Salutare! Mi-am uitat parola.',
+'lostpwmsg2' => 'Te rog sa imi trimiti o noua parola pentu utilizatorul: ',
+'regards' => 'Multumiri,',
+'yourusername' => ' numele tau de utilizator ',
+'locale' => 'ro-RO',
+'filenotexist' => 'Fisierul nu exista sau nu ai permisiunea de a il accesa.',
+'showtask' => 'Vezi tichet',
+'now' => 'Acum',
+'go' => 'Du-te!',
+'opentaskanon' => 'Deschide un tichet nou in mod anonim',
+'register' => 'Inregistrare cont nou',
+'addnewtask' => 'Adauga tichet nou',
+'reports' => 'Rapoarte',
+'editmydetails' => 'Modifica detalii',
+'logout' => 'Deautentificare',
+'disabledaccount' => 'Contul tau a fost dezactivat!<br />Vi se face deautentificarea...',
+'poweredby' => 'Conceput de FlySpray',
+'sponsoredby' => 'Flyspray este sponsorizat de',
+'projects' => 'Proiecte',
+'allprojects' => 'Toate proiectele',
+'selectproject' => 'pentru proiectul:',
+'tasksall' => 'Toate tichetele',
+'tasksassigned' => 'Tichete atribuite mie',
+'tasksreported' => 'Tichete deschise de mine',
+'taskswatched' => 'Tichete urmarite',
+'mysearch' => 'Cautarile mele',
+'admintoolbox' => 'Panou administrare',
+'manageproject' => 'Panou administrare proiect',
+'permissions' => 'Permisiuni',
+'hide' => 'Ascunde',
+'pendingreq' => 'tichet(e) care necesita atentia ta',
+'errorpage' => 'FlySpray nu poate afisa pagina ceruta.
+ Este posibil ca tichetul sa nu existe sau nu ai
+ permisiunea de a vizualiza pagina pe care ai cerut-o.<br /><br />
+ Se poate sa fi incercat sa folosesti un URL pentru a interoga baza de date
+ prin spate. Daca e adevarat, pune-te la colt cu mainile la spate si
+ fa-ti o mustrare de constinta. Cand te intorci pe aici, sa nu mai faci asta!',
+'permissionsforproject' => 'Permisiuni pentru ',
+'switchto' => 'Schimba la',
+'lastsearch' => 'Ultima cautare',
+'modify' => 'Modifica',
+'noticefrom' => 'Notificare de la',
+'hasopened' => 'a deschis un tichet nou si ti l-a atribuit tie:',
+'moreinfonew' => 'Poti gasi mai multe informatii la pagina:',
+'newtaskcategory' => 'Un nou tichet a fost deschis in aceasta categorie',
+'categoryowner' => 'Primesti acest mail deoarece figurezi ca titular de categorie.',
+'tasksummary' => 'Sumar tichet:',
+'newtaskadded' => 'Tichetul a fost adaugat.',
+'summaryanddetails' => 'Este necesar sa completezi atat sumarul problemei cat si niste detalii.',
+'summaryrequired' => 'Este necesar sa completezi campul Sumar.',
+'goback' => 'Inapoi.',
+'messagefrom' => 'Acesta este un mesaj de la platforma Flyspray ',
+'hasjustmodified' => 'a modificat tichetul urmator',
+'changedfields' => 'Campurile modificate sunt completate cu (**) inainte',
+'moreinfomodify' => 'Poti primi mai multe informatii despre tichet dand click pe urmatorul link:',
+'nolongerassigned' => 'Tichetul urmator nu iti mai este atribuit. Acum este atribuit lui',
+'hasassigned' => 'ti-a atribuit urmatorul tichet:',
+'taskupdated' => 'Tichetul a fost actualizat.',
+'tasksupdated' => 'Tichetele au fost actualizate.',
+'hasclosedassigned' => 'a inchis tichetul la care ai fost atribuit:',
+'unassigned' => 'Neatribuit',
+'hasclosed' => 'a inchis tichetul.',
+'youonnotify' => 'Primesti acest mail deoarece esti in lista de notificari.',
+'taskclosedmsg' => 'Tichetul a fost inchis.',
+'returntotask' => 'Intoarce-te la detaliile tichetului',
+'backtoindex' => 'Intoarce-te la lista de tichete',
+'noclosereason' => 'Nu ai selectat un motiv pentru a inchide acest tichet.',
+'hasreopened' => 'a redeschis tichetul pe care l-ai inchis:',
+'taskreopenedmsg' => 'Tichetul a fost redeschis.',
+'backtotask' => 'Inapoi la tichet.',
+'commentaddedmsg' => 'Raspunsul a fost adaugat.',
+'commenttoassigned' => 'a adaugat un comentariu unui tichet la care esti atribuit:',
+'commenttotask' => 'a adaugat urmatorul comentariu la acest tichet.',
+'nocommententered' => 'Ar trebui sa scrii ceva in zona de comentarii inainte de a apasa butonul.',
+'fillinfields' => 'Nu ai completat toate campurile.',
+'notcurrentpass' => 'Aceasta nu este parola curenta!',
+'passchanged' => 'Parola a fost schimbata.',
+'closewindow' => 'Poti inchide aceasta fereastra.',
+'passnomatch' => 'Parolele noi nu sunt aceleasi!',
+'usernametaken' => 'Acest nume de utilizator exista. Va trebui sa iti alegi altul',
+'usernametakenbulk' => 'Numele de utilizator exista deja',
+'newusercreated' => 'Contul a fost creat.',
+'accountcreated' => 'Contul tau a fost creat.',
+'newuserwarning' => 'Este posibil ca acest cont sa fie aprobat de catre administrator. Daca nu te poti conecta, se poate ca acesta sa fie motivul.',
+'nomatchpass' => 'Parolele nu sunt aceleasi.',
+'confirmwrong' => 'Codul de confirmare nu este corect!',
+'formnotcomplete' => 'Formularul nu a fost completat in intregime.',
+'formnotnumeric' => 'Datele inserate nu sunt de tip numeric!',
+'groupnametaken' => 'Acest grup exista deja.',
+'newgroupadded' => 'Grup nou adaugat.',
+'optionssaved' => 'Optiunile Flyspray salvate.',
+'hasuploaded' => 'a atasat un fisier la un tichet la care esti atribuit:',
+'hasattached' => 'a atasat un fisier urmatorului tichet.',
+'fileuploaded' => 'Fisierul a fost atasat.',
+'fileerror' => 'A fost o eroare la atasarea fisierului. E posibil ca drepturile de scriere pentru directorul <i>attachments/</i> nu sunt corecte.',
+'contactadmin' => 'Contacteaza administratorul pentru acest proiect.',
+'selectfileerror' => 'Nu ai selectat un fisier.',
+'userupdated' => 'Detaliile utilizatorului au fost actualizate',
+'realandemail' => 'Nu ai completat Numele Real si Adresa Email',
+'groupupdated' => 'Definitiile de grup au fost actualizate.',
+'groupanddesc' => 'Nu ai completat numele grupului.',
+'fillallfields' => 'Completeaza toate campurile.',
+'listPmustN' => '"Ordine" trebuie sa fie de tip numeric.',
+'listupdated' => 'Lista a fost modificata.',
+'listitemadded' => 'S-a adaugat elementul nou.',
+'relatedaddedmsg' => 'Tichetul inrudit a fost adaugat.',
+'relatederror' => 'Acest tichet exista deja in lista tichetelor inrudite.',
+'relatedremoved' => 'Tichetul inrudit a fost eliminat din lista.',
+'notifyadded' => 'Adaugare persoana la lista de notificari',
+'notifyerror' => 'Persoana este deja in lista de notificari pentru acest tichet.',
+'notifyremoved' => 'Persoana eliminata din lista de notificari',
+'editcommentsaved' => 'Salvarile comentariului s-au facut.',
+'commentdeletedmsg' => 'Comentariul a fost sters.',
+'gotonewtask' => 'Du-te la tichetul pe care tocmai l-ai deschis',
+'projectcreated' => 'Proiectul a fost creat. Poti face acum personalizarile dorite.',
+'customiseproject' => 'Personalizeaza acest proiect',
+'projectupdated' => 'Preferintele proiecfului au fost actualizate',
+'emptytitle' => 'Nu ai completat titlul proiectului!',
+'loginbelow' => 'Poti incerca sa te autentifici.',
+'attachmentdeletedmsg' => 'Fisierul atasat a fost sters',
+'reminderaddedmsg' => 'Atentionarea a fost adaugata.',
+'reminderdeletedmsg' => 'Atentionarea selectata a fost stearsa.',
+'flyspraytask' => 'Tichet Flyspray',
+'fieldsmissing' => 'Unele campuri nu au fost completate sau au fost completate gresit.',
+'relatedinvalid' => 'Nu exista acest tichet.',
+'relatedproject' => 'Acest tichet este atasat altui proiect. Doresti sa faci faci o legatura intre ele?',
+'addanyway' => 'Adauga oricum',
+'cancel' => 'Renunta',
+'alreadyedited' => 'Acest tichet a suferit modificari inainte ca tu sa salvezi modificarile. Inca doresti sa salvezi modificarile facute doar de tine?',
+'saveanyway' => 'Salveaza modificarile mele',
+'nouserselected' => 'Niciun utilizator selectat. Selecteaza macar un utilizatori inainte sa incerci din nou.',
+'groupswitchupdated' => 'Grupurile de utilizatori au fost modificate cu succes.',
+'takenownershipmsg' => 'Tichetul a fost atribuit.',
+'adminrequestmade' => 'Cererea ta a fost trimisa unui Manager de Proiect.',
+'newdepnotify' => 'O dependenta a fost adaugata tichetului urmator:',
+'dependadded' => 'Dependenta de tichet a fost adaugata',
+'dependaddfailed' => 'A fost o problema la adaugarea acestei dependente. Verifica daca respectivul tichet exista sau daca nu cumva se blocheaza reciproc.',
+'depremovedmsg' => 'Dependenta a fost eliminata',
+'newdepis' => 'Noua dependenta este',
+'magicurlsent' => 'Un mesaj a fost trimis la adresa de notificare. Acest mesaj contine un link care te va trimite catre pagina in care exista respectivul tichet.',
+'changefspass' => 'Schimba parola',
+'magicurlmessage' => 'Te rugam sa dai click pe linkul de mai jos pentru a iti schimba parola:',
+'erroronform' => 'A fost o problema la trimiterea formularului',
+'addressused' => 'Aceasta adrea a fost folosita pentru crearea unui cont de utilizator FlySpray. Daca nu asteptai acest mail, ignora-l si sterge-l. Da click pe urmatorul link pentru a finaliza procesul de inregistrare:',
+'confirmcodeis' => 'Codul de confirmare este:',
+'codesent' => 'Codul de confirmare a fost trimis. Te rugam sa urmezi instructiunile din mailul trimis.',
+'codenotsent' => 'Nu putem trimite codul. Incearca mai tarziu.',
+'taskmadeprivatemsg' => 'Tichetul a fost facut privat',
+'taskmadepublicmsg' => 'Tichetul a fost facut public din nou',
+'realandnotify' => 'Trebuie sa completezi campul Nume Real si fie Adresa Email, fie ID Jabber.',
+'pmreqdeniedmsg' => 'Cererea catre Managerul de Proiect a fost refuzata',
+'massopsuccess' => 'Operatiuni in masa cu succes - acolo unde exista permisiuni',
+'usernotexist' => 'Utilizatorul completat in formularul de conectare nu exista in baza noastra de date. Va rugam sa verificati daca ati introdus numele de utilizator corect.',
+'commentattachperms' => 'Nu pot sterge acel comentariu - nu ai permisiunea de a sterge fisiere atasate.',
+'voterecorded' => 'Votul tau a fost inregistrat',
+'votefailed' => 'Votul tau nu a putut fi inregistrat in acest moment.',
+'createnewgroup' => 'Creaza grup nou',
+'requiredfields' => 'Campurile obligatorii sunt marcate cu',
+'addthisgroup' => 'Adauga acest grup',
+'createnewproject' => 'Creaza un nou proiect',
+'addnewproject' => 'Adauga proiect nou',
+'htmlallowed' => 'Cod HTML permis',
+'createthisproject' => 'Creaza acest proiect',
+'inlineimages' => 'Afiseaza imaginile atasate in linie',
+'createnewtask' => 'Creaza un nou tichet in proiect:',
+'addanother' => 'Adauga alt tichet dupa acesta',
+'addthistask' => 'Adauga tichet',
+'notifyme' => 'Anunta-ma pe mail cand acest tichet are modificari',
+'newtask' => 'Tichet nou',
+'attachafile' => 'Ataseaza fisier',
+'registernewuser' => 'Inregistreaza utilizator nou',
+'none' => 'Nimic',
+'registeraccount' => 'Inregistreaza acest cont',
+'registerbulkaccount' => 'Inregistreaza conturi',
+'both' => 'Ambele',
+'notifyfrom' => 'Notificare de la ',
+'donotreply' => 'MESAJ GENERAT AUTOMAT DE SISTEM, VA RUGAM SA NU RASPUNDETI LA EL!',
+'disclaimer' => 'Acest mesaj il primiti pentru a fi la curent cu modificarile aduse la tichetul pe care l-ati postat.',
+'userwho' => 'Utilizatorul / utilizatorii',
+'moreinfo' => 'Pentru a vizualiza tichetul, apasati pe linkul de mai jos:',
+'newtaskopened' => 'Pe platforma CLB-Request a fost deschis un nou tichet. Detaliile acestuia sunt urmatoarele:',
+'notify.taskclosed' => 'Tichetul de mai jos a fost inchis:',
+'notify.taskreopened' => 'Tichetul de mai jos a fost redeschis:',
+'newdep' => 'Tichetul de mai jos are un alt tichet marcat ca duplicat:',
+'notify.depremoved' => 'Tichetul de mai jos nu mai este marcat ca duplicat:',
+'olddepwas' => 'Vechea dependenta era',
+'notify.commentadded' => 'Tichetului urmator i s-a adaugat un nou comentariu:',
+'commentis' => 'Continutul comentariului este mai jos.',
+'newattachment' => 'Un fisier a fost atasat urmatorului tichet:',
+'detailsbelow' => 'Detaliile sunt mai jos.',
+'notify.relatedadded' => 'Un tichet similar celui de mai jos a fost postat:',
+'relatedis' => 'Tichetul similar este',
+'assignedtoyou' => 'Ai fost atribuit urmatorului tichet:',
+'takenownership' => 'a luat conducerea urmatorului tichet:',
+'requiresaction' => 'Urmatorul tichet necesita interventia unui Manager de Proiect:',
+'requiresactionnotify' => 'Tichetul necesita interventia unui Manager de Proiect',
+'pmdeny' => 'Managerul de proiect a refuzat cererea aflata in asteptare la urmatorul tichet:',
+'pmdenynotify' => 'Managerul de proiect a refuzat cererea',
+'fileaddedtoo' => 'Unul sau mai multe fisiere au fost atasate.',
+'taskwatching' => 'Tichetul urmator este urmarit de tine',
+'isdepfor' => 'este dependenta de',
+'denialreason' => 'Motivul respingerii',
+'taskchanged' => 'Tichetul dvs. a fost modificat. Pentru un scurt raport cu privire la acest tichet, accesati linkul de mai jos si apasati pe butonul Raport tichet.',
+'useraddedtoassignees' => 'Un utilizator a fost atribuit urmatorului tichet.',
+'removeddepis' => 'Dependenta eliminata este',
+'isnodepfor' => 'nu mai este dependenta pentru',
+'usergroups' => 'Grupuri de utilizatori',
+'pmtoolbox' => 'Panou administrare Man. Proiect',
+'groupmanage' => 'Administrare grup',
+'pendingrequests' => 'Asteapta interventii',
+'reasongiven' => 'Motiv?',
+'nopendingreq' => 'Nu este nimic care sa necesite interventia ta.',
+'givereason' => 'Ofera un motiv',
+'catlisted' => 'Modificare lista categorii',
+'oslisted' => 'Modificare lista Sisteme Operare',
+'verlisted' => 'Modificare lista versiuni',
+'tasktypeed' => 'Modificare lista tipuri tichete',
+'resed' => 'Modificare lista rezolvari',
+'deny' => 'Respinge',
+'notifiedwhen' => 'Notificare cand',
+'onlynewtasks' => 'Noi tichete sunt deschise',
+'allevents' => 'Orice eveniment care apare in orice tichet',
+'feeds' => 'Feeduri',
+'feeddescription' => 'Descriere feed',
+'feedimgurl' => 'URL imagine feed (lasa necompletat daca nu doresti imagine)',
+'notifysubject' => 'Subiect notificari',
+'notifysubjectinfo' => '(%p = nume proiect, %s = sumar tichet, %t = id tichet, %a = actiune, %u = utilizator)',
+'priority6' => 'Urgent',
+'priority5' => 'Foarte mare',
+'priority4' => 'Medie',
+'priority3' => 'Mica',
+'priority2' => 'Foarte mica',
+'priority1' => 'Fara prioritizare',
+'sendcode' => 'Trimite cod!',
+'entercode' => 'Introdu codul primit in mesajul de notificare. Dealtfel, introdu si parola dorita.',
+'confirmationcode' => 'Cod confirmare',
+'registererror' => 'Asigura-te ca ai completat toate campurile obligatorii si ca ai introdus detaliile necesare pentru tipul de notificare dorit.',
+'validusername' => '(numai caracterele alfanumerice si - _ . sunt permise)',
+'validemail' => '(foloseste ; pentru a diferentia adresele de mail)',
+'emailtakenbulk' => 'Adresa de email este deja folosita',
+'emailtaken' => 'Aceasta adresa de email sau ID-Jabber este deja folosita. Trebuie sa folosesti alta.',
+'note' => '<strong>Observatie:</strong> Vei primi un cod de confirmare inainte ca contul de utilizator sa fie creat. Codul va fi trimis folosind tipul de notificare ales mai sus.<br />Daca nu introduci detaliile corecte, <strong>nu vei primi codul</strong>.',
+'changelog' => 'Log modificari',
+'changeloggen' => 'Generator log modificari',
+'listfrom' => 'Afiseaza logul de modificari de la',
+'to' => 'pana la',
+'oldestfirst' => 'Cel mai vechi primul',
+'recentfirst' => 'Cel mai recent primul',
+'severityrep' => 'Raport severitate',
+'totalopen' => 'Total tichete deschise',
+'age' => 'Varsta',
+'agerep' => 'Raport varsta',
+'eventsrep' => 'Raport evenimente',
+'events' => 'Evenimente',
+'Tasks' => 'Tichete',
+'opened' => 'Deschise',
+'edited' => 'Editate',
+'assigned' => 'Atribuite',
+'within' => 'Pana in',
+'pastday' => 'Ziua anterioara',
+'pastweek' => 'Saptamana anterioara',
+'pastmonth' => 'Luna anterioara',
+'pastyear' => 'Anul anterior',
+'nolimit' => 'Fara limite',
+'from' => 'De la',
+'duein' => 'Pana in',
+'selectfromdate' => 'Selecteaza de la data',
+'selecttodate' => 'Selecteaza pana la data',
+'showvoters' => 'Arata/ascunde voturi',
+'roadmap' => 'Harta',
+'roadmapfor' => 'Harta pentru versiunea',
+'tasks' => 'tichete',
+'completed' => 'completate.',
+'opentasks' => 'tichete deschise',
+'of' => '% din',
+'severity5' => 'Critica',
+'severity4' => 'Mare',
+'severity3' => 'Normala',
+'severity2' => 'Mica',
+'severity1' => 'Foarte mica',
+'Redirect' => 'Redirectare',
+'redirectmsg' => 'Daca browserul nu permite redirectionarile, apasa %sAICI%s sa fii redirectionat',
+'allowclosedcomments' => 'Permite comentarii noi la un tichet inchis',
+'comment' => 'Comentariu',
+'editowncomments' => 'Modifica propriile comentarii',
+'reopened' => 'Redeschis',
+'loading' => 'Incarcare...',
+'notifyown' => 'Notifica pentru schimbarile proprii',
+'youremail' => 'Adresa ta de e-mail',
+'thankyouforbug' => 'Multumim pentru ca ati raportat problema. Puteti vizualiza situatia tichetului si progresul acestuia oricand dand click pe urmatorul link:',
+'anonuser' => 'Utilizator anonim',
+'conflict' => 'Conflict',
+'file' => 'Fisier',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => 'Marime',
+'projectgroup' => 'Grup proiect',
+'profile' => 'Profil:',
+'viewprofile' => 'Vezi profil',
+'regdate' => 'Inregistrat din',
+'tasksopened' => 'Tichete deschise',
+'replyto' => 'Raspunde lui',
+'notifytypes' => 'Tip notificari',
+'pm.taskchanged' => 'Tichet modificat',
+'pm.taskreopened' => 'Tichet redeschis',
+'pm.depadded' => 'Dependenta adaugata',
+'pm.depremoved' => 'Dependenta eliminata',
+'pmrequest' => 'Cerere Manager Proiect',
+'pmrequestdenied' => 'Cerere Manager Proiect respinsa',
+'newassignee' => 'Persoana noua atribuita',
+'revdepadded' => 'Dependenta in oglinda adaugata',
+'revdepaddedremoved' => 'Dependenta in oglinda eliminata',
+'assigneeadded' => 'Persoana atribuita',
+'addusergroup' => 'Adauga utilizator la acest grup',
+'groupmembers' => 'Membrii grupului',
+'deleteuser' => 'Sterge acest utilizator',
+'userdeleted' => 'Utilizator sters',
+'autoassign' => 'Atribuie automat un tichet la titularul de categorie',
+'ssl' => 'SSL',
+'updatewrong' => 'Ai optiunea de verificare daca exista update-uri, insa o eroare a aparut in incercarea
+ de a contacta serverul de update, hostul tau nu permite conectarile catre alte surse externe
+ sau eroarea a fost cauzata de o problema de retea.
+ Te rugam sa accesezi site-ul FlySpray pentru a fi sigur ca folosesti ultima versiune aparuta.',
+'deleteproject' => 'Sterge acest proiect si muta continutul in',
+'projectdeleted' => 'Proiectul a fost sters cu succes',
+'feedforall' => 'Feed pentru toate proiectele',
+'usercreated' => 'Utilizator creat',
+'userdeleted' => 'Utilizator sters',
+'created' => 'Creat',
+'deleted' => 'Sters',
+'userid' => 'ID utilizator',
+'editassignments' => 'Modifica atribuirile',
+'preview' => 'Previzualizare',
+'anyprogress' => 'Orice progres',
+'tasksrelated' => 'Tichetele asemanatoare',
+'duplicatetasks' => 'Tichetele duplicate',
+'databasemodfailed' => 'Modificarea bazei de date a esuat. Posibil sa nu existe suficiente permisiuni.',
+'frequency' => 'Frecventa',
+'newuserregistered' => 'Un utilizator nou s-a inregistrat. Detaliile sunt urmatoarele:',
+'newuserregisterednotify' => 'Un nou utilizator s-a inregistrat',
+'notify_registration' => 'Notifica administratorii cand apare o noua inregistrare de utilizator',
+'textversion' => 'Versiune Text',
+'onlyprimary' => 'Tichete care nu blocheaza alte tichete',
+'onlyblocker' => 'Tichete care blocheaza alte tichete',
+'blockerornoblocker' => 'Blocheaza sau nu, selectare ambelor optiuni de filtrare nu au niciun sens.',
+'switch' => 'Schimba',
+'max' => 'max.',
+'dates' => 'Data',
+'selectduedatefrom' => 'De la',
+'selectduedateto' => 'pana la',
+'selectsincedatefrom' => 'Schimbate de la',
+'selectsincedateto' => 'pana la',
+'selectdate' => 'Selecteaza data',
+'selectopenedfrom' => 'Deschise de la',
+'selectopenedto' => 'pana la',
+'selectclosedfrom' => 'Inchise de la',
+'selectclosedto' => 'pana la',
+'startat' => 'Incepe de la',
+'hasattachment' => 'Are fisier atasat',
+'private' => 'Privat',
+'watching' => 'Urmarire',
+'alreadyvotedthistask' => 'ai votat pentru acest tichet',
+'alreadyvotedthisday' => 'ai votat deja astazi',
+'visibility' => 'Vizibilitate',
+'public' => 'Public',
+'leaveemptyauto' => 'Lasa parolele necompletate, daca doresti o parola generata automat.',
+'novalidemail' => 'Nu s-a introdus o adresa de email valida.',
+'novalidjabber' => 'Nu s-a introdus o adresa Jabber valida.',
+'missingrequired' => 'Nu s-au completat toate campurile obligatorii.',
+'entervalidusername' => 'Introdu un utilizator valid si un nume real.',
+'couldnotaddusernotif' => 'Nu s-a putut adauga acest utilizator la lista de notificari.',
+'defaulttask' => 'Descrierea tichetului implicita',
+'all' => 'toate',
+'events.useraddedtoassignees' => 'Persoana atribuita',
+'eventlog' => 'Log - Rapoarte',
+'assignmentchanged' => 'Atribuire',
+'detailedinfo' => 'Informatii detaliate',
+'All' => 'Toate',
+'tasksireported' => 'Tichete deschise de mine',
+'recentlyopened' => 'Deschise recent',
+'stats' => 'Statistici',
+'totaltasks' => 'tichete totale',
+'mostwanted' => 'Cele mai dorite tichete',
+'defaultentry' => 'Pagina implicita',
+'toplevel' => 'Vizualizare de sus',
+'overview' => 'Ansamblu',
+'error#' => 'Eroare #',
+'error1' => 'Nu ai permisiuni de a vizualiza acest fisier atasat.',
+'error3' => 'Actiune repetata, redirectionare la pagina principala.',
+'error4' => 'Nu ai drepturi administrative.',
+'error5' => 'Acel utilizator nu exista in baza de date.',
+'error6' => 'Zona de administrare invalida.',
+'error7' => 'Autentificare esuata, parola incorecta!',
+'error71' => 'Contul blocat pentru %d minute din cauza prea multor incercari esuate de autentificare!',
+'error8' => 'Nu ai introdus numele de utilizator si parola.',
+'error9' => 'Tichetul nu exista sau nu exista permisiuni pentru a il vizualiza.',
+'error10' => 'Tichetul nu exista.',
+'error101' => 'Nu ai permisiunea de a vizualiza acest tichet.',
+'error102' => 'Nu ai permisiunea de a vizualiza tichetul. Incearca sa te autentifici pe site.',
+'error11' => 'Nu ai permisiunea de a modifica acest comentariu.',
+'error12' => 'Cheia nu este valida. Esti sigur ca ai primit-o prin mesajele de notificare?',
+'error13' => 'Utilizatorii anonimi nu au profil.',
+'error14' => 'Nu ai destule drepturi sa concepi un grup nou.',
+'error15' => 'Nu ai destule drepturi pentru a deschide un tichet.',
+'error16' => 'Nu esti Manager de Proiect.',
+'error17' => 'Zona de administrare proiect invalida.',
+'error18' => 'Link URL invalid.',
+'error19' => 'Acest utilizator nu exista in baza de date.',
+'error20' => 'Modificare a bazei de date invalida.',
+'error21' => 'Unul sau mai multe mailuri nu au putut fi trimise. Verifica configurarile.',
+'error22' => 'Nu sunt permise inregistrarile noi de utilizatori.',
+'error23' => 'Grupul sau utilizatorul nu au dreptul de a se autentifica.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Roadmap only available for a specific project.',
+'error26' => 'Unsupported oauth provider.',
+'error27' => 'Unable to login in. Authorize us to view your email address.',
+'error28' => 'You have no permission to access this area.',
+'done' => 'finalizat',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Proiectul nu a putut fi sters.',
+'GMT' => 'GMT',
+'timezone' => 'Zona locala',
+'accept' => 'Accept',
+'reasonfordeinal' => 'Motivul pentru refuz',
+'pruneclosedlinks' => 'Sterge linkurile inchise',
+'pruneclosedtasks' => 'Sterge tichetele inchise',
+'pagegenerated' => 'Pagina si imagine generata in %d secunde.',
+'pruninglevel' => 'Nivelul de stergere',
+'lastuser' => 'Ultimul utilizator nu a putut fi sters.',
+'allprivate' => 'Toate proiectele sunt private.',
+'deletegroup' => 'Sterge acest grup si muta utilizatorii in',
+'parent' => 'Parinte',
+'ordertip' => 'Ordinea in care aceste elemente vor aparea in lista',
+'showtip' => 'Afiseaza acest element in lista',
+'deletetip' => 'Sterge acest element din lista',
+'del' => 'del',
+'request1' => 'Se cere inchiderea tichetului.',
+'request2' => 'Se cere redeschiderea tichetului.',
+'allpriorities' => 'Toate prioritatile',
+'noroadmap' => 'Fisa de parcurs nu este disponibila (se pare ca nu exista in plan "versiuni" ulterioare).',
+'expand' => 'Extindere',
+'collapse' => 'Restrangere',
+'expandall' => 'Extinde tot',
+'collapseall' => 'Restrange tot',
+'minpwsize' => 'Parola trebuie sa contina minim 5 caractere',
+'passwordtoosmall' => 'Parola introdusa este prea scurta.',
+'accountwaslocked' => 'Contul tau a fost blocat din cauza prea multor incercari de autentificare esuate.',
+'failedattempts' => 'Au fost %d tentative de autentificare esuate.',
+'groupnotexist' => 'Grupul selectat nu exista in acest proiect.',
+'searchindetails' => 'Cauta detalii',
+'showasassignees' => 'Afiseaza ca atribuiti',
+'find' => 'Atribuire',
+'tls' => 'TLS',
+'isadmin' => 'Este admin',
+'addvotes' => 'Adauga vot',
+'removevote' => 'Sterge vot',
+'voteremoved' => 'Votul tau a fost sters',
+'voteremovefailed' => 'Votul tau nu a putut fi sters la acest moment.',
+'novotes' => 'In prezent nu ai niciun vot la niciun tichet.',
+'connectedtasks' => 'Tichete conectate:',
+'taskdependencies' => 'Tichete dependente',
+'viewgraph' => 'vezi grafic',
+'notaskdependencies' => 'Acest tichet nu depinde de un altul.',
+'dependson' => 'Depinde de',
+'blocks' => 'Blocheaza',
+'newdependency' => 'Depinde de:',
+'nouserstoadd' => 'Nu sunt utilizatori de adaugat. Fii sigur ca Nume, Nume utilizator si Adresa de mail sunt definite pentru fiecare utilizator in parte.',
+'dispintro' => 'Afiseaza mesajul introductiv',
+'mainmessage' => 'Mesaj introductiv',
+/* note only the English version for 'dispintro' is supported
+ * other languages should also be taken cared of in the future*/
+
+'setsupertask' => 'Seteaza ID Super-Tichet ID:',
+'supertaskmodified' => 'ID Super-Tichet a fost modificat',
+'set' => 'Seteaza',
+'supertask' => 'Super-Tichet',
+'setparent' => 'Seteaza acest tichet ca principal',
+'selfsupertasknotallowed' => 'ID-ul Super-Tichetului nu poate fi acelasi cu ID-ul de tichet',
+'quickaction' => 'Optiuni tichet',
+'updateselectedtasks' => 'Actualizeaza tichetele selectate',
+'notspecified' => 'Nespecificat',
+'editselectedtasks' => 'Modifica tichetele selectate',
+'information' => 'Informatii',
+'taskclosedisabled' => 'Inchiderea tichetului nu este disponibila deoarece tichetele care depind de el inca sunt deschise:-',
+'daysleft' => 'zile ramase',
+'dayoverdue' => 'zile depasite',
+'duetoday' => 'pana azi',
+'daysbeforealert' => 'Zile inaintea alertei',
+'associatedsubtask' => 'S-a asociat cu succes tichetul',
+'associatesubtask' => 'Seteaza acest tichet ca duplicat',
+'subtaskid' => 'ID Sub-tichet',
+'subtaskalreadyhasparent' => 'Sub-tichetul introdus are deja un tichet de tip parinte. Te rog sa elimini asocierea si sa incerci din nou.',
+'subtaskisparent' => 'Sub-tichetul introdus este parintele acestui tichet. Nu se poate face asocierea.',
+'subtasknotexist' => 'Sub-tichetul introdus nu exista.',
+'subtaskremovedmsg' => 'Sub-tichetul a fost eliminat cu succes',
+'subtaskadded' => 'Sub-tichet adaugat',
+'subtaskremoved' => 'Sub-tichet eliminat',
+'addnewsubtask' => 'Adauga tichet nou ca si completare',
+'hidesubtasks' => 'Ascunde subcategorie',
+'voteforthistask' => 'Voteaza pentru acest tichet',
+'watchthistask' => 'Urmareste acest tichet',
+'privatethistask' => 'Fa acest tichet privat',
+'adddependenttask' => 'Adauga dependenta de tichetul',
+'associatetaskid' => 'ID sub-tichet',
+'parenttaskid' => 'ID tichet parinte',
+'invalidsupertaskid' => 'ID parinte nu este valid.',
+'supertaskadded' => 'Tichet important adaugat',
+'supertaskremoved' => 'Tichet important sters',
+'effort' => 'Efort',
+'efforttracking' => 'Cronometru efort',
+'useeffort' => 'Proiectul foloseste cronometrarea de efort',
+'estimatedeffort' => 'Durata de lucru estimata',
+'totalestimatedeffort' => 'Efortul total estimat',
+'currenteffortdone' => 'Durata de lucru efectuata',
+'starteffort' => 'Incepe cronometrarea',
+'endeffort' => 'Opreste cronometrarea',
+'cleareffort' => 'Sterge cronometrarea',
+'addeffort' => 'Adauga efort',
+'manualeffort' => 'Adauga efort manual (H:m)',
+'efforttrackingstarted' => 'Cronometrarea efortului la acest tichet a inceput.',
+'efforttrackingnotstarted'=> 'Nu pot incepe cronometrarea la un tichet la care exista deja o cronometrare in curs.',
+'efforttrackingstopped' => 'Cronometrarea efortului la acest tichet a luat sfarsit.',
+'efforttrackingcancelled' => 'S-a anulat cronometrarea de efort la acest tichet.',
+'efforttrackingadded' => 'Efortul manual pentru acest tichet a fost inregistrat.',
+'trackinginprogress' => 'Urmarirea progresului',
+'viewestimatedeffort' => 'Poate vizualiza efortul depus',
+'viewcurrenteffortdone' => 'Poate vizualiza timpul pentru dezvoltare curent',
+'trackeffort' => 'Poate cronometra efortul',
+'invalideffort' => 'Timpul oferit este invalid. Trebuie sa fie de tip numeric.',
+'showpass' => 'Afiseaza parola',
+'chooseafile' => 'Alege un fisier!',
+'incorrectfiletype' => 'Fisierul ales este incorect. Fisiere permise: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Nu este nevoie de parola. Esti inregistrat prin %s',
+'addmultipletasks' => 'Adauga mai multe tichete',
+'pendingnewuserrequest' => 'Utilizator nou in asteptare',
+'adminrequestswaiting' => 'Cerinte pentru administrator in asteptare',
+'clicktoedit' => 'Editeaza rapid tichetul apasand pe fiecare camp in parte',
+'confirmedit' => 'Modifica',
+'canceledit' => 'Renunta',
+'regapprovedbyadmin' => 'Inregistrarile sunt aprobate de cate administratori (se dezactiveaza codul de confirmare)',
+'activity' => 'Activitate',
+'myactivity' => 'Activitatea mea',
+'emailverificationwrong' => 'Adresa de confirmare nu corespunde cu adresa oferita initial',
+'verifyemailaddress' => 'Confirma adresa de email',
+'hideemails' => 'Ascunde adresele de email ale utilizatorilor',
+'hidemyemail' => 'Ascunde-mi adresa de email',
+'exporttasklist' => 'Exporta lista tichete',
+'onedecimal' => 'o singura zecimala',
+'manday' => 'Munca persoanei pe zi',
+'mandays' => 'Munca persoanei pe zile',
+'mandayabbrev' => 'mpz',
+'hourspermanday' => 'Ore de munca pe zi (HH:mm)',
+'itemexists' => 'Elementul %s exista deja in baza de date.',
+'categoryitemexists' => 'Elementul %s este deja in categoria %s.',
+'pageswelcomemsg' => 'Paginile in care sa se afiseze mesajul introductiv',
+'pagesintromsg' => 'Paginile in care sa se afiseze mesajul introductiv',
+'activeoauths' => 'Provideri Oauth activi',
+'onlyoauthreg' => 'Permite doar autentificarile Oauth',
+'estimatedeffortformat' => 'Formatul pentru estimarea efortului',
+'currenteffortdoneformat' => 'Formatul curent pentru efortul depus',
+'minute' => 'minut',
+'minutes' => 'minute',
+'minuteplural' => 'minute',
+'minutesingular' => 'minut',
+'minuteabbrev' => 'min',
+'hourplural' => 'ore',
+'hoursingular' => 'ora',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => 'Efortul estimat pentru tichetele deschise',
+'currenteffortdoneopen' => 'Efortul curent depus in tichetele deschise',
+'signinwith' => 'Inregistreaza-te cu %s',
+'canviewroadmap' => 'Poate vizualiza fisa de parcurs',
+'enableavatars' => 'Activeza imagini profil',
+'maxavatarsize' => 'Dimensiune maxima a imaginii de profil (pixeli)',
+'taskhassubtask' => 'Acest tichet are urmatorul sub-tichet',
+'taskhassubtasks' => 'Acest tichet are urmatoarele sub-tichete',
+'translations' => 'Traduceri',
+'translate' => 'Tradu',
+'taskdescription' => 'DESCRIERE:',
+'notaskdescription' => 'nu exista o descriere a tichetului',
+'pleaseselect' => 'Selecteaza',
+'closeselectedtasks' => 'Inchide tichetele selectate',
+'closetasks' => 'inchide tichete',
+'hintforbulkimport' => '<b>Adauga mai multe tichete deodata</b>',
+'taskissubtaskof' => 'Acesta este un sub-tichet al',
+'applyfirstline' => 'Introdu prima linie',
+'addmorerows' => 'Adauga si alt tichet in lista',
+'addtasks' => 'Adauga toate tichetele',
+'massopsdisabled' => 'Momentan nu se pot modifica mai multe tichete in aceasta versiune (1.0). Incercam sa facem implementarea intr-o versiune ulterioara',
+'viewroadmap' => 'Poate vizualiza fisa de parcurs',
+'nosuicide' => 'Programul nu iti permite sa distrugi accesul la FlySpray prin dezactivarea propriului cont sau prin schimbarea propriului grup.',
+'movingtodifferentproject' => 'Mutarea unui tichet care are fie un parinte sau sub-tichete intr-un alt proiect nu este permisa. Trebuie sa intrerupi conexiunile intre tichete inainte.',
+'musthavesameproject' => 'Tichetul tip parinte si sub-tichetul trebuie sa apartina aceluiasi proiect.',
+'defaultorderby' => 'Ordoneaza implicit lista de tichete dupa',
+'defaultorderby2' => 'apoi dupa',
+'viewowntasks' => 'Vizualizare propriile tichete',
+'viewgroupstasks' => 'Vizualizare tichetele de grup',
+'urlrewriting' => 'Rescriere URL',
+'enablehtaccess' => 'Activeaza scrierea pe fisierul .htaccess din calea in care este instalat FlySpray inainte de a activa rescrierea URL',
+'nomodrewrite' => 'Modul de rescriere pare sa nu fie disponibil pe acest server; imi pare rau dar nu pot activa rescrierea de URL',
+'on' => 'Pornit',
+'off' => 'Oprit',
+'defaultorderbydirection' => 'Sortarea implicita dupa directie',
+'ascending' => 'Crescator',
+'descending' => 'Descrescator',
+'myassignedtasks' => 'Tichetele mele',
+'commentedon' => 'a raspuns pe',
+'maxvoteperday' => 'Voturi maxime pe zi',
+'votesperproject' => 'Limita utilizatorului de voturi per proiect',
+'votelimitreached' => 'S-a atins numarul maxim de voturi pentru acest proiect. Verifica profilul tau sa vezi la ce tichete ai votat in prezent. De acolo poti anula cateva voturi. Asa putem vedea care sunt tichetele cele mai importante pentru tine. Rezolvarea tichetelor iti reseteaza limita de voturi pe zi.',
+'myvotes' => 'Voturile mele',
+'tag' => 'Eticheta',
+'tags' => 'Etichete',
+'tagsinfo' => 'Etichetare libera in FlySpray: separa etichetele cu ;. Nu se poate face o cautare dupa etichete inca. Nici filtrare si nici sortare.',
+'novalues' => 'fara intrari',
+'youhaveregistered' => 'Te-ai inregistrat la FlySpray. Detaliile de conectare sunt urmatoarele:',
+'youhaveregisterednotify' => 'Inregistrarea la FlySpray a fost acceptata de administratori.',
+'usedintasks' => 'Folosire',
+'freetagging' => 'permite etichete definite de utilizatori',
+'keyboardshortcuts' => 'Taste rapide',
+'testmailsettings' => 'Verifica setarile de email active in prezent',
+'test' => 'Testeaza',
+'testmailsettingsnotice' => 'Verifica daca ai primit emailul de test pe adresa de mail a utilizatorului in cauza (vezi \'profilulmeu\'-pagina)',
+'invalidinput' => 'Se pare ca sunt niste proprietati incompatibile ce trebuiesc rezolvate inainte de a muta tichetul in alt proiect.',
+'invalidprogress' => 'Selecteaza valoarea de progres valida.',
+'invalidpriority' => 'Selecteaza o valoare de prioritate valida.',
+'invalidseverity' => 'Selecteaza o valoare de severitate valida.',
+'invalidstatus' => 'Selecteaza un status de tichet valid in momentul cand muti acest tichet in alt proiect.',
+'invalidcategory' => 'Selecteaza o categorie valida in momentul cand muti acest tichet in alt proiect.',
+'invalidreportedversion' => 'Selecteaza o versiune valida cand muti acest tichet in al proiect.',
+'invaliddueversion' => 'Selecteaza o versiunea de implementat valida in momentul cand muti acest tichet in alt proiect.',
+'invalidos' => 'Selecteaza un Sistem de Operare valid in momentul cand muti acest tichet in alt proiect.',
+'invalidtags' => 'Selecteaza doar etichetele permise in momentul cand muti tichetul in alt proiect.',
+'invalidassignees' => 'Selecteaza doar persoanele permise sa li se atribuie tichete atunci cand muti tichetul in alt proiect.',
+'customize' => 'personalizeaza',
+'hidesubs' => 'ascunde sub-tichete',
+'hideprivate' => 'ascunde tichetele private',
+'hideclosed' => 'ascunde tichetele inchise',
+'currentproject' => 'proiect curent',
+'targetproject' => 'proiect tinta',
+'availablekeybshortcuts' => 'Shortcuturi prin tastatura disponibile',
+'logindialoglogout' => 'Fereastra autentificare / deautentificare',
+'focustaskidsearch' => 'concentreaza cautarea dupa ID tichet',
+'openselectedtask' => 'deschide tichetul selectat',
+'movecursorup' => 'urca cursorul',
+'movecursordown' => 'coboara cursorul',
+'taskdetails' => 'Detalii tichet',
+'taskediting' => 'Editare tichet',
+'savetask' => 'salveaza tichet'
+);
+?>
diff --git a/lang/ru.php b/lang/ru.php
new file mode 100644
index 0000000..d91ff2f
--- /dev/null
+++ b/lang/ru.php
@@ -0,0 +1,1036 @@
+<?php
+//
+// This file is auto generated with .langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the .langedit.php editor!
+//
+$translation = array(
+'locale' => 'ru',
+'edituser' => 'Редактировать пользователÑ',
+'accountenabled' => 'Ðккаунт включён',
+'editallusers' => 'Ð’Ñе пользователи',
+'username' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ',
+'usersupdated' => 'Пользователь уÑпешно обновлён',
+'realname' => 'Полное имÑ',
+'emailaddress' => 'ÐÐ´Ñ€ÐµÑ Email',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Фото профилÑ',
+'notifytype' => 'Тип уведомлениÑ',
+'group' => 'Группа',
+'accountenabled' => 'Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ активна',
+'disableaccounts' => 'Отключить аккаунт',
+'deleteaccounts' => 'Удалить аккаунт',
+'updatedetails' => 'Сохранить',
+'setglobally' => 'ÐаÑтройка была уÑтановлена Ð´Ð»Ñ Ð²Ñей ÑиÑтемы.',
+'usergroupmanage' => 'Управление пользователÑми и группами',
+'newuser' => 'Создать нового пользователÑ',
+'newuserbulk' => 'ЗарегиÑтрировать неÑколько новых пользователей',
+'bulkuserstoadd' => 'СпиÑок новых пользователей',
+'optionsforallusers' => 'ÐаÑтройки Ð´Ð»Ñ Ð²Ñех новых пользователей',
+'newgroup' => 'Создать новую группу',
+'yes' => 'Да',
+'no' => 'Ðет',
+'editgroup' => 'Редактировать группу',
+'groupname' => 'Ð˜Ð¼Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹',
+'description' => 'ОпиÑание',
+'admin' => 'Группа админиÑтраторов',
+'opennewtasks' => 'Создание задач',
+'modifytasks' => 'ИзменÑÑ‚ÑŒ(-ить) ÑущеÑтвующие задачи',
+'addcomments' => 'Добавить комментарий',
+'attachfiles' => 'ПриÑоединить файлы',
+'vote' => 'ГолоÑовать',
+'groupenabled' => 'Пользователи из Ñтой группы могут входить в ÑиÑтему',
+'groupopen' => 'Пользователи из Ñтой группы могут входить в ÑиÑтему',
+'tasktypelist' => '-СпиÑок типов задач',
+'categorylist' => 'СпиÑок категорий',
+'oslist' => 'СпиÑок операционных ÑиÑтем',
+'resolutionlist' => 'СпиÑок резолюций',
+'versionlist' => 'СпиÑок верÑий',
+'severitylist' => 'СпиÑок значений критичноÑти',
+'listnote' => 'Замечание: ÑнÑтие отметки Ñ Ñ‡ÐµÐºÐ±Ð¾ÐºÑа "Показать" может изменить некоторые задачи в режиме редактированиÑ. Изменение Ð¿Ð¾Ð»Ñ "Ðазвание" повлиÑет на вÑе задачи, иÑпользующие Ñто имÑ. Элементы, которые невозможно удалить, необходимы Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾Ð¹ работы ÑиÑтемы или иÑпользуютÑÑ.',
+'name' => 'Ðазвание',
+'order' => 'ПорÑдок',
+'back' => 'Ðазад',
+'text' => 'ТекÑÑ‚',
+'highlight' => 'Выделить',
+'show' => 'Показать',
+'owner' => 'Владелец',
+'selectowner' => 'Выбрать владельца',
+'update' => 'Сохранить',
+'addnew' => 'Добавить',
+'flysprayprefs' => 'ÐаÑтройки Flyspray',
+'projecttitle' => 'Ðазвание проекта',
+'baseurl' => 'Базовый URL Ð´Ð»Ñ Ñтой уÑтановки',
+'replyaddress' => 'ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð° в уведомлениÑÑ….',
+'themestyle' => 'Тема / Стиль',
+'language' => 'Язык',
+'anonview' => 'Разрешить незарегиÑтрированным пользователÑм проÑматривать задачи',
+'allowanon' => 'Разрешить незарегиÑтрированным пользователÑм Ñоздавать задачи',
+'never' => 'Ðикогда',
+'anonymously' => 'Ðнонимно (без региÑтрации)',
+'afterregister' => 'Только поÑле региÑтрации',
+'spamproof' => 'ИÑпользовать код Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации новых пользователей',
+'anongroup' => 'Группа Ð´Ð»Ñ Ð²Ð½Ð¾Ð²ÑŒ Ñоздаваемых пользователей',
+'groupassigned' => 'Членам Ñтой группы могут быть назначены задачи',
+'forcenotify' => 'СпоÑоб отправки уведомлений',
+'neversend' => 'Ðикогда не отправлÑÑ‚ÑŒ',
+'userchoose' => 'Ðа выбор пользователÑ',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Владелец категории по умолчанию',
+'noone' => 'Ðикому',
+'jabbernotify' => 'Уведомление по Jabber',
+'jabberserver' => 'Сервер',
+'jabberport' => 'Порт',
+'jabberuser' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ',
+'jabberpass' => 'Пароль',
+'saveoptions' => 'Сохранить наÑтройки',
+'editcomment' => 'Редактировать комментарий',
+'commentby' => 'Комментарий от',
+'saveeditedcomment' => 'Сохранить отредактированный комментарий',
+'projectprefs' => 'СвойÑтва проекта',
+'pagetitle' => 'Заголовок Ñтраницы',
+'defaultproject' => 'Проект "по умолчанию"',
+'projectlists' => 'СпиÑок проектов',
+'showlogo' => 'Показывать титульный логотип',
+'showgravatars' => 'Показать Gravatars',
+'emailNoHTML' => 'Запретить HTML в E-Mails',
+'intromessage' => 'ПриветÑтвенное Ñообщение',
+'isactive' => 'Проект активен',
+'createproject' => 'Создать новый проект',
+'nopermission' => 'ÐедоÑтаточно прав, чтобы иÑпользовать Ñту Ñтраницу',
+'listordertip' => 'ПорÑдок, в котором данные Ñлементы будут предÑтавлены в ÑпиÑке',
+'listshowtip' => 'Показывать Ñтот Ñлемент в ÑпиÑке',
+'categoryownertip' => 'Данный пользователь получит уведомление, когда в Ñтой категории будет Ñоздана задача',
+'categoryparenttip' => 'РодительÑÐºÐ°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð´Ð»Ñ Ð²Ð½Ð¾Ð²ÑŒ Ñоздаваемой',
+'notsubcategory' => 'Ðет (ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð²ÐµÑ€Ñ…Ð½ÐµÐ³Ð¾ уровнÑ)',
+'showinlineimages' => 'Показывать вложенные изображениÑ',
+'dateformat' => 'Формат даты',
+'dateformat_extended' => 'Формат даты (подробный)',
+'cache_feeds' => 'Где хранить кÑш новоÑтных лент',
+'no_cache' => 'Ðе кÑшировать',
+'cache_disk' => 'КÑш на диÑке',
+'cache_db' => 'КÑш в базе данных',
+'subcategoryof' => 'ПодкатегориÑ',
+'visiblecolumns' => 'Видимые колонки в ÑпиÑке задач',
+'visiblefields' => 'ÐŸÐ¾Ð»Ñ Ð¿Ñ€Ð¸ добавлении, изменении или проÑмотре задачи',
+'tense' => 'Когда выпущена',
+'listtensetip' => 'ПрошлаÑ, Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ð¸Ð»Ð¸ будущаÑ',
+'past' => 'ПрошлаÑ',
+'present' => 'ТекущаÑ',
+'future' => 'БудущаÑ',
+'oldpass' => 'Старый пароль',
+'nooldpass' => 'Старый пароль не был задан',
+'oldpasswrong' => 'Старый пароль был указан неверно',
+'changepass' => 'Изменить пароль',
+'confirmpass' => 'Подтвердить пароль',
+'projectmanager' => 'Руководитель проекта',
+'viewtasks' => 'ПроÑмотр задач',
+'modifyowntasks' => 'Редактирование ÑобÑтвенных задач',
+'modifyalltasks' => 'Редактирование чужих задач',
+'viewcomments' => 'ПроÑмотр комментариев',
+'editcomments' => 'Редактирование комментариев',
+'deletecomments' => 'Удаление комментариев',
+'viewattachments' => 'ПроÑмотр вложений',
+'createattachments' => 'Создание вложений',
+'deleteattachments' => 'Удаление вложений',
+'viewhistory' => 'ПроÑмотр иÑтории изменений',
+'closeowntasks' => 'Закрытие ÑобÑтвенных задач',
+'closeothertasks' => 'Закрытие чужих задач',
+'assigntoself' => 'Ðазначение Ñебе еще не назначенных задач',
+'assignotherstoself' => 'Ðазначение Ñебе чужих задач',
+'viewreports' => 'ПроÑмотр журнала Ñобытий',
+'othersview' => 'Разрешить вÑем проÑмотр Ñтого проекта',
+'othersviewroadmap' => 'Разрешить вÑем пользователÑм проÑматривать план Ñтого проекта',
+'usersandgroups' => 'Пользователи и группы',
+'globalgroup' => 'Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°',
+'globalgroups' => 'Глобальные группы',
+'defaultglobalgroup' => 'Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… пользователей (по умолчанию)',
+'addtogroup' => 'Добавить к группе',
+'moveuserstogroup' => 'ПеремеÑтить пользователей в группу',
+'nogroup' => 'Без группы - удалить из проекта',
+'eventdesc' => 'ОпиÑание ÑобытиÑ',
+'requestedby' => 'Кем запрошено',
+'daterequested' => 'Дата и Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа',
+'closetask' => 'Закрыть задачу',
+'reopentask' => 'Повторно открыть задачу',
+'applymember' => 'ЗаÑвка на учаÑтие в проекте',
+'forcurrentproj' => 'Ð”Ð»Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ³Ð¾ проекта',
+'lostpw' => 'ВоÑÑтановление забытого паролÑ',
+'lostpwexplain' => "Введите Ñвой логин и Вам будет отправлена ÑÑылка Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ.\nПиÑьмо будет отправлено на адреÑ, указанный в Вашем профиле.",
+'sendlink' => 'ПоÑлать ÑÑылку',
+'savenewpass' => 'Сохранить новый пароль',
+'anonreg' => 'Разрешить региÑтрироватьÑÑ Ð½Ð¾Ð²Ñ‹Ð¼ пользователÑм',
+'allowanonopentask' => 'Разрешить анонимным пользователÑм Ñоздавать задачи',
+'editglobalgroup' => 'Редактировать глобальную группу',
+'editgroupforproj' => 'Редактировать группу Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°',
+'notshownforadmin' => 'Ð”Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ админиÑтраторов права не показаны, потому что Вам не нужно их редактировать.',
+'general' => 'Общие',
+'userregistration' => 'СамоÑтоÑÑ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ€ÐµÐ³Ð¸ÑтрациÑ',
+'notifications' => 'УведомлениÑ',
+'resetoptions' => 'ВоÑÑтановить прежние значениÑ',
+'preferences' => 'СвойÑтва',
+'tasktypes' => 'Типы задач',
+'resolutions' => 'Резолюции',
+'categories' => 'Категории',
+'operatingsystems' => 'Операционные ÑиÑтемы',
+'versions' => 'ВерÑии',
+'admintoolboxlong' => 'ÐаÑтройки админиÑтратора',
+'newproject' => 'Ðовый проект',
+'delete' => 'Удалить',
+'link' => 'СÑылка',
+'referencelinks' => 'СÑылки:',
+'listdeletetip' => 'Удалить Ñтот Ñлемент из ÑпиÑка',
+'lookandfeel' => 'Внешний вид',
+'globaltheme' => 'Тема по умолчанию',
+'emailnotify' => 'Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Email',
+'fromaddress' => 'От',
+'smtpserver' => 'Сервер SMTP',
+'smtpuser' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ SMTP',
+'smtppass' => 'Пароль SMTP',
+'addrewrite' => '(иÑпользовать переопределение адреÑов)',
+'usereminderdaemon' => 'Включить фоновую Ñлужбу напоминаний',
+'tasksperpage' => 'Задач на Ñтраницу',
+'addtoassignees' => 'Добавление ÑÐµÐ±Ñ Ðº иÑполнителÑм',
+'taskstatuses' => 'СоÑтоÑÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡',
+'canvote' => 'Можно голоÑовать за задачи',
+'loginsuccessful' => 'Вход уÑпешен.',
+'youareloggedout' => 'Ð’Ñ‹ вышли из ÑиÑтемы (возможно, принудительно)',
+'waitwhiletransfer' => 'Ð’Ñ‹ будете переадреÑованы. Подождите...',
+'clicknowait' => 'Ðажмите на ÑÑылку, еÑли не хотите ждать.',
+'accountdisabled' => 'Ваш аккаунт заблокирован. СвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором',
+'task' => 'Задача',
+'edittask' => 'Редактировать задачу',
+'openedby' => 'Кем открыта:',
+'editedby' => 'ПоÑледним редактировал',
+'tasktype' => 'Тип задачи',
+'category' => 'КатегориÑ',
+'status' => 'СоÑтоÑние',
+'assignedto' => 'Кому назначена:',
+'operatingsystem' => 'ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð°Ñ ÑиÑтема',
+'severity' => 'КритичноÑÑ‚ÑŒ',
+'reportedversion' => 'Обнаружена в верÑии',
+'dueinversion' => 'ОжидаетÑÑ Ð² верÑии',
+'defaultdueinversion' => 'ВерÑÐ¸Ñ Ð¿Ð¾ умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… задач',
+'undecided' => 'Ðе решено',
+'percentcomplete' => 'ЗавершенноÑÑ‚ÑŒ',
+'details' => 'Подробное опиÑание',
+'savedetails' => 'Сохранить изменениÑ',
+'canceledit' => 'Прекратить редактирование',
+'anonymous' => 'Ðнонимный иÑточник',
+'complete' => 'завершено',
+'closedby' => 'Кем закрыта:',
+'reasonforclosing' => 'Причина закрытиÑ:',
+'reopenthistask' => 'Открыть задачу повторно',
+'comments' => 'Комментарии',
+'attachments' => 'ВложениÑ',
+'relatedtasks' => 'РодÑтвенные задачи и дубликаты',
+'edit' => 'Редактировать',
+'addcomment' => 'Добавить комментарий',
+'fileuploadedby' => 'Кем выгружен файл:',
+'uploadafile' => 'Прикрепить файл',
+'addalink' => 'Добавить ÑÑылку',
+'addanotherlink' => 'Добавить другую ÑÑылку',
+'uploadnow' => 'Выгрузить!',
+'thesearerelated' => 'Эти задачи ÑвÑзаны Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ задачей',
+'remove' => 'Удалить',
+'addnewrelated' => 'Добавить родÑтвенную задачу',
+'add' => 'Добавить!',
+'otherrelated' => 'Задачи, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ Ñта задача ÑвÑзана',
+'receivenotify' => 'Эти пользователи получат подробное уведомление при изменении задачи.',
+'addusertolist' => 'Добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ðº Ñтому ÑпиÑку',
+'addtolist' => 'Добавить к ÑпиÑку',
+'addmyself' => 'Добавить Ð¼ÐµÐ½Ñ Ðº Ñтому ÑпиÑку',
+'removemyself' => 'Удалить Ð¼ÐµÐ½Ñ Ð¸Ð· Ñтого ÑпиÑка',
+'theseusersnotify' => 'Этим пользователÑм будет отправлено подробное уведомление при изменении задачи',
+'attachedtoproject' => 'Принадлежит проекту',
+'reminders' => 'ÐапоминаниÑ',
+'system' => 'СиÑтема',
+'systemvalues' => 'ОбщеÑиÑтемный ÑпиÑок значений',
+'projectvalues' => 'СпиÑок значений проекта',
+'remindthisuser' => 'Ðапоминание пользователю:',
+'thisoften' => 'ЧаÑтота напоминаний:',
+'startafter' => 'Ð’Ñ€ÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð´Ð¾ запуÑка напоминаний',
+'hour' => 'ЧаÑ',
+'hours' => 'ЧаÑов',
+'day' => 'День',
+'days' => 'Дней',
+'week' => 'ÐеделÑ',
+'weeks' => 'Ðедель',
+'addreminder' => 'Добавить напоминание',
+'defaultreminder' => 'Это напоминание о задаче Flyspray:',
+'message' => 'Сообщение',
+'closed' => 'Завершено',
+'filename' => 'Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°:',
+'date' => 'Дата',
+'filesize' => 'Размер файла:',
+'closurecomment' => 'Комментарий при закрытии:',
+'history' => 'ИÑториÑ',
+'nohistory' => 'ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¾Ñ‚ÑутÑтвует.',
+'eventdate' => 'Дата',
+'user' => 'Пользователь',
+'event' => 'Событие',
+'fieldchanged' => 'ИзменилоÑÑŒ поле',
+'taskopened' => 'Задача открыта',
+'taskreopened' => 'Задача открыта повторно',
+'taskclosed' => 'Задача закрыта',
+'commentadded' => 'Добавлен комментарий',
+'commentedited' => 'Отредактирован комментарий',
+'commentdeleted' => 'Удален комментарий',
+'attachmentadded' => 'Добавлено вложение',
+'attachmentdeleted' => 'Удалено вложение',
+'taskedited' => 'Задача отредактирована',
+'notificationadded' => 'Пользователь добавлен к ÑпиÑку уведомлений',
+'notificationdeleted' => 'Пользователь удален из ÑпиÑка уведомлений',
+'relatedadded' => 'Добавлена родÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'relateddeleted' => 'Удалена родÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'taskassigned' => 'Задача назначена',
+'taskreassigned' => 'Задача переназначена',
+'assignmentremoved' => 'Ðазначение удалено',
+'summary' => 'Ðазвание',
+'addedasrelated' => 'Задача добавлена к родÑтвенным задачам ',
+'deletedasrelated' => 'Задача удалена из родÑтвенных задач',
+'reminderadded' => 'Ðапоминание добавлено',
+'reminderdeleted' => 'Ðапоминание удалено',
+'priority' => 'Приоритет',
+'previousvalue' => 'Предыдущее значение',
+'newvalue' => 'Ðовое значение',
+'selectareason' => 'Выберите причину',
+'assigntome' => 'Ðазначить Ñебе',
+'reopenrequest' => 'Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° повторное открытие',
+'requestclose' => 'ЗапроÑить закрытие',
+'ownershiptaken' => 'Пользователь Ñтал владельцем',
+'closerequestmade' => 'Запрошено закрытие задачи',
+'reopenrequestmade' => 'Запрошено повторное открытие задачи',
+'taskdependson' => 'Задача завиÑит от',
+'taskdependsontask' => 'Задача завиÑит от',
+'taskdependsontasks' => 'Задача завиÑит от',
+'taskblock' => 'Задача болокирует закрытие',
+'taskblocks' => 'Эта задача не позволит завершить Ñледующие задачи',
+'depadded' => 'Добавлена завиÑимоÑÑ‚ÑŒ',
+'depaddedother' => 'Эта задача добавлена как завиÑимоÑÑ‚ÑŒ Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'depremoved' => 'ЗавиÑимоÑÑ‚ÑŒ удалена',
+'depremovedother' => 'Эта задача удалена из ÑпиÑка завиÑимоÑтей другой задачи',
+'showdetailserror' => 'Задача не ÑущеÑтвует, или у Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ ÐµÐµ проÑмотра.',
+'makeprivate' => 'Ñделать чаÑтной',
+'makepublic' => 'Ñделать общедоÑтупной',
+'taskmadeprivate' => 'Задача помечена как чаÑтнаÑ',
+'taskmadepublic' => 'Задача помечена как общедоÑтупнаÑ',
+'confirmdeletecomment' => 'Удалить Ñтот комментарий? %s',
+'attachementswilldeleted' => 'Ð’Ñе Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ удалены!',
+'confirmdeleteattach' => 'Удалить Ñто вложение?',
+'selectedhistory' => '-ОтображаютÑÑ ÑвойÑтва выбранных Ñобытий (?)',
+'showallhistory' => 'Показать вкладку Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð¹ иÑторией',
+'hidethis' => 'Скрыть Ñту облаÑÑ‚ÑŒ',
+'mark100' => 'Пометить задачу "Готова на 100%"',
+'watchtask' => 'отÑлеживать задачу',
+'stopwatching' => 'прекратить отÑлеживание',
+'commentlink' => 'СÑылка на Ñтот комментарий',
+'submitreq' => 'Отправить запроÑ',
+'reasonforreq' => 'Причина запроÑа',
+'pmreqdenied' => 'Руководитель Проекта отказал в ответ на запроÑ',
+'taskpendingreq' => 'ЕÑÑ‚ÑŒ запроÑÑ‹ Руководителю Проекта. Смотрите вкладку "ИÑториÑ" Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð¹ информации.',
+'previoustask' => 'ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'nexttask' => 'Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'duedate' => 'ОжидаетÑÑ Ðº дате',
+'attachnoperms' => 'У ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ ÐµÑÑ‚ÑŒ вложениÑ, но у Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¸Ñ… проÑмотра.',
+'linknoperms' => 'У ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ ÐµÑÑ‚ÑŒ ÑÑылки, но у Ð²Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¸Ñ… проÑмотра.',
+'open' => 'Открытые',
+'depgraph' => 'ПоÑмотреть завиÑимоÑти',
+'reset' => 'СброÑить',
+'selectusers' => 'Выбрать пользователей...',
+'addmetoassignees' => 'Добавить Ð¼ÐµÐ½Ñ Ðº иÑполнителÑм',
+'addedtoassignees' => 'Пользователь добавлен к ÑпиÑку иÑполнителей',
+'dependencygraph' => 'График завиÑимоÑтей',
+'attachanotherfile' => 'Прикрепить другой файл',
+'OK' => 'ОК',
+'addvote' => 'ПроголоÑовать "за"',
+'disable_lostpw' => 'Отключить воÑÑтановление паролÑ',
+'disable_changepw' => 'Отключить Ñоздание и редактирование паролÑ',
+'notifyfromfs' => 'Уведомление от Flyspray',
+'autogenerated' => 'СООБЩЕÐИЕ СОЗДÐÐО ÐВТОМÐТИЧЕСКИ. ОТВЕТ ÐЕ ТРЕБУЕТСЯ',
+'forward' => 'Вперед',
+'previous' => 'ПредыдущаÑ',
+'next' => 'СледующаÑ',
+'first' => 'ПерваÑ',
+'last' => 'ПоÑледнÑÑ',
+'page' => 'Страница %d из %d',
+'search' => 'ПоиÑк',
+'alltasktypes' => 'Ð’Ñе типы задач',
+'allseverities' => 'ЛюбаÑ',
+'alldevelopers' => 'Ð’Ñе разработчики',
+'notyetassigned' => 'Пока не назначена',
+'allcategories' => 'Ð’Ñе категории',
+'allstatuses' => 'Ð’Ñе ÑоÑтоÑниÑ',
+'allopentasks' => 'Ð’Ñе открытые задачи',
+'sortthiscolumn' => 'Сортировать по Ñтой колонке',
+'id' => 'ID',
+'project' => 'Проект',
+'dateopened' => 'Когда открыта',
+'progress' => 'ПрогреÑÑ',
+'searchthisproject' => 'ИÑкать в Ñтом проекте:',
+'dueanyversion' => 'Ðеважно (в любой верÑии)',
+'anyversion' => 'Ðеважно (в любой верÑии)',
+'dueversion' => 'ОжидаетÑÑ Ð² верÑии',
+'lastedit' => 'ПоÑледнее изменение',
+'os' => 'ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð°Ñ ÑиÑтема',
+'reportedin' => 'Обнаружена в верÑии',
+'taskrange' => 'Показаны задачи Ñ %d по %d (из %d)',
+'noresults' => 'Ðичего не найдено',
+'takeaction' => 'Выполнить',
+'watchtasks' => 'ОтÑлеживать Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² выбранных задачах',
+'stopwatchingtasks' => 'ПереÑтать отÑлеживать Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² выбранных задачах',
+'assigntaskstome' => 'Ðазначить выбранные задачи мне',
+'dueby' => 'ОжидаетÑÑ',
+'dueanytime' => 'Ð’ любое времÑ',
+'selectduedate' => 'Укажите ожидаемую дату завершениÑ',
+'toggleselected' => 'Инвертировать выбранные',
+'due' => 'ОжидаетÑÑ',
+'assignedtome' => 'Ðазначенные мне',
+'tasklist' => 'СпиÑок задач',
+'dateclosed' => 'Дата завершениÑ',
+'advanced' => 'Параметры фильтра',
+'searchcomments' => 'ИÑкать в комментариÑÑ…',
+'searchforall' => 'ИÑкать вÑе Ñлова',
+'anonusers' => 'Ðнонимные пользователи',
+'miscellaneous' => 'Дополнительно',
+'users' => 'Пользователи',
+'taskproperties' => 'СвойÑтва задачи',
+'selectsincedate' => 'Укажите дату "изменено Ñ"',
+'changedsince' => 'Изменено Ñ',
+'updatefs' => 'ПожалуйÑта, обновите FlySpray',
+'currentversion' => 'Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑиÑ',
+'latestversion' => 'поÑледнÑÑ Ð´Ð¾ÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ',
+'hidemessage' => '(напомнить позже)',
+'saveas' => 'Сохранить фильтр под именем',
+'nosearches' => 'Ðет Ñохраненных фильтров',
+'saving' => 'Сохранение...',
+'votes' => 'ГолоÑов за задачу',
+'tovote' => 'ГолоÑовать',
+'allclosedtasks' => 'Ð’Ñе завершенные задачи',
+'password' => 'Пароль',
+'login' => 'Вход!',
+'rememberme' => 'Запомнить менÑ',
+'lostpassword' => 'Забыли пароль?',
+'lostpwforfs' => 'Забыт пароль Flyspray',
+'lostpwmsg1' => "Привет.\n\nЯ тут Ñвой пароль забыл ",
+'lostpwmsg2' => ", пожалуйÑта, пришлите мне новый\n\nÐ˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ: ",
+'regards' => 'ПриветÑтвую,',
+'yourusername' => ' Ваше Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ',
+'filenotexist' => 'Файл не найден или у Ð’Ð°Ñ Ð½ÐµÐ´Ð¾Ñтаточно прав Ð´Ð»Ñ Ð´Ð¾Ñтупа',
+'showtask' => 'Показать задачу',
+'now' => 'СейчаÑ',
+'go' => 'Поехали!',
+'opentaskanon' => 'Ðнонимно открыть задачу',
+'register' => 'ЗарегиÑтрироватьÑÑ',
+'addnewtask' => 'Создать задачу',
+'reports' => 'Журнал Ñобытий',
+'editmydetails' => 'Редактировать мои данные',
+'logout' => 'Выход',
+'disabledaccount' => 'Ваш аккаунт был заблокирован!<br>ÐвтоматичеÑкий выход из ÑиÑтемы...',
+'poweredby' => 'ПоддерживаетÑÑ Flyspray',
+'sponsoredby' => 'Flyspray ÑпонÑируетÑÑ',
+'projects' => 'Проекты',
+'allprojects' => 'Ð’Ñе проекты',
+'selectproject' => 'Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°:',
+'tasksall' => 'Ð’Ñе задачи',
+'tasksassigned' => 'Задачи, назначенные мне',
+'tasksreported' => 'Задачи, Ñозданные мной',
+'taskswatched' => 'Задачи, которые Ñ Ð¾Ñ‚Ñлеживаю',
+'mysearch' => 'Мои фильтры',
+'admintoolbox' => 'ÐаÑтройки админиÑтратора',
+'manageproject' => 'Управление проектом',
+'permissions' => 'ПроÑмотреть разрешениÑ',
+'hide' => 'Скрыть',
+'pendingreq' => 'запроÑ(-ов) Руководителю Проекта',
+'errorpage' => 'Flyspray не может найти указанную Ñтраницу. Возможно, Ð’Ñ‹ запроÑили задачу, которой не ÑущеÑтвует, или у Ð’Ð°Ñ Ð½ÐµÐ´Ð¾Ñтаточно прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра запрошенных данных.<br /><br />Рможет быть, Ð’Ñ‹ что-то намудрили Ñ URL, чтобы в базу данных залезть? ЕÑли так, то марш в угол и подумайте о Ñвоем поведении. Когда вернетеÑÑŒ, - больше так не делайте!',
+'permissionsforproject' => 'Права Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°:',
+'switchto' => 'Перейти к',
+'lastsearch' => 'ПоÑледний фильтр',
+'modify' => 'Изменить',
+'noticefrom' => 'Уведомление от',
+'hasopened' => 'Ñоздал новую задачу и назначил ее Вам:',
+'moreinfonew' => 'Более подробно об Ñтой ошибке Ð’Ñ‹ можете узнать на Ñтранице FlySpray',
+'newtaskcategory' => 'Ð’ Ñтой категории была Ñоздана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'categoryowner' => 'Ð’Ñ‹ получили Ñто Ñообщение, потому что Ð’Ñ‹ указаны, как владелец категории.',
+'tasksummary' => 'Кратко о задаче:',
+'newtaskadded' => 'Создана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°.',
+'summaryanddetails' => 'Вам надо заполнить как краткое, так и полное опиÑание.',
+'summaryrequired' => 'Вам необходимо заполнить краткое опиÑание задачи.',
+'goback' => 'Ðазад.',
+'messagefrom' => 'Сообщение от ÑиÑтемы ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð·Ð° ошибками Flyspray, уÑтановленной по адреÑу ',
+'hasjustmodified' => 'только что изменил указанную задачу.',
+'changedfields' => 'ИзменившиеÑÑ Ð¿Ð¾Ð»Ñ Ð¾Ñ‚Ð¼ÐµÑ‡ÐµÐ½Ñ‹ звездочками (**)',
+'moreinfomodify' => 'Подробную информацию о задаче можно получить по URL:',
+'nolongerassigned' => 'Ð”Ð°Ð½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° переназначена Ð´Ð»Ñ (to)',
+'hasassigned' => 'назначил(-а) Вам Ñледующую задачу:',
+'taskupdated' => 'Задача была обновлена.',
+'tasksupdated' => 'Задачи были обновлены.',
+'hasclosedassigned' => 'закрыл(-а) назначенную Вам задачу:',
+'unassigned' => 'Ðе назначена',
+'hasclosed' => 'закрыл(-а) задачу:',
+'youonnotify' => 'Ð’Ñ‹ получили Ñто Ñообщение, потому что вы в ÑпиÑке Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹.',
+'taskclosedmsg' => 'Задача (была) закрыта',
+'returntotask' => 'ВернутьÑÑ Ðº ÑвойÑтвам задачи',
+'backtoindex' => 'Ðазад к ÑпиÑку задач',
+'noclosereason' => 'Ð’Ñ‹ не указали причину Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'hasreopened' => 'повторно открыл задачу Flyspray, ранее закрытую Вами:',
+'taskreopenedmsg' => 'Задача была открыта повторно.',
+'backtotask' => 'Ðазад к задаче',
+'commentaddedmsg' => 'Добавлен комментарий.',
+'commenttoassigned' => 'добавил комментарий к назначенной Вам задаче:',
+'commenttotask' => 'добавил комментарий к Ñтой задаче.',
+'nocommententered' => 'Желательно ввеÑти комментарий перед тем, как на кнопку "Добавить комментарий" жать.',
+'fillinfields' => 'Ðе вÑе Ð¿Ð¾Ð»Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ñ‹',
+'notcurrentpass' => 'Введен неправильный пароль!',
+'passchanged' => 'Пароль изменен',
+'closewindow' => 'Это окно можно закрыть',
+'passnomatch' => 'Введенные пароли не Ñовпадают!',
+'usernametaken' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ иÑпользуетÑÑ. Выберите другое.',
+'usernametakenbulk' => 'Это Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ занÑто',
+'newusercreated' => 'Ðовый пользователь Ñоздан.',
+'accountcreated' => 'Ваш аккаунт Ñоздан.',
+'newuserwarning' => 'Обратите внимание, что глобальные наÑтройки могут требовать подтверждение вашей региÑтрации админиÑтратором. Это может быть причиной того, что Ð’Ñ‹ не можете войти.',
+'nomatchpass' => 'Пароли не Ñовпадают.',
+'confirmwrong' => 'Ðеверный код подтверждениÑ!',
+'formnotcomplete' => 'Форма заполнена не полноÑтью.',
+'formnotnumeric' => 'ÐечиÑловое значение!',
+'groupnametaken' => 'Ð˜Ð¼Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ уже иÑпользуетÑÑ.',
+'newgroupadded' => 'Добавлена Ð½Ð¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°.',
+'optionssaved' => 'ÐаÑтройки Flyspray Ñохранены.',
+'hasuploaded' => 'выгрузил файл Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð±Ñ‹Ð»Ð° назначена:',
+'hasattached' => 'прикрепил файл к Ñледующей задаче:',
+'fileuploaded' => 'Файл уÑпешно выгружен.',
+'fileerror' => 'Ошибки при выгрузке Вашего файла. Возможно, неверно наÑтроены права Ð´Ð»Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð° <i>attachments</i>.',
+'contactadmin' => 'СвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором Ñтого проекта.',
+'selectfileerror' => 'Ðе выбран файл.',
+'userupdated' => 'Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ пользователе обновлена',
+'realandemail' => 'Ðе введены Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ Email',
+'groupupdated' => 'ОпиÑание группы обновлено.',
+'groupanddesc' => 'Ðе введено Ð¸Ð¼Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹',
+'fillallfields' => 'Заполните вÑе полÑ',
+'listPmustN' => 'Ð’ поле "ПорÑдок" должно быть чиÑловое значение.',
+'listupdated' => 'СпиÑок обновлен.',
+'listitemadded' => 'Добавлен новый Ñлемент ÑпиÑка.',
+'relatedaddedmsg' => 'К ÑпиÑку добавлена родÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°.',
+'relatederror' => 'Задача уже в ÑпиÑке родÑтвенных задач.',
+'relatedremoved' => 'РодÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° удалена из ÑпиÑка.',
+'notifyadded' => 'Пользователь добавлен к ÑпиÑку уведомлений.',
+'notifyerror' => 'Пользователь уже в ÑпиÑке уведомлений Ð´Ð»Ñ Ñтой задачи.',
+'notifyremoved' => 'Пользователь удален из ÑпиÑка уведомлений.',
+'editcommentsaved' => 'Измененный комментарий Ñохранен.',
+'commentdeletedmsg' => 'Комментарий был удален',
+'gotonewtask' => 'Перейти к только что Ñозданной задаче',
+'projectcreated' => 'Проект уÑпешно Ñоздан. Теперь можете изменить наÑтройки в разделе "Управление проектом"',
+'customiseproject' => 'ÐаÑтроить Ñтот проект',
+'projectupdated' => 'ÐаÑтройки проекта обновлены',
+'emptytitle' => 'Поле "Заголовок проекта" пуÑто. ВернитеÑÑŒ и заполните его.',
+'loginbelow' => 'Теперь Ð’Ñ‹ можете войти в ÑиÑтему.',
+'attachmentdeletedmsg' => 'Вложение было удалено',
+'reminderaddedmsg' => 'Ðапоминание добавлено.',
+'reminderdeletedmsg' => 'Выбранное напоминание было удалено.',
+'flyspraytask' => 'Задача Flyspray',
+'fieldsmissing' => 'Ðекоторые Ð¿Ð¾Ð»Ñ Ñодержат неверные данные или пуÑÑ‚Ñ‹.',
+'relatedinvalid' => 'Ðет такой задачи.',
+'relatedproject' => 'Эта задача отноÑитÑÑ Ðº другому проекту. Ð’Ñе равно добавить ÑвÑзь?',
+'addanyway' => 'Добавить принудительно',
+'cancel' => 'Отмена',
+'alreadyedited' => 'Кто-то уже Ð²Ð½ÐµÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² задачу, пока Ð’Ñ‹ ее тут разглÑдывали. Ð’Ñе еще хотите Ñохранить Ñвои Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (перезапиÑать чужие)?',
+'saveanyway' => 'Сохранить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾',
+'nouserselected' => 'Ðе выбрано ни одного пользователÑ. Перед тем, как пробовать еще раз, выберите кого-нибудь.',
+'groupswitchupdated' => 'Группы пользователей обновлены',
+'takenownershipmsg' => 'Эта задача назначена Вам',
+'adminrequestmade' => 'Ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð±Ñ‹Ð» отправлен Руководителю Проекта',
+'newdepnotify' => 'К задаче была добавлена Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ:',
+'dependadded' => 'Добавлена завиÑимоÑÑ‚ÑŒ',
+'dependaddfailed' => 'Ошибка при добавлении завиÑимоÑти. УбедитеÑÑŒ, что задача ÑущеÑтвует и не возникает взаимной блокировки (задачи не должны завиÑеть друг от друга).',
+'depremovedmsg' => 'ЗавиÑимоÑÑ‚ÑŒ была удалена',
+'newdepis' => 'ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ',
+'magicurlsent' => 'Сообщение было отправлено по вашему адреÑу. Ð’ нем ÑодержитÑÑ ÑÑылка на Ñтраницу, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ Ð’Ñ‹ Ñможете завершить запрошенное дейÑтвие.',
+'changefspass' => 'Изменить пароль Flyspray',
+'magicurlmessage' => 'ПожалуйÑта, перейдите по указанной ÑÑылке, чтобы изменить Ñвой пароль Ð´Ð»Ñ Flyspray:',
+'erroronform' => 'Возникла ошибка при обработке запроÑа',
+'addressused' => 'Этот Ð°Ð´Ñ€ÐµÑ Ð±Ñ‹Ð» иÑпользован Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Flyspray. ЕÑли Ð’Ñ‹ не ожидаете данное Ñообщение, пожалуйÑта, проÑто удалите его. Ð”Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€ÐµÐ³Ð¸Ñтрации воÑпользуйтеÑÑŒ ÑÑылкой:',
+'confirmcodeis' => 'Код подтверждениÑ:',
+'codesent' => 'Вам был выÑлан код подтверждениÑ. Следуйте инÑтрукциÑм в Ñообщении.',
+'codenotsent' => 'Ðе могу выÑлать Ваш код, повторите попытку попозже.',
+'taskmadeprivatemsg' => 'Эта задача была помечена как "ЧаÑтнаÑ"',
+'taskmadepublicmsg' => 'Эта задача опÑÑ‚ÑŒ общедоÑтупна',
+'realandnotify' => 'Вам надо указать полное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ Email или Jabber ID.',
+'pmreqdeniedmsg' => 'Отказ в ответ на Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŽ Проекта',
+'massopsuccess' => 'Ð“Ñ€ÑƒÐ¿Ð¿Ð¾Ð²Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ ÑƒÑпешна (где Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ð»Ð¸)',
+'usernotexist' => 'Пользователь не найден',
+'commentattachperms' => 'Ð’Ñ‹ не можете удалить Ñтот комментарий - нет прав Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹',
+'voterecorded' => 'Ваш Ð³Ð¾Ð»Ð¾Ñ Ð±Ñ‹Ð» учтен',
+'votefailed' => 'Ваш Ð³Ð¾Ð»Ð¾Ñ Ð½Ðµ может быть учтен в данный момент',
+'createnewgroup' => 'Создать новую группу',
+'requiredfields' => 'ПолÑ, обÑзательные Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ, отмечены',
+'addthisgroup' => 'Добавить Ñту группу',
+'createnewproject' => 'Создать новый проект',
+'addnewproject' => 'Добавить новый проект',
+'htmlallowed' => 'Разрешен код HTML',
+'createthisproject' => 'Создать проект',
+'inlineimages' => 'Показывать картинки в текÑте',
+'createnewtask' => 'Создать новую задачу в проекте:',
+'addanother' => 'Добавить еще задачу поÑле Ñтой',
+'addthistask' => 'Добавить задачу',
+'notifyme' => 'Уведомить менÑ, когда задача изменитÑÑ',
+'newtask' => 'ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'attachafile' => 'Прикрепить файл',
+'registernewuser' => 'Создание нового пользователÑ',
+'none' => 'Ðикакого/Ðикуда/Ðет/Ðичего',
+'registeraccount' => 'Создать учетную запиÑÑŒ',
+'registerbulkaccount' => 'Создать неÑколько учетных запиÑей',
+'both' => 'Оба типа',
+'notifyfrom' => 'Уведомление от ',
+'donotreply' => 'СООБЩЕÐИЕ СОЗДÐÐО ÐВТОМÐТИЧЕСКИ. ОТВЕЧÐТЬ ÐЕ ÐÐДО!',
+'disclaimer' => 'Ð’Ñ‹ получили Ñто Ñообщение, потому что Ð’Ñ‹ запроÑили его от ÑиÑтемы ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð·Ð° ошибками Flyspray. ЕÑли вы не ожидаете Ñтого ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ не хотите получать почтовые ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð² дальнейшем, Ð’Ñ‹ можете наÑтроить ÑпоÑобы уведомлениÑ, Ð¿ÐµÑ€ÐµÐ¹Ð´Ñ Ð½Ð° Ñтраницу Flyspray по ÑÑылке, указанной выше.',
+'userwho' => 'Ðвтор изменений',
+'moreinfo' => 'Более подробно - по ÑÑылке:',
+'newtaskopened' => 'Создана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° Flyspray. ПодробноÑти ниже.',
+'notify.taskclosed' => 'Задача закрыта:',
+'notify.taskreopened' => 'Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° была открыта повторно:',
+'newdep' => 'У Ñледующей задачи Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ:',
+'notify.depremoved' => 'У Ñледующей задачи завиÑимоÑÑ‚ÑŒ удалена:',
+'olddepwas' => 'Ð¡Ñ‚Ð°Ñ€Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ была',
+'notify.commentadded' => 'К задаче добавлен комментарий:',
+'commentis' => 'ТекÑÑ‚ комментариÑ.',
+'newattachment' => 'Ðовый файл был прикреплен к задаче:',
+'detailsbelow' => 'ПодробноÑти ниже.',
+'notify.relatedadded' => 'ÐÐ¾Ð²Ð°Ñ Ñ€Ð¾Ð´ÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° была добавлена к задаче:',
+'relatedis' => 'РодÑÑ‚Ð²ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° -',
+'assignedtoyou' => 'Вам назначена задача:',
+'takenownership' => 'назначил ÑÐµÐ±Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†ÐµÐ¼ Ñледующей задачи:',
+'requiresaction' => 'Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° требует вмешательÑтва Ð ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°',
+'requiresactionnotify' => 'Задача требует вмешательÑтва Ð ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°',
+'pmdeny' => 'Руководитель проекта отклонил Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð»Ñ Ñледующей задачи:',
+'pmdenynotify' => 'Руководитель Проекта отказал в ответ на запроÑ',
+'fileaddedtoo' => 'К Ñтому комментарию прикреплен один или более файлов.',
+'taskwatching' => 'Задача, которую Ð’Ñ‹ отÑлеживаете ',
+'isdepfor' => 'теперь ÑвлÑетÑÑ Ð·Ð°Ð²Ð¸ÑимоÑтью Ð´Ð»Ñ Ñледующей задачи',
+'denialreason' => 'Причина отказа',
+'taskchanged' => 'Задача Flyspray изменилаÑÑŒ. Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÐ½Ñ‹ далее по текÑту. Ð”Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ отчета об изменениÑÑ…, перейдите на Ñтраницу задачи по указанной ÑÑылке и откройте вкладку "ИÑториÑ"',
+'useraddedtoassignees' => 'Пользователь добавил ÑÐµÐ±Ñ Ðº ÑпиÑку иÑполнителей.',
+'removeddepis' => 'Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ:',
+'isnodepfor' => 'больше не ÑвлÑетÑÑ Ð·Ð°Ð²Ð¸ÑимоÑтью Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'usergroups' => 'Группы и пользователи',
+'pmtoolbox' => 'ÐаÑтройки Ð ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°',
+'groupmanage' => 'Управление группами и пользователÑми',
+'pendingrequests' => 'ЗапроÑÑ‹ Руководителю Проекта',
+'reasongiven' => 'Причина запроÑа',
+'nopendingreq' => 'Ðет запроÑов Руководителю Проекта.',
+'givereason' => 'Укажите причину',
+'catlisted' => 'Редактор ÑпиÑка категорий',
+'oslisted' => 'Редактор ÑпиÑка операционных ÑиÑтем',
+'verlisted' => 'Редактор ÑпиÑка верÑий',
+'tasktypeed' => 'Редактор ÑпиÑка типов задач',
+'resed' => 'Редактор ÑпиÑка резолюций',
+'deny' => 'Отказать',
+'notifiedwhen' => 'Уведомлен, когда',
+'onlynewtasks' => 'Созданы новые задачи',
+'allevents' => 'Ð’Ñе ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð²Ð¾ вÑех задачах',
+'feeds' => 'Ленты новоÑтей',
+'feeddescription' => 'ОпиÑание ленты новоÑтей',
+'feedimgurl' => 'СÑылка на картинку Ð´Ð»Ñ Ð»ÐµÐ½Ñ‚Ñ‹ новоÑтей (оÑтавите пуÑтой - будет без картинки)',
+'notifysubject' => 'Тема Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹',
+'notifysubjectinfo' => '(%p = заголовок проекта, %s = краткое опиÑание задачи, %t = идентификатор задачи, %a = дейÑтвие, %u = Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ)',
+'priority6' => 'В мгновение!',
+'priority5' => 'Ðемедленно',
+'priority4' => 'Срочно',
+'priority3' => 'Ð’Ñ‹Ñокий',
+'priority2' => 'Обычный',
+'priority1' => 'Ðизкий',
+'sendcode' => 'Ð’Ñ‹Ñлать код!',
+'entercode' => 'Введите выÑланный Вам код Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¸ новый пароль',
+'confirmationcode' => 'Код подтверждениÑ',
+'registererror' => 'УбедитеÑÑŒ, что Ð’Ñ‹ заполнили вÑе необходимые Ð¿Ð¾Ð»Ñ Ð¸ указали правильные данные Ð´Ð»Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð³Ð¾ типа уведомлений.',
+'validusername' => '(разрешены алфавитно-цифровые Ñимволы и - _ .)',
+'validemail' => '(иÑпользуйте ; Ð´Ð»Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð¸Ñ Ð½ÐµÑкольких адреÑов Ñлектронной почты)',
+'emailtakenbulk' => 'ÐÐ´Ñ€ÐµÑ Ñлектронной почты уже иÑпользуетÑÑ',
+'emailtaken' => 'ÐÐ´Ñ€ÐµÑ email или идентификатор Jabber уже занÑÑ‚Ñ‹. Вам придетÑÑ Ð²Ñ‹Ð±Ñ€Ð°Ñ‚ÑŒ Ñебе другие.',
+'note' => '<strong>Внимание:</strong> Вам будет выÑлан код Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð´Ð¾ того, как ваш аккаунт будет Ñоздан. Код будет выÑлан тем методом, который Ð’Ñ‹ выбрали Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹ (Ñм. выше).<br />ЕÑли Ð’Ñ‹ указали неверные данные, то Ð’Ñ‹ <strong>не получите Ваш код</strong>.',
+'changelog' => 'Журнал изменений',
+'changeloggen' => 'Генератор журнала изменений',
+'listfrom' => 'Показать журнал изменений Ñ',
+'to' => 'до',
+'oldestfirst' => 'Сначала более Ñтарые',
+'recentfirst' => 'Сначала более новые',
+'severityrep' => 'Отчет о критичноÑти задач',
+'totalopen' => 'Ð’Ñего открытых задач',
+'age' => 'ВозраÑÑ‚',
+'agerep' => 'Отчет о возраÑте',
+'eventsrep' => 'Отчет о ÑобытиÑÑ…',
+'events' => 'Событий',
+'Tasks' => 'Задачи',
+'opened' => 'Открытых',
+'edited' => 'Отредактированных',
+'assigned' => 'Ðазначенных',
+'within' => 'За',
+'pastday' => 'Прошедший день',
+'pastweek' => 'ПоÑледнÑÑ Ð½ÐµÐ´ÐµÐ»Ñ',
+'pastmonth' => 'ПоÑледний меÑÑц',
+'pastyear' => 'ПоÑледний год',
+'nolimit' => 'Без ограничений',
+'from' => 'От',
+'duein' => 'ОжидаетÑÑ Ð²',
+'selectfromdate' => 'Дата начала выборки',
+'selecttodate' => 'Дата Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ Ð²Ñ‹Ð±Ð¾Ñ€ÐºÐ¸',
+'showvoters' => 'Показать/Ñкрыть проголоÑовавших',
+'roadmap' => 'План работ',
+'roadmapfor' => 'План работ Ð´Ð»Ñ Ð²ÐµÑ€Ñии',
+'tasks' => 'задач(-и)',
+'completed' => 'завершены (-ные).',
+'opentasks' => 'задач открыто',
+'of' => '% из',
+'severity5' => 'Критично',
+'severity4' => 'Ð’Ñ‹Ñоко',
+'severity3' => 'Средне',
+'severity2' => 'Ðизко',
+'severity1' => 'Очень низко',
+'Redirect' => 'Перенаправить',
+'redirectmsg' => 'ЕÑли Ваш браузер не поддерживает автоматичеÑкое перенаправление, перейдите по ÑÑылке %HERE%',
+'allowclosedcomments' => 'Разрешить комментировать закрытые задачи',
+'comment' => 'Комментарий',
+'editowncomments' => 'Редактирование ÑобÑтвенных комментариев',
+'reopened' => 'Открыта повторно',
+'loading' => 'Загрузка...',
+'notifyown' => 'УведомлÑÑ‚ÑŒ о ÑобÑтвенных изменениÑÑ….',
+'youremail' => 'Ваш e-mail адреÑ',
+'thankyouforbug' => 'СпаÑибо за Ñообщение о проблеме. Ð’Ñ‹ можете отÑлеживать ÑоÑтоÑние задачи по Ñледующей ÑÑылке:',
+'anonuser' => 'Ðнонимный пользователь',
+'conflict' => 'Конфликт',
+'file' => 'Файл',
+'KiB' => 'КБ',
+'MiB' => 'МБ',
+'size' => 'Размер',
+'projectgroup' => 'Группа проекта',
+'profile' => 'Профиль:',
+'viewprofile' => 'ПроÑмотр профилÑ',
+'regdate' => 'ЗарегиÑтрирован Ñ',
+'tasksopened' => 'Открыто задач',
+'replyto' => 'Обратный адреÑ',
+'notifytypes' => 'Тип уведомлениÑ',
+'pm.taskchanged' => 'Задача изменена',
+'pm.taskreopened' => 'Задача открыта повторно',
+'pm.depadded' => 'Добавлена завиÑимоÑÑ‚ÑŒ',
+'pm.depremoved' => 'ЗавиÑимоÑÑ‚ÑŒ удалена',
+'pmrequest' => 'Ð—Ð°Ð¿Ñ€Ð¾Ñ Ñ€ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŽ проекта',
+'pmrequestdenied' => 'Ð—Ð°Ð¿Ñ€Ð¾Ñ Ñ€ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŽ проекта отклонен',
+'newassignee' => 'Ðовый иÑполнитель',
+'revdepadded' => 'Добавлена Ð¾Ð±Ñ€Ð°Ñ‚Ð½Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ',
+'revdepaddedremoved' => 'ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ удалена',
+'assigneeadded' => 'Добавлен иÑполнитель',
+'addusergroup' => 'Добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ðº Ñтой группе',
+'groupmembers' => 'Члены группы',
+'deleteuser' => 'Удалить Ñтого пользователÑ',
+'userdeleted' => 'Пользователь удален',
+'autoassign' => 'ÐвтоматичеÑки назначать задачу владельцу категории',
+'ssl' => 'SSL',
+'updatewrong' => 'Ð’Ñ‹ разрешили проверку на наличие обновлений, но ÑвÑзь Ñ Ñервером обновлений не уÑтановлена. Либо у Ð’Ð°Ñ Ð·Ð°Ð¿Ñ€ÐµÑ‰ÐµÐ½ иÑходÑщий трафик, либо ошибка возникла из-за каких-то проблем в Ñети. Зайдите на Ñайт Flyspray чтобы убедитьÑÑ, что Ð’Ñ‹ работаете Ñ Ð¿Ð¾Ñледней верÑией программы.',
+'deleteproject' => 'Удалить Ñтот проект и перемеÑтить вÑе данные в',
+'projectdeleted' => 'Проект уÑпешно удален',
+'feedforall' => 'ÐовоÑÑ‚Ð½Ð°Ñ Ð»ÐµÐ½Ñ‚Ð° Ð´Ð»Ñ Ð²Ñех проектов',
+'usercreated' => 'Пользователь Ñоздан',
+'userdeleted' => 'Пользователь удалён',
+'created' => 'Создан',
+'deleted' => 'Удален',
+'userid' => 'Идентификатор пользователÑ',
+'editassignments' => 'Редактировать назначениÑ',
+'preview' => 'ПредпроÑмотр',
+'anyprogress' => 'Любой прогреÑÑ',
+'tasksrelated' => 'РодÑтвенные задачи',
+'duplicatetasks' => 'Задачи Ñ Ñтой же проблемой (дубликаты)',
+'databasemodfailed' => 'Ошибка при обновлении базы данных. Возможно, недоÑтаточно прав Ð´Ð»Ñ Ð´Ð¾Ñтупа к базе данных.',
+'frequency' => 'ЧаÑтота',
+'newuserregistered' => 'ЗарегиÑтрирован новый пользователь Flyspray. Подробные данные о пользователе:',
+'newuserregisterednotify' => 'ЗарегиÑтрирован новый пользователь',
+'notify_registration' => 'УведомлÑÑ‚ÑŒ админиÑтраторов о новых зарегиÑтрированных пользователÑÑ…',
+'textversion' => 'ТекÑÑ‚Ð¾Ð²Ð°Ñ Ð²ÐµÑ€ÑиÑ',
+'onlyprimary' => 'Задачи, которые не блокируют другие задачи',
+'onlyblocker' => 'Задачи, которые блокируют другие задачи',
+'blockerornoblocker' => '- Блокирует или не блокирует, выбор обоих параметров не имеет ÑмыÑла. (?)',
+'switch' => 'Сменить проект',
+'max' => 'макÑ.',
+'dates' => 'Даты',
+'selectduedatefrom' => 'ОжидаютÑÑ Ñ',
+'selectduedateto' => 'по',
+'selectsincedatefrom' => 'Измененные Ñ',
+'selectsincedateto' => 'по',
+'selectdate' => 'Выберите дату',
+'selectopenedfrom' => 'Открытые Ñ',
+'selectopenedto' => 'по',
+'selectclosedfrom' => 'Закрытые Ñ',
+'selectclosedto' => 'по',
+'startat' => 'Ðачать Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ñ Ð´Ð°Ñ‚Ñ‹:',
+'hasattachment' => 'Содержат вложение',
+'private' => 'ЧаÑÑ‚Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð°',
+'watching' => 'ОтÑлеживаемаÑ',
+'alreadyvotedthistask' => 'вы уже проголоÑовали',
+'alreadyvotedthisday' => 'ÑÐµÐ³Ð¾Ð´Ð½Ñ ÑƒÐ¶Ðµ проголоÑовали',
+'visibility' => 'ВидимоÑÑ‚ÑŒ',
+'public' => 'ОбщедоÑтупна',
+'leaveemptyauto' => 'ОÑтавьте Ð¿Ð¾Ð»Ñ Ð²Ð²Ð¾Ð´Ð° Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¿ÑƒÑтыми, еÑли хотите, чтобы пароль был Ñгенерирован автоматичеÑки.',
+'novalidemail' => 'Введите корректный Ð°Ð´Ñ€ÐµÑ email.',
+'novalidjabber' => 'Введите корректный Ð°Ð´Ñ€ÐµÑ Jabber.',
+'missingrequired' => 'Ð’Ñ‹ не заполнили вÑе необходимые полÑ.',
+'entervalidusername' => 'Введите корректные логин и Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ',
+'couldnotaddusernotif' => 'Ðе могу добавить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ðº ÑпиÑку уведомлений.',
+'defaulttask' => 'ОпиÑание задачи "по умолчанию"',
+'all' => 'вÑе',
+'events.useraddedtoassignees'=> 'Пользователь добавлен к ÑпиÑку иÑполнителей',
+'eventlog' => 'Журнал Ñобытий',
+'assignmentchanged' => 'Ðазначение изменено',
+'detailedinfo' => 'ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ',
+'All' => 'Ð’Ñе',
+'tasksireported' => 'Открытые мной',
+'recentlyopened' => 'Ðедавно открытые',
+'stats' => 'СтатиÑтика',
+'totaltasks' => 'вÑего задач',
+'mostwanted' => 'Самые ожидаемые задачи',
+'defaultentry' => 'Страница по умолчанию',
+'toplevel' => 'ПроÑмотр верхнего уровнÑ',
+'overview' => 'Обзор',
+'error#' => 'Ошибка #',
+'error1' => 'ÐедоÑтаточно прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра вложениÑ',
+'error3' => 'Повтор дейÑтвиÑ, перенаправление на главную Ñтраницу.',
+'error4' => 'У Ð’Ð°Ñ Ð½ÐµÑ‚ админиÑтративных прав',
+'error5' => 'Пользователь не ÑущеÑтвует',
+'error6' => 'ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ админиÑÑ‚Ñ€Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (неверный адреÑ?)',
+'error7' => 'Попытка входа неудачна (неверные Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль)!',
+'error71' => 'Ðккаунт заблокирован на %d минут из-за Ñлишком большого чиÑла неудачных попыток входа!',
+'error8' => 'Ð’Ñ‹ не указали Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ пароль.',
+'error9' => 'Задача не ÑущеÑтвует или у Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ ÐµÐµ проÑмотра.',
+'error10' => 'Задача не ÑущеÑтвует или у Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ ÐµÐµ проÑмотра.',
+'error101' => 'У Ð²Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñтой задачи',
+'error102' => 'У Ð²Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Ñтой задачи, региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ помочь.',
+'error11' => 'Ðет прав Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого комментариÑ',
+'error12' => 'Ðеверный Ñекретный код! Ð’Ñ‹ уверены, что именно Ñто было указано в вашем уведомлении?',
+'error13' => 'У анонимных пользователей нет ÑобÑтвенных наÑтроек.',
+'error14' => 'Ð’Ñ‹ не можете Ñоздать новую группу - недоÑтаточно прав.',
+'error15' => 'Ð’Ñ‹ не можете Ñоздать новую задачу - недоÑтаточно прав.',
+'error16' => 'Вы не Руководитель Проекта.',
+'error17' => 'ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ Ð ÑƒÐºÐ¾Ð²Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ ÐŸÑ€Ð¾ÐµÐºÑ‚Ð°',
+'error18' => 'ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÑÐµÐºÑ€ÐµÑ‚Ð½Ð°Ñ ÑÑылка',
+'error19' => 'Этот пользователь не ÑущеÑтвует в данной базе Flyspray.',
+'error20' => 'Ðекорректный Ð·Ð°Ð¿Ñ€Ð¾Ñ Ðº базе данных',
+'error21' => 'Один или неÑколько Ñлектронных пиÑем не могут быть отправлены. Проверьте наÑтройки.',
+'error22' => 'РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ñ… пользователей запрещена.',
+'error23' => 'Пользователю или группе запрещен вход в ÑиÑтему.',
+'error24' => 'Ðе найден ни иÑполнÑемый файл dot, ни общедоÑтупный dot-Ñервер.',
+'error25' => 'План работ может быть Ñформирован только Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð¾Ð².',
+'error26' => 'Ðеподдерживаемый провайдер OAuth.',
+'error27' => 'Ðе удаетÑÑ Ð²Ð¾Ð¹Ñ‚Ð¸. Разрешаю нам поÑмотреть ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты.',
+'error28' => 'У Ð²Ð°Ñ Ð½ÐµÑ‚ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° доÑтуп в Ñтот раздел.',
+'done' => 'завершено',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Проект не может быть удален',
+'GMT' => 'GMT',
+'timezone' => 'ЧаÑовой поÑÑ',
+'accept' => 'Подтвердить',
+'reasonfordeinal' => 'Причина отказа',
+'pruneclosedlinks' => 'Скрыть закрытые ÑвÑзи',
+'pruneclosedtasks' => 'Скрыть закрытые задачи',
+'pagegenerated' => 'Страница и изображение Ñозданы за %d Ñекунд.',
+'pruninglevel' => 'Режим отображениÑ',
+'lastuser' => 'ЕдинÑтвенный пользователь не может быть удален',
+'allprivate' => 'Ðет проектов Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа',
+'deletegroup' => 'Удалить Ñту группу и перемеÑтить пользователей в',
+'parent' => 'Родитель',
+'ordertip' => 'ПорÑдок, в котором Ñти Ñлементы будут отображены в ÑпиÑке',
+'showtip' => 'Отображать Ñтот Ñлемент в ÑпиÑке',
+'deletetip' => 'Удалить Ñтот Ñлемент из ÑпиÑка',
+'del' => 'удалить',
+'request1' => 'Запрошено закрытие задачи',
+'request2' => 'Запрошено повторное открытие задачи',
+'allpriorities' => 'Любой',
+'noroadmap' => 'Ðевозможно Ñоздать план работ (не ÑущеÑтвует верÑии проекта, помеченной как "будущаÑ")',
+'expand' => 'Развернуть',
+'collapse' => 'Свернуть',
+'expandall' => 'Развернуть вÑе',
+'collapseall' => 'Свернуть вÑе',
+'minpwsize' => 'Минимальный размер Ð¿Ð°Ñ€Ð¾Ð»Ñ 5 Ñимволов',
+'passwordtoosmall' => 'Короткий пароль',
+'accountwaslocked' => 'Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ была заблокирована из-за Ñлишком большого чиÑла неудачных попыток входа.',
+'failedattempts' => 'Там были %d неудачные попытки входа.',
+'groupnotexist' => 'Ð’Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° не ÑущеÑтвует в Ñтом проекте.',
+'searchindetails' => 'ПоиÑк подробноÑтей',
+'showasassignees' => 'Показать как предÑтавителÑ',
+'find' => 'ИÑкать',
+'tls' => 'TLS',
+'isadmin' => 'Это админ',
+'addvotes' => 'Добавить голоÑ',
+'removevote' => 'Удалить голоÑ',
+'voteremoved' => 'Ваш Ð³Ð¾Ð»Ð¾Ñ Ð±Ñ‹Ð» удален',
+'voteremovefailed' => 'Ваш Ð³Ð¾Ð»Ð¾Ñ Ð½Ðµ может быть удален ÑейчаÑ.',
+'novotes' => 'У Ð²Ð°Ñ Ð¿Ð¾ÐºÐ° нет голоÑов за задачи',
+'connectedtasks' => 'СвÑзанные задачи:',
+'taskdependencies' => 'ЗавиÑимоÑти задачи',
+'viewgraph' => 'ПоÑмотреть график',
+'notaskdependencies' => 'Эта задача не завиÑит ни от каких других задач.',
+'dependson' => 'ЗавиÑит от',
+'blocks' => 'Блоки',
+'newdependency' => 'ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð²Ð¸ÑимоÑÑ‚ÑŒ',
+'nouserstoadd' => 'Ðет пользователей Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ. ПожалуйÑта, убедитеÑÑŒ, что имÑ, логин и e-mail определены Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ пользователÑ.',
+'dispintro' => 'Показать приветÑтвенное Ñообщение',
+'mainmessage' => 'Моё приветÑтвенное Ñообщение',
+'setsupertask' => 'УÑтановить ID Ñверхзадачи:',
+'supertaskmodified' => 'ID Ñверхзадачи был изменён',
+'set' => 'УÑтановить',
+'supertask' => 'Сверхзадача',
+'setparent' => 'Ð”Ð»Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñтой задачи уÑтановите ID родительÑкой задачи',
+'selfsupertasknotallowed' => 'ID Ñверхзадачи не может быть таким же как и ID Ñамой задачи',
+'quickaction' => 'БыÑтрые дейÑтвиÑ',
+'updateselectedtasks' => 'Обновить выбранные задачи',
+'notspecified' => 'Ðе указано',
+'editselectedtasks' => 'Изменить выбранные задачи',
+'information' => 'ИнформациÑ',
+'taskclosedisabled' => 'Закрыть задачу нельзÑ, Ñ‚.к. Ñледующие завиÑимые задачи открыты: -',
+'daysleft' => 'дней оÑталоÑÑŒ',
+'dayoverdue' => 'дней проÑкочено',
+'duetoday' => 'ОжидаетÑÑ ÑегоднÑ.',
+'daysbeforealert' => 'ÐеÑколько дней до готовноÑти',
+'associatedsubtask' => 'Подзадачи уÑпешно ÑвÑзаны #FS',
+'associatesubtask' => 'ID подзадачи Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð²Ñзки к Ñтой задиче',
+'subtaskid' => 'ID подзадачи',
+'subtaskalreadyhasparent' => 'Подзадача, которую вы указали, имеет родительÑкую задачу, уÑтраните Ñто Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð²Ñзки.',
+'subtaskisparent' => 'Подзадача, которую вы указали, ÑвлÑетÑÑ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÐµÐ¼ Ñтой задачи. Подзадача не привÑзана.',
+'subtasknotexist' => 'Подзадачи, которую вы указали, не ÑущеÑтвует',
+'subtaskremovedmsg' => 'Подзадача уÑпешно удалена',
+'subtaskadded' => 'Подзадача добавлена',
+'subtaskremoved' => 'Подзадача удалена',
+'addnewsubtask' => 'Добавить новую подзадачу',
+'hidesubtasks' => 'Скрыть подзадачи',
+'voteforthistask' => 'ГолоÑовать за Ñту задачу',
+'watchthistask' => 'ОтÑлеживать задачу',
+'privatethistask' => 'Сделать чаÑтной',
+'adddependenttask' => 'СвÑзать задачи',
+'associatetaskid' => 'ID подзадачи',
+'parenttaskid' => 'ID родительÑкой задачи',
+'invalidsupertaskid' => 'ID родительÑкой задачи не дейÑтвительно.',
+'supertaskadded' => 'Сверхзадача Ñоздана',
+'supertaskremoved' => 'Сверхзадача удалена',
+'effort' => 'Effort',
+'efforttracking' => 'Effort Tracking',
+'useeffort' => 'Project Uses Effort Tracking',
+'estimatedeffort' => 'Estimated Effort',
+'totalestimatedeffort' => 'Total estimated effort',
+'currenteffortdone' => 'Current effort done',
+'starteffort' => 'Start Tracking',
+'endeffort' => 'Stop Tracking',
+'cleareffort' => 'Clear Tracking',
+'addeffort' => 'Add Effort',
+'manualeffort' => 'Manually Add Effort (H:M)',
+'efforttrackingstarted' => 'Effort Tracking Commenced for this task.',
+'efforttrackingnotstarted'=> 'Unable to start tracking on an issue that is already being tracked',
+'efforttrackingstopped' => 'Effort Tracking Ended for this task.',
+'efforttrackingcancelled' => 'Effort Tracking Cancelled for this task.',
+'efforttrackingadded' => 'Manual effort recorded for task.',
+'trackinginprogress' => 'Tracking in Progress',
+'viewestimatedeffort' => 'Can View Effort Tracking',
+'viewcurrenteffortdone' => 'Can View Current Effort done',
+'trackeffort' => 'Can Track Effort',
+'invalideffort' => 'The effort entered is invalid. Must be a number',
+'showpass' => 'Показать пароль',
+'chooseafile' => 'ПожалуйÑта, выберите файл!',
+'incorrectfiletype' => 'Ðеверный тип файла. Разрешены: jpg, jpeg, gif, png.',
+'oauthreqpass' => 'Пароль не воÑÑтановить. Ð’Ñ‹ зарегеÑтрированы через %s',
+'addmultipletasks' => 'Добавить неÑколько задач',
+'pendingnewuserrequest' => 'Ðовые пользователи, ожидающие раÑÑмотрениÑ',
+'adminrequestswaiting' => 'ÐдминиÑтратор проÑит подождать',
+'clicktoedit' => 'Ð”Ð»Ñ Ð±Ñ‹Ñтрого редактированиÑ, нажимайте на полÑ',
+'confirmedit' => 'подтвердить',
+'canceledit' => 'отмена',
+'regapprovedbyadmin' => 'РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ ÑƒÑ‚Ð²ÐµÑ€Ð¶Ð´Ð°ÐµÑ‚ÑÑ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором (отключить код подтверждениÑ)',
+'activity' => 'ÐктивноÑÑ‚ÑŒ',
+'myactivity' => 'ÐœÐ¾Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ',
+'emailverificationwrong' => 'Подтверждение не ÑоответÑтвуют заданному адреÑу Ñлектронной почты',
+'verifyemailaddress' => 'Подтвердите Ð°Ð´Ñ€ÐµÑ Ñлектронной почты',
+'hideemails' => 'Скрывать адреÑа Ñлектронной почты пользователей',
+'hidemyemail' => 'Скрыть мой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты',
+'exporttasklist' => 'ЭкÑпортировать ÑпиÑок задач',
+'onedecimal' => 'одна деÑÑтаÑ',
+'manday' => 'человеко-день',
+'mandays' => 'человеко-дней',
+'mandayabbrev' => 'чд',
+'hourspermanday' => 'ЧаÑов в одном человеко-дне (HH:mm)',
+'itemexists' => 'Пункт %s уже ÑущеÑтвует в базе данных.',
+'categoryitemexists' => 'Пункт %s уже ÑущеÑтвует в категории %s в базе данных.',
+'pageswelcomemsg' => 'Страницы, где отображаетÑÑ Ð¾Ñновное приветÑтвенное Ñообщение',
+'pagesintromsg' => 'Страницы, где отображаетÑÑ Ð¿Ñ€Ð¸Ð²ÐµÑ‚Ñтвенное Ñообщение',
+'activeoauths' => 'Ðктивировать OAuth провайдеров',
+'onlyoauthreg' => 'Разрешить только OAuths региÑтрацию',
+'estimatedeffortformat' => 'Estimated effort display format',
+'currenteffortdoneformat' => 'Current effort done display format',
+'minute' => 'минута',
+'minutes' => 'минут',
+'minuteplural' => 'минут',
+'minutesingular' => 'минута',
+'minuteabbrev' => 'мин',
+'hourplural' => 'чаÑов',
+'hoursingular' => 'чаÑ',
+'hourabbrev' => 'ч',
+'estimatedeffortopen' => 'Estimated effort for open tasks',
+'currenteffortdoneopen' => 'Current effort spent in open tasks',
+'signinwith' => 'Войти в ÑиÑтему Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ %s',
+'canviewroadmap' => 'Может проÑматривать план работ',
+'enableavatars' => 'Включить аватарки',
+'maxavatarsize' => 'МакÑимальный размер аватарки в пикÑелÑÑ…',
+'taskhassubtask' => 'Эта задача имеет Ñледующую подзадачу',
+'taskhassubtasks' => 'Эта задача имеет Ñледующие подзадачи',
+'translations' => 'Переводы',
+'translate' => 'Перевод',
+'taskdescription' => 'ОпиÑание задачи',
+'notaskdescription' => 'нет опиÑÐ°Ð½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸',
+'pleaseselect' => 'ПожалуйÑта, выберите',
+'closeselectedtasks' => 'Закрыть выбранные задачи',
+'closetasks' => 'закрыть задачи',
+'hintforbulkimport' => '<b>Tips for bulk importing:</b>
+ <ol>
+ <li>Copy and paste from an excel spreadsheet or CSV by pasting one entire column.</li>
+ <li>Currently you can only paste Summary and Details.</li>
+ <li>There are suggestions when you assign to someone, and to no-one if there is no matched name.</li>
+ </ol>',
+'taskissubtaskof' => 'Эта задача ÑвлÑетÑÑ Ð¿Ð¾Ð´Ð·Ð°Ð´Ð°Ñ‡ÐµÐ¹',
+'applyfirstline' => 'Применить первую Ñтроку',
+'addmorerows' => 'Добавить неÑколько Ñтрок',
+'addtasks' => 'Добавить задачи',
+'massopsdisabled' => 'Sorry, bulk editing is currently disabled for Flyspray 1.0. We plan to finish implementation for a later release of Flyspray. You can enable them in source code again at your own risk, but read the comments there before doing it.',
+'viewroadmap' => 'Может проÑматривать план работ',
+'nosuicide' => 'Dear user, my program doesn\'t allow you to destroy your access to Flyspray by disabling your own account or switching your own group. The empathic brother of HAL9000',
+'movingtodifferentproject' => 'Перемещение задачи, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑвлÑетÑÑ Ð¸Ð»Ð¸ родителем, или подзадачей в другом проекте, не допуÑкаетÑÑ. Сначала вы должны разорвать ÑвÑзь между ними.',
+'musthavesameproject' => 'Родитель и подзадача должны принадлежать к одному проекту',
+'defaultorderby' => 'ПорÑдок ÑпиÑка задач по умолчанию',
+'viewowntasks' => 'Смотреть Ñвои задачи',
+'viewgroupstasks' => 'Смотреть групповые задач',
+'urlrewriting' => 'Url rewriting',
+'enablehtaccess' => 'Please enable your .htaccess file at Flyspray root before turning url rewriting on',
+'nomodrewrite' => 'Mod rewrite doesn\'t seem to be available on this server, sorry but I can\'t turn url rewriting on',
+'on' => 'Включить',
+'off' => 'Выключить',
+'defaultorderbydirection' => 'Ðаправление по умолчанию',
+'ascending' => 'По возроÑтанию',
+'descending' => 'По убыванию',
+'myassignedtasks' => 'Ðазначенные мне задачи',
+'commentedon' => 'прокомментировал',
+'maxvoteperday' => 'МакÑимум голоÑов в день',
+'votesperproject' => 'Лимит голоÑов за проект Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ',
+'votelimitreached' => 'Ð’Ñ‹ доÑтигли предела Ñвоих голоÑов за Ñтот проект. См. Ñтраницу вашего профилÑ, Ð´Ð»Ñ ÐºÐ°ÐºÐ¸Ðµ задачи вы в данный момент проголоÑовали. Там вы также можете забрать Ñвои голоÑа. Так мы можем видеть, какие задачи наиболее важны Ð´Ð»Ñ Ð²Ð°Ñ. За решенные задачи вы получите Ñвой Ð³Ð¾Ð»Ð¾Ñ Ð¾Ð±Ñ€Ð°Ñ‚Ð½Ð¾ в Ñвой лимит голоÑов.',
+'myvotes' => 'Мои голоÑа',
+'tag' => 'Тег',
+'tags' => 'Теги',
+'tagsinfo' => 'Free tagging/labeling of tasks in Flyspray:
+Separate tags by ;
+Tags are currently not managable and are not used for search or filtering. They are currently just like additional stickers on a window.',
+'novalues' => 'нет запиÑей',
+'youhaveregistered' => 'Ð’Ñ‹ зарегиÑтрировалиÑÑŒ на Flyspray. Детали:',
+'youhaveregisterednotify' => 'Ваша региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð½Ð° Flyspray была принÑта админиÑтратором.',
+'usedintasks' => 'ИÑпользовать',
+);
+?>
diff --git a/lang/sk.php b/lang/sk.php
new file mode 100644
index 0000000..36f0ec4
--- /dev/null
+++ b/lang/sk.php
@@ -0,0 +1,929 @@
+<?php
+
+/*******************************************************************************
+ * File: sk.php
+ * Authors: Mancuska Martin (borgcube) http://blog.borg.sk/
+ * Pokorny Fridolin (fridex) http://fridex.yw.sk
+ * Jan Callus(callus) jan.callus@gmail.com
+ * Description:
+ * Slovak translation of web-based bug tracking system FlySpray.
+ * http://flyspray.org/
+ *
+ * Last modified: 10/13/14
+ ******************************************************************************/
+
+
+$translation = array(
+'edituser' => 'Edituj užívateľa',
+'accountenabled' => 'Konto povolené',
+'editallusers' => 'Zobraz všekých používateľov',
+'username' => 'Užívateľské meno',
+'usersupdated' => 'Užívatelia úspešne aktualizovaný',
+'realname' => 'SkutoÄné meno',
+'emailaddress' => 'Emailová adresa',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Typ notifikácie',
+'group' => 'Skupina',
+'enableaccounts' => 'PovoliÅ¥ úÄty',
+'disableaccounts' => 'ZakázaÅ¥ úÄty',
+'deleteaccounts' => 'ZmazaÅ¥ úÄty',
+'updatedetails' => 'Aktualizovať detaily',
+'setglobally' => 'Toto nastavenie bolo nastavené globálne',
+'usergroupmanage' => 'Správa užívateľov a skupín',
+'newuser' => 'Registrácia nového užívateľa',
+'newuserbulk' => 'Hromadná registrácia nových užívateľov',
+'bulkuserstoadd' => 'Zoznam nových užívateľov',
+'optionsforallusers' => 'Možnosti pre všetkých nových používateľov',
+'newgroup' => 'Vytvoriť novú skupinu',
+'yes' => 'Ãno',
+'no' => 'Nie',
+'editgroup' => 'Editovať skupinu',
+'groupname' => 'Názov skupiny',
+'description' => 'Popis',
+'admin' => 'Admin skupina',
+'opennewtasks' => 'Otvoriť nové dotazy',
+'modifytasks' => 'Upraviť existujúce dotazy',
+'addcomments' => 'Pridať komentáre',
+'attachfiles' => 'Priložiť súbory',
+'vote' => 'Hlasuj',
+'groupenabled' => 'Členovia sa môžu prihlásiť',
+'groupopen' => 'Členovia sa môžu prihlásiť',
+'tasktypelist' => 'Zoznam typov dotazov',
+'categorylist' => 'Zoznam kategórií',
+'oslist' => 'Zoznam operaÄných systémov',
+'resolutionlist' => 'Resolutions list',
+'versionlist' => 'Zoznam verzií',
+'severitylist' => 'Severities list',
+'listnote' => 'Poznámka: OdÅ¡krtnutím "zobraziÅ¥" sa môžu zmeniÅ¥ úlohy v režime úprav. Zmenou poľa "Meno/názov" sa zmenia vÅ¡etky úlohy s daným menom. Veci, ktoré nie je možné odstrániÅ¥, sú buÄ chránené, pretože sú nevyhnutné pre správne fungovanie, alebo sa používajú v úlohách.',
+'name' => 'Meno/názov',
+'order' => 'Usporiadať',
+'back' => 'Späť',
+'text' => 'Text',
+'highlight' => 'Zvýrazniť',
+'show' => 'Zobraziť',
+'owner' => 'Vlastník',
+'selectowner' => 'Zvoľ vlastníka',
+'update' => 'Aktualizuj',
+'addnew' => 'Pridať nové',
+'flysprayprefs' => 'Flyspray nastavenia',
+'projecttitle' => 'Titul projektu',
+'baseurl' => 'Základná URL pre inštaláciu systému:',
+'replyaddress' => 'OdpoveÄ pre e-mail notifikáciu',
+'themestyle' => 'Téma / Štýl',
+'language' => 'Jazyk',
+'anonview' => 'Povoliť neprihláseným užívateľom prezerať úlohy',
+'allowanon' => 'Povoliť neprihláseným užívateľom otvoriť úlohy',
+'never' => 'Nikdy',
+'anonymously' => 'Anonymne',
+'afterregister' => 'Len po prihlásení',
+'spamproof' => 'Zapnúť potvrdzujúci kód pri registrácii nových užívateľov',
+'anongroup' => 'Skupina pre nové registrácie užívateľov',
+'groupassigned' => 'Členom tejto skupiny môžu byť priradené úlohy',
+'forcenotify' => 'Force task notifications as',
+'neversend' => 'Nikdy nepodlať',
+'userchoose' => 'Dovoľ každému používateľovi vybrať',
+'email' => 'Email',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Prednastavený vlastník kategórie',
+'noone' => 'Nikto',
+'jabbernotify' => 'Jabber Notifikácie',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Užívateľské konto',
+'jabberpass' => 'Užívateľské heslo',
+'saveoptions' => 'Uložiť voľby',
+'editcomment' => 'Editovať komentár',
+'commentby' => 'Komentoval',
+'saveeditedcomment' => 'Uložiť editovaný komentár',
+'projectprefs' => 'Nastavenia projektu',
+'pagetitle' => 'Titulok stránky',
+'defaultproject' => 'Predvolený projekt',
+'projectlists' => 'Zoznam projektov',
+'showlogo' => 'Zobraziť logo v titulku',
+'showgravatars' => 'Zobraziť Gravatarov',
+'emailNoHTML' => 'Žiadne HTML v E-mailoch',
+'intromessage' => 'Ǔvodná správa',
+'isactive' => 'Projekt je aktívny',
+'createproject' => 'Vytvoriť nový projekt',
+'nopermission' => 'Nemáte oprávnenia na prezeranie tejto stránky',
+'listordertip' => 'The order these items will appear in the list ',
+'listshowtip' => 'Zobraz túto položku v zozname',
+'categoryownertip' => 'Táto soba bude upozornená pri otvorení novej úlohy v tejto kategórii',
+'categoryparenttip' => ' RodiÄovská kategória pod ktorú bude patriÅ¥ táto nová',
+'notsubcategory' => 'Žiadna (základná kategória)',
+'showinlineimages' => 'Zobraziť vložené obrázky príloh',
+'dateformat' => 'Formát dátumu',
+'dateformat_extended' => 'Detailný formát dátumu',
+'cache_feeds' => 'Cache feeds',
+'no_cache' => 'No caching',
+'cache_disk' => 'Cache on disk',
+'cache_db' => 'Cache in DB',
+'subcategoryof' => 'Sub-category of',
+'visiblecolumns' => 'Stĺpce zobrazené v zozname dotazov',
+'visiblefields' => 'Zobraz políÄka pri pridaj / oprav / zobraz úlohu',
+'tense' => 'ÄŒas',
+'listtensetip' => 'Minulosť, prítomnosť alebo budúcnosť',
+'past' => 'Minulosť',
+'present' => 'SúÄastnosÅ¥',
+'future' => 'Budúcnosť',
+'oldpass' => 'Staré heslo',
+'nooldpass' => 'Nebolo zadané staré heslo',
+'oldpasswrong' => 'Staré heslo je nesprávne',
+'changepass' => 'Zmeniť heslo',
+'confirmpass' => 'Potvrdiť heslo',
+'projectmanager' => 'Project Manager',
+'viewtasks' => 'Zobraz dotazy',
+'modifyowntasks' => 'Upraviť vlastné dotazy',
+'modifyalltasks' => 'Upraviť úlohy, ktoré nie sú užívateľa',
+'viewcomments' => 'Zobraziť komentáre',
+'editcomments' => 'Editovať komentáre',
+'deletecomments' => 'Zmazať komentáre',
+'viewattachments' => 'Zobraziť prílohy',
+'createattachments' => 'Vytvoriť prílohy',
+'deleteattachments' => 'Zmazať prílohy',
+'viewhistory' => 'Zobraziť históriu',
+'closeowntasks' => 'Uzavrieť vlastné dotazy',
+'closeothertasks' => 'Zatvoriť úlohy, ktoré sú užívateľa',
+'assigntoself' => 'Priradiť úlohy sebe (ak nie sú priradené)',
+'assignotherstoself' => 'Priradiť ostatné úlohy sebe',
+'viewreports' => 'Zobraziť log udalostí',
+'othersview' => 'Povoliť všetkým vidieť tento projekt',
+'usersandgroups' => 'Užívatelia a skupiny',
+'globalgroup' => 'Globálna skupina',
+'globalgroups' => 'Globálne skupiny',
+'defaultglobalgroup' => 'Predvolená globálna skupina pre nových užívateľov',
+'addtogroup' => 'Pridať do skupiny',
+'moveuserstogroup' => 'Premiestniť užívateľov do skupiny',
+'nogroup' => 'Žiadna skupina - odstrániť z projektu',
+'eventdesc' => 'Popis udalosti',
+'requestedby' => 'Vyžiadané od',
+'daterequested' => 'Dátum je požadovaný',
+'closetask' => 'Uzavrieť dotaz',
+'reopentask' => 'Znovuotvoriť dotaz',
+'applymember' => 'PožiadaÅ¥ o Älenstvo v projekte',
+'forcurrentproj' => 'Pre aktuálny projekt',
+'lostpw' => 'Lost password retrieval',
+'lostpwexplain' => 'Zadajte Vaľe prihlasovacie meno pre poslanie adresy pre zmeny hesla. Adresa bude poslaná do Vašeho profilu',
+'sendlink' => 'Poslať link',
+'savenewpass' => 'Uložiť nové heslo',
+'anonreg' => 'Povoliť registráciu nových používateľov',
+'allowanonopentask' => 'Dovoliť neprihláseným používateľom otvoriť úlohy',
+'editglobalgroup' => 'Editovať globálnu skupinu',
+'editgroupforproj' => 'Upraviť skupinu pre projekty',
+'notshownforadmin' => 'Práva nie sú zobrazené pre skupinu administrátorov. Nemusíte ich upravovať',
+'general' => 'Hlavné',
+'userregistration' => 'Registrácia',
+'notifications' => 'Notifikácie',
+'resetoptions' => 'Resetovať voľby',
+'preferences' => 'Nastavenia',
+'tasktypes' => 'Typy dotazov',
+'resolutions' => 'Rozlíšenie',
+'categories' => 'Kategórie',
+'operatingsystems' => 'OperaÄné systémy',
+'versions' => 'Verzie',
+'admintoolboxlong' => 'Administrátorsky Toolbox',
+'newproject' => 'Nový projekt',
+'delete' => 'Zmazať',
+'link' => 'Linka',
+'referencelinks' => 'ReferenÄné odkazy:',
+'listdeletetip' => 'Zmazať túto položku zo zoznamu',
+'lookandfeel' => 'Vzhľad',
+'globaltheme' => 'Globálna téma',
+'emailnotify' => 'Emailové notifikácie',
+'fromaddress' => 'Z adresy',
+'smtpserver' => 'SMTP Server',
+'smtpuser' => 'SMTP užívateľ',
+'smtppass' => 'SMTP heslo',
+'addrewrite' => 'Použi prepis adresy',
+'usereminderdaemon' => 'Povoliť pripomínajúceho daemona bežiaceho na pozadí',
+'tasksperpage' => 'Úloh na stranu v zozname úloh',
+'addtoassignees' => 'Add self to assignees',
+'taskstatuses' => 'Statusy úloh',
+'canvote' => 'Môže hlasovať za úlohy',
+'loginsuccessful' => 'Prihlásenie bolo úspešné.',
+'youareloggedout' => 'Boli ste úspešne odhlásený.',
+'waitwhiletransfer' => 'Prosím Äakajte na presmerovanie...',
+'clicknowait' => 'Kliknite sem, ak nechcete ÄakaÅ¥.',
+'accountdisabled' => 'VaÅ¡ úÄet bol pozastavený. Kontaktujte prosím hlavného administrátora pre informácie.',
+'task' => 'Úlohy',
+'edittask' => 'Upraviť túto ulohu',
+'openedby' => 'Založené',
+'editedby' => 'Naposledy upravené',
+'tasktype' => 'Typ úlohy',
+'category' => 'Kategória',
+'status' => 'Status',
+'assignedto' => 'Pridelené',
+'operatingsystem' => 'OperaÄný systém',
+'severity' => 'Záležitosť',
+'reportedversion' => 'Hlásená verzia',
+'dueinversion' => 'Kvôli verzii',
+'defaultdueinversion' => 'Predvolený dôvod vo verzii pre nové úlohy',
+'undecided' => 'Nerozhodnuté',
+'percentcomplete' => 'Percent Complete',
+'details' => 'Podrobnosti',
+'savedetails' => 'Uložiť podrobnosti',
+'canceledit' => 'Zružiť úpravu',
+'anonymous' => 'Anonymný prispievateľ',
+'complete' => 'UkonÄené',
+'closedby' => 'Uzamknuté',
+'reasonforclosing' => 'Dôvod zamknutia:',
+'reopenthistask' => 'Znovu otvoriť túto úlohu',
+'comments' => 'Komentáre',
+'attachments' => 'Prílohy',
+'relatedtasks' => 'Podobné úlohy',
+'edit' => 'Upraviť',
+'addcomment' => 'Pridať komentár',
+'fileuploadedby' => 'Súbor pridaný užívateľom',
+'uploadafile' => 'Pridať prílohu',
+'addalink' => 'Pridaj linku',
+'addanotherlink' => 'Pridaj ÄalÅ¡iu linku',
+'uploadnow' => 'Poslať',
+'thesearerelated' => 'Tieto úlohy sú podobné',
+'remove' => 'Vymazať',
+'addnewrelated' => 'Pridať podobnú úlohu',
+'add' => 'Pridať!',
+'otherrelated' => 'Úlohy, ktoré sô podobné tejto: ',
+'receivenotify' => 'Týto užívatelia budú upozornení pri zmene úlohy.',
+'addusertolist' => 'Pridaj používateľa do zoznamu',
+'addtolist' => 'Pridaj do zoznamu',
+'addmyself' => 'Pridaj sa do tohto zoznamu',
+'removemyself' => 'Odstrániť sa z tohto zoznamu',
+'theseusersnotify' => 'Týto užívatelia budú upozornení pri akejkoľvek zmene tejto úlohy.',
+'attachedtoproject' => 'Pridané k projektu',
+'reminders' => 'Pripomienky',
+'system' => 'System',
+'remindthisuser' => 'Pripomenúť tohto používateľa',
+'thisoften' => 'This often',
+'startafter' => 'Wait before starting reminders',
+'hours' => 'Hodín',
+'days' => 'Dní',
+'weeks' => 'Týždňov',
+'addreminder' => 'Pridať upomienku',
+'defaultreminder' => 'Pripomienka: Pozrite sa na nasledujúcu úlohu:',
+'message' => 'Správa',
+'closed' => 'Zamknuté',
+'filename' => 'Názov súboru:',
+'date' => 'Dátum',
+'filesize' => 'Veľkosť súboru:',
+'closurecomment' => 'Pripomienky k zamknutiu:',
+'history' => 'História',
+'nohistory' => 'Žiadna história nie je k dispozícii.',
+'eventdate' => 'Dátum',
+'user' => 'Používateľ',
+'event' => 'Udalosť',
+'fieldchanged' => 'Pole zmenené',
+'taskopened' => 'Úloha otvorená',
+'taskreopened' => 'Úloha bola znovu otvorená',
+'taskclosed' => 'Úloha uzavretá',
+'commentadded' => 'Pridaný komentár',
+'commentedited' => 'Upravený komentár',
+'commentdeleted' => 'Komentár bol zmazaný',
+'attachmentadded' => 'Príloha pridaná',
+'attachmentdeleted' => 'Priloha zmazaná',
+'taskedited' => 'Zmenené podrobnosti úlohy',
+'notificationadded' => 'Používateľ bol pridaný do zoznamu notifikácií',
+'notificationdeleted' => 'Používateľ bol zmazaný zo zoznamu notifikácií',
+'relatedadded' => 'Podobné úlohy pridané',
+'relateddeleted' => 'Podobné úlohy zmazané',
+'taskassigned' => 'Úlohy pridelené ku',
+'taskreassigned' => 'Ǔlohy znovu pridelené ku',
+'assignmentremoved' => 'Odstránené priradenia',
+'summary' => 'Súhrn',
+'addedasrelated' => 'Úlohy pridané do zoznamu podobných ku',
+'deletedasrelated' => 'Úlohy zmazané zo zoznamu podobných ku',
+'reminderadded' => 'Poznámka pridaná',
+'reminderdeleted' => 'Poznámka odstránená',
+'priority' => 'Priorita',
+'previousvalue' => 'Predchádzajúca hodnota',
+'newvalue' => 'Nová hodnota',
+'selectareason' => 'Vyberte dôvod',
+'assigntome' => 'Prideľ ku mne',
+'reopenrequest' => 'Znovuotvoriť požiadavku',
+'requestclose' => 'Zatvoriť požiadavku',
+'ownershiptaken' => 'Používateľ prevzal vlastníctvo',
+'closerequestmade' => 'Requested task closure',
+'reopenrequestmade' => 'Požadované na znovuotvorenie',
+'taskdependson' => 'Táto úloha závisí na',
+'taskblocks' => 'Táto úloha blokuje pre uzatvorenie',
+'depadded' => 'Pridaná závislosť',
+'depaddedother' => 'Táto úloha pridaná ako závislosť',
+'depremoved' => 'Závislosť zmazaná',
+'depremovedother' => 'Táto úloha bola odstránená zo zosnamu závislostí',
+'showdetailserror' => 'Táto úloha neexistuje, alebo nemáte právo na prezeranie.',
+'makeprivate' => 'Súkromné',
+'makepublic' => 'Verejné',
+'taskmadeprivate' => 'Úloha je súkromná',
+'taskmadepublic' => 'Úloha je už verejná',
+'confirmdeletecomment' => 'SkutoÄne si želáte vymazaÅ¥? %s',
+'confirmdeleteattach' => 'SkutoÄne si prajete odtrániÅ¥ túto prílohu?',
+'selectedhistory' => 'Zobrazije podrobnosti vybranej histórie ',
+'showallhistory' => 'Znovu zobraziť celú históriu',
+'hidethis' => 'Znovu skry túto oblasť',
+'mark100' => 'OznaÄ Ãºlohu 100% splnenú',
+'watchtask' => 'sleduj úlohu',
+'stopwatching' => 'ukonÄiÅ¥ sledovanie',
+'commentlink' => 'Odkazovať na tento komentár',
+'submitreq' => 'Poslať požiadavku',
+'reasonforreq' => 'Dôvod požiadavky',
+'pmreqdenied' => 'Vedúci projektu zamietol požiadavku',
+'taskpendingreq' => 'ÄŒaká sa na odpoveÄ vedúceho projektu. Viac informácií je možné nájsÅ¥ v histórii',
+'previoustask' => 'Predchádzajúca úloha',
+'nexttask' => 'Nasledujúca úloha',
+'duedate' => 'Kvôli dátumu',
+'attachnoperms' => 'Tento komentár obsahuje prílohy, ale nemáte právo na ich prezeranie',
+'linknoperms' => 'Existujú odkazy s týmto komentárom, ale nemáte oprávnenie na ich zobrazenie.',
+'open' => 'Otvoriť',
+'depgraph' => 'Zobraz graf závislosti',
+'reset' => 'Reset',
+'selectusers' => 'Vyber používateľov...',
+'addmetoassignees' => 'Pridaj ma k nástupcovi',
+'addedtoassignees' => 'Používatelia pridaní do zoznamu nástupcov',
+'dependencygraph' => 'Graf závislosti',
+'attachanotherfile' => 'PripojiÅ¥ Äalší súbor',
+'OK' => 'OK',
+'addvote' => 'Hlasovať',
+'disable_lostpw' => 'Zakázať stratené vyhľadanie hesla',
+'disable_changepw' => 'Zakázať vytvoriť / editovať heslo',
+'notifyfromfs' => 'Upozornenie od Flyspray',
+'autogenerated' => 'TÃTO SPRÃVA BOLA AUTOMATICKY VYGENEROVANÃ. PROSÃM NEODPOVEDAJTE NA ŇU',
+'forward' => 'Nasledujúci',
+'previous' => 'Predchádzajúci',
+'next' => 'Ďalší',
+'first' => 'Prvý',
+'last' => 'Posledný',
+'page' => 'Strana %d z %d',
+'search' => 'Hľadaj',
+'alltasktypes' => 'Typ všetkých úloh',
+'allseverities' => 'All Severities',
+'alldevelopers' => 'Všetci vývojári',
+'notyetassigned' => 'Ešte nepriradené',
+'allcategories' => 'Všetky kategórie',
+'allstatuses' => 'VÅ¡etky statusy',
+'allopentasks' => 'Všetky otvorené úlohy',
+'sortthiscolumn' => 'Usporiadať podľa tohto stĺpca',
+'id' => 'ID',
+'project' => 'Projekt',
+'dateopened' => 'Otvorené',
+'progress' => 'Stav',
+'searchthisproject' => 'Vyhľadať v tomto projekte',
+'dueanyversion' => 'Rôznu verziu',
+'anyversion' => 'Reported In Any Version',
+'dueversion' => 'Due In Version',
+'lastedit' => 'Naposledy upravené',
+'os' => 'OperaÄný systém',
+'reportedin' => 'Zoradené v',
+'taskrange' => 'Zobrazemé úlohy %d - %d z %d',
+'noresults' => 'Neboli nájdené žiadne výsledky.',
+'takeaction' => 'Take action.',
+'watchtasks' => 'Sledovať vybrané úlohy.',
+'stopwatchingtasks' => 'Prestať sledovať úlohy',
+'assigntaskstome' => 'PriraÄ mi úlohu',
+'dueby' => 'Due by',
+'dueanytime' => 'Due anytime',
+'selectduedate' => 'Select Due Date',
+'toggleselected' => 'Toggle Selected',
+'due' => 'Due',
+'assignedtome' => 'PriraÄ ku mne',
+'tasklist' => 'Zoznam úloh',
+'dateclosed' => 'Dátum uzavretý',
+'advanced' => 'PokroÄilé',
+'searchcomments' => 'Hľadaj v komentároch',
+'searchforall' => 'Hľadaj vo všekých slovách',
+'anonusers' => 'Anonymní používatelia',
+'miscellaneous' => 'Rôzne',
+'users' => 'Používatelia',
+'taskproperties' => 'Podrobnosti úlohy',
+'selectsincedate' => 'Vyber zmenené od',
+'changedsince' => 'Zmenené od',
+'updatefs' => 'Vykonajte update FlySpray, prosím.',
+'currentversion' => 'Aktuálna verzia je',
+'latestversion' => 'a najnovšia verzia je',
+'hidemessage' => '(upozorni ma neskôr)',
+'saveas' => 'Ulož výsledok hľadania ako',
+'nosearches' => 'Žiadne uložené výsledky hľadania',
+'saving' => 'Ukladám...',
+'votes' => 'Hlasy',
+'allclosedtasks' => 'Všetky uzatvorené úlohy',
+'password' => 'Heslo',
+'login' => 'Prihlásiť!',
+'rememberme' => 'Upozorni ma',
+'lostpassword' => 'Zabudnuté heslo?',
+'lostpwforfs' => 'Zabudnuté heslo pre Flyspray',
+'lostpwmsg1' => "Ahoj.\n\nI have lost my password for Flyspray on ",
+'lostpwmsg2' => ", prosím poskytni mi nové heslo.\n\nPoužívateľské heslo: ",
+'regards' => "\n\nS pozdravom,",
+'yourusername' => ' Vaše prihlasovacie meno ',
+'locale' => 'sk-SK',
+'filenotexist' => 'Súbor neexistuje alebo nemáte oprávnenia na jeho prezeranie',
+'showtask' => 'Zobraz úlohy',
+'now' => 'Teraz',
+'go' => 'ÃsÅ¥',
+'opentaskanon' => 'Anonymne vytvoriť novú úlohu',
+'register' => 'Registrovať',
+'addnewtask' => 'Pridať novú úlohu',
+'reports' => 'Log udalosti',
+'editmydetails' => 'Upraviť moje nastavenia',
+'logout' => 'Odhlásiť',
+'disabledaccount' => 'VaÅ¡ úÄet bol pozastavený!<br />Odhlasovanie...',
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray je hrdo sponzorovaný',
+'projects' => 'Projekty',
+'allprojects' => 'VÅ¡etky projekty',
+'selectproject' => 'pre projekt:',
+'tasksall' => 'Všetky úlohy',
+'tasksassigned' => 'Úlohy mne pipísané',
+'tasksreported' => 'Úlohy mi oznámené',
+'taskswatched' => 'Sledované úlohy',
+'mysearch' => 'Moje výsledky hľadania',
+'admintoolbox' => 'Admin Toolbox',
+'manageproject' => 'Spravovať projekt',
+'permissions' => 'Zobraz oprávnenia',
+'hide' => 'Skry',
+'pendingreq' => 'PM požiadavka Äaká',
+'errorpage' => "Flyspray nemôže poskytnúť požadovanô stránku.\n Pravdepodobne neexistuje, alebo nemáte oprávnenia pre jej zobrazenie.<br /><br />\n Prípadne sa nepokúšajte o SQL Injection",
+'permissionsforproject' => 'Práva pre ',
+'switchto' => 'Prepnúť do',
+'lastsearch' => 'Posledné hľadanie',
+'modify' => 'Upraviť',
+'noticefrom' => 'Oznámenie od',
+'hasopened' => 'hotvoril novú úlohu a priradil ju ku Vám:',
+'moreinfonew' => 'Viac informácií o tejto chybe je možné nájsť na Flyspray adrese:',
+'newtaskcategory' => 'Nová Flyspray úloha bola vytvorená v kategórii',
+'categoryowner' => 'Táto správa Vám bola poslaná, pretože ste majiteľ tejto katogórie.',
+'tasksummary' => 'Súhrn úlohy:',
+'newtaskadded' => 'Vaša nová úloha bola pridaná.',
+'summaryanddetails' => 'Musíte vyplniť aj súhrn aj podrobnosti.',
+'goback' => 'ÃsÅ¥ späť.',
+'messagefrom' => 'Táto správa je od ',
+'hasjustmodified' => 'práve upravil nasledujúcu úlohu.',
+'changedfields' => 'Zmenené polia sú vyznaÄene hviezdiÄkami (**)',
+'moreinfomodify' => 'Viac informácií o tejto úlohe je možné nájsť na:',
+'nolongerassigned' => 'Táto úloha už nie je pripísaná Vám. Je pripísaná ',
+'hasassigned' => 'pripísal úlohu:',
+'taskupdated' => 'Úloha bola aktualizovaná.',
+'tasksupdated' => 'Úlohy boli aktualizované.',
+'hasclosedassigned' => 'uzavrel úlohu, ku ktorej si bol pripísaný :',
+'unassigned' => 'OdznaÄené',
+'hasclosed' => 'uzavrel túto úlohu.',
+'youonnotify' => 'Táto správa Vám bola zaslaná, pretože ste v zozname oznámení.',
+'taskclosedmsg' => 'Úloha bola uzavretá.',
+'returntotask' => 'Späť na podrobnosti úlohy.',
+'backtoindex' => 'ÃsÅ¥ späť na zoznam',
+'noclosereason' => 'Nevybrali ste dôvod uzatvorenia tejto úlohy.',
+'hasreopened' => 'otvoril úlohu, ktorú ste zatvorili:',
+'taskreopenedmsg' => 'Úloha bola znovu otvorená.',
+'backtotask' => 'ÃsÅ¥ späť na úlohy.',
+'commentaddedmsg' => 'Komentár bol pridaný.',
+'commenttoassigned' => 'pridal komentár k úlohe, ku ktorej ste bol pripísaný: ',
+'commenttotask' => 'pridal nasledujúci komentár k úlohe.',
+'nocommententered' => 'Vložte prosím komentár.',
+'fillinfields' => 'Nevyplnili ste všetky polia.',
+'notcurrentpass' => 'Vložili ste zlé aktuálne heslo!',
+'passchanged' => 'Vaše heslo bolo zmenené.',
+'closewindow' => 'Môžete zavrieť okno.',
+'passnomatch' => 'Nové heslá sa nezhodujú!',
+'usernametaken' => 'Vybrané používateľské heslo sa už používa. Vyberte iné.',
+'usernametakenbulk' => 'Užívateľské meno je už použité',
+'newusercreated' => 'Nový úÄet bol vytvorený.',
+'accountcreated' => 'VaÅ¡ úÄet bol úspeÅ¡ne vytvorený.',
+'newuserwarning' => 'Note that the global preferences might require your account to be approved by an admin. If you cannot login, this is probably why.',
+'nomatchpass' => 'Heslá sa nezhodujú.',
+'confirmwrong' => 'Potvrdzovací kód je nesprávny!',
+'formnotcomplete' => 'Formulár nebol kompletne vyplnený.',
+'formnotnumeric' => 'Zadané dáta musia byÅ¥ Äíselné',
+'groupnametaken' => 'Názov skupiny sa už používa.',
+'newgroupadded' => 'Nová skupina bola pridaná.',
+'optionssaved' => 'Nastavenia Flyspray boli uložené.',
+'hasuploaded' => 'has uploaded a file attachment to a task that have been assigned:',
+'hasattached' => 'has attached a file to the following task.',
+'fileuploaded' => 'Súbor bol nahraný.',
+'fileerror' => 'PoÄas nahrávania súboru sa vyskytla chyba. Pravdepodobne sú nesprávne nastavené prístupové práva pre adresár <i>attachments/</i>.',
+'contactadmin' => 'Kontaktujte Administrátora pre tento projekt.',
+'selectfileerror' => 'Nevybrali ste žiadny súbor.',
+'userupdated' => 'Údaje o používateľovi boli upravené',
+'realandemail' => 'Nevyplnili ste polia skutoÄné meno a e-mailová adresa.',
+'groupupdated' => 'Definícia skupiny bolo upravená.',
+'groupanddesc' => 'Nevyplnili ste názov skupiny.',
+'fillallfields' => 'Prosím, vyplňte všetky polia.',
+'listPmustN' => '"Poradie" musí byÅ¥ Äíslo.',
+'listupdated' => 'Zoznam bol upravený.',
+'listitemadded' => 'Nová položka bola pridaná.',
+'relatedaddedmsg' => 'Úloha bola pridaná do zoznamu..',
+'relatederror' => 'That task is already on this related task list.',
+'relatedremoved' => 'Related task removed from list.',
+'notifyadded' => 'Používateľ bol pridaný do zoznamu notifikácií.',
+'notifyerror' => 'Tento používateľ sa už nachádza v notifikaÄnom zozname pre túto úlohu.',
+'notifyremoved' => 'Používateľ bol odstránený z notifikaÄného zoznamu.',
+'editcommentsaved' => 'Upravený komentár bol uložený.',
+'commentdeletedmsg' => 'Komentár bol zmazaný.',
+'gotonewtask' => 'ChoÄ do práve vytvorenej úlohy.',
+'projectcreated' => 'Váš nový projekt bol vytvorený. Teraz môžete upraviť projekt v sekcii správy projektov.',
+'customiseproject' => 'Upraviť tento projekt',
+'projectupdated' => 'Nastavenia projektu boli upravené',
+'emptytitle' => 'Názov projektu ste nechali prázdny. Vráťte sa a upravte to.',
+'loginbelow' => 'Teraz sa môžete skúsiť prihlásiť.',
+'attachmentdeletedmsg' => 'Príloha bola zmazaná.',
+'reminderaddedmsg' => 'Váš pripomienkovaÄ bol pridaný.',
+'reminderdeletedmsg' => 'Vybraný pripomienkovaÄ bol zmazaný.',
+'flyspraytask' => 'Flyspray úloha',
+'fieldsmissing' => 'Niektoré polia obsahovali nesprávne dáta alebo neobsahovali niÄ.',
+'relatedinvalid' => 'Táká úloha neexistuje.',
+'relatedproject' => 'Táto úloha je pridelená k inému projektu. Chcete napriek tomu priÄleniÅ¥ úlohu k tomuto projektu?',
+'addanyway' => 'Pridať',
+'cancel' => 'Zrušiť',
+'alreadyedited' => 'Táto úloha bola upravená niekým iným pred tým, ako ste ju uložili. Chcete napriek tomu uložiť zmeny?',
+'saveanyway' => 'Uložiť napriek tomu.',
+'nouserselected' => 'Žiadny užívateľ nebol vybraný. Vyberte aspoň jedného užívateľa a skúste znovu.',
+'groupswitchupdated' => 'Skupina užívateľov bola úspešne upravená.',
+'takenownershipmsg' => 'Táto úloha bola Vám pripísana.',
+'adminrequestmade' => 'Vaša požiadavka bola poslaná vedúcemu projektu.',
+'newdepnotify' => 'Nová závislosť bola pridaná k nasledujúcej úlohe:',
+'dependadded' => 'Závislosť úlohy bola pridaná',
+'dependaddfailed' => 'Vyskytol sa problém pri pridávaní závislosti. Uistite sa, že úloha existuje a nie je blokovaná.',
+'depremovedmsg' => 'Závislosť na úlohe bola zmazaná.',
+'newdepis' => 'Nová závislosť je ',
+'magicurlsent' => 'Bola Vám poslaná správa na Váš e-mail. Obsahuje adresu, na ktorej môžete dokonÄiÅ¥ túto úlohu.',
+'changefspass' => 'Zmeniť heslo',
+'magicurlmessage' => 'Kliknite na dole uvedenú adresu pre zmenu Vašeho hesla:',
+'erroronform' => 'Vyskytol sa problém v posielaných dátach.',
+'addressused' => 'Táto e-mailová adresa bola použitá pri registrácii Flyspray úÄtu. Ak ste túto adresu dostali náhodne vymaže ju. Pre dokonÄenie registrácie navÅ¡tívte nasledujúcu adresu:',
+'confirmcodeis' => 'Váš povrdzujúci kót je:',
+'codesent' => 'Váš potvrdzujúci kod bol poslaný. Nasledujte inštrukcie obsiahnuté v poslanej správe.en sent.',
+'codenotsent' => 'Nebolo Vám možné poslať kód. Skúste neskôr, prosím.',
+'taskmadeprivatemsg' => 'Táto úloha je súkromná.',
+'taskmadepublicmsg' => 'Táto úloha je znovu verejná.',
+'realandnotify' => 'Musíte zadaÅ¥ skutoÄné meno a taktiež e-mail, Jabber.',
+'pmreqdeniedmsg' => 'Vedúci projektu žiadosť zamietol.',
+'massopsuccess' => 'Hromadné operácie boli uskutoÄnené - práva povolili ich úpravu.',
+'usernotexist' => 'Užívateľ neexistuje.',
+'commentattachperms' => 'Komentár nemôže byť zmazaný - nemáte práva na mazanie prílohh.',
+'voterecorded' => 'Váš hlas bol zaznamenaný',
+'votefailed' => 'Váš hlas nemožno momentálne spracovať.',
+'createnewgroup' => 'Vytvoriť novú skupinu',
+'requiredfields' => 'Povinné údaje sú vyznaÄené',
+'addthisgroup' => 'Pridať túto skupinu',
+'createnewproject' => 'Vytvoriť nový projekt',
+'addnewproject' => 'Pridať nový projekt',
+'htmlallowed' => 'HTML kódy sú povolené',
+'createthisproject' => 'Vytvoriť tento projekt',
+'inlineimages' => 'Zobraziť obrazové prílohy v texte.',
+'createnewtask' => 'Vytvoriť nový dotaz v tomto projekte:',
+'addanother' => 'VytvoriÅ¥ ÄalÅ¡iu úlohu.',
+'addthistask' => 'Pridať úlohu',
+'notifyme' => 'Upozorni ma pri akejkoľvek zmene v tejto úlohe.',
+'newtask' => 'Nová úloha',
+'attachafile' => 'Priložiť súbor',
+'registernewuser' => 'Registrovať nového užívateľa',
+'none' => 'Žiadne',
+'registeraccount' => 'Registrovať toto konto',
+'registerbulkaccount' => 'Zoznam úÄtov',
+'both' => 'Oboje',
+'notifyfrom' => 'Notifikácia od ',
+'donotreply' => 'TÃTO SPRÃVA JE AUTOMATICKà SPRÃVA SYSTÉMU. NEODPOVEDAJTE NA ŇU.',
+'disclaimer' => 'Táto správa Vám bola poslaná, pretože ste si ju vyžiadali od Flyspray. Ak si neželáte, alebo ste túto správu neoÄakávali, môžete upraviÅ¥ nastavenia na adrese uvedenej vyÅ¡Å¡ie.',
+'userwho' => 'Užívateľ, ktorý to urobil',
+'moreinfo' => 'Viac informácií môžete nájsť na nasledovnej adrese:',
+'newtaskopened' => 'Nová Flypray úloha bola vytvorená. Podrobnosti nájdete nižšie.',
+'notify.taskclosed' => 'Nasledujúca úloha sa uzavrela:',
+'notify.taskreopened' => 'Nasledujúca úloha bola znovu otvorená.',
+'newdep' => 'Nasledujúca úloha má novú závislosť.',
+'notify.depremoved' => 'Nasledujúca úloha mala odstránenú závislosť:',
+'olddepwas' => 'Bývalá závislosť bola',
+'notify.commentadded' => 'Nasledujúca úloha má nový komentár:',
+'commentis' => 'Text komentáru je uvedený nižšie.',
+'newattachment' => 'Nový súbor bol priložený k nasledovnej úlohe:',
+'detailsbelow' => 'Detaily sú nižšie.',
+'notify.relatedadded' => 'Nová podobná úloha bola pridaná k tejto úlohe:',
+'relatedis' => 'Podobná úloha je',
+'assignedtoyou' => 'Boli ste priradený k nasledovnej úlohe:',
+'takenownership' => 'prevzal vlastníctvo nasledovnej úlohe:',
+'requiresaction' => 'Nasledovná úloha vyžaduje zásah vedúceho projektu:',
+'requiresactionnotify' => 'Úloha vyžaduje zásah vedúceho projektu',
+'pmdeny' => 'Vedúci projektu zamietol požiadavku k nasledujúcej úlohe:',
+'pmdenynotify' => 'Vedúci projektu zamietol požiadavku',
+'fileaddedtoo' => 'Jeden alebo viac súborov bolo priložených.',
+'taskwatching' => 'Nasledujúca úloha, ktorú sledujete,',
+'isdepfor' => 'je nová závislosť k',
+'denialreason' => 'Dôvod zamietnutia',
+'taskchanged' => 'Nasledujúca úloha bola zmenená. Zmeny sú uvedené nižšie. Pre viac informácií o zmenách sa nachádza na nasledujúcej adrese v sekcii história.',
+'useraddedtoassignees' => 'Užívateľ sa pridal do zoznamu pridelených užívateľov úlohy.',
+'removeddepis' => 'Zmazaná závislosť je',
+'isnodepfor' => 'už nie je závislosť pre',
+'usergroups' => 'Užívateľské skupiny',
+'pmtoolbox' => 'Toolbox vedúceho projektu',
+'groupmanage' => 'Management skupín',
+'pendingrequests' => 'Čakajúce žiadosti',
+'reasongiven' => 'Dôvod zadaný',
+'nopendingreq' => 'Žiadne žiadosti pre projektového manažéra.',
+'givereason' => 'Podať riešenie',
+'catlisted' => 'Editor zoznamu kategórií',
+'oslisted' => 'Editor zoznamu operaÄných systémov',
+'verlisted' => 'Editor verzií',
+'tasktypeed' => 'Editor typov úloh',
+'resed' => 'Editor rozdelenia',
+'deny' => 'Zakáž',
+'notifiedwhen' => 'NotifikovaÅ¥ keÄ',
+'onlynewtasks' => 'Nové úlohy boli otvorené',
+'allevents' => 'Any event occurs in any task',
+'feeds' => 'Odobery',
+'feeddescription' => 'Popis oderu',
+'feedimgurl' => 'Feed adresa obrázku URL (pre žiadny obrázok nechajte prázdne)',
+'notifysubject' => 'Predmet pre notifikácie',
+'notifysubjectinfo' => '(%p = názov projektu, %s = zhrnutie úlohy, %t = id úlohy, %a = akcia, %u = užívateľ)',
+'priority6' => 'BLESK',
+'priority5' => 'Mimoriadna',
+'priority4' => 'Naliehavá',
+'priority3' => 'Vysoká',
+'priority2' => 'Normalna',
+'priority1' => 'Nízka',
+'sendcode' => 'Posielam kód!',
+'entercode' => 'Vložte potvrdzujúci kód, ktorý ste obdržali v správe, a želané užívateľské heslo.',
+'confirmationcode' => 'Potvrdzujúci kód',
+'registererror' => 'Uistite sa, že ste zadali všetky povinné údaje a korektne vyplnili podrobnosti pre Váš požadovaný typ notifikácie.',
+'validusername' => '(iba písmená a - _ . sú povolené)',
+'validemail' => '(Použite ; na oddelenie viac e-mailových adries)',
+'emailtakenbulk' => 'Email adresa je už použitá',
+'emailtaken' => 'Táto emailová adresa alebo Jabber-ID je už evidované. Zvoľte si inú.',
+'note' => '<strong>Poznámka:</strong> Bude vám zaslaný potvrdzujúci kód pred vytvorením úÄtu. Potvrdzujúci kód bude zaslaný preferovanou metódou notifikácie.<br />Ak ste zadali nesprávne kontaktné údaje, <strong>nebude Vám možné potvrdzujúci kód poslaÅ¥</strong>.',
+'changelog' => 'Changelog',
+'changeloggen' => 'Changelog Generator',
+'listfrom' => 'Zoznam zmien (changelog) od',
+'to' => 'do',
+'oldestfirst' => 'Od najstarších',
+'recentfirst' => 'Od najnovších',
+'severityrep' => 'Severity Report',
+'totalopen' => 'Celkovo otvorených úloh',
+'age' => 'Vek',
+'agerep' => 'Vek správy',
+'eventsrep' => 'Správa udalosti',
+'events' => 'Udalosti',
+'Tasks' => 'Úlohy',
+'opened' => 'Otvorené',
+'edited' => 'Editované',
+'assigned' => 'Priradené',
+'within' => 'V rámci',
+'pastday' => 'Predchádzajúci deň',
+'pastweek' => 'Predchádzajúci víkend',
+'pastmonth' => 'Predchádzajúci mesiac',
+'pastyear' => 'Predchádzajúci rok',
+'nolimit' => 'Bez limitu',
+'from' => 'Od',
+'duein' => 'Kvôli',
+'selectfromdate' => 'Vybrať od dátumu',
+'selecttodate' => 'Vybrať po dátum',
+'showvoters' => 'Zobraziť/skryť hlasujúcich',
+'roadmap' => 'Plán',
+'roadmapfor' => 'Plán pre verziu',
+'tasks' => 'úlohy',
+'completed' => 'hotové.',
+'opentasks' => 'otvorené úlohy',
+'of' => '% z',
+'severity5' => 'Kritická',
+'severity4' => 'Vysoká',
+'severity3' => 'Stredná',
+'severity2' => 'Nízka',
+'severity1' => 'Veľmi nízka',
+'Redirect' => 'Presmerovanie',
+'redirectmsg' => 'Ak Váš prehliadaÄ nepodporuje meta presmerovanie, prosim kliknite %sTU%s na presmerovanie',
+'allowclosedcomments' => 'Povoliť komentáre na uzavretých úlohách',
+'comment' => 'Komentár',
+'editowncomments' => 'Editovať vlastné komentáre',
+'reopened' => 'Znovuotvorené',
+'loading' => 'Nahrávam...',
+'notifyown' => 'Notifikovať vlastné zmeny',
+'youremail' => 'Vaša emailová adresa',
+'thankyouforbug' => 'Ďakujeme za nahlásenie problému. Možete túto úlohu sledovať a vidieť jej priebeh kedykoľvek navštívením nasledovnej URL:',
+'anonuser' => 'Anonymný užívateľ',
+'conflict' => 'Konflikt',
+'file' => 'Súbor',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Veľkosť',
+'projectgroup' => 'Skupina projektu',
+'profile' => 'Profil:',
+'viewprofile' => 'Zobraziť profil',
+'regdate' => 'Registrovaný od',
+'tasksopened' => 'Úloha otvorená',
+'replyto' => 'Odpovedať na',
+'notifytypes' => 'Typy notifikácie',
+'pm.taskchanged' => 'Úloha zmenená',
+'pm.taskreopened' => 'Úloha znovuotvorená',
+'pm.depadded' => 'Závislosti pridané',
+'pm.depremoved' => 'Závislosti odobraté',
+'pmrequest' => 'PM požiadavka',
+'pmrequestdenied' => 'PM požiadavka zamietnutá',
+'newassignee' => 'Nové priradenie',
+'revdepadded' => 'Spätná závislosť pridaná',
+'revdepaddedremoved' => 'Spätná závislosť odobraná',
+'assigneeadded' => 'Priradenie pridané',
+'addusergroup' => 'Pridať užívateľa do tejto skupiny',
+'groupmembers' => 'ÄŒlenovia skupiny',
+'deleteuser' => 'Zmazať tohto užívateľa',
+'userdeleted' => 'Užívateľ zmazaný',
+'autoassign' => 'Auto-priradenie úlohy na majiteľa kategórie',
+'ssl' => 'SSL',
+'updatewrong' => 'Máte povolenú kontrolu aktualizácií, ale vyskytol sa problém pri pripojení k aktualizaÄnému serveru. Problém môže byÅ¥ vo VaÅ¡om hostingu (nepodporuje odchádzajúce spojenia), alebo v sieti. Skontrolujte verziu Flyspray na domovskej stránke projektu Flyspray.',
+'deleteproject' => 'Zmazať tento projekt a presunúť obsah do',
+'projectdeleted' => 'Zmazanie projektu úspešné',
+'feedforall' => 'Feed pre všetky projekty',
+'usercreated' => 'Užívateľ vytvorený',
+'created' => 'Vytvorené',
+'deleted' => 'Zmazané',
+'userid' => 'Užívateľské ID',
+'editassignments' => 'Editovať priradenia',
+'preview' => 'Náhľad',
+'anyprogress' => 'Rôzny priebeh',
+'tasksrelated' => 'Úlohy podobné tejto úlohe',
+'duplicatetasks' => 'Duplicita úlohy',
+'databasemodfailed' => 'Chyba úpravy databázy. Pravdepodobne nemáte oprávnenia na jej úpravu.',
+'frequency' => 'Frekvencia',
+'newuserregistered' => 'Nový užívateľ bol zaregistrovaný. Detail užívateľa je nasledovný:',
+'newuserregisterednotify' => 'Nový užívateľ bol zaregistrovaný',
+'notify_registration' => 'Notifikovať administrátorov pri nových registráciach',
+'textversion' => 'Textová verzia',
+'onlyprimary' => 'Úlohy neblokujú ostatné úlohy',
+'switch' => 'Prepnúť',
+'max' => 'max.',
+'dates' => 'Dátumy',
+'selectduedatefrom' => 'Kvôli od',
+'selectduedateto' => 'po',
+'selectsincedatefrom' => 'Zmenené od',
+'selectsincedateto' => 'do',
+'selectdate' => 'Vyber dátum',
+'selectopenedfrom' => 'Otvorené od',
+'selectopenedto' => 'do',
+'selectclosedfrom' => 'Zatvorené od',
+'selectclosedto' => 'do',
+'startat' => 'ZaÄiatok o',
+'hasattachment' => 'Má prílohu',
+'private' => 'Súkromné',
+'watching' => 'Sledovanie',
+'alreadyvotedthistask' => 'už ste hlasovali za túto úlohu',
+'alreadyvotedthisday' => 'už dnes zahlasované',
+'visibility' => 'Viditeľnosť',
+'public' => 'Verejné',
+'leaveemptyauto' => 'Nechajte polia na heslo prázne, ak chcete , aby sa heslo vygenerovalo automaticky.',
+'novalidemail' => 'Nezadali ste platnú e-mailovú adresu.',
+'novalidjabber' => 'Nezadali ste platnú Jabber adresu.',
+'missingrequired' => 'Nevyplnili ste všetky povinné položky.',
+'entervalidusername' => 'Prosím vložte platné užívateľské a reálne meno.',
+'couldnotaddusernotif' => 'Nedá sa pridaÅ¥ tohto užívateľa do notifikaÄného zoznamu.',
+'defaulttask' => 'Predvolený popis úlohy',
+'all' => 'všetko',
+'events.useraddedtoassignees'=> 'Užívateľ pridaný do priradení',
+'vote(s)' => 'hlas(ov)',
+'eventlog' => 'Log udalostí',
+'assignmentchanged' => 'Priradenie zmenené',
+'detailedinfo' => 'Detailné informácie',
+'All' => 'VÅ¡etko',
+'tasksireported' => 'Mnou pridané úlohy',
+'recentlyopened' => 'AKtuálne otvorené',
+'stats' => 'Å tatistiky',
+'totaltasks' => 'celkom úloh',
+'mostwanted' => 'Najžiadanejšie úlohy',
+'defaultentry' => 'Predvolená vstupná stránka',
+'toplevel' => 'Domov',
+'overview' => 'Prehľad',
+'error#' => 'Chyba #',
+'error1' => 'Nemáte oprávnenia na prezeranie tejto prílohy.',
+'error3' => 'Opakovaná akcia, presmerúvam na hlavnú stránku.',
+'error4' => 'Nemáte administrátorské oprávnenia.',
+'error5' => 'Tento užívateľ neexistuje v tejto inštalácii.',
+'error6' => 'Zlá administrátorská sekcia',
+'error7' => 'Prihlásenie zlyhalo, nesprávne heslo!',
+'error71' => 'Konto zablokované na %d minút z dôvodu opakovaných nesprávnych prihlásení!',
+'error8' => 'Nezadali ste heslo alebo užívateľské meno.',
+'error9' => 'Táto úloha neexistuje alebo nemáte oprávnenia na jej zobrazenie.',
+'error10' => 'Táto úloha neexistuje.',
+'error101' => 'Nemáte oprávnenie na zobrazenie tejto úlohy.',
+'error102' => 'Prihláste sa prosím. Ako anonymný užívateľ nemáte oprávnenie na zobrazenie tejto úlohy.',
+'error11' => 'NedostatoÄné oprávnenia na editáciu tohto komentáru.',
+'error12' => 'Nesprávny kód. Zadali ste správny kód?',
+'error13' => 'Anonymní užívatelia nemajú profil.',
+'error14' => 'Nemáte oprávnenia na vytvorenie novej skupiny.',
+'error15' => 'Nemáte oprávnenia na vytvorenie novej úlohy.',
+'error16' => 'Nie ste vedúci projektu.',
+'error17' => 'Zlá PM oblasť',
+'error18' => 'Nesprávna adresa',
+'error19' => 'Tento užívateľ neexistuje v tejto inštalácii.',
+'error20' => 'Nesprávna modifikácia databázy.',
+'error21' => 'Jeden alebo viac emailov sa nepodarilo odoslať. Skontrolujte Vašu konfiguráciu.',
+'error22' => 'Registrácia nových užívateľov je povolená.',
+'error23' => 'Užívateľ alebo skupina nie sú oprávnené k prihláseniu.',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Plán je dostupný len pre špecifický projekt.',
+'done' => 'hotovo',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projekt nemohol byť zmazaný.',
+'GMT' => 'GMT',
+'timezone' => 'Časové pásmo',
+'accept' => 'Schváliť',
+'reasonfordeinal' => 'Dôvod zamietnutia',
+'pruneclosedlinks' => 'Prune zatvorené odkazy',
+'pruneclosedtasks' => 'Prune zatvorené úlohy',
+'pagegenerated' => 'Stránka a obrázok vygenerované za %d sekúnd.',
+'pruninglevel' => 'Pruning Level',
+'lastuser' => 'Posledný užívateľ nemohol byť zmazaný.',
+'allprivate' => 'Všetky projekty sú súkromné.',
+'deletegroup' => 'Zmazať túto skupinu a presunúť užívateľov do',
+'parent' => 'RodiÄovský',
+'ordertip' => 'Usporiadanie sa zobrazí v zozname',
+'showtip' => 'Zobraziť túto položku v zozname',
+'deletetip' => 'Zmazať položku zo zoznamu',
+'del' => 'del',
+'request1' => 'Bola podaná požiadavka na uzatvorenie úlohy.',
+'request2' => 'Bola podaná požiadavka na znovuotvorenie úlohy.',
+'allpriorities' => 'VÅ¡etky priority',
+'noroadmap' => 'Plán nie je dostupný (neexistujú budúce verzie).',
+'expand' => 'Rozbaliť',
+'collapse' => 'Zbaliť',
+'expandall' => 'Rozbaliť všetko',
+'collapseall' => 'Zbaliť všetko',
+'minpwsize' => 'Minimálna dĺžka hesla je 5 znakov',
+'passwordtoosmall' => 'Dĺžka hesla je príliš malá.',
+'accountwaslocked' => 'Vaše heslo bolo zablokované z dôvodu príliš mnohých neplatných pokusov o prihlásenie.',
+'failedattempts' => 'Zaznamenané %d neplatné pokusy o prihlásenie.',
+'groupnotexist' => 'Zvolená skupina neexistuje v tomto projekte.',
+'searchindetails' => 'Hľadať detaily',
+'showasassignees' => 'Zobraz ako priradenie',
+'find' => 'Nájsť',
+'tls' => 'TLS',
+'isadmin' => 'Je admin',
+'addvotes' => 'Pridať hlasy',
+'removevote' => 'Zmaž hlasovanie',
+'voteremoved' => 'Tvoj hlas bol zmazaný',
+'voteremovefailed' => 'Váš hlas v tomto okamihu nemohol byť odstránený.',
+'connectedtasks' => 'Pripojené úlohy:',
+'taskdependencies' => 'Závislosti úloh',
+'viewgraph' => 'Zobrazenie grafu',
+'notaskdependencies' => 'Táto úloha nezávisí na iných úlohách.',
+'dependson' => 'Závisí na',
+'blocks' => 'Blokuje',
+'newdependency' => 'Nová závislosť:',
+'nouserstoadd' => 'Žiadny užívateľ nepridaný. Uistite sa prosím, meno, užívateľské meno a e-mail sú definované pre každého užívateľa.',
+'dispintro' => 'Dobraz hlavnú úvodnú správu',
+'mainmessage' => 'Hlavná úvodná správa',
+'setsupertask' => 'Nastav ID Nad-úlohy:',
+'supertaskmodified' => 'Nad-úloha bola modifikovaná',
+'set' => 'Množina',
+'supertask' => 'Nad-úloha',
+'setparent' => 'Nastav ID rodiÄovskej úlohy pre túto úlohu',
+'selfsupertasknotallowed' => 'ID nad-úlohy nemôže byť rovnaké ako ID úlohy samotnej',
+'quickaction' => 'Rýchle akcie',
+'updateselectedtasks' => 'Aktualizovať vybrané úlohy',
+'notspecified' => 'Nešpecifikované',
+'editselectedtasks' => 'Upraviť vybrané úlohy',
+'information' => 'Informácie',
+'taskclosedisabled' => 'Zatvorenie úlohy je v súÄasnosti zakázané nakoľko nasledujúce závislé úlohy sú stále otvorené: -',
+'daysleft' => 'Zvyšné dni',
+'dayoverdue' => 'dní oneskorenia',
+'duetoday' => 'OÄakávané dnes.',
+'daysbeforealert' => 'Dní pred výstrahou',
+'associatedsubtask' => 'Úspešne prepojená pod-úloha #FS',
+'associatesubtask' => 'Prepoj pod-úlohu s touto úlohou',
+'subtaskid' => 'Pod-úloha ID',
+'subtaskalreadyhasparent' => 'Zadaná pod-úloha už má nadradenú úlohu, prosím, zrušte tamto prepojenie pred pripojením.',
+'subtaskisparent' => 'Zadaná pod-úloha je rodiÄovskou úlohou tejto úlohy. Pod-úloha nie je spojená.',
+'subtasknotexist' => 'Zadaná pod-úloha neexistuje.',
+'subtaskremovedmsg' => 'Pod-úloha bola úspešne zmazaná',
+'subtaskadded' => 'Pod-úloha pridaná',
+'subtaskremoved' => 'Pod-úloha zmazaná',
+'addnewsubtask' => 'Pridať novú pod-úloha',
+'hidesubtasks' => 'Skryť pod-podúlohy',
+'voteforthistask' => 'Hlasujte pre túto úlohu',
+'watchthistask' => 'Sledujte túto úlohu',
+'privatethistask' => 'Urobiť túto úlohu súkromnú',
+'adddependenttask' => 'Pripojte závislú úlohu',
+'associatetaskid' => 'ID pod-úlohy',
+'parenttaskid' => 'ID RodiÄovskej-úlohy',
+'invalidsupertaskid' => 'ID RodiÄovskej-úlohy neplatné',
+'supertaskadded' => 'Nad-úloha pridaná',
+'effort' => 'Vynaložené úsilie',
+'efforttracking' => 'Sledovanie vynaloženého úsilia',
+'useeffort' => 'Projekt používa sledovanie vynaloženého úsilia',
+'estimatedeffort' => 'Odhadované úsilie',
+'totalestimatedeffort' => 'Celkovo odhadované úsilie',
+'actualeffort' => 'Aktuálne vznaložené úsilie',
+'starteffort' => 'ZaÄni sledovaÅ¥',
+'endeffort' => 'UkonÄi sledovanie',
+'cleareffort' => 'Vymaž sledovanie',
+'addeffort' => 'Pridaj úsilie',
+'manualeffort' => 'Manuálne pridaj úsilie (H:M)',
+'efforttrackingstarted' => 'Sledovanie úsilia pre túto úlohu zahajené.',
+'efforttrackingnotstarted'=> 'Nemožno spustiť sledovanie na problém, ktorý je už sledovaný',
+'efforttrackingstopped' => 'Sledovanie úsilia pre túto úlohu ukonÄené.',
+'efforttrackingcancelled' => 'Sledovanie úsilia pre túto úlohu prerušené.',
+'efforttrackingadded' => 'Manuálne pridanie úsilia pre túto úlohu zaznamenané.',
+'trackinginprogress' => 'Sledovanie prebieha',
+'vieweffort' => 'Je možné zobraziť sledovanie úsilia',
+'trackeffort' => 'Je možné sledovať úsilie',
+'invalideffort' => 'Úsilie ktoré ste zadali, je neplatné. Musíte zadaÅ¥ Äíslo',
+'showpass' => 'Zobraz heslo',
+'addmultipletasks' => 'Pridať viac úloh',
+'pendingnewuserrequest' => 'Čakajúce požiadavky nových používateľov',
+'adminrequestswaiting' => 'Čakajúce požiadavkz administrátora',
+'regapprovedbyadmin' => 'Registrácia schválená administrátorom (zakázaný potvrdzovací kód)'
+);
+
+?>
diff --git a/lang/sl.php b/lang/sl.php
new file mode 100644
index 0000000..fa90880
--- /dev/null
+++ b/lang/sl.php
@@ -0,0 +1,424 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => 'Uredi uporabnika',
+'username' => 'Uporabniško ime',
+'realname' => 'Naziv (ime, priimek)',
+'emailaddress' => 'E-pošta',
+'profileimage' => 'Slika profila',
+'notifytype' => 'NaÄin obveÅ¡Äanja',
+'updatedetails' => 'Posodobite podrobnosti',
+'newuser' => 'Registracija novega uporabnika',
+'yes' => 'Da',
+'no' => 'Ne',
+'addcomments' => 'Dodaj komentar',
+'attachfiles' => 'Pripni datoteke',
+'vote' => 'oddan glas',
+'back' => 'Nazaj',
+'show' => 'Prikaži',
+'update' => 'Posodobi',
+'addnew' => 'Dodaj novo',
+'flysprayprefs' => 'Flyspray nastavitve',
+'language' => 'Jezik',
+'never' => 'Nikoli',
+'email' => 'E-pošta',
+'noone' => 'Vsem',
+'editcomment' => 'Uredi komentar',
+'commentby' => 'Komentiral',
+'saveeditedcomment' => 'Shrani urejan komentar',
+'dateformat' => 'Format datuma',
+'dateformat_extended' => 'Podroben format datuma',
+'oldpass' => 'Staro geslo',
+'changepass' => 'Spremenite geslo',
+'confirmpass' => 'Potrdite geslo',
+'viewtasks' => 'Pregled poroÄil',
+'editcomments' => 'Uredi komentarje',
+'closeowntasks' => 'Zapri lastno poroÄilo',
+'closeothertasks' => 'Zapri poroÄilo katere niste lastnik',
+'viewreports' => 'Oglej poroÄil',
+'globalgroup' => 'Pravice portala',
+'closetask' => 'Zapri poroÄilo',
+'lostpw' => 'Pridobitev izgubljenega gesla',
+'lostpwexplain' => 'VpiÅ¡ite vaÅ¡e uporabniÅ¡ko ime in poslali vam bomo povezavo za spremembo obstojeÄega gesla. E-poÅ¡ta bo poslana na naslov kot je naveden v vaÅ¡em uporabniÅ¡kem profilu.',
+'sendlink' => 'Pošlji povezavo',
+'savenewpass' => 'Shrani novo geslo',
+'notifications' => 'ObveÅ¡Äanje',
+'resetoptions' => 'Ponastavi nastavitve',
+'operatingsystems' => 'Operacijski sistemi',
+'delete' => 'Izbriši',
+'link' => 'Povezava',
+'tasksperpage' => 'Å tevilo poroÄil na eno stran',
+'loginsuccessful' => 'Prijava uspešna.',
+'task' => 'PoroÄilo',
+'edittask' => 'Uredi poroÄilo',
+'openedby' => 'PoroÄilo kreiral',
+'editedby' => 'Zadnje urejanje',
+'tasktype' => 'Tip sporoÄila',
+'category' => 'Kategorija',
+'status' => 'Status',
+'assignedto' => 'Dodeljeno',
+'operatingsystem' => 'Operacijski sistem',
+'severity' => 'Pogostost',
+'reportedversion' => 'Verzija programa',
+'dueinversion' => 'Do verzije',
+'undecided' => 'NedoloÄeno',
+'percentcomplete' => 'KonÄano',
+'details' => 'Podrobnosti',
+'savedetails' => 'Shrani spremembe',
+'canceledit' => 'PrekliÄi urejanje',
+'anonymous' => 'Anonimni uporabnik',
+'complete' => 'konÄano',
+'closedby' => 'Zaprl uporabnik',
+'reasonforclosing' => 'Razlog:',
+'reopenthistask' => 'Ponovno odpri poroÄilo',
+'comments' => 'Komentar',
+'attachments' => 'Priponka',
+'relatedtasks' => 'Sorodna poroÄila',
+'edit' => 'Uredi',
+'addcomment' => 'Dodaj komentar',
+'fileuploadedby' => 'Datoteko naložil',
+'uploadafile' => 'Priponka',
+'addalink' => 'Dodaj povezavo',
+'addanotherlink' => 'Dodaj novo povezave',
+'uploadnow' => 'Naloži zdaj!',
+'remove' => 'Odstrani',
+'addnewrelated' => 'Dodaj novo odvisno poroÄilo',
+'add' => 'Dodaj',
+'addusertolist' => 'Dodaj uporabnika k seznamu',
+'theseusersnotify' => 'Ti uporabniki bodo prejemali podrobna obvestila o vsakrÅ¡nih spremembah sporoÄila.',
+'attachedtoproject' => 'Dodano k projektu',
+'reminders' => 'Opomniki',
+'system' => 'Sistem',
+'remindthisuser' => 'Opomni uporabnika',
+'thisoften' => 'Obvesti na vsakih',
+'hour' => 'ura',
+'hours' => 'ura (ure)',
+'day' => 'dan',
+'days' => 'dan (dni)',
+'week' => 'teden',
+'weeks' => 'teden (tednov)',
+'addreminder' => 'Dodaj opomnik',
+'defaultreminder' => 'To je opomnik za poroÄilo:',
+'message' => 'SporoÄilo',
+'closed' => 'Zaprto',
+'filename' => 'Ime datoteke:',
+'date' => 'Datum',
+'closurecomment' => 'Dodatni komentar:',
+'history' => 'Zgodovina',
+'nohistory' => 'Ni zgodovine.',
+'eventdate' => 'Datum',
+'user' => 'Uporabnik',
+'event' => 'Dogodek',
+'fieldchanged' => 'Sprememba polja',
+'taskopened' => 'Kreirano poroÄilo',
+'taskreopened' => 'Ponovno kreiranje poroÄila',
+'taskclosed' => 'PoroÄilo zaprto',
+'commentadded' => 'Dodan komentar',
+'commentedited' => 'Urejanje komentarja',
+'commentdeleted' => 'Komentar izbrisan',
+'attachmentadded' => 'Dodana priponka',
+'attachmentdeleted' => 'Priponka izbrisana',
+'notificationadded' => 'Uporabnik dodan na seznam obveÅ¡Äanja',
+'notificationdeleted' => 'Uporabnik odstranil iz seznama obveÅ¡Äanja',
+'taskassigned' => 'PoroÄilo dodeljeno',
+'taskreassigned' => 'PoroÄilo dodeljeno uporabniku',
+'summary' => 'Povzetek',
+'priority' => 'Prednost',
+'selectareason' => 'Izberite razlog',
+'assigntome' => 'Dodeli meni',
+'ownershiptaken' => 'PoroÄilo prevzel uporabnik',
+'depadded' => 'Dodana odvisnost',
+'depaddedother' => 'To poroÄilo je dodano v odvisnost',
+'makeprivate' => 'naredi zasebno',
+'makepublic' => 'naredi javno',
+'mark100' => 'OznaÄi poroÄilo kot 100% konÄano',
+'watchtask' => 'priÄni slediti',
+'stopwatching' => 'prenehaj slediti',
+'commentlink' => 'Povezava k komentarju',
+'reasonforreq' => 'Razlog zahtevka',
+'previoustask' => 'PrejÅ¡nje poroÄilo',
+'nexttask' => 'Naslednje poroÄilo',
+'duedate' => 'Do datuma',
+'open' => 'Odprta',
+'depgraph' => 'Ogled grafa odvisnosti',
+'reset' => 'Ponastavi',
+'selectusers' => 'Izberi uporabnika...',
+'dependencygraph' => 'Odvisnostni graf',
+'attachanotherfile' => 'Dodaj novo priponko',
+'OK' => 'Vredu',
+'addvote' => 'Dodaj glas',
+'notifyfromfs' => 'SporoÄilo od Flyspray',
+'autogenerated' => 'TO JE SAMODEJNO SPOROÄŒILO, PROSIMO NE ODGOVARJAJTE NANJ',
+'forward' => 'Naprej',
+'previous' => 'Prešnja',
+'next' => 'Naslednja',
+'first' => 'Prva',
+'last' => 'Zadnja',
+'page' => 'Stran %d od %d',
+'search' => 'Iskanje',
+'alltasktypes' => 'Vsi tipi poroÄil',
+'allseverities' => 'Vsaka pogostost',
+'alldevelopers' => 'Vsi razvijalci',
+'notyetassigned' => 'Å e ni dodeljeno',
+'allcategories' => 'Vse kategorije',
+'allstatuses' => 'Vsi statusi',
+'allopentasks' => 'Vsa odprta poroÄila',
+'sortthiscolumn' => 'Uredite po tem stolpcu',
+'id' => 'ID',
+'project' => 'Projekt',
+'dateopened' => 'Odprto',
+'progress' => 'Napredovanje',
+'searchthisproject' => 'Iskanje po tem projektu',
+'dueanyversion' => 'Do vsake verzije',
+'anyversion' => 'PoroÄano v vsaki verziji',
+'dueversion' => 'Do verzije',
+'os' => 'Operacijski sistem',
+'taskrange' => 'Prikazovanje poroÄila %d - %d od %d',
+'takeaction' => 'Izvedi dejanje',
+'watchtasks' => 'Spremljal izbrano poroÄilo',
+'stopwatchingtasks' => 'Prenehaj opazovati izbrano poroÄilo',
+'assigntaskstome' => 'Dodeli izbrana poroÄila meni',
+'selectduedate' => 'Izberite do datuma',
+'assignedtome' => 'Dodeljeno meni',
+'tasklist' => 'Seznam poroÄil',
+'dateclosed' => 'Datum zaprtja',
+'advanced' => 'Napredno',
+'searchcomments' => 'Iskanje po komentarjih',
+'searchforall' => 'Iskanje po vseh besedah',
+'anonusers' => 'Anonimni uporabniki',
+'miscellaneous' => 'Razno',
+'users' => 'Uporabniki',
+'taskproperties' => 'Podrobnosti poroÄila',
+'currentversion' => 'Tvoja trenutna verzija je',
+'hidemessage' => '(opomni me kasneje)',
+'saveas' => 'Shrani iskanje kot',
+'nosearches' => 'Ni shranjenih iskanj',
+'saving' => 'Shranjevanje...',
+'votes' => 'oddani glasovi',
+'tovote' => 'Oddaj glas',
+'allclosedtasks' => 'Vsa zaprta poroÄila',
+'password' => 'Geslo',
+'login' => 'PRIJAVA',
+'rememberme' => 'Zapomni si me',
+'lostpassword' => 'Izgubljeno geslo?',
+'lostpwforfs' => 'Izgubljeno geslo za Flyspray',
+'lostpwmsg1' => "Živjo.\n\nIzgubil sem geslo za Flyspray na ",
+'locale' => 'sl-SI',
+'showtask' => 'Prikaži poroÄilo',
+'now' => 'Sedaj',
+'go' => 'Pojdi!',
+'opentaskanon' => 'Dodaj novo poroÄilo (anonimno)',
+'register' => 'Registracija',
+'addnewtask' => 'Dodaj novo poroÄilo',
+'reports' => 'Pregled poroÄil',
+'editmydetails' => 'Uredi moje podrobnosti',
+'logout' => 'ODJAVA',
+'poweredby' => 'Flyspray',
+'sponsoredby' => 'Flyspray sponzorira',
+'projects' => 'Projekti',
+'allprojects' => 'Vsi projekti',
+'tasksall' => 'Vsa poroÄila',
+'taskswatched' => 'Opazovana poroÄila',
+'mysearch' => 'Moja iskanja',
+'manageproject' => 'Upravljanje projekta',
+'permissions' => 'Ogled pravic',
+'hide' => 'Skrij',
+'errorpage' => "Flyspray cannot provide the page you requested.\nPerhaps you requested a task that does not exist, or you\ndo not have permission to view the page you wanted.<br /><br />\nYou may have tried to use a naughty URL to interact with the database\nbackend using SQL injection. If this is true, go to the corner and think\nabout your actions. When you return, please do not do it again!",
+'permissionsforproject' => 'Dodeljene pravice za ',
+'lastsearch' => 'Zadnje iskanje',
+'modify' => 'Spremenite',
+'noticefrom' => 'SporoÄilo od',
+'summaryrequired' => 'Vpisati morate jedro poroÄanega poroÄila.',
+'goback' => 'Nazaj',
+'taskupdated' => 'PoroÄilo je bilo posodobljeno.',
+'tasksupdated' => 'PoroÄila so bila posodobljena.',
+'unassigned' => 'NedoloÄeno',
+'taskclosedmsg' => 'PoroÄilo je bilo zaprto.',
+'noclosereason' => 'Za zaprtje poroÄila je potrebno izbrati razlog.',
+'hasreopened' => 'je ponovno odprl že zaprto Flyspray poroÄilo, katero ste zaprli vi.',
+'taskreopenedmsg' => 'Opravilo je bilo ponovno odprto.',
+'backtotask' => 'Nazaj na poroÄilo.',
+'commentaddedmsg' => 'Komentar uspešno dodan.',
+'closewindow' => 'Lahko zaprete to okno.',
+'usernametakenbulk' => 'Uporabniško ime že zasedeno',
+'newuserwarning' => 'V vednost, da je možno da mora pred prvo prijavo skrbnik potrdi vaÅ¡ raÄun. ÄŒe se ne morete prijaviti je lahko to vzrok.',
+'optionssaved' => 'Flyspray nastavitve shranjene.',
+'loginbelow' => 'Sedaj se lahko prijavite (desno zgoraj -> PRIJAVA).',
+'flyspraytask' => 'Flyspray poroÄilo',
+'addanyway' => 'Vseeno dodaj',
+'cancel' => 'PrekliÄi',
+'changefspass' => 'Spremenite Flyspray geslo',
+'addressused' => 'VaÅ¡ naslov je bil uporabljen za registracijo na Flyspray portalu. ÄŒe niste priÄakovali tega sporoÄila ga prosimo spreglejte in izbriÅ¡ite. Za registracijo sledite spodnji povezavi:',
+'confirmcodeis' => 'Vaša potrditvena koda:',
+'addthistask' => 'Oddaj poroÄilo',
+'notifyme' => 'Obvestite me o spremembi',
+'newtask' => 'Novo poroÄilo',
+'attachafile' => 'Priponka',
+'registernewuser' => 'Registracija novega uporabnika',
+'none' => 'Brez',
+'registeraccount' => 'Registracija raÄuna',
+'disclaimer' => 'To sporoÄilo ste prejeli ker je bila oddana zahteva na Flyspray portalu. ÄŒe niste priÄakovali tega sporoÄila ali Äe ne želite prejemati podobnih sporoÄil v bodoÄe lahko spremenite naÄin obveÅ¡Äanja v zgoraj navedeni povezavi.',
+'userwho' => 'PoroÄilo kreiral',
+'moreinfo' => 'VeÄ informacij na spletni povezavi:',
+'newtaskopened' => 'Odprto je novo poroÄilo na Flyspray portalu.',
+'fileaddedtoo' => 'PoroÄilo vsebuje eno ali veÄ priponk.',
+'feeds' => 'PoroÄanje',
+'priority6' => 'KritiÄno',
+'priority5' => 'Takoj',
+'priority4' => 'Urgentno',
+'priority3' => 'Visoka',
+'priority2' => 'Normalno',
+'priority1' => 'Nizka',
+'sendcode' => 'Registracija uporabnika',
+'entercode' => 'VpiÅ¡ite potrditveno kodo, katero ste prejeli v sporoÄilu. Prav tako vpiÅ¡ite željeno uporabniÅ¡ko geslo.',
+'note' => '<strong>Opomba:</strong> Pred kreiranjem novega raÄuna vam bo na e-poÅ¡to poslana aktivacijska povezava. Aktivacijska povezava bo poslana na zgoraj vpisan e-poÅ¡tni naslov.<br />ÄŒe so vpisani neresniÄni podatki <strong>ne boste prejeli aktivacijske povezave</strong>.',
+'to' => 'do',
+'events' => 'PoroÄila',
+'opened' => 'Odprto',
+'edited' => 'Urejano',
+'assigned' => 'Dodeljeno',
+'pastweek' => 'Pretekli teden',
+'from' => 'Od',
+'roadmap' => 'Razvojni naÄrt',
+'opentasks' => 'odprtih poroÄil',
+'severity5' => 'Vedno',
+'severity4' => 'Skoraj vedno',
+'severity3' => 'Pogosto',
+'severity2' => 'VÄasih',
+'severity1' => 'Skoraj nikoli',
+'reopened' => 'Ponovno odprto',
+'loading' => 'Nalaganje...',
+'notifyown' => 'Obvesti o lastnih spremembah',
+'youremail' => 'Vaš e-poštni naslov ',
+'file' => 'Datoteka',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => 'Velikost',
+'projectgroup' => 'Pravice projekta',
+'profile' => 'Profil',
+'viewprofile' => 'Ogled profila',
+'regdate' => 'Uporabnik od',
+'tasksopened' => 'Kreiral poroÄila',
+'pm.taskreopened' => 'Opravilo ponovno odprto',
+'anyprogress' => 'Vsi napredki',
+'tasksrelated' => 'PoroÄilo sorodno temu poroÄilu',
+'duplicatetasks' => 'Podvojite poroÄilo tega poroÄila',
+'newuserregistered' => 'Na Flyspray se je registriral nov uporabnik. Podatki so sledeÄi:',
+'onlyprimary' => 'PoroÄila ki ne zavirajo drugih poroÄil',
+'onlyblocker' => 'PoroÄila, ki onemogoÄajo druga poroÄila',
+'switch' => 'Prikaži',
+'max' => 'najveÄ',
+'dates' => 'Datumi',
+'selectduedatefrom' => 'Od',
+'selectduedateto' => 'do',
+'selectsincedatefrom' => 'Spremenjeno od',
+'selectsincedateto' => 'do',
+'selectopenedfrom' => 'Odprto od',
+'selectopenedto' => 'do',
+'selectclosedfrom' => 'Zaprto od',
+'selectclosedto' => 'do',
+'startat' => 'ZaÄni',
+'hasattachment' => 'Ima priponko',
+'private' => 'Zasebno',
+'watching' => 'Opazujem',
+'alreadyvotedthistask' => 'glasovali ste za to poroÄilo',
+'alreadyvotedthisday' => 'danes ste že glasovali',
+'visibility' => 'Vidljivost',
+'public' => 'Javno',
+'defaulttask' => 'Privzeti opis poroÄila',
+'all' => 'vsi',
+'eventlog' => 'Pregled poroÄil',
+'All' => 'Vsi',
+'tasksireported' => 'PoroÄila ki sem jih poroÄal',
+'recentlyopened' => 'Nedavno odprta poroÄila',
+'stats' => 'Statistika',
+'totaltasks' => 'vseh poroÄil',
+'mostwanted' => 'Najbolj zaželjena poroÄila',
+'overview' => 'Pregled',
+'error#' => 'Napaka #',
+'error7' => 'Napaka pri prijavi, nepravilno geslo!',
+'done' => 'konÄano',
+'timezone' => 'ÄŒasovni pas',
+'accept' => 'Sprejmi',
+'pruneclosedlinks' => 'Zaprte odvisnosti povezave',
+'pruneclosedtasks' => 'Zaprte odvisnosti poroÄil',
+'pruninglevel' => 'Stopnja odvisnosti',
+'request1' => 'Zahtevano je zaprtje poroÄila.',
+'allpriorities' => 'Vse prioritete',
+'noroadmap' => 'Trenutno ni javno objavljenega razvojnega naÄrta.',
+'minpwsize' => 'Najmanjša dolžina gesla je 5 znakov',
+'searchindetails' => 'Podrobnosti iskanja',
+'find' => 'Najdi',
+'addvotes' => 'Dodaj glas',
+'removevote' => 'Odstrani glas',
+'novotes' => 'Ni veljavnih glasov.',
+'taskdependencies' => 'Odvisnost poroÄila',
+'viewgraph' => 'ogled grafa',
+'notaskdependencies' => 'To poroÄilo nima povezave z nobenim predhodnim poroÄilom.',
+'dependson' => 'Odvisen od',
+'newdependency' => 'Nova odvisnost:',
+'setparent' => 'Nastavite ID matiÄnega poroÄila za to poroÄilo',
+'quickaction' => 'Hitra dejanja',
+'associatesubtask' => 'Nastavitev ID podporoÄila za to poroÄilo',
+'addnewsubtask' => 'Dodajte novo podporoÄilo',
+'hidesubtasks' => 'Skrij podporoÄila',
+'voteforthistask' => 'Glasujte za to poroÄilo',
+'watchthistask' => 'Opazujte to poroÄilo',
+'privatethistask' => 'Nastavite poroÄilo kot zasebno',
+'adddependenttask' => 'Povežite soodvisno poroÄilo',
+'incorrectfiletype' => 'Nepravilen tip datoteke. Dovoljeno: jpg, jpeg, gif, png.',
+'addmultipletasks' => 'Dodaj množiÄna poroÄila',
+'clicktoedit' => 'Za hitro urejanje kliknite na polje',
+'confirmedit' => 'Potrdi',
+'activity' => 'Dejavnost',
+'myactivity' => 'Moja dejavnost',
+'verifyemailaddress' => 'Potrdite e-poštni naslov',
+'hidemyemail' => 'Skrij moj e-poštni naslov',
+'exporttasklist' => 'Izvozi seznam poroÄil',
+'hourplural' => 'ure',
+'hoursingular' => 'ura',
+'hourabbrev' => 'h',
+'taskdescription' => 'Opis poroÄila',
+'notaskdescription' => 'Ni opisa',
+'closeselectedtasks' => 'Zapri izbrana poroÄila',
+'closetasks' => 'Zapri poroÄila',
+'hintforbulkimport' => "<b>Navodila za množiÄno poroÄanje:</b>\n<ol>\n<li>Kopiranje in lepljenje iz Excel ali CSV datoteke.</li>\n<li>Prilepite lahko Povzetek in Podrobnosti.</li>\n</ol>",
+'applyfirstline' => 'Shrani prvo vrstico',
+'addmorerows' => 'Dodaj novo vrstico',
+'addtasks' => 'Dodaj poroÄila',
+'myassignedtasks' => 'PoroÄila dodeljena meni',
+'commentedon' => 'komentiral dne',
+'myvotes' => 'Moje glasovanje',
+'tags' => 'Oznake',
+'youhaveregistered' => 'Registrirali ste se na Flyspray. Vaši podatki so:',
+'youhaveregisterednotify' => 'Vaša registracija na Flyspray je bila sprejeta.',
+'keyboardshortcuts' => 'Bližnjice na tipkovnici',
+'availablekeybshortcuts' => 'Na voljo bližnjice na tipkovnici',
+'logindialoglogout' => 'Prijava / Odjava',
+'focustaskidsearch' => 'Iskanje po ID oznaki',
+'openselectedtask' => 'Odpri izbrano poroÄilo',
+'movecursorup' => 'Premakne kurzor gor',
+'movecursordown' => 'Premakne kurzor dol',
+'taskdetails' => 'Podrobnosti opravila',
+'taskediting' => 'Urejanje opravila',
+'savetask' => 'Shrani opravilo',
+'taskhassubtask' => 'To opravilo ima sledeÄe podopravilo',
+'taskhassubtasks' => 'To opravilo ima sledeÄa podopravila',
+'taskissubtaskof' => 'To opravilo je podopravilo od opravila',
+'supertaskadded' => 'Dodano glavno opravilo',
+'supertaskremoved' => 'Odstranjeno glavno opravilo',
+'subtaskadded' => 'Dodano podopravilo',
+'subtaskremoved' => 'Odstranjeno podopravilo',
+);
+
+?>
diff --git a/lang/sr.php b/lang/sr.php
new file mode 100644
index 0000000..4b9c960
--- /dev/null
+++ b/lang/sr.php
@@ -0,0 +1,830 @@
+<?php
+
+$translation = array(
+'edituser' =>'Уреди кориÑника',
+'username' =>'КориÑничко име',
+'realname' =>'Стварно име',
+'emailaddress' =>'Емајл адреÑа',
+'jabberid' =>'Jabber адреÑа',
+'notifytype' =>'Тип обавештења',
+'group' =>'Група',
+'accountenabled' =>'Пријава дозвољена',
+'updatedetails' =>'Ðжурирај детаље',
+'setglobally' =>'Ова повлаÑтица поÑтављена је глобално.',
+'usergroupmanage' =>'Управљање кориÑницима и групама',
+'newuser' =>'РегиÑтровање новог кориÑника',
+'newgroup' =>'Креирање нове групе',
+'yes' =>'Да',
+'no' =>'Ðе',
+'editgroup' =>'Уреди групу',
+'groupname' =>'Ðазив групе',
+'description' =>'ОпиÑ',
+'admin' =>'Ðдмин група',
+'opennewtasks' =>'Креирај нови задатак',
+'modifytasks' =>'Измени поÑтојећи задатак',
+'addcomments' =>'Додај коментар',
+'attachfiles' =>'Придружи фајл',
+'vote' =>'ГлаÑај',
+'groupenabled' =>'Чланови Ñе могу пријављивати',
+'groupopen' =>'Чланови Ñе могу пријављивати',
+'tasktypelist' =>'ЛиÑта типова задатака',
+'categorylist' =>'ЛиÑта категорија',
+'oslist' =>'ЛиÑта оперативних ÑиÑтема',
+'resolutionlist' =>'РеÑÐ¾Ð»ÑƒÑ‚Ð¸Ð¾Ð½Ñ Ð»Ð¸ÑÑ‚',
+'versionlist' =>'ЛиÑта верзија',
+'severitylist' =>'ЛиÑта важноÑти',
+'listnote' =>'Ðапомена: Дечекирање колоне "Приказ" може изменити неке задатке када Ñу у режиму измене. Промена поља у колони "Име" ће Ñе одноÑити на Ñве задатке, где Ñе помиње промена. Слогови који Ñе не могу обриÑати заштићени Ñу јер Ñу потребни за иÑправно функциониÑање задатака.',
+'name' =>'Име',
+'order' =>'РедоÑлед',
+'back' =>'Ðазад',
+'text' =>'ТекÑÑ‚',
+'highlight' =>'ÐаглаÑи',
+'show' =>'Приказ',
+'owner' =>'ВлаÑник',
+'selectowner' =>'Избор влаÑника',
+'update' =>'Ðжурирај',
+'addnew' =>'Додај нови',
+'flysprayprefs' =>'Flyspray повлаÑтице',
+'projecttitle' =>'ÐаÑлов пројекта',
+'baseurl' =>'Базни URL за ову инÑталацију',
+'replyaddress' =>'Обавештења реплицирај на емајл адреÑу',
+'themestyle' =>'Тема / Ñтил',
+'language' =>'Језик',
+'anonview' =>'Дозволи непријављеним кориÑницима да гледају задатке',
+'allowanon' =>'Дозволи непријављеним кориÑницима да креирају задатке',
+'never' =>'Ðикад',
+'anonymously' =>'Ðнонимно',
+'afterregister' =>'Само након региÑтровања',
+'spamproof' =>'Омогући потврдни коод за региÑтрацију нових кориÑника',
+'anongroup' =>'Група за ново-региÑтрованог кориÑника',
+'groupassigned' =>'Чланови ове групе могу додељивати задатке',
+'forcenotify' =>'Ðаложи обавештења о задацима',
+'neversend' =>'да Ñе никад не шаљу',
+'userchoose' =>'да Ñваки кориÑник може изабрати',
+'email' =>'Емајл',
+'jabber' =>'Jabber адреÑа',
+'defaultcatowner' =>'Подразумевани влаÑник категорије',
+'noone' =>'Ðиједан',
+'jabbernotify' =>'Jabber обавештења',
+'jabberserver' =>'Сервер',
+'jabberport' =>'Порт',
+'jabberuser' =>'КориÑничко име',
+'jabberpass' =>'КориÑничка лозинка',
+'saveoptions' =>'Сачувај',
+'editcomment' =>'Уреди коментар',
+'commentby' =>'КоментариÑао',
+'saveeditedcomment' =>'Сними измењени коментар',
+'projectprefs' =>'МогућноÑти над пројектима',
+'pagetitle' =>'ÐаÑлов Ñтране',
+'defaultproject' =>'Подразумевани пројекат',
+'projectlists' =>'ЛиÑта пројеката',
+'showlogo' =>'Прикажи наÑлов лого-а',
+'intromessage' =>'Уводна порука',
+'isactive' =>'Пројекат је активан',
+'createproject' =>'Креирај нови пројекат',
+'nopermission' =>'Ðемате дозволу да кориÑтите ову Ñтрану!',
+'listordertip' =>'РедоÑлед ће бити приказан у лиÑти',
+'listshowtip' =>'Прикажи ову Ñтавку у лиÑти',
+'categoryownertip' =>'Ово лице ће примати обавештења када Ñе отвори задатак у овој категорији',
+'categoryparenttip' =>'Тhe parent category this new one will fall under',
+'notsubcategory' =>'Ðиједна (врховна категорија!)',
+'showinlineimages' =>'Прикажи Ñлику прикључка у линији',
+'dateformat' =>'Формат датума (%d, %m, %y, или %Y)',
+'dateformat_extended' =>'Детаљни формат датума',
+'cache_feeds' =>'Кеширање података',
+'no_cache' =>'Ðема кеширања',
+'cache_disk' =>'Кеширање на диÑку',
+'cache_db' =>'Кеширање у ДБ',
+'subcategoryof' =>'Подкатегорија категорије',
+'visiblecolumns' =>'Колоне заглавља приказане у таÑк лиÑти',
+'tense' =>'Верзија',
+'listtensetip' =>'Прошла, Ñадашња, или будућа',
+'past' =>'Прошла',
+'present' =>'Садашња',
+'future' =>'Будућа',
+'oldpass' =>'Стара лозинка',
+'nooldpass' =>'Стара лозинка није подешена',
+'oldpasswrong' =>'Стара лозинка је погрешна',
+'changepass' =>'Ðова лозинка',
+'confirmpass' =>'Потврди нову лозинку',
+'projectmanager' =>'Руководилац пројеката',
+'viewtasks' =>'Увид задатака',
+'modifyowntasks' =>'Измена ÑопÑтвених задатака',
+'modifyalltasks' =>'Измена задатака који ниÑу у влаÑништву',
+'viewcomments' =>'Увид коментара',
+'editcomments' =>'Измена коментара',
+'deletecomments' =>'БриÑање коментара',
+'viewattachments' =>'Увид прикључка',
+'createattachments' =>'Ðаправи прикључење',
+'deleteattachments' =>'Обриши прикључке',
+'viewhistory' =>'Увид промена',
+'closeowntasks' =>'Затвори ÑопÑтвене задатке',
+'closeothertasks' =>'Затвори задатке који ниÑу у влаÑништву',
+'assigntoself' =>'Додели задатак Ñеби, ако није већ додељен',
+'assignotherstoself' =>'Додели друге задатке Ñеби',
+'viewreports' =>'Увид дневника догађаја',
+'othersview' =>'Дозволи Ñваком да гледа овај пројекат',
+'usersandgroups' =>'КориÑници и групе',
+'globalgroup' =>'Општа група',
+'globalgroups' =>'Опште групе',
+'defaultglobalgroup' =>'Подразумевана општа група за нове кориÑнике',
+'addtogroup' =>'Додај у групу',
+'moveuserstogroup' =>'ПремеÑти кориÑника у групу',
+'nogroup' =>'Ðема група - Избачене из пројекта',
+'eventdesc' =>'ÐžÐ¿Ð¸Ñ Ð´Ð¾Ð³Ð°Ñ’Ð°Ñ˜Ð°',
+'requestedby' =>'Тражен од',
+'daterequested' =>'Датум захтева',
+'closetask' =>'Затвор задатак',
+'reopentask' =>'Поново отовори задатак',
+'applymember' =>'Примени за чланове пројекта',
+'forcurrentproj' =>'За текући пројекат',
+'lostpw' =>'Проналажење изгубљене лозинке',
+'lostpwexplain' =>'УнеÑите Ваше кориÑничко име за Ñлање линка за промену лозинке. Ово ће бити Ñачувано у Ваш профил обавештења.',
+'sendlink' =>'Пошаљи линк',
+'savenewpass' =>'Сачувај нову лозинку',
+'anonreg' =>'Дозволи региÑтрацију нових кориÑника',
+'allowanonopentask' =>'Дозволи непријављеним кориÑницима да отварају задатке',
+'editglobalgroup' =>'Измени општу групу',
+'editgroupforproj' =>'Измени пројектну групу',
+'notshownforadmin' =>'Дозволе ниÑу приказане за админиÑтраторÑку групу. Ðепотребно их је мењати.',
+'general' =>'Опште',
+'userregistration' =>'КориÑничка региÑтрација',
+'notifications' =>'Обавештења',
+'resetoptions' =>'Поништи опције',
+'preferences' =>'Општа подешавања',
+'tasktypes' =>'Типови задатака',
+'resolutions' =>'Разрешења',
+'categories' =>'Категорије',
+'operatingsystems' =>'Оперативни ÑиÑтеми',
+'versions' =>'Верзије',
+'admintoolboxlong' =>'ÐдминиÑтраторÑке алатке',
+'newproject' =>'Ðови пројекат',
+'delete' =>'Обриши',
+'listdeletetip' =>'Обриши ову Ñтавку из лиÑте',
+'lookandfeel' =>'Изглед и оÑећај',
+'globaltheme' =>'Општа тема',
+'emailnotify' =>'Емаjл обавештења',
+'fromaddress' =>'Шаље Ñа адреÑе',
+'smtpserver' =>'SMTP Ñервер',
+'smtpuser' =>'SMTP кориÑничко име',
+'smtppass' =>'SMTP лозинка',
+'addrewrite' =>'КориÑти замену адреÑе',
+'usereminderdaemon' =>'Омогући позадинÑку уÑлугу (deamon) за подÑетник',
+'tasksperpage' =>'Задатака по Ñтрани лиÑте задатака',
+'addtoassignees' =>'Додај Ñе Ñам лиÑти додељених',
+'taskstatuses' =>'СтатуÑи задатака',
+'canvote' =>'Може глаÑати за задатак',
+'loginsuccessful' =>'Пријава уÑпешна.',
+'youareloggedout' =>'Ви Ñте Ñе одјавили.',
+'waitwhiletransfer' =>'Молим Ñачекајте преноÑ...',
+'clicknowait' =>'Кликни овде, ако не желиш да чекаш.',
+'accountdisabled' =>'Ваш налог је иÑкључен. Контактирајте админиÑтратора ÑиÑтема.',
+'task' =>'Задатак',
+'edittask' =>'Измени овај задатак',
+'openedby' =>'Отворен од',
+'editedby' =>'ПоÑледњи мењао',
+'tasktype' =>'Тип задатка',
+'category' =>'Категорија',
+'status' =>'СтатуÑ',
+'assignedto' =>'Додељен',
+'operatingsystem' =>'Оперативни ÑиÑтем',
+'severity' =>'ВажноÑÑ‚',
+'reportedversion' =>'Пријављена верзија',
+'dueinversion' =>'Очекује Ñе у верзији',
+'undecided' =>'Ðеодређено',
+'percentcomplete' =>'% извршења',
+'details' =>'Детаљи',
+'savedetails' =>'Сачувај детаље',
+'canceledit' =>'Откажи измену',
+'anonymous' =>'Ðепознати пошиљалац',
+'complete' =>'завршено',
+'closedby' =>'Затворено од Ñтране',
+'reasonforclosing' =>'Разлог затварања:',
+'reopenthistask' =>'Поново отовори овај задатак',
+'comments' =>'Коментари',
+'attachments' =>'Прикључци',
+'relatedtasks' =>'Повезани задаци',
+'edit' =>'Измени',
+'addcomment' =>'Додај коментар',
+'fileuploadedby' =>'Датотека поÑлата од Ñтране ',
+'uploadafile' =>'Прикључи датотеку',
+'uploadnow' =>'Пошаљи Ñада!',
+'thesearerelated' =>'Ови задаци Ñу у вези Ñа овим задатком',
+'remove' =>'Избаци',
+'addnewrelated' =>'Додај нови задатак који је Ñ Ð¾Ð²Ð¸Ð¼ у вези',
+'add' =>'Додај!',
+'otherrelated' =>'Други задаци Ñ ÐºÐ¾Ñ˜Ð¸Ð¼Ð° је овај у вези',
+'receivenotify' =>'Ови кориÑници ће примати детаљна обавештења када Ñе задатак измени.',
+'addusertolist' =>'Додај кориÑника овој лиÑти',
+'addtolist' =>'Додај у лиÑту',
+'addmyself' =>'Сам Ñе додај овој лиÑти',
+'removemyself' =>'Избаци Ñе Ñам из ове лиÑте',
+'theseusersnotify' =>'Ови кориÑници ће примати детаљна обавештења увек када Ñе овај задатак измени.',
+'attachedtoproject' =>'Прикључен пројекту',
+'reminders' =>'ПодÑетник',
+'system' =>'СиÑтем',
+'remindthisuser' =>'ПодÑети овог кориÑника',
+'thisoften' =>'То Ñе чеÑто',
+'startafter' =>'Сачекајте пре покретања подÑетника',
+'hours' =>'Сат(и)',
+'days' =>'Дан(а)',
+'weeks' =>'Ðедеља(е)',
+'addreminder' =>'Додај подÑетник',
+'defaultreminder' =>'Ово је подÑетник који ће пратити Ñеледеће Flyspray задатке:',
+'message' =>'Порука',
+'closed' =>'Затворен',
+'filename' =>'Ðазив датотеке:',
+'date' =>'Датум',
+'filesize' =>'Величина датотеке:',
+'closurecomment' =>'Додатно појашњење о разлогу затварања:',
+'history' =>'ИÑторија',
+'nohistory' =>'ИÑторија није доÑтупна.',
+'eventdate' =>'Датум',
+'user' =>'КориÑник',
+'event' =>'Догађај',
+'fieldchanged' =>'Промењено је',
+'taskopened' =>'Задатак отворен',
+'taskreopened' =>'Задатак поново отворен',
+'taskclosed' =>'Задатак затворен',
+'commentadded' =>'Додат коментар',
+'commentedited' =>'Коментар измењен',
+'commentdeleted' =>'Коментар обриÑан',
+'attachmentadded' =>'Прикључак додат',
+'attachmentdeleted' =>'Прикључак обриÑан',
+'taskedited' =>'Детаљи задатка измењени',
+'notificationadded' =>'КориÑник додат лиÑти обавештења',
+'notificationdeleted' =>'КориÑник избачен из лиÑте обавештења',
+'relatedadded' =>'Повезани задатак додат',
+'relateddeleted' =>'Повезани задатак избачен',
+'taskassigned' =>'Задатак додељен',
+'taskreassigned' =>'Задатак преуÑмерен на',
+'assignmentremoved' =>'Додела избриÑана',
+'summary' =>'Кратак опиÑ',
+'addedasrelated' =>'Задатак додат лиÑти повезаних',
+'deletedasrelated' =>'Задатак избачен из лиÑте повезаних',
+'reminderadded' =>'Додат подÑетник',
+'reminderdeleted' =>'ПодÑетник избачен',
+'priority' =>'Приоритет',
+'previousvalue' =>'Претходна вредноÑÑ‚',
+'newvalue' =>'Ðова вредноÑÑ‚',
+'selectareason' =>'Избор разлога',
+'assigntome' =>'Додели мени',
+'reopenrequest' =>'Захтев за поновно отварање',
+'requestclose' =>'Захтев за затварање',
+'ownershiptaken' =>'КориÑниково влаÑништво',
+'closerequestmade' =>'Затражено затварање задатка',
+'reopenrequestmade' =>'Затражено поновно отварање задатка',
+'taskdependson' =>'Овај задатак завиÑи од:',
+'taskblocks' =>'Овај задатак блокира ове од затварања',
+'depadded' =>'Додата завиÑноÑÑ‚',
+'depaddedother' =>'Овај задатак додаје као завиÑноÑÑ‚',
+'depremoved' =>'ЗавиÑноÑÑ‚ уклоњена',
+'depremovedother' =>'Овај задатак је избачен из лиÑте завиÑних задатака',
+'showdetailserror' =>'Овај задатак не поÑтоји, или немате дозоволу да га погледате.',
+'makeprivate' =>'направи га приватним',
+'makepublic' =>'направи га јавним',
+'taskmadeprivate' =>'Задатак је Ñада приватан',
+'taskmadepublic' =>'ПриватноÑÑ‚ уклоњена! Задатак је Ñада јаван',
+'confirmdeletecomment' =>'Сигурни Ñте да желите обриÑати овај комнтар? %s',
+'confirmdeleteattach' =>'Сигурни Ñте да желите обриÑати овај прикључак?',
+'selectedhistory' =>'Приказ изабраних детаља иÑторије',
+'showallhistory' =>'Прикажи језичак пуне иÑторије поново',
+'hidethis' =>'Сакриј ову зону поново',
+'mark100' =>'Означи задатак 100% завршеним',
+'watchtask' =>'прати промене',
+'stopwatching' =>'зауÑтави праћење',
+'commentlink' =>'Линк ка коментару',
+'submitreq' =>'Пошаљи захтев',
+'reasonforreq' =>'Разлог забране',
+'pmreqdenied' =>'Руководилац пројектима је забранио захтев',
+'taskpendingreq' =>'Руководилац пројеката тренутно нешто ради. Видите на "ИÑторија" језичку шта Ñе догађа.',
+'previoustask' =>'Претходни задатак',
+'nexttask' =>'Ðаредни задатак',
+'duedate' =>'Рок',
+'attachnoperms' =>'ПоÑтоје прикључци за овај коментар, али Ви немате дозволу за њихов увид.',
+'open' =>'Отвори',
+'depgraph' =>'Увид графичког приказа завиÑноÑти',
+'reset' =>'РеÑет',
+'selectusers' =>'Избор кориÑника...',
+'addmetoassignees' =>'Додај ме лиÑти додељених',
+'addedtoassignees' =>'КориÑник додат лиÑти додељених',
+'dependencygraph' =>'Графички приказ завиÑноÑти',
+'attachanotherfile' =>'Прикачи још неку датотеку',
+'OK' =>'У реду',
+'addvote' =>'Дај глаÑ',
+'notifyfromfs' =>'Обавештење од Flyspray ÑиÑтема',
+'autogenerated' =>'ОВО ЈЕ ÐУТОМÐТСКИ ГЕÐЕРИСÐÐРПОРУКÐ! ÐЕ ОДГОВÐРÐЈ!',
+'forward' =>'Ðапред',
+'previous' =>'Претходни',
+'next' =>'Следећи',
+'first' =>'Први',
+'last' =>'ПоÑледњи',
+'page' =>'Страна %d од %d',
+'search' =>'Тражи',
+'alltasktypes' =>'Сви типови задатака',
+'allseverities' =>'Све важноÑти',
+'alldevelopers' =>'Сви програмери',
+'notyetassigned' =>'Ðије ипак додељен',
+'allcategories' =>'Све категорије',
+'allstatuses' =>'Сви ÑтатуÑи',
+'allopentasks' =>'Сви отоворени задаци',
+'sortthiscolumn' =>'Сорт по овој колони',
+'id' =>'Бр.',
+'project' =>'Пројекат',
+'dateopened' =>'Отворен',
+'progress' =>'Ðапредак у раду',
+'searchthisproject' =>'Претражи овај пројекат за',
+'dueanyversion' =>'Очекује Ñе у ма којој верзији',
+'anyversion' =>'Пријављен у ма којој верзији',
+'dueversion' =>'Очекује Ñе у верзији',
+'lastedit' =>'ПоÑедње измењен',
+'os' =>'Оперативни ÑиÑтем',
+'reportedin' =>'Пријављен у',
+'taskrange' =>'Приказ задатака %d - %d од %d',
+'noresults' =>'Ваша претрага није дала резлтате.',
+'takeaction' =>'Примени изабрано',
+'watchtasks' =>'Праћење изабраних задатака',
+'stopwatchingtasks' =>'Прекини праћење изабраних задатака',
+'assigntaskstome' =>'Додели ми изабрани задатак',
+'dueby' =>'ДоÑпева',
+'dueanytime' =>'Било када',
+'selectduedate' =>'Избор датума доÑпећа',
+'toggleselected' =>'Промена избора',
+'due' =>'Рок',
+'assignedtome' =>'Додељен мени',
+'tasklist' =>'ЛиÑта задатака',
+'dateclosed' =>'Датум затварања',
+'advanced' =>'Још уÑлова за претрагу',
+'searchcomments' =>'Тражи у коментарима',
+'searchforall' =>'Тражи за Ñве речи',
+'anonusers' =>'Ðепознати кориÑник',
+'miscellaneous' =>'Разно',
+'users' =>'КориÑници',
+'taskproperties' =>'СвојÑтва задатка',
+'selectsincedate' =>'Изабери измене од',
+'changedsince' =>'Измене од',
+'updatefs' =>'Молимо ажурирајте Ваш Flyspray.',
+'currentversion' =>'Ваша транутна верзија је',
+'latestversion' =>', а поÑледња верзија је',
+'hidemessage' =>'(подÑети ме каÑније)',
+'saveas' =>'Сачувај претрагу као',
+'nosearches' =>'Ðема Ñачуваних претрага',
+'saving' =>'Снимање...',
+'votes' =>'ГлаÑова',
+'allclosedtasks' =>'Сви затворени задаци',
+'password' =>'Лозинка',
+'login' =>'Пријави Ñе',
+'rememberme' =>'ПодÑети ме',
+'lostpassword' =>'Изгубили Ñте лозинку?',
+'lostpwforfs' =>'Изгубили ÑÑ‚e лозинку за Flyspray',
+'lostpwmsg1' =>'Здраво!
+
+Изгубио/ла Ñам моју лозинку за Flyspray на',
+'lostpwmsg2' =>', молим Ð’Ð°Ñ Ð¿Ð¾ÑˆÐ°Ñ™Ð¸Ñ‚Ðµ ми нову лозинку.
+
+КориÑничко име: ',
+'regards' =>'
+
+Поздрав од ',
+'yourusername' =>' Ваше кориÑничко име ',
+'locale' =>'en-AU',
+'filenotexist' =>'Датотека не поÑтоји, или немате доволу да јој приÑтупите.',
+'showtask' =>'Прикажи задатак',
+'now' =>'Сада',
+'go' =>'Иди!',
+'opentaskanon' =>'Отвори нови задатак као непознат',
+'register' =>'РегиÑтруј Ñе',
+'addnewtask' =>'Додај нови задатак',
+'reports' =>'Дневник догађаја',
+'editmydetails' =>'Измени моје детаље',
+'logout' =>'Одјави Ñе',
+'disabledaccount' =>'Ваша пријава је онемогућена!<br />Одмах ћете бити одјављени...',
+'poweredby' =>'Powered by Flyspray',
+'projects' =>'Пројекти',
+'allprojects' =>'Сви пројекти',
+'selectproject' =>'за пројекат:',
+'tasksall' =>'Сви задаци',
+'tasksassigned' =>'Задаци додељени мени',
+'tasksreported' =>'Задаци које Ñам ја пријавио',
+'taskswatched' =>'Пратим задатак',
+'mysearch' =>'Моја претрага',
+'admintoolbox' =>'ÐдминиÑтраторÑке алатке',
+'manageproject' =>'Управљање пројектима',
+'permissions' =>'Види дозволе',
+'hide' =>'Сакриј',
+'pendingreq' =>'Чека Ñе за ПМ захтев',
+'errorpage' =>'Flyspray не може приказати ову Ñтрану.
+Можда Ñте тражили задатак који не поÑтоји, или немате
+дозволу да видите Ñтраницу коју Ñте желели.<br /><br />
+Можда Ñте покушали да кориÑтите погрешан линк у интеракцији Ñа базом података.
+Ðко је то тачно размиÑлите мало о Ñвојим поÑтупцима и молимо Ð’Ð°Ñ Ð´Ð° то не чините поново.',
+'permissionsforproject' =>'Дозволе за ',
+'switchto' =>'Пређи на',
+'lastsearch' =>'ПоÑледња претрага',
+'modify' =>'Измени',
+'noticefrom' =>'Обавештење од',
+'hasopened' =>'имате ОТВОРЕРÐОВИ Flyspray задатак и додељен је Вама:',
+'moreinfonew' =>'Можете наћи Више информација о овој грешки на Flyspray Ñтрани:',
+'newtaskcategory' =>'Ðови Flyspray задатак биће отоворен у овој категорији',
+'categoryowner' =>'Ви добијате ово јер Ñте наведени као влаÑник категорије.',
+'tasksummary' =>'Резиме задатка:',
+'newtaskadded' =>'Ваш нови задатак је додат.',
+'summaryanddetails' =>'Потребно је поунити оба поља, "Кратак опиÑ" и "Детаљи".',
+'goback' =>'Иди назад.',
+'messagefrom' =>'Ова порука је од Flyspray ÑиÑтема за праћење грешака на ',
+'hasjustmodified' =>'Ñте изменили Ñледећи задатак.',
+'changedfields' =>'Измењена поља имају Ð¿Ñ€ÐµÑ„Ð¸ÐºÑ (**)',
+'moreinfomodify' =>'Можете имати више информација о овом задатку Ñледећи овај линк:',
+'nolongerassigned' =>'Следећи задатак више није додељен Вама. Сада је додељен',
+'hasassigned' =>'имате додељен Ñледећи Flyspray задатак:',
+'taskupdated' =>'Задатак је измењен.',
+'hasclosedassigned' =>'је затворен Ñледећи Flyspray задатак који Вам је додељен:',
+'unassigned' =>'Ðедодељен',
+'hasclosed' =>'је затворен Ñледећи задатак.',
+'youonnotify' =>'Ви добијате ово јер Ñте на лиÑти за обавештавање.',
+'taskclosedmsg' =>'Задатак је затворен.',
+'returntotask' =>'Повратак на детаље задатка',
+'backtoindex' =>'Повраћај на лиÑту задатака',
+'noclosereason' =>'ÐиÑте изабрали разлог за затварање тог задатка.',
+'hasreopened' =>'поново је отворен Ñледећи Flyspray задатак који Ñте затворили:',
+'taskreopenedmsg' =>'Задатак је поново отворен.',
+'backtotask' =>'Врати Ñе на задатак.',
+'commentaddedmsg' =>'Коментар је дат.',
+'commenttoassigned' =>'додат је Ñледећи коментар задатку који Вам је додељен:',
+'commenttotask' =>'дат је Ñледећи коментар овом задатку.',
+'nocommententered' =>'Ви заиÑта треба да унеÑете коментар пре наго што кликнете на дугме.',
+'fillinfields' =>'ÐиÑте попунили Ñва поља.',
+'notcurrentpass' =>'То није Ваша тренутна лозинка!',
+'passchanged' =>'Ваша лозинка је промењена.',
+'closewindow' =>'Можете Ñада затворити овај прозор.',
+'passnomatch' =>'Ваша нова лозинка није иÑта',
+'usernametaken' =>'То кориÑничко име је ваћ заузето! Мораћете да унеÑете друго.',
+'newusercreated' =>'Ðови кориÑникчки налог је креиран.',
+'accountcreated' =>'Ваш налог је креиран.',
+'newuserwarning' =>'Имајте у виду да Ваш налог треба да буде одобрен од Ñтране админииÑтратора ÑиÑтема. Ðко не можете да Ñе пријавите то је разлог зашто не можете.',
+'nomatchpass' =>'Лозинка не одговара.',
+'confirmwrong' =>'Потврдни коод је погрешан!',
+'formnotcomplete' =>'Форма није цела попуњена.',
+'formnotnumeric' =>'Унешени податак није нумерички!',
+'groupnametaken' =>'Име групе је већ заузето.',
+'newgroupadded' =>'Ðова група додата.',
+'optionssaved' =>'Flyspray опције Ñачуване',
+'hasuploaded' =>'отпремили Ñте датотеку, као прикључак, задатку који је додељен:',
+'hasattached' =>'прикачена је датотека Ñледећем задатку.',
+'fileuploaded' =>'Датотека је отпремљена.',
+'fileerror' =>'Дошло је до грешке у отпремању Ваше датотеке. Можда Ñу дозволе над <i>прикљученим/</i> директоријумима погрешне.',
+'contactadmin' =>'Контактирајте админиÑтратора овог пројекта.',
+'selectfileerror' =>'ÐиÑте изабрали датотеку.',
+'userupdated' =>'Подаци о кориÑнику Ñу измењени',
+'realandemail' =>'ÐиÑте попунили поља "Стварно име" и/или "Eмаjл адреÑа".',
+'groupupdated' =>'Дефиниција група измењна.',
+'groupanddesc' =>'ÐиÑте унели име групе.',
+'fillallfields' =>'Молим попуните Ñва поља.',
+'listPmustN' =>'Поредак ће бити нумерички.',
+'listupdated' =>'ЛиÑта је ажурирана!',
+'listitemadded' =>'Додат нови Ñлог у лиÑти.',
+'relatedaddedmsg' =>'Повезани задатак је додат лиÑти.',
+'relatederror' =>'Овај задатак је већ у вези Ñа задацима из лиÑте повезаних.',
+'relatedremoved' =>'Повезани задатак је избачен из лиÑте.',
+'notifyadded' =>'КориÑник је додат лиÑти обавештавања.',
+'notifyerror' =>'КориÑник је већ у лиÑти обавештавања за овај задатак.',
+'notifyremoved' =>'КориÑник је избачен из лиÑте обавештавања.',
+'editcommentsaved' =>'Измењени коментар је Ñачуван.',
+'commentdeletedmsg' =>'Коментар је обриÑан.',
+'gotonewtask' =>'Иди на новонаправљени задатак',
+'projectcreated' =>'Ваш нови пројекат је креиран. Можете га Ñада подеÑити у зони за управљање пројектима.',
+'customiseproject' =>'ПодеÑи овај пројекат',
+'projectupdated' =>'Подешавања пројекта измењена',
+'emptytitle' =>'Поље за назив пројекта ниÑте унели! Молимо унеÑите га.',
+'loginbelow' =>'Сада можете покушати да Ñе пријавите.',
+'attachmentdeletedmsg' =>'Прикључак је обриÑан',
+'reminderaddedmsg' =>'Ваш подÑетник је додат.',
+'reminderdeletedmsg' =>'Ваш изабрани подÑетик је обриÑан.',
+'flyspraytask' =>'Flyspray задатак',
+'fieldsmissing' =>'Ðека поља не Ñадрже податке, или Ñу подаци погрешни.',
+'relatedinvalid' =>'Ðе поÑтоји такав задатак.',
+'relatedproject' =>'Овај задатак је прикључен другом пројекту. Додај везу у Ñваком Ñлучају',
+'addanyway' =>'Додај у Ñваком Ñлучају',
+'cancel' =>'Откажи',
+'alreadyedited' =>'Овај задатак је изменио неко други пре него што Ñте Ñачували промене. Да ли још желите да Ñачувате промене?',
+'saveanyway' =>'Сачувај моје промене у Ñваком Ñлучају',
+'nouserselected' =>'КориÑник није изабран. Изаберите најмање једног кориÑника пре него што покушате поново.',
+'groupswitchupdated' =>'КориÑничка група је измењена уÑпешно.',
+'takenownershipmsg' =>'Овај задатак је Ñада додељен Вама.',
+'adminrequestmade' =>'Ваш захтев је Ñада поÑлат руководиоцу пројекта.',
+'newdepnotify' =>'Ðова завиÑноÑÑ‚ је додата Ñледећем задатку:',
+'dependadded' =>'ЗавиÑноÑÑ‚ задатка додата',
+'dependaddfailed' =>'Дошло је до грешке при додавању завиÑноÑти. Проверите да ли задатак поÑтоји и да ли Ñе можда узајамно не блокирају.',
+'depremovedmsg' =>'ЗавиÑноÑÑ‚ задатака је уклоњена',
+'newdepis' =>'Ðова завиÑноÑÑ‚ је',
+'magicurlsent' =>'Порука је поÑлата на Вашу адреÑу за обавештења. Она Ñадржи адреÑу линка који ће Ð’Ð°Ñ Ð¾Ð´Ð²ÐµÑти на Ñтраницу где ћете довршити овај задатак.',
+'changefspass' =>'Промена Flyspray лозинке',
+'magicurlmessage' =>'Молимо Ñледите Ñледећи линк иÑпод за промену Ваше Flyspray лозинке:',
+'erroronform' =>'Дошло је до проблема у Ñлању Ваше форме',
+'addressused' =>'Ова адреÑа Ñе кориÑти да Ñе региÑтрује Flyspray налог. Ðко не очекујете ову поруку, молимо игноришите је и обришите. Следите Ñледећи link да довршите региÑтрацију:',
+'confirmcodeis' =>'Ваш потврдни коод је:',
+'codesent' =>'Ваш потврдни коод је поÑлат. Молимо Ñледите инÑтрукције Ñадржане у поруци.',
+'codenotsent' =>'Ðе може Ñе поÑлати Ваш коод, молимо покушајте каÑније.',
+'taskmadeprivatemsg' =>'Овај задатак је начињен приватним',
+'taskmadepublicmsg' =>'Овај задатак је начињен поново јавним',
+'realandnotify' =>'Потребно је попунити поља "Стварно име", "Емаjл адреÑа", или "Jabber адреÑа".',
+'pmreqdeniedmsg' =>'Захтев одбијен од Ñтране рукуводиоца пројекта',
+'massopsuccess' =>'МаÑовно извођење операција уÑпешно - дозволе Ñу поÑтављене',
+'usernotexist' =>'КориÑник не поÑтоји у овој Flyspray инÑталацији',
+'commentattachperms' =>'Ðе можете обриÑати коментар, јер немате дозволу за бриÑање приклучења',
+'voterecorded' =>'Ваш Ð³Ð»Ð°Ñ Ñ˜Ðµ упамћен',
+'votefailed' =>'Ваш Ð³Ð»Ð°Ñ Ð½ÐµÑ›Ðµ бити додат овог пута.',
+'createnewgroup' =>'Креирај нову групу',
+'requiredfields' =>'Обавезна поља Ñу означена Ñа',
+'addthisgroup' =>'Додај ову групу',
+'createnewproject' =>'Креирај нови пројекат',
+'addnewproject' =>'Додај нови пројекат',
+'htmlallowed' =>'ХТМЛ коод је дозвољен',
+'createthisproject' =>'Креирај овај пројекат',
+'inlineimages' =>'Прикажи прикључену Ñлику у линији',
+'createnewtask' =>'Креирање новог задатка у пројекту:',
+'addanother' =>'Додавање још једног задатка након овог',
+'addthistask' =>'Додај овај задатак',
+'notifyme' =>'ОбавеÑти ме о било каквој промени овог задатка',
+'newtask' =>'Ðови задатак',
+'attachafile' =>'Прикачи датотеку',
+'registernewuser' =>'РегиÑтровање новог кориÑнка',
+'none' =>'Ðишта',
+'registeraccount' =>'РегиÑтровање овог налога',
+'both' =>'Обадва',
+'notifyfrom' =>'Обавештење од ',
+'donotreply' =>'ОВО ЈЕ ÐУТОМÐТСКРПОРУКÐ, ÐЕ ОДГОВÐРÐЈТЕ ÐРЊУ!',
+'disclaimer' =>'Примили Ñте ову поруку пошто је затражена од Flyspray ÑиÑтема за праћење грешака. Ðко не очекујете поруку, или не желите да примате пошту у будуће, можете променити подешавања у Вашем ÑиÑтему обавештавања на овом линку изнад.',
+'userwho' =>'КориÑник који је то урадио',
+'moreinfo' =>'Више информација могу Ñе наћи пратећи Ñледећи линк:',
+'newtaskopened' =>'Ðови Flyspray задатак је отворен. Детаљи Ñу видљиви иÑпод.',
+'notify.taskclosed' =>'Следећи задатак је Ñада затворен:',
+'notify.taskreopened' =>'Следећи задатак је Ñада поново отворен:',
+'newdep' =>'Следећи задатак има нову завиÑноÑÑ‚:',
+'notify.depremoved' =>'Следећи задатак има избачену завиÑноÑÑ‚:',
+'olddepwas' =>'Стара завиÑноÑÑ‚ је',
+'notify.commentadded' =>'Следећи задатак има додат нови коментар:',
+'commentis' =>'ТекÑÑ‚ коментара је иÑпод.',
+'newattachment' =>'Ðова датотека је прикључена Ñледећем задатку:',
+'detailsbelow' =>'Детаљи Ñу иÑпод.',
+'notify.relatedadded' =>'Ðовоповезани задатак је додат Ñледећем задатку:',
+'relatedis' =>'Повезани задатак је',
+'assignedtoyou' =>'Додељен Вам је Ñледећи задатак:',
+'takenownership' =>'је преузео влаÑништво над Ñледећим задатком:',
+'requiresaction' =>'Следећи задатак захтева акцију руководиоца пројекта:',
+'requiresactionnotify' =>'Задатак захтева акцију руководиоца пројекта',
+'pmdeny' =>'Руководилац пројеката је одбио захтев за Ñледеће задатке:',
+'pmdenynotify' =>'Руководилац пројеката је одбио захтев.',
+'fileaddedtoo' =>'Једна, или више, датотека Ñу прикључене.',
+'taskwatching' =>'Следећи задатак пратите',
+'isdepfor' =>'је нова завиÑноÑÑ‚ за',
+'denialreason' =>'Разлог забране',
+'taskchanged' =>'Следећи задатак је измењен. Измене Ñу видљиве иÑпод. За више информација о изменама види на линку, као и на језичку "ИÑторија".',
+'useraddedtoassignees' =>'КориÑник Ñе Ñам додао лиÑти кориÑника којима је додељен овај задатак.',
+'removeddepis' =>'Уклоњена завиÑноÑÑ‚ је',
+'isnodepfor' =>'није завиÑноÑÑ‚ више за',
+'usergroups' =>'КориÑничке групе',
+'pmtoolbox' =>'Ðлати руководиоца пројеката',
+'groupmanage' =>'Управљање групама',
+'pendingrequests' =>'Ðе решени захтеви',
+'reasongiven' =>'Разлог наведен',
+'nopendingreq' =>'Ðема не решених захтева за руководиоца пројекта.',
+'givereason' =>'Ðаведите разлог',
+'catlisted' =>'Уређивач лиÑте категорија',
+'oslisted' =>'Уређивач лиÑте опретаивних ÑиÑтема',
+'verlisted' =>'Уређивач лиÑте верзија',
+'tasktypeed' =>'Уређивач лиÑте типова',
+'resed' =>'Уређивач лиÑте разлучивања',
+'deny' =>'Одбиј',
+'notifiedwhen' =>'обавештен кад',
+'onlynewtasks' =>'Ðови задтак отоворен',
+'allevents' =>'Ðови догађај Ñе појавио у задатку',
+'feeds' =>'FEED-ови',
+'feeddescription' =>'ÐžÐ¿Ð¸Ñ FEED-а',
+'feedimgurl' =>'Линк Ñлике за FEED (празно, ако нема Ñлике)',
+'notifysubject' =>'Субјекат поруке обавештења',
+'notifysubjectinfo' =>'(%p = наÑлов пројекта, %s = задатак укратко, %t = број задатка, %a = акција, %u = кориÑник)',
+'priority6' =>'Тренутни',
+'priority5' =>'Скорији',
+'priority4' =>'Ургентни',
+'priority3' =>'ВиÑоки',
+'priority2' =>'Ðормалан',
+'priority1' =>'Ðизак',
+'sendcode' =>'Пошаљи коод!',
+'entercode' =>'УнеÑите потврдни коод, који Ñте примили у Вашој поруци. Такође унеÑите Вашу тражену лозинку.',
+'confirmationcode' =>'Потврдни коод',
+'registererror' =>'Проверите да ли Ñте попунили Ñва обавезна поља и након тога унеÑтите иÑправне податке за Ваш жељени начин обавештења.',
+'validusername' =>'(Ñамо алфанумерички знаци и - _ . Ñу дозвољени)',
+'emailtaken' =>'Емајл адреÑа, или Jabber адреÑа Ñу већ наведени. Морате изабрати другу.',
+'note' =>'<strong>Ðапомена:</strong> Биће Вам поÑлат коод потврде, пре него што Ñе Ваш налог креира. Коод ће Вам бити поÑлат коришћењем претходно изабране методе.<br />Ðко унеÑете погрешне податке, <strong>нећете примити Ваш потврдни коод</strong>.',
+'changelog' =>'Дневник промена',
+'changeloggen' =>'Генератор дневника промена',
+'listfrom' =>'ЛиÑтање Ñтавки дневника од',
+'to' =>'до',
+'oldestfirst' =>'Старији прво',
+'recentfirst' =>'Ðајновији прво',
+'severityrep' =>'Извештај важноÑти',
+'totalopen' =>'Укупно отоворених задатака',
+'age' =>'Датум',
+'agerep' =>'Датум извештаја',
+'eventsrep' =>'Извештај о одгађајима',
+'events' =>'Догађаји',
+'Tasks' =>'Задаци',
+'opened' =>'Отворени',
+'edited' =>'измењени',
+'assigned' =>'Додељени',
+'within' =>'Унутар',
+'pastday' =>'Јуче',
+'pastweek' =>'Прошле недеље',
+'pastmonth' =>'Прошлог меÑеца',
+'pastyear' =>'Прошле године',
+'nolimit' =>'Ðема ограничења',
+'from' =>'Од',
+'duein' =>'ДоÑпева у',
+'selectfromdate' =>'Избор од датума',
+'selecttodate' =>'Избод до датума',
+'showvoters' =>'Прикажи/Ñакриј глаÑаче',
+'roadmap' =>'План',
+'roadmapfor' =>'План за верзију',
+'tasks' =>'задатака',
+'completed' =>'завршено.',
+'opentasks' =>'отворених задатака',
+'of' =>'% од',
+'severity5' =>'Критична',
+'severity4' =>'ВиÑока',
+'severity3' =>'Средња',
+'severity2' =>'ÐиÑка',
+'severity1' =>'Веома ниÑка',
+'Redirect' =>'ПреуÑмери',
+'redirectmsg' =>'Ðко Ваш Интернет претраживач не подржава редирекцију кликните на %sHERE%s за редирекцију',
+'allowclosedcomments' =>'Дозволи коментаре над затвореним задацима',
+'comment' =>'Коментар',
+'editowncomments' =>'Измени Ñвој коментар',
+'reopened' =>'Поново отворен',
+'loading' =>'Учитавање...',
+'notifyown' =>'Обавештења о ÑопÑтвеним променама',
+'youremail' =>'Ваша емајл адреÑа',
+'thankyouforbug' =>'Захваљујемо Вам Ñе на пријави проблема. Можете видети задатке и пратити напредак над њима, било када, пратећи Ñледећи линк:',
+'anonuser' =>'Ðепознати кориÑник',
+'conflict' =>'Конфликт',
+'file' =>'Датотека',
+'KiB' =>'кБ',
+'MiB' =>'МБ',
+'size' =>'Величина',
+'projectgroup' =>'Пројектна група',
+'profile' =>'Профил:',
+'viewprofile' =>'Увид профила',
+'regdate' =>'РегиÑтрован од',
+'tasksopened' =>'Задатак отворен',
+'replyto' =>'Одоговори',
+'notifytypes' =>'Тип обавештења',
+'pm.taskchanged' =>'Задатак измењен',
+'pm.taskreopened' =>'Задатак поново отворен',
+'pm.depadded' =>'ЗавÑноÑÑ‚ додата',
+'pm.depremoved' =>'ЗавиÑноÑÑ‚ уклоњена',
+'pmrequest' =>'ПМ захтев',
+'pmrequestdenied' =>'ПМ захтев одбачен',
+'newassignee' =>'Ðови овлашћеник',
+'revdepadded' =>'Контра-завиÑноÑÑ‚ додата',
+'revdepaddedremoved' =>'Контра-завиÑноÑÑ‚ укллоњена',
+'assigneeadded' =>'Овалшћеник додат',
+'addusergroup' =>'Додај кориÑника овој групи',
+'groupmembers' =>'Чланови групе',
+'deleteuser' =>'БриÑање кориÑника',
+'userdeleted' =>'КориÑник обриÑан',
+'autoassign' =>'ÐутоматÑка додела задатка влаÑнику категорије',
+'ssl' =>'SSL',
+'updatewrong' =>'Провера ажурирања је укључена и у том поÑтупку Ñе појавила грешка
+приликом контактирања Ñервера. Или је проблем Ñа Ñпољашњим приÑтупом овог хоÑта,
+или је проблем Ñа мрежом.
+Молимо поÑетите Flyspray Ñајт, ради добављања најновије верзије!',
+'deleteproject' =>'Бриши овај пројекат и премеÑти Ñадржај у',
+'projectdeleted' =>'Пројекат је уÑпешно обриÑан',
+'feedforall' =>'FEED за Ñве пројекте',
+'usercreated' =>'КориÑник креиран',
+'userdeleted' =>'КориÑник обриÑан',
+'created' =>'Креиран',
+'deleted' =>'ОбриÑан',
+'userid' =>'КориÑнички број',
+'editassignments' =>'Измени доделу',
+'preview' =>'Преглед',
+'anyprogress' =>'Ма који напредак',
+'tasksrelated' =>'Задаци који Ñу у вези',
+'duplicatetasks' =>'Дуплих задатака',
+'databasemodfailed' =>'Промена базе података неуÑпешна. Могући разог је недоÑтатак привилегија.',
+'frequency' =>'УчеÑтало',
+'newuserregistered' =>'Ðови кориÑник је региÑтрован у Вашој Flyspray инÑталацији. КориÑнички подаци Ñу Ñледећи:',
+'newuserregisterednotify'=>'Ðови кориÑник региÑтрован',
+'notify_registration' =>'ОбавеÑти админиÑтратора о новим региÑтрацијама',
+'textversion' =>'ТекÑÑ‚ верзија',
+'onlyprimary' =>'Задатак не блокира друге задатке',
+'switch' =>'Промени',
+'max' =>'макÑ.',
+'dates' =>'Датуми',
+'selectduedatefrom' =>'Очекује Ñе од',
+'selectduedateto' =>'доÑпева до',
+'selectsincedatefrom' =>'Мењено од',
+'selectsincedateto' =>'до',
+'selectdate' =>'Изабери датум',
+'selectopenedfrom' =>'Отворено од',
+'selectopenedto' =>'до',
+'selectclosedfrom' =>'Затворено од',
+'selectclosedto' =>'до',
+'startat' =>'Почни на',
+'hasattachment' =>'Има прикључак (attachment)',
+'private' =>'Приватно',
+'watching' =>'Праћење промена',
+'alreadyvotedthistask' =>'глаÑали Ñте за овај задатак',
+'alreadyvotedthisday' =>'већ глаÑано данаÑ',
+'visibility' =>'ВидљивоÑÑ‚',
+'public' =>'Јавно',
+'leaveemptyauto' =>'Ðемојте уноÑитити лозинку, ако желите да Ñе она аутоматÑки генерише.',
+'novalidemail' =>'ÐиÑте унели важећу емаjл адреÑу.',
+'novalidjabber' =>'ÐиÑте унели важећу Jabber адреÑу.',
+'missingrequired' =>'ÐиÑте попунили Ñва обавезна поља!',
+'entervalidusername' =>'Молимо унеÑите важеће кориÑничко и Ñтварно име.',
+'couldnotaddusernotif' =>'Овај кориÑник неће бити додат лиÑти обавештења.',
+'defaulttask' =>'Подразумевани Ð¾Ð¿Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‚ÐºÐ°',
+'all' =>'Ñве',
+'events.useraddedtoassignees' =>'КориÑник додат лиÑти додељених',
+'vote(s)' =>'глаÑ(ова)',
+'eventlog' =>'Дневник догађаја',
+'assignmentchanged' =>'Додела промењена',
+'detailedinfo' =>'Детаљне информације',
+'All' =>'Све',
+'tasksireported' =>'Задаци и извештаји',
+'recentlyopened' =>'ЧеÑто отварани',
+'stats' =>'СтатуÑи',
+'totaltasks' =>'укупно задатака',
+'mostwanted' =>'Ðајтраженији задаци',
+'defaultentry' =>'Подразумевана улазна Ñтрана',
+'toplevel' =>'Увид највишег нивоа',
+'overview' =>'Преглед',
+'error#' =>'Грешка број: ',
+'error1' =>'Ðемате довољно дозвола да видите овај прикључак!',
+'error3' =>'Поновљен поÑтупак, враћање на главну Ñтрану!',
+'error4' =>'Ðемате админиÑтраторÑка права!',
+'error5' =>'КориÑник није региÑтрован у овој Flyspray инÑталацији!',
+'error6' =>'Погрешна админиÑтраторÑка зона.',
+'error7' =>'Пријава неуÑпела. Погрешна лозинка!',
+'error71' =>'КориÑнички налог је закључан за наредних %d минута због превеликог броја покушаја пријаве!',
+'error8' =>'ÐиÑте унели кориÑничко име и/или лозинку!',
+'error9' =>'Задатак не поÑтоји, или немате дозволу за увид задатака!',
+'error10' =>'Овај задатак не поÑтоји!',
+'error101' =>'Ðемате дозволу за увид овог задатка!',
+'error102' =>'Ðемате дозволу за увид овог задатка! Пријавите Ñе, можда ће пријава помоћи.',
+'error11' =>'Ðемате дозволу за измену овог коментара!',
+'error12' =>'Ðеважећи магични кључ! Да ли Ñте Ñигурни да је ово Ваша порука обавештења?',
+'error13' =>'Ðнонимни кориÑник нема профил!',
+'error14' =>'Ðемате довољно дозвола да креирате нову групу!',
+'error15' =>'Ðемате довољно дозвола да отворите задатак!',
+'error16' =>'Ви ниÑте руководилац пројеката!',
+'error17' =>'Погрешна ПМ зона!',
+'error18' =>'Погрешан магични линк!',
+'error19' =>'КориÑник не поÑтоји у овој Flayspray инÑталацији!',
+'error20' =>'Погрешна измена базе података!',
+'error21' =>'Једна, или више, електронÑких порука (емајл) није поÑлата! Проверите Вашу конфигурацију.',
+'error22' =>'Забрањена региÑтрација новим кориÑницима!',
+'error23' =>'КориÑнику, или његовој групи, је забрањена пријава!',
+'error24' =>'Neither the dot executable nor a public dot server has been set.',
+'error25' =>'План је раÑположив Ñамо за Ñпецифичне пројекте.',
+'done' =>'урађено',
+'rss' =>'RSS',
+'atom' =>'Ðтом',
+'projectnotdeleted' =>'Пројекат није обриÑан!',
+'GMT' =>'GMT',
+'timezone' =>'ВременÑка зона',
+'accept' =>'Прихвати',
+'reasonfordeinal' =>'Разлог за забрану',
+'pruneclosedlinks' =>'ОдÑтрани затворене линкове',
+'pruneclosedtasks' =>'ОдÑтрани затворене задатке',
+'pagegenerated' =>'Страна и Ñлика генериÑани у %d Ñекунди.',
+'pruninglevel' =>'ОдÑтрањивање нивоа',
+'lastuser' =>'ПоÑледњи кориÑник не може бити обриÑан!',
+'allprivate' =>'Сви пројекти Ñу приватни!',
+'deletegroup' =>'Обриши ову групу и премеÑти кориÑњике у',
+'parent' =>'Ðадређен',
+'ordertip' =>'РедоÑлед Ñтавки биће приказан у лиÑти',
+'showtip' =>'Прикажи ову Ñтавку у лиÑти',
+'deletetip' =>'Бриши ову Ñтавку из лиÑте',
+'del' =>'Бриши',
+'request1' =>'Затражено је затварање задатка.',
+'request2' =>'ПоÑтупак за поновно отварање задатка уÑпешно окончан.',
+'allpriorities' =>'Сви приоритети',
+'noroadmap' =>'Ова опција није раÑположива у тренутној верзији.',
+'expand' =>'Рашири',
+'collapse' =>'Сажми',
+'expandall' =>'Рашири Ñве',
+'collapseall' =>'Сажми Ñве',
+'minpwsize' =>'Минимална дужина лозинке је 5 знакова',
+'passwordtoosmall' =>'Дужина лозинке је прекратка',
+'accountwaslocked' =>'Ваш кориÑнички налог је закључан због превеликог броја покушаја пријаве!',
+'failedattempts' =>'Укупно %d неуÑпешних покушаја пријаве!',
+'groupnotexist' =>'Изабрана група не поÑтоји у овом пројекту!',
+'searchindetails' =>'Детаљи претраге',
+'showasassignees' =>'Прикажи као додељене',
+'find' =>'Ðађи',
+'tls' =>'TLS',
+'isadmin' =>'Да ли је ово админиÑтратор?',
+'addvotes' =>'ГлаÑај за',
+);
diff --git a/lang/sr_lat.php b/lang/sr_lat.php
new file mode 100644
index 0000000..102a604
--- /dev/null
+++ b/lang/sr_lat.php
@@ -0,0 +1,830 @@
+<?php
+
+$translation = array(
+'edituser' => 'Uredi korisnika',
+'username' => 'KorisniÄko ime',
+'realname' => 'Stvarno ime',
+'emailaddress' => 'E-mail adresa',
+'jabberid' => 'Jabber ID',
+'notifytype' => 'Tip obaveštenja',
+'group' => 'Grupa',
+'accountenabled' => 'Prijava dozvoljena',
+'updatedetails' => 'Ažuriraj detalje',
+'setglobally' => 'Ova povlastica postavljena je globalno.',
+'usergroupmanage' => 'Upravljanje korisnicima i grupama',
+'newuser' => 'Registrovanje novog korisnika',
+'newgroup' => 'Kreiranje nove grupe',
+'yes' => 'Da',
+'no' => 'Ne',
+'editgroup' => 'Uredi grupu',
+'groupname' => 'Naziv grupe',
+'description' => 'Opis',
+'admin' => 'Admin grupa',
+'opennewtasks' => 'Kreiraj novi zadatak',
+'modifytasks' => 'Izmeni postojeći zadatak',
+'addcomments' => 'Dodaj komentar',
+'attachfiles' => 'Pridruži fajl',
+'vote' => 'Glasaj',
+'groupenabled' => 'ÄŒlanovi se mogu prijavljivati',
+'groupopen' => 'ÄŒlanovi se mogu prijavljivati',
+'tasktypelist' => 'Lista tipova zadataka',
+'categorylist' => 'Lista kategorija',
+'oslist' => 'Lista operativnih sistema',
+'resolutionlist' => 'Resolutions list',
+'versionlist' => 'Lista verzija',
+'severitylist' => 'Lista važnosti',
+'listnote' => 'Napomena: DeÄekiranje kolone "Prikaz" može izmeniti neke zadatke kada su u režimu izmene. Promena polja u koloni "Ime" će se odnositi na sve zadatke, gde se pominje promena. Slogovi koji se ne mogu obrisati zaÅ¡tićeni su jer su potrebni za ispravno funkcionisanje zadataka.',
+'name' => 'Ime',
+'order' => 'Redosled',
+'back' => 'Nazad',
+'text' => 'Tekst',
+'highlight' => 'Naglasi',
+'show' => 'Prikaz',
+'owner' => 'Vlasnik',
+'selectowner' => 'Izbor vlasnika',
+'update' => 'Ažuriraj',
+'addnew' => 'Dodaj novi',
+'flysprayprefs' => 'Flyspray povlastice',
+'projecttitle' => 'Naslov projekta',
+'baseurl' => 'Bazni URL za ovu instalaciju',
+'replyaddress' => 'Obaveštenja repliciraj na e-mail adresu',
+'themestyle' => 'Tema / stil',
+'language' => 'Jezik',
+'anonview' => 'Dozvoli neprijavljenim korisnicima da gledaju zadatke',
+'allowanon' => 'Dozvoli neprijavljenim korisnicima da kreiraju zadatke',
+'never' => 'Nikad',
+'anonymously' => 'Anonimno',
+'afterregister' => 'Samo nakon registrovanja',
+'spamproof' => 'Omogući potvrdni kood za registraciju novih korisnika',
+'anongroup' => 'Grupa za novo-registrovanog korisnika',
+'groupassigned' => 'ÄŒlanovi ove grupe mogu dodeljivati zadatke',
+'forcenotify' => 'Naloži obaveštenja o zadacima',
+'neversend' => 'da se nikad ne Å¡alju',
+'userchoose' => 'da svaki korisnik može izabrati',
+'email' => 'E-mail',
+'jabber' => 'Jabber ID',
+'defaultcatowner' => 'Podrazumevani vlasnik kategorije',
+'noone' => 'Nijedan',
+'jabbernotify' => 'Jabber obaveštenja',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'KorisniÄko ime',
+'jabberpass' => 'KorisniÄka lozinka',
+'saveoptions' => 'SaÄuvaj',
+'editcomment' => 'Uredi komentar',
+'commentby' => 'Komentarisao',
+'saveeditedcomment' => 'Snimi izmenjeni komentar',
+'projectprefs' => 'Mogućnosti nad projektima',
+'pagetitle' => 'Naslov strane',
+'defaultproject' => 'Podrazumevani projekat',
+'projectlists' => 'Lista projekata',
+'showlogo' => 'Prikaži naslov logo-a',
+'intromessage' => 'Uvodna poruka',
+'isactive' => 'Projekat je aktivan',
+'createproject' => 'Kreiraj novi projekat',
+'nopermission' => 'Nemate dozvolu da koristite ovu stranu!',
+'listordertip' => 'Redosled će biti prikazan u listi',
+'listshowtip' => 'Prikaži ovu stavku u listi',
+'categoryownertip' => 'Ovo lice će primati obaveštenja kada se otvori zadatak u ovoj kategoriji',
+'categoryparenttip' => 'The parent category this new one will fall under',
+'notsubcategory' => 'Nijedna (vrhovna kategorija!)',
+'showinlineimages' => 'Prikaži sliku prikljuÄka u liniji',
+'dateformat' => 'Format datuma (%d, %m, %y, ili %Y)',
+'dateformat_extended' => 'Detaljni format datuma',
+'cache_feeds' => 'Keširanje podataka',
+'no_cache' => 'Nema keširanja',
+'cache_disk' => 'Keširanje na disku',
+'cache_db' => 'Keširanje u DB',
+'subcategoryof' => 'Podkategorija kategorije',
+'visiblecolumns' => 'Kolone zaglavlja prikazane u task listi',
+'tense' => 'Verzija',
+'listtensetip' => 'Prošla, sadašnja, ili buduća',
+'past' => 'Prošla',
+'present' => 'Sadašnja',
+'future' => 'Buduća',
+'oldpass' => 'Stara lozinka',
+'nooldpass' => 'Stara lozinka nije podešena',
+'oldpasswrong' => 'Stara lozinka je pogrešna',
+'changepass' => 'Nova lozinka',
+'confirmpass' => 'Potvrdi novu lozinku',
+'projectmanager' => 'Rukovodilac projekata',
+'viewtasks' => 'Uvid zadataka',
+'modifyowntasks' => 'Izmena sopstvenih zadataka',
+'modifyalltasks' => 'Izmena zadataka koji nisu u vlasništvu',
+'viewcomments' => 'Uvid komentara',
+'editcomments' => 'Izmena komentara',
+'deletecomments' => 'Brisanje komentara',
+'viewattachments' => 'Uvid prikljuÄka',
+'createattachments' => 'Napravi prikljuÄenje',
+'deleteattachments' => 'ObriÅ¡i prikljuÄke',
+'viewhistory' => 'Uvid promena',
+'closeowntasks' => 'Zatvori sopstvene zadatke',
+'closeothertasks' => 'Zatvori zadatke koji nisu u vlasništvu',
+'assigntoself' => 'Dodeli zadatak sebi, ako nije već dodeljen',
+'assignotherstoself' => 'Dodeli druge zadatke sebi',
+'viewreports' => 'Uvid dnevnika događaja',
+'othersview' => 'Dozvoli svakom da gleda ovaj projekat',
+'usersandgroups' => 'Korisnici i grupe',
+'globalgroup' => 'Opšta grupa',
+'globalgroups' => 'Opšte grupe',
+'defaultglobalgroup' => 'Podrazumevana opšta grupa za nove korisnike',
+'addtogroup' => 'Dodaj u grupu',
+'moveuserstogroup' => 'Premesti korisnika u grupu',
+'nogroup' => 'Nema grupa - IzbaÄene iz projekta',
+'eventdesc' => 'Opis događaja',
+'requestedby' => 'Tražen od',
+'daterequested' => 'Datum zahteva',
+'closetask' => 'Zatvor zadatak',
+'reopentask' => 'Ponovo otovori zadatak',
+'applymember' => 'Primeni za Älanove projekta',
+'forcurrentproj' => 'Za tekući projekat',
+'lostpw' => 'Pronalaženje izgubljene lozinke',
+'lostpwexplain' => 'Unesite VaÅ¡e korisniÄko ime za slanje linka za promenu lozinke. Ovo će biti saÄuvano u VaÅ¡ profil obaveÅ¡tenja.',
+'sendlink' => 'Pošalji link',
+'savenewpass' => 'SaÄuvaj novu lozinku',
+'anonreg' => 'Dozvoli registraciju novih korisnika',
+'allowanonopentask' => 'Dozvoli neprijavljenim korisnicima da otvaraju zadatke',
+'editglobalgroup' => 'Izmeni opštu grupu',
+'editgroupforproj' => 'Izmeni projektnu grupu',
+'notshownforadmin' => 'Dozvole nisu prikazane za administratorsku grupu. Nepotrebno ih je menjati.',
+'general' => 'Opšte',
+'userregistration' => 'KorisniÄka registracija',
+'notifications' => 'Obaveštenja',
+'resetoptions' => 'Poništi opcije',
+'preferences' => 'Opšta podešavanja',
+'tasktypes' => 'Tipovi zadataka',
+'resolutions' => 'Razrešenja',
+'categories' => 'Kategorije',
+'operatingsystems' => 'Operativni sistemi',
+'versions' => 'Verzije',
+'admintoolboxlong' => 'Administratorske alatke',
+'newproject' => 'Novi projekat',
+'delete' => 'Obriši',
+'listdeletetip' => 'Obriši ovu stavku iz liste',
+'lookandfeel' => 'Izgled i osećaj',
+'globaltheme' => 'Opšta tema',
+'emailnotify' => 'E-mail obaveštenja',
+'fromaddress' => 'Å alje sa adrese',
+'smtpserver' => 'SMTP servera',
+'smtpuser' => 'SMTP korisniÄko ime',
+'smtppass' => 'SMTP lozinka',
+'addrewrite' => 'Koristi zamenu adrese',
+'usereminderdaemon' => 'Omogući pozadinsku uslugu (daemon) za podsetnik',
+'tasksperpage' => 'Zadataka po strani liste zadataka',
+'addtoassignees' => 'Dodaj se sam listi dodeljenih',
+'taskstatuses' => 'Statusi zadataka',
+'canvote' => 'Može glasati za zadatak',
+'loginsuccessful' => 'Prijava uspešna.',
+'youareloggedout' => 'Vi ste se odjavili.',
+'waitwhiletransfer' => 'Molim saÄekajte prenos...',
+'clicknowait' => 'Klikni ovde, ako ne želiÅ¡ da ÄekaÅ¡.',
+'accountdisabled' => 'VaÅ¡ nalog je iskljuÄen. Kontaktirajte administratora sistema.',
+'task' => 'Zadatak',
+'edittask' => 'Izmeni ovaj zadatak',
+'openedby' => 'Otvoren od',
+'editedby' => 'Poslednji menjao',
+'tasktype' => 'Tip zadatka',
+'category' => 'Kategorija',
+'status' => 'Status',
+'assignedto' => 'Dodeljen',
+'operatingsystem' => 'Operativni sistem',
+'severity' => 'Važnost',
+'reportedversion' => 'Prijavljena verzija',
+'dueinversion' => 'OÄekuje se u verziji',
+'undecided' => 'Neodređeno',
+'percentcomplete' => '% izvršenja',
+'details' => 'Detalji',
+'savedetails' => 'SaÄuvaj detalje',
+'canceledit' => 'Otkaži izmenu',
+'anonymous' => 'Nepoznati pošiljalac',
+'complete' => 'završeno',
+'closedby' => 'Zatvoreno od strane',
+'reasonforclosing' => 'Razlog zatvaranja:',
+'reopenthistask' => 'Ponovo otovori ovaj zadatak',
+'comments' => 'Komentari',
+'attachments' => 'PrikljuÄci',
+'relatedtasks' => 'Povezani zadaci',
+'edit' => 'Izmeni',
+'addcomment' => 'Dodaj komentar',
+'fileuploadedby' => 'Datoteka poslata od strane ',
+'uploadafile' => 'PrikljuÄi datoteku',
+'uploadnow' => 'Pošalji sada!',
+'thesearerelated' => 'Ovi zadaci su u vezi sa ovim zadatkom',
+'remove' => 'Izbaci',
+'addnewrelated' => 'Dodaj novi zadatak koji je s ovim u vezi',
+'add' => 'Dodaj!',
+'otherrelated' => 'Drugi zadaci s kojima je ovaj u vezi',
+'receivenotify' => 'Ovi korisnici će primati detaljna obaveštenja kada se zadatak izmeni.',
+'addusertolist' => 'Dodaj korisnika ovoj listi',
+'addtolist' => 'Dodaj u listu',
+'addmyself' => 'Sam se dodaj ovoj listi',
+'removemyself' => 'Izbaci se sam iz ove liste',
+'theseusersnotify' => 'Ovi korisnici će primati detaljna obaveštenja uvek kada se ovaj zadatak izmeni.',
+'attachedtoproject' => 'PrikljuÄen projektu',
+'reminders' => 'Podsetnik',
+'system' => 'Sistem',
+'remindthisuser' => 'Podseti ovog korisnika',
+'thisoften' => 'To se Äesto',
+'startafter' => 'SaÄekajte pre pokretanja podsetnika',
+'hours' => 'Sat(i)',
+'days' => 'Dan(a)',
+'weeks' => 'Nedelja(e)',
+'addreminder' => 'Dodaj podsetnik',
+'defaultreminder' => 'Ovo je podsetnik koji će pratiti seledeće Flyspray zadatke:',
+'message' => 'Poruka',
+'closed' => 'Zatvoren',
+'filename' => 'Naziv datoteke:',
+'date' => 'Datum',
+'filesize' => 'VeliÄina datoteke:',
+'closurecomment' => 'Dodatno pojašnjenje o razlogu zatvaranja:',
+'history' => 'Istorija',
+'nohistory' => 'Istorija nije dostupna.',
+'eventdate' => 'Datum',
+'user' => 'Korisnik',
+'event' => 'Događaj',
+'fieldchanged' => 'Promenjeno je',
+'taskopened' => 'Zadatak otvoren',
+'taskreopened' => 'Zadatak ponovo otvoren',
+'taskclosed' => 'Zadatak zatvoren',
+'commentadded' => 'Dodat komentar',
+'commentedited' => 'Komentar izmenjen',
+'commentdeleted' => 'Komentar obrisan',
+'attachmentadded' => 'PrikljuÄak dodat',
+'attachmentdeleted' => 'PrikljuÄak obrisan',
+'taskedited' => 'Detalji zadatka izmenjeni',
+'notificationadded' => 'Korisnik dodat listi obaveštenja',
+'notificationdeleted' => 'Korisnik izbaÄen iz liste obaveÅ¡tenja',
+'relatedadded' => 'Povezani zadatak dodat',
+'relateddeleted' => 'Povezani zadatak izbaÄen',
+'taskassigned' => 'Zadatak dodeljen',
+'taskreassigned' => 'Zadatak preusmeren na',
+'assignmentremoved' => 'Dodela izbrisana',
+'summary' => 'Kratak opis',
+'addedasrelated' => 'Zadatak dodat listi povezanih',
+'deletedasrelated' => 'Zadatak izbaÄen iz liste povezanih',
+'reminderadded' => 'Dodat podsetnik',
+'reminderdeleted' => 'Podsetnik izbaÄen',
+'priority' => 'Prioritet',
+'previousvalue' => 'Prethodna vrednost',
+'newvalue' => 'Nova vrednost',
+'selectareason' => 'Izbor razloga',
+'assigntome' => 'Dodeli meni',
+'reopenrequest' => 'Zahtev za ponovno otvaranje',
+'requestclose' => 'Zahtev za zatvaranje',
+'ownershiptaken' => 'Korisnikovo vlasništvo',
+'closerequestmade' => 'Zatraženo zatvaranje zadatka',
+'reopenrequestmade' => 'Zatraženo ponovno otvaranje zadatka',
+'taskdependson' => 'Ovaj zadatak zavisi od:',
+'taskblocks' => 'Ovaj zadatak blokira ove od zatvaranja',
+'depadded' => 'Dodata zavisnost',
+'depaddedother' => 'Ovaj zadatak dodaje kao zavisnost',
+'depremoved' => 'Zavisnost uklonjena',
+'depremovedother' => 'Ovaj zadatak je izbaÄen iz liste zavisnih zadataka',
+'showdetailserror' => 'Ovaj zadatak ne postoji, ili nemate dozovolu da ga pogledate.',
+'makeprivate' => 'napravi ga privatnim',
+'makepublic' => 'napravi ga javnim',
+'taskmadeprivate' => 'Zadatak je sada privatan',
+'taskmadepublic' => 'Privatnost uklonjena! Zadatak je sada javan',
+'confirmdeletecomment' => 'Sigurni ste da želite obrisati ovaj komntar? %s',
+'confirmdeleteattach' => 'Sigurni ste da želite obrisati ovaj prikljuÄak?',
+'selectedhistory' => 'Prikaz izabranih detalja istorije',
+'showallhistory' => 'Prikaži jeziÄak pune istorije ponovo',
+'hidethis' => 'Sakrij ovu zonu ponovo',
+'mark100' => 'OznaÄi zadatak 100% zavrÅ¡enim',
+'watchtask' => 'prati promene',
+'stopwatching' => 'zaustavi praćenje',
+'commentlink' => 'Link ka komentaru',
+'submitreq' => 'Pošalji zahtev',
+'reasonforreq' => 'Razlog zabrane',
+'pmreqdenied' => 'Rukovodilac projektima je zabranio zahtev',
+'taskpendingreq' => 'Rukovodilac projekata trenutno neÅ¡to radi. Vidite na "Istorija" jeziÄku Å¡ta se dogaÄ‘a.',
+'previoustask' => 'Prethodni zadatak',
+'nexttask' => 'Naredni zadatak',
+'duedate' => 'Rok',
+'attachnoperms' => 'Postoje prikljuÄci za ovaj komentar, ali Vi nemate dozvolu za njihov uvid.',
+'open' => 'Otvori',
+'depgraph' => 'Uvid grafiÄkog prikaza zavisnosti',
+'reset' => 'Reset',
+'selectusers' => 'Izbor korisnika...',
+'addmetoassignees' => 'Dodaj me listi dodeljenih',
+'addedtoassignees' => 'Korisnik dodat listi dodeljenih',
+'dependencygraph' => 'GrafiÄki prikaz zavisnosti',
+'attachanotherfile' => 'PrikaÄi joÅ¡ neku datoteku',
+'OK' => 'U redu',
+'addvote' => 'Daj glas',
+'notifyfromfs' => 'Obaveštenje od Flyspray sistema',
+'autogenerated' => 'OVO JE AUTOMATSKI GENERISANA PORUKA! NE ODGOVARAJ!',
+'forward' => 'Napred',
+'previous' => 'Prethodni',
+'next' => 'Sledeći',
+'first' => 'Prvi',
+'last' => 'Poslednji',
+'page' => 'Strana %d od %d',
+'search' => 'Traži',
+'alltasktypes' => 'Svi tipovi zadataka',
+'allseverities' => 'Sve važnosti',
+'alldevelopers' => 'Svi programeri',
+'notyetassigned' => 'Nije ipak dodeljen',
+'allcategories' => 'Sve kategorije',
+'allstatuses' => 'Svi statusi',
+'allopentasks' => 'Svi otovoreni zadaci',
+'sortthiscolumn' => 'Sort po ovoj koloni',
+'id' => 'Br.',
+'project' => 'Projekat',
+'dateopened' => 'Otvoren',
+'progress' => 'Napredak u radu',
+'searchthisproject' => 'Pretraži ovaj projekat za',
+'dueanyversion' => 'OÄekuje se u ma kojoj verziji',
+'anyversion' => 'Prijavljen u ma kojoj verziji',
+'dueversion' => 'OÄekuje se u verziji',
+'lastedit' => 'Posednje izmenjen',
+'os' => 'Operativni sistem',
+'reportedin' => 'Prijavljen u',
+'taskrange' => 'Prikaz zadataka %d - %d od %d',
+'noresults' => 'Vaša pretraga nije dala rezltate.',
+'takeaction' => 'Primeni izabrano',
+'watchtasks' => 'Praćenje izabranih zadataka',
+'stopwatchingtasks' => 'Prekini praćenje izabranih zadataka',
+'assigntaskstome' => 'Dodeli mi izabrani zadatak',
+'dueby' => 'Dospeva',
+'dueanytime' => 'Bilo kada',
+'selectduedate' => 'Izbor datuma dospeća',
+'toggleselected' => 'Promena izbora',
+'due' => 'Rok',
+'assignedtome' => 'Dodeljen meni',
+'tasklist' => 'Lista zadataka',
+'dateclosed' => 'Datum zatvaranja',
+'advanced' => 'Još uslova za pretragu',
+'searchcomments' => 'Traži u komentarima',
+'searchforall' => 'Traži za sve reÄi',
+'anonusers' => 'Nepoznati korisnik',
+'miscellaneous' => 'Razno',
+'users' => 'Korisnici',
+'taskproperties' => 'Svojstva zadatka',
+'selectsincedate' => 'Izaberi izmene od',
+'changedsince' => 'Izmene od',
+'updatefs' => 'Molimo ažurirajte Vaš Flyspray.',
+'currentversion' => 'Vaša tranutna verzija je',
+'latestversion' => ', a poslednja verzija je',
+'hidemessage' => '(podseti me kasnije)',
+'saveas' => 'SaÄuvaj pretragu kao',
+'nosearches' => 'Nema saÄuvanih pretraga',
+'saving' => 'Snimanje...',
+'votes' => 'Glasova',
+'allclosedtasks' => 'Svi zatvoreni zadaci',
+'password' => 'Lozinka',
+'login' => 'Prijavi se',
+'rememberme' => 'Podseti me',
+'lostpassword' => 'Izgubili ste lozinku?',
+'lostpwforfs' => 'Izgubili ste lozinku za Flyspray',
+'lostpwmsg1' => 'Zdravo!
+
+Izgubio/la sam moju lozinku za Flyspray na ',
+'lostpwmsg2' => ', molim Vas pošaljite mi novu lozinku.
+
+KorisniÄko ime: ',
+'regards' => '
+
+Pozdrav od',
+'yourusername' => ' VaÅ¡e korisniÄko ime ',
+'locale' => 'en-AU',
+'filenotexist' => 'Datoteka ne postoji, ili nemate dovolu da joj pristupite.',
+'showtask' => 'Prikaži zadatak',
+'now' => 'Sada',
+'go' => 'Idi!',
+'opentaskanon' => 'Otvori novi zadatak kao nepoznat',
+'register' => 'Registruj se',
+'addnewtask' => 'Dodaj novi zadatak',
+'reports' => 'Dnevnik događaja',
+'editmydetails' => 'Izmeni moje detalje',
+'logout' => 'Odjavi se',
+'disabledaccount' => 'Vaša prijava je onemogućena!<br />Odmah ćete biti odjavljeni...',
+'poweredby' => 'Powered by Flyspray',
+'projects' => 'Projekti',
+'allprojects' => 'Svi projekti',
+'selectproject' => 'za projekat:',
+'tasksall' => 'Svi zadaci',
+'tasksassigned' => 'Zadaci dodeljeni meni',
+'tasksreported' => 'Zadaci koje sam ja prijavio',
+'taskswatched' => 'Pratim zadatak',
+'mysearch' => 'Moja pretraga',
+'admintoolbox' => 'Administratorske alatke',
+'manageproject' => 'Upravljanje projektima',
+'permissions' => 'Vidi dozvole',
+'hide' => 'Sakrij',
+'pendingreq' => 'ÄŒeka se za PM zahtev',
+'errorpage' => 'Flyspray ne može prikazati ovu stranu.
+ Možda ste tražili zadatak koji ne postoji, ili nemate
+ dozvolu da vidite stranicu koju ste želeli.<br /><br />
+ Možda ste pokušali da koristite pogrešan link u interakciji sa bazom podataka.
+ Ako je to taÄno razmislite malo o svojim postupcima i molimo Vas da to ne Äinite ponovo.',
+'permissionsforproject' => 'Dozvole za ',
+'switchto' => 'Pređi na',
+'lastsearch' => 'Poslednja pretraga',
+'modify' => 'Izmeni',
+'noticefrom' => 'Obaveštenje od',
+'hasopened' => 'imate OTVOREN NOVI Flyspray zadatak i dodeljen je Vama:',
+'moreinfonew' => 'Možete naći Više informacija o ovoj greški na Flyspray strani:',
+'newtaskcategory' => 'Novi Flyspray zadatak biće otovoren u ovoj kategoriji',
+'categoryowner' => 'Vi dobijate ovo jer ste navedeni kao vlasnik kategorije.',
+'tasksummary' => 'Rezime zadatka:',
+'newtaskadded' => 'Vaš novi zadatak je dodat.',
+'summaryanddetails' => 'Potrebno je pouniti oba polja, "Kratak opis" i "Detalji".',
+'goback' => 'Idi nazad.',
+'messagefrom' => 'Ova poruka je od Flyspray sistema za praćenje grešaka na ',
+'hasjustmodified' => 'ste izmenili sledeći zadatak.',
+'changedfields' => 'Izmenjena polja imaju prefiks (**)',
+'moreinfomodify' => 'Možete imati više informacija o ovom zadatku sledeći ovaj URL:',
+'nolongerassigned' => 'Sledeći zadatak više nije dodeljen Vama. Sada je dodeljen',
+'hasassigned' => 'imate dodeljen sledeći Flyspray zadatak:',
+'taskupdated' => 'Zadatak je izmenjen.',
+'hasclosedassigned' => 'je zatvoren sledeći Flyspray zadatak koji Vam je dodeljen:',
+'unassigned' => 'Nedodeljen',
+'hasclosed' => 'je zatvoren sledeći zadatak.',
+'youonnotify' => 'Vi dobijate ovo jer ste na listi za obaveštavanje.',
+'taskclosedmsg' => 'Zadatak je zatvoren.',
+'returntotask' => 'Povratak na detalje zadatka',
+'backtoindex' => 'Povraćaj na listu zadataka',
+'noclosereason' => 'Niste izabrali razlog za zatvaranje tog zadatka.',
+'hasreopened' => 'ponovo je otvoren sledeći Flyspray zadatak koji ste zatvorili:',
+'taskreopenedmsg' => 'Zadatak je ponovo otvoren.',
+'backtotask' => 'Vrati se na zadatak.',
+'commentaddedmsg' => 'Komentar je dat.',
+'commenttoassigned' => 'dodat je sledeći komentar zadatku koji Vam je dodeljen:',
+'commenttotask' => 'dat je sledeći komentar ovom zadatku.',
+'nocommententered' => 'Vi zaista treba da unesete komentar pre nago Å¡to kliknete na dugme.',
+'fillinfields' => 'Niste popunili sva polja.',
+'notcurrentpass' => 'To nije Vaša trenutna lozinka!',
+'passchanged' => 'Vaša lozinka je promenjena.',
+'closewindow' => 'Možete sada zatvoriti ovaj prozor.',
+'passnomatch' => 'Vaša nova lozinka nije ista',
+'usernametaken' => 'To korisniÄko ime je vać zauzeto! Moraćete da unesete drugo.',
+'newusercreated' => 'Novi korisnikÄki nalog je kreiran.',
+'accountcreated' => 'Vaš nalog je kreiran.',
+'newuserwarning' => 'Imajte u vidu da Vaš nalog treba da bude odobren od strane adminiistratora sistema. Ako ne možete da se prijavite to je razlog zašto ne možete.',
+'nomatchpass' => 'Lozinka ne odgovara.',
+'confirmwrong' => 'Potvrdni kood je pogrešan!',
+'formnotcomplete' => 'Forma nije cela popunjena.',
+'formnotnumeric' => 'UneÅ¡eni podatak nije numeriÄki!',
+'groupnametaken' => 'Ime grupe je već zauzeto.',
+'newgroupadded' => 'Nova grupa dodata.',
+'optionssaved' => 'Flyspray opcije saÄuvane',
+'hasuploaded' => 'otpremili ste datoteku, kao prikljuÄak, zadatku koji je dodeljen:',
+'hasattached' => 'prikaÄena je datoteka sledećem zadatku.',
+'fileuploaded' => 'Datoteka je otpremljena.',
+'fileerror' => 'DoÅ¡lo je do greÅ¡ke u otpremanju VaÅ¡e datoteke. Možda su dozvole nad <i>prikljuÄenim/</i> direktorijumima pogreÅ¡ne.',
+'contactadmin' => 'Kontaktirajte administratora ovog projekta.',
+'selectfileerror' => 'Niste izabrali datoteku.',
+'userupdated' => 'Podaci o korisniku su izmenjeni',
+'realandemail' => 'Niste popunili polja "Stvarno ime" i/ili "e-mail adresa".',
+'groupupdated' => 'Definicija grupa izmenjna.',
+'groupanddesc' => 'Niste uneli ime grupe.',
+'fillallfields' => 'Molim popunite sva polja.',
+'listPmustN' => '"Poredak" će biti numeriÄki.',
+'listupdated' => 'Lista je ažurirana!',
+'listitemadded' => 'Dodat novi slog u listi.',
+'relatedaddedmsg' => 'Povezani zadatak je dodat listi.',
+'relatederror' => 'Ovaj zadatak je već u vezi sa zadacima iz liste povezanih.',
+'relatedremoved' => 'Povezani zadatak je izbaÄen iz liste.',
+'notifyadded' => 'Korisnik je dodat listi obaveštavanja.',
+'notifyerror' => 'Korisnik je već u listi obaveštavanja za ovaj zadatak.',
+'notifyremoved' => 'Korisnik je izbaÄen iz liste obaveÅ¡tavanja.',
+'editcommentsaved' => 'Izmenjeni komentar je saÄuvan.',
+'commentdeletedmsg' => 'Komentar je obrisan.',
+'gotonewtask' => 'Idi na novonapravljeni zadatak',
+'projectcreated' => 'Vaš novi projekat je kreiran. Možete ga sada podesiti u zoni za upravljanje projektima.',
+'customiseproject' => 'Podesi ovaj projekat',
+'projectupdated' => 'Podešavanja projekta izmenjena',
+'emptytitle' => 'Polje za naziv projekta niste uneli! Molimo unesite ga.',
+'loginbelow' => 'Sada možete pokušati da se prijavite.',
+'attachmentdeletedmsg' => 'PrikljuÄak je obrisan',
+'reminderaddedmsg' => 'Vaš podsetnik je dodat.',
+'reminderdeletedmsg' => 'Vaš izabrani podsetik je obrisan.',
+'flyspraytask' => 'Flyspray zadatak',
+'fieldsmissing' => 'Neka polja ne sadrže podatke, ili su podaci pogrešni.',
+'relatedinvalid' => 'Ne postoji takav zadatak.',
+'relatedproject' => 'Ovaj zadatak je prikljuÄen drugom projektu. Dodaj vezu u svakom sluÄaju',
+'addanyway' => 'Dodaj u svakom sluÄaju',
+'cancel' => 'Otkaži',
+'alreadyedited' => 'Ovaj zadatak je izmenio neko drugi pre nego Å¡to ste saÄuvali promene. Da li joÅ¡ želite da saÄuvate promene?',
+'saveanyway' => 'SaÄuvaj moje promene u svakom sluÄaju',
+'nouserselected' => 'Korisnik nije izabran. Izaberite najmanje jednog korisnika pre nego što pokušate ponovo.',
+'groupswitchupdated' => 'KorisniÄka grupa je izmenjena uspeÅ¡no.',
+'takenownershipmsg' => 'Ovaj zadatak je sada dodeljen Vama.',
+'adminrequestmade' => 'Vaš zahtev je sada poslat rukovodiocu projekta.',
+'newdepnotify' => 'Nova zavisnost je dodata sledećem zadatku:',
+'dependadded' => 'Zavisnost zadatka dodata',
+'dependaddfailed' => 'Došlo je do greške pri dodavanju zavisnosti. Proverite da li zadatak postoji i da li se možda uzajamno ne blokiraju.',
+'depremovedmsg' => 'Zavisnost zadataka je uklonjena',
+'newdepis' => 'Nova zavisnost je',
+'magicurlsent' => 'Poruka je poslata na Vašu adresu za obaveštenja. Ona sadrži adresu linka koji će Vas odvesti na stranicu gde ćete dovršiti ovaj zadatak.',
+'changefspass' => 'Promena Flyspray lozinke',
+'magicurlmessage' => 'Molimo sledite sledeći link ispod za promenu Vaše Flyspray lozinke:',
+'erroronform' => 'Došlo je do problema u slanju Vaše forme',
+'addressused' => 'Ova adresa se koristi da se registruje Flyspray nalog. Ako ne oÄekujete ovu poruku, molimo ignoriÅ¡ite je i obriÅ¡ite. Sledite sledeći URL da dovrÅ¡ite registraciju:',
+'confirmcodeis' => 'Vaš potvrdni kood je:',
+'codesent' => 'Vaš potvrdni kood je poslat. Molimo sledite instrukcije sadržane u poruci.',
+'codenotsent' => 'Ne može se poslati Vaš kood, molimo pokušajte kasnije.',
+'taskmadeprivatemsg' => 'Ovaj zadatak je naÄinjen privatnim',
+'taskmadepublicmsg' => 'Ovaj zadatak je naÄinjen ponovo javnim',
+'realandnotify' => 'Potrebno je popuniti polja "Stvarno ime", "E-mail adresa", ili "Jabber ID".',
+'pmreqdeniedmsg' => 'Zahtev odbijen od strane rukuvodioca projekta',
+'massopsuccess' => 'Masovno izvođenje operacija uspešno - dozvole su postavljene',
+'usernotexist' => 'Korisnik ne postoji u ovoj Flyspray instalaciji',
+'commentattachperms' => 'Ne možete obrisati komentar, jer nemate dozvolu za brisanje prikluÄenja',
+'voterecorded' => 'Vaš glas je upamćen',
+'votefailed' => 'Vaš glas neće biti dodat ovog puta.',
+'createnewgroup' => 'Kreiraj novu grupu',
+'requiredfields' => 'Obavezna polja su oznaÄena sa',
+'addthisgroup' => 'Dodaj ovu grupu',
+'createnewproject' => 'Kreiraj novi projekat',
+'addnewproject' => 'Dodaj novi projekat',
+'htmlallowed' => 'HTML kood je dozvoljen',
+'createthisproject' => 'Kreiraj ovaj projekat',
+'inlineimages' => 'Prikaži prikljuÄenu sliku u liniji',
+'createnewtask' => 'Kreiranje novog zadatka u projektu:',
+'addanother' => 'Dodavanje još jednog zadatka nakon ovog',
+'addthistask' => 'Dodaj ovaj zadatak',
+'notifyme' => 'Obavesti me o bilo kakvoj promeni ovog zadatka',
+'newtask' => 'Novi zadatak',
+'attachafile' => 'PrikaÄi datoteku',
+'registernewuser' => 'Registrovanje novog korisnka',
+'none' => 'Ništa',
+'registeraccount' => 'Registrovanje ovog naloga',
+'both' => 'Obadva',
+'notifyfrom' => 'Obaveštenje od ',
+'donotreply' => 'OVO JE AUTOMATSKA PORUKA, NE ODGOVARAJTE NA NJU!',
+'disclaimer' => 'Primili ste ovu poruku poÅ¡to je zatražena od FlySpray sistema za praćenje greÅ¡aka. Ako ne oÄekujete poruku, ili ne želite da primate poÅ¡tu u buduće, možete promeniti podeÅ¡avanja u VaÅ¡em sistemu obaveÅ¡tavanja na ovom linku iznad.',
+'userwho' => 'Korisnik koji je to uradio',
+'moreinfo' => 'Više informacija mogu se naći prateći sledeći link:',
+'newtaskopened' => 'Novi Flyspray zadatak je otvoren. Detalji su vidljivi ispod.',
+'notify.taskclosed' => 'Sledeći zadatak je sada zatvoren:',
+'notify.taskreopened' => 'Sledeći zadatak je sada ponovo otvoren:',
+'newdep' => 'Sledeći zadatak ima novu zavisnost:',
+'notify.depremoved' => 'Sledeći zadatak ima izbaÄenu zavisnost:',
+'olddepwas' => 'Stara zavisnost je',
+'notify.commentadded' => 'Sledeći zadatak ima dodat novi komentar:',
+'commentis' => 'Tekst komentara je ispod.',
+'newattachment' => 'Nova datoteka je prikljuÄena sledećem zadatku:',
+'detailsbelow' => 'Detalji su ispod.',
+'notify.relatedadded' => 'Novopovezani zadatak je dodat sledećem zadatku:',
+'relatedis' => 'Povezani zadatak je',
+'assignedtoyou' => 'Dodeljen Vam je sledeći zadatak:',
+'takenownership' => 'je preuzeo vlasništvo nad sledećim zadatkom:',
+'requiresaction' => 'Sledeći zadatak zahteva akciju rukovodioca projekta:',
+'requiresactionnotify' => 'Zadatak zahteva akciju rukovodioca projekta',
+'pmdeny' => 'Rukovodilac projekata je odbio zahtev za sledeće zadatke:',
+'pmdenynotify' => 'Rukovodilac projekata je odbio zahtev.',
+'fileaddedtoo' => 'Jedna, ili viÅ¡e, datoteka su prikljuÄene.',
+'taskwatching' => 'Sledeći zadatak pratite',
+'isdepfor' => 'je nova zavisnost za',
+'denialreason' => 'Razlog zabrane',
+'taskchanged' => 'Sledeći zadatak je izmenjen. Izmene su vidljive ispod. Za viÅ¡e informacija o izmenama vidi na linku, kao i na jeziÄku "Istorija".',
+'useraddedtoassignees' => 'Korisnik se sam dodao listi korisnika kojima je dodeljen ovaj zadatak.',
+'removeddepis' => 'Uklonjena zavisnost je',
+'isnodepfor' => 'nije zavisnost više za',
+'usergroups' => 'KorisniÄke grupe',
+'pmtoolbox' => 'Alati rukovodioca projekata',
+'groupmanage' => 'Upravljanje grupama',
+'pendingrequests' => 'Ne rešeni zahtevi',
+'reasongiven' => 'Razlog naveden',
+'nopendingreq' => 'Nema ne rešenih zahteva za rukovodioca projekta.',
+'givereason' => 'Navedite razlog',
+'catlisted' => 'UreÄ‘ivaÄ liste kategorija',
+'oslisted' => 'UreÄ‘ivaÄ liste opretaivnih sistema',
+'verlisted' => 'UreÄ‘ivaÄ liste verzija',
+'tasktypeed' => 'UreÄ‘ivaÄ liste tipova',
+'resed' => 'UreÄ‘ivaÄ liste razluÄivanja',
+'deny' => 'Odbij',
+'notifiedwhen' => 'obavešten kad',
+'onlynewtasks' => 'Novi zadtak otovoren',
+'allevents' => 'Novi događaj se pojavio u zadatku',
+'feeds' => 'Feed-ovi',
+'feeddescription' => 'Opis Feed-a',
+'feedimgurl' => 'URL slike za Feed (prazno, ako nema slike)',
+'notifysubject' => 'Subjekat poruke obaveštenja',
+'notifysubjectinfo' => '(%p = naslov projekta, %s = zadatak ukratko, %t = broj zadatka, %a = akcija, %u = korisnik)',
+'priority6' => 'Trenutni',
+'priority5' => 'Skoriji',
+'priority4' => 'Urgentni',
+'priority3' => 'Visoki',
+'priority2' => 'Normalan',
+'priority1' => 'Nizak',
+'sendcode' => 'Pošalji kood!',
+'entercode' => 'Unesite potvrdni kood, koji ste primili u Vašoj poruci. Takođe unesite Vašu traženu lozinku.',
+'confirmationcode' => 'Potvrdni kood',
+'registererror' => 'Proverite da li ste popunili sva obavezna polja i nakon toga unestite ispravne podatke za VaÅ¡ željeni naÄin obaveÅ¡tenja.',
+'validusername' => '(samo alfanumeriÄki znaci i - _ . su dozvoljeni)',
+'emailtaken' => 'E-mail adresa, ili Jabber-ID su već navedeni. Morate izabrati drugu.',
+'note' => '<strong>Napomena:</strong> Biće Vam poslat kood potvrde, pre nego što se Vaš nalog kreira. Kood će Vam biti poslat korišćenjem prethodno izabrane metode.<br />Ako unesete pogrešne podatke, <strong>nećete primiti Vaš potvrdni kood</strong>.',
+'changelog' => 'Dnevnik promena',
+'changeloggen' => 'Generator dnevnika promena',
+'listfrom' => 'Listanje stavki dnevnika od',
+'to' => 'do',
+'oldestfirst' => 'Stariji prvo',
+'recentfirst' => 'Najnoviji prvo',
+'severityrep' => 'Izveštaj važnosti',
+'totalopen' => 'Ukupno otovorenih zadataka',
+'age' => 'Datum',
+'agerep' => 'Datum izveštaja',
+'eventsrep' => 'Izveštaj o odgađajima',
+'events' => 'Događaji',
+'Tasks' => 'Zadaci',
+'opened' => 'Otvoreni',
+'edited' => 'izmenjeni',
+'assigned' => 'Dodeljeni',
+'within' => 'Unutar',
+'pastday' => 'JuÄe',
+'pastweek' => 'Prošle nedelje',
+'pastmonth' => 'Prošlog meseca',
+'pastyear' => 'Prošle godine',
+'nolimit' => 'Nema ograniÄenja',
+'from' => 'Od',
+'duein' => 'Dospeva u',
+'selectfromdate' => 'Izbor od datuma',
+'selecttodate' => 'Izbod do datuma',
+'showvoters' => 'Prikaži/sakrij glasaÄe',
+'roadmap' => 'Plan',
+'roadmapfor' => 'Plan za verziju',
+'tasks' => 'zadataka',
+'completed' => 'završeno.',
+'opentasks' => 'otvorenih zadataka',
+'of' => '% od',
+'severity5' => 'KritiÄna',
+'severity4' => 'Visoka',
+'severity3' => 'Srednja',
+'severity2' => 'Niska',
+'severity1' => 'Veoma niska',
+'Redirect' => 'Preusmeri',
+'redirectmsg' => 'Ako VaÅ¡ Internet pretraživaÄ ne podržava redirekciju kliknite na %sHERE%s za redirekciju',
+'allowclosedcomments' => 'Dozvoli komentare nad zatvorenim zadacima',
+'comment' => 'Komentar',
+'editowncomments' => 'Izmeni svoj komentar',
+'reopened' => 'Ponovo otvoren',
+'loading' => 'UÄitavanje...',
+'notifyown' => 'Obaveštenja o sopstvenim promenama',
+'youremail' => 'Vaša e-mail adresa',
+'thankyouforbug' => 'Zahvaljujemo Vam se na prijavi problema. Možete videti zadatke i pratiti napredak nad njima, bilo kada, prateći sledeći URL:',
+'anonuser' => 'Nepoznati korisnik',
+'conflict' => 'Konflikt',
+'file' => 'Datoteka',
+'KiB' => 'kB',
+'MiB' => 'MB',
+'size' => 'VeliÄina',
+'projectgroup' => 'Projektna grupa',
+'profile' => 'Profil:',
+'viewprofile' => 'Uvid profila',
+'regdate' => 'Registrovan od',
+'tasksopened' => 'Zadatak otvoren',
+'replyto' => 'Odogovori',
+'notifytypes' => 'Tip obaveštenja',
+'pm.taskchanged' => 'Zadatak izmenjen',
+'pm.taskreopened' => 'Zadatak ponovo otvoren',
+'pm.depadded' => 'Zavsnost dodata',
+'pm.depremoved' => 'Zavisnost uklonjena',
+'pmrequest' => 'PM zahtev',
+'pmrequestdenied' => 'PM zahtev odbaÄen',
+'newassignee' => 'Novi ovlašćenik',
+'revdepadded' => 'Kontra-zavisnost dodata',
+'revdepaddedremoved' => 'Kontra-zavisnost ukllonjena',
+'assigneeadded' => 'Ovalšćenik dodat',
+'addusergroup' => 'Dodaj korisnika ovoj grupi',
+'groupmembers' => 'ÄŒlanovi grupe',
+'deleteuser' => 'Brisanje korisnika',
+'userdeleted' => 'Korisnik obrisan',
+'autoassign' => 'Automatska dodela zadatka vlasniku kategorije',
+'ssl' => 'SSL',
+'updatewrong' => 'Provera ažuriranja je ukljuÄena i u tom postupku se pojavila greÅ¡ka
+ prilikom kontaktiranja servera. Ili je problem sa spoljašnjim pristupom ovog hosta,
+ ili je problem sa mrežom.
+ Molimo posetite FlySpray sajt, radi dobavljanja najnovije verzije!',
+'deleteproject' => 'Briši ovaj projekat i premesti sadržaj u',
+'projectdeleted' => 'Projekat je uspešno obrisan',
+'feedforall' => 'Feed za sve projekte',
+'usercreated' => 'Korisnik kreiran',
+'userdeleted' => 'Korisnik obrisan',
+'created' => 'Kreiran',
+'deleted' => 'Obrisan',
+'userid' => 'KorisniÄki broj',
+'editassignments' => 'Izmeni dodelu',
+'preview' => 'Pregled',
+'anyprogress' => 'Ma koji napredak',
+'tasksrelated' => 'Zadaci koji su u vezi',
+'duplicatetasks' => 'Duplih zadataka',
+'databasemodfailed' => 'Promena baze podataka neuspešna. Mogući razog je nedostatak privilegija.',
+'frequency' => 'UÄestalo',
+'newuserregistered' => 'Novi korisnik je registrovan u VaÅ¡oj Flyspray instalaciji. KorisniÄki podaci su sledeći:',
+'newuserregisterednotify' => 'Novi korisnik registrovan',
+'notify_registration' => 'Obavesti administratora o novim registracijama',
+'textversion' => 'Tekst verzija',
+'onlyprimary' => 'Zadatak ne blokira druge zadatke',
+'switch' => 'Promeni',
+'max' => 'maks.',
+'dates' => 'Datumi',
+'selectduedatefrom' => 'OÄekuje se od',
+'selectduedateto' => 'dospeva do',
+'selectsincedatefrom' => 'Menjeno od',
+'selectsincedateto' => 'do',
+'selectdate' => 'Izaberi datum',
+'selectopenedfrom' => 'Otvoreno od',
+'selectopenedto' => 'do',
+'selectclosedfrom' => 'Zatvoreno od',
+'selectclosedto' => 'do',
+'startat' => 'PoÄni na',
+'hasattachment' => 'Ima prikljuÄak (attachment)',
+'private' => 'Privatno',
+'watching' => 'Praćenje promena',
+'alreadyvotedthistask' => 'glasali ste za ovaj zadatak',
+'alreadyvotedthisday' => 'već glasano danas',
+'visibility' => 'Vidljivost',
+'public' => 'Javno',
+'leaveemptyauto' => 'Nemojte unosititi lozinku, ako želite da se ona automatski generiše.',
+'novalidemail' => 'Niste uneli važeću e-mail adresu.',
+'novalidjabber' => 'Niste uneli važeću Jabber adresu.',
+'missingrequired' => 'Niste popunili sva obavezna polja!',
+'entervalidusername' => 'Molimo unesite važeće korisniÄko i stvarno ime.',
+'couldnotaddusernotif' => 'Ovaj korisnik neće biti dodat listi obaveštenja.',
+'defaulttask' => 'Podrazumevani opis zadatka',
+'all' => 'sve',
+'events.useraddedtoassignees' => 'Korisnik dodat listi dodeljenih',
+'vote(s)' => 'glas(ova)',
+'eventlog' => 'Dnevnik događaja',
+'assignmentchanged' => 'Dodela promenjena',
+'detailedinfo' => 'Detaljne informacije',
+'All' => 'Sve',
+'tasksireported' => 'Zadaci i izveštaji',
+'recentlyopened' => 'ÄŒesto otvarani',
+'stats' => 'Statusi',
+'totaltasks' => 'ukupno zadataka',
+'mostwanted' => 'Najtraženiji zadaci',
+'defaultentry' => 'Podrazumevana ulazna strana',
+'toplevel' => 'Uvid najvišeg nivoa',
+'overview' => 'Pregled',
+'error#' => 'Greška broj: ',
+'error1' => 'Nemate dovoljno dozvola da vidite ovaj prikljuÄak!',
+'error3' => 'Ponovljen postupak, vraćanje na glavnu stranu!',
+'error4' => 'Nemate administratorska prava!',
+'error5' => 'Korisnik nije registrovan u ovoj Flyspray instalaciji!',
+'error6' => 'Pogrešna administratorska zona.',
+'error7' => 'Prijava neuspela. Pogrešna lozinka!',
+'error71' => 'KorisniÄki nalog je zakljuÄan za narednih %d minuta zbog prevelikog broja pokuÅ¡aja prijave!',
+'error8' => 'Niste uneli korisniÄko ime i/ili lozinku!',
+'error9' => 'Zadatak ne postoji, ili nemate dozvolu za uvid zadataka!',
+'error10' => 'Ovaj zadatak ne postoji!',
+'error101' => 'Nemate dozvolu za uvid ovog zadatka!',
+'error102' => 'Nemate dozvolu za uvid ovog zadatka! Prijavite se, možda će prijava pomoći.',
+'error11' => 'Nemate dozvolu za izmenu ovog komentara!',
+'error12' => 'Nevažeći magiÄni kljuÄ! Da li ste sigurni da je ovo VaÅ¡a poruka obaveÅ¡tenja?',
+'error13' => 'Anonimni korisnik nema profil!',
+'error14' => 'Nemate dovoljno dozvola da kreirate novu grupu!',
+'error15' => 'Nemate dovoljno dozvola da otvorite zadatak!',
+'error16' => 'Vi niste rukovodilac projekata!',
+'error17' => 'Pogrešna PM zona!',
+'error18' => 'PogreÅ¡an magiÄni URL!',
+'error19' => 'Korisnik ne postoji u ovoj Flyspray instalaciji!',
+'error20' => 'Pogrešna izmena baze podataka!',
+'error21' => 'Jedna, ili više, elektronskih poruka (e-mail) nije poslata! Proverite Vašu konfiguraciju.',
+'error22' => 'Zabranjena registracija novim korisnicima!',
+'error23' => 'Korisniku, ili njegovoj grupi, je zabranjena prijava!',
+'error24' => 'Neither the dot executable nor a public dot server has been set.',
+'error25' => 'Plan je raspoloživ samo za specifiÄne projekte.',
+'done' => 'urađeno',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projekat nije obrisan!',
+'GMT' => 'GMT',
+'timezone' => 'Vremenska zona',
+'accept' => 'Prihvati',
+'reasonfordeinal' => 'Razlog za zabranu',
+'pruneclosedlinks' => 'Odstrani zatvorene linkove',
+'pruneclosedtasks' => 'Odstrani zatvorene zadatke',
+'pagegenerated' => 'Strana i slika generisani u %d sekundi.',
+'pruninglevel' => 'Odstranjivanje nivoa',
+'lastuser' => 'Poslednji korisnik ne može biti obrisan!',
+'allprivate' => 'Svi projekti su privatni!',
+'deletegroup' => 'Obriši ovu grupu i premesti korisnjike u',
+'parent' => 'Nadređen',
+'ordertip' => 'Redosled stavki biće prikazan u listi',
+'showtip' => 'Prikaži ovu stavku u listi',
+'deletetip' => 'Briši ovu stavku iz liste',
+'del' => 'Briši',
+'request1' => 'Zatraženo je zatvaranje zadatka.',
+'request2' => 'Postupak za ponovno otvaranje zadatka uspeÅ¡no okonÄan.',
+'allpriorities' => 'Svi prioriteti',
+'noroadmap' => 'Ova opcija nije raspoloživa u trenutnoj verziji.',
+'expand' => 'Raširi',
+'collapse' => 'Sažmi',
+'expandall' => 'Raširi sve',
+'collapseall' => 'Sažmi sve',
+'minpwsize' => 'Minimalna dužina lozinke je 5 znakova',
+'passwordtoosmall' => 'Dužina lozinke je prekratka',
+'accountwaslocked' => 'VaÅ¡ korisniÄki nalog je zakljuÄan zbog prevelikog broja pokuÅ¡aja prijave!',
+'failedattempts' => 'Ukupno %d neuspešnih pokušaja prijave!',
+'groupnotexist' => 'Izabrana grupa ne postoji u ovom projektu!',
+'searchindetails' => 'Detalji pretrage',
+'showasassignees' => 'Prikaži kao dodeljene',
+'find' => 'Nađi',
+'tls' => 'TLS',
+'isadmin' => 'Da li je ovo administrator?',
+'addvotes' => 'Glasaj za',
+);
diff --git a/lang/sv_se.php b/lang/sv_se.php
new file mode 100644
index 0000000..4407372
--- /dev/null
+++ b/lang/sv_se.php
@@ -0,0 +1,836 @@
+<?php
+
+$translation = array(
+'edituser' => 'Redigera användare',
+'accountenabled' => 'Konto Aktiverat',
+'editallusers' => 'Visa alla användare',
+'username' => 'Användarnamn',
+'usersupdated' => 'Användare uppdaterade',
+'realname' => 'Fullständigt namn',
+'emailaddress' => 'Epostadress',
+'jabberid' => 'Jabber ID',
+'profileimage' => 'Profilbild',
+'notifytype' => 'Aviseringssätt',
+'group' => 'Grupp',
+'enableaccounts' => 'Aktivera konton',
+'disableaccounts' => 'Avaktivera konton',
+'deleteaccounts' => 'Ta bort konton',
+'updatedetails' => 'Uppdatera detaljer',
+'setglobally' => 'Denna inställning är global.',
+'usergroupmanage' => 'Användar- och grupphantering',
+'newuser' => 'Skapa ny användare',
+'newuserbulk' => 'Skapa flera nya användare',
+'bulkuserstoadd' => 'Lista över nya användare',
+'optionsforallusers' => 'Inställningar för alla nya användare',
+'newgroup' => 'Skapa ny grupp',
+'yes' => 'Ja',
+'no' => 'Nej',
+'editgroup' => 'Redigera grupp',
+'groupname' => 'Gruppnamn',
+'description' => 'Beskrivning',
+'admin' => 'Administratörsgrupp',
+'opennewtasks' => 'Nytt ärende',
+'modifytasks' => 'Ändra befintliga ärenden',
+'addcomments' => 'Lägga till kommentarer',
+'attachfiles' => 'Bifoga filer',
+'vote' => 'Rösta',
+'groupenabled' => 'Medlemmar kan logga in',
+'groupopen' => 'Medlemmar kan logga in',
+'tasktypelist' => 'Ärendetypslista',
+'categorylist' => 'Kategorilista',
+'oslist' => 'Operativsystemslista',
+'resolutionlist' => 'Lösningslista',
+'versionlist' => 'Versionslista',
+'severitylist' => 'Allvarlighetslista',
+'listnote' => 'Tänk på: Om man kryssar ur "Visa", kan ärenden ändras i redigeringsläge. Om man ändrar "Namn", ändras alla ärenden med det namnet. Objekt som inte går att ta bort är endera skyddade eftersom de behövs för funktionaliteten eller används i något ärende.',
+'name' => 'Namn',
+'order' => 'Ordning',
+'back' => 'Tillbaka',
+'text' => 'Text',
+'highlight' => 'Markera',
+'show' => 'Visa',
+'owner' => 'Ägare',
+'selectowner' => 'Välj ägare',
+'update' => 'Uppdatera',
+'addnew' => 'Lägg till ny',
+'flysprayprefs' => 'Flyspray-inställningar',
+'projecttitle' => 'Projektnamn',
+'baseurl' => 'Base URL för installationen',
+'replyaddress' => 'Svarsadress för aviseringar',
+'themestyle' => 'Tema/stil',
+'language' => 'Språk',
+'anonview' => 'Tillåt anonyma användare att se ärenden',
+'allowanon' => 'Tillåt anonyma användare att öppna nya ärenden',
+'never' => 'Aldrig',
+'anonymously' => 'Anonymt',
+'afterregister' => 'Enbart efter registrering',
+'spamproof' => 'Slå på bekräftelse för nya användarregistreringar',
+'anongroup' => 'Grupp för nya användare',
+'groupassigned' => 'Medlemmar av dessa grupper kan kopplas till ärenden',
+'forcenotify' => 'Tvinga ärendeaviseringar som',
+'neversend' => 'Skicka aldrig',
+'userchoose' => 'Låt varje användare välja',
+'email' => 'Epost',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'Förvald kategoriägare',
+'noone' => 'Ingen',
+'jabbernotify' => 'Jabber-aviseringar',
+'jabberserver' => 'Server',
+'jabberport' => 'Port',
+'jabberuser' => 'Användarnamn för konto',
+'jabberpass' => 'Lösenord för konto',
+'saveoptions' => 'Spara inställningar',
+'editcomment' => 'Ändra kommentar',
+'commentby' => 'Kommentar av',
+'saveeditedcomment' => 'Sparar ändrad kommentar',
+'projectprefs' => 'Projektinställningar',
+'pagetitle' => 'Sidnamn',
+'defaultproject' => 'Förvalt projekt',
+'projectlists' => 'Projektlistor',
+'showlogo' => 'Visa titelbild',
+'showgravatars' => 'Visa gravatars',
+'emailNoHTML' => 'Ingen HTML i E-Postmeddelanden',
+'intromessage' => 'Introduktionstext',
+'isactive' => 'Projektet är aktivt',
+'createproject' => 'Skapa ett nytt projekt',
+'nopermission' => 'Du har inte behörighet att använda denna sida.',
+'listordertip' => 'Listan visas i denna ordning',
+'listshowtip' => 'Visa detta objekt i listan',
+'categoryownertip' => 'Denna person får meddelande när ett ärende med denna kategori öppnas',
+'categoryparenttip' => 'Den kategori som denna nya kommer att ligga under',
+'notsubcategory' => 'Ingen (högstanivåkategori)',
+'showinlineimages' => 'Visa bifogade bilder i meddelandet',
+'dateformat' => 'Datumformat',
+'dateformat_extended' => 'Detaljerat datumformat',
+'cache_feeds' => 'Mellanlagra feeds',
+'no_cache' => 'Ingen mellanlagring',
+'cache_disk' => 'Mellanlagra på disk',
+'cache_db' => 'Mellanlagra i databasen',
+'subcategoryof' => 'Underkategori',
+'visiblecolumns' => 'Kolumner som visas i ärendelistan',
+'visiblefields' => 'Kolumner som visas vid lägg till/ändra/visa ärende',
+'tense' => 'Tid',
+'listtensetip' => 'DÃ¥tid, nutid eller framtid',
+'past' => 'DÃ¥tid',
+'present' => 'Nutid',
+'future' => 'Framtid',
+'oldpass' => 'Gammalt lösenord',
+'nooldpass' => 'Inget gammalt lösenord',
+'oldpasswrong' => 'Felaktigt gammalt lösenord',
+'changepass' => 'Ändra lösenord',
+'confirmpass' => 'Bekräfta lösenord',
+'projectmanager' => 'Projektadministrering',
+'viewtasks' => 'Visa ärenden',
+'modifyowntasks' => 'Ändra egna ärenden',
+'modifyalltasks' => 'Ändra andras ärenden',
+'viewcomments' => 'Visa kommentarer',
+'editcomments' => 'Ändra kommentarer',
+'deletecomments' => 'Ta bort kommentarer',
+'viewattachments' => 'Visa bifogade filer',
+'createattachments' => 'Bifoga filer',
+'deleteattachments' => 'Ta bort bifogade filer',
+'viewhistory' => 'Visa historik',
+'closeowntasks' => 'Stänga egna ärenden',
+'closeothertasks' => 'Stänga andras ärenden',
+'assigntoself' => 'Tilldela ärende till sig själv om det inte redan är det',
+'assignotherstoself' => 'Tilldela andras ärenden till sig själv',
+'viewreports' => 'Visa händelselogg',
+'othersview' => 'Tillåt vem som helst att se detta projekt',
+'usersandgroups' => 'Användare och grupper',
+'globalgroup' => 'Global grupp',
+'globalgroups' => 'Globala grupper',
+'defaultglobalgroup' => 'Förvald global grupp för nya användare',
+'addtogroup' => 'Lägg till grupp',
+'moveuserstogroup' => 'Flytta användare till grupp',
+'nogroup' => 'Ingen grupp - ta bort från projekt',
+'eventdesc' => 'Händelsebeskrivning',
+'requestedby' => 'Efterfrågad av',
+'daterequested' => 'Datum för efterfrågan',
+'closetask' => 'Stäng ärende',
+'reopentask' => 'Återta ärende',
+'applymember' => 'Ansök om medlemskap i projekt',
+'forcurrentproj' => 'För nuvarande projekt',
+'lostpw' => 'Bortglömt lösenord',
+'lostpwexplain' => 'Ange ditt användarnamn och klicka på Skicka länk så får du en länk till en sida där du kan ange ett nytt lösenord. Länken skickas till din aviseringsadress.',
+'sendlink' => 'Skicka länk',
+'savenewpass' => 'Spara nytt lösenord',
+'anonreg' => 'Tillåt nyregistreringar av användare',
+'allowanonopentask' => 'Tillåt anonyma användare att skapa ärenden',
+'editglobalgroup' => 'Ändra global grupp',
+'editgroupforproj' => 'Ändra grupp för projekt',
+'notshownforadmin' => 'Behörigheter visas inte för administratörer. Du behöver inte ändra dem.',
+'general' => 'Generellt',
+'userregistration' => 'Användarregistrering',
+'notifications' => 'Aviseringar',
+'resetoptions' => 'Återställ',
+'preferences' => 'Inställningar',
+'tasktypes' => 'Ärendetyper',
+'resolutions' => 'Lösningar',
+'categories' => 'Kategorier',
+'operatingsystems' => 'Operativsystem',
+'versions' => 'Versioner',
+'admintoolboxlong' => 'Administratörsverktyg',
+'newproject' => 'Nytt projekt',
+'delete' => 'Ta bort',
+'link' => 'Länk',
+'referencelinks' => 'Referens länkar',
+'listdeletetip' => 'Ta bort objektet från listan',
+'lookandfeel' => 'Utseende och känsla',
+'globaltheme' => 'Globalt tema',
+'emailnotify' => 'Epostavisering',
+'fromaddress' => 'Från-adress',
+'smtpserver' => 'SMTP Server',
+'smtpuser' => 'SMTP användarnamn',
+'smtppass' => 'SMTP lösenord',
+'addrewrite' => 'Använd adressomskrivning',
+'usereminderdaemon' => 'Använd bakgrundspåminnelse-daemon',
+'tasksperpage' => 'Ärenden per sida i ärendelistan',
+'addtoassignees' => 'Lägg till sig själv till tilldelare',
+'taskstatuses' => 'Ärendestatus',
+'canvote' => 'Kan rösta på ärenden',
+'loginsuccessful' => 'Inloggning lyckades.',
+'youareloggedout' => 'Du är utloggad.',
+'waitwhiletransfer' => 'Vänta medan du blir vidarebefodrad...',
+'clicknowait' => 'Klicka här om du inte vill vänta.',
+'accountdisabled' => 'Ditt konto är inaktiverat. Kontakta en administratör.',
+'task' => 'Ärende',
+'edittask' => 'Ändra detta ärende',
+'openedby' => 'Skapad av',
+'editedby' => 'Senast ändrad av',
+'tasktype' => 'Ärendetyp',
+'category' => 'Kategori',
+'status' => 'Status',
+'assignedto' => 'Tilldelad',
+'operatingsystem' => 'Operativsystem',
+'severity' => 'Allvarlighet',
+'reportedversion' => 'Rapporterad version',
+'dueinversion' => 'Åtgärdad till version',
+'defaultdueinversion' => 'Standard åtgärd till version för nya ärenden',
+'undecided' => 'Inte bestämt',
+'percentcomplete' => 'Procent färdig',
+'details' => 'Beskrivning',
+'savedetails' => 'Spara ändringar',
+'canceledit' => 'Avbryt ändring',
+'anonymous' => 'Anonym inskickare',
+'complete' => 'färdig',
+'closedby' => 'Stängd av',
+'reasonforclosing' => 'Anledning till stängning:',
+'reopenthistask' => 'Återta ärendet',
+'comments' => 'Kommentarer',
+'attachments' => 'Bifogade filer',
+'relatedtasks' => 'Besläktade ärenden',
+'edit' => 'Ändra',
+'addcomment' => 'Kommentera',
+'fileuploadedby' => 'Filen uppladdad av',
+'uploadafile' => 'Bifoga en fil',
+'addalink' => 'Lägg till en länk',
+'addanotherlink' => 'Lägg till en länk till',
+'uploadnow' => 'Ladda upp nu!',
+'thesearerelated' => 'Dessa ärenden är besläktade med detta',
+'remove' => 'Ta bort',
+'addnewrelated' => 'Lägg till ett besläktat ärende',
+'add' => 'Lägg till!',
+'otherrelated' => 'Andra besläktade ärenden',
+'receivenotify' => 'Användare som får aviseringar när ärendet ändras.',
+'addusertolist' => 'Lägg till användare till listan',
+'addtolist' => 'Lägg till',
+'addmyself' => 'Lägg till mig själv till listan',
+'removemyself' => 'Ta bort mig själv från listan',
+'theseusersnotify' => 'Användare som får detaljerade aviseringar när ärendet ändras.',
+'attachedtoproject' => 'Kopplad till projekt',
+'reminders' => 'PÃ¥minnelser',
+'system' => 'System',
+'remindthisuser' => 'Påminn denna användare',
+'thisoften' => 'Så här ofta',
+'startafter' => 'Starta påminnelser efter',
+'hour' => 'Timma',
+'hours' => 'Timmar',
+'day' => 'Dag',
+'days' => 'Dagar',
+'week' => 'Vecka',
+'weeks' => 'Veckor',
+'addreminder' => 'Lägg till påminnelse',
+'defaultreminder' => 'Det här är en påminnelse för att titta på detta ärende i Flyspray:',
+'message' => 'Meddelande',
+'closed' => 'Stängd',
+'filename' => 'Filnamn:',
+'date' => 'Datum',
+'filesize' => 'Filstorlek:',
+'closurecomment' => 'Ytterligare stängningskommentar:',
+'history' => 'Historik',
+'nohistory' => 'Det finns ingen historik.',
+'eventdate' => 'Datum',
+'user' => 'Användare',
+'event' => 'Händelse',
+'fieldchanged' => 'Fält ändrat',
+'taskopened' => 'Ärende öppnat',
+'taskreopened' => 'Ärende återaktiverat',
+'taskclosed' => 'Ärende stängt',
+'commentadded' => 'Kommentar tillagd',
+'commentedited' => 'Kommentar ändrad',
+'commentdeleted' => 'Kommentar borttagen',
+'attachmentadded' => 'Ny bifogad fil',
+'attachmentdeleted' => 'Borttagen bifogad fil',
+'taskedited' => 'Ändrade ärendebeskrivning',
+'notificationadded' => 'Användare tillagd till aviseringslistan',
+'notificationdeleted' => 'Användare borttagen från aviseringslistan',
+'relatedadded' => 'Besläktat ärende tillagt',
+'relateddeleted' => 'Besläktat ärende borttaget',
+'taskassigned' => 'Ärende tilldelat till',
+'taskreassigned' => 'Ärende återtilldelat till',
+'assignmentremoved' => 'Tilldelning borttagen',
+'summary' => 'Sammanfattning',
+'addedasrelated' => 'Ärende tillagt till relationslistan för',
+'deletedasrelated' => 'Ärende borttaget från relationslistan för',
+'reminderadded' => 'PÃ¥minnelse tillagd',
+'reminderdeleted' => 'PÃ¥minnelse borttagen',
+'priority' => 'Prioritet',
+'previousvalue' => 'Föregående värde',
+'newvalue' => 'Nytt värde',
+'selectareason' => 'Välj anledning',
+'assigntome' => 'Tilldela mig',
+'reopenrequest' => 'Efterfråga återöppning',
+'requestclose' => 'Efterfråga stängning',
+'ownershiptaken' => 'Tilldelade sig själv',
+'closerequestmade' => 'Begärd stängning',
+'reopenrequestmade' => 'Begärd återöppning',
+'taskdependson' => 'Ärendet beror på',
+'taskblocks' => 'Ärendet blockerar dessa',
+'depadded' => 'Lagt till beroende av',
+'depaddedother' => 'Detta ärende tillagd som beroende för',
+'depremoved' => 'Tagit bort beroende av',
+'depremovedother' => 'Ärende borttaget som beroende för',
+'showdetailserror' => 'Endera så finns inte ärendet eller så har du inte behörighet att se det.',
+'makeprivate' => 'gör privat',
+'makepublic' => 'gör publikt',
+'taskmadeprivate' => 'Ärendet gjort privat',
+'taskmadepublic' => 'Privat borttagen - ärendet gjort publikt',
+'confirmdeletecomment' => 'Vill du verkligen ta bort kommentaren? %s',
+'confirmdeleteattach' => 'Vill du verkligen ta bort denna bifogade fil?',
+'selectedhistory' => 'Visa valda historikdetaljer',
+'showallhistory' => 'Visa all historik igen',
+'hidethis' => 'Göm detta fält igen',
+'mark100' => 'Sätt ärendet som 100% färdigt',
+'watchtask' => 'avisera mig',
+'stopwatching' => 'avsluta avisering',
+'commentlink' => 'Kommentarlänk',
+'submitreq' => 'Sänd önskemål',
+'reasonforreq' => 'Anledning för önskemål',
+'pmreqdenied' => 'Projektadministratör nekade efterfrågan',
+'taskpendingreq' => 'Väntar på svar från projektadministratör. Se historikfliken för detaljer.',
+'previoustask' => 'Föregående ärende',
+'nexttask' => 'Nästa ärende',
+'duedate' => 'Färdigdatum',
+'attachnoperms' => 'Det finns bifogade filer till denna kommentar med du har inte behörighet att se dem.',
+'open' => 'Öppna',
+'depgraph' => 'Visa beroendegraf',
+'reset' => 'Återställ',
+'selectusers' => 'Välj användare...',
+'addmetoassignees' => 'Lägg till mig som tilldelare',
+'addedtoassignees' => 'Användare tillagd till tilldelarlistan',
+'dependencygraph' => 'Beroendegraf',
+'attachanotherfile' => 'Bifoga en till fil',
+'OK' => 'OK',
+'addvote' => 'rösta',
+'notifyfromfs' => 'Avisering från Flyspray',
+'autogenerated' => 'DETTA ÄR ETT AUTOMATGENERERAT MEDDELANDE. SVARA INTE',
+'forward' => 'Framåt',
+'previous' => 'Föregående',
+'next' => 'Nästa',
+'first' => 'Första',
+'last' => 'Sista',
+'page' => 'Sida %d av %d',
+'search' => 'Sök',
+'alltasktypes' => 'Alla ärendetyper',
+'allseverities' => 'Alla allvarlighetsgrader',
+'alldevelopers' => 'Alla utvecklare',
+'notyetassigned' => 'Inte tilldelad',
+'allcategories' => 'Alla kategorier',
+'allstatuses' => 'Oberoende av status',
+'allopentasks' => 'Alla öppna ärenden',
+'sortthiscolumn' => 'Sortera på denna kolumn',
+'id' => 'Id',
+'project' => 'Projekt',
+'dateopened' => 'Öppnad den',
+'progress' => 'Framsteg',
+'searchthisproject' => 'Sök i detta projekt efter',
+'dueanyversion' => 'Färdig i alla versioner',
+'anyversion' => 'Rapporterad för alla versioner',
+'dueversion' => 'Färdig i version',
+'lastedit' => 'Senast ändrad',
+'os' => 'Operativsystem',
+'reportedin' => 'Rapporterad i',
+'taskrange' => 'Visar ärende %d - %d av %d',
+'noresults' => 'Sökningen gav inga resultat.',
+'takeaction' => 'Utför',
+'watchtasks' => 'Aviseringar för valda ärenden',
+'stopwatchingtasks' => 'Avsluta avisering för valda ärenden',
+'assigntaskstome' => 'Tilldela valda ärenden till mig',
+'dueby' => 'Färdig den',
+'dueanytime' => 'Färdig när som helst',
+'selectduedate' => 'Välj färdigdatum',
+'toggleselected' => 'Byt val',
+'due' => 'Färdig den',
+'assignedtome' => 'Tilldela mig själv',
+'tasklist' => 'Ärendelista',
+'dateclosed' => 'Stängningsdatum',
+'advanced' => 'Avancerad',
+'searchcomments' => 'Sök i kommentarer',
+'searchforall' => 'Sök alla ord',
+'anonusers' => 'Anonyma användare',
+'miscellaneous' => 'Diverse',
+'users' => 'Användare',
+'taskproperties' => 'Ärendeegenskaper',
+'selectsincedate' => 'Välj ändrad sedan',
+'changedsince' => 'Ändrad sedan',
+'updatefs' => 'Vänligen uppdatera Flyspray.',
+'currentversion' => 'Din version är',
+'latestversion' => 'och den senaste versionen är',
+'hidemessage' => '(påminn mig senare)',
+'saveas' => 'Spara sökning som',
+'nosearches' => 'Inga sparade sökningar',
+'saving' => 'Sparar...',
+'votes' => 'Röster',
+'allclosedtasks' => 'Alla stängda ärenden',
+'password' => 'Lösenord',
+'login' => 'Logga in!',
+'rememberme' => 'Kom ihåg mig',
+'lostpassword' => 'Glömt lösenordet?',
+'lostpwforfs' => 'Bortglömt lösenord för Flyspray',
+'lostpwmsg1' => "Hej.\n\nJag har glömt mitt lösenord för ",
+'lostpwmsg2' => ", kan ni ordna ett nytt lösenord till mig?\n\nanvändarnamn: ",
+'regards' => "\n\nMvh,",
+'yourusername' => ' ditt användarnamn ',
+'locale' => 'sv-SE',
+'filenotexist' => 'Endera så finns inte filen eller så har du inte behörighet att komma åt den.',
+'showtask' => 'Visa ärende',
+'now' => 'Nu',
+'go' => 'Kör!',
+'opentaskanon' => 'Öppna ett nytt ärende anonymt',
+'register' => 'Registrera',
+'addnewtask' => 'Nytt ärende',
+'reports' => 'Händelselogg',
+'editmydetails' => 'Ändra mina uppgifter',
+'logout' => 'Logga ut',
+'disabledaccount' => 'Ditt konto är avstängt!<br />Loggar ut dig nu...',
+'poweredby' => 'Drivs av Flyspray',
+'projects' => 'Projekt',
+'allprojects' => 'Alla Projekt',
+'selectproject' => 'till Projekt:',
+'tasksall' => 'Alla ärenden',
+'tasksassigned' => 'Ärenden tilldelad mig',
+'tasksreported' => 'Ärenden rapporterade av mig',
+'taskswatched' => 'Ärenden som aviseras mig',
+'mysearch' => 'Mina sökningar',
+'admintoolbox' => 'Administratörsverktyg',
+'manageproject' => 'Administrera projekt',
+'permissions' => 'Visa behörigheter',
+'hide' => 'Göm',
+'pendingreq' => 'Det finns gjorda efterfrågningar',
+'errorpage' => "Flyspray kan inte hitta den sida som du efterfrågade.\n Du kanske sökte ett ärende som inte finns eller så\n har du inte behörighet att se sidan.<br /><br />\n Du kanske försökte med en otillåten URL för att komma åt\n databasen via SQL injection. Om du gjort det så gå till närmsta\n hörn, sätt dig ner och fundera över vad du gjort. När du kommer\n tillbaka så gör inte om det!",
+'permissionsforproject' => 'Behörigheter för ',
+'switchto' => 'Byt till',
+'lastsearch' => 'Senaste sökning',
+'modify' => 'Ändra',
+'noticefrom' => 'Avisering från',
+'hasopened' => 'har öppnat ett nytt ärende i Flyspray och tilldelat ärendet till dig:',
+'moreinfonew' => 'Du kan få mer infomation om den här buggen på Flyspraysidan:',
+'newtaskcategory' => 'Det har öppnats ett nytt ärende i Flyspray med denna kategori',
+'categoryowner' => 'Du får detta meddelande för att du är angiven som kategoriägare.',
+'tasksummary' => 'Sammanfattning:',
+'newtaskadded' => 'Ditt nya ärende är nu tillagt.',
+'summaryanddetails' => 'Du måste fylla i både sammanfattning och beskrivning.',
+'goback' => 'GÃ¥ tillbaka.',
+'messagefrom' => 'Detta är ett meddelande från bugghanteringssystemet Flyspray på',
+'hasjustmodified' => 'har nyss ändrat ärendet som följer.',
+'changedfields' => 'Ändrade fält är markerade med stjärnor (**)',
+'moreinfomodify' => 'Du kan få mer information om detta ärende på adressen:',
+'nolongerassigned' => 'Ärendet som följer är inte tilldelat till dig längre. Det är nu tilldelat',
+'hasassigned' => 'har tilldelat dig följade Flysprayärende:',
+'taskupdated' => 'Ärendet är uppdaterat.',
+'hasclosedassigned' => 'har stängt följade Flysprayärende vilket du var tilldelad:',
+'unassigned' => 'Ej tilldelat',
+'hasclosed' => 'har stängt följande ärende.',
+'youonnotify' => 'Du får detta för att du är på aviseringlistan.',
+'taskclosedmsg' => 'Ärendet stängt.',
+'returntotask' => 'Gå tillbaka till ärendedetaljer',
+'backtoindex' => 'Gå tillbaka till ärendelistan',
+'noclosereason' => 'Du angav ingen anledning till att stänga ärendet.',
+'hasreopened' => 'har återöppnat följande Flysprayärende som du har stängt:',
+'taskreopenedmsg' => 'Ärendet har återöppnats.',
+'backtotask' => 'Gå tillbaka till ärendet.',
+'commentaddedmsg' => 'Kommentaren tillagd.',
+'commenttoassigned' => 'har lagt till en kommentar till ett ärende som du är tilldelad:',
+'commenttotask' => 'har lagt till följande kommentar till ärendet.',
+'nocommententered' => 'Du borde ange en kommentar innan du klickar på skickaknappen.',
+'fillinfields' => 'Du fyllde inte i alla fält.',
+'notcurrentpass' => 'Det är inte ditt nuvarande lösenord!',
+'passchanged' => 'Ditt lösenord är ändrat.',
+'closewindow' => 'Du kan nu stänga detta fönster.',
+'passnomatch' => 'Dina nya lösenord matchade inte varandra!',
+'usernametaken' => 'Användarnamnet används redan. Du måste välja ett nytt.',
+'newusercreated' => 'Ett nytt användarkonto har skapats.',
+'accountcreated' => 'Ditt konto är nu skapat.',
+'newuserwarning' => 'Ditt konto kan behöva godkännas av en administratör. Om du inte kann logga in så beror det antagligen på det.',
+'nomatchpass' => 'Lösenorden är inte lika.',
+'confirmwrong' => 'Fel bekräftelsekod!',
+'formnotcomplete' => 'Formuläret är inte fullständigt ifyllt.',
+'formnotnumeric' => 'Texten måste vara siffror!',
+'groupnametaken' => 'Gruppnamnet används redan.',
+'newgroupadded' => 'Ny grupp tillagd.',
+'optionssaved' => 'Inställningar för Flyspray sparade.',
+'hasuploaded' => 'har bifogat en fil till ett ärende som du är tilldelad:',
+'hasattached' => 'har bifogat en fil till följande ärende.',
+'fileuploaded' => 'Filen har bifogats.',
+'fileerror' => 'Kunde inte bifoga filen. Kanske behörigheterna för <i>attachments/</i>-katalogen är felaktiga.',
+'contactadmin' => 'Kontakta administratören för detta projekt.',
+'selectfileerror' => 'Du valde ingen fil.',
+'userupdated' => 'Användardetaljer har uppdaterats',
+'realandemail' => 'Du måste fylla i både fullständigt namn och epost!',
+'groupupdated' => 'Gruppinställningar uppdaterades.',
+'groupanddesc' => 'Du måste ange gruppnamn.',
+'fillallfields' => 'Fyll i alla fält.',
+'listPmustN' => '"Sorteringsordning" måste vara siffror.',
+'listupdated' => 'Listan är uppdaterad.',
+'listitemadded' => 'Ny post är tillagt till listan.',
+'relatedaddedmsg' => 'Besläktat ärende lagt till listan.',
+'relatederror' => 'Det ärendet finns redan i listan.',
+'relatedremoved' => 'Besläktat ärende borttaget från listan.',
+'notifyadded' => 'Användare tillagd till aviseringlistan.',
+'notifyerror' => 'Användaren finns redan i aviseringlistan för det ärendet.',
+'notifyremoved' => 'Användare borttagen från aviseringslistan.',
+'editcommentsaved' => 'Ändrad kommentar är sparad.',
+'commentdeletedmsg' => 'Kommentaren är borttagen.',
+'gotonewtask' => 'Gå till ärendet du just öppnat',
+'projectcreated' => 'Ditt nya projekt är skapat. Följ länken nedan för att ändra kategori-, operativsystem- och versionslistorna.',
+'customiseproject' => 'Skräddarsy projektet',
+'projectupdated' => 'Projektinställningar uppdaterade',
+'emptytitle' => 'Du lämnade projektnamnet tomt. Gå tillbaka och justera det.',
+'loginbelow' => 'Du kan nu försöka att logga in.',
+'attachmentdeletedmsg' => 'Den bifogade filen är borttagen',
+'reminderaddedmsg' => 'Din påminnelse är tillagd.',
+'reminderdeletedmsg' => 'Vald påminnelse är borttagen.',
+'flyspraytask' => 'Ärende i Flyspray',
+'fieldsmissing' => 'Några fält hade felaktiga eller inga data.',
+'relatedinvalid' => 'Det finns inget sådant ärende.',
+'relatedproject' => 'Ärendet är kopplat till ett annat projekt. Lägg till släktskap ändå?',
+'addanyway' => 'Lägg till ändå',
+'cancel' => 'Avbryt',
+'alreadyedited' => 'Ärendet ändrades av någon anna innan du hann spara. Vill du fortarande spara dina ändringar?',
+'saveanyway' => 'Spara ändringar ändå',
+'nouserselected' => 'Ingen användare vald. Välj åtminstonde en användare innan du fortsätter.',
+'groupswitchupdated' => 'Lyckades ändra användargrupper',
+'takenownershipmsg' => 'Ärendet är nu tilldelat dig.',
+'adminrequestmade' => 'Din efterfrågan har skickats till en projektadministratör.',
+'newdepnotify' => 'Ett nytt beroende har skapats för följande ärende:',
+'dependadded' => 'Beroende har skapats',
+'dependaddfailed' => 'Kunde inte skapa beroende. Kontrollera att ärendet finns och att det inte blir ett ömsesidigt beroende.',
+'depremovedmsg' => 'Beroende borttaget',
+'newdepis' => 'Det nya beroendet är',
+'magicurlsent' => 'Ett meddelande är skickat till din adress. Följ länken i meddelandet för att slutföra ärendet.',
+'changefspass' => 'Ändra lösenord i Flyspray',
+'magicurlmessage' => 'Följ denna länk för att ändra ditt lösenord i Flyspray:',
+'erroronform' => 'Det blev fel när formuläret skickades.',
+'addressused' => 'Den här adressen har använts för att registrera ett Flyspraykonto. Om inte det här meddelandet var väntat så kan du ignorera och ta bort det. Gå till denna adress för att slutföra din registrering:',
+'confirmcodeis' => 'Din bekräftelsekod är:',
+'codesent' => 'Vi har skickat din bekräftelsekod. Följ instruktionerna i meddelandet.',
+'codenotsent' => 'Kan inte skicka din kod. Försök igen senare.',
+'taskmadeprivatemsg' => 'Ärendet har gjorts privat.',
+'taskmadepublicmsg' => 'Ärendet har gjorts publikt igen',
+'realandnotify' => 'Du måste fylla i fullständigt namn och endera epostadress eller Jabber-id.',
+'pmreqdeniedmsg' => 'Begäran till projektansvarig nekades',
+'massopsuccess' => 'Alla åtgärder lyckades - där behörigheterna tillät.',
+'usernotexist' => 'Användaren finns inte i denna installation av Flyspray',
+'commentattachperms' => 'Du får kan inte ta bort kommentaren eftersom du inte har behörighet att ta bort bifogade filer.',
+'voterecorded' => 'Din röst är registrerad',
+'votefailed' => 'Din röst kunde inte registreras nu.',
+'createnewgroup' => 'Skapa ny grupp',
+'requiredfields' => 'Obligatoriska fält markeras med',
+'addthisgroup' => 'Skapa gruppen',
+'createnewproject' => 'Skapa ett nytt projekt',
+'addnewproject' => 'Nytt projekt',
+'htmlallowed' => 'HTML-kod är tillåten',
+'createthisproject' => 'Skapa projektet',
+'inlineimages' => 'Visa bifogade bilder i meddelandet',
+'createnewtask' => 'Skapa nytt ärende i projektet:',
+'addanother' => 'Lägg till ett till ärende efter detta',
+'addthistask' => 'Lägg till ärendet',
+'notifyme' => 'Avisera mig när ärendet ändras',
+'newtask' => 'Nytt ärende',
+'attachafile' => 'Bifoga en fil',
+'registernewuser' => 'Registrera ny användare',
+'none' => 'Ingen',
+'registeraccount' => 'Registrera kontot',
+'both' => 'BÃ¥da',
+'notifyfrom' => 'Avisering från ',
+'donotreply' => 'DETTA ÄR ETT AUTOMATGENERERAT MEDDELANDE. SVARA INTE PÅ DET.',
+'disclaimer' => 'Du får detta meddelande eftersom du har begärt det från bugghanteringssystemet Flyspray. Om du inte förväntade dig att få det eller inte vill få meddelanden i framtiden så kan du ändra dina aviseringsinställningar via adressen som visas ovan.',
+'userwho' => 'Användare som gjorde detta',
+'moreinfo' => 'Mer information kan du hitta på denna adress:',
+'newtaskopened' => 'Ett nytt ärende i Flyspray har öppnats. Detaljer nedan.',
+'notify.taskclosed' => 'Följande ärenden är nu stängda:',
+'notify.taskreopened' => 'Följande ärenden är nu återöppnade:',
+'newdep' => 'Följande ärenden har nu nytt beroende:',
+'notify.depremoved' => 'Följande ärende har ett beroende borttaget:',
+'olddepwas' => 'Det gamla beroendet var',
+'notify.commentadded' => 'Följande ärende har en ny kommentar:',
+'commentis' => 'Kommentaren finns nedan.',
+'newattachment' => 'En ny fil har bifogats till ärende:',
+'detailsbelow' => 'Detaljer nedan.',
+'notify.relatedadded' => 'Ett nytt besläktat ärende har lagts till detta ärende:',
+'relatedis' => 'Det besläktade ärendet är',
+'assignedtoyou' => 'Du har tilldelats ärende:',
+'takenownership' => 'äger nu följande ärende:',
+'requiresaction' => 'Ärende som måste åtgärdas av en projektadministratör:',
+'requiresactionnotify' => 'Ärendet måste åtgärdas av en projektadministratör',
+'pmdeny' => 'En projektadministratör har avslagit avvaktande begäran för ärende:',
+'pmdenynotify' => 'En projektadministratör har avslagit begäran',
+'fileaddedtoo' => 'Det fanns bifogade filer till denna kommentar.',
+'taskwatching' => 'Ärenden som du har aviseringar från',
+'isdepfor' => 'är ett nytt beroende av',
+'denialreason' => 'Anledning för avlsag',
+'taskchanged' => 'Ärendet har ändrats. Ändringarna visas nedan. För mer information om vad som ändrats, gå till adressen för ärendet och klicka på historikfliken.',
+'useraddedtoassignees' => 'En användare har lagt till sig till tilldelningslistan för detta ärende.',
+'removeddepis' => 'Det borttagna beroendet är',
+'isnodepfor' => 'är inte längre beroende av',
+'usergroups' => 'Användargrupper',
+'pmtoolbox' => 'Projektadministratörsverktyg',
+'groupmanage' => 'Gruppadministrering',
+'pendingrequests' => 'Avvaktande begäran',
+'reasongiven' => 'Angiven anledning',
+'nopendingreq' => 'Det finns inga avvaktande begäran.',
+'givereason' => 'Ange en anledning',
+'catlisted' => 'Redigera kategorier',
+'oslisted' => 'Redigera operativsystem',
+'verlisted' => 'Redigera versioner',
+'tasktypeed' => 'Redigera ärendetyper',
+'resed' => 'Redigera lösningar',
+'deny' => 'Neka',
+'notifiedwhen' => 'Aviserad när',
+'onlynewtasks' => 'Ärenden öppnas',
+'allevents' => 'Någon händelse i något ärende',
+'feeds' => 'Feeds',
+'feeddescription' => 'Beskrivning av feed',
+'feedimgurl' => 'Bild URL för feed (lämna tom för ingen bild)',
+'notifysubject' => 'Ärende för aviseringar',
+'notifysubjectinfo' => '(%p = projektnamn, %s = ärendesummering, %t = ärendenummer, %a = action, %u = användarnamn)',
+'priority6' => 'Blixtsnabbt',
+'priority5' => 'Omedelbar',
+'priority4' => 'Brådskande',
+'priority3' => 'Hög',
+'priority2' => 'Normal',
+'priority1' => 'LÃ¥g',
+'sendcode' => 'Skicka kod!',
+'entercode' => 'Ange den bekräftelsekod som du fick i ditt meddelande. Ange även önskat lösenord.',
+'confirmationcode' => 'Bekräftelsekod',
+'registererror' => 'Kontrollera så att du har skrivit in alla obligatoriska fält och att du har korrekta uppgifter för dina aviseringssätt.',
+'validusername' => '(endast bokstäver, siffror och - _ . tillåts)',
+'emailtaken' => 'Den epostadressen eller Jabber-ID används redan. Du måste välja något annat.',
+'note' => '<strong>Observera:</strong> Vi skickar en bekräftelsekod innan kontot skapas. Koden skickas till dig via det aviseringssätt du har valt ovan.<br />Om du anger felaktiga uppgifter så kommer du <strong>inte få din kod</strong>.',
+'changelog' => 'Ändringslogg',
+'changeloggen' => 'Ändringsloggsgenerator',
+'listfrom' => 'Visa ändringslogg från',
+'to' => 'till',
+'oldestfirst' => 'Äldst först',
+'recentfirst' => 'Senaste först',
+'severityrep' => 'Allvarsrapport',
+'totalopen' => 'Antal öppna ärenden',
+'age' => 'Ã…lder',
+'agerep' => 'Ã…ldersrapport',
+'eventsrep' => 'Händelserapport',
+'events' => 'Händelser',
+'Tasks' => 'Ärenden',
+'opened' => 'Öppnad',
+'edited' => 'Ändrad',
+'assigned' => 'Tilldelad',
+'within' => 'Inom',
+'pastday' => 'Senaste dagen',
+'pastweek' => 'Senaste veckan',
+'pastmonth' => 'Senaste månaden',
+'pastyear' => 'Senaste året',
+'nolimit' => 'Ingen begränsning',
+'from' => 'Från',
+'duein' => 'Färdig den',
+'selectfromdate' => 'Välj fråndatum',
+'selecttodate' => 'Välj tilldatum',
+'showvoters' => 'Visa/göm röstare',
+'roadmap' => 'Arbetsplan',
+'roadmapfor' => 'Arbetsplan för version',
+'tasks' => 'ärenden',
+'completed' => 'klar.',
+'opentasks' => 'öppna ärenden',
+'of' => '% av',
+'severity5' => 'Kritisk',
+'severity4' => 'Hög',
+'severity3' => 'Medium',
+'severity2' => 'LÃ¥g',
+'severity1' => 'Mycket låg',
+'Redirect' => 'Omdirigera',
+'redirectmsg' => 'Om din webbläsare inte stödjer omdirigeringar så klicka %sHÄR%s för att komma dit.',
+'allowclosedcomments' => 'Tillåt kommentarer på stängda ärenden',
+'comment' => 'Kommentar',
+'editowncomments' => 'Ändra egna kommentarer',
+'reopened' => 'Återöppnad',
+'loading' => 'Laddar...',
+'notifyown' => 'Avisera egna ändringar',
+'youremail' => 'Din epostadress',
+'thankyouforbug' => 'Tack för att rapporterar ditt problem. Du kan se ärendet och hur det fortlöper när som helst via denna länk:',
+'anonuser' => 'Anonym användare',
+'conflict' => 'Konflikt',
+'file' => 'Fil',
+'KiB' => 'KiB',
+'MiB' => 'MiB',
+'size' => 'Storlek',
+'projectgroup' => 'Projektgroup',
+'profile' => 'Profil:',
+'viewprofile' => 'Visa profile',
+'regdate' => 'Registrerad sedan',
+'tasksopened' => 'Ärende öppnat',
+'replyto' => 'Svar till',
+'notifytypes' => 'Aviseringssätt',
+'pm.taskchanged' => 'Ärende ändrat',
+'pm.taskreopened' => 'Ärende återöppnat',
+'pm.depadded' => 'Beroende tillagt',
+'pm.depremoved' => 'Beroende borttaget',
+'pmrequest' => 'Efterfrågan, projektadm.',
+'pmrequestdenied' => 'Efterfrågan, projektadm. avslagen',
+'newassignee' => 'Ny tilldelare',
+'revdepadded' => 'Beroende av tillagd',
+'revdepaddedremoved' => 'Beroende av borttagen',
+'assigneeadded' => 'Tilldelare tillagd',
+'addusergroup' => 'Lägg användare till denna grupp',
+'groupmembers' => 'Gruppmedlemmar',
+'deleteuser' => 'Ta bort användare',
+'userdeleted' => 'Användare borttagen',
+'autoassign' => 'Tilldela ärendet till kategoriägaren automatiskt',
+'ssl' => 'SSL',
+'updatewrong' => "Du har automatisk uppdateringskontroll aktiverat, men ett fel uppstod\n när uppdateringsservern skulle kontaktas. Endera så tillåter inte din server\n utgående trafik eller så blev det ett nätverksfel.\n Du kan gå till Flysprays webbsida för att se till att du kör senaste versionen.",
+'deleteproject' => 'Ta bort detta projekt och flytta allt innehåll till',
+'projectdeleted' => 'Lyckades ta bort projektet',
+'feedforall' => 'Feed för alla projekt',
+'usercreated' => 'Användare skapad',
+'created' => 'Skapad',
+'deleted' => 'Borttagen',
+'userid' => 'Användar-id',
+'editassignments' => 'Ändra tilldelningar',
+'preview' => 'Förhandsgranska',
+'anyprogress' => 'NÃ¥got framsteg',
+'tasksrelated' => 'Ärenden besläktade med detta',
+'duplicatetasks' => 'Dubletter av detta ärende',
+'databasemodfailed' => 'Kunde inte ändra databasen. Möjlig anledning är otillräckliga behörigheter.',
+'frequency' => 'Frekvens',
+'newuserregistered' => 'En ny användare är registrerad i Flyspray. Användarinformation:',
+'newuserregisterednotify' => 'En ny användare är registrerad',
+'notify_registration' => 'Avisera administratörer när en ny användare registreras',
+'textversion' => 'Textversion',
+'onlyprimary' => 'Ärenden som blockerar andra',
+'switch' => 'Byt',
+'max' => 'max.',
+'dates' => 'Datum',
+'selectduedatefrom' => 'Färdigdatum från',
+'selectduedateto' => 'till',
+'selectsincedatefrom' => 'Ändrad från',
+'selectsincedateto' => 'till',
+'selectdate' => 'Välj datum',
+'selectopenedfrom' => 'Öppnad från',
+'selectopenedto' => 'till',
+'selectclosedfrom' => 'Stängd från',
+'selectclosedto' => 'till',
+'startat' => 'Börja den',
+'hasattachment' => 'Har bifogade filer',
+'private' => 'Privat',
+'watching' => 'Aviseras',
+'alreadyvotedthistask' => 'du har röstat',
+'alreadyvotedthisday' => 'redan röstat idag',
+'visibility' => 'Synlighet',
+'public' => 'Publik',
+'leaveemptyauto' => 'Lämna lösenord tomt om du vill ha ett automatgenererat lösenord.',
+'novalidemail' => 'Du angav inte en giltig epostadress.',
+'novalidjabber' => 'Du angav inte en giltig Jabberadress.',
+'missingrequired' => 'Du fyllde inte i alla obligatoriska fält.',
+'entervalidusername' => 'Skriv in ett giltigt användarnamn och fullständigt namn.',
+'couldnotaddusernotif' => 'Kunde inte lägga in denna användare i aviseringslistan.',
+'defaulttask' => 'Förvald ärendebeskrivning',
+'all' => 'alla',
+'events.useraddedtoassignees'=> 'Användare satt som tilldelare',
+'vote(s)' => 'röst(er)',
+'eventlog' => 'Händelselogg',
+'assignmentchanged' => 'Tilldelning ändrad',
+'detailedinfo' => 'Detaljerad information',
+'All' => 'Alla',
+'tasksireported' => 'Ärenden jag rapporterat',
+'recentlyopened' => 'Nyligen öppnade',
+'stats' => 'Statistik',
+'totaltasks' => 'totalt antal ärenden',
+'mostwanted' => 'Mest efterfrågade ärenden',
+'defaultentry' => 'Förvald startsida',
+'toplevel' => 'Top level view',
+'overview' => 'Översikt',
+'error#' => 'Felnr.',
+'error1' => 'Du har inte tillräckliga behörigheter för att se den bifogade filen.',
+'error3' => 'Upprepad händelse, styr om till huvudsidan.',
+'error4' => 'Du har inga administratörsbehörigheter.',
+'error5' => 'Användaren finns inte i denna Flyspray.',
+'error6' => 'Felaktig administratörsarea.',
+'error7' => 'Inloggning misslyckades (fel användarnamn eller lösenord)!',
+'error71' => 'Användarkontot är låst i %d minuter på grund av för många misslyckade inloggningsförsök!',
+'error8' => 'Du måste ange både användarnamn och lösenord.',
+'error9' => 'Ärendet finns inte eller så har du inte behörighet att se det.',
+'error10' => 'Ärendet finns inte eller så har du inte behörighet att se det.',
+'error101' => 'Du har inga rättigheter av visa detta ärende.',
+'error102' => 'Du har inga rättigheter av visa detta ärende, försök att logga in.',
+'error11' => 'Ingen behörighet att ändra denna kommentar.',
+'error12' => 'Inte en giltig kod! Är du säker på att det är den du fick från registreringsmeddelandet?',
+'error13' => 'Anonyma användare har ingen profil.',
+'error14' => 'Du har inte behörighet att skapa en ny grupp.',
+'error15' => 'Du har inte behörighet att öppna ett ärende.',
+'error16' => 'Du är inte projektadministratör.',
+'error17' => 'Felatkig projektadministratörsarea.',
+'error18' => 'Felaktig URL.',
+'error19' => 'Den användaren finns inte i denna Flyspray.',
+'error20' => 'Felaktig databasändring.',
+'error21' => 'Ett eller flera epostmeddelanden kunde inte skickas. Kontrollera dina inställningar.',
+'error22' => 'Nyregistreringar av användare tillåts inte.',
+'error23' => 'Användare eller grupp får inte logga in.',
+'error24' => 'Varken dot-programmet eller en dot-server har angivits i inställningarna.',
+'error25' => 'Arbetsplaner är bara tillgängliga från ett specifikt projekt.',
+'done' => 'klar',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => 'Projektet kunde inte tas bort.',
+'GMT' => 'GMT',
+'timezone' => 'Tidzon',
+'accept' => 'Acceptera',
+'reasonfordeinal' => 'Anledning för avslag',
+'pruneclosedlinks' => 'Beskär kopplingar till stängda länkar',
+'pruneclosedtasks' => 'Beskär kopplingar till stängda ärenden',
+'pagegenerated' => 'Sida och bild genererad på %d sekunder.',
+'pruninglevel' => 'Beskärningsnivå',
+'lastuser' => 'Sista användaren kan inte tas bort.',
+'allprivate' => 'Alla projekt är privata.',
+'deletegroup' => 'Ta bort denna grupp och flytta användare till',
+'parent' => 'Förälder',
+'ordertip' => 'Ordningen som de kommer visas i listan',
+'showtip' => 'Visa denna i listan',
+'deletetip' => 'Ta bort denna från listan',
+'del' => 'bort',
+'request1' => 'En ärendestängning är begärd.',
+'request2' => 'En återöppning av ärendet är begärd.',
+'allpriorities' => 'Alla prioriteringar',
+'noroadmap' => 'Ingen arbetsplan finns (ingen projektspecifik "framtids"-version finns).',
+'expand' => 'Fäll ut',
+'collapse' => 'Fäll in',
+'expandall' => 'Fäll ut alla',
+'collapseall' => 'Fäll in alla',
+'minpwsize' => 'Minsta lösenordslängd är 5 tecken',
+'passwordtoosmall' => 'Lösenordslängden är för kort.',
+'accountwaslocked' => 'Ditt användarkonto har blivit låst på grund av för många misslyckade inloggningsförsök.',
+'failedattempts' => 'Det var %d misslyckade inloggningsförsök.',
+'groupnotexist' => 'Den valda gruppen finns inte i detta projekt.',
+'searchindetails' => 'Sök i beskrivning',
+);
+
+//
+// Translated by Mikael Silfver, mikael dot silfver gmail com.
+//
+?>
diff --git a/lang/zh_cn.php b/lang/zh_cn.php
new file mode 100644
index 0000000..6c6aa62
--- /dev/null
+++ b/lang/zh_cn.php
@@ -0,0 +1,1080 @@
+<?php
+//
+// This file is auto generated with langedit.php
+// Characters are UTF-8 encoded
+//
+// Be careful when editing this file manually, some text editors
+// may convert text to UCS-2 or similar (16-bit) which is NOT
+// readable by the PHP parser
+//
+// Furthermore, nothing else than the language array is saved
+// when using the langedit.php editor!
+//
+$translation = array(
+'edituser' => '编辑用户',
+'accountenabled' => '账户å¯ç”¨',
+'editallusers' => '查看所有用户',
+'username' => '用户å',
+'usersupdated' => '用户æˆåŠŸåœ°æ›´æ–°',
+'realname' => '真实姓å',
+'emailaddress' => '邮件地å€',
+'jabberid' => 'Jabber ID',
+'profileimage' => '资料图片',
+'notifytype' => '通知类型',
+'group' => '群组',
+'enableaccounts' => 'å¯ç”¨å¸æˆ·',
+'disableaccounts' => 'ç¦ç”¨å¸æˆ·',
+'deleteaccounts' => '删除账户',
+'updatedetails' => '更新详细信æ¯',
+'setglobally' => '这个常规设置被设为全局的了.',
+'usergroupmanage' => '用户和群组管ç†',
+'newuser' => '注册新用户',
+'newuserbulk' => '注册多个新用户',
+'bulkuserstoadd' => '新用户列表',
+'optionsforallusers' => '所有新用户选项',
+'newgroup' => '创建群组',
+'yes' => '是',
+'no' => 'å¦',
+'editgroup' => '编辑群组',
+'groupname' => '群组å',
+'description' => 'æè¿°',
+'admin' => '管ç†ç¾¤ç»„',
+'opennewtasks' => '打开新任务',
+'modifytasks' => '修改任务',
+'addcomments' => '添加评论',
+'attachfiles' => '附件',
+'vote' => '投票',
+'groupenabled' => 'æˆå‘˜å¯ä»¥ç™»å½•',
+'groupopen' => 'å…许用户登录',
+'tasktypelist' => '任务类型列表',
+'categorylist' => '模å—列表',
+'oslist' => 'æ“作系统列表',
+'resolutionlist' => '解决方å¼åˆ—表',
+'versionlist' => '版本列表',
+'severitylist' => '问题严é‡æ€§åˆ—表',
+'listnote' => '注æ„: 在编辑模å¼ä¸‹ï¼Œå–消 "显示"å¤é€‰æ¡†ä¼šæ”¹å˜ä¸€äº›ä»»åŠ¡. 修改 "å称"字段会用这个å称修改全部任务.',
+'name' => 'å称',
+'order' => '顺åº',
+'back' => 'åŽé€€',
+'text' => '文本',
+'highlight' => '高亮',
+'show' => '显示',
+'owner' => '主管',
+'selectowner' => '选择所有者',
+'update' => 'æ›´æ–°',
+'addnew' => '增加',
+'flysprayprefs' => 'Flyspray 常规设置',
+'projecttitle' => '项目标题',
+'baseurl' => 'Base URL for 这个安装',
+'replyaddress' => '通知的回å¤ç”µå­é‚®ä»¶åœ°å€',
+'themestyle' => '主题 / æ ·å¼',
+'customstyle' => '自定义',
+'language' => '语言',
+'anonview' => 'å…许匿å用户查看任务',
+'allowanon' => 'å…许匿å用户打开新任务',
+'never' => '从ä¸',
+'anonymously' => '匿å',
+'afterregister' => 'åªæœ‰åœ¨æ³¨å†Œä¹‹åŽ',
+'spamproof' => 'å¯ç”¨æ–°ç”¨æˆ·æ³¨å†Œç¡®è®¤ç ',
+'anongroup' => '新用户注册的群组',
+'groupassigned' => '这些群组的æˆå‘˜èƒ½è¢«æŒ‡æ´¾ä»»åŠ¡',
+'forcenotify' => '任务通知方å¼',
+'neversend' => 'ä¸å‘é€',
+'userchoose' => '让用户自己选择',
+'email' => '电å­é‚®ä»¶',
+'jabber' => 'Jabber',
+'defaultcatowner' => '默认的模å—主管',
+'noone' => 'ä¸è¦',
+'jabbernotify' => 'Jabber通知',
+'jabberserver' => 'æœåŠ¡å™¨',
+'jabberport' => '端å£',
+'jabberuser' => '账户用户å',
+'jabberpass' => '账户å£ä»¤',
+'saveoptions' => 'ä¿å­˜é€‰é¡¹',
+'editcomment' => '编辑评论',
+'commentby' => '评论人',
+'saveeditedcomment' => 'ä¿å­˜è¯„论',
+'projectprefs' => '项目常规设置',
+'pagetitle' => '页é¢æ ‡é¢˜',
+'defaultproject' => '默认的项目',
+'projectlists' => '项目列表',
+'showlogo' => '显示标题图åƒ',
+'showgravatars' => '显示头åƒ',
+'emailNoHTML' => '没有HTML电å­é‚®ä»¶',
+'intromessage' => '介ç»æ€§ä¿¡æ¯',
+'active' => '活动',
+'inactive' => 'ä¸æ´»åŠ¨',
+'isactive' => '项目是活动的',
+'showinactive' => '显示ä¸æ´»åŠ¨é¡¹ç›®',
+'hideinactive' => 'éšè—ä¸æ´»åŠ¨é¡¹ç›®',
+'createproject' => '创建一个新项目',
+'nopermission' => '您没有æƒé™ä½¿ç”¨è¿™ä¸ªé¡µé¢.',
+'listordertip' => '这些æ¡ç›®çš„顺åºå°†æ˜¾ç¤ºåœ¨åˆ—表里',
+'listshowtip' => '在列表里显示这个æ¡ç›® ',
+'categoryownertip' => '当这个模å—中打开一个任务时,这个人将收到通知',
+'categoryparenttip' => '这个新模å—的上级模å—将会é™çº§',
+'notsubcategory' => 'æ—  (顶级模å—)',
+'showinlineimages' => '嵌入显示图åƒé™„件',
+'dateformat' => '日期格å¼',
+'dateformat_extended' => '详细日期格å¼',
+'cache_feeds' => 'èšåˆç¼“å­˜',
+'no_cache' => 'ä¸ç¼“å­˜',
+'cache_disk' => '缓存到ç£ç›˜',
+'cache_db' => '缓存到数æ®åº“',
+'subcategoryof' => 'å­æ¨¡å—',
+'visiblecolumns' => '任务列表中显示的列',
+'visiblefields' => '当添加/编辑/查看任务时',
+'tense' => '时间',
+'listtensetip' => '过去的, 现在的或者将æ¥çš„',
+'past' => '过去的',
+'present' => '现在的',
+'future' => 'å°†æ¥çš„',
+'oldpass' => '旧的å£ä»¤',
+'nooldpass' => '没有设置旧的å£ä»¤',
+'oldpasswrong' => '旧的å£ä»¤æ˜¯é”™çš„',
+'changepass' => '修改å£ä»¤',
+'confirmpass' => '确认å£ä»¤',
+'projectmanager' => '项目ç»ç†',
+'viewtasks' => '查看任务',
+'modifyowntasks' => '修改自己主管的任务',
+'modifyalltasks' => '修改ä¸å±žäºŽç”¨æˆ·ä¸»ç®¡çš„任务',
+'viewcomments' => '查看评论',
+'editcomments' => '编辑评论',
+'deletecomments' => '删除评论',
+'viewattachments' => '查看附件',
+'createattachments' => '创建附件',
+'deleteattachments' => '删除附件',
+'viewhistory' => '查看历å²',
+'closeowntasks' => '关闭主管任务',
+'closeothertasks' => '任务ä¸æ˜¯ç”¨æˆ·ä¸»ç®¡çš„关闭',
+'assigntoself' => '如果没有被指派,指派任务给自己',
+'assignotherstoself' => '指派其他人的任务到自己',
+'viewreports' => '查看事件日志',
+'othersview' => 'å…许任何人查看这个项目的任务',
+'othersviewroadmap' => 'å…许任何人查看这个项目的视图',
+'usersandgroups' => '用户群组',
+'globalgroup' => '全局群组',
+'globalgroups' => '全局群组',
+'defaultglobalgroup' => '新用户默认的全局群组',
+'addtogroup' => '增加到群组',
+'moveuserstogroup' => '移动用户到群组',
+'nogroup' => '无群组 - 从项目移除',
+'eventdesc' => '事件æè¿°',
+'requestedby' => '申请人',
+'daterequested' => '申请日期',
+'closetask' => '关闭任务',
+'reopentask' => 'é‡æ–°æ‰“开任务',
+'applymember' => '申请项目æˆå‘˜èµ„æ ¼',
+'forcurrentproj' => '当å‰çš„项目',
+'lostpw' => '找回丢失的å£ä»¤',
+'lostpwexplain' => '请输入您的用户å,我们会å‘é€ä¸€ä¸ªä¿®æ”¹æ‚¨å£ä»¤çš„链接到您的个人资料中填写的通知邮件地å€.',
+'sendlink' => 'å‘é€é“¾æŽ¥',
+'savenewpass' => 'ä¿å­˜æ–°å£ä»¤',
+'anonreg' => 'å…许新用户注册',
+'allowanonopentask' => 'å…许匿å用户打开任务',
+'editglobalgroup' => '编辑全局群组',
+'editgroupforproj' => '编辑项目的群组',
+'notshownforadmin' => '管ç†ç¾¤ç»„çš„æƒé™æ²¡æœ‰æ˜¾ç¤º.您ä¸éœ€è¦ç¼–辑它们.',
+'general' => '常用',
+'userregistration' => '用户注册',
+'notifications' => '通知',
+'resetoptions' => 'é‡ç½®é€‰é¡¹',
+'preferences' => '常规设置',
+'tasktypes' => '任务类型',
+'resolutions' => '解决方å¼',
+'categories' => '模å—定义',
+'categoriesglobal' => '全局模å—',
+'categoriesproject' => '项目模å—',
+'categoriestarget' => '目标模å—',
+'operatingsystems' => 'æ“作系统',
+'versions' => '版本定义',
+'admintoolboxlong' => '管ç†å‘˜å·¥å…·ç®±',
+'newproject' => '新建项目',
+'delete' => '删除',
+'link' => '链接',
+'referencelinks' => 'å‚考链接',
+'listdeletetip' => '从列表中删除这个æ¡ç›®',
+'lookandfeel' => '外观',
+'globaltheme' => '全局主题',
+'emailnotify' => '电å­é‚®ä»¶ 通知',
+'fromaddress' => 'å‘件地å€',
+'smtpserver' => 'SMTP æœåŠ¡å™¨',
+'smtpuser' => 'SMTP 用户å',
+'smtppass' => 'SMTP å£ä»¤',
+'addrewrite' => '使用地å€é‡å†™',
+'usereminderdaemon' => 'å¯ç”¨åŽå°æ醒æœåŠ¡',
+'tasksperpage' => 'æ¯é¡µä»»åŠ¡åˆ—表显示的任务数',
+'addtoassignees' => '增加自己为被指派人',
+'taskstatuses' => '任务状æ€',
+'canvote' => 'å¯ä»¥å¯¹ä»»åŠ¡æŠ•ç¥¨',
+'loginsuccessful' => '登录æˆåŠŸ.',
+'youareloggedout' => '您已ç»æ³¨é”€äº†.',
+'waitwhiletransfer' => '请ç¨ä¾¯...',
+'clicknowait' => '如果您ä¸æƒ³ç­‰å¾…,请点这里.',
+'accountdisabled' => '您的账户被ç¦ç”¨äº†. 请è”系管ç†å‘˜.',
+'task' => '任务',
+'edittask' => '编辑这个任务',
+'openedby' => '任务创建者',
+'editedby' => '最åŽä¿®æ”¹äººï¼š',
+'tasktype' => '任务类型',
+'category' => '模å—',
+'status' => '状æ€',
+'assignedto' => '指派到',
+'operatingsystem' => 'æ“作系统',
+'severity' => '严é‡æ€§',
+'reportedversion' => '报告版本',
+'dueinversion' => '计划完æˆç‰ˆæœ¬',
+'defaultdueinversion' => '默认新任务计划完æˆç‰ˆæœ¬',
+'undecided' => '未决定',
+'percentcomplete' => '完æˆç™¾åˆ†æ¯”',
+'details' => '详细信æ¯',
+'savedetails' => 'ä¿å­˜è¯¦ç»†ä¿¡æ¯',
+'canceledit' => 'å–消编辑',
+'anonymous' => '匿åæ交者',
+'complete' => '完æˆ',
+'closedby' => '已关闭,æ“作者:',
+'reasonforclosing' => '关闭的ç†ç”±:',
+'reopenthistask' => 'é‡æ–°æ‰“开这个任务',
+'comments' => '评论数é‡',
+'attachments' => '附件数é‡',
+'relatedtasks' => 'å…³è”任务',
+'edit' => '编辑',
+'addcomment' => '增加评论',
+'fileuploadedby' => '文件上传,由',
+'uploadafile' => '附加一个文件',
+'addalink' => '添加一个链接',
+'addanotherlink' => '添加å¦ä¸€ä¸ªé“¾æŽ¥',
+'uploadnow' => '现在上传!',
+'thesearerelated' => '这些任务被关è”到这个任务了',
+'remove' => '移除',
+'addnewrelated' => '增加一个新的关è”任务',
+'add' => '增加!',
+'otherrelated' => '这个任务关è”的其他的任务',
+'receivenotify' => '当这个任务被修改时,下列用户将收到详细的通知.',
+'addusertolist' => '增加用户到通知列表',
+'addtolist' => '增加到列表',
+'addmyself' => '把我加入到这个列表中',
+'removemyself' => '从这个列表中移除我自己',
+'theseusersnotify' => '当这个任务被修改时,下é¢çš„用户将收到详细的通知.',
+'attachedtoproject' => '属于项目',
+'reminders' => 'æ醒',
+'system' => '系统',
+'systemvalues' => '系统列表值范围',
+'projectvalues' => '项目的具体列表值',
+'remindthisuser' => 'æ醒用户',
+'thisoften' => 'æ¯éš”',
+'startafter' => 'å¯åŠ¨æ醒å‰ç­‰å¾…',
+'hour' => 'å°æ—¶',
+'hours' => 'å°æ—¶',
+'day' => '天',
+'days' => '天',
+'week' => '星期',
+'weeks' => '星期',
+'addreminder' => '增加æ醒',
+'defaultreminder' => '下é¢æ˜¯ Flyspray 的任务æ醒:',
+'message' => '消æ¯',
+'closed' => '已解决的',
+'filename' => '文件å称:',
+'date' => '日期',
+'filesize' => '文件大å°:',
+'closurecomment' => '其它关于关闭的评论:',
+'history' => '历å²',
+'nohistory' => '没有历å².',
+'eventdate' => '日期',
+'user' => '用户',
+'event' => '事件',
+'fieldchanged' => '字段å‘生å˜åŠ¨',
+'taskopened' => '任务被打开了',
+'taskreopened' => '任务被é‡æ–°æ‰“开了',
+'taskclosed' => '任务被关闭了',
+'commentadded' => '增加了评论',
+'commentedited' => '修改了评论',
+'commentdeleted' => '删除了评论',
+'attachmentadded' => '附件增加了',
+'attachmentdeleted' => '附件删除了',
+'taskedited' => '任务详细信æ¯ä¿®æ”¹äº†',
+'notificationadded' => '用户增加到通知列表了',
+'notificationdeleted' => '用户从通知列表中移除了',
+'relatedadded' => 'å…³è”任务增加了',
+'relateddeleted' => 'å…³è”任务移除了',
+'taskassigned' => '任务被指派到',
+'taskreassigned' => '任务被é‡æ–°æŒ‡æ´¾åˆ°',
+'assignmentremoved' => '指派被移除了',
+'summary' => '标题',
+'addedasrelated' => '任务增加到其关è”列表了:',
+'deletedasrelated' => '任务从其关è”列表中移除了',
+'reminderadded' => 'æ醒增加了',
+'reminderdeleted' => 'æ醒删除了',
+'priority' => '优先级',
+'previousvalue' => 'å‰ä¸€ä¸ªå€¼',
+'newvalue' => '新值',
+'selectareason' => '选择一个ç†ç”±',
+'assigntome' => '指派到我',
+'reopenrequest' => '请求é‡æ–°æ‰“å¼€',
+'requestclose' => '请求关闭',
+'ownershiptaken' => '用户æˆä¸ºä¸»ç®¡',
+'closerequestmade' => '申请任务关闭',
+'reopenrequestmade' => '申请任务被é‡æ–°æ‰“å¼€',
+'taskdependson' => '这个任务ä¾èµ–',
+'taskdependsontask' => '任务å–决于',
+'taskdependsontasks' => '任务å–决于',
+'taskblock' => '这个任务被阻止关闭',
+'taskblocks' => '这个任务被阻止关闭',
+'depadded' => 'ä¾èµ–增加的',
+'depaddedother' => '这个任务增加为ä¾èµ–',
+'depremoved' => 'ä¾èµ–移除',
+'depremovedother' => '这个任务从其他的任务ä¾èµ–列表中移除了',
+'showdetailserror' => '任务ä¸å­˜åœ¨, 或者您没有查看它的æƒé™ã€‚',
+'makeprivate' => '置为ä¸å…¬å¼€',
+'makepublic' => '置为公开',
+'taskmadeprivate' => '任务æˆä¸ºä¸å…¬å¼€çš„了',
+'taskmadepublic' => '移除了ç§æœ‰å±žæ€§ - 任务æˆä¸ºå…¬å¼€çš„了',
+'confirmdeletecomment' => '真的删除这个评论? %s',
+'attachementswilldeleted' => '所有附件也将被删除!',
+'confirmdeleteattach' => '真的删除这个附件?',
+'selectedhistory' => '正在显示选定的历å²è¯¦ç»†ä¿¡æ¯',
+'showallhistory' => 'é‡æ–°æ˜¾ç¤ºå…¨éƒ¨åŽ†å²æ ‡ç­¾',
+'hidethis' => 'é‡æ–°éšè—这个区域',
+'mark100' => '标记任务为100%完æˆ',
+'watchtask' => '关注任务',
+'stopwatching' => 'åœæ­¢å…³æ³¨',
+'commentlink' => '链接到这个评论',
+'submitreq' => 'æ交请求',
+'reasonforreq' => '这个请求的ç†ç”±',
+'pmreqdenied' => '项目ç»ç†æ‹’ç»äº†è¯·æ±‚',
+'taskpendingreq' => '项目ç»ç†æ“作被挂起. 进一步详细信æ¯è¯·æŸ¥çœ‹åŽ†å²é¡µ.',
+'previoustask' => 'å‰ä¸€ä¸ªä»»åŠ¡',
+'nexttask' => '下一个任务',
+'duedate' => '计划完æˆæ—¥æœŸ',
+'attachnoperms' => '这个评论有附件, 但是您没有æƒé™æŸ¥çœ‹',
+'linknoperms' => '这个评论有链接, 但是您没有æƒé™æŸ¥çœ‹',
+'open' => '未解决的',
+'depgraph' => '查看ä¾èµ–图表',
+'reset' => 'é‡ç½®',
+'selectusers' => '选择用户...',
+'addmetoassignees' => '把我加入被指派人',
+'addedtoassignees' => '用户增加到被指派人列表',
+'dependencygraph' => 'ä¾èµ–图表',
+'attachanotherfile' => '附加å¦ä¸€ä¸ª 文件',
+'OK' => '好',
+'addvote' => '增加投票',
+'disable_lostpw' => 'ç¦æ­¢ä¸¢å¤±å¯†ç æ£€ç´¢',
+'disable_changepw' => 'ç¦æ­¢å¯†ç åˆ›å»º/编辑',
+'notifyfromfs' => 'æ¥è‡ªFlyspray的通知',
+'autogenerated' => '这个是自动生æˆçš„消æ¯,请勿回å¤',
+'forward' => 'å‘å‰',
+'previous' => 'å‰ä¸€ä¸ª',
+'next' => '下一个',
+'first' => '第一个',
+'last' => '最åŽä¸€ä¸ª',
+'page' => '第 %d 页,共 %d 页',
+'search' => 'æœç´¢',
+'alltasktypes' => '全部任务类型',
+'allseverities' => '全部严é‡æ€§çº§åˆ«',
+'alldevelopers' => '全部开å‘人员',
+'notyetassigned' => '尚未被指派',
+'allcategories' => '全部模å—',
+'allstatuses' => '全部状æ€',
+'allopentasks' => '全部打开的任务',
+'sortthiscolumn' => '以此列排åº',
+'id' => 'ID',
+'project' => '项目',
+'dateopened' => '创建时间',
+'progress' => '进度',
+'searchthisproject' => '在项目中æœç´¢',
+'dueanyversion' => '延期到任何版本',
+'anyversion' => '任何版本中的报告',
+'dueversion' => '计划完æˆç‰ˆæœ¬',
+'lastedit' => '最åŽä¿®æ”¹æ—¶é—´',
+'os' => 'æ“作系统',
+'reportedin' => '报告',
+'taskrange' => '正在显示 任务 %d - %d (共%d 个)',
+'noresults' => '您的æœç´¢æ²¡æœ‰è¿”回结果.',
+'takeaction' => 'æ“作',
+'watchtasks' => '关注选定的任务',
+'stopwatchingtasks' => 'åœæ­¢å…³æ³¨é€‰å®šçš„任务',
+'assigntaskstome' => '将选定的任务指派给我',
+'dueby' => '延期,由',
+'dueanytime' => '延期任æ„事件',
+'selectduedate' => '选择延期日期',
+'toggleselected' => 'å选选定的',
+'due' => '延期',
+'assignedtome' => '指派到我的',
+'tasklist' => '任务列表',
+'dateclosed' => '关闭的日期',
+'advanced' => '高级',
+'searchcomments' => '在评论中æœç´¢',
+'searchforall' => 'æœç´¢å…¨éƒ¨è¯',
+'anonusers' => '匿å用户',
+'miscellaneous' => 'æ‚项',
+'users' => '用户',
+'taskproperties' => '任务属性',
+'selectsincedate' => '选择å‘生å˜åŠ¨è‡ªä»Ž',
+'changedsince' => 'å‘生å˜åŠ¨è‡ªä»Ž',
+'updatefs' => '请更新Flyspray.',
+'currentversion' => '您的当å‰çš„版本是',
+'latestversion' => ',最新的版本是',
+'hidemessage' => '(以åŽæ醒我)',
+'saveas' => 'ä¿å­˜æœç´¢ä¸º',
+'nosearches' => '没有ä¿å­˜çš„æœç´¢',
+'saving' => 'ä¿å­˜...',
+'votes' => '投票',
+'tovote' => '投票',
+'allclosedtasks' => '全部关闭的任务',
+'password' => 'å£ä»¤',
+'login' => '登录!',
+'rememberme' => 'è®°ä½æˆ‘',
+'lostpassword' => '丢失了å£ä»¤',
+'lostpwforfs' => '丢失了Flysprayçš„å£ä»¤',
+'lostpwmsg1' => "您好。\n\n我丢失了 Flyspray å£ä»¤ ",
+'lostpwmsg2' => ", 请æ供我一个新å£ä»¤.\n\nUsername: ",
+'regards' => "\n\n敬礼,",
+'yourusername' => ' 您的用户å ',
+'locale' => 'zh-CN',
+'filenotexist' => '文件ä¸å­˜åœ¨, 或者您没有æƒé™è®¿é—®å®ƒ.',
+'showtask' => '显示任务',
+'now' => '现在',
+'go' => '开始!',
+'opentaskanon' => '匿å打开一个新任务',
+'register' => '注册',
+'addnewtask' => '增加新任务',
+'reports' => '事件日志',
+'editmydetails' => '编辑我的详细信æ¯',
+'logout' => '注销',
+'disabledaccount' => '您的账户被ç¦ç”¨äº†!<br>å³åˆ»å°†æ‚¨æ³¨é”€...',
+'poweredby' => 'Powered by Flyspray',
+'sponsoredby' => 'Flyspray å‘起者',
+'projects' => '项目',
+'allprojects' => '全部项目',
+'selectproject' => '项目:',
+'tasksall' => '全部任务',
+'tasksassigned' => '被指派到我的任务',
+'tasksreported' => '我报告的任务',
+'taskswatched' => '我关注的 ',
+'mysearch' => '我的æœç´¢',
+'admintoolbox' => '管ç†å‘˜å·¥å…·ç®±',
+'manageproject' => '管ç†é¡¹ç›®',
+'permissions' => '查看æƒé™',
+'hide' => 'éšè—',
+'pendingreq' => '项目ç»ç†è¯·æ±‚正在等待',
+'errorpage' => "Flyspray ä¸èƒ½æä¾›æ‚¨ç”³è¯·çš„é¡µé¢ .\n 也许您申请的任务ä¸å­˜åœ¨, 或者您\n 没有æƒé™æŸ¥çœ‹æ‚¨æƒ³è¦çš„页é¢.<br /><br />\n 您å¯èƒ½åœ¨è¯•å›¾è®¿é—®æ•°æ®åº“. 果真如此, 想想您åšçš„æ“作. 返回之åŽ, 请ä¸è¦å†åšäº†!",
+'permissionsforproject' => 'æƒé™ï¼š',
+'switchto' => '切æ¢åˆ°',
+'lastsearch' => '上次æœç´¢',
+'modify' => '修改',
+'noticefrom' => 'æ醒,æ¥è‡ªï¼š',
+'hasopened' => '打开了一个新的任务, 并把它指派给您:',
+'moreinfonew' => '您在下é¢é¡µé¢æ‰¾åˆ°å…³äºŽè¿™ä¸ªé”™è¯¯çš„更多信æ¯:',
+'newtaskcategory' => '这个模å—中有一个新任务被打开了',
+'categoryowner' => '因为您是这个模å—的主管,所以您能收到这个消æ¯.',
+'tasksummary' => '任务标题:',
+'newtaskadded' => '您有一个新任务.',
+'summaryanddetails' => '标题和详细信æ¯æ‚¨éƒ½éœ€è¦å¡«å†™.',
+'summaryrequired' => '你需è¦å¡«å†™ä¸€ä¸ªç®€çŸ­çš„任务总结',
+'goback' => '返回.',
+'messagefrom' => '这个是一个æ¥è‡ªFlyspray 错误跟踪系统的消æ¯',
+'hasjustmodified' => '刚刚修改了下é¢çš„任务.',
+'changedfields' => 'å‘生了å˜åŠ¨çš„字段由星å·(**)å‰ç¼€',
+'moreinfomodify' => '您能在下é¢çš„URL得到更多关于这个任务的信æ¯:',
+'nolongerassigned' => '下é¢çš„任务ä¸å†è¢«æŒ‡æ´¾åˆ°æ‚¨äº†,它现在被指派到',
+'hasassigned' => 'å·²ç»æŒ‡æ´¾ç»™æ‚¨ä¸‹é¢çš„Flyspray 任务:',
+'taskupdated' => '任务被更新了.',
+'tasksupdated' => '任务已ç»æ›´æ–°äº†.',
+'hasclosedassigned' => 'å·²ç»å…³é—­äº†ä¸‹é¢è¿™äº›æŒ‡æ´¾åˆ°æ‚¨çš„任务:',
+'unassigned' => '未指派的',
+'hasclosed' => 'å·²ç»å…³é—­äº†ä¸‹é¢è¿™äº›ä»»åŠ¡.',
+'youonnotify' => '您正在接收这个,因为您在通知列表中.',
+'taskclosedmsg' => '任务已ç»è¢«å…³é—­äº†.',
+'returntotask' => '返回到任务详细信æ¯',
+'backtoindex' => '回到任务列表',
+'noclosereason' => '您需è¦é€‰æ‹©ä¸€ä¸ªç†ç”±æ¥å…³é—­è¿™ä¸ªä»»åŠ¡.',
+'hasreopened' => 'å·²ç»é‡æ–°æ‰“开了下é¢è¿™äº›è¢«æ‚¨å…³é—­çš„Flyspray 任务:',
+'taskreopenedmsg' => '任务被é‡æ–°æ‰“开了.',
+'backtotask' => '回到任务.',
+'commentaddedmsg' => '已增加评论。',
+'commenttoassigned' => '一个指派到您的任务增加了一个评论:',
+'commenttotask' => 'å·²ç»ç»™è¿™ä¸ªä»»åŠ¡äº†å¢žåŠ äº†ä¸‹é¢çš„评论',
+'nocommententered' => '在æ交之å‰ï¼Œæ‚¨éœ€è¦å¡«å†™è¯„论内容',
+'fillinfields' => '您没有填写全部的信æ¯',
+'notcurrentpass' => 'è¿™ä¸æ˜¯æ‚¨çš„当å‰çš„å£ä»¤!',
+'passchanged' => '您的å£ä»¤è¢«ä¿®æ”¹äº†.',
+'closewindow' => '您现在å¯ä»¥å…³é—­è¿™ä¸ªçª—å£.',
+'passnomatch' => '您的输入的两个新å£ä»¤ä¸ä¸€è‡´!',
+'usernametaken' => '这个用户åå·²ç»å­˜åœ¨äº†. 您需è¦å†é€‰æ‹©ä¸€ä¸ª.',
+'usernametakenbulk' => '用户åå·²ç»è¢«å ç”¨äº†.',
+'newusercreated' => '新用户账户被创建了.',
+'accountcreated' => '您的账户被创建了',
+'newuserwarning' => '注æ„全局常规设置å¯èƒ½éœ€è¦ç”±ç®¡ç†å‘˜æ‰¹å‡†æ‚¨çš„账户. 如果您ä¸èƒ½ç™»å½•, å¯èƒ½æ˜¯å› ä¸ºè¿™ä¸ªåŽŸå› .',
+'nomatchpass' => 'å£ä»¤ä¸åŒ¹é….',
+'confirmwrong' => '确认ç ä¸æ­£ç¡®!',
+'formnotcomplete' => '表å•æ²¡æœ‰å®Œå…¨å¡«å†™.',
+'formnotnumeric' => 'æ’入的数æ®ä¸æ˜¯æ•°å­—!',
+'groupnametaken' => '这个群组å称已ç»è¢«å ç”¨äº†.',
+'newgroupadded' => '增加了一个新群组.',
+'optionssaved' => 'Flyspray 的选项ä¿å­˜äº†.',
+'hasuploaded' => '上传了一个文件附件到被指派的任务:',
+'hasattached' => '附加了一个文件到下é¢çš„ 任务.',
+'fileuploaded' => '上传了一个文件.',
+'fileerror' => '上传您的文件时å‘生错误. 也许<i>附件/</i>目录的æƒé™ä¸æ­£ç¡®.',
+'contactadmin' => 'è”系这个项目的管ç†å‘˜.',
+'selectfileerror' => '您没有选择文件.',
+'userupdated' => '用户的详细信æ¯å·²ç»è¢«æ›´æ–°äº†',
+'realandemail' => '您没有填写真实å称和电å­é‚®ä»¶åœ°å€.',
+'groupupdated' => '群组定义更新了.',
+'groupanddesc' => '您没有填写群组å称.',
+'fillallfields' => '请填写全部信æ¯.',
+'listPmustN' => '"顺åº" 这里åªèƒ½å¡«å†™æ•°å­—.',
+'listupdated' => '列表更新了.',
+'listitemadded' => '增加了一个新列表æ¡ç›®.',
+'relatedaddedmsg' => 'å…³è”任务增加到列表中了.',
+'relatederror' => '这个任务已ç»åœ¨è¿™ä¸ªå…³è”任务列表里了.',
+'relatedremoved' => 'å…³è”任务从列表中移除了.',
+'notifyadded' => '用户增加到通知列表了.',
+'notifyerror' => '这个用户已ç»åœ¨ä»»åŠ¡çš„通知列表中了.',
+'notifyremoved' => '用户从通知列表中移除了.',
+'editcommentsaved' => '更新的评论已ä¿å­˜ã€‚',
+'commentdeletedmsg' => '评论已ç»è¢«åˆ é™¤äº†ã€‚',
+'gotonewtask' => '到您刚刚创建的新任务去',
+'projectcreated' => '您创建了新项目. 点击下é¢é“¾æŽ¥è®¾ç½®æ¨¡å—, æ“作系统和版本列表',
+'customiseproject' => '定制这个项目',
+'projectupdated' => '项目常规设置更新了',
+'emptytitle' => '您没有输入项目标题.',
+'loginbelow' => '您现在å¯ä»¥ç™»å½•äº†.',
+'attachmentdeletedmsg' => '附件被删除了',
+'reminderaddedmsg' => '增加了一个您的æ醒.',
+'reminderdeletedmsg' => '选中的æ醒被删除了.',
+'flyspraytask' => 'Flyspray任务',
+'fieldsmissing' => '一些字段没有数æ®æˆ–者数æ®æ— æ•ˆ.',
+'relatedinvalid' => '没有这个任务.',
+'relatedproject' => '这个任务属于ä¸åŒçš„项目. 还è¦å¢žåŠ å…³è”å—?',
+'addanyway' => '增加',
+'cancel' => 'å–消',
+'alreadyedited' => '这个任务在您ä¿å­˜ä¹‹å‰è¢«å…¶ä»–人修改了.您还è¦ç»§ç»­ä¿å­˜å—?',
+'saveanyway' => '继续ä¿å­˜',
+'nouserselected' => '没有选择用户. 选择åªå°‘一个用户å†è¯•ä¸€æ¬¡.',
+'groupswitchupdated' => '用户群组 修改 æˆåŠŸåœ°.',
+'takenownershipmsg' => '这个任务现在被指派给您了.',
+'adminrequestmade' => '您的请求被å‘é€åˆ°é¡¹ç›®ç»ç†äº†.',
+'newdepnotify' => '一个新的ä¾èµ–被增加到下é¢çš„任务:',
+'dependadded' => '增加了一个任务ä¾èµ–',
+'dependaddfailed' => '增加ä¾èµ–时出现错误. 检查任务是å¦å­˜åœ¨ï¼Œä»¥åŠä»»åŠ¡ä¹‹é—´æ²¡æœ‰ç›¸äº’阻止.',
+'depremovedmsg' => '任务ä¾èµ–被移除了',
+'newdepis' => 'æ–°çš„ä¾èµ–是',
+'magicurlsent' => 'å‘您的通知地å€å‘é€äº†æ¶ˆæ¯. 包å«ä¸€ä¸ªå®Œæˆä»»åŠ¡æ‰€éœ€è¦çš„链接.',
+'changefspass' => '修改Flysprayå£ä»¤',
+'magicurlmessage' => '请进入下é¢åœ°å€ä»¥ä¿®æ”¹å£ä»¤:',
+'erroronform' => 'æ交表å•æ—¶å‡ºçŽ°é—®é¢˜',
+'addressused' => '这个信æ¯æ˜¯ç”¨äºŽæ³¨å†ŒFlyspray账户. 如果这是您ä¸éœ€è¦çš„ä¿¡æ¯ï¼Œè¯·åˆ é™¤å®ƒ. 进入下é¢çš„链接完æˆæ³¨å†Œ:',
+'confirmcodeis' => '您的确认ç æ˜¯:',
+'codesent' => '您的确认ç å‘出了. 请按照消æ¯ä¸­çš„æ示æ“作.',
+'codenotsent' => '无法å‘é€ç¡®è®¤ç , 请ç¨å€™é‡è¯•.',
+'taskmadeprivatemsg' => '这个任务被置为ä¸å…¬å¼€äº†',
+'taskmadepublicmsg' => '这个任务被é‡æ–°ç½®ä¸ºå…¬å¼€äº†',
+'realandnotify' => '您需è¦å¡«å†™çœŸå®žå§“å字段, 邮件地å€å’ŒJabber ID至少è¦å¡«ä¸€é¡¹.',
+'pmreqdeniedmsg' => '项目ç»ç†è¯·æ±‚被拒ç»äº†',
+'massopsuccess' => '批é‡æ“作æˆåŠŸ',
+'usernotexist' => '用户ä¸å­˜åœ¨',
+'commentattachperms' => '您ä¸èƒ½åˆ é™¤è¿™ä¸ªè¯„论- 没有删除附件的æƒé™',
+'voterecorded' => '您的投票被记录了',
+'votefailed' => '您的投票现在ä¸èƒ½åŠ å…¥.',
+'createnewgroup' => '创建新的群组',
+'requiredfields' => '必填字段åŽé¢æ ‡è®°äº†ä¸€ä¸ª',
+'addthisgroup' => '增加这个群组',
+'createnewproject' => '创建一个新项目',
+'addnewproject' => '增加新的项目',
+'htmlallowed' => 'å¯ä»¥ä½¿ç”¨HTML代ç ',
+'createthisproject' => '创建这个项目',
+'inlineimages' => '嵌入显示图片附件',
+'createnewtask' => '在项目中创建一个新的任务:',
+'addanother' => '在此之åŽï¼Œå¢žåŠ å¦ä¸€ä¸ªä»»åŠ¡',
+'addthistask' => '增加这个任务',
+'notifyme' => '当任务å‘生å˜åŒ–时通知我',
+'newtask' => '新任务',
+'attachafile' => '附加一个文件',
+'registernewuser' => '注册新用户',
+'none' => 'æ— ',
+'registeraccount' => '注册这个账户',
+'registerbulkaccount' => '注册账户',
+'both' => '两者都è¦',
+'notifyfrom' => 'FlySpray通知:æ¥è‡ª',
+'donotreply' => '这是一个自动å‘é€çš„消æ¯ï¼Œè¯·å‹¿å›žå¤.',
+'disclaimer' => 'æ ¹æ®æ‚¨ä»ŽFlyspray 错误跟踪系统请求,我们å‘é€äº†è¿™ä¸ªæ¶ˆæ¯. 您å¯ä»¥è¿›å…¥ä¸‹é¢é“¾æŽ¥å–消此类消æ¯çš„å‘é€.',
+'userwho' => 'åšæ­¤æ“作的用户是',
+'moreinfo' => '从下é¢URLå¯ä»¥å¾—到详细信æ¯:',
+'newtaskopened' => '一个新的Flyspray任务被打开了. 详情如下.',
+'notify.taskclosed' => '下é¢çš„任务现在被关闭了:',
+'notify.taskreopened' => '下é¢çš„任务已ç»è¢«é‡æ–°æ‰“开了:',
+'newdep' => '下é¢çš„任务增加了一个新的ä¾èµ–:',
+'notify.depremoved' => '下é¢çš„任务移除了一个ä¾èµ–:',
+'olddepwas' => '原æ¥çš„ä¾èµ–是',
+'notify.commentadded' => '下é¢çš„任务增加了一个新的评论:',
+'commentis' => '评论内容如下.',
+'newattachment' => '一个新的文件被附加到下é¢çš„任务:',
+'detailsbelow' => '详情如下.',
+'notify.relatedadded' => '一个新的关è”任务被增加到下é¢çš„任务:',
+'relatedis' => 'å…³è”任务是',
+'assignedtoyou' => '您被指派到下é¢çš„任务:',
+'takenownership' => 'æˆä¸ºäº†ä¸‹é¢çš„任务的主管:',
+'requiresaction' => '下é¢çš„任务需è¦é¡¹ç›®ç»ç†çš„æ“作:',
+'requiresactionnotify' => '任务需è¦é¡¹ç›®ç»ç†çš„æ“作',
+'pmdeny' => '项目ç»ç†æ‹’ç»äº†çš„下é¢ä»»åŠ¡çš„请求挂起:',
+'pmdenynotify' => '项目ç»ç†æ‹’ç»äº†è¯·æ±‚',
+'fileaddedtoo' => '这个评论有文件附件.',
+'taskwatching' => '您正在关注下é¢çš„任务',
+'isdepfor' => '是一个新的ä¾èµ–,对于',
+'denialreason' => 'æ‹’ç»çš„ç†ç”±',
+'taskchanged' => '下é¢çš„任务被å˜æ›´äº†.å˜æ›´åˆ—在下é¢.想得到详细的å˜æ›´ä¿¡æ¯,请访问下é¢é“¾æŽ¥ï¼Œå¹¶ç‚¹åŽ†å²é¡µ.',
+'useraddedtoassignees' => '用户把自己加入到这个任务的指派用户了.',
+'removeddepis' => '被移除的ä¾èµ–是',
+'isnodepfor' => 'ä¸å†æ˜¯ä¾èµ–了,对于',
+'usergroups' => '用户群组',
+'pmtoolbox' => '项目ç»ç†å·¥å…·ç®±',
+'groupmanage' => '群组管ç†',
+'pendingrequests' => '挂起的请求',
+'reasongiven' => '给出的ç†ç”±',
+'nopendingreq' => '没有挂起的项目ç»ç†è¯·æ±‚.',
+'givereason' => '给出一个ç†ç”±',
+'catlisted' => '模å—列表编辑',
+'oslisted' => 'æ“作系统列表编辑',
+'verlisted' => '版本列表编辑',
+'tasktypeed' => '任务类型列表编辑',
+'resed' => '解决方å¼åˆ—表编辑',
+'deny' => 'æ‹’ç»',
+'notifiedwhen' => '通知在',
+'onlynewtasks' => '新任务被打开了',
+'allevents' => '任何任务中的任何事件',
+'feeds' => 'èšåˆ',
+'feeddescription' => 'èšåˆè®¢é˜…',
+'feedimgurl' => 'èšåˆå›¾åƒé“¾æŽ¥ (没有请ä¸è¦å¡«)',
+'notifysubject' => '通知主题',
+'notifysubjectinfo' => '(%p =项目标题, %s = 任务标题, %t = 任务ID, %a = æ“作, %u = 用户å)',
+'priority6' => '必须马上解决',
+'priority5' => '尽快解决',
+'priority4' => '紧急',
+'priority3' => '高',
+'priority2' => '普通',
+'priority1' => '低',
+'sendcode' => 'å‘é€ç¡®è®¤ç !',
+'entercode' => '输入您收到的确认ç . 并输入您的账户å£ä»¤.',
+'confirmationcode' => '确认ç ',
+'registererror' => '填写所有必填字段,输入正确的通知类型.',
+'validusername' => '(åªå¯æŽ¥å—数字,英文字符和 - _ . )',
+'validemail' => '(使用 ; 分离多个电å­é‚®ä»¶åœ°å€)',
+'emailtakenbulk' => '邮件地å€å·²ç»è¢«å ç”¨.',
+'emailtaken' => '邮件地å€æˆ–Jabber ID被å ç”¨äº†. 您需è¦å†é€‰æ‹©ä¸€ä¸ª.',
+'note' => '<strong>注æ„:</strong> 在您的账户创建之å‰ï¼Œæˆ‘们将å‘é€ç»™æ‚¨ä¸€ä¸ªç¡®è®¤ç . 确认ç ä½¿ç”¨æ‚¨ä¸Šé¢é€‰æ‹©çš„通知方å¼.<br />如果您输入的信æ¯æ˜¯é”™è¯¯çš„,您将<strong>ä¸ä¼šæ”¶åˆ°ç¡®è®¤ç </strong>.',
+'changelog' => 'å˜æ›´æ—¥å¿—',
+'changeloggen' => 'å˜æ›´æ—¥å¿—生æˆ',
+'listfrom' => '列出å˜æ›´æ—¥å¿—项',
+'to' => '到',
+'oldestfirst' => '旧的在å‰',
+'recentfirst' => '近期在å‰',
+'severityrep' => '严é‡æ€§æŠ¥å‘Š',
+'totalopen' => '全部打开的任务',
+'age' => '寿命',
+'agerep' => '寿命报告',
+'eventsrep' => '事件报告',
+'events' => '事件',
+'Tasks' => '任务',
+'opened' => '未解决的',
+'edited' => '已修改的',
+'assigned' => '指派的',
+'within' => '居于',
+'pastday' => '上一日',
+'pastweek' => '上一周',
+'pastmonth' => '上一月',
+'pastyear' => '上一年',
+'nolimit' => 'ä¸é™',
+'from' => '从',
+'duein' => '延期到',
+'selectfromdate' => '选择开始日期',
+'selecttodate' => '选择结æŸæ—¥æœŸ',
+'showvoters' => '显示éšè—投票人',
+'roadmap' => '路线图',
+'roadmapfor' => '版本的路线图',
+'tasks' => '任务',
+'completed' => '完æˆäº†.',
+'opentasks' => '个打开的任务',
+'of' => '% ,全部',
+'severity5' => '严é‡',
+'severity4' => '高',
+'severity3' => '中',
+'severity2' => '低',
+'severity1' => '很低',
+'Redirect' => '转å‘',
+'redirectmsg' => '如果您的æµè§ˆå™¨ ä¸æ”¯æŒHTTPé‡å®šå‘请点这个%sHERE%s链接 ',
+'allowclosedcomments' => 'å…许对关闭的任务评论',
+'comment' => '评论',
+'editowncomments' => '编辑自己的评论',
+'reopened' => 'é‡æ–°æ‰“开了',
+'loading' => '载入...',
+'notifyown' => '主管的任务å‘生å˜æ›´æ—¶ä¹Ÿå‘é€é€šçŸ¥',
+'youremail' => '您的电å­é‚®ä»¶åœ°å€',
+'thankyouforbug' => '感谢您报告您的问题. 您å¯ä»¥é€šè¿‡ä¸‹é¢çš„链接看到这个任务并且éšæ—¶è§‚察它的进度:',
+'anonuser' => '匿å用户',
+'conflict' => '冲çª',
+'file' => '文件',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => '大å°',
+'projectgroup' => '项目群组',
+'profile' => '个人资料:',
+'viewprofile' => '查看个人资料',
+'regdate' => '注册时间',
+'tasksopened' => '打开的任务',
+'replyto' => '回å¤åˆ°',
+'notifytypes' => '通知类型',
+'pm.taskchanged' => '任务å˜æ›´äº†',
+'pm.taskreopened' => '任务é‡æ–°æ‰“开了',
+'pm.depadded' => 'ä¾èµ–增加了',
+'pm.depremoved' => 'ä¾èµ–移除了',
+'pmrequest' => '项目ç»ç†è¯·æ±‚',
+'pmrequestdenied' => '项目ç»ç†è¯·æ±‚被拒ç»äº†',
+'newassignee' => '新的被指派人',
+'revdepadded' => 'åå‘ä¾èµ–增加了',
+'revdepaddedremoved' => 'åå‘ä¾èµ–移除了',
+'assigneeadded' => '增加了被指派人',
+'addusergroup' => 'å‘这个组增加用户',
+'groupmembers' => '群组æˆå‘˜',
+'deleteuser' => '删除这个用户',
+'userdeleted' => '用户删除了',
+'autoassign' => '自动为模å—主管分é…一个任务',
+'ssl' => 'SSL',
+'updatewrong' => "您选择了自动更新功能,\n 但是在连结æœåŠ¡å™¨æ—¶å‡ºçŽ°é—®é¢˜, 也许您无法访问因特网\n 或者由于网络故障造æˆ.\n 请访问Flyspray 网站确认您è¿è¡Œçš„是最新版本的FlySpray.",
+'deleteproject' => '删除这个项目并把内容移至',
+'projectdeleted' => '项目被æˆåŠŸåœ°åˆ é™¤äº†',
+'feedforall' => '所有项目的èšåˆ',
+'usercreated' => '用户创建了',
+'created' => '创建了',
+'deleted' => '删除了',
+'userid' => '用户ID',
+'editassignments' => '编辑任务指派',
+'preview' => '预览',
+'anyprogress' => '任何进度',
+'tasksrelated' => '这个任务的关è”任务',
+'duplicatetasks' => '这个任务的é‡å¤ä»»åŠ¡',
+'databasemodfailed' => '修改数æ®åº“失败. å¯èƒ½æ˜¯æ²¡æœ‰è¶³å¤Ÿçš„æƒé™.',
+'frequency' => '频度',
+'newuserregistered' => '一个新的用户注册了. 用户的详细情况如下所示:',
+'newuserregisterednotify' => '一个新的用户注册了',
+'notify_registration' => '新用户注册时通知管ç†å‘˜',
+'textversion' => '纯文本版本',
+'onlyprimary' => '任务没有阻止其他任务',
+'onlyblocker' => '任务阻止其他任务',
+'blockerornoblocker' => '拦截器或éžæ‹¦æˆªå™¨,选择两个过滤器选项没有æ„义。',
+'switch' => '进入',
+'max' => '最大',
+'dates' => '日期',
+'selectduedatefrom' => '延期的 从',
+'selectduedateto' => '到',
+'selectsincedatefrom' => 'å˜æ›´çš„ 从',
+'selectsincedateto' => '到',
+'selectdate' => '选择日期',
+'selectopenedfrom' => '打开的 从',
+'selectopenedto' => '到',
+'selectclosedfrom' => '关闭的从',
+'selectclosedto' => '到',
+'startat' => '开始于',
+'hasattachment' => '有附件',
+'private' => 'ä¸å…¬å¼€çš„',
+'watching' => '关注',
+'alreadyvotedthistask' => '您对这个任务投了票',
+'alreadyvotedthisday' => '今天已ç»æŠ•è¿‡ç¥¨äº†',
+'visibility' => 'å¯è§',
+'public' => '公开',
+'leaveemptyauto' => '如果您想è¦è‡ªåŠ¨ç”Ÿæˆå£ä»¤ï¼Œè¯·ä¸è¦å¡«å†™å£ä»¤å­—段.',
+'novalidemail' => '您输入的邮件地å€ä¸æ­£ç¡®.',
+'novalidjabber' => '您输入的Jabber IDä¸æ­£ç¡®.',
+'missingrequired' => '您没有填写全部的必填字段.',
+'entervalidusername' => '请输入有效的用户å和真实姓å.',
+'couldnotaddusernotif' => 'ä¸èƒ½æŠŠç”¨æˆ·åŠ å…¥åˆ°é€šçŸ¥åˆ—表.',
+'defaulttask' => '默认任务æè¿°',
+'all' => '全部',
+'events.useraddedtoassignees'=> '用户æˆä¸ºè¢«æŒ‡æ´¾äºº',
+'eventlog' => '事件日志',
+'assignmentchanged' => '指派å‘生了å˜åŠ¨',
+'detailedinfo' => '详细信æ¯',
+'All' => '全部',
+'tasksireported' => '我报告的',
+'recentlyopened' => '最近打开的',
+'stats' => '状æ€',
+'totaltasks' => '个全部的任务',
+'mostwanted' => '最想è¦çš„任务',
+'defaultentry' => '默认入å£é¡µé¢',
+'toplevel' => '最上级查看',
+'overview' => '总览',
+'error#' => '错误 #',
+'error1' => '您没有查看这个附件所需的æƒé™.',
+'error3' => 'é‡å¤çš„æ“作, 回到主页.',
+'error4' => '您没有管ç†æƒé™.',
+'error5' => '这个用户ä¸å­˜åœ¨.',
+'error6' => '无效的管ç†å‘˜åŒºåŸŸ.',
+'error7' => '登录失败(用户å或者å£ä»¤é”™è¯¯)!',
+'error71' => 'å¸æˆ·è¢«é”定%d分钟由于太多失败的登录å°è¯•!',
+'error8' => '您没有用户å或者å£ä»¤.',
+'error9' => '任务ä¸å­˜åœ¨æˆ–者没有æƒé™æŸ¥çœ‹è¿™ä¸ªä»»åŠ¡.',
+'error10' => '任务ä¸å­˜åœ¨æˆ–者没有æƒé™æŸ¥çœ‹è¿™ä¸ªä»»åŠ¡.',
+'error101' => '您没有æƒé™æŸ¥çœ‹æ­¤ä»»åŠ¡.',
+'error102' => '您没有æƒé™æŸ¥çœ‹æ­¤ä»»åŠ¡,登录å¯èƒ½ä¼šæœ‰æ‰€å¸®åŠ©.',
+'error11' => '没有编辑这个评论的æƒé™ã€‚',
+'error12' => '无效的激活ç !请核实一下?',
+'error13' => '匿å用户没有个人资料.',
+'error14' => '您没有足够的æƒé™åˆ›å»ºä¸€ä¸ªæ–°çš„群组.',
+'error15' => '您没有足够的æƒé™æ‰“开一个任务.',
+'error16' => '您ä¸æ˜¯é¡¹ç›®ç»ç†.',
+'error17' => '无效的项目ç»ç†åŒºåŸŸ.',
+'error18' => '无效的激活地å€é“¾æŽ¥.',
+'error19' => '用户ä¸å­˜åœ¨.',
+'error20' => '无效的数æ®åº“修改.',
+'error21' => '邮件å‘é€å¤±è´¥ï¼Œè¯·æ£€æŸ¥é…ç½®.',
+'error22' => 'ä¸å…许新用户注册.',
+'error23' => '用户或群组ä¸å…许登录.',
+'error24' => '被设置的既ä¸æ˜¯ä¸€ä¸ªå¯æ‰§è¡Œæ–‡ä»¶ä¹Ÿä¸æ˜¯ä¸€ä¸ªå…¬å…±æœåŠ¡å™¨.',
+'error25' => 'åªæœ‰åœ¨æŒ‡å®šçš„项目中路线图æ‰å¯ç”¨.',
+'error26' => 'ä¸æ”¯æŒè®¤è¯æ供者.',
+'error27' => '无法登录。授æƒæˆ‘们查看您的电å­é‚®ä»¶åŒºåŸŸ.',
+'error28' => '你没有æƒé™è®¿é—®è¯¥åœ°åŒº',
+'done' => '完æˆ',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => '项目ä¸èƒ½è¢«åˆ é™¤.',
+'GMT' => 'GMT',
+'timezone' => '时区',
+'accept' => '接å—',
+'reasonfordeinal' => 'æ‹’ç»çš„ç†ç”±',
+'pruneclosedlinks' => '删除关闭的链接',
+'pruneclosedtasks' => '删除关闭的任务',
+'pagegenerated' => '页é¢å’Œå›¾å½¢åœ¨ %d 秒内生æˆ.',
+'pruninglevel' => '正在删除级别',
+'lastuser' => '最åŽçš„用户ä¸èƒ½è¢«åˆ é™¤.',
+'allprivate' => '所有的项目都是ä¸å…¬å¼€çš„.',
+'deletegroup' => '删除这个群组,把用户移到',
+'parent' => '上级',
+'ordertip' => 'æ¡ç›®çš„顺åºä¼šåœ¨åˆ—表中显示',
+'showtip' => '在列表中显示这一æ¡',
+'deletetip' => '从列表中删除这一æ¡',
+'del' => '删除',
+'request1' => '一个任务关闭请求.',
+'request2' => '请求é‡æ–°æ‰“开任务.',
+'allpriorities' => '所有优先级',
+'noroadmap' => '没有å¯ç”¨çš„路线图(ä¸å­˜åœ¨é¡¹ç›®ç‰¹å®šçš„“未æ¥â€ç‰ˆæœ¬)。',
+'expand' => '展开',
+'collapse' => '折å ',
+'expandall' => '全部展开',
+'collapseall' => '全部折å ',
+'minpwsize' => '最å°å¯†ç å¤§å°æ˜¯5字符',
+'passwordtoosmall' => '密ç é•¿åº¦å¤ªå°äº†ã€‚',
+'accountwaslocked' => '你的账户被é”,由于太多的失败的登录å°è¯•.',
+'failedattempts' => '有%d失败的登录å°è¯•.',
+'groupnotexist' => '在这个项目选择组ä¸å­˜åœ¨.',
+'searchindetails' => 'æœç´¢ç»†èŠ‚',
+'showasassignees' => '显示为å—托人',
+'find' => 'æœç´¢',
+'tls' => 'TLS',
+'isadmin' => '是管ç†è€…',
+'addvotes' => '添加投票',
+'removevote' => '删除投票',
+'voteremoved' => '你的投票被删除',
+'voteremovefailed' => '你的投票ä¸èƒ½è¢«åˆ é™¤.',
+'novotes' => '你现在没有任何å¯æŠ•ç¥¨.',
+'connectedtasks' => '连接的任务:',
+'taskdependencies' => '任务ä¾èµ–关系',
+'viewgraph' => '查看图表',
+'notaskdependencies' => '这个任务ä¸ä¾èµ–于任何其他任务.',
+'dependson' => 'ä¾èµ–于',
+'blocks' => 'å—',
+'newdependency' => 'æ–°çš„ä¾èµ–关系:',
+'nouserstoadd' => '没有用户添加.请确ä¿å称,用户å和电å­é‚®ä»¶æ˜¯ä¸ºæ¯ä¸ªç”¨æˆ·å®šä¹‰çš„.',
+'dispintro' => '显示主介ç»ä¿¡æ¯',
+'mainmessage' => '主介ç»ä¿¡æ¯',
+'setsupertask' => '设置Super-Task ID:',
+'supertaskmodified' => 'Super-Task ID被修改',
+'set' => '设置',
+'supertask' => 'Super-Task',
+'setparent' => '为本任务设置父任务ID',
+'selfsupertasknotallowed' => 'Super-Task IDä¸èƒ½å’Œä»»åŠ¡ID相åŒ',
+'quickaction' => ' 快速æ“作',
+'updateselectedtasks' => '更新所选任务',
+'notspecified' => '未指定',
+'editselectedtasks' => '编辑所选任务',
+'information' => 'ä¿¡æ¯',
+'taskclosedisabled' => 'ç¦æ­¢å…³é—­å½“å‰ä»»åŠ¡,å› ä¾èµ–任务尚未关闭:-',
+'daysleft' => '天还剩',
+'dayoverdue' => '天过期',
+'duetoday' => '今天到期',
+'daysbeforealert' => '天å‰æ醒',
+'associatedsubtask' => '相关的å­ä»»åŠ¡#FS',
+'associatesubtask' => '为本任务设置å­ä»»åŠ¡ID',
+'subtaskid' => 'å­ä»»åŠ¡ID',
+'subtaskalreadyhasparent' => '您输入的å­ä»»åŠ¡å·²ç»æœ‰ä¸€ä¸ªçˆ¶ä»»åŠ¡,请明确此关è”.',
+'subtaskisparent' => '您输入的å­ä»»åŠ¡æ˜¯çˆ¶ä»»åŠ¡çš„任务.å­ä»»åŠ¡æ— å…³',
+'subtasknotexist' => '您输入的å­ä»»åŠ¡ä¸å­˜åœ¨.',
+'subtaskremovedmsg' => 'å­ä»»åŠ¡å·²ç»æˆåŠŸè¢«ç§»é™¤',
+'subtaskadded' => '添加å­ä»»åŠ¡',
+'subtaskremoved' => '删除å­ä»»åŠ¡',
+'addnewsubtask' => '添加新的å­ä»»åŠ¡',
+'hidesubtasks' => 'éšè—å­ä»»åŠ¡',
+'voteforthistask' => '为本任务投票',
+'watchthistask' => '查看这个任务',
+'privatethistask' => '置为ä¸å…¬å¼€ä»»åŠ¡',
+'adddependenttask' => '增加ä¾èµ–任务',
+'associatetaskid' => 'å­ä»»åŠ¡id',
+'parenttaskid' => '父任务id',
+'invalidsupertaskid' => '父任务id无效.',
+'supertaskadded' => '添加超级任务',
+'supertaskremoved' => '删除超级任务',
+'effort' => '工作é‡',
+'efforttracking' => '工作é‡è·Ÿè¸ª',
+'useeffort' => '项目使用的工作é‡è·Ÿè¸ª',
+'estimatedeffort' => '估计工作é‡',
+'totalestimatedeffort' => '估计总工作é‡',
+'currenteffortdone' => '当å‰å®Œæˆå·¥ä½œé‡',
+'starteffort' => '开始跟踪',
+'endeffort' => 'åœæ­¢è·Ÿè¸ª',
+'cleareffort' => '清除跟踪',
+'addeffort' => '添加跟踪',
+'manualeffort' => '手动添加跟踪(H:M)',
+'efforttrackingstarted' => '开始跟踪这个任务的工作é‡',
+'efforttrackingnotstarted'=> '无法开始跟踪,这个问题已ç»è¢«è·Ÿè¸ª',
+'efforttrackingstopped' => '这个任务的工作é‡è·Ÿè¸ªç»“æŸ.',
+'efforttrackingcancelled' => '这个任务的工作é‡è·Ÿè¸ªè¢«å–消.',
+'efforttrackingadded' => '手动记录工作é‡çš„任务.',
+'trackinginprogress' => '跟踪过程中',
+'viewestimatedeffort' => 'å¯ä»¥æŸ¥çœ‹å·¥ä½œé‡è·Ÿè¸ª',
+'viewcurrenteffortdone' => 'å¯ä»¥æŸ¥çœ‹å½“å‰å·¥ä½œé‡åšäº†ä»€ä¹ˆ',
+'trackeffort' => 'å¯ä»¥è·Ÿè¸ªå·¥ä½œé‡',
+'invalideffort' => '输入的是工作é‡æ— æ•ˆçš„,必须是一个数字',
+'showpass' => '显示密ç ',
+'chooseafile' => '请选择一个文件!',
+'incorrectfiletype' => '错误的文件类型.å…许:jpg,jpeg,gif,png',
+'oauthreqpass' => '没有密ç è¯·æ±‚.你注册通过%s',
+'addmultipletasks' => '添加多个任务',
+'pendingnewuserrequest' => '等待请求的新用户',
+'adminrequestswaiting' => '等待管ç†å‘˜è¯·æ±‚',
+'clicktoedit' => '点击æ¯ä¸ªå­—段快速编辑',
+'confirmedit' => '确认',
+'regapprovedbyadmin' => '由管ç†å‘˜æ³¨å†Œæ‰¹å‡†(ç¦ç”¨ç¡®è®¤ä»£ç )',
+'activity' => '活动',
+'myactivity' => '我的活动',
+'emailverificationwrong' => '电å­é‚®ä»¶ä¸åŒ¹é…给定的电å­é‚®ä»¶åœ°å€',
+'verifyemailaddress' => '确认电å­é‚®ä»¶åœ°å€',
+'hideemails' => 'éšè—用户电å­é‚®ä»¶åœ°å€',
+'hidemyemail' => 'éšè—我的电å­é‚®ä»¶åœ°å€',
+'exporttasklist' => '导出任务列表',
+'onedecimal' => '一个å°æ•°',
+'manday' => '人工日',
+'mandays' => '人工日',
+'mandayabbrev' => '简写的人工日',
+'hourspermanday' => '一个人工日的工作时 (HH:mm)',
+'itemexists' => '项目%så·²ç»å­˜åœ¨äºŽæ•°æ®åº“中',
+'categoryitemexists' => '项目%så·²ç»å­˜åœ¨åœ¨%sæ•°æ®åº“ç±».',
+'pageswelcomemsg' => '页é¢æ˜¾ç¤ºä¸»è¦ä»‹ç»ä¿¡æ¯',
+'pagesintromsg' => '页é¢æ˜¾ç¤ºä»‹ç»ä¿¡æ¯',
+'activeoauths' => '活跃的认è¯æ供者',
+'onlyoauthreg' => 'åªå…许认è¯è€…注册',
+'estimatedeffortformat' => '估计工作é‡æ˜¾ç¤ºæ ¼å¼',
+'currenteffortdoneformat' => '当å‰å®Œæˆå·¥ä½œé‡æ˜¾ç¤ºæ ¼å¼',
+'minute' => '分钟',
+'minutes' => '分钟',
+'minuteplural' => '分钟',
+'minutesingular' => '分钟',
+'minuteabbrev' => '最å°å€¼',
+'hourplural' => 'å°æ—¶',
+'hoursingular' => 'å°æ—¶',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => '估计开放任务工作é‡',
+'currenteffortdoneopen' => '当å‰å¼€æ”¾ä»»åŠ¡è€—费工作é‡',
+'signinwith' => '登录%s',
+'canviewroadmap' => 'å¯ä»¥æŸ¥çœ‹è·¯çº¿å›¾',
+'enableavatars' => 'å¯ç”¨å¤´åƒ',
+'maxavatarsize' => '最大头åƒåƒç´ å€¼',
+'taskhassubtask' => '这个任务有以下å­ä»»åŠ¡',
+'taskhassubtasks' => '这个任务有以下å­ä»»åŠ¡',
+'translations' => '翻译',
+'translate' => '翻译',
+'taskdescription' => '任务æè¿°',
+'notaskdescription' => '没有任务æè¿°',
+'pleaseselect' => '请选择',
+'closeselectedtasks' => '关闭选择任务',
+'closetasks' => '关闭任务',
+'hintforbulkimport' => "<b>批é‡å¯¼å…¥çš„技巧:</b>\n<ol>\n<li>从excel电å­è¡¨æ ¼æˆ–CSV文件中å¤åˆ¶ç²˜è´´ä¸€æ•´åˆ—.</li>\n<li>ç›®å‰ä½ åªèƒ½ç²˜è´´æ ‡é¢˜å’Œç»†èŠ‚.</li>\n<li>建议当你想为项目指派人员而系统中ä¸å­˜åœ¨åŒ¹é…的人员时,请ä¸è¦å½•å…¥äººå。</li>\n</ol>",
+'taskissubtaskof' => '本任务的父任务',
+'applyfirstline' => '应用第一行',
+'addmorerows' => '添加更多的行',
+'addtasks' => '添加任务',
+'massopsdisabled' => '对ä¸èµ·,批é‡ç¼–辑目å‰åœ¨Flyspray 1.0被ç¦æ­¢ï¼Œæˆ‘们计划在åŽé¢çš„版本进行实现。你å¯ä»¥åœ¨æºç ä¸­å¯ç”¨è¿™ä¸ªåŠŸèƒ½ï¼Œä½†æ˜¯æ­¤ä¸¾æœ‰å¯èƒ½ä¼šé€ æˆä¸å¯é¢„料的åŽæžœï¼Œè¯·åœ¨å¯ç”¨è¿™ä¸ªåŠŸèƒ½å‰ä»”细阅读注释。',
+'viewroadmap' => 'å¯ä»¥æŸ¥çœ‹è·¯çº¿å›¾',
+'nosuicide' => '亲爱的用户,因为管ç†å‘˜å·²ç»ç¦ç”¨äº†æ‚¨çš„å¸æˆ·ï¼Œæ‚¨ä¸å…许访问网站或改å˜ç”¨æˆ·ç»„。',
+'movingtodifferentproject'=> '移动一个父任务或å­ä»»åŠ¡åˆ°å¦ä¸€ä¸ªé¡¹ç›®æ˜¯ä¸å…许的.你首先必须打破他们之间的è”系。',
+'musthavesameproject' => '父任务和å­ä»»åŠ¡å¿…须属于åŒä¸€ä¸ªé¡¹ç›®.',
+'defaultorderby' => '排åºé»˜è®¤ä»»åŠ¡åˆ—表',
+'defaultorderby2' => '排åº',
+'viewowntasks' => '查看自己的任务',
+'viewgroupstasks' => '查看任务组',
+'urlrewriting' => 'é‡å†™URL',
+'enablehtaccess' => '请在é‡å†™URL之å‰è®©ä½ çš„.htaccess文件在Flyspray根目录',
+'nomodrewrite' => 'é‡å†™åœ¨è¿™ä¸ªæœåŠ¡ä¸Šä¼¼ä¹Žä¸å¯ç”¨ï¼Œä¸å¥½æ„æ€ï¼Œæˆ‘ä¸èƒ½é‡å†™url在',
+'on' => '打开',
+'off' => '关闭',
+'defaultorderbydirection' => '按默认方å‘',
+'ascending' => 'å‡åº',
+'descending' => 'é™åº',
+'myassignedtasks' => '我分é…的任务',
+'commentedon' => '评论',
+'maxvoteperday' => 'æ¯å¤©æœ€å¤šé€‰ç¥¨',
+'votesperproject' => '在æ¯ä¸ªé¡¹ç›®ä¸­ç”¨æˆ·è¢«é™åˆ¶çš„选票',
+'votelimitreached' => '你的投票é™åˆ¶ä¸ºè¿™ä¸ªé¡¹ç›®.在个人资料页é¢æŸ¥çœ‹å“ªäº›ä»»åŠ¡å¯ä»¥æŠ•ç¥¨.你也å¯ä»¥æ”¶å›žçš„选票.我们å¯ä»¥çœ‹åˆ°å“ªäº›ä»»åŠ¡å¯¹ä½ æ˜¯æœ€é‡è¦çš„.解决任务å¯ä»¥æ”¶å›žä½ çš„选票.',
+'myvotes' => '我的选票',
+'tag' => '标签',
+'tags' => '标签',
+'tagsinfo' => '使用一个或多个è¯æ¥å½¢å®¹è¿™ä¸ªä»»åŠ¡çš„关键点,多个è¯ä¹‹é—´ä½¿ç”¨è‹±æ–‡çš„分å·;分隔,添加标签能让开å‘者处ç†åŒä¸€ç±»é—®é¢˜ã€‚',
+'novalues' => '没有æ¡ç›®',
+'youhaveregistered' => 'ä½ å·²ç»åœ¨Flyspray注册,你的详细资料如下:',
+'youhaveregisterednotify' => '你在Flyspray上的注册已ç»è¢«ç®¡ç†å‘˜æŽ¥å—.',
+'usedintasks' => '用法',
+'freetagging' => 'å…许用户定义标签',
+'keyboardshortcuts' => '键盘快æ·é”®',
+'testmailsettings' => '测试当å‰ç”Ÿæ•ˆçš„邮箱设置',
+'test' => '测试',
+'testmailsettingsnotice' => '并检查您是å¦åœ¨å½“å‰ç”¨æˆ·çš„电å­é‚®ç®±ä¸­æ”¶åˆ°æµ‹è¯•ç”µå­é‚®ä»¶ï¼ˆè¯·å‚阅“我的详细信æ¯â€é¡µé¢ï¼‰ã€‚',
+'invalidinput' => '噢,有一些ä¸å…¼å®¹çš„属性å‘é€æˆ–丢失。',
+'invalidprogress' => '请选择一个正确的进度数值。',
+'invalidpriority' => '请选择一个正确的优先级数值。',
+'invalidseverity' => '请选择一个正确的严é‡æ€§æ•°å€¼ã€‚',
+'invalidstatus' => '请选择一个正确的任务状æ€ã€‚',
+'invalidcategory' => '请选择一个正确的模å—。',
+'invalidreportedversion' => '请选择一个正确的报告版本。',
+'invaliddueversion' => '请选择一个正确的计划完æˆç‰ˆæœ¬ã€‚',
+'invalidos' => '请选择一个正确的æ“作系统。',
+'invalidtags' => '请仅选择å…许的标签。',
+'invalidassignees' => '请仅选择å…许的指派人',
+'customize' => '自定义',
+'hidesubs' => 'éšè—å­ä»»åŠ¡',
+'hideprivate' => 'éšè—ç§æœ‰çš„任务',
+'hideclosed' => 'éšè—关闭的任务',
+'currentproject' => '当å‰é¡¹ç›®',
+'targetproject' => '目标项目',
+'availablekeybshortcuts' => 'å¯ç”¨çš„键盘快æ·é”®',
+'logindialoglogout' => '登录对è¯æ¡†/注销',
+'focustaskidsearch' => 'èšç„¦ä»»åŠ¡ç¼–å·æœç´¢',
+'openselectedtask' => '打开选择的任务',
+'movecursorup' => 'å‘上移动光标',
+'movecursordown' => 'å‘下移动光标',
+'taskdetails' => '任务详情',
+'taskediting' => '任务编辑中',
+'savetask' => 'ä¿å­˜ä»»åŠ¡',
+'generalintegration' => '一般附加内容',
+'generalintegrationdesc' => '这些内容将被放在默认主题的body标签内,在正文内容与底部之间。它真正出现在å±å¹•ä¸Šçš„地方å–决于CSS,您å¯ä»¥ä½¿ç”¨custom_*.cssæ¥è‡ªå®šä¹‰ã€‚',
+'footerintegration' => '底部附加内容',
+'footerintegrationdesc' => '默认情况下,此内容将默认放置在逻辑页脚区域中。它真正出现在å±å¹•ä¸Šçš„地方å–决于CSS,您å¯ä»¥ä½¿ç”¨custom_*.cssæ¥è‡ªå®šä¹‰ã€‚',
+'editorbold' => 'B',
+'editorboldhint' => '粗体',
+'editoritalic' => 'I',
+'editoritalichint' => '斜体',
+'editorunderline' => 'U',
+'editorunderlinehint' => '下划线',
+'editorstrikethrough' => 'S',
+'editorstrikethroughhint' => '删除线',
+);
+
+?>
diff --git a/lang/zh_tw.php b/lang/zh_tw.php
new file mode 100644
index 0000000..e6c2c68
--- /dev/null
+++ b/lang/zh_tw.php
@@ -0,0 +1,1045 @@
+<?php
+
+$translation = array(
+'edituser' => '編輯用戶',
+'accountenabled' => '帳號啟用',
+'editallusers' => '查看所有用戶',
+'username' => '使用者',
+'usersupdated' => '使用者更新æˆåŠŸ',
+'realname' => '真實姓å',
+'emailaddress' => 'é›»å­éƒµä»¶',
+'jabberid' => 'Jabber ID',
+'profileimage' => '圖片',
+'notifytype' => '通知類型',
+'group' => '群組',
+'enableaccounts' => '啟動帳號',
+'disableaccounts' => '關閉帳號',
+'deleteaccounts' => '移除帳號',
+'updatedetails' => '更新詳細資訊',
+'setglobally' => '這個一般設定已被設定為全域設定',
+'usergroupmanage' => '使用者與群組管ç†',
+'newuser' => '註冊新使用者',
+'newuserbulk' => '註冊多個新的使用者',
+'bulkuserstoadd' => '新用戶列表',
+'optionsforallusers' => '所有新用戶é¸é …',
+'newgroup' => '建立群組',
+'yes' => '是',
+'no' => 'å¦',
+'editgroup' => '編輯群組',
+'groupname' => '群組å',
+'description' => 'æè¿°',
+'admin' => '管ç†ç¾¤çµ„',
+'opennewtasks' => '打開新建任務',
+'modifytasks' => '修改任務',
+'addcomments' => '新增註解',
+'attachfiles' => '附件',
+'vote' => '投票',
+'groupopen' => 'æˆå“¡å¯ä»¥ç™»å…¥',
+'groupenabled' => 'æˆå“¡å¯ä»¥ç™»å…¥',
+'tasktypelist' => '任務類型列表',
+'categorylist' => '模組列表',
+'oslist' => '作業系統列表',
+'resolutionlist' => '解決方å¼åˆ—表',
+'versionlist' => '版本列表',
+'severitylist' => 'å•é¡Œåš´é‡æ€§åˆ—表',
+'listnote' => '注æ„: 在編輯環境下,å–消 "顯示" 複é¸æ¡†æœƒæ”¹è®Šä¸€äº›ä»»å‹™ã€‚ 修改"å稱"字串會用這個å稱修改全部任務。',
+'name' => 'å稱',
+'order' => 'é †åº',
+'back' => '退後',
+'text' => '文本',
+'highlight' => '高亮',
+'show' => '顯示',
+'owner' => '主管',
+'selectowner' => 'é¸æ“‡æ‰€æœ‰è€…',
+'update' => 'æ›´æ–°',
+'addnew' => '增加',
+'flysprayprefs' => 'Flyspray 常用設定',
+'projecttitle' => '專案標題',
+'baseurl' => 'Base URL for 這個安è£',
+'replyaddress' => '通知的回覆電å­éƒµä»¶åœ°å€',
+'themestyle' => '主題 / 樣å¼',
+'language' => '語言',
+'customstyle' => '自定義的樣å¼',
+'anonview' => 'å…許匿å用戶查看任務',
+'allowanon' => 'å…許匿å用戶打開新任務',
+'never' => '從ä¸',
+'anonymously' => '匿å',
+'afterregister' => 'åªæœ‰åœ¨è¨»å†Šä¹‹å¾Œ',
+'spamproof' => '啟用新用戶註冊確èªç¢¼',
+'anongroup' => '新用戶註冊的群組',
+'groupassigned' => '這些群組的æˆå“¡èƒ½è¢«æŒ‡æ´¾ä»»å‹™',
+'forcenotify' => '任務通知方å¼',
+'neversend' => 'ä¸ç™¼é€',
+'userchoose' => '讓用戶自己é¸æ“‡',
+'email' => 'é›»å­éƒµä»¶',
+'jabber' => 'Jabber',
+'defaultcatowner' => 'é è¨­çš„模組主管',
+'noone' => 'ä¸è¦',
+'jabbernotify' => 'Jabber通知',
+'jabberserver' => '伺æœå™¨',
+'jabberport' => '連接埠',
+'jabberuser' => '帳號帳號',
+'jabberpass' => '帳號密碼',
+'saveoptions' => '儲存é¸é …',
+'editcomment' => '編輯評論',
+'commentby' => '評論人',
+'saveeditedcomment' => '儲存評論',
+'projectprefs' => '專案常用設定',
+'pagetitle' => 'é é¢æ¨™é¡Œ',
+'defaultproject' => 'é è¨­çš„專案',
+'projectlists' => '專案列表',
+'showlogo' => '顯示標題圖片',
+'showgravatars' => '顯示頭åƒ',
+'emailNoHTML' => '沒有HTMLé›»å­éƒµä»¶',
+'intromessage' => '介紹性訊æ¯',
+'active' => 'å•Ÿå‹•',
+'inactive' => '關閉',
+'isactive' => '專案是啟動的',
+'createproject' => '建立一個新專案',
+'showinactive' => '顯示關閉的專案',
+'hideinactive' => 'éš±è—關閉的專案',
+'nopermission' => '您沒有權é™ä½¿ç”¨é€™å€‹é é¢.',
+'listordertip' => '這些æ¢ç›®çš„é †åºå°‡é¡¯ç¤ºåœ¨åˆ—表裡',
+'listshowtip' => '在列表裡顯示這個æ¢ç›® ',
+'categoryownertip' => '當這個模組中打開一個任務時,這個人將收到通知',
+'categoryparenttip' => '這個新模組的上級模組將會é™ç´š',
+'notsubcategory' => '無 (頂級模組)',
+'showinlineimages' => '嵌入顯示圖片附件',
+'dateformat' => '日期格å¼',
+'dateformat_extended' => '詳細日期格å¼',
+'cache_feeds' => '動態訊æ¯æš«å­˜',
+'no_cache' => 'ä¸æš«å­˜',
+'cache_disk' => '暫存到ç£ç¢Ÿ',
+'cache_db' => '暫存到資料庫',
+'subcategoryof' => 'å­æ¨¡çµ„',
+'visiblecolumns' => '任務列表中顯示的列',
+'visiblefields' => '當新增/編輯/查看任務時',
+'tense' => '時間',
+'listtensetip' => 'éŽåŽ»çš„, ç¾åœ¨çš„或者將來的',
+'past' => 'éŽåŽ»çš„',
+'present' => 'ç¾åœ¨çš„',
+'future' => '將來的',
+'oldpass' => '舊的密碼',
+'nooldpass' => '沒有設定舊的密碼',
+'oldpasswrong' => '舊的密碼是錯的',
+'changepass' => '修改密碼',
+'confirmpass' => '確èªå¯†ç¢¼',
+'projectmanager' => '專案經ç†',
+'viewtasks' => '查看任務',
+'modifyowntasks' => '修改自己主管的任務',
+'modifyalltasks' => '修改ä¸å±¬æ–¼ç”¨æˆ¶ä¸»ç®¡çš„任務',
+'viewcomments' => '查看評論',
+'editcomments' => '編輯評論',
+'deletecomments' => '刪除評論',
+'viewattachments' => '查看附件',
+'createattachments' => '建立附件',
+'deleteattachments' => '刪除附件',
+'viewhistory' => '查看歷å²',
+'closeowntasks' => '關閉主管任務',
+'closeothertasks' => '任務ä¸æ˜¯ç”¨æˆ¶ä¸»ç®¡çš„關閉',
+'assigntoself' => '如果沒有被指派,指派任務給自己',
+'assignotherstoself' => '指派其他人的任務到自己',
+'viewreports' => '查看事件日誌',
+'othersview' => 'å…許任何人查看這個專案的任務',
+'othersviewroadmap' => 'å…許任何人查看這個專案的視圖',
+'usersandgroups' => '用戶群組',
+'globalgroup' => '全域群組',
+'globalgroups' => '全域群組',
+'defaultglobalgroup' => '新用戶é è¨­çš„全域群組',
+'addtogroup' => '增加到群組',
+'moveuserstogroup' => '移動用戶到群組',
+'nogroup' => '無群組 - 從專案移除',
+'eventdesc' => '事件æè¿°',
+'requestedby' => '申請人',
+'daterequested' => '申請日期',
+'closetask' => '關閉任務',
+'reopentask' => 'é‡æ–°æ‰“開任務',
+'applymember' => '申請專案æˆå“¡è³‡æ ¼',
+'forcurrentproj' => '當å‰çš„專案',
+'lostpw' => '找回éºå¤±çš„密碼',
+'lostpwexplain' => '請輸入您的帳號,我們會發é€ä¸€å€‹ä¿®æ”¹æ‚¨å¯†ç¢¼çš„連çµåˆ°æ‚¨çš„個人資料中填寫的通知郵件地å€.',
+'sendlink' => '發é€é€£çµ',
+'savenewpass' => '儲存新密碼',
+'anonreg' => 'å…許新用戶註冊',
+'allowanonopentask' => 'å…許匿å用戶打開任務',
+'editglobalgroup' => '編輯全域群組',
+'editgroupforproj' => '編輯專案的群組',
+'notshownforadmin' => '管ç†ç¾¤çµ„的權é™æ²’有顯示.您ä¸éœ€è¦ç·¨è¼¯å®ƒå€‘.',
+'general' => '常用',
+'userregistration' => '用戶註冊',
+'notifications' => '通知',
+'resetoptions' => 'é‡ç½®é¸é …',
+'preferences' => '常用設定',
+'tasktypes' => '任務類型',
+'resolutions' => '解決方å¼',
+'categories' => '模組定義',
+'operatingsystems' => '作業系統',
+'versions' => '版本定義',
+'admintoolboxlong' => '管ç†å“¡å·¥å…·ç®±',
+'newproject' => '新建專案',
+'delete' => '刪除',
+'link' => '連çµ',
+'referencelinks' => 'åƒè€ƒé€£çµ',
+'listdeletetip' => '從列表中刪除這個æ¢ç›®',
+'lookandfeel' => '外觀',
+'globaltheme' => '全域主題',
+'emailnotify' => 'é›»å­éƒµä»¶ 通知',
+'fromaddress' => '發件地å€',
+'smtpserver' => 'SMTP 伺æœå™¨',
+'smtpuser' => 'SMTP 帳號',
+'smtppass' => 'SMTP 密碼',
+'addrewrite' => '使用地å€é‡å¯«',
+'usereminderdaemon' => '啟用後å°æ醒æœå‹™',
+'tasksperpage' => 'æ¯é ä»»å‹™åˆ—表顯示的任務數',
+'addtoassignees' => '增加自己為被指派人',
+'taskstatuses' => '任務狀態',
+'canvote' => 'å¯ä»¥å°ä»»å‹™æŠ•ç¥¨',
+'loginsuccessful' => '登入æˆåŠŸ.',
+'youareloggedout' => '您已經註銷了.',
+'waitwhiletransfer' => 'è«‹ç¨å¾Œ...',
+'clicknowait' => '如果您ä¸æƒ³ç­‰å¾…,請點這裡.',
+'accountdisabled' => '您的帳號被ç¦ç”¨äº†. è«‹è¯ç¹«ç®¡ç†å“¡.',
+'task' => '任務',
+'edittask' => '編輯這個任務',
+'openedby' => '任務打開人:',
+'editedby' => '最後修改者:',
+'tasktype' => '任務類型',
+'category' => '模組',
+'status' => '狀態',
+'assignedto' => '指派到',
+'operatingsystem' => '作業系統',
+'severity' => 'åš´é‡æ€§',
+'reportedversion' => '報告版本',
+'dueinversion' => '延期到版本',
+'defaultdueinversion' => 'é è¨­æ–°ä»»å‹™å»¶æœŸåˆ°ç‰ˆæœ¬',
+'undecided' => '未決定',
+'percentcomplete' => '完æˆç™¾åˆ†æ¯”',
+'details' => '詳細訊æ¯',
+'savedetails' => '儲存詳細訊æ¯',
+'canceledit' => 'å–消編輯',
+'anonymous' => '匿åæ交者',
+'complete' => '完æˆ',
+'closedby' => '關閉,由',
+'reasonforclosing' => '關閉的ç†ç”±:',
+'reopenthistask' => 'é‡æ–°æ‰“開這個任務',
+'comments' => '評論數é‡',
+'attachments' => '附件數é‡',
+'relatedtasks' => 'é—œè¯ä»»å‹™',
+'edit' => '編輯',
+'addcomment' => '增加評論',
+'fileuploadedby' => '文件上傳,由',
+'uploadafile' => '附加一個文件',
+'addalink' => '新增一個連çµ',
+'addanotherlink' => '新增å¦ä¸€å€‹é€£çµ',
+'uploadnow' => 'ç¾åœ¨ä¸Šå‚³!',
+'thesearerelated' => '這些任務被關è¯åˆ°é€™å€‹ä»»å‹™äº†',
+'remove' => '移除',
+'addnewrelated' => '增加一個新的關è¯ä»»å‹™',
+'add' => '增加!',
+'otherrelated' => '這個任務關è¯çš„其他的任務',
+'receivenotify' => '當這個任務被修改時,下列用戶將收到詳細的通知.',
+'addusertolist' => '增加用戶到通知列表',
+'addtolist' => '增加到列表',
+'addmyself' => '把我加入到這個列表中',
+'removemyself' => '從這個列表中移除我自己',
+'theseusersnotify' => '當這個任務被修改時,下é¢çš„用戶將收到詳細的通知.',
+'attachedtoproject' => '屬於專案',
+'reminders' => 'æ醒',
+'system' => '系統',
+'systemvalues' => '系統列表值範åœ',
+'projectvalues' => '專案的具體列表值',
+'remindthisuser' => 'æ醒用戶',
+'thisoften' => 'æ¯éš”',
+'startafter' => 'å•Ÿå‹•æ醒å‰ç­‰å¾…',
+'hour' => 'å°æ™‚',
+'hours' => 'å°æ™‚',
+'day' => '天',
+'days' => '天',
+'week' => '星期',
+'weeks' => '星期',
+'addreminder' => '增加æ醒',
+'defaultreminder' => '下é¢æ˜¯ Flyspray 的任務æ醒:',
+'message' => '消æ¯',
+'closed' => '已解決的',
+'filename' => '文件å稱:',
+'date' => '日期',
+'filesize' => '文件大å°:',
+'closurecomment' => '其它關於關閉的評論:',
+'history' => 'æ­·å²',
+'nohistory' => '沒有歷å².',
+'eventdate' => '日期',
+'user' => '用戶',
+'event' => '事件',
+'fieldchanged' => '欄ä½ç™¼ç”Ÿè®Šå‹•',
+'taskopened' => '任務被打開了',
+'taskreopened' => '任務被é‡æ–°æ‰“開了',
+'taskclosed' => '任務被關閉了',
+'commentadded' => '評論增加了',
+'commentedited' => '評論修改了',
+'commentdeleted' => '評論刪除了',
+'attachmentadded' => '附件增加了',
+'attachmentdeleted' => '附件刪除了',
+'taskedited' => '任務詳細訊æ¯ä¿®æ”¹äº†',
+'notificationadded' => '用戶增加到通知列表了',
+'notificationdeleted' => '用戶從通知列表中移除了',
+'relatedadded' => 'é—œè¯ä»»å‹™å¢žåŠ äº†',
+'relateddeleted' => 'é—œè¯ä»»å‹™ç§»é™¤äº†',
+'taskassigned' => '任務被指派到',
+'taskreassigned' => '任務被é‡æ–°æŒ‡æ´¾åˆ°',
+'assignmentremoved' => '指派被移除了',
+'summary' => '摘è¦',
+'addedasrelated' => '任務增加到其關è¯åˆ—表了:',
+'deletedasrelated' => '任務從其關è¯åˆ—表中移除了',
+'reminderadded' => 'æ醒增加了',
+'reminderdeleted' => 'æ醒刪除了',
+'priority' => '優先級',
+'previousvalue' => 'å‰ä¸€å€‹å€¼',
+'newvalue' => '新值',
+'selectareason' => 'é¸æ“‡ä¸€å€‹ç†ç”±',
+'assigntome' => '指派到我',
+'reopenrequest' => '請求é‡æ–°æ‰“é–‹',
+'requestclose' => '請求關閉',
+'ownershiptaken' => '用戶æˆç‚ºä¸»ç®¡',
+'closerequestmade' => '申請任務關閉',
+'reopenrequestmade' => '申請任務被é‡æ–°æ‰“é–‹',
+'taskdependson' => '這個任務ä¾è³´',
+'taskdependsontask' => '任務å–決於',
+'taskdependsontasks' => '任務å–決於',
+'taskblock' => '這個任務被阻止關閉',
+'taskblocks' => '這個任務被阻止關閉',
+'depadded' => 'ä¾è³´å¢žåŠ çš„',
+'depaddedother' => '這個任務增加為ä¾è³´',
+'depremoved' => 'ä¾è³´ç§»é™¤',
+'depremovedother' => '這個任務從其他的任務ä¾è³´åˆ—表中移除了',
+'showdetailserror' => '任務ä¸å­˜åœ¨, 或者您沒有查看它的權é™ã€‚',
+'makeprivate' => '置為ä¸å…¬é–‹',
+'makepublic' => '置為公開',
+'taskmadeprivate' => '任務æˆç‚ºä¸å…¬é–‹çš„了',
+'taskmadepublic' => '移除了ç§æœ‰å±¬æ€§ - 任務æˆç‚ºå…¬é–‹çš„了',
+'confirmdeletecomment' => '真的刪除這個評論? %s',
+'attachementswilldeleted' => '所有附件也將被刪除!',
+'confirmdeleteattach' => '真的刪除這個附件?',
+'selectedhistory' => '正在顯示é¸å®šçš„æ­·å²è©³ç´°è¨Šæ¯',
+'showallhistory' => 'é‡æ–°é¡¯ç¤ºå…¨éƒ¨æ­·å²æ¨™ç±¤',
+'hidethis' => 'é‡æ–°éš±è—這個å€åŸŸ',
+'mark100' => '標記任務為100%完æˆ',
+'watchtask' => '關注任務',
+'stopwatching' => 'åœæ­¢é—œæ³¨',
+'commentlink' => '連çµåˆ°é€™å€‹è©•è«–',
+'submitreq' => 'æ交請求',
+'reasonforreq' => '這個請求的ç†ç”±',
+'pmreqdenied' => '專案經ç†æ‹’絶了請求',
+'taskpendingreq' => '專案經ç†æ“作被掛起. 進一步詳細訊æ¯è«‹æŸ¥çœ‹æ­·å²é .',
+'previoustask' => 'å‰ä¸€å€‹ä»»å‹™',
+'nexttask' => '下一個任務',
+'duedate' => '延期到日期',
+'attachnoperms' => '這個評論有附件, 但是您沒有權é™æŸ¥çœ‹',
+'linknoperms' => '這個評論有連çµ, 但是您沒有權é™æŸ¥çœ‹',
+'open' => '未解決的',
+'depgraph' => '查看ä¾è³´åœ–表',
+'reset' => 'é‡ç½®',
+'selectusers' => 'é¸æ“‡ç”¨æˆ¶...',
+'addmetoassignees' => '把我加入被指派人',
+'addedtoassignees' => '用戶增加到被指派人列表',
+'dependencygraph' => 'ä¾è³´åœ–表',
+'attachanotherfile' => '附加å¦ä¸€å€‹ 文件',
+'OK' => '好',
+'addvote' => '增加投票',
+'disable_lostpw' => 'ç¦æ­¢éºå¤±å¯†ç¢¼æª¢ç´¢',
+'disable_changepw' => 'ç¦æ­¢å¯†ç¢¼å»ºç«‹/編輯',
+'notifyfromfs' => '來自Flyspray的通知',
+'autogenerated' => '這個是自動生æˆçš„消æ¯,請勿回覆',
+'forward' => 'å‘å‰',
+'previous' => 'å‰ä¸€å€‹',
+'next' => '下一個',
+'first' => '第一個',
+'last' => '最後一個',
+'page' => '第 %d é ï¼Œå…± %d é ',
+'search' => 'æœå°‹',
+'alltasktypes' => '全部任務類型',
+'allseverities' => '全部嚴é‡æ€§ç´šåˆ¥',
+'alldevelopers' => '全部開發人員',
+'notyetassigned' => '尚未被指派',
+'allcategories' => '全部模組',
+'allstatuses' => '全部狀態',
+'allopentasks' => '全部打開的任務',
+'sortthiscolumn' => '以此列排åº',
+'id' => '編號',
+'project' => '專案',
+'dateopened' => '打開的',
+'progress' => '進度',
+'searchthisproject' => '在專案中æœå°‹',
+'dueanyversion' => '延期到任何版本',
+'anyversion' => '任何版本中的報告',
+'dueversion' => '延期到版本',
+'lastedit' => '最近修改的',
+'os' => '作業系統',
+'reportedin' => '報告',
+'taskrange' => '正在顯示 任務 %d - %d (共%d 個)',
+'noresults' => '您的æœå°‹æ²’有返回çµæžœ.',
+'takeaction' => 'æ“作',
+'watchtasks' => '關注é¸å®šçš„任務',
+'stopwatchingtasks' => 'åœæ­¢é—œæ³¨é¸å®šçš„任務',
+'assigntaskstome' => 'å°‡é¸å®šçš„任務指派給我',
+'dueby' => '延期,由',
+'dueanytime' => '延期任æ„事件',
+'selectduedate' => 'é¸æ“‡å»¶æœŸæ—¥æœŸ',
+'toggleselected' => 'åé¸é¸å®šçš„',
+'due' => '延期',
+'assignedtome' => '指派到我的',
+'tasklist' => '任務列表',
+'dateclosed' => '關閉的日期',
+'advanced' => '進階',
+'searchcomments' => '在評論中æœå°‹',
+'searchforall' => 'æœå°‹å…¨éƒ¨è©ž',
+'anonusers' => '匿å用戶',
+'miscellaneous' => '雜項',
+'users' => '用戶',
+'taskproperties' => '任務屬性',
+'selectsincedate' => 'é¸æ“‡ç™¼ç”Ÿè®Šå‹•è‡ªå¾ž',
+'changedsince' => '發生變動自從',
+'updatefs' => 'è«‹æ›´æ–°Flyspray.',
+'currentversion' => '您的當å‰çš„版本是',
+'latestversion' => ',最新的版本是',
+'hidemessage' => '(以後æ醒我)',
+'saveas' => '儲存æœå°‹ç‚º',
+'nosearches' => '沒有儲存的æœå°‹',
+'saving' => '儲存...',
+'votes' => '投票',
+'tovote' => '投票',
+'allclosedtasks' => '全部關閉的任務',
+'password' => '密碼',
+'login' => '登入!',
+'rememberme' => '記ä½æˆ‘',
+'lostpassword' => 'éºå¤±äº†å¯†ç¢¼',
+'lostpwforfs' => 'éºå¤±äº†Flyspray的密碼',
+'lostpwmsg1' => "您好。\n\n我éºå¤±äº† Flyspray 密碼 ",
+'lostpwmsg2' => ", è«‹æ供我一個新密碼.\n\nUsername: ",
+'regards' => "\n\n敬禮,",
+'yourusername' => ' 您的帳號 ',
+'locale' => 'zh-TW',
+'filenotexist' => '文件ä¸å­˜åœ¨, 或者您沒有權é™è¨ªå•å®ƒ.',
+'showtask' => '顯示任務',
+'now' => 'ç¾åœ¨',
+'go' => '開始!',
+'opentaskanon' => '匿å打開一個新任務',
+'register' => '註冊',
+'addnewtask' => '增加新任務',
+'reports' => '事件日誌',
+'editmydetails' => '編輯我的詳細訊æ¯',
+'logout' => '註銷',
+'disabledaccount' => '您的帳號被ç¦ç”¨äº†!<br>å³åˆ»å°‡æ‚¨è¨»éŠ·...',
+'poweredby' => 'å¹³å°æä¾› FlySpray',
+'sponsoredby' => 'Flyspray 發起者',
+'projects' => '專案',
+'allprojects' => '全部專案',
+'selectproject' => '專案:',
+'tasksall' => '全部任務',
+'tasksassigned' => '被指派到我的任務',
+'tasksreported' => '我報告的任務',
+'taskswatched' => '我關注的 ',
+'mysearch' => '我的æœå°‹',
+'admintoolbox' => '管ç†å“¡å·¥å…·ç®±',
+'manageproject' => '管ç†å°ˆæ¡ˆ',
+'permissions' => '查看權é™',
+'hide' => 'éš±è—',
+'pendingreq' => '專案經ç†è«‹æ±‚正在等待',
+'errorpage' => "Flyspray ä¸èƒ½æ供您申請的é é¢ .\n 也許您申請的任務ä¸å­˜åœ¨, 或者您\n 沒有權é™æŸ¥çœ‹æ‚¨æƒ³è¦çš„é é¢.<br /><br />\n 您å¯èƒ½åœ¨è©¦åœ–訪å•è³‡æ–™åº«. 果真如此, 想想您åšçš„æ“作. 返回之後, è«‹ä¸è¦å†åšäº†!",
+'permissionsforproject' => '權é™ï¼š',
+'switchto' => '切æ›åˆ°',
+'lastsearch' => '上次æœå°‹',
+'modify' => '修改',
+'noticefrom' => 'æ醒,來自:',
+'hasopened' => '打開了一個新的任務, 並把它指派給您:',
+'moreinfonew' => '您在下é¢é é¢æ‰¾åˆ°é—œæ–¼é€™å€‹éŒ¯èª¤çš„更多訊æ¯:',
+'newtaskcategory' => '這個模組中有一個新任務被打開了',
+'categoryowner' => '因為您是這個模組的主管,所以您能收到這個消æ¯.',
+'tasksummary' => '任務摘è¦:',
+'newtaskadded' => '您有一個新任務.',
+'summaryanddetails' => '摘è¦å’Œè©³ç´°è¨Šæ¯æ‚¨éƒ½éœ€è¦å¡«å¯«.',
+'summaryrequired' => '你需è¦å¡«å¯«ä¸€å€‹ç°¡çŸ­çš„任務總çµ',
+'goback' => '返回.',
+'messagefrom' => '這個是一個來自Flyspray 錯誤跟蹤系統的消æ¯',
+'hasjustmodified' => '剛剛修改了下é¢çš„任務.',
+'changedfields' => '發生了變動的欄ä½ç”±æ˜Ÿè™Ÿ(**)首碼',
+'moreinfomodify' => '您能在下é¢çš„URL得到更多關於這個任務的訊æ¯:',
+'nolongerassigned' => '下é¢çš„任務ä¸å†è¢«æŒ‡æ´¾åˆ°æ‚¨äº†,它ç¾åœ¨è¢«æŒ‡æ´¾åˆ°',
+'hasassigned' => '已經指派給您下é¢çš„Flyspray 任務:',
+'taskupdated' => '任務被更新了.',
+'tasksupdated' => '任務已經更新了.',
+'hasclosedassigned' => '已經關閉了下é¢é€™äº›æŒ‡æ´¾åˆ°æ‚¨çš„任務:',
+'unassigned' => '未指派的',
+'hasclosed' => '已經關閉了下é¢é€™äº›ä»»å‹™.',
+'youonnotify' => '您正在接收這個,因為您在通知列表中.',
+'taskclosedmsg' => '任務已經被關閉了.',
+'returntotask' => '返回到任務詳細訊æ¯',
+'backtoindex' => '回到任務列表',
+'noclosereason' => '您需è¦é¸æ“‡ä¸€å€‹ç†ç”±ä¾†é—œé–‰é€™å€‹ä»»å‹™.',
+'hasreopened' => '已經é‡æ–°æ‰“開了下é¢é€™äº›è¢«æ‚¨é—œé–‰çš„Flyspray 任務:',
+'taskreopenedmsg' => '任務被é‡æ–°æ‰“開了.',
+'backtotask' => '回到任務.',
+'commentaddedmsg' => '增加的評論.',
+'commenttoassigned' => '一個指派到您的任務增加了一個評論:',
+'commenttotask' => '已經給這個任務了增加了下é¢çš„è©•è«–',
+'nocommententered' => '在æ交之å‰ï¼Œæ‚¨éœ€è¦å¡«å¯«è©•è«–內容',
+'fillinfields' => '您沒有填寫全部的訊æ¯',
+'notcurrentpass' => '這ä¸æ˜¯æ‚¨çš„當å‰çš„密碼!',
+'passchanged' => '您的密碼被修改了.',
+'closewindow' => '您ç¾åœ¨å¯ä»¥é—œé–‰é€™å€‹çª—å£.',
+'passnomatch' => '您的輸入的兩個新密碼ä¸ä¸€è‡´!',
+'usernametaken' => '這個帳號已經存在了. 您需è¦å†é¸æ“‡ä¸€å€‹.',
+'usernametakenbulk' => '帳號已經被å ç”¨äº†.',
+'newusercreated' => '新用戶帳號被建立了.',
+'accountcreated' => '您的帳號被建立了',
+'newuserwarning' => '注æ„全域常用設定å¯èƒ½éœ€è¦ç”±ç®¡ç†å“¡æ‰¹å‡†æ‚¨çš„帳號. 如果您ä¸èƒ½ç™»å…¥, å¯èƒ½æ˜¯å› ç‚ºé€™å€‹åŽŸå› .',
+'nomatchpass' => '密碼ä¸åŒ¹é….',
+'confirmwrong' => '確èªç¢¼ä¸æ­£ç¢º!',
+'formnotcomplete' => '表單沒有完全填寫.',
+'formnotnumeric' => 'æ’入的數據ä¸æ˜¯æ•¸å­—!',
+'groupnametaken' => '這個群組å稱已經被å ç”¨äº†.',
+'newgroupadded' => '增加了一個新群組.',
+'optionssaved' => 'Flyspray çš„é¸é …儲存了.',
+'hasuploaded' => '上傳了一個文件附件到被指派的任務:',
+'hasattached' => '附加了一個文件到下é¢çš„ 任務.',
+'fileuploaded' => '上傳了一個文件.',
+'fileerror' => '上傳您的文件時發生錯誤. 也許<i>附件/</i>目錄的權é™ä¸æ­£ç¢º.',
+'contactadmin' => 'è¯ç¹«é€™å€‹å°ˆæ¡ˆçš„管ç†å“¡.',
+'selectfileerror' => '您沒有é¸æ“‡æ–‡ä»¶.',
+'userupdated' => '用戶的詳細訊æ¯å·²ç¶“被更新了',
+'realandemail' => '您沒有填寫真實å稱和電å­éƒµä»¶åœ°å€.',
+'groupupdated' => '群組定義更新了.',
+'groupanddesc' => '您沒有填寫群組å稱.',
+'fillallfields' => '請填寫全部訊æ¯.',
+'listPmustN' => '"é †åº" 這裡åªèƒ½å¡«å¯«æ•¸å­—.',
+'listupdated' => '列表更新了.',
+'listitemadded' => '增加了一個新列表æ¢ç›®.',
+'relatedaddedmsg' => 'é—œè¯ä»»å‹™å¢žåŠ åˆ°åˆ—表中了.',
+'relatederror' => '這個任務已經在這個關è¯ä»»å‹™åˆ—表裡了.',
+'relatedremoved' => 'é—œè¯ä»»å‹™å¾žåˆ—表中移除了.',
+'notifyadded' => '用戶增加到通知列表了.',
+'notifyerror' => '這個用戶已經在任務的通知列表中了.',
+'notifyremoved' => '用戶從通知列表中移除了.',
+'editcommentsaved' => '更新的評論儲存了.',
+'commentdeletedmsg' => '評論已經被刪除了.',
+'gotonewtask' => '到您剛剛建立的新任務去',
+'projectcreated' => '您建立了新專案. 點擊下é¢é€£çµè¨­å®šæ¨¡çµ„, 作業系統和版本列表',
+'customiseproject' => '定製這個專案',
+'projectupdated' => '專案常用設定更新了',
+'emptytitle' => '您沒有輸入專案標題.',
+'loginbelow' => '您ç¾åœ¨å¯ä»¥ç™»å…¥äº†.',
+'attachmentdeletedmsg' => '附件被刪除了',
+'reminderaddedmsg' => '增加了一個您的æ醒.',
+'reminderdeletedmsg' => 'é¸ä¸­çš„æ醒被刪除了.',
+'flyspraytask' => 'Flyspray任務',
+'fieldsmissing' => '一些欄ä½æ²’有數據或者數據無效.',
+'relatedinvalid' => '沒有這個任務.',
+'relatedproject' => '這個任務屬於ä¸åŒçš„專案. é‚„è¦å¢žåŠ é—œè¯å—Ž?',
+'addanyway' => '增加',
+'cancel' => 'å–消',
+'alreadyedited' => '這個任務在您儲存之å‰è¢«å…¶ä»–人修改了.您還è¦ç¹¼çºŒå„²å­˜å—Ž?',
+'saveanyway' => '繼續儲存',
+'nouserselected' => '沒有é¸æ“‡ç”¨æˆ¶. é¸æ“‡åªå°‘一個用戶å†è©¦ä¸€æ¬¡.',
+'groupswitchupdated' => '用戶群組 修改 æˆåŠŸåœ°.',
+'takenownershipmsg' => '這個任務ç¾åœ¨è¢«æŒ‡æ´¾çµ¦æ‚¨äº†.',
+'adminrequestmade' => '您的請求被發é€åˆ°å°ˆæ¡ˆç¶“ç†äº†.',
+'newdepnotify' => '一個新的ä¾è³´è¢«å¢žåŠ åˆ°ä¸‹é¢çš„任務:',
+'dependadded' => '增加了一個任務ä¾è³´',
+'dependaddfailed' => '增加ä¾è³´æ™‚出ç¾éŒ¯èª¤. 檢查任務是å¦å­˜åœ¨ï¼Œä»¥åŠä»»å‹™ä¹‹é–“沒有相互阻止.',
+'depremovedmsg' => '任務ä¾è³´è¢«ç§»é™¤äº†',
+'newdepis' => 'æ–°çš„ä¾è³´æ˜¯',
+'magicurlsent' => 'å‘您的通知地å€ç™¼é€äº†æ¶ˆæ¯. 包å«ä¸€å€‹å®Œæˆä»»å‹™æ‰€éœ€è¦çš„連çµ.',
+'changefspass' => '修改Flyspray密碼',
+'magicurlmessage' => '請進入下é¢åœ°å€ä»¥ä¿®æ”¹å¯†ç¢¼:',
+'erroronform' => 'æ交表單時出ç¾å•é¡Œ',
+'addressused' => '這個訊æ¯æ˜¯ç”¨æ–¼è¨»å†ŠFlyspray帳號. 如果這是您ä¸éœ€è¦çš„訊æ¯ï¼Œè«‹åˆªé™¤å®ƒ. 進入下é¢çš„連çµå®Œæˆè¨»å†Š:',
+'confirmcodeis' => '您的確èªç¢¼æ˜¯:',
+'codesent' => '您的確èªç¢¼ç™¼å‡ºäº†. 請按照消æ¯ä¸­çš„æ示æ“作.',
+'codenotsent' => '無法發é€ç¢ºèªç¢¼, è«‹ç¨å€™é‡è©¦.',
+'taskmadeprivatemsg' => '這個任務被置為ä¸å…¬é–‹äº†',
+'taskmadepublicmsg' => '這個任務被é‡æ–°ç½®ç‚ºå…¬é–‹äº†',
+'realandnotify' => '您需è¦å¡«å¯«çœŸå¯¦å§“å欄ä½, 郵件地å€å’ŒJabber ID至少è¦å¡«ä¸€é ….',
+'pmreqdeniedmsg' => '專案經ç†è«‹æ±‚被拒絶了',
+'massopsuccess' => '批次æ“作æˆåŠŸ',
+'usernotexist' => '用戶ä¸å­˜åœ¨',
+'commentattachperms' => '您ä¸èƒ½åˆªé™¤é€™å€‹è©•è«–- 沒有刪除附件的權é™',
+'voterecorded' => '您的投票被記錄了',
+'votefailed' => '您的投票ç¾åœ¨ä¸èƒ½åŠ å…¥.',
+'createnewgroup' => '建立新的群組',
+'requiredfields' => '必填欄ä½å¾Œé¢æ¨™è¨˜äº†ä¸€å€‹',
+'addthisgroup' => '增加這個群組',
+'createnewproject' => '建立一個新專案',
+'addnewproject' => '增加新的專案',
+'htmlallowed' => 'å¯ä»¥ä½¿ç”¨HTML程å¼ç¢¼',
+'createthisproject' => '建立這個專案',
+'inlineimages' => '嵌入顯示圖片附件',
+'createnewtask' => '在專案中建立一個新的任務:',
+'addanother' => '在此之後,增加å¦ä¸€å€‹ä»»å‹™',
+'addthistask' => '增加這個任務',
+'notifyme' => '當任務發生變化時通知我',
+'newtask' => '新任務',
+'attachafile' => '附加一個文件',
+'registernewuser' => '註冊新用戶',
+'none' => 'ç„¡',
+'registeraccount' => '註冊這個帳號',
+'registerbulkaccount' => '註冊帳號',
+'both' => '兩者都è¦',
+'notifyfrom' => 'FlySpray通知:來自',
+'donotreply' => '這是一個自動發é€çš„消æ¯ï¼Œè«‹å‹¿å›žè¦†.',
+'disclaimer' => '根據您從Flyspray 錯誤跟蹤系統請求,我們發é€äº†é€™å€‹æ¶ˆæ¯. 您å¯ä»¥é€²å…¥ä¸‹é¢é€£çµå–消此類消æ¯çš„發é€.',
+'userwho' => 'åšæ­¤æ“作的用戶是',
+'moreinfo' => '從下é¢URLå¯ä»¥å¾—到詳細訊æ¯:',
+'newtaskopened' => '一個新的Flyspray任務被打開了. 詳情如下.',
+'notify.taskclosed' => '下é¢çš„任務ç¾åœ¨è¢«é—œé–‰äº†:',
+'notify.taskreopened' => '下é¢çš„任務已經被é‡æ–°æ‰“開了:',
+'newdep' => '下é¢çš„任務增加了一個新的ä¾è³´:',
+'notify.depremoved' => '下é¢çš„任務移除了一個ä¾è³´:',
+'olddepwas' => '原來的ä¾è³´æ˜¯',
+'notify.commentadded' => '下é¢çš„任務增加了一個新的評論:',
+'commentis' => '評論內容如下.',
+'newattachment' => '一個新的文件被附加到下é¢çš„任務:',
+'detailsbelow' => '詳情如下.',
+'notify.relatedadded' => '一個新的關è¯ä»»å‹™è¢«å¢žåŠ åˆ°ä¸‹é¢çš„任務:',
+'relatedis' => 'é—œè¯ä»»å‹™æ˜¯',
+'assignedtoyou' => '您被指派到下é¢çš„任務:',
+'takenownership' => 'æˆç‚ºäº†ä¸‹é¢çš„任務的主管:',
+'requiresaction' => '下é¢çš„任務需è¦å°ˆæ¡ˆç¶“ç†çš„æ“作:',
+'requiresactionnotify' => '任務需è¦å°ˆæ¡ˆç¶“ç†çš„æ“作',
+'pmdeny' => '專案經ç†æ‹’絶了的下é¢ä»»å‹™çš„請求掛起:',
+'pmdenynotify' => '專案經ç†æ‹’絶了請求',
+'fileaddedtoo' => '這個評論有文件附件.',
+'taskwatching' => '您正在關注下é¢çš„任務',
+'isdepfor' => '是一個新的ä¾è³´ï¼Œå°æ–¼',
+'denialreason' => '拒絶的ç†ç”±',
+'taskchanged' => '下é¢çš„任務被變更了.變更列在下é¢.想得到詳細的變更訊æ¯,請訪å•ä¸‹é¢é€£çµï¼Œä¸¦é»žæ­·å²é .',
+'useraddedtoassignees' => '用戶把自己加入到這個任務的指派用戶了.',
+'removeddepis' => '被移除的ä¾è³´æ˜¯',
+'isnodepfor' => 'ä¸å†æ˜¯ä¾è³´äº†ï¼Œå°æ–¼',
+'usergroups' => '用戶群組',
+'pmtoolbox' => '專案經ç†å·¥å…·ç®±',
+'groupmanage' => '群組管ç†',
+'pendingrequests' => '掛起的請求',
+'reasongiven' => '給出的ç†ç”±',
+'nopendingreq' => '沒有掛起的專案經ç†è«‹æ±‚.',
+'givereason' => '給出一個ç†ç”±',
+'catlisted' => '模組列表編輯',
+'oslisted' => '作業系統列表編輯',
+'verlisted' => '版本列表編輯',
+'tasktypeed' => '任務類型列表編輯',
+'resed' => '解決方å¼åˆ—表編輯',
+'deny' => '拒絶',
+'notifiedwhen' => '通知在',
+'onlynewtasks' => '新任務被打開了',
+'allevents' => '任何任務中的任何事件',
+'feeds' => '動態訊æ¯',
+'feeddescription' => '動態訊æ¯è¨‚é–²',
+'feedimgurl' => '動態訊æ¯åœ–ç‰‡é€£çµ (沒有請ä¸è¦å¡«)',
+'notifysubject' => '通知主題',
+'notifysubjectinfo' => '(%p =專案標題, %s = 任務摘è¦, %t = 任務ID, %a = æ“作, %u = 使用者)',
+'priority6' => '必須馬上解決',
+'priority5' => '儘快解決',
+'priority4' => '緊急',
+'priority3' => '高',
+'priority2' => '普通',
+'priority1' => '低',
+'sendcode' => '發é€ç¢ºèªç¢¼!',
+'entercode' => '輸入您收到的確èªç¢¼. 並輸入您的帳號密碼.',
+'confirmationcode' => '確èªç¢¼',
+'registererror' => '填寫所有必填欄ä½,輸入正確的通知類型.',
+'validusername' => '(åªå¯æŽ¥å—數字,英文字元和 - _ . )',
+'validemail' => '(使用 ; 分離多個電å­éƒµä»¶åœ°å€)',
+'emailtakenbulk' => '郵件地å€å·²ç¶“被å ç”¨.',
+'emailtaken' => '郵件地å€æˆ–Jabber ID被å ç”¨äº†. 您需è¦å†é¸æ“‡ä¸€å€‹.',
+'note' => '<strong>注æ„:</strong> 在您的帳號建立之å‰ï¼Œæˆ‘們將發é€çµ¦æ‚¨ä¸€å€‹ç¢ºèªç¢¼. 確èªç¢¼ä½¿ç”¨æ‚¨ä¸Šé¢é¸æ“‡çš„通知方å¼.<br />如果您輸入的訊æ¯æ˜¯éŒ¯èª¤çš„,您將<strong>ä¸æœƒæ”¶åˆ°ç¢ºèªç¢¼</strong>.',
+'changelog' => '變更日誌',
+'changeloggen' => '變更日誌生æˆ',
+'listfrom' => '列出變更日誌項',
+'to' => '到',
+'oldestfirst' => '舊的在å‰',
+'recentfirst' => '近期在å‰',
+'severityrep' => 'åš´é‡æ€§å ±å‘Š',
+'totalopen' => '全部打開的任務',
+'age' => '壽命',
+'agerep' => '壽命報告',
+'eventsrep' => '事件報告',
+'events' => '事件',
+'Tasks' => '任務',
+'opened' => '未解決的',
+'edited' => '已修改的',
+'assigned' => '指派的',
+'within' => 'å±…æ–¼',
+'pastday' => '上一日',
+'pastweek' => '上一週',
+'pastmonth' => '上一月',
+'pastyear' => '上一年',
+'nolimit' => 'ä¸é™',
+'from' => '從',
+'duein' => '延期到',
+'selectfromdate' => 'é¸æ“‡é–‹å§‹æ—¥æœŸ',
+'selecttodate' => 'é¸æ“‡çµæŸæ—¥æœŸ',
+'showvoters' => '顯示隱è—投票人',
+'roadmap' => '路線圖',
+'roadmapfor' => '版本的路線圖',
+'tasks' => '任務',
+'completed' => '完æˆäº†.',
+'opentasks' => '個打開的任務',
+'of' => '% ,全部',
+'severity5' => 'åš´é‡',
+'severity4' => '高',
+'severity3' => '中',
+'severity2' => '低',
+'severity1' => '很低',
+'Redirect' => '轉å‘',
+'redirectmsg' => '如果您的ç€è¦½å™¨ ä¸æ”¯æŒHTTPé‡å®šå‘請點這個%sHERE%sé€£çµ ',
+'allowclosedcomments' => 'å…許å°é—œé–‰çš„任務評論',
+'comment' => 'è©•è«–',
+'editowncomments' => '編輯自己的評論',
+'reopened' => 'é‡æ–°æ‰“開了',
+'loading' => '載入...',
+'notifyown' => '主管的任務發生變更時也發é€é€šçŸ¥',
+'youremail' => '您的電å­éƒµä»¶åœ°å€',
+'thankyouforbug' => 'æ„Ÿè¬æ‚¨å ±å‘Šæ‚¨çš„å•é¡Œ. 您å¯ä»¥é€šéŽä¸‹é¢çš„連çµçœ‹åˆ°é€™å€‹ä»»å‹™ä¸¦ä¸”隨時觀察它的進度:',
+'anonuser' => '匿å用戶',
+'conflict' => 'è¡çª',
+'file' => '文件',
+'KiB' => 'KB',
+'MiB' => 'MB',
+'size' => '大å°',
+'projectgroup' => '專案群組',
+'profile' => '個人資料:',
+'viewprofile' => '查看個人資料',
+'regdate' => '註冊時間',
+'tasksopened' => '打開的任務',
+'replyto' => '回覆到',
+'notifytypes' => '通知類型',
+'pm.taskchanged' => '任務變更了',
+'pm.taskreopened' => '任務é‡æ–°æ‰“開了',
+'pm.depadded' => 'ä¾è³´å¢žåŠ äº†',
+'pm.depremoved' => 'ä¾è³´ç§»é™¤äº†',
+'pmrequest' => '專案經ç†è«‹æ±‚',
+'pmrequestdenied' => '專案經ç†è«‹æ±‚被拒絶了',
+'newassignee' => '新的被指派人',
+'revdepadded' => 'åå‘ä¾è³´å¢žåŠ äº†',
+'revdepaddedremoved' => 'åå‘ä¾è³´ç§»é™¤äº†',
+'assigneeadded' => '增加了被指派人',
+'addusergroup' => 'å‘這個組增加用戶',
+'groupmembers' => '群組æˆå“¡',
+'deleteuser' => '刪除這個用戶',
+'userdeleted' => '用戶刪除了',
+'autoassign' => '自動為模組主管分é…一個任務',
+'ssl' => 'SSL',
+'updatewrong' => "您é¸æ“‡äº†è‡ªå‹•æ›´æ–°åŠŸèƒ½,\n 但是在連çµä¼ºæœå™¨æ™‚出ç¾å•é¡Œ, 也許您無法訪å•å› ç‰¹ç¶²\n 或者由於網絡故障造æˆ.\n 請訪å•Flyspray 網站確èªæ‚¨é‹è¡Œçš„是最新版本的FlySpray.",
+'deleteproject' => '刪除這個專案並把內容移至',
+'projectdeleted' => '專案被æˆåŠŸåœ°åˆªé™¤äº†',
+'feedforall' => '所有專案的動態訊æ¯',
+'usercreated' => '用戶建立了',
+'created' => '建立了',
+'deleted' => '刪除了',
+'userid' => '用戶ID',
+'editassignments' => '編輯任務指派',
+'preview' => 'é è¦½',
+'anyprogress' => '任何進度',
+'tasksrelated' => '這個任務的關è¯ä»»å‹™',
+'duplicatetasks' => '這個任務的é‡è¤‡ä»»å‹™',
+'databasemodfailed' => '修改資料庫失敗. å¯èƒ½æ˜¯æ²’有足夠的權é™.',
+'frequency' => '頻率',
+'newuserregistered' => '一個新的用戶註冊了. 用戶的詳細情æ³å¦‚下所示:',
+'newuserregisterednotify' => '一個新的用戶註冊了',
+'notify_registration' => '新用戶註冊時通知管ç†å“¡',
+'textversion' => '純文字版本',
+'onlyprimary' => '任務沒有阻止其他任務',
+'onlyblocker' => '任務阻止其他任務',
+'blockerornoblocker' => '攔截器或éžæ””截器,é¸æ“‡å…©å€‹éŽæ¿¾å™¨é¸é …沒有æ„義。',
+'switch' => '進入',
+'max' => '最大',
+'dates' => '日期',
+'selectduedatefrom' => '延期的 從',
+'selectduedateto' => '到',
+'selectsincedatefrom' => '變更的 從',
+'selectsincedateto' => '到',
+'selectdate' => 'é¸æ“‡æ—¥æœŸ',
+'selectopenedfrom' => '打開的 從',
+'selectopenedto' => '到',
+'selectclosedfrom' => '關閉的從',
+'selectclosedto' => '到',
+'startat' => '開始於',
+'hasattachment' => '有附件',
+'private' => 'ä¸å…¬é–‹çš„',
+'watching' => '關注',
+'alreadyvotedthistask' => '您å°é€™å€‹ä»»å‹™æŠ•äº†ç¥¨',
+'alreadyvotedthisday' => '今天已經投éŽç¥¨äº†',
+'visibility' => 'å¯è¦‹',
+'public' => '公開',
+'leaveemptyauto' => '如果您想è¦è‡ªå‹•ç”Ÿæˆå¯†ç¢¼ï¼Œè«‹ä¸è¦å¡«å¯«å¯†ç¢¼æ¬„ä½.',
+'novalidemail' => '您輸入的郵件地å€ä¸æ­£ç¢º.',
+'novalidjabber' => '您輸入的Jabber IDä¸æ­£ç¢º.',
+'missingrequired' => '您沒有填寫全部的必填欄ä½.',
+'entervalidusername' => '請輸入有效的帳號和真實姓å.',
+'couldnotaddusernotif' => 'ä¸èƒ½æŠŠç”¨æˆ¶åŠ å…¥åˆ°é€šçŸ¥åˆ—表.',
+'defaulttask' => 'é è¨­ä»»å‹™æè¿°',
+'all' => '全部',
+'events.useraddedtoassignees'=> '用戶æˆç‚ºè¢«æŒ‡æ´¾äºº',
+'eventlog' => '事件日誌',
+'assignmentchanged' => '指派發生了變動',
+'detailedinfo' => '詳細訊æ¯',
+'All' => '全部',
+'tasksireported' => '我報告的',
+'recentlyopened' => '最近打開的',
+'stats' => '狀態',
+'totaltasks' => '個全部的任務',
+'mostwanted' => '最想è¦çš„任務',
+'defaultentry' => 'é è¨­å…¥å£é é¢',
+'toplevel' => '最上層查看',
+'overview' => '總覽',
+'error#' => '錯誤 #',
+'error1' => '您沒有查看這個附件所需的權é™.',
+'error3' => 'é‡è¤‡çš„æ“作, 回到主é .',
+'error4' => '您沒有管ç†æ¬Šé™.',
+'error5' => '這個用戶ä¸å­˜åœ¨.',
+'error6' => '無效的管ç†å“¡å€åŸŸ.',
+'error7' => '登入失敗(帳號或者密碼錯誤)!',
+'error71' => '帳戶被鎖定%d分é˜ç”±æ–¼å¤ªå¤šå¤±æ•—的登入嘗試!',
+'error8' => '您沒有帳號或者密碼.',
+'error9' => '任務ä¸å­˜åœ¨æˆ–者沒有權é™æŸ¥çœ‹é€™å€‹ä»»å‹™.',
+'error10' => '任務ä¸å­˜åœ¨æˆ–者沒有權é™æŸ¥çœ‹é€™å€‹ä»»å‹™.',
+'error101' => '您沒有權é™æŸ¥çœ‹æ­¤ä»»å‹™.',
+'error102' => '您沒有權é™æŸ¥çœ‹æ­¤ä»»å‹™,登入å¯èƒ½æœƒæœ‰æ‰€å¹«åŠ©.',
+'error11' => '沒有編輯這個評論的權é™.',
+'error12' => '無效的激活碼!請核實一下?',
+'error13' => '匿å用戶沒有個人資料.',
+'error14' => '您沒有足夠的權é™å»ºç«‹ä¸€å€‹æ–°çš„群組.',
+'error15' => '您沒有足夠的權é™æ‰“開一個任務.',
+'error16' => '您ä¸æ˜¯å°ˆæ¡ˆç¶“ç†.',
+'error17' => '無效的專案經ç†å€åŸŸ.',
+'error18' => '無效的激活地å€é€£çµ.',
+'error19' => '用戶ä¸å­˜åœ¨.',
+'error20' => '無效的資料庫修改.',
+'error21' => '郵件發é€å¤±æ•—,請檢查é…ç½®.',
+'error22' => 'ä¸å…許新用戶註冊.',
+'error23' => '用戶或群組ä¸å…許登入.',
+'error24' => '被設定的既ä¸æ˜¯ä¸€å€‹å¯åŸ·è¡Œæ–‡ä»¶ä¹Ÿä¸æ˜¯ä¸€å€‹å…¬å…±ä¼ºæœå™¨.',
+'error25' => 'åªæœ‰åœ¨æŒ‡å®šçš„專案中路線圖æ‰å¯ç”¨.',
+'error26' => 'ä¸æ”¯æŒèªè­‰æ供者.',
+'error27' => '無法登入。授權我們查看您的電å­éƒµä»¶å€åŸŸ.',
+'error28' => '你沒有權é™è¨ªå•è©²åœ°å€',
+'done' => '完æˆ',
+'rss' => 'RSS',
+'atom' => 'Atom',
+'projectnotdeleted' => '專案ä¸èƒ½è¢«åˆªé™¤.',
+'GMT' => 'GMT',
+'timezone' => '時å€',
+'accept' => '接å—',
+'reasonfordeinal' => '拒絶的ç†ç”±',
+'pruneclosedlinks' => '刪除關閉的連çµ',
+'pruneclosedtasks' => '刪除關閉的任務',
+'pagegenerated' => 'é é¢å’Œåœ–形在 %d 秒內生æˆ.',
+'pruninglevel' => '正在刪除級別',
+'lastuser' => '最後的用戶ä¸èƒ½è¢«åˆªé™¤.',
+'allprivate' => '所有的專案都是ä¸å…¬é–‹çš„.',
+'deletegroup' => '刪除這個群組,把用戶移到',
+'parent' => '上級',
+'ordertip' => 'æ¢ç›®çš„é †åºæœƒåœ¨åˆ—表中顯示',
+'showtip' => '在列表中顯示這一æ¢',
+'deletetip' => '從列表中刪除這一æ¢',
+'del' => '刪除',
+'request1' => '一個任務關閉請求.',
+'request2' => '請求é‡æ–°æ‰“開任務.',
+'allpriorities' => '所有優先級',
+'noroadmap' => '沒有å¯ç”¨çš„路線圖(ä¸å­˜åœ¨å°ˆæ¡ˆç‰¹å®šçš„“未來â€ç‰ˆæœ¬)。',
+'expand' => '展開',
+'collapse' => '摺疊',
+'expandall' => '全部展開',
+'collapseall' => '全部摺疊',
+'minpwsize' => '最å°å¯†ç¢¼å¤§å°æ˜¯5å­—å…ƒ',
+'passwordtoosmall' => '密碼長度太å°äº†ã€‚',
+'accountwaslocked' => '你的帳號被鎖,由於太多的失敗的登入嘗試.',
+'failedattempts' => '有%d失敗的登入嘗試.',
+'groupnotexist' => '在這個專案é¸æ“‡çµ„ä¸å­˜åœ¨.',
+'searchindetails' => 'æœå°‹ç´°ç¯€',
+'showasassignees' => '顯示為å—託人',
+'find' => 'æœå°‹',
+'tls' => 'TLS',
+'isadmin' => '是管ç†è€…',
+'addvotes' => '新增投票',
+'removevote' => '刪除投票',
+'voteremoved' => '你的投票被刪除',
+'voteremovefailed' => '你的投票ä¸èƒ½è¢«åˆªé™¤.',
+'novotes' => 'ä½ ç¾åœ¨æ²’有任何å¯æŠ•ç¥¨.',
+'connectedtasks' => '連接的任務:',
+'taskdependencies' => '任務ä¾è³´é—œä¿‚',
+'viewgraph' => '查看圖表',
+'notaskdependencies' => '這個任務ä¸ä¾è³´æ–¼ä»»ä½•å…¶ä»–任務.',
+'dependson' => 'ä¾è³´æ–¼',
+'blocks' => 'å¡Š',
+'newdependency' => 'æ–°çš„ä¾è³´é—œä¿‚:',
+'nouserstoadd' => '沒有用戶新增.請確ä¿å稱,帳號和電å­éƒµä»¶æ˜¯ç‚ºæ¯å€‹ç”¨æˆ¶å®šç¾©çš„.',
+'dispintro' => '顯示主介紹訊æ¯',
+'mainmessage' => '主介紹訊æ¯',
+'setsupertask' => '設定Super-Task ID:',
+'supertaskmodified' => 'Super-Task ID被修改',
+'set' => '設定',
+'supertask' => 'Super-Task',
+'setparent' => '為本任務設定父任務ID',
+'selfsupertasknotallowed' => 'Super-Task IDä¸èƒ½å’Œä»»å‹™ID相åŒ',
+'quickaction' => ' 快速æ“作',
+'updateselectedtasks' => '更新所é¸ä»»å‹™',
+'notspecified' => '未指定',
+'editselectedtasks' => '編輯所é¸ä»»å‹™',
+'information' => '訊æ¯',
+'taskclosedisabled' => 'ç¦æ­¢é—œé–‰ç•¶å‰ä»»å‹™,å› ä¾è³´ä»»å‹™å°šæœªé—œé–‰:-',
+'daysleft' => '天還剩',
+'dayoverdue' => '天éŽæœŸ',
+'duetoday' => '今天到期',
+'daysbeforealert' => '天å‰æ醒',
+'associatedsubtask' => '相關的å­ä»»å‹™#FS',
+'associatesubtask' => '為本任務設定å­ä»»å‹™ID',
+'subtaskid' => 'å­ä»»å‹™ID',
+'subtaskalreadyhasparent' => '您輸入的å­ä»»å‹™å·²ç¶“有一個父任務,請明確此關è¯.',
+'subtaskisparent' => '您輸入的å­ä»»å‹™æ˜¯çˆ¶ä»»å‹™çš„任務.å­ä»»å‹™ç„¡é—œ',
+'subtasknotexist' => '您輸入的å­ä»»å‹™ä¸å­˜åœ¨.',
+'subtaskremovedmsg' => 'å­ä»»å‹™å·²ç¶“æˆåŠŸè¢«ç§»é™¤',
+'subtaskadded' => '新增å­ä»»å‹™',
+'subtaskremoved' => '刪除å­ä»»å‹™',
+'addnewsubtask' => '新增新的å­ä»»å‹™',
+'hidesubtasks' => 'éš±è—å­ä»»å‹™',
+'voteforthistask' => '為本任務投票',
+'watchthistask' => '查看這個任務',
+'privatethistask' => '置為ä¸å…¬é–‹ä»»å‹™',
+'adddependenttask' => '增加ä¾è³´ä»»å‹™',
+'associatetaskid' => 'å­ä»»å‹™id',
+'parenttaskid' => '父任務id',
+'invalidsupertaskid' => '父任務id無效.',
+'supertaskadded' => '新增超級任務',
+'supertaskremoved' => '刪除超級任務',
+'effort' => '工作é‡',
+'efforttracking' => '工作é‡è·Ÿè¹¤',
+'useeffort' => '專案使用的工作é‡è·Ÿè¹¤',
+'estimatedeffort' => '估計工作é‡',
+'totalestimatedeffort' => '估計總工作é‡',
+'currenteffortdone' => '當å‰å®Œæˆå·¥ä½œé‡',
+'starteffort' => '開始跟蹤',
+'endeffort' => 'åœæ­¢è·Ÿè¹¤',
+'cleareffort' => '清除跟蹤',
+'addeffort' => '新增跟蹤',
+'manualeffort' => '手動新增跟蹤(H:M)',
+'efforttrackingstarted' => '開始跟蹤這個任務的工作é‡',
+'efforttrackingnotstarted'=> '無法開始跟蹤,這個å•é¡Œå·²ç¶“被跟蹤',
+'efforttrackingstopped' => '這個任務的工作é‡è·Ÿè¹¤çµæŸ.',
+'efforttrackingcancelled' => '這個任務的工作é‡è·Ÿè¹¤è¢«å–消.',
+'efforttrackingadded' => '手動記錄工作é‡çš„任務.',
+'trackinginprogress' => '跟蹤éŽç¨‹ä¸­',
+'viewestimatedeffort' => 'å¯ä»¥æŸ¥çœ‹å·¥ä½œé‡è·Ÿè¹¤',
+'viewcurrenteffortdone' => 'å¯ä»¥æŸ¥çœ‹ç•¶å‰å·¥ä½œé‡åšäº†ä»€éº¼',
+'trackeffort' => 'å¯ä»¥è·Ÿè¹¤å·¥ä½œé‡',
+'invalideffort' => '輸入的是工作é‡ç„¡æ•ˆçš„,必須是一個數字',
+'showpass' => '顯示密碼',
+'chooseafile' => 'è«‹é¸æ“‡ä¸€å€‹æ–‡ä»¶!',
+'incorrectfiletype' => '錯誤的文件類型.å…許:jpg,jpeg,gif,png',
+'oauthreqpass' => '沒有密碼請求.你註冊通éŽ%s',
+'addmultipletasks' => '新增多個任務',
+'pendingnewuserrequest' => '等待請求的新用戶',
+'adminrequestswaiting' => '等待管ç†å“¡è«‹æ±‚',
+'clicktoedit' => '點擊æ¯å€‹æ¬„ä½å¿«é€Ÿç·¨è¼¯',
+'confirmedit' => '確èª',
+'regapprovedbyadmin' => '由管ç†å“¡è¨»å†Šæ‰¹å‡†(ç¦ç”¨ç¢ºèªç¨‹å¼ç¢¼)',
+'activity' => '活動',
+'myactivity' => '我的活動',
+'emailverificationwrong' => 'é›»å­éƒµä»¶ä¸åŒ¹é…給定的電å­éƒµä»¶åœ°å€',
+'verifyemailaddress' => '確èªé›»å­éƒµä»¶åœ°å€',
+'hideemails' => 'éš±è—用戶電å­éƒµä»¶åœ°å€',
+'hidemyemail' => 'éš±è—我的電å­éƒµä»¶åœ°å€',
+'exporttasklist' => '匯出任務列表',
+'onedecimal' => '一個å°æ•¸',
+'manday' => '人工日',
+'mandays' => '人工日',
+'mandayabbrev' => '簡寫的人工日',
+'hourspermanday' => '一個人工日的工作時 (HH:mm)',
+'itemexists' => '專案%s已經存在於資料庫中',
+'categoryitemexists' => '專案%s已經存在在%s資料庫類.',
+'pageswelcomemsg' => 'é é¢é¡¯ç¤ºä¸»è¦ä»‹ç´¹è¨Šæ¯',
+'pagesintromsg' => 'é é¢é¡¯ç¤ºä»‹ç´¹è¨Šæ¯',
+'activeoauths' => 'æ´»èºçš„èªè­‰æ供者',
+'onlyoauthreg' => 'åªå…許èªè­‰è€…註冊',
+'estimatedeffortformat' => '估計工作é‡é¡¯ç¤ºæ ¼å¼',
+'currenteffortdoneformat' => '當å‰å®Œæˆå·¥ä½œé‡é¡¯ç¤ºæ ¼å¼',
+'minute' => '分é˜',
+'minutes' => '分é˜',
+'minuteplural' => '分é˜',
+'minutesingular' => '分é˜',
+'minuteabbrev' => '最å°å€¼',
+'hourplural' => 'å°æ™‚',
+'hoursingular' => 'å°æ™‚',
+'hourabbrev' => 'h',
+'estimatedeffortopen' => '估計開放任務工作é‡',
+'currenteffortdoneopen' => '當å‰é–‹æ”¾ä»»å‹™è€—費工作é‡',
+'signinwith' => '登入%s',
+'canviewroadmap' => 'å¯ä»¥æŸ¥çœ‹è·¯ç·šåœ–',
+'enableavatars' => '啟用頭åƒ',
+'maxavatarsize' => '最大頭åƒåƒç´ å€¼',
+'taskhassubtask' => '這個任務有以下å­ä»»å‹™',
+'taskhassubtasks' => '這個任務有以下å­ä»»å‹™',
+'translations' => '翻譯',
+'translate' => '翻譯',
+'taskdescription' => '任務æè¿°',
+'notaskdescription' => '沒有任務æè¿°',
+'pleaseselect' => 'è«‹é¸æ“‡',
+'closeselectedtasks' => '關閉é¸æ“‡ä»»å‹™',
+'closetasks' => '關閉任務',
+'hintforbulkimport' => "<b>批次導入的技巧:</b>\n<ol>\n<li>複製和貼上從excelé›»å­è¡¨æ ¼æˆ–CSV貼上一個整列.</li>\n<li>ç›®å‰ä½ åªèƒ½è²¼ä¸Šå½™ç¸½å’Œç´°ç¯€.</li>\n<li>There are suggestions when you assign to someone, and to no-one if there is no matched name.</li>\n</ol>",
+'taskissubtaskof' => '本任務的父任務',
+'applyfirstline' => '應用第一行',
+'addmorerows' => '新增更多的行',
+'addtasks' => '新增任務',
+'massopsdisabled' => 'å°ä¸èµ·,批次編輯目å‰åœ¨Flyspray 1.0被ç¦æ­¢.我們計劃在後é¢çš„版本進行實ç¾.You can enable them in source code again at your own risk, but read the comments there before doing it.',
+'viewroadmap' => 'å¯ä»¥æŸ¥çœ‹è·¯ç·šåœ–',
+'nosuicide' => '親愛的用戶,因為管ç†å“¡å·²ç¶“ç¦ç”¨äº†æ‚¨çš„帳戶,您ä¸å…許訪å•ç¶²ç«™æˆ–改變用戶組。',
+'movingtodifferentproject'=> '移動一個父任務或å­ä»»å‹™åˆ°å¦ä¸€å€‹å°ˆæ¡ˆæ˜¯ä¸å…許的.你首先必須打破他們之間的è¯ç¹«ã€‚',
+'musthavesameproject' => '父任務和å­ä»»å‹™å¿…須屬於åŒä¸€å€‹å°ˆæ¡ˆ.',
+'defaultorderby' => '排åºé è¨­ä»»å‹™åˆ—表',
+'defaultorderby2' => '與',
+'viewowntasks' => '查看自己的任務',
+'viewgroupstasks' => '查看任務組',
+'urlrewriting' => '覆寫URL',
+'enablehtaccess' => '請在覆寫URL之å‰è®“ä½ çš„.htaccess文件在Flyspray根目錄',
+'nomodrewrite' => 'é‡å¯«åœ¨é€™å€‹æœå‹™ä¸Šä¼¼ä¹Žä¸å¯ç”¨ï¼Œä¸å¥½æ„æ€ï¼Œæˆ‘ä¸èƒ½è¦†å¯«URL在',
+'on' => '打開',
+'off' => '關閉',
+'defaultorderbydirection' => '按é è¨­æ–¹å‘',
+'ascending' => 'å‡åº',
+'descending' => 'é™åº',
+'myassignedtasks' => '我分é…的任務',
+'commentedon' => 'è©•è«–',
+'maxvoteperday' => 'æ¯å¤©æœ€å¤šé¸ç¥¨',
+'votesperproject' => '在æ¯å€‹å°ˆæ¡ˆä¸­ç”¨æˆ¶è¢«é™åˆ¶çš„é¸ç¥¨',
+'votelimitreached' => '你的投票é™è£½ç‚ºé€™å€‹å°ˆæ¡ˆã€‚在個人資料é é¢æŸ¥çœ‹å“ªäº›ä»»å‹™å¯ä»¥æŠ•ç¥¨.你也å¯ä»¥æ”¶å›žçš„é¸ç¥¨.我們å¯ä»¥çœ‹åˆ°å“ªäº›ä»»å‹™å°ä½ æ˜¯æœ€é‡è¦çš„.解決任務å¯ä»¥æ”¶å›žä½ çš„é¸ç¥¨.',
+'myvotes' => '我的é¸ç¥¨',
+'tag' => '標籤',
+'tags' => '標籤',
+'tagsinfo' => '任務的自由標記/標籤:是單ç¨çš„標籤;它們還ä¸èƒ½ä½¿ç”¨æœå°‹ã€æŽ’åºæˆ–éŽæ¿¾.',
+'novalues' => '沒有æ¢ç›®',
+'youhaveregistered' => '你已經在Flyspray註冊,你的詳細資料如下:',
+'youhaveregisterednotify' => '你在Flyspray上的註冊已經被管ç†å“¡æŽ¥å—.',
+'usedintasks' => '用法',
+'freetagging' => 'å…許用戶定義標籤',
+'keyboardshortcuts' => '快速éµ',
+'openselectedtask' => 'é–‹å•Ÿé¸æ“‡çš„任務',
+'usedintasks' => '使用é‡',
+'testmailsettings' => '測試目å‰çš„é›»å­éƒµä»¶ä¼ºæœå™¨è¨­å®š',
+'test' => '測試',
+'testmailsettingsnotice' => 'And also check if you received the test email in the mail account of the current user (see \'myprofile\'-page)',
+'customize' => '自訂',
+'movecursorup' => '游標å‘上',
+'movecursordown' => '游標å‘下',
+'taskdetails' => '詳細任務',
+'taskediting' => '編輯任務',
+'savetask' => '儲存任務',
+'hidesubs' => 'éš±è—éžä¸»è¦ä»»å‹™',
+'hideprivate' => 'éš±è—éš±ç§ä»»å‹™',
+'hideclosed' => 'éš±è—關閉任務',
+'currentproject' => 'ç›®å‰ä»»å‹™',
+'targetproject' => '目標任務',
+'availablekeybshortcuts' => 'å¯ç”¨çš„快速éµ',
+'logindialoglogout' => '登入登出紀錄',
+
+);
+
+?>
diff --git a/phpunit_mysql.xml b/phpunit_mysql.xml
new file mode 100644
index 0000000..1e51cc1
--- /dev/null
+++ b/phpunit_mysql.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php" colors="true" backupGlobals="false">
+<php>
+ <var name="db_dsn" value="mysql:dbname=flyspray_test;host=localhost"/>
+ <var name="dbtype" value="mysqli"/>
+ <var name="dbhost" value="localhost"/>
+ <var name="dbname" value="flyspray_test"/>
+ <var name="dbuser" value="root"/>
+ <var name="dbpass" value=""/>
+ <var name="dbprefix" value="flyspray_"/>
+</php>
+<testsuites>
+ <testsuite name="Flyspray Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+</testsuites>
+<filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+</filter>
+</phpunit>
diff --git a/phpunit_pgsql.xml b/phpunit_pgsql.xml
new file mode 100644
index 0000000..15565f5
--- /dev/null
+++ b/phpunit_pgsql.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php" colors="true" backupGlobals="false">
+<php>
+ <var name="db_dsn" value="pgsql:dbname=flyspray_test;host=localhost"/>
+ <var name="dbtype" value="pgsql"/>
+ <var name="dbhost" value="localhost"/>
+ <var name="dbname" value="flyspray_test"/>
+ <var name="dbuser" value="postgres"/>
+ <var name="dbpass" value=""/>
+ <var name="dbprefix" value="flyspray_"/>
+</php>
+<testsuites>
+ <testsuite name="Flyspray Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+</testsuites>
+<filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+</filter>
+</phpunit>
diff --git a/plugins/.htaccess b/plugins/.htaccess
new file mode 100644
index 0000000..3777c6a
--- /dev/null
+++ b/plugins/.htaccess
@@ -0,0 +1,8 @@
+<Files *.php>
+Deny From All
+</Files>
+
+<Files fetch.php>
+Allow From All
+</Files>
+
diff --git a/plugins/dokuwiki/conf/.htaccess b/plugins/dokuwiki/conf/.htaccess
new file mode 100644
index 0000000..bcc3ea0
--- /dev/null
+++ b/plugins/dokuwiki/conf/.htaccess
@@ -0,0 +1,3 @@
+## no access to the conf directory
+order allow,deny
+deny from all
diff --git a/plugins/dokuwiki/conf/acronyms.conf b/plugins/dokuwiki/conf/acronyms.conf
new file mode 100644
index 0000000..2cfedea
--- /dev/null
+++ b/plugins/dokuwiki/conf/acronyms.conf
@@ -0,0 +1,143 @@
+# Acronyms.
+
+ACL Access Control List
+AFAICS As far as I can see
+AFAIK As far as I know
+AJAX Asynchronous JavaScript and XML
+AIM AOL (America Online) Instant Messenger
+AOL America Online
+API Application Programming Interface
+ASAP As soon as possible
+ASCII American Standard Code for Information Interchange
+ASP Active Server Pages
+BTW By the way
+CGI Common Gateway Interface
+CMS Content Management System
+CSS Cascading Style Sheets
+CVS Concurrent Versions System
+DBA Database Administrator
+DHTML Dynamic HyperText Markup Language
+DMCA Digital Millenium Copyright Act
+DNS Domain Name Server
+DOM Document Object Model
+DTD Document Type Definition
+EOF End of file
+EOL End of line
+EOM End of message
+EOT End of text
+ESMTP Extended Simple Mail Transfer Protocol
+FAQ Frequently Asked Questions
+FDL GNU Free Documentation License
+FTP File Transfer Protocol
+FOSS Free & Open-Source Software
+FLOSS Free/Libre and Open Source Software
+FUD Fear, Uncertainty, and Doubt
+GB Gigabyte
+GHz Gigahertz
+GIF Graphics Interchange Format
+GPL GNU General Public License
+GUI Graphical User Interface
+HTML HyperText Markup Language
+HTTP Hyper Text Transfer Protocol
+HTTPS Hypertext Transfer Protocol Secure
+IANAL I am not a lawyer (but)
+ICANN Internet Corporation for Assigned Names and Numbers
+ICQ I seek you (Instant Messenger)
+IE5 Internet Explorer 5
+IE6 Internet Explorer 6
+IE Internet Explorer
+IIRC If I remember correctly
+IIS Internet Information Services
+IMAP Internet Message Access Protocol
+IMHO In my humble opinion
+IMO In my opinion
+IOW In other words
+IRC Internet Relay Chat
+IRL In real life
+ISO International Organization for Standardization
+ISP Internet Service Provider
+JDK Java Development Kit
+JPEG Joint Photographics Experts Group
+JPG Joint Photographics Experts Group
+JS JavaScript
+KISS Keep it simple stupid
+LGPL GNU Lesser General Public License
+LOL Laughing out loud
+MathML Mathematical Markup Language
+MB Megabyte
+MHz Megahertz
+MIME Multipurpose Internet Mail Extension
+MIT Massachusetts Institute of Technology
+MML Mathematical Markup Language
+MP3 Motion Picture Experts Group Layer 3
+MPEG Motion Picture Experts Group
+MSDN Microsoft Developer Network
+MS Microsoft
+MSIE Microsoft Internet Explorer
+NS4.7 Netscape 4.7
+NS4 Netscape 4
+NS6 Netscape 6
+NS7 Netscape 7
+OMG Oh my God
+OPML Outline Processor Markup Language
+OS Operating System
+OSS Open Source Software
+OTOH On the other hand
+P2P Peer to Peer
+PDA Personal Digital Assistant
+PDF Portable Document Format
+Perl Practical Extraction and Report Language
+PERL Practical Extraction and Report Language
+PHP Hypertext Preprocessor
+PICS Platform for Internet Content Selection
+PIN Personal Identification Number
+PITA Pain in the Ass
+PNG Portable Network Graphics
+POP3 Post Office Protocol 3
+POP Post Office Protocol
+QoS Quality of Service
+RAID Redundant Array of Inexpensive Disks
+RFC Request for Comments (Internet Standards)
+RDF Resource Description Framework
+ROTFL Rolling on the floor laughing
+RPC Remote Procedure Call
+RSS Rich Site Summary
+RTFM Read The Fine Manual
+RTF Rich Text File
+SCSI Small Computer System Interface
+SDK Software Development Kit
+SGML Standard General Markup Language
+SMIL Synchronized Multimedia Integration Language
+SMTP Simple Mail Transfer Protocol
+SOAP Simple Object Access Protocol
+spec specification
+SQL Structured Query Language
+SSH Secure Shell
+SSI Server Side Includes
+SSL Secure Sockets Layer
+SVG Scalable Vector Graphics
+TIA Thanks in advance
+TIFF Tagged Image File Format
+TLD Top Level Domain
+TLS Transport Layer Security
+TOC Table of Contents
+URI Uniform Resource Identifier
+URL Uniform Resource Locator
+URN Uniform Resource Name
+VBA Visual Basic for Applications
+VB Visual Basic
+W3C World Wide Web Consortium
+WAN Wide Area Network
+WAP Wireless Access Protocol
+WML Wireless Markup Language
+WTF? What the f***
+WWW World Wide Web
+WYSIWYG What You See Is What You Get
+XHTML Extensible HyperText Markup Language
+XML Extensible Markup Language
+XMPP Extensible Messaging and Presence Protocol
+XSD XML (Extensible Markup Language) Schema Definition
+XSL Extensible Stylesheet Language
+XSLT Extensible Stylesheet Language Transformations
+XUL XML User Interface Language
+YMMV Your mileage may vary
diff --git a/plugins/dokuwiki/conf/dokuwiki.php b/plugins/dokuwiki/conf/dokuwiki.php
new file mode 100644
index 0000000..21dd285
--- /dev/null
+++ b/plugins/dokuwiki/conf/dokuwiki.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * This is DokuWiki's Main Configuration file
+ *
+ * All the default values are kept here, you should not modify it but use
+ * a local.php file instead to override the settings from here.
+ *
+ * This is a piece of PHP code so PHP syntax applies!
+ *
+ * For help with the configuration see http://www.splitbrain.org/dokuwiki/wiki:config
+ */
+
+
+/* Datastorage and Permissions */
+
+$conf['fmode'] = 0644; //set file creation mode
+$conf['dmode'] = 0755; //set directory creation mode
+$conf['lang'] = 'en'; //your language
+$conf['basedir'] = ''; //absolute dir from serveroot - blank for autodetection
+$conf['baseurl'] = ''; //URL to server including protocol - blank for autodetect
+$conf['savedir'] = './data'; //where to store all the files
+$conf['allowdebug'] = 0; //allow debug output, enable if needed 0|1
+
+/* Display Options */
+
+$conf['start'] = 'start'; //name of start page
+$conf['title'] = 'DokuWiki'; //what to show in the title
+$conf['template'] = 'default'; //see tpl directory
+$conf['fullpath'] = 0; //show full path of the document or relative to datadir only? 0|1
+$conf['recent'] = 20; //how many entries to show in recent
+$conf['breadcrumbs'] = 10; //how many recent visited pages to show
+$conf['youarehere'] = 0; //show "You are here" navigation? 0|1
+$conf['typography'] = 1; //convert quotes, dashes and stuff to typographic equivalents? 0|1
+$conf['htmlok'] = 0; //may raw HTML be embedded? This may break layout and XHTML validity 0|1
+$conf['phpok'] = 0; //may PHP code be embedded? Never do this on the internet! 0|1
+$conf['dformat'] = 'Y/m/d H:i'; //dateformat accepted by PHPs date() function
+$conf['signature'] = ' --- //[[@MAIL@|@NAME@]] @DATE@//'; //signature see wiki:config for details
+$conf['toptoclevel'] = 1; //Level starting with and below to include in AutoTOC (max. 5)
+$conf['maxtoclevel'] = 3; //Up to which level include into AutoTOC (max. 5)
+$conf['maxseclevel'] = 3; //Up to which level create editable sections (max. 5)
+$conf['camelcase'] = 0; //Use CamelCase for linking? (I don't like it) 0|1
+$conf['deaccent'] = 1; //deaccented chars in pagenames (1) or romanize (2) or keep (0)?
+$conf['useheading'] = 0; //use the first heading in a page as its name
+$conf['refcheck'] = 1; //check for references before deleting media files
+$conf['refshow'] = 0; //how many references should be shown, 5 is a good value
+
+/* Antispam Features */
+
+$conf['usewordblock']= 1; //block spam based on words? 0|1
+$conf['indexdelay'] = 60*60*24*5; //allow indexing after this time (seconds) default is 5 days
+$conf['relnofollow'] = 1; //use rel="nofollow" for external links?
+$conf['mailguard'] = 'hex'; //obfuscate email addresses against spam harvesters?
+ //valid entries are:
+ // 'visible' - replace @ with [at], . with [dot] and - with [dash]
+ // 'hex' - use hex entities to encode the mail address
+ // 'none' - do not obfuscate addresses
+
+/* Authentication Options - read http://www.splitbrain.org/dokuwiki/wiki:acl */
+
+$conf['useacl'] = 0; //Use Access Control Lists to restrict access?
+$conf['autopasswd'] = 1; //autogenerate passwords and email them to user
+$conf['authtype'] = 'plain'; //which authentication backend should be used
+$conf['passcrypt'] = 'smd5'; //Used crypt method (smd5,md5,sha1,ssha,crypt,mysql,my411)
+$conf['defaultgroup']= 'user'; //Default groups new Users are added to
+$conf['superuser'] = '!!not set!!'; //The admin can be user or @group
+$conf['profileconfirm'] = '1'; //Require current password to confirm changes to user profile
+$conf['disableactions'] = ''; //comma separated list of actions to disable
+
+/* Advanced Options */
+
+$conf['updatecheck'] = 1; //automatically check for new releases?
+$conf['userewrite'] = 0; //this makes nice URLs: 0: off 1: .htaccess 2: internal
+$conf['useslash'] = 0; //use slash instead of colon? only when rewrite is on
+$conf['usedraft'] = 1; //automatically save a draft while editing (0|1)
+$conf['sepchar'] = '_'; //word separator character in page names; may be a
+ // letter, a digit, '_', '-', or '.'.
+$conf['canonical'] = 0; //Should all URLs use full canonical http://... style?
+$conf['autoplural'] = 0; //try (non)plural form of nonexisting files?
+$conf['compression'] = 'gz'; //compress old revisions: (0: off) ('gz': gnuzip) ('bz2': bzip)
+ // bz2 generates smaller files, but needs more cpu-power
+$conf['cachetime'] = 60*60*24*10; //maximum age for cachefile in seconds (defaults to a day)
+$conf['locktime'] = 15*60; //maximum age for lockfiles (defaults to 15 minutes)
+$conf['fetchsize'] = 0; //maximum size (bytes) fetch.php may download from extern, disabled by default
+$conf['notify'] = ''; //send change info to this email (leave blank for nobody)
+$conf['registernotify'] = ''; //send info about newly registered users to this email (leave blank for nobody)
+$conf['mailfrom'] = ''; //use this email when sending mails
+$conf['gzip_output'] = 0; //use gzip content encodeing for the output xhtml (if allowed by browser)
+$conf['gdlib'] = 2; //the GDlib version (0, 1 or 2) 2 tries to autodetect
+$conf['im_convert'] = ''; //path to ImageMagicks convert (will be used instead of GD)
+$conf['jpg_quality'] = '70'; //quality of compression when scaling jpg images (0-100)
+$conf['spellchecker']= 0; //enable Spellchecker (needs PHP >= 4.3.0 and aspell installed)
+$conf['subscribers'] = 0; //enable change notice subscription support
+$conf['compress'] = 1; //Strip whitespaces and comments from Styles and JavaScript? 1|0
+$conf['hidepages'] = ''; //Regexp for pages to be skipped from RSS, Search and Recent Changes
+$conf['send404'] = 0; //Send a HTTP 404 status for non existing pages?
+$conf['sitemap'] = 0; //Create a google sitemap? How often? In days.
+$conf['rss_type'] = 'rss1'; //type of RSS feed to provide, by default:
+ // 'rss' - RSS 0.91
+ // 'rss1' - RSS 1.0
+ // 'rss2' - RSS 2.0
+ // 'atom' - Atom 0.3
+$conf['rss_linkto'] = 'diff'; //what page RSS entries link to:
+ // 'diff' - page showing revision differences
+ // 'page' - the revised page itself
+ // 'rev' - page showing all revisions
+ // 'current' - most recent revision of page
+$conf['rss_update'] = 5*60; //Update the RSS feed every n minutes (defaults to 5 minutes)
+$conf['recent_days'] = 7; //How many days of recent changes to keep. (days)
+
+//Set target to use when creating links - leave empty for same window
+$conf['target']['wiki'] = '';
+$conf['target']['interwiki'] = '';
+$conf['target']['extern'] = '';
+$conf['target']['media'] = '';
+$conf['target']['windows'] = '';
+
+//Proxy setup - if your Server needs a proxy to access the web set these
+$conf['proxy']['host'] = '';
+$conf['proxy']['port'] = '';
+$conf['proxy']['user'] = '';
+$conf['proxy']['pass'] = '';
+$conf['proxy']['ssl'] = 0;
+
+/* Safemode Hack */
+
+$conf['safemodehack'] = 0; //read http://wiki.splitbrain.org/wiki:safemodehack !
+$conf['ftp']['host'] = 'localhost';
+$conf['ftp']['port'] = '21';
+$conf['ftp']['user'] = 'user';
+$conf['ftp']['pass'] = 'password';
+$conf['ftp']['root'] = '/home/user/htdocs';
+
diff --git a/plugins/dokuwiki/conf/entities.conf b/plugins/dokuwiki/conf/entities.conf
new file mode 100644
index 0000000..888224d
--- /dev/null
+++ b/plugins/dokuwiki/conf/entities.conf
@@ -0,0 +1,21 @@
+# Typography replacements
+#
+# Order does matter!
+#
+# Do not use HTML entities here because it may break non-HTML renderers (XML Atomfeed for instance).
+# Use UTF-8 chars directly instead.
+
+<-> ↔
+-> →
+<- â†
+<=> ⇔
+=> ⇒
+<= â‡
+>> »
+<< «
+--- —
+-- –
+(c) ©
+(tm) â„¢
+(r) ®
+... …
diff --git a/plugins/dokuwiki/conf/interwiki.conf b/plugins/dokuwiki/conf/interwiki.conf
new file mode 100644
index 0000000..4ba2f9e
--- /dev/null
+++ b/plugins/dokuwiki/conf/interwiki.conf
@@ -0,0 +1,121 @@
+# Each URL may contain one of the placeholders {URL} or {NAME}
+# {URL} is replaced by the URL encoded representation of the wikiname
+# this is the right thing to do in most cases
+# {NAME} this is replaced by the wikiname as given in the document
+# no further encoding is done
+# If no placeholder is defined the urlencoded name is appended to the URL
+
+# You can add more InterWiki shortcuts here.
+
+wp http://en.wikipedia.org/wiki/
+wpde http://de.wikipedia.org/wiki/
+wpmeta http://meta.wikipedia.org/wiki/
+doku http://wiki.splitbrain.org/
+rfc https://tools.ietf.org/html/rfc
+sb http://www.splitbrain.org/go/
+amazon http://www.amazon.com/exec/obidos/ASIN/
+amazon.de http://www.amazon.de/exec/obidos/ASIN/
+amazon.uk http://www.amazon.co.uk/exec/obidos/ASIN/
+google.de http://www.google.de/search?q=
+man http://man.cx/
+phpfn http://www.php.net/{NAME}
+go http://www.google.com/search?q={URL}&amp;btnI=lucky
+bug http://bugs.splitbrain.org/index.php?do=details&amp;id=
+coral http://{HOST}.{PORT}.nyud.net:8090/{PATH}?{QUERY}
+xref http://dev.splitbrain.org/reference/dokuwiki/{NAME}.html
+
+# Standards from http://usemod.com/intermap.txt follow
+
+AbbeNormal http://www.ourpla.net/cgi-bin/pikie.cgi?
+AcadWiki http://xarch.tu-graz.ac.at/autocad/wiki/
+Acronym http://www.acronymfinder.com/af-query.asp?String=exact&amp;Acronym=
+Advogato http://www.advogato.org/
+AIWiki http://www.ifi.unizh.ch/ailab/aiwiki/aiw.cgi?
+ALife http://news.alife.org/wiki/index.php?
+AndStuff http://andstuff.org/wiki.php?
+Annotation http://bayle.stanford.edu/crit/nph-med.cgi/
+AnnotationWiki http://www.seedwiki.com/page.cfm?wikiid=368&amp;doc=
+AwarenessWiki http://taoriver.net/aware/
+BenefitsWiki http://www.benefitslink.com/cgi-bin/wiki.cgi?
+BridgesWiki http://c2.com/w2/bridges/
+C2find http://c2.com/cgi/wiki?FindPage&amp;value=
+Cache http://www.google.com/search?q=cache:
+CLiki http://ww.telent.net/cliki/
+CmWiki http://www.ourpla.net/cgi-bin/wiki.pl?
+CreationMatters http://www.ourpla.net/cgi-bin/wiki.pl?
+DejaNews http://www.deja.com/=dnc/getdoc.xp?AN=
+DeWikiPedia http://www.wikipedia.de/wiki.cgi?
+Dictionary http://www.dict.org/bin/Dict?Database=*&amp;Form=Dict1&amp;Strategy=*&amp;Query=
+DiveIntoOsx http://diveintoosx.org/
+DocBook http://docbook.org/wiki/moin.cgi/
+DolphinWiki http://www.object-arts.com/wiki/html/Dolphin/
+EfnetCeeWiki http://purl.net/wiki/c/
+EfnetCppWiki http://purl.net/wiki/cpp/
+EfnetPythonWiki http://purl.net/wiki/python/
+EfnetXmlWiki http://purl.net/wiki/xml/
+EljWiki http://elj.sourceforge.net/phpwiki/index.php/
+EmacsWiki http://www.emacswiki.org/cgi-bin/wiki.pl?
+FinalEmpire http://final-empire.sourceforge.net/cgi-bin/wiki.pl?
+Foldoc http://www.foldoc.org/foldoc/foldoc.cgi?
+FoxWiki http://fox.wikis.com/wc.dll?Wiki~
+FreeBSDman http://www.FreeBSD.org/cgi/man.cgi?apropos=1&amp;query=
+Google http://www.google.com/search?q=
+GoogleGroups http://groups.google.com/groups?q=
+GreenCheese http://www.greencheese.org/
+HammondWiki http://www.dairiki.org/HammondWiki/index.php3?
+Haribeau http://wiki.haribeau.de/cgi-bin/wiki.pl?
+IAWiki http://www.IAwiki.net/
+IMDB http://us.imdb.com/Title?
+JargonFile http://sunir.org/apps/meta.pl?wiki=JargonFile&amp;redirect=
+JiniWiki http://www.cdegroot.com/cgi-bin/jini?
+JspWiki http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=
+KmWiki http://www.voght.com/cgi-bin/pywiki?
+KnowHow http://www2.iro.umontreal.ca/~paquetse/cgi-bin/wiki.cgi?
+LanifexWiki http://opt.lanifex.com/cgi-bin/wiki.pl?
+LegoWiki http://www.object-arts.com/wiki/html/Lego-Robotics/
+LinuxWiki http://www.linuxwiki.de/
+LugKR http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?
+MathSongsWiki http://SeedWiki.com/page.cfm?wikiid=237&amp;doc=
+MbTest http://www.usemod.com/cgi-bin/mbtest.pl?
+MeatBall http://www.usemod.com/cgi-bin/mb.pl?
+MetaWiki http://sunir.org/apps/meta.pl?
+MetaWikiPedia http://meta.wikipedia.com/wiki/
+MoinMoin http://purl.net/wiki/moin/
+MuWeb http://www.dunstable.com/scripts/MuWebWeb?
+NetVillage http://www.netbros.com/?
+OpenWiki http://openwiki.com/?
+OrgPatterns http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns?
+PangalacticOrg http://www.pangalactic.org/Wiki/
+PersonalTelco http://www.personaltelco.net/index.cgi/
+PhpWiki http://phpwiki.sourceforge.net/phpwiki/index.php?
+Pikie http://pikie.darktech.org/cgi/pikie?
+PPR http://c2.com/cgi/wiki?
+PurlNet http://purl.oclc.org/NET/
+PythonInfo http://www.python.org/cgi-bin/moinmoin/
+PythonWiki http://www.pythonwiki.de/
+PyWiki http://www.voght.com/cgi-bin/pywiki?
+SeaPig http://www.seapig.org/
+SeattleWireless http://seattlewireless.net/?
+SenseisLibrary http://senseis.xmp.net/?
+Shakti http://cgi.algonet.se/htbin/cgiwrap/pgd/ShaktiWiki/
+SourceForge http://sourceforge.net/{NAME}
+Squeak http://minnow.cc.gatech.edu/squeak/
+StrikiWiki http://ch.twi.tudelft.nl/~mostert/striki/teststriki.pl?
+SVGWiki http://www.protocol7.com/svg-wiki/default.asp?
+Tavi http://tavi.sourceforge.net/index.php?
+TmNet http://www.technomanifestos.net/?
+TMwiki http://www.EasyTopicMaps.com/?page=
+TWiki http://twiki.org/cgi-bin/view/{NAME}
+TwistedWiki http://purl.net/wiki/twisted/
+Unreal http://wiki.beyondunreal.com/wiki/
+UseMod http://www.usemod.com/cgi-bin/wiki.pl?
+VisualWorks http://wiki.cs.uiuc.edu/VisualWorks/
+WebDevWikiNL http://www.promo-it.nl/WebDevWiki/index.php?page=
+WebSeitzWiki http://webseitz.fluxent.com/wiki/
+Why http://clublet.com/c/c/why?
+Wiki http://c2.com/cgi/wiki?
+WikiPedia http://www.wikipedia.com/wiki/
+WikiWorld http://WikiWorld.com/wiki/index.php/
+YpsiEyeball http://sknkwrks.dyndns.org:1957/writewiki/wiki.pl?
+ZWiki http://www.zwiki.org/
+
diff --git a/plugins/dokuwiki/conf/mime.conf b/plugins/dokuwiki/conf/mime.conf
new file mode 100644
index 0000000..90356ff
--- /dev/null
+++ b/plugins/dokuwiki/conf/mime.conf
@@ -0,0 +1,41 @@
+#Add extensions and mimetypes of files you want to allow to upload here
+
+jpg image/jpeg
+jpeg image/jpeg
+gif image/gif
+png image/png
+tgz application/octet-stream
+tar application/x-gtar
+gz application/octet-stream
+zip application/zip
+pdf application/pdf
+txt text/plain
+ps application/postscript
+doc application/msword
+xls application/msexcel
+ppt application/mspowerpoint
+rtf application/msword
+xml text/xml
+swf application/x-shockwave-flash
+
+# You should enable HTML uploads only for restricted Wikis.
+# Spammers are known to upload spam pages through unprotected Wikis.
+#html text/html
+#htm text/html
+
+rpm application/octet-stream
+deb application/octet-stream
+conf text/plain
+
+sxw application/soffice
+sxc application/soffice
+sxi application/soffice
+sxd application/soffice
+
+odc application/vnd.oasis.opendocument.chart
+odf application/vnd.oasis.opendocument.formula
+odg application/vnd.oasis.opendocument.graphics
+odi application/vnd.oasis.opendocument.image
+odp application/vnd.oasis.opendocument.presentation
+ods application/vnd.oasis.opendocument.spreadsheet
+odt application/vnd.oasis.opendocument.text
diff --git a/plugins/dokuwiki/conf/smileys.conf b/plugins/dokuwiki/conf/smileys.conf
new file mode 100644
index 0000000..f0e59c9
--- /dev/null
+++ b/plugins/dokuwiki/conf/smileys.conf
@@ -0,0 +1,28 @@
+# Smileys configured here will be replaced by the
+# configured images in the smiley directory
+
+8-) icon_cool.gif
+8-O icon_eek.gif
+8-o icon_eek.gif
+:-( icon_sad.gif
+:-) icon_smile.gif
+=) icon_smile2.gif
+:-/ icon_doubt.gif
+:-\ icon_doubt2.gif
+:-? icon_confused.gif
+:-D icon_biggrin.gif
+:-P icon_razz.gif
+:-o icon_surprised.gif
+:-O icon_surprised.gif
+:-x icon_silenced.gif
+:-X icon_silenced.gif
+:-| icon_neutral.gif
+;-) icon_wink.gif
+^_^ icon_fun.gif
+:?: icon_question.gif
+:!: icon_exclaim.gif
+LOL icon_lol.gif
+FIXME fixme.gif
+DELETEME delete.gif
+
+#;&lt;P icon_kaddi.gif
diff --git a/plugins/dokuwiki/dokuwiki_constants.inc.php b/plugins/dokuwiki/dokuwiki_constants.inc.php
new file mode 100644
index 0000000..f984c2f
--- /dev/null
+++ b/plugins/dokuwiki/dokuwiki_constants.inc.php
@@ -0,0 +1,9 @@
+<?php
+define('FLYSPRAY_USE_CACHE', true);
+define('FLYSPRAY_HAS_PREVIEW', true);
+define('DOKU_PLUGIN', BASEDIR . '/plugins/dokuwiki/lib/plugins/');
+define('DOKU_CONF', BASEDIR . '/plugins/dokuwiki/conf/');
+define('DOKU_INTERNAL_LINK', $conf['general']['doku_url']);
+define('DOKU_BASE', $baseurl .'plugins/dokuwiki/');
+define('DOKU_URL', BASEDIR .'/plugins/dokuwiki/');
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/dokuwiki_formattext.inc.php b/plugins/dokuwiki/dokuwiki_formattext.inc.php
new file mode 100644
index 0000000..e101502
--- /dev/null
+++ b/plugins/dokuwiki/dokuwiki_formattext.inc.php
@@ -0,0 +1,155 @@
+<?php
+class dokuwiki_TextFormatter
+{
+ static function render($text, $type = null, $id = null, $instructions = null)
+ {
+ global $conf, $baseurl, $db;
+
+ // Unfortunately dokuwiki also uses $conf
+ $fs_conf = $conf;
+ $conf = array();
+
+ // Dokuwiki generates some notices
+ error_reporting(E_ALL ^ E_NOTICE);
+ if (!$instructions) {
+ include_once(BASEDIR . '/plugins/dokuwiki/inc/parser/parser.php');
+ }
+ require_once(BASEDIR . '/plugins/dokuwiki/inc/common.php');
+ require_once(BASEDIR . '/plugins/dokuwiki/inc/parser/xhtml.php');
+
+
+ // Create a renderer
+ $Renderer = new Doku_Renderer_XHTML();
+
+ if (!is_string($instructions) || strlen($instructions) < 1) {
+ $modes = p_get_parsermodes();
+
+ $Parser = new Doku_Parser();
+
+ // Add the Handler
+ $Parser->Handler = new Doku_Handler();
+
+ // Add modes to parser
+ foreach($modes as $mode){
+ $Parser->addMode($mode['mode'], $mode['obj']);
+ }
+ $instructions = $Parser->parse($text);
+
+
+ // Cache the parsed text
+ if (!is_null($type) && !is_null($id)) {
+ $fields = array('content'=> serialize($instructions), 'type'=> $type , 'topic'=> $id,
+ 'last_updated'=> time());
+
+ $keys = array('type','topic');
+ //autoquote is always true on db class
+ $db->Replace('{cache}', $fields, $keys);
+ }
+ } else {
+ $instructions = unserialize($instructions);
+ }
+
+ $Renderer->smileys = getSmileys();
+ $Renderer->entities = getEntities();
+ $Renderer->acronyms = getAcronyms();
+ $Renderer->interwiki = getInterwiki();
+
+ $conf = $fs_conf;
+ $conf['cachedir'] = FS_CACHE_DIR; // for dokuwiki
+ $conf['fperm'] = 0600;
+ $conf['dperm'] = 0700;
+
+ // Loop through the instructions
+ foreach ($instructions as $instruction) {
+ // Execute the callback against the Renderer
+ call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1]);
+ }
+
+ $return = $Renderer->doc;
+
+ // Display the output
+ if (Get::val('histring')) {
+ $words = explode(' ', Get::val('histring'));
+ foreach($words as $word) {
+ $return = html_hilight($return, $word);
+ }
+ }
+
+ return $return;
+ }
+ static function textarea( $name, $rows, $cols, $attrs = null, $content = null) {
+
+ $name = htmlspecialchars($name, ENT_QUOTES, 'utf-8');
+ $rows = intval($rows);
+ $cols = intval($cols);
+ $return = '<div class="dokuwiki_toolbar">'
+ . dokuwiki_TextFormatter::getDokuWikiToolbar( $attrs['id'] )
+ . '</div>';
+
+ $return .= "<textarea name=\"{$name}\" cols=\"$cols\" rows=\"$rows\" ";
+ if (is_array($attrs)) {
+ $return .= join_attrs($attrs);
+ }
+ $return .= '>';
+ if (!is_null($content)) {
+ $return .= htmlspecialchars($content, ENT_QUOTES, 'utf-8');
+ }
+ $return .= '</textarea>';
+ return $return;
+ }
+ /**
+ * Displays a toolbar for formatting text in the DokuWiki Syntax
+ * Uses Javascript
+ *
+ * @param string $textareaId
+ */
+ static function getDokuWikiToolbar( $textareaId ) {
+ global $conf, $baseurl;
+
+ return '<a
+tabindex="-1" title="'.eL('editorbold').'" href="javascript:void(0);" onclick="surroundText(\'**\', \'**\', \''.$textareaId.'\'); return false;"><img src="'.$baseurl.'plugins/dokuwiki/img/format-text-bold.png" alt="Bold" border="0" /></a><a
+tabindex="-1" title="'.eL('editoritalic').'" href="javascript:void(0);" onclick="surroundText(\'//\', \'//\', \''.$textareaId.'\'); return false;"><img src="'.$baseurl.'plugins/dokuwiki/img/format-text-italic.png" alt="Italicized" border="0" /></a><a
+tabindex="-1" title="'.eL('editorunderline').'" href="javascript:void(0);" onclick="surroundText(\'__\', \'__\', \''.$textareaId.'\'); return false;"><img src="'.$baseurl.'plugins/dokuwiki/img/format-text-underline.png" alt="Underline" border="0" /></a><a
+tabindex="-1" title="'.eL('editorstrikethrough').'" href="javascript:void(0);" onclick="surroundText(\'&lt;del&gt;\', \'&lt;/del&gt;\', \''.$textareaId.'\'); return false;"><img src="'.$baseurl.'plugins/dokuwiki/img/format-text-strikethrough.png" alt="Strikethrough" border="0" /></a>
+<img src="'.$baseurl.'plugins/dokuwiki/img/divider.gif" align="bottom" alt="|" style="margin: 0 3px 0 3px;" />
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'======\', \'======\', \''.$textareaId.'\'); return false;">
+ <img title="Level 1 Headline" src="'.$baseurl.'plugins/dokuwiki/img/h1.gif" width="23" height="22" alt="Heading1" border="0" /></a>
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'=====\', \'=====\', \''.$textareaId.'\'); return false;">
+ <img title="Level 2 Headline" src="'.$baseurl.'plugins/dokuwiki/img/h2.gif" width="23" height="22" alt="Heading2" border="0" /></a>
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'====\', \'====\', \''.$textareaId.'\'); return false;">
+ <img title="Level 3 Headline" src="'.$baseurl.'plugins/dokuwiki/img/h3.gif" width="23" height="22" alt="Heading3" border="0" /></a>
+
+ <img title="Divider" src="'.$baseurl.'plugins/dokuwiki/img/divider.gif" alt="|" style="margin: 0 3px 0 3px;" />
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'&#123;&#123;http://\', \'&#125;&#125;\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/image-x-generic.png" alt="Insert Image" title="Insert Image" border="0" /></a>
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="replaceText(\'\n * \', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/ul.gif" width="23" height="22" alt="Insert List" title="Insert List" border="0" /></a>
+ <a tabindex="-1" href="javascript:void(0);" onclick="replaceText(\'\n - \', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/ol.gif" width="23" height="22" alt="Insert List" title="Insert List" border="0" /></a>
+ <a tabindex="-1" href="javascript:void(0);" onclick="replaceText(\'----\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/hr.gif" width="23" height="22" alt="Horizontal Rule" title="Horizontal Rule" border="0" /></a>
+
+ <img src="'.$baseurl.'plugins/dokuwiki/img/divider.gif" alt="|" style="margin: 0 3px 0 3px;" />
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'[[http://example.com|External Link\', \']]\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/text-html.png" alt="Insert Hyperlink" title="Insert Hyperlink" border="0" /></a>
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'[[\', \']]\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/email.png" alt="Insert Email" title="Insert Email" border="0" /></a>
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'[[ftp://\', \']]\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/network.png" alt="Insert FTP Link" title="Insert FTP Link" border="0" /></a>
+
+ <img src="'.$baseurl.'plugins/dokuwiki/img/divider.gif" alt="|" style="margin: 0 3px 0 3px;" />
+
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'<code>\', \'</code>\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/source.png" alt="Insert Code" title="Insert Code" border="0" /></a>
+ <a tabindex="-1" href="javascript:void(0);" onclick="surroundText(\'<code php>\', \'</code>\', \''.$textareaId.'\'); return false;">
+ <img src="'.$baseurl.'plugins/dokuwiki/img/source_php.png" alt="Insert Code" title="Insert PHP Code" border="0" /></a>
+ ';
+ }
+}
+?>
diff --git a/plugins/dokuwiki/img/divider.gif b/plugins/dokuwiki/img/divider.gif
new file mode 100644
index 0000000..d4f35e1
--- /dev/null
+++ b/plugins/dokuwiki/img/divider.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/email.png b/plugins/dokuwiki/img/email.png
new file mode 100644
index 0000000..789a5ea
--- /dev/null
+++ b/plugins/dokuwiki/img/email.png
Binary files differ
diff --git a/plugins/dokuwiki/img/format-text-bold.png b/plugins/dokuwiki/img/format-text-bold.png
new file mode 100644
index 0000000..7166e3d
--- /dev/null
+++ b/plugins/dokuwiki/img/format-text-bold.png
Binary files differ
diff --git a/plugins/dokuwiki/img/format-text-italic.png b/plugins/dokuwiki/img/format-text-italic.png
new file mode 100644
index 0000000..ef68fb3
--- /dev/null
+++ b/plugins/dokuwiki/img/format-text-italic.png
Binary files differ
diff --git a/plugins/dokuwiki/img/format-text-strikethrough.png b/plugins/dokuwiki/img/format-text-strikethrough.png
new file mode 100644
index 0000000..e4ca573
--- /dev/null
+++ b/plugins/dokuwiki/img/format-text-strikethrough.png
Binary files differ
diff --git a/plugins/dokuwiki/img/format-text-underline.png b/plugins/dokuwiki/img/format-text-underline.png
new file mode 100644
index 0000000..d33422b
--- /dev/null
+++ b/plugins/dokuwiki/img/format-text-underline.png
Binary files differ
diff --git a/plugins/dokuwiki/img/h1.gif b/plugins/dokuwiki/img/h1.gif
new file mode 100644
index 0000000..4a358da
--- /dev/null
+++ b/plugins/dokuwiki/img/h1.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/h2.gif b/plugins/dokuwiki/img/h2.gif
new file mode 100644
index 0000000..a6aca2c
--- /dev/null
+++ b/plugins/dokuwiki/img/h2.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/h3.gif b/plugins/dokuwiki/img/h3.gif
new file mode 100644
index 0000000..0459f89
--- /dev/null
+++ b/plugins/dokuwiki/img/h3.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/hr.gif b/plugins/dokuwiki/img/hr.gif
new file mode 100644
index 0000000..486d47c
--- /dev/null
+++ b/plugins/dokuwiki/img/hr.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/image-x-generic.png b/plugins/dokuwiki/img/image-x-generic.png
new file mode 100644
index 0000000..10f4671
--- /dev/null
+++ b/plugins/dokuwiki/img/image-x-generic.png
Binary files differ
diff --git a/plugins/dokuwiki/img/img.gif b/plugins/dokuwiki/img/img.gif
new file mode 100644
index 0000000..02817e7
--- /dev/null
+++ b/plugins/dokuwiki/img/img.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/network.png b/plugins/dokuwiki/img/network.png
new file mode 100644
index 0000000..1929fe1
--- /dev/null
+++ b/plugins/dokuwiki/img/network.png
Binary files differ
diff --git a/plugins/dokuwiki/img/ol.gif b/plugins/dokuwiki/img/ol.gif
new file mode 100644
index 0000000..0368a46
--- /dev/null
+++ b/plugins/dokuwiki/img/ol.gif
Binary files differ
diff --git a/plugins/dokuwiki/img/source.png b/plugins/dokuwiki/img/source.png
new file mode 100644
index 0000000..bb18aa8
--- /dev/null
+++ b/plugins/dokuwiki/img/source.png
Binary files differ
diff --git a/plugins/dokuwiki/img/source_php.png b/plugins/dokuwiki/img/source_php.png
new file mode 100644
index 0000000..a86ad14
--- /dev/null
+++ b/plugins/dokuwiki/img/source_php.png
Binary files differ
diff --git a/plugins/dokuwiki/img/text-html.png b/plugins/dokuwiki/img/text-html.png
new file mode 100644
index 0000000..51beaff
--- /dev/null
+++ b/plugins/dokuwiki/img/text-html.png
Binary files differ
diff --git a/plugins/dokuwiki/img/ul.gif b/plugins/dokuwiki/img/ul.gif
new file mode 100644
index 0000000..6c31b5c
--- /dev/null
+++ b/plugins/dokuwiki/img/ul.gif
Binary files differ
diff --git a/plugins/dokuwiki/inc/HTTPClient.php b/plugins/dokuwiki/inc/HTTPClient.php
new file mode 100644
index 0000000..7a4d1e6
--- /dev/null
+++ b/plugins/dokuwiki/inc/HTTPClient.php
@@ -0,0 +1,436 @@
+<?php
+/**
+ * HTTP Client
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Goetz <cpuidle@gmx.de>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+require_once(DOKU_CONF.'dokuwiki.php');
+
+define('HTTP_NL',"\r\n");
+
+
+/**
+ * This class implements a basic HTTP client
+ *
+ * It supports POST and GET, Proxy usage, basic authentication,
+ * handles cookies and referers. It is based upon the httpclient
+ * function from the VideoDB project.
+ *
+ * @link http://www.splitbrain.org/go/videodb
+ * @author Andreas Goetz <cpuidle@gmx.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class HTTPClient {
+ //set these if you like
+ var $agent; // User agent
+ var $http; // HTTP version defaults to 1.0
+ var $timeout; // read timeout (seconds)
+ var $cookies;
+ var $referer;
+ var $max_redirect;
+ var $max_bodysize; // abort if the response body is bigger than this
+ var $header_regexp; // if set this RE must match against the headers, else abort
+ var $headers;
+ var $debug;
+
+ // don't set these, read on error
+ var $error;
+ var $redirect_count;
+
+ // read these after a successful request
+ var $resp_status;
+ var $resp_body;
+ var $resp_headers;
+
+ // set these to do basic authentication
+ var $user;
+ var $pass;
+
+ // set these if you need to use a proxy
+ var $proxy_host;
+ var $proxy_port;
+ var $proxy_user;
+ var $proxy_pass;
+ var $proxy_ssl; //boolean set to true if your proxy needs SSL
+
+ /**
+ * Constructor.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function __construct(){
+ $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')';
+ $this->timeout = 15;
+ $this->cookies = array();
+ $this->referer = '';
+ $this->max_redirect = 3;
+ $this->redirect_count = 0;
+ $this->status = 0;
+ $this->headers = array();
+ $this->http = '1.0';
+ $this->debug = false;
+ $this->max_bodysize = 0;
+ $this->header_regexp= '';
+ if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip';
+ $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'.
+ 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
+ $this->headers['Accept-Language'] = 'en-us';
+ }
+
+
+ /**
+ * Simple function to do a GET request
+ *
+ * Returns the wanted page or false on an error;
+ *
+ * @param string $url The URL to fetch
+ * @param bool $sloppy304 Return body on 304 not modified
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function get($url,$sloppy304=false){
+ if(!$this->sendRequest($url)) return false;
+ if($this->status == 304 && $sloppy304) return $this->resp_body;
+ if($this->status != 200) return false;
+ return $this->resp_body;
+ }
+
+ /**
+ * Simple function to do a POST request
+ *
+ * Returns the resulting page or false on an error;
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function post($url,$data){
+ if(!$this->sendRequest($url,$data,'POST')) return false;
+ if($this->status != 200) return false;
+ return $this->resp_body;
+ }
+
+ /**
+ * Do an HTTP request
+ *
+ * @author Andreas Goetz <cpuidle@gmx.de>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function sendRequest($url,$data=array(),$method='GET'){
+ $this->error = '';
+ $this->status = 0;
+
+ // parse URL into bits
+ $uri = parse_url($url);
+ $server = $uri['host'];
+ $path = $uri['path'];
+ if(empty($path)) $path = '/';
+ if(!empty($uri['query'])) $path .= '?'.$uri['query'];
+ $port = $uri['port'];
+ if($uri['user']) $this->user = $uri['user'];
+ if($uri['pass']) $this->pass = $uri['pass'];
+
+ // proxy setup
+ if($this->proxy_host){
+ $request_url = $url;
+ $server = $this->proxy_host;
+ $port = $this->proxy_port;
+ if (empty($port)) $port = 8080;
+ }else{
+ $request_url = $path;
+ $server = $server;
+ if (empty($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80;
+ }
+
+ // add SSL stream prefix if needed - needs SSL support in PHP
+ if($port == 443 || $this->proxy_ssl) $server = 'ssl://'.$server;
+
+ // prepare headers
+ $headers = $this->headers;
+ $headers['Host'] = $uri['host'];
+ $headers['User-Agent'] = $this->agent;
+ $headers['Referer'] = $this->referer;
+ $headers['Connection'] = 'Close';
+ if($method == 'POST'){
+ $post = $this->_postEncode($data);
+ $headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ $headers['Content-Length'] = strlen($post);
+ }
+ if($this->user) {
+ $headers['Authorization'] = 'BASIC '.base64_encode($this->user.':'.$this->pass);
+ }
+ if($this->proxy_user) {
+ $headers['Proxy-Authorization'] = 'BASIC '.base64_encode($this->proxy_user.':'.$this->proxy_pass);
+ }
+
+ // stop time
+ $start = time();
+
+ // open socket
+ $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout);
+ if (!$socket){
+ $resp->status = '-100';
+ $this->error = "Could not connect to $server:$port\n$errstr ($errno)";
+ return false;
+ }
+ //set non blocking
+ stream_set_blocking($socket,0);
+
+ // build request
+ $request = "$method $request_url HTTP/".$this->http.HTTP_NL;
+ $request .= $this->_buildHeaders($headers);
+ $request .= $this->_getCookies();
+ $request .= HTTP_NL;
+ $request .= $post;
+
+ $this->_debug('request',$request);
+
+ // send request
+ fputs($socket, $request);
+ // read headers from socket
+ $r_headers = '';
+ do{
+ if(time()-$start > $this->timeout){
+ $this->status = -100;
+ $this->error = 'Timeout while reading headers';
+ return false;
+ }
+ if(feof($socket)){
+ $this->error = 'Premature End of File (socket)';
+ return false;
+ }
+ $r_headers .= fread($socket,1); #FIXME read full lines here?
+ }while(!preg_match('/\r\n\r\n$/',$r_headers));
+
+ $this->_debug('response headers',$r_headers);
+
+ // check if expected body size exceeds allowance
+ if($this->max_bodysize && preg_match('/\r\nContent-Length:\s*(\d+)\r\n/i',$r_headers,$match)){
+ if($match[1] > $this->max_bodysize){
+ $this->error = 'Reported content length exceeds allowed response size';
+ return false;
+ }
+ }
+
+ // get Status
+ if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) {
+ $this->error = 'Server returned bad answer';
+ return false;
+ }
+ $this->status = $m[2];
+
+ // handle headers and cookies
+ $this->resp_headers = $this->_parseHeaders($r_headers);
+ if(isset($this->resp_headers['set-cookie'])){
+ foreach ((array) $this->resp_headers['set-cookie'] as $c){
+ list($key, $value, $foo) = explode('=', $cookie);
+ $this->cookies[$key] = $value;
+ }
+ }
+
+ $this->_debug('Object headers',$this->resp_headers);
+
+ // check server status code to follow redirect
+ if($this->status == 301 || $this->status == 302 ){
+ if (empty($this->resp_headers['location'])){
+ $this->error = 'Redirect but no Location Header found';
+ return false;
+ }elseif($this->redirect_count == $this->max_redirect){
+ $this->error = 'Maximum number of redirects exceeded';
+ return false;
+ }else{
+ $this->redirect_count++;
+ $this->referer = $url;
+ if (!preg_match('/^http/i', $this->resp_headers['location'])){
+ $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].
+ $this->resp_headers['location'];
+ }
+ // perform redirected request, always via GET (required by RFC)
+ return $this->sendRequest($this->resp_headers['location'],array(),'GET');
+ }
+ }
+
+ // check if headers are as expected
+ if($this->header_regexp && !preg_match($this->header_regexp,$r_headers)){
+ $this->error = 'The received headers did not match the given regexp';
+ return false;
+ }
+
+ //read body (with chunked encoding if needed)
+ $r_body = '';
+ if(preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i',$r_header)){
+ do {
+ unset($chunk_size);
+ do {
+ if(feof($socket)){
+ $this->error = 'Premature End of File (socket)';
+ return false;
+ }
+ if(time()-$start > $this->timeout){
+ $this->status = -100;
+ $this->error = 'Timeout while reading chunk';
+ return false;
+ }
+ $byte = fread($socket,1);
+ $chunk_size .= $byte;
+ } while (preg_match('/[a-zA-Z0-9]/',$byte)); // read chunksize including \r
+
+ $byte = fread($socket,1); // readtrailing \n
+ $chunk_size = hexdec($chunk_size);
+ $this_chunk = fread($socket,$chunk_size);
+ $r_body .= $this_chunk;
+ if ($chunk_size) $byte = fread($socket,2); // read trailing \r\n
+
+ if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
+ $this->error = 'Allowed response size exceeded';
+ return false;
+ }
+ } while ($chunk_size);
+ }else{
+ // read entire socket
+ while (!feof($socket)) {
+ if(time()-$start > $this->timeout){
+ $this->status = -100;
+ $this->error = 'Timeout while reading response';
+ return false;
+ }
+ $r_body .= fread($socket,4096);
+ if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){
+ $this->error = 'Allowed response size exceeded';
+ return false;
+ }
+ }
+ }
+
+ // close socket
+ $status = socket_get_status($socket);
+ fclose($socket);
+
+ // decode gzip if needed
+ if($this->resp_headers['content-encoding'] == 'gzip'){
+ $this->resp_body = gzinflate(substr($r_body, 10));
+ }else{
+ $this->resp_body = $r_body;
+ }
+
+ $this->_debug('response body',$this->resp_body);
+ $this->redirect_count = 0;
+ return true;
+ }
+
+ /**
+ * print debug info
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _debug($info,$var){
+ if(!$this->debug) return;
+ print '<b>'.$info.'</b><br />';
+ ob_start();
+ print_r($var);
+ $content = htmlspecialchars(ob_get_contents());
+ ob_end_clean();
+ print '<pre>'.$content.'</pre>';
+ }
+
+ /**
+ * convert given header string to Header array
+ *
+ * All Keys are lowercased.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _parseHeaders($string){
+ $headers = array();
+ $lines = explode("\n",$string);
+ foreach($lines as $line){
+ list($key,$val) = explode(':',$line,2);
+ $key = strtolower(trim($key));
+ $val = trim($val);
+ if(empty($val)) continue;
+ if(isset($headers[$key])){
+ if(is_array($headers[$key])){
+ $headers[$key][] = $val;
+ }else{
+ $headers[$key] = array($headers[$key],$val);
+ }
+ }else{
+ $headers[$key] = $val;
+ }
+ }
+ return $headers;
+ }
+
+ /**
+ * convert given header array to header string
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _buildHeaders($headers){
+ $string = '';
+ foreach($headers as $key => $value){
+ if(empty($value)) continue;
+ $string .= $key.': '.$value.HTTP_NL;
+ }
+ return $string;
+ }
+
+ /**
+ * get cookies as http header string
+ *
+ * @author Andreas Goetz <cpuidle@gmx.de>
+ */
+ function _getCookies(){
+ foreach ($this->cookies as $key => $val){
+ if ($headers) $headers .= '; ';
+ $headers .= $key.'='.$val;
+ }
+
+ if ($headers) $headers = "Cookie: $headers".HTTP_NL;
+ return $headers;
+ }
+
+ /**
+ * Encode data for posting
+ *
+ * @todo handle mixed encoding for file upoads
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _postEncode($data){
+ foreach($data as $key => $val){
+ if($url) $url .= '&';
+ $url .= $key.'='.urlencode($val);
+ }
+ return $url;
+ }
+}
+
+
+/**
+ * Adds DokuWiki specific configs to the HTTP client
+ *
+ * @author Andreas Goetz <cpuidle@gmx.de>
+ */
+class DokuHTTPClient extends HTTPClient {
+
+ /**
+ * Constructor.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function __construct(){
+ global $conf;
+
+ // call parent constructor
+ parent::__construct();
+
+ // set some values from the config
+ $this->proxy_host = $conf['proxy']['host'];
+ $this->proxy_port = $conf['proxy']['port'];
+ $this->proxy_user = $conf['proxy']['user'];
+ $this->proxy_pass = $conf['proxy']['pass'];
+ $this->proxy_ssl = $conf['proxy']['ssl'];
+ }
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/JpegMeta.php b/plugins/dokuwiki/inc/JpegMeta.php
new file mode 100644
index 0000000..5f9c331
--- /dev/null
+++ b/plugins/dokuwiki/inc/JpegMeta.php
@@ -0,0 +1,2982 @@
+<?php
+/**
+ * JPEG metadata reader/writer
+ *
+ * @license PHP license 2.0 (http://www.php.net/license/2_02.txt)
+ * @link http://www.zonageek.com/software/php/jpeg/index.php
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @todo Add support for Maker Notes, Extend for GIF and PNG metadata
+ */
+
+// This class is a modified and enhanced version of the JPEG class by
+// Sebastian Delmont. Original Copyright notice follows:
+//
+// +----------------------------------------------------------------------+
+// | PHP version 4.0 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Sebastian Delmont <sdelmont@zonageek.com> |
+// +----------------------------------------------------------------------+
+
+class JpegMeta
+{
+ var $_fileName;
+ var $_fp = null;
+ var $_type = 'unknown';
+
+ var $_markers;
+ var $_info;
+
+
+ /**
+ * Constructor
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function __construct($fileName)
+ {
+
+ $this->_fileName = $fileName;
+
+ $this->_fp = null;
+ $this->_type = 'unknown';
+
+ unset($this->_info);
+ unset($this->_markers);
+ }
+
+ /**
+ * Returns all gathered info as multidim array
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function & getRawInfo()
+ {
+ $this->_parseAll();
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ return $this->_info;
+ }
+
+ /**
+ * Returns basic image info
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function & getBasicInfo()
+ {
+ $this->_parseAll();
+
+ $info = array();
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $info['Name'] = $this->_info['file']['Name'];
+ if (isset($this->_info['file']['Url'])) {
+ $info['Url'] = $this->_info['file']['Url'];
+ $info['NiceSize'] = "???KB";
+ }
+ else {
+ $info['Size'] = $this->_info['file']['Size'];
+ $info['NiceSize'] = $this->_info['file']['NiceSize'];
+ }
+
+ if (@isset($this->_info['sof']['Format'])) {
+ $info['Format'] = $this->_info['sof']['Format'] . " JPEG";
+ }
+ else {
+ $info['Format'] = $this->_info['sof']['Format'] . " JPEG";
+ }
+
+ if (@isset($this->_info['sof']['ColorChannels'])) {
+ $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W";
+ }
+
+ $info['Width'] = $this->getWidth();
+ $info['Height'] = $this->getHeight();
+ $info['DimStr'] = $this->getDimStr();
+
+ $dates = $this->getDates();
+
+ $info['DateTime'] = $dates['EarliestTime'];
+ $info['DateTimeStr'] = $dates['EarliestTimeStr'];
+
+ $info['HasThumbnail'] = $this->hasThumbnail();
+
+ return $info;
+ }
+
+
+ /**
+ * Convinience function to access nearly all available Data
+ * through one function
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getField($fields)
+ {
+ if(!is_array($fields)) $fields = array($fields);
+ $info = false;
+ foreach($fields as $field){
+ if(strtolower(substr($field,0,5)) == 'iptc.'){
+ $info = $this->getIPTCField(substr($field,5));
+ }elseif(strtolower(substr($field,0,5)) == 'exif.'){
+ $info = $this->getExifField(substr($field,5));
+ }elseif(strtolower(substr($field,0,5)) == 'file.'){
+ $info = $this->getFileField(substr($field,5));
+ }elseif(strtolower(substr($field,0,5)) == 'date.'){
+ $info = $this->getDateField(substr($field,5));
+ }elseif(strtolower($field) == 'simple.camera'){
+ $info = $this->getCamera();
+ }elseif(strtolower($field) == 'simple.raw'){
+ return $this->getRawInfo();
+ }elseif(strtolower($field) == 'simple.title'){
+ $info = $this->getTitle();
+ }elseif(strtolower($field) == 'simple.shutterspeed'){
+ $info = $this->getShutterSpeed();
+ }else{
+ $info = $this->getExifField($field);
+ }
+ if($info != false) break;
+ }
+
+ if($info === false) $info = $alt;
+ if(is_array($info)){
+ if(isset($info['val'])){
+ $info = $info['val'];
+ }else{
+ $info = join(', ',$info);
+ }
+ }
+ return trim($info);
+ }
+
+ /**
+ * Convinience function to set nearly all available Data
+ * through one function
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function setField($field, $value)
+ {
+ if(strtolower(substr($field,0,5)) == 'iptc.'){
+ return $this->setIPTCField(substr($field,5),$value);
+ }elseif(strtolower(substr($field,0,5)) == 'exif.'){
+ return $this->setExifField(substr($field,5),$value);
+ }else{
+ return $this->setExifField($field,$value);
+ }
+ }
+
+ /**
+ * Convinience function to delete nearly all available Data
+ * through one function
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function deleteField($field)
+ {
+ if(strtolower(substr($field,0,5)) == 'iptc.'){
+ return $this->deleteIPTCField(substr($field,5));
+ }elseif(strtolower(substr($field,0,5)) == 'exif.'){
+ return $this->deleteExifField(substr($field,5));
+ }else{
+ return $this->deleteExifField($field);
+ }
+ }
+
+ /**
+ * Return a date field
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getDateField($field)
+ {
+ if (!isset($this->_info['dates'])) {
+ $this->_info['dates'] = $this->getDates();
+ }
+
+ if (isset($this->_info['dates'][$field])) {
+ return $this->_info['dates'][$field];
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a file info field
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getFileField($field)
+ {
+ if (!isset($this->_info['file'])) {
+ $this->_parseFileInfo();
+ }
+
+ if (isset($this->_info['file'][$field])) {
+ return $this->_info['file'][$field];
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the camera info (Maker and Model)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @todo handle makernotes
+ */
+ function getCamera(){
+ $make = $this->getField(array('Exif.Make','Exif.TIFFMake'));
+ $model = $this->getField(array('Exif.Model','Exif.TIFFModel'));
+ $cam = trim("$make $model");
+ if(empty($cam)) return false;
+ return $cam;
+ }
+
+ /**
+ * Return shutter speed as a ratio
+ *
+ * @author Joe Lapp <joe.lapp@pobox.com>
+ */
+ function getShutterSpeed()
+ {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+ if(!isset($this->_info['exif']['ExposureTime'])){
+ return '';
+ }
+
+ $field = $this->_info['exif']['ExposureTime'];
+ if($field['den'] == 1) return $field['num'];
+ return $field['num'].'/'.$field['den'];
+ }
+
+ /**
+ * Return an EXIF field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getExifField($field)
+ {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['exif'][$field])) {
+ return $this->_info['exif'][$field];
+ }
+
+ return false;
+ }
+
+ /**
+ * Return an Adobe Field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getAdobeField($field)
+ {
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['adobe'][$field])) {
+ return $this->_info['adobe'][$field];
+ }
+
+ return false;
+ }
+
+ /**
+ * Return an IPTC field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getIPTCField($field)
+ {
+ if (!isset($this->_info['iptc'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['iptc'][$field])) {
+ return $this->_info['iptc'][$field];
+ }
+
+ return false;
+ }
+
+ /**
+ * Set an EXIF field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ * @author Joe Lapp <joe.lapp@pobox.com>
+ */
+ function setExifField($field, $value)
+ {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['exif'] == false) {
+ $this->_info['exif'] = array();
+ }
+
+ // make sure datetimes are in correct format
+ if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') {
+ if(strlen($value) < 8 || $value{4} != ':' || $value{7} != ':') {
+ $value = date('Y:m:d H:i:s', strtotime($value));
+ }
+ }
+
+ $this->_info['exif'][$field] = $value;
+
+ return true;
+ }
+
+ /**
+ * Set an Adobe Field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function setAdobeField($field, $value)
+ {
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['adobe'] == false) {
+ $this->_info['adobe'] = array();
+ }
+
+ $this->_info['adobe'][$field] = $value;
+
+ return true;
+ }
+
+ /**
+ * Calculates the multiplier needed to resize the image to the given
+ * dimensions
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getResizeRatio($maxwidth,$maxheight=0){
+ if(!$maxheight) $maxheight = $maxwidth;
+
+ $w = $this->getField('File.Width');
+ $h = $this->getField('File.Height');
+
+ $ratio = 1;
+ if($w >= $h){
+ if($w >= $maxwidth){
+ $ratio = $maxwidth/$w;
+ }elseif($h > $maxheight){
+ $ratio = $maxheight/$h;
+ }
+ }else{
+ if($h >= $maxheight){
+ $ratio = $maxheight/$h;
+ }elseif($w > $maxwidth){
+ $ratio = $maxwidth/$w;
+ }
+ }
+ return $ratio;
+ }
+
+
+ /**
+ * Set an IPTC field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function setIPTCField($field, $value)
+ {
+ if (!isset($this->_info['iptc'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['iptc'] == false) {
+ $this->_info['iptc'] = array();
+ }
+
+ $this->_info['iptc'][$field] = $value;
+
+ return true;
+ }
+
+ /**
+ * Delete an EXIF field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function deleteExifField($field)
+ {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['exif'] != false) {
+ unset($this->_info['exif'][$field]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete an Adobe field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function deleteAdobeField($field)
+ {
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['adobe'] != false) {
+ unset($this->_info['adobe'][$field]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete an IPTC field
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function deleteIPTCField($field)
+ {
+ if (!isset($this->_info['iptc'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if ($this->_info['iptc'] != false) {
+ unset($this->_info['iptc'][$field]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the image's title, tries various fields
+ *
+ * @param int $max maximum number chars (keeps words)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function getTitle($max=80){
+ $cap = '';
+
+ // try various fields
+ $cap = $this->getField(array('Iptc.Headline',
+ 'Iptc.Caption',
+ 'Exif.UserComment',
+ 'Exif.TIFFUserComment',
+ 'Exif.TIFFImageDescription',
+ 'File.Name'));
+ if (empty($cap)) return false;
+
+ if(!$max) return $cap;
+ // Shorten to 80 chars (keeping words)
+ $new = preg_replace('/\n.+$/','',wordwrap($cap, $max));
+ if($new != $cap) $new .= '...';
+
+ return $new;
+ }
+
+ /**
+ * Gather various date fields
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getDates()
+ {
+ $this->_parseAll();
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $dates = array();
+
+ $latestTime = 0;
+ $latestTimeSource = "";
+ $earliestTime = time();
+ $earliestTimeSource = "";
+
+ if (@isset($this->_info['exif']['DateTime'])) {
+ $dates['ExifDateTime'] = $this->_info['exif']['DateTime'];
+
+ $aux = $this->_info['exif']['DateTime'];
+ $aux{4} = "-";
+ $aux{7} = "-";
+ $t = strtotime($aux);
+
+ if ($t > $latestTime) {
+ $latestTime = $t;
+ $latestTimeSource = "ExifDateTime";
+ }
+
+ if ($t < $earliestTime) {
+ $earliestTime = $t;
+ $earliestTimeSource = "ExifDateTime";
+ }
+ }
+
+ if (@isset($this->_info['exif']['DateTimeOriginal'])) {
+ $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime'];
+
+ $aux = $this->_info['exif']['DateTimeOriginal'];
+ $aux{4} = "-";
+ $aux{7} = "-";
+ $t = strtotime($aux);
+
+ if ($t > $latestTime) {
+ $latestTime = $t;
+ $latestTimeSource = "ExifDateTimeOriginal";
+ }
+
+ if ($t < $earliestTime) {
+ $earliestTime = $t;
+ $earliestTimeSource = "ExifDateTimeOriginal";
+ }
+ }
+
+ if (@isset($this->_info['exif']['DateTimeDigitized'])) {
+ $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime'];
+
+ $aux = $this->_info['exif']['DateTimeDigitized'];
+ $aux{4} = "-";
+ $aux{7} = "-";
+ $t = strtotime($aux);
+
+ if ($t > $latestTime) {
+ $latestTime = $t;
+ $latestTimeSource = "ExifDateTimeDigitized";
+ }
+
+ if ($t < $earliestTime) {
+ $earliestTime = $t;
+ $earliestTimeSource = "ExifDateTimeDigitized";
+ }
+ }
+
+ if (@isset($this->_info['iptc']['DateCreated'])) {
+ $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated'];
+
+ $aux = $this->_info['iptc']['DateCreated'];
+ $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2);
+ $t = strtotime($aux);
+
+ if ($t > $latestTime) {
+ $latestTime = $t;
+ $latestTimeSource = "IPTCDateCreated";
+ }
+
+ if ($t < $earliestTime) {
+ $earliestTime = $t;
+ $earliestTimeSource = "IPTCDateCreated";
+ }
+ }
+
+ if (@isset($this->_info['file']['UnixTime'])) {
+ $dates['FileModified'] = $this->_info['file']['UnixTime'];
+
+ $t = $this->_info['file']['UnixTime'];
+
+ if ($t > $latestTime) {
+ $latestTime = $t;
+ $latestTimeSource = "FileModified";
+ }
+
+ if ($t < $earliestTime) {
+ $earliestTime = $t;
+ $earliestTimeSource = "FileModified";
+ }
+ }
+
+ $dates['Time'] = $earliestTime;
+ $dates['TimeSource'] = $earliestTimeSource;
+ $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime);
+ $dates['EarliestTime'] = $earliestTime;
+ $dates['EarliestTimeSource'] = $earliestTimeSource;
+ $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime);
+ $dates['LatestTime'] = $latestTime;
+ $dates['LatestTimeSource'] = $latestTimeSource;
+ $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime);
+
+ return $dates;
+ }
+
+ /**
+ * Get the image width, tries various fields
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getWidth()
+ {
+ if (!isset($this->_info['sof'])) {
+ $this->_parseMarkerSOF();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['sof']['ImageWidth'])) {
+ return $this->_info['sof']['ImageWidth'];
+ }
+
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if (isset($this->_info['exif']['PixelXDimension'])) {
+ return $this->_info['exif']['PixelXDimension'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the image height, tries various fields
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getHeight()
+ {
+ if (!isset($this->_info['sof'])) {
+ $this->_parseMarkerSOF();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['sof']['ImageHeight'])) {
+ return $this->_info['sof']['ImageHeight'];
+ }
+
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if (isset($this->_info['exif']['PixelYDimension'])) {
+ return $this->_info['exif']['PixelYDimension'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get an dimension string for use in img tag
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function getDimStr()
+ {
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $w = $this->getWidth();
+ $h = $this->getHeight();
+
+ return "width='" . $w . "' height='" . $h . "'";
+ }
+
+ /**
+ * Checks for an embedded thumbnail
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function hasThumbnail($which = 'any')
+ {
+ if (($which == 'any') || ($which == 'exif')) {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
+ if (isset($this->_info['exif']['JFIFThumbnail'])) {
+ return 'exif';
+ }
+ }
+ }
+
+ if ($which == 'adobe') {
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) {
+ if (isset($this->_info['adobe']['ThumbnailData'])) {
+ return 'exif';
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Send embedded thumbnail to browser
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ */
+ function sendThumbnail($which = 'any')
+ {
+ $data = null;
+
+ if (($which == 'any') || ($which == 'exif')) {
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
+ if (isset($this->_info['exif']['JFIFThumbnail'])) {
+ $data =& $this->_info['exif']['JFIFThumbnail'];
+ }
+ }
+ }
+
+ if (($which == 'adobe') || ($data == null)){
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) {
+ if (isset($this->_info['adobe']['ThumbnailData'])) {
+ $data =& $this->_info['adobe']['ThumbnailData'];
+ }
+ }
+ }
+
+ if ($data != null) {
+ header("Content-type: image/jpeg");
+ echo $data;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Save changed Metadata
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function save($fileName = "") {
+ if ($fileName == "") {
+ $tmpName = tempnam(dirname($this->_fileName),'_metatemp_');
+ $this->_writeJPEG($tmpName);
+ if (@file_exists($tmpName)) {
+ return io_rename($tmpName, $this->_fileName);
+ }
+ } else {
+ return $this->_writeJPEG($fileName);
+ }
+ return false;
+ }
+
+ /*************************************************************/
+ /* PRIVATE FUNCTIONS (Internal Use Only!) */
+ /*************************************************************/
+
+ /*************************************************************/
+ function _dispose()
+ {
+ $this->_fileName = $fileName;
+
+ $this->_fp = null;
+ $this->_type = 'unknown';
+
+ unset($this->_markers);
+ unset($this->_info);
+ }
+
+ /*************************************************************/
+ function _readJPEG()
+ {
+ unset($this->_markers);
+ unset($this->_info);
+ $this->_markers = array();
+ $this->_info = array();
+
+ $this->_fp = @fopen($this->_fileName, 'rb');
+ if ($this->_fp) {
+ if (file_exists($this->_fileName)) {
+ $this->_type = 'file';
+ }
+ else {
+ $this->_type = 'url';
+ }
+ }
+ else {
+ $this->_fp = null;
+ return false; // ERROR: Can't open file
+ }
+
+ // Check for the JPEG signature
+ $c1 = ord(fgetc($this->_fp));
+ $c2 = ord(fgetc($this->_fp));
+
+ if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI)
+ $this->_markers = null;
+ return false; // ERROR: File is not a JPEG
+ }
+
+ $count = 0;
+
+ $done = false;
+ $ok = true;
+
+ while (!$done) {
+ $capture = false;
+
+ // First, skip any non 0xFF bytes
+ $discarded = 0;
+ $c = ord(fgetc($this->_fp));
+ while (!feof($this->_fp) && ($c != 0xFF)) {
+ $discarded++;
+ $c = ord(fgetc($this->_fp));
+ }
+ // Then skip all 0xFF until the marker byte
+ do {
+ $marker = ord(fgetc($this->_fp));
+ } while (!feof($this->_fp) && ($marker == 0xFF));
+
+ if (feof($this->_fp)) {
+ return false; // ERROR: Unexpected EOF
+ }
+ if ($discarded != 0) {
+ return false; // ERROR: Extraneous data
+ }
+
+ $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp));
+ if (feof($this->_fp)) {
+ return false; // ERROR: Unexpected EOF
+ }
+ if ($length < 2) {
+ return false; // ERROR: Extraneous data
+ }
+ $length = $length - 2; // The length we got counts itself
+
+ switch ($marker) {
+ case 0xC0: // SOF0
+ case 0xC1: // SOF1
+ case 0xC2: // SOF2
+ case 0xC9: // SOF9
+ case 0xE0: // APP0: JFIF data
+ case 0xE1: // APP1: EXIF data
+ case 0xED: // APP13: IPTC / Photoshop data
+ $capture = true;
+ break;
+ case 0xDA: // SOS: Start of scan... the image itself and the last block on the file
+ $capture = false;
+ $length = -1; // This field has no length... it includes all data until EOF
+ $done = true;
+ break;
+ default:
+ $capture = true;//false;
+ break;
+ }
+
+ $this->_markers[$count] = array();
+ $this->_markers[$count]['marker'] = $marker;
+ $this->_markers[$count]['length'] = $length;
+
+ if ($capture) {
+ $this->_markers[$count]['data'] =& fread($this->_fp, $length);
+ }
+ elseif (!$done) {
+ $result = @fseek($this->_fp, $length, SEEK_CUR);
+ // fseek doesn't seem to like HTTP 'files', but fgetc has no problem
+ if (!($result === 0)) {
+ for ($i = 0; $i < $length; $i++) {
+ fgetc($this->_fp);
+ }
+ }
+ }
+ $count++;
+ }
+
+ if ($this->_fp) {
+ fclose($this->_fp);
+ $this->_fp = null;
+ }
+
+ return $ok;
+ }
+
+ /*************************************************************/
+ function _parseAll()
+ {
+ if (!isset($this->_markers)) {
+ $this->_readJPEG();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ if (!isset($this->_info['jfif'])) {
+ $this->_parseMarkerJFIF();
+ }
+ if (!isset($this->_info['jpeg'])) {
+ $this->_parseMarkerSOF();
+ }
+ if (!isset($this->_info['exif'])) {
+ $this->_parseMarkerExif();
+ }
+ if (!isset($this->_info['adobe'])) {
+ $this->_parseMarkerAdobe();
+ }
+ if (!isset($this->_info['file'])) {
+ $this->_parseFileInfo();
+ }
+ }
+
+ /*************************************************************/
+ function _writeJPEG($outputName)
+ {
+ $this->_parseAll();
+
+ $wroteEXIF = false;
+ $wroteAdobe = false;
+
+ $this->_fp = @fopen($this->_fileName, 'r');
+ if ($this->_fp) {
+ if (file_exists($this->_fileName)) {
+ $this->_type = 'file';
+ }
+ else {
+ $this->_type = 'url';
+ }
+ }
+ else {
+ $this->_fp = null;
+ return false; // ERROR: Can't open file
+ }
+
+ $this->_fpout = fopen($outputName, 'wb');
+ if ($this->_fpout) {
+ }
+ else {
+ $this->_fpout = null;
+ fclose($this->_fp);
+ $this->_fp = null;
+ return false; // ERROR: Can't open output file
+ }
+
+ // Check for the JPEG signature
+ $c1 = ord(fgetc($this->_fp));
+ $c2 = ord(fgetc($this->_fp));
+
+ if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI)
+ return false; // ERROR: File is not a JPEG
+ }
+
+ fputs($this->_fpout, chr(0xFF), 1);
+ fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI)
+
+ $count = 0;
+
+ $done = false;
+ $ok = true;
+
+ while (!$done) {
+ // First, skip any non 0xFF bytes
+ $discarded = 0;
+ $c = ord(fgetc($this->_fp));
+ while (!feof($this->_fp) && ($c != 0xFF)) {
+ $discarded++;
+ $c = ord(fgetc($this->_fp));
+ }
+ // Then skip all 0xFF until the marker byte
+ do {
+ $marker = ord(fgetc($this->_fp));
+ } while (!feof($this->_fp) && ($marker == 0xFF));
+
+ if (feof($this->_fp)) {
+ $ok = false;
+ break; // ERROR: Unexpected EOF
+ }
+ if ($discarded != 0) {
+ $ok = false;
+ break; // ERROR: Extraneous data
+ }
+
+ $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp));
+ if (feof($this->_fp)) {
+ $ok = false;
+ break; // ERROR: Unexpected EOF
+ }
+ if ($length < 2) {
+ $ok = false;
+ break; // ERROR: Extraneous data
+ }
+ $length = $length - 2; // The length we got counts itself
+
+ unset($data);
+ if ($marker == 0xE1) { // APP1: EXIF data
+ $data =& $this->_createMarkerEXIF();
+ $wroteEXIF = true;
+ }
+ elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data
+ $data =& $this->_createMarkerAdobe();
+ $wroteAdobe = true;
+ }
+ elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file
+ $done = true;
+ }
+
+ if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) {
+ if (isset($this->_info['exif']) && is_array($this->_info['exif'])) {
+ $exif =& $this->_createMarkerEXIF();
+ $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0);
+ unset($exif);
+ }
+ $wroteEXIF = true;
+ }
+
+ if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) {
+ if ((isset($this->_info['adobe']) && is_array($this->_info['adobe']))
+ || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) {
+ $adobe =& $this->_createMarkerAdobe();
+ $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0);
+ unset($adobe);
+ }
+ $wroteAdobe = true;
+ }
+
+ $origLength = $length;
+ if (isset($data)) {
+ $length = strlen($data);
+ }
+
+ if ($marker != -1) {
+ $this->_writeJPEGMarker($marker, $length, $data, $origLength);
+ }
+ }
+
+ if ($this->_fp) {
+ fclose($this->_fp);
+ $this->_fp = null;
+ }
+
+ if ($this->_fpout) {
+ fclose($this->_fpout);
+ $this->_fpout = null;
+ }
+
+ return $ok;
+ }
+
+ /*************************************************************/
+ function _writeJPEGMarker($marker, $length, &$data, $origLength)
+ {
+ if ($length <= 0) {
+ return false;
+ }
+
+ fputs($this->_fpout, chr(0xFF), 1);
+ fputs($this->_fpout, chr($marker), 1);
+ fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1);
+ fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1);
+
+ if (isset($data)) {
+ // Copy the generated data
+ fputs($this->_fpout, $data, $length);
+
+ if ($origLength > 0) { // Skip the original data
+ $result = @fseek($this->_fp, $origLength, SEEK_CUR);
+ // fseek doesn't seem to like HTTP 'files', but fgetc has no problem
+ if ($result != 0) {
+ for ($i = 0; $i < $origLength; $i++) {
+ fgetc($this->_fp);
+ }
+ }
+ }
+ }
+ else {
+ if ($marker == 0xDA) { // Copy until EOF
+ while (!feof($this->_fp)) {
+ $data =& fread($this->_fp, 1024 * 16);
+ fputs($this->_fpout, $data, strlen($data));
+ }
+ }
+ else { // Copy only $length bytes
+ $data =& fread($this->_fp, $length);
+ fputs($this->_fpout, $data, $length);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets basic info from the file - should work with non-JPEGs
+ *
+ * @author Sebastian Delmont <sdelmont@zonageek.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _parseFileInfo()
+ {
+ if (file_exists($this->_fileName)) {
+ $this->_info['file'] = array();
+ $this->_info['file']['Name'] = basename($this->_fileName);
+ $this->_info['file']['Path'] = realpath($this->_fileName);
+ $this->_info['file']['Size'] = filesize($this->_fileName);
+ if ($this->_info['file']['Size'] < 1024) {
+ $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B';
+ }
+ elseif ($this->_info['file']['Size'] < (1024 * 1024)) {
+ $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB';
+ }
+ elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) {
+ $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'MB';
+ }
+ else {
+ $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B';
+ }
+ $this->_info['file']['UnixTime'] = filemtime($this->_fileName);
+
+ // get image size directly from file
+ $size = getimagesize($this->_fileName);
+ $this->_info['file']['Width'] = $size[0];
+ $this->_info['file']['Height'] = $size[1];
+ // set mime types and formats
+ // http://www.php.net/manual/en/function.getimagesize.php
+ // http://www.php.net/manual/en/function.image-type-to-mime-type.php
+ switch ($size[2]){
+ case 1:
+ $this->_info['file']['Mime'] = 'image/gif';
+ $this->_info['file']['Format'] = 'GIF';
+ break;
+ case 2:
+ $this->_info['file']['Mime'] = 'image/jpeg';
+ $this->_info['file']['Format'] = 'JPEG';
+ break;
+ case 3:
+ $this->_info['file']['Mime'] = 'image/png';
+ $this->_info['file']['Format'] = 'PNG';
+ break;
+ case 4:
+ $this->_info['file']['Mime'] = 'application/x-shockwave-flash';
+ $this->_info['file']['Format'] = 'SWF';
+ break;
+ case 5:
+ $this->_info['file']['Mime'] = 'image/psd';
+ $this->_info['file']['Format'] = 'PSD';
+ break;
+ case 6:
+ $this->_info['file']['Mime'] = 'image/bmp';
+ $this->_info['file']['Format'] = 'BMP';
+ break;
+ case 7:
+ $this->_info['file']['Mime'] = 'image/tiff';
+ $this->_info['file']['Format'] = 'TIFF (Intel)';
+ break;
+ case 8:
+ $this->_info['file']['Mime'] = 'image/tiff';
+ $this->_info['file']['Format'] = 'TIFF (Motorola)';
+ break;
+ case 9:
+ $this->_info['file']['Mime'] = 'application/octet-stream';
+ $this->_info['file']['Format'] = 'JPC';
+ break;
+ case 10:
+ $this->_info['file']['Mime'] = 'image/jp2';
+ $this->_info['file']['Format'] = 'JP2';
+ break;
+ case 11:
+ $this->_info['file']['Mime'] = 'application/octet-stream';
+ $this->_info['file']['Format'] = 'JPX';
+ break;
+ case 12:
+ $this->_info['file']['Mime'] = 'application/octet-stream';
+ $this->_info['file']['Format'] = 'JB2';
+ break;
+ case 13:
+ $this->_info['file']['Mime'] = 'application/x-shockwave-flash';
+ $this->_info['file']['Format'] = 'SWC';
+ break;
+ case 14:
+ $this->_info['file']['Mime'] = 'image/iff';
+ $this->_info['file']['Format'] = 'IFF';
+ break;
+ case 15:
+ $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp';
+ $this->_info['file']['Format'] = 'WBMP';
+ break;
+ case 16:
+ $this->_info['file']['Mime'] = 'image/xbm';
+ $this->_info['file']['Format'] = 'XBM';
+ break;
+ default:
+ $this->_info['file']['Mime'] = 'image/unknown';
+ }
+ }
+ else {
+ $this->_info['file'] = array();
+ $this->_info['file']['Name'] = basename($this->_fileName);
+ $this->_info['file']['Url'] = $this->_fileName;
+ }
+
+ return true;
+ }
+
+ /*************************************************************/
+ function _parseMarkerJFIF()
+ {
+ if (!isset($this->_markers)) {
+ $this->_readJPEG();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $data = null;
+ $count = count($this->_markers);
+ for ($i = 0; $i < $count; $i++) {
+ if ($this->_markers[$i]['marker'] == 0xE0) {
+ $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4);
+ if ($signature == 'JFIF') {
+ $data =& $this->_markers[$i]['data'];
+ break;
+ }
+ }
+ }
+
+ if ($data == null) {
+ $this->_info['jfif'] = false;
+ return false;
+ }
+
+ $pos = 0;
+ $this->_info['jfif'] = array();
+
+
+ $vmaj = $this->_getByte($data, 5);
+ $vmin = $this->_getByte($data, 6);
+
+ $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin);
+
+ $units = $this->_getByte($data, 7);
+ switch ($units) {
+ case 0:
+ $this->_info['jfif']['Units'] = 'pixels';
+ break;
+ case 1:
+ $this->_info['jfif']['Units'] = 'dpi';
+ break;
+ case 2:
+ $this->_info['jfif']['Units'] = 'dpcm';
+ break;
+ default:
+ $this->_info['jfif']['Units'] = 'unknown';
+ break;
+ }
+
+ $xdens = $this->_getShort($data, 8);
+ $ydens = $this->_getShort($data, 10);
+
+ $this->_info['jfif']['XDensity'] = $xdens;
+ $this->_info['jfif']['YDensity'] = $ydens;
+
+ $thumbx = $this->_getByte($data, 12);
+ $thumby = $this->_getByte($data, 13);
+
+ $this->_info['jfif']['ThumbnailWidth'] = $thumbx;
+ $this->_info['jfif']['ThumbnailHeight'] = $thumby;
+
+ return true;
+ }
+
+ /*************************************************************/
+ function _parseMarkerSOF()
+ {
+ if (!isset($this->_markers)) {
+ $this->_readJPEG();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $data = null;
+ $count = count($this->_markers);
+ for ($i = 0; $i < $count; $i++) {
+ switch ($this->_markers[$i]['marker']) {
+ case 0xC0: // SOF0
+ case 0xC1: // SOF1
+ case 0xC2: // SOF2
+ case 0xC9: // SOF9
+ $data =& $this->_markers[$i]['data'];
+ $marker = $this->_markers[$i]['marker'];
+ break;
+ }
+ }
+
+ if ($data == null) {
+ $this->_info['sof'] = false;
+ return false;
+ }
+
+ $pos = 0;
+ $this->_info['sof'] = array();
+
+
+ switch ($marker) {
+ case 0xC0: // SOF0
+ $format = 'Baseline';
+ break;
+ case 0xC1: // SOF1
+ $format = 'Progessive';
+ break;
+ case 0xC2: // SOF2
+ $format = 'Non-baseline';
+ break;
+ case 0xC9: // SOF9
+ $format = 'Arithmetic';
+ break;
+ default:
+ return false;
+ break;
+ }
+
+
+ $this->_info['sof']['Format'] = $format;
+
+ $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0);
+ $this->_info['sof']['ImageHeight'] = $this->_getShort($data, $pos + 1);
+ $this->_info['sof']['ImageWidth'] = $this->_getShort($data, $pos + 3);
+ $this->_info['sof']['ColorChannels'] = $this->_getByte($data, $pos + 5);
+
+ return true;
+ }
+
+ /*************************************************************/
+ function _parseMarkerExif()
+ {
+ if (!isset($this->_markers)) {
+ $this->_readJPEG();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $data = null;
+ $count = count($this->_markers);
+ for ($i = 0; $i < $count; $i++) {
+ if ($this->_markers[$i]['marker'] == 0xE1) {
+ $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6);
+ if ($signature == "Exif\0\0") {
+ $data =& $this->_markers[$i]['data'];
+ break;
+ }
+ }
+ }
+
+ if ($data == null) {
+ $this->_info['exif'] = false;
+ return false;
+ }
+ $pos = 6;
+ $this->_info['exif'] = array();
+
+ // We don't increment $pos after this because Exif uses offsets relative to this point
+
+ $byteAlign = $this->_getShort($data, $pos + 0);
+
+ if ($byteAlign == 0x4949) { // "II"
+ $isBigEndian = false;
+ }
+ elseif ($byteAlign == 0x4D4D) { // "MM"
+ $isBigEndian = true;
+ }
+ else {
+ return false; // Unexpected data
+ }
+
+ $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian);
+ if ($alignCheck != 0x002A) // That's the expected value
+ return false; // Unexpected data
+
+ if ($isBigEndian) {
+ $this->_info['exif']['ByteAlign'] = "Big Endian";
+ }
+ else {
+ $this->_info['exif']['ByteAlign'] = "Little Endian";
+ }
+
+ $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian);
+ if ($offsetIFD0 < 8)
+ return false; // Unexpected data
+
+ $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0');
+ if ($offsetIFD1 != 0)
+ $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1');
+
+ return true;
+ }
+
+ /*************************************************************/
+ function _readIFD($data, $base, $offset, $isBigEndian, $mode)
+ {
+ $EXIFTags = $this->_exifTagNames($mode);
+
+ $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian);
+ $offset += 2;
+
+ $exifTIFFOffset = 0;
+ $exifTIFFLength = 0;
+ $exifThumbnailOffset = 0;
+ $exifThumbnailLength = 0;
+
+ for ($i = 0; $i < $numEntries; $i++) {
+ $tag = $this->_getShort($data, $base + $offset, $isBigEndian);
+ $offset += 2;
+ $type = $this->_getShort($data, $base + $offset, $isBigEndian);
+ $offset += 2;
+ $count = $this->_getLong($data, $base + $offset, $isBigEndian);
+ $offset += 4;
+
+ if (($type < 1) || ($type > 12))
+ return false; // Unexpected Type
+
+ $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 );
+
+ $dataLength = $typeLengths[$type] * $count;
+ if ($dataLength > 4) {
+ $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian);
+ $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength);
+ }
+ else {
+ $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength);
+ }
+ $offset += 4;
+
+ switch ($type) {
+ case 1: // UBYTE
+ if ($count == 1) {
+ $value = $this->_getByte($rawValue, 0);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getByte($rawValue, $j);
+ }
+ break;
+ case 2: // ASCII
+ $value = $rawValue;
+ break;
+ case 3: // USHORT
+ if ($count == 1) {
+ $value = $this->_getShort($rawValue, 0, $isBigEndian);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian);
+ }
+ break;
+ case 4: // ULONG
+ if ($count == 1) {
+ $value = $this->_getLong($rawValue, 0, $isBigEndian);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian);
+ }
+ break;
+ case 5: // URATIONAL
+ if ($count == 1) {
+ $a = $this->_getLong($rawValue, 0, $isBigEndian);
+ $b = $this->_getLong($rawValue, 4, $isBigEndian);
+ $value = array();
+ $value['val'] = 0;
+ $value['num'] = $a;
+ $value['den'] = $b;
+ if (($a != 0) && ($b != 0)) {
+ $value['val'] = $a / $b;
+ }
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++) {
+ $a = $this->_getLong($rawValue, $j * 8, $isBigEndian);
+ $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian);
+ $value = array();
+ $value[$j]['val'] = 0;
+ $value[$j]['num'] = $a;
+ $value[$j]['den'] = $b;
+ if (($a != 0) && ($b != 0))
+ $value[$j]['val'] = $a / $b;
+ }
+ }
+ break;
+ case 6: // SBYTE
+ if ($count == 1) {
+ $value = $this->_getByte($rawValue, 0);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getByte($rawValue, $j);
+ }
+ break;
+ case 7: // UNDEFINED
+ $value = $rawValue;
+ break;
+ case 8: // SSHORT
+ if ($count == 1) {
+ $value = $this->_getShort($rawValue, 0, $isBigEndian);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian);
+ }
+ break;
+ case 9: // SLONG
+ if ($count == 1) {
+ $value = $this->_getLong($rawValue, 0, $isBigEndian);
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++)
+ $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian);
+ }
+ break;
+ case 10: // SRATIONAL
+ if ($count == 1) {
+ $a = $this->_getLong($rawValue, 0, $isBigEndian);
+ $b = $this->_getLong($rawValue, 4, $isBigEndian);
+ $value = array();
+ $value['val'] = 0;
+ $value['num'] = $a;
+ $value['den'] = $b;
+ if (($a != 0) && ($b != 0))
+ $value['val'] = $a / $b;
+ }
+ else {
+ $value = array();
+ for ($j = 0; $j < $count; $j++) {
+ $a = $this->_getLong($rawValue, $j * 8, $isBigEndian);
+ $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian);
+ $value = array();
+ $value[$j]['val'] = 0;
+ $value[$j]['num'] = $a;
+ $value[$j]['den'] = $b;
+ if (($a != 0) && ($b != 0))
+ $value[$j]['val'] = $a / $b;
+ }
+ }
+ break;
+ case 11: // FLOAT
+ $value = $rawValue;
+ break;
+
+ case 12: // DFLOAT
+ $value = $rawValue;
+ break;
+ default:
+ return false; // Unexpected Type
+ }
+
+ $tagName = '';
+ if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset
+ $this->_readIFD($data, $base, $value, $isBigEndian, 'exif');
+ }
+ elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset
+ $this->_readIFD($data, $base, $value, $isBigEndian, 'gps');
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets
+ $exifTIFFOffset = $value;
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts
+ $exifTIFFLength = $value;
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset
+ $exifThumbnailOffset = $value;
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength
+ $exifThumbnailLength = $value;
+ }
+ elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset
+ $this->_readIFD($data, $base, $value, $isBigEndian, 'interop');
+ }
+ // elseif (($mode == 'exif') && ($tag == 0x927C)) { // MakerNote
+ // }
+ else {
+ if (isset($EXIFTags[$tag])) {
+ $tagName = $EXIFTags[$tag];
+ if (isset($this->_info['exif'][$tagName])) {
+ if (!is_array($this->_info['exif'][$tagName])) {
+ $aux = array();
+ $aux[0] = $this->_info['exif'][$tagName];
+ $this->_info['exif'][$tagName] = $aux;
+ }
+
+ $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value;
+ }
+ else {
+ $this->_info['exif'][$tagName] = $value;
+ }
+ }
+ else {
+#echo sprintf("<h1>Unknown tag %02x (t: %d l: %d) %s in %s</h1>", $tag, $type, $count, $mode, $this->_fileName);
+ // Unknown Tags will be ignored!!!
+ // That's because the tag might be a pointer (like the Exif tag)
+ // and saving it without saving the data it points to might
+ // create an invalid file.
+ }
+ }
+ }
+
+ if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) {
+ $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength);
+ }
+
+ if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) {
+ $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength);
+ }
+
+ $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian);
+ return $nextOffset;
+ }
+
+ /*************************************************************/
+ function & _createMarkerExif()
+ {
+ $data = null;
+ $count = count($this->_markers);
+ for ($i = 0; $i < $count; $i++) {
+ if ($this->_markers[$i]['marker'] == 0xE1) {
+ $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6);
+ if ($signature == "Exif\0\0") {
+ $data =& $this->_markers[$i]['data'];
+ break;
+ }
+ }
+ }
+
+ if (!isset($this->_info['exif'])) {
+ return false;
+ }
+
+ $data = "Exif\0\0";
+ $pos = 6;
+ $offsetBase = 6;
+
+ if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) {
+ $isBigEndian = true;
+ $aux = "MM";
+ $pos = $this->_putString($data, $pos, $aux);
+ }
+ else {
+ $isBigEndian = false;
+ $aux = "II";
+ $pos = $this->_putString($data, $pos, $aux);
+ }
+ $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian);
+ $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8
+
+ $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0');
+ $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1');
+
+ $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true);
+ $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false);
+
+ return $data;
+ }
+
+ /*************************************************************/
+ function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext)
+ {
+ $tiffData = null;
+ $tiffDataOffsetPos = -1;
+
+ $entryCount = count($entries);
+
+ $dataPos = $pos + 2 + ($entryCount * 12) + 4;
+ $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian);
+
+ for ($i = 0; $i < $entryCount; $i++) {
+ $tag = $entries[$i]['tag'];
+ $type = $entries[$i]['type'];
+
+ if ($type == -99) { // SubIFD
+ $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
+ $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG
+ $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1
+ $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
+
+ $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false);
+ }
+ elseif ($type == -98) { // TIFF Data
+ $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
+ $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG
+ $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1
+ $tiffDataOffsetPos = $pos;
+ $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now
+ $tiffData =& $entries[$i]['value'] ;
+ }
+ else { // Regular Entry
+ $pos = $this->_putShort($data, $pos, $tag, $isBigEndian);
+ $pos = $this->_putShort($data, $pos, $type, $isBigEndian);
+ $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian);
+ if (strlen($entries[$i]['value']) > 4) {
+ $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
+ $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']);
+ }
+ else {
+ $val = str_pad($entries[$i]['value'], 4, "\0");
+ $pos = $this->_putString($data, $pos, $val);
+ }
+ }
+ }
+
+ if ($tiffData != null) {
+ $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian);
+ $dataPos = $this->_putString($data, $dataPos, $tiffData);
+ }
+
+ if ($hasNext) {
+ $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian);
+ }
+ else {
+ $pos = $this->_putLong($data, $pos, 0, $isBigEndian);
+ }
+
+ return $dataPos;
+ }
+
+ /*************************************************************/
+ function & _getIFDEntries($isBigEndian, $mode)
+ {
+ $EXIFNames = $this->_exifTagNames($mode);
+ $EXIFTags = $this->_exifNameTags($mode);
+ $EXIFTypeInfo = $this->_exifTagTypes($mode);
+
+ $ifdEntries = array();
+ $entryCount = 0;
+
+ reset($EXIFNames);
+ while (list($tag, $name) = each($EXIFNames)) {
+ $type = $EXIFTypeInfo[$tag][0];
+ $count = $EXIFTypeInfo[$tag][1];
+ $value = null;
+
+ if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset
+ if (isset($this->_info['exif']['EXIFVersion'])) {
+ $value =& $this->_getIFDEntries($isBigEndian, "exif");
+ $type = -99;
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset
+ if (isset($this->_info['exif']['GPSVersionID'])) {
+ $value =& $this->_getIFDEntries($isBigEndian, "gps");
+ $type = -99;
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets
+ if (isset($this->_info['exif']['TIFFStrips'])) {
+ $value =& $this->_info['exif']['TIFFStrips'];
+ $type = -98;
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts
+ if (isset($this->_info['exif']['TIFFStrips'])) {
+ $value = strlen($this->_info['exif']['TIFFStrips']);
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset
+ if (isset($this->_info['exif']['JFIFThumbnail'])) {
+ $value =& $this->_info['exif']['JFIFThumbnail'];
+ $type = -98;
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength
+ if (isset($this->_info['exif']['JFIFThumbnail'])) {
+ $value = strlen($this->_info['exif']['JFIFThumbnail']);
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset
+ if (isset($this->_info['exif']['InteroperabilityIndex'])) {
+ $value =& $this->_getIFDEntries($isBigEndian, "interop");
+ $type = -99;
+ }
+ else {
+ $value = null;
+ }
+ }
+ elseif (isset($this->_info['exif'][$name])) {
+ $origValue =& $this->_info['exif'][$name];
+
+ // This makes it easier to process variable size elements
+ if (!is_array($origValue) || isset($origValue['val'])) {
+ unset($origValue); // Break the reference
+ $origValue = array($this->_info['exif'][$name]);
+ }
+ $origCount = count($origValue);
+
+ if ($origCount == 0 ) {
+ $type = -1; // To ignore this field
+ }
+
+ $value = " ";
+
+ switch ($type) {
+ case 1: // UBYTE
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+
+ $this->_putByte($value, $j, $origValue[$j]);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putByte($value, $j, 0);
+ $j++;
+ }
+ break;
+ case 2: // ASCII
+ $v = strval($origValue[0]);
+ if (($count != 0) && (strlen($v) > $count)) {
+ $v = substr($v, 0, $count);
+ }
+ elseif (($count > 0) && (strlen($v) < $count)) {
+ $v = str_pad($v, $count, "\0");
+ }
+
+ $count = strlen($v);
+
+ $this->_putString($value, 0, $v);
+ break;
+ case 3: // USHORT
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putShort($value, $j * 2, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 4: // ULONG
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putLong($value, $j * 4, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 5: // URATIONAL
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $v = $origValue[$j];
+ if (is_array($v)) {
+ $a = $v['num'];
+ $b = $v['den'];
+ }
+ else {
+ $a = 0;
+ $b = 0;
+ // TODO: Allow other types and convert them
+ }
+ $this->_putLong($value, $j * 8, $a, $isBigEndian);
+ $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putLong($value, $j * 8, 0, $isBigEndian);
+ $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 6: // SBYTE
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $this->_putByte($value, $j, $origValue[$j]);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putByte($value, $j, 0);
+ $j++;
+ }
+ break;
+ case 7: // UNDEFINED
+ $v = strval($origValue[0]);
+ if (($count != 0) && (strlen($v) > $count)) {
+ $v = substr($v, 0, $count);
+ }
+ elseif (($count > 0) && (strlen($v) < $count)) {
+ $v = str_pad($v, $count, "\0");
+ }
+
+ $count = strlen($v);
+
+ $this->_putString($value, 0, $v);
+ break;
+ case 8: // SSHORT
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putShort($value, $j * 2, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 9: // SLONG
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putLong($value, $j * 4, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 10: // SRATIONAL
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $v = $origValue[$j];
+ if (is_array($v)) {
+ $a = $v['num'];
+ $b = $v['den'];
+ }
+ else {
+ $a = 0;
+ $b = 0;
+ // TODO: Allow other types and convert them
+ }
+
+ $this->_putLong($value, $j * 8, $a, $isBigEndian);
+ $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putLong($value, $j * 8, 0, $isBigEndian);
+ $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian);
+ $j++;
+ }
+ break;
+ case 11: // FLOAT
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $v = strval($origValue[$j]);
+ if (strlen($v) > 4) {
+ $v = substr($v, 0, 4);
+ }
+ elseif (strlen($v) < 4) {
+ $v = str_pad($v, 4, "\0");
+ }
+ $this->_putString($value, $j * 4, $v);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putString($value, $j * 4, "\0\0\0\0");
+ $j++;
+ }
+ break;
+ case 12: // DFLOAT
+ if ($count == 0) {
+ $count = $origCount;
+ }
+
+ $j = 0;
+ while (($j < $count) && ($j < $origCount)) {
+ $v = strval($origValue[$j]);
+ if (strlen($v) > 8) {
+ $v = substr($v, 0, 8);
+ }
+ elseif (strlen($v) < 8) {
+ $v = str_pad($v, 8, "\0");
+ }
+ $this->_putString($value, $j * 8, $v);
+ $j++;
+ }
+
+ while ($j < $count) {
+ $this->_putString($value, $j * 8, "\0\0\0\0\0\0\0\0");
+ $j++;
+ }
+ break;
+ default:
+ $value = null;
+ break;
+ }
+ }
+
+ if ($value != null) {
+ $ifdEntries[$entryCount] = array();
+ $ifdEntries[$entryCount]['tag'] = $tag;
+ $ifdEntries[$entryCount]['type'] = $type;
+ $ifdEntries[$entryCount]['count'] = $count;
+ $ifdEntries[$entryCount]['value'] = $value;
+
+ $entryCount++;
+ }
+ }
+
+ return $ifdEntries;
+ }
+
+ /*************************************************************/
+ function _parseMarkerAdobe()
+ {
+ if (!isset($this->_markers)) {
+ $this->_readJPEG();
+ }
+
+ if ($this->_markers == null) {
+ return false;
+ }
+
+ $data = null;
+ $count = count($this->_markers);
+ for ($i = 0; $i < $count; $i++) {
+ if ($this->_markers[$i]['marker'] == 0xED) {
+ $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14);
+ if ($signature == "Photoshop 3.0\0") {
+ $data =& $this->_markers[$i]['data'];
+ break;
+ }
+ }
+ }
+
+ if ($data == null) {
+ $this->_info['adobe'] = false;
+ $this->_info['iptc'] = false;
+ return false;
+ }
+ $pos = 14;
+ $this->_info['adobe'] = array();
+ $this->_info['adobe']['raw'] = array();
+ $this->_info['iptc'] = array();
+
+ $datasize = strlen($data);
+
+ while ($pos < $datasize) {
+ $signature = $this->_getFixedString($data, $pos, 4);
+ if ($signature != '8BIM')
+ return false;
+ $pos += 4;
+
+ $type = $this->_getShort($data, $pos);
+ $pos += 2;
+
+ $strlen = $this->_getByte($data, $pos);
+ $pos += 1;
+ $header = '';
+ for ($i = 0; $i < $strlen; $i++) {
+ $header .= $data{$pos + $i};
+ }
+ $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself
+
+ $length = $this->_getLong($data, $pos);
+ $pos += 4;
+
+ $basePos = $pos;
+
+ switch ($type) {
+ case 0x0404: // Caption (IPTC Data)
+ $pos = $this->_readIPTC($data, $pos);
+ if ($pos == false)
+ return false;
+ break;
+ case 0x040A: // CopyrightFlag
+ $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos);
+ $pos += $length;
+ break;
+ case 0x040B: // ImageURL
+ $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length);
+ $pos += $length;
+ break;
+ case 0x040C: // Thumbnail
+ $aux = $this->_getLong($data, $pos);
+ $pos += 4;
+ if ($aux == 1) {
+ $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos);
+ $pos += 4;
+ $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos);
+ $pos += 4;
+
+ $pos += 16; // Skip some data
+
+ $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28);
+ $pos += $length - 28;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // We save all blocks, even those we recognized
+ $label = sprintf('8BIM_0x%04x', $type);
+ $this->_info['adobe']['raw'][$label] = array();
+ $this->_info['adobe']['raw'][$label]['type'] = $type;
+ $this->_info['adobe']['raw'][$label]['header'] = $header;
+ $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length);
+
+ $pos = $basePos + $length + ($length % 2); // Even padding
+ }
+
+ }
+
+ /*************************************************************/
+ function _readIPTC(&$data, $pos = 0)
+ {
+ $totalLength = strlen($data);
+
+ $IPTCTags =& $this->_iptcTagNames();
+
+ while ($pos < ($totalLength - 5)) {
+ $signature = $this->_getShort($data, $pos);
+ if ($signature != 0x1C02)
+ return $pos;
+ $pos += 2;
+
+ $type = $this->_getByte($data, $pos);
+ $pos += 1;
+ $length = $this->_getShort($data, $pos);
+ $pos += 2;
+
+ $basePos = $pos;
+ $label = '';
+
+ if (isset($IPTCTags[$type])) {
+ $label = $IPTCTags[$type];
+ }
+ else {
+ $label = sprintf('IPTC_0x%02x', $type);
+ }
+
+ if ($label != '') {
+ if (isset($this->_info['iptc'][$label])) {
+ if (!is_array($this->_info['iptc'][$label])) {
+ $aux = array();
+ $aux[0] = $this->_info['iptc'][$label];
+ $this->_info['iptc'][$label] = $aux;
+ }
+ $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length);
+ }
+ else {
+ $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length);
+ }
+ }
+
+ $pos = $basePos + $length; // No padding
+ }
+ return $pos;
+ }
+
+ /*************************************************************/
+ function & _createMarkerAdobe()
+ {
+ if (isset($this->_info['iptc'])) {
+ if (!isset($this->_info['adobe'])) {
+ $this->_info['adobe'] = array();
+ }
+ if (!isset($this->_info['adobe']['raw'])) {
+ $this->_info['adobe']['raw'] = array();
+ }
+ if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) {
+ $this->_info['adobe']['raw']['8BIM_0x0404'] = array();
+ }
+ $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404;
+ $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption";
+ $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC();
+ }
+
+ if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) {
+ $data = "Photoshop 3.0\0";
+ $pos = 14;
+
+ reset($this->_info['adobe']['raw']);
+ while (list($key) = each($this->_info['adobe']['raw'])) {
+ $pos = $this->_write8BIM(
+ $data,
+ $pos,
+ $this->_info['adobe']['raw'][$key]['type'],
+ $this->_info['adobe']['raw'][$key]['header'],
+ $this->_info['adobe']['raw'][$key]['data'] );
+ }
+ }
+
+ return $data;
+ }
+
+ /*************************************************************/
+ function _write8BIM(&$data, $pos, $type, $header, &$value)
+ {
+ $signature = "8BIM";
+
+ $pos = $this->_putString($data, $pos, $signature);
+ $pos = $this->_putShort($data, $pos, $type);
+
+ $len = strlen($header);
+
+ $pos = $this->_putByte($data, $pos, $len);
+ $pos = $this->_putString($data, $pos, $header);
+ if (($len % 2) == 0) { // Even padding, including the length byte
+ $pos = $this->_putByte($data, $pos, 0);
+ }
+
+ $len = strlen($value);
+ $pos = $this->_putLong($data, $pos, $len);
+ $pos = $this->_putString($data, $pos, $value);
+ if (($len % 2) != 0) { // Even padding
+ $pos = $this->_putByte($data, $pos, 0);
+ }
+ return $pos;
+ }
+
+ /*************************************************************/
+ function & _writeIPTC()
+ {
+ $data = " ";
+ $pos = 0;
+
+ $IPTCNames =& $this->_iptcNameTags();
+
+ reset($this->_info['iptc']);
+
+
+ while (list($label) = each($this->_info['iptc'])) {
+ $value =& $this->_info['iptc'][$label];
+ $type = -1;
+
+ if (isset($IPTCNames[$label])) {
+ $type = $IPTCNames[$label];
+ }
+ elseif (substr($label, 0, 7) == "IPTC_0x") {
+ $type = hexdec(substr($label, 7, 2));
+ }
+
+ if ($type != -1) {
+ if (is_array($value)) {
+ for ($i = 0; $i < count($value); $i++) {
+ $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]);
+ }
+ }
+ else {
+ $pos = $this->_writeIPTCEntry($data, $pos, $type, $value);
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /*************************************************************/
+ function _writeIPTCEntry(&$data, $pos, $type, &$value)
+ {
+ $pos = $this->_putShort($data, $pos, 0x1C02);
+ $pos = $this->_putByte($data, $pos, $type);
+ $pos = $this->_putShort($data, $pos, strlen($value));
+ $pos = $this->_putString($data, $pos, $value);
+
+ return $pos;
+ }
+
+ /*************************************************************/
+ function _exifTagNames($mode)
+ {
+ $tags = array();
+
+ if ($mode == 'ifd0') {
+ $tags[0x010E] = 'ImageDescription';
+ $tags[0x010F] = 'Make';
+ $tags[0x0110] = 'Model';
+ $tags[0x0112] = 'Orientation';
+ $tags[0x011A] = 'XResolution';
+ $tags[0x011B] = 'YResolution';
+ $tags[0x0128] = 'ResolutionUnit';
+ $tags[0x0131] = 'Software';
+ $tags[0x0132] = 'DateTime';
+ $tags[0x013B] = 'Artist';
+ $tags[0x013E] = 'WhitePoint';
+ $tags[0x013F] = 'PrimaryChromaticities';
+ $tags[0x0211] = 'YCbCrCoefficients';
+ $tags[0x0212] = 'YCbCrSubSampling';
+ $tags[0x0213] = 'YCbCrPositioning';
+ $tags[0x0214] = 'ReferenceBlackWhite';
+ $tags[0x8298] = 'Copyright';
+ $tags[0x8769] = 'ExifIFDOffset';
+ $tags[0x8825] = 'GPSIFDOffset';
+ }
+ if ($mode == 'ifd1') {
+ $tags[0x00FE] = 'TIFFNewSubfileType';
+ $tags[0x00FF] = 'TIFFSubfileType';
+ $tags[0x0100] = 'TIFFImageWidth';
+ $tags[0x0101] = 'TIFFImageHeight';
+ $tags[0x0102] = 'TIFFBitsPerSample';
+ $tags[0x0103] = 'TIFFCompression';
+ $tags[0x0106] = 'TIFFPhotometricInterpretation';
+ $tags[0x0107] = 'TIFFThreshholding';
+ $tags[0x0108] = 'TIFFCellWidth';
+ $tags[0x0109] = 'TIFFCellLength';
+ $tags[0x010A] = 'TIFFFillOrder';
+ $tags[0x010E] = 'TIFFImageDescription';
+ $tags[0x010F] = 'TIFFMake';
+ $tags[0x0110] = 'TIFFModel';
+ $tags[0x0111] = 'TIFFStripOffsets';
+ $tags[0x0112] = 'TIFFOrientation';
+ $tags[0x0115] = 'TIFFSamplesPerPixel';
+ $tags[0x0116] = 'TIFFRowsPerStrip';
+ $tags[0x0117] = 'TIFFStripByteCounts';
+ $tags[0x0118] = 'TIFFMinSampleValue';
+ $tags[0x0119] = 'TIFFMaxSampleValue';
+ $tags[0x011A] = 'TIFFXResolution';
+ $tags[0x011B] = 'TIFFYResolution';
+ $tags[0x011C] = 'TIFFPlanarConfiguration';
+ $tags[0x0122] = 'TIFFGrayResponseUnit';
+ $tags[0x0123] = 'TIFFGrayResponseCurve';
+ $tags[0x0128] = 'TIFFResolutionUnit';
+ $tags[0x0131] = 'TIFFSoftware';
+ $tags[0x0132] = 'TIFFDateTime';
+ $tags[0x013B] = 'TIFFArtist';
+ $tags[0x013C] = 'TIFFHostComputer';
+ $tags[0x0140] = 'TIFFColorMap';
+ $tags[0x0152] = 'TIFFExtraSamples';
+ $tags[0x0201] = 'TIFFJFIFOffset';
+ $tags[0x0202] = 'TIFFJFIFLength';
+ $tags[0x0211] = 'TIFFYCbCrCoefficients';
+ $tags[0x0212] = 'TIFFYCbCrSubSampling';
+ $tags[0x0213] = 'TIFFYCbCrPositioning';
+ $tags[0x0214] = 'TIFFReferenceBlackWhite';
+ $tags[0x8298] = 'TIFFCopyright';
+ $tags[0x9286] = 'TIFFUserComment';
+ }
+ elseif ($mode == 'exif') {
+ $tags[0x829A] = 'ExposureTime';
+ $tags[0x829D] = 'FNumber';
+ $tags[0x8822] = 'ExposureProgram';
+ $tags[0x8824] = 'SpectralSensitivity';
+ $tags[0x8827] = 'ISOSpeedRatings';
+ $tags[0x8828] = 'OECF';
+ $tags[0x9000] = 'EXIFVersion';
+ $tags[0x9003] = 'DatetimeOriginal';
+ $tags[0x9004] = 'DatetimeDigitized';
+ $tags[0x9101] = 'ComponentsConfiguration';
+ $tags[0x9102] = 'CompressedBitsPerPixel';
+ $tags[0x9201] = 'ShutterSpeedValue';
+ $tags[0x9202] = 'ApertureValue';
+ $tags[0x9203] = 'BrightnessValue';
+ $tags[0x9204] = 'ExposureBiasValue';
+ $tags[0x9205] = 'MaxApertureValue';
+ $tags[0x9206] = 'SubjectDistance';
+ $tags[0x9207] = 'MeteringMode';
+ $tags[0x9208] = 'LightSource';
+ $tags[0x9209] = 'Flash';
+ $tags[0x920A] = 'FocalLength';
+ $tags[0x927C] = 'MakerNote';
+ $tags[0x9286] = 'UserComment';
+ $tags[0x9290] = 'SubSecTime';
+ $tags[0x9291] = 'SubSecTimeOriginal';
+ $tags[0x9292] = 'SubSecTimeDigitized';
+ $tags[0xA000] = 'FlashPixVersion';
+ $tags[0xA001] = 'ColorSpace';
+ $tags[0xA002] = 'PixelXDimension';
+ $tags[0xA003] = 'PixelYDimension';
+ $tags[0xA004] = 'RelatedSoundFile';
+ $tags[0xA005] = 'InteropIFDOffset';
+ $tags[0xA20B] = 'FlashEnergy';
+ $tags[0xA20C] = 'SpatialFrequencyResponse';
+ $tags[0xA20E] = 'FocalPlaneXResolution';
+ $tags[0xA20F] = 'FocalPlaneYResolution';
+ $tags[0xA210] = 'FocalPlaneResolutionUnit';
+ $tags[0xA214] = 'SubjectLocation';
+ $tags[0xA215] = 'ExposureIndex';
+ $tags[0xA217] = 'SensingMethod';
+ $tags[0xA300] = 'FileSource';
+ $tags[0xA301] = 'SceneType';
+ $tags[0xA302] = 'CFAPattern';
+ }
+ elseif ($mode == 'interop') {
+ $tags[0x0001] = 'InteroperabilityIndex';
+ $tags[0x0002] = 'InteroperabilityVersion';
+ $tags[0x1000] = 'RelatedImageFileFormat';
+ $tags[0x1001] = 'RelatedImageWidth';
+ $tags[0x1002] = 'RelatedImageLength';
+ }
+ elseif ($mode == 'gps') {
+ $tags[0x0000] = 'GPSVersionID';
+ $tags[0x0001] = 'GPSLatitudeRef';
+ $tags[0x0002] = 'GPSLatitude';
+ $tags[0x0003] = 'GPSLongitudeRef';
+ $tags[0x0004] = 'GPSLongitude';
+ $tags[0x0005] = 'GPSAltitudeRef';
+ $tags[0x0006] = 'GPSAltitude';
+ $tags[0x0007] = 'GPSTimeStamp';
+ $tags[0x0008] = 'GPSSatellites';
+ $tags[0x0009] = 'GPSStatus';
+ $tags[0x000A] = 'GPSMeasureMode';
+ $tags[0x000B] = 'GPSDOP';
+ $tags[0x000C] = 'GPSSpeedRef';
+ $tags[0x000D] = 'GPSSpeed';
+ $tags[0x000E] = 'GPSTrackRef';
+ $tags[0x000F] = 'GPSTrack';
+ $tags[0x0010] = 'GPSImgDirectionRef';
+ $tags[0x0011] = 'GPSImgDirection';
+ $tags[0x0012] = 'GPSMapDatum';
+ $tags[0x0013] = 'GPSDestLatitudeRef';
+ $tags[0x0014] = 'GPSDestLatitude';
+ $tags[0x0015] = 'GPSDestLongitudeRef';
+ $tags[0x0016] = 'GPSDestLongitude';
+ $tags[0x0017] = 'GPSDestBearingRef';
+ $tags[0x0018] = 'GPSDestBearing';
+ $tags[0x0019] = 'GPSDestDistanceRef';
+ $tags[0x001A] = 'GPSDestDistance';
+ }
+
+ return $tags;
+ }
+
+ /*************************************************************/
+ function _exifTagTypes($mode)
+ {
+ $tags = array();
+
+ if ($mode == 'ifd0') {
+ $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any
+ $tags[0x010F] = array(2, 0); // Make -> ASCII, Any
+ $tags[0x0110] = array(2, 0); // Model -> ASCII, Any
+ $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1
+ $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1
+ $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1
+ $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT
+ $tags[0x0131] = array(2, 0); // Software -> ASCII, Any
+ $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20
+ $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any
+ $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2
+ $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6
+ $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3
+ $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2
+ $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1
+ $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6
+ $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any
+ $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1
+ $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1
+ }
+ if ($mode == 'ifd1') {
+ $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1
+ $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1
+ $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1
+ $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1
+ $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3
+ $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1
+ $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1
+ $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1
+ $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1
+ $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1
+ $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1
+ $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any
+ $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any
+ $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any
+ $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip)
+ $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1
+ $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1
+ $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1
+ $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip)
+ $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel)
+ $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel)
+ $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1
+ $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1
+ $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1
+ $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1
+ $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample)
+ $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1
+ $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any
+ $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20
+ $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any
+ $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any
+ $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample)
+ $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3)
+ $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1
+ $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1
+ $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3
+ $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2
+ $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1
+ $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6
+ $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any
+ $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any
+ }
+ elseif ($mode == 'exif') {
+ $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1
+ $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1
+ $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1
+ $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any
+ $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any
+ $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any
+ $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4
+ $tags[0x9003] = array(2, 20); // DatetimeOriginal -> ASCII, 20
+ $tags[0x9004] = array(2, 20); // DatetimeDigitized -> ASCII, 20
+ $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4
+ $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1
+ $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1
+ $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1
+ $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1
+ $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1
+ $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1
+ $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1
+ $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1
+ $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1
+ $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1
+ $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1
+ $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any
+ $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any
+ $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any
+ $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any
+ $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any
+ $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4
+ $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1
+ $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1
+ $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1
+ $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13
+ $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1
+ $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1
+ $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any
+ $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1
+ $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1
+ $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1
+ $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2
+ $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1
+ $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1
+ $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1
+ $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1
+ $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any
+ }
+ elseif ($mode == 'interop') {
+ $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any
+ $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4
+ $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any
+ $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1
+ $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1
+ }
+ elseif ($mode == 'gps') {
+ $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4
+ $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2
+ $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3
+ $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2
+ $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3
+ $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2
+ $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1
+ $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3
+ $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any
+ $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2
+ $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2
+ $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1
+ $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2
+ $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1
+ $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2
+ $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1
+ $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2
+ $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1
+ $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any
+ $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2
+ $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3
+ $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2
+ $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3
+ $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2
+ $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1
+ $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2
+ $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1
+ }
+
+ return $tags;
+ }
+
+ /*************************************************************/
+ function _exifNameTags($mode)
+ {
+ $tags = $this->_exifTagNames($mode);
+ return $this->_names2Tags($tags);
+ }
+
+ /*************************************************************/
+ function _iptcTagNames()
+ {
+ $tags = array();
+ $tags[0x14] = 'SuplementalCategories';
+ $tags[0x19] = 'Keywords';
+ $tags[0x78] = 'Caption';
+ $tags[0x7A] = 'CaptionWriter';
+ $tags[0x69] = 'Headline';
+ $tags[0x28] = 'SpecialInstructions';
+ $tags[0x0F] = 'Category';
+ $tags[0x50] = 'Byline';
+ $tags[0x55] = 'BylineTitle';
+ $tags[0x6E] = 'Credit';
+ $tags[0x73] = 'Source';
+ $tags[0x74] = 'CopyrightNotice';
+ $tags[0x05] = 'ObjectName';
+ $tags[0x5A] = 'City';
+ $tags[0x5C] = 'Sublocation';
+ $tags[0x5F] = 'ProvinceState';
+ $tags[0x65] = 'CountryName';
+ $tags[0x67] = 'OriginalTransmissionReference';
+ $tags[0x37] = 'DateCreated';
+ $tags[0x0A] = 'CopyrightFlag';
+
+ return $tags;
+ }
+
+ /*************************************************************/
+ function & _iptcNameTags()
+ {
+ $tags = $this->_iptcTagNames();
+ return $this->_names2Tags($tags);
+ }
+
+ /*************************************************************/
+ function _names2Tags($tags2Names)
+ {
+ $names2Tags = array();
+ reset($tags2Names);
+ while (list($tag, $name) = each($tags2Names)) {
+ $names2Tags[$name] = $tag;
+ }
+
+ return $names2Tags;
+ }
+
+ /*************************************************************/
+ function _getByte(&$data, $pos)
+ {
+ return ord($data{$pos});
+ }
+
+ /*************************************************************/
+ function _putByte(&$data, $pos, $val)
+ {
+ $val = intval($val);
+
+ $data{$pos} = chr($val);
+
+ return $pos + 1;
+ }
+
+ /*************************************************************/
+ function _getShort(&$data, $pos, $bigEndian = true)
+ {
+ if ($bigEndian) {
+ return (ord($data{$pos}) << 8)
+ + ord($data{$pos + 1});
+ }
+ else {
+ return ord($data{$pos})
+ + (ord($data{$pos + 1}) << 8);
+ }
+ }
+
+ /*************************************************************/
+ function _putShort(&$data, $pos = 0, $val, $bigEndian = true)
+ {
+ $val = intval($val);
+
+ if ($bigEndian) {
+ $data{$pos + 0} = chr(($val & 0x0000FF00) >> 8);
+ $data{$pos + 1} = chr(($val & 0x000000FF) >> 0);
+ }
+ else {
+ $data{$pos + 0} = chr(($val & 0x00FF) >> 0);
+ $data{$pos + 1} = chr(($val & 0xFF00) >> 8);
+ }
+
+ return $pos + 2;
+ }
+
+ /*************************************************************/
+ function _getLong(&$data, $pos, $bigEndian = true)
+ {
+ if ($bigEndian) {
+ return (ord($data{$pos}) << 24)
+ + (ord($data{$pos + 1}) << 16)
+ + (ord($data{$pos + 2}) << 8)
+ + ord($data{$pos + 3});
+ }
+ else {
+ return ord($data{$pos})
+ + (ord($data{$pos + 1}) << 8)
+ + (ord($data{$pos + 2}) << 16)
+ + (ord($data{$pos + 3}) << 24);
+ }
+ }
+
+ /*************************************************************/
+ function _putLong(&$data, $pos, $val, $bigEndian = true)
+ {
+ $val = intval($val);
+
+ if ($bigEndian) {
+ $data{$pos + 0} = chr(($val & 0xFF000000) >> 24);
+ $data{$pos + 1} = chr(($val & 0x00FF0000) >> 16);
+ $data{$pos + 2} = chr(($val & 0x0000FF00) >> 8);
+ $data{$pos + 3} = chr(($val & 0x000000FF) >> 0);
+ }
+ else {
+ $data{$pos + 0} = chr(($val & 0x000000FF) >> 0);
+ $data{$pos + 1} = chr(($val & 0x0000FF00) >> 8);
+ $data{$pos + 2} = chr(($val & 0x00FF0000) >> 16);
+ $data{$pos + 3} = chr(($val & 0xFF000000) >> 24);
+ }
+
+ return $pos + 4;
+ }
+
+ /*************************************************************/
+ function & _getNullString(&$data, $pos)
+ {
+ $str = '';
+ $max = strlen($data);
+
+ while ($pos < $max) {
+ if (ord($data{$pos}) == 0) {
+ return $str;
+ }
+ else {
+ $str .= $data{$pos};
+ }
+ $pos++;
+ }
+
+ return $str;
+ }
+
+ /*************************************************************/
+ function & _getFixedString(&$data, $pos, $length = -1)
+ {
+ if ($length == -1) {
+ $length = strlen($data) - $pos;
+ }
+
+ return substr($data, $pos, $length);
+ }
+
+ /*************************************************************/
+ function _putString(&$data, $pos, &$str)
+ {
+ $len = strlen($str);
+ for ($i = 0; $i < $len; $i++) {
+ $data{$pos + $i} = $str{$i};
+ }
+
+ return $pos + $len;
+ }
+
+ /*************************************************************/
+ function _hexDump(&$data, $start = 0, $length = -1)
+ {
+ if (($length == -1) || (($length + $start) > strlen($data))) {
+ $end = strlen($data);
+ }
+ else {
+ $end = $start + $length;
+ }
+
+ $ascii = '';
+ $count = 0;
+
+ echo "<tt>\n";
+
+ while ($start < $end) {
+ if (($count % 16) == 0) {
+ echo sprintf('%04d', $count) . ': ';
+ }
+
+ $c = ord($data{$start});
+ $count++;
+ $start++;
+
+ $aux = dechex($c);
+ if (strlen($aux) == 1)
+ echo '0';
+ echo $aux . ' ';
+
+ if ($c == 60)
+ $ascii .= '&lt;';
+ elseif ($c == 62)
+ $ascii .= '&gt;';
+ elseif ($c == 32)
+ $ascii .= '&nbsp;';
+ elseif ($c > 32)
+ $ascii .= chr($c);
+ else
+ $ascii .= '.';
+
+ if (($count % 4) == 0) {
+ echo ' - ';
+ }
+
+ if (($count % 16) == 0) {
+ echo ': ' . $ascii . "<br>\n";
+ $ascii = '';
+ }
+ }
+
+ if ($ascii != '') {
+ while (($count % 16) != 0) {
+ echo '-- ';
+ $count++;
+ if (($count % 4) == 0) {
+ echo ' - ';
+ }
+ }
+ echo ': ' . $ascii . "<br>\n";
+ }
+
+ echo "</tt>\n";
+ }
+
+/*****************************************************************/
+}
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
diff --git a/plugins/dokuwiki/inc/cache.php b/plugins/dokuwiki/inc/cache.php
new file mode 100644
index 0000000..4de19be
--- /dev/null
+++ b/plugins/dokuwiki/inc/cache.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * Generic class to handle caching
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+
+require_once(DOKU_INC.'inc/io.php');
+require_once(DOKU_INC.'inc/pageutils.php');
+require_once(DOKU_INC.'inc/parserutils.php');
+
+class cache {
+ var $key = ''; // primary identifier for this item
+ var $ext = ''; // file ext for cache data, secondary identifier for this item
+ var $cache = ''; // cache file name
+ var $depends = array(); // array containing cache dependency information,
+ // used by _useCache to determine cache validity
+
+ var $_event = ''; // event to be triggered during useCache
+
+ function __construct($key,$ext) {
+ $this->key = $key;
+ $this->ext = $ext;
+ $this->cache = getCacheName($key,$ext);
+ }
+
+ /**
+ * public method to determine whether the cache can be used
+ *
+ * to assist in cetralisation of event triggering and calculation of cache statistics,
+ * don't override this function override _useCache()
+ *
+ * @param array $depends array of cache dependencies, support dependecies:
+ * 'age' => max age of the cache in seconds
+ * 'files' => cache must be younger than mtime of each file
+ * (nb. dependency passes if file doesn't exist)
+ *
+ * @return bool true if cache can be used, false otherwise
+ */
+ function useCache($depends=array()) {
+ $this->depends = $depends;
+ $this->_addDependencies();
+
+ if ($this->_event) {
+ return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
+ } else {
+ return $this->_stats($this->_useCache());
+ }
+ }
+
+ /**
+ * private method containing cache use decision logic
+ *
+ * this function processes the following keys in the depends array
+ * purge - force a purge on any non empty value
+ * age - expire cache if older than age (seconds)
+ * files - expire cache if any file in this array was updated more recently than the cache
+ *
+ * can be overridden
+ *
+ * @return bool see useCache()
+ */
+ function _useCache() {
+
+ if (!empty($this->depends['purge'])) return false; // purge requested?
+ if (!($this->_time = @filemtime($this->cache))) return false; // cache exists?
+
+ // cache too old?
+ if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
+
+ if (!empty($this->depends['files'])) {
+ foreach ($this->depends['files'] as $file) {
+ if ($this->_time < @filemtime($file)) return false; // cache older than files it depends on?
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * add dependencies to the depends array
+ *
+ * this method should only add dependencies,
+ * it should not remove any existing dependencies and
+ * it should only overwrite a dependency when the new value is more stringent than the old
+ */
+ function _addDependencies() {
+ if (isset($_REQUEST['purge'])) $this->depends['purge'] = true; // purge requested
+ }
+
+ /**
+ * retrieve the cached data
+ *
+ * @param bool $clean true to clean line endings, false to leave line endings alone
+ * @return string cache contents
+ */
+ function retrieveCache($clean=true) {
+ return io_readFile($this->cache, $clean);
+ }
+
+ /**
+ * cache $data
+ *
+ * @param string $data the data to be cached
+ * @return none
+ */
+ function storeCache($data) {
+ io_savefile($this->cache, $data);
+ }
+
+ /**
+ * remove any cached data associated with this cache instance
+ */
+ function removeCache() {
+ @unlink($this->cache);
+ }
+
+ /**
+ * Record cache hits statistics.
+ * (Only when debugging allowed, to reduce overhead.)
+ *
+ * @param bool $success result of this cache use attempt
+ * @return bool pass-thru $success value
+ */
+ function _stats($success) {
+ global $conf;
+ static $stats = NULL;
+ static $file;
+
+ if (!$conf['allowdebug']) { return $success; }
+
+ if (is_null($stats)) {
+ $file = $conf['cachedir'].'/cache_stats.txt';
+ $lines = explode("\n",io_readFile($file));
+
+ foreach ($lines as $line) {
+ $i = strpos($line,',');
+ $stats[substr($line,0,$i)] = $line;
+ }
+ }
+
+ if (isset($stats[$this->ext])) {
+ list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
+ } else {
+ $ext = $this->ext;
+ $count = 0;
+ $hits = 0;
+ }
+
+ $count++;
+ if ($success) $hits++;
+ $stats[$this->ext] = "$ext,$count,$hits";
+
+ io_saveFile($file,join("\n",$stats));
+
+ return $success;
+ }
+}
+
+class cache_parser extends cache {
+
+ var $file = ''; // source file for cache
+ var $mode = ''; // input mode (represents the processing the input file will undergo)
+
+ var $_event = 'PARSER_CACHE_USE';
+
+ function __construct($id, $file, $mode) {
+ if ($id) $this->page = $id;
+ $this->file = $file;
+ $this->mode = $mode;
+
+ parent::__construct($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
+ }
+
+ function _useCache() {
+
+ if (!@file_exists($this->file)) return false; // source exists?
+ return parent::_useCache();
+ }
+
+ function _addDependencies() {
+ global $conf;
+
+ $this->depends['age'] = isset($this->depends['age']) ?
+ min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
+
+ // parser cache file dependencies ...
+ $files = array($this->file, // ... source
+ DOKU_CONF.'dokuwiki.php', // ... config
+ DOKU_CONF.'local.php', // ... local config
+ DOKU_INC.'inc/parser/parser.php', // ... parser
+ DOKU_INC.'inc/parser/handler.php', // ... handler
+ );
+
+ $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
+ parent::_addDependencies();
+ }
+
+}
+
+class cache_renderer extends cache_parser {
+
+ function useCache($depends=array()) {
+ $use = parent::useCache($depends);
+
+ // meta data needs to be kept in step with the cache
+ if (!$use && isset($this->page)) {
+ p_set_metadata($this->page,array(),true);
+ }
+
+ return $use;
+ }
+
+ function _useCache() {
+ global $conf;
+
+ if (!parent::_useCache()) return false;
+
+ // for wiki pages, check metadata dependencies
+ if (isset($this->page)) {
+ $metadata = p_get_metadata($this->page);
+
+ // check currnent link existence is consistent with cache version
+ // first check the purgefile
+ // - if the cache is more recent that the purgefile we know no links can have been updated
+ if ($this->_time < @filemtime($conf['cachedir'].'/purgefile')) {
+
+# $links = p_get_metadata($this->page,"relation references");
+ $links = $metadata['relation']['references'];
+
+ if (!empty($links)) {
+ foreach ($links as $id => $exists) {
+ if ($exists != @file_exists(wikiFN($id,'',false))) return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function _addDependencies() {
+
+ // renderer cache file dependencies ...
+ $files = array(
+ DOKU_INC.'inc/parser/'.$this->mode.'.php', // ... the renderer
+ );
+
+ // page implies metadata and possibly some other dependencies
+ if (isset($this->page)) {
+
+ $metafile = metaFN($this->page,'.meta');
+ if (@file_exists($metafile)) {
+ $files[] = $metafile; // ... the page's own metadata
+ $files[] = DOKU_INC.'inc/parser/metadata.php'; // ... the metadata renderer
+
+ $valid = p_get_metadata($this->page, 'date valid');
+ if (!empty($valid['age'])) {
+ $this->depends['age'] = isset($this->depends['age']) ?
+ min($this->depends['age'],$valid['age']) : $valid['age'];
+ }
+
+ } else {
+ $this->depends['purge'] = true; // ... purging cache will generate metadata
+ return;
+ }
+ }
+
+ $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
+ parent::_addDependencies();
+ }
+}
+
+class cache_instructions extends cache_parser {
+
+ function __construct($id, $file) {
+ parent::__construct($id, $file, 'i');
+ }
+
+ function retrieveCache($clean=true) {
+ $contents = io_readFile($this->cache, false);
+ return !empty($contents) ? unserialize($contents) : array();
+ }
+
+ function storeCache($instructions) {
+ io_savefile($this->cache,serialize($instructions));
+ }
+}
diff --git a/plugins/dokuwiki/inc/common.php b/plugins/dokuwiki/inc/common.php
new file mode 100644
index 0000000..dd4fa31
--- /dev/null
+++ b/plugins/dokuwiki/inc/common.php
@@ -0,0 +1,1009 @@
+<?php
+/**
+ * Common DokuWiki functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+require_once(DOKU_CONF.'dokuwiki.php');
+require_once(DOKU_INC.'inc/io.php');
+require_once(DOKU_INC.'inc/utf8.php');
+require_once(DOKU_INC.'inc/parserutils.php');
+require_once(DOKU_INC.'inc/infoutils.php');
+
+/**
+ * These constants are used with the recents function
+ */
+define('RECENTS_SKIP_DELETED',2);
+define('RECENTS_SKIP_MINORS',4);
+define('RECENTS_SKIP_SUBSPACES',8);
+
+/**
+ * Wrapper around htmlspecialchars()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see htmlspecialchars()
+ */
+function hsc($string){
+ return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
+}
+
+/**
+ * print a newline terminated string
+ *
+ * You can give an indention as optional parameter
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function ptln($string,$intend=0){
+ for($i=0; $i<$intend; $i++) print ' ';
+ echo "$string\n";
+}
+
+/**
+ * strips control characters (<32) from the given string
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function stripctl($string){
+ return preg_replace('/[\x00-\x1F]+/s','',$string);
+}
+
+/**
+ * Return info about the current document as associative
+ * array.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function pageinfo(){
+ global $ID;
+ global $REV;
+ global $USERINFO;
+ global $conf;
+
+ // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
+ // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
+ $info['id'] = $ID;
+ $info['rev'] = $REV;
+
+ if($_SERVER['REMOTE_USER']){
+ $info['userinfo'] = $USERINFO;
+ $info['perm'] = auth_quickaclcheck($ID);
+ $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER']);
+ $info['client'] = $_SERVER['REMOTE_USER'];
+
+ // if some outside auth were used only REMOTE_USER is set
+ if(!$info['userinfo']['name']){
+ $info['userinfo']['name'] = $_SERVER['REMOTE_USER'];
+ }
+
+ }else{
+ $info['perm'] = auth_aclcheck($ID,'',null);
+ $info['subscribed'] = false;
+ $info['client'] = clientIP(true);
+ }
+
+ $info['namespace'] = getNS($ID);
+ $info['locked'] = checklock($ID);
+ $info['filepath'] = realpath(wikiFN($ID));
+ $info['exists'] = @file_exists($info['filepath']);
+ if($REV){
+ //check if current revision was meant
+ if($info['exists'] && (@filemtime($info['filepath'])==$REV)){
+ $REV = '';
+ }else{
+ //really use old revision
+ $info['filepath'] = realpath(wikiFN($ID,$REV));
+ $info['exists'] = @file_exists($info['filepath']);
+ }
+ }
+ $info['rev'] = $REV;
+ if($info['exists']){
+ $info['writable'] = (is_writable($info['filepath']) &&
+ ($info['perm'] >= AUTH_EDIT));
+ }else{
+ $info['writable'] = ($info['perm'] >= AUTH_CREATE);
+ }
+ $info['editable'] = ($info['writable'] && empty($info['lock']));
+ $info['lastmod'] = @filemtime($info['filepath']);
+
+ //load page meta data
+ $info['meta'] = p_get_metadata($ID);
+
+ //who's the editor
+ if($REV){
+ $revinfo = getRevisionInfo($ID, $REV, 1024);
+ }else{
+ $revinfo = isset($info['meta']['last_change']) ? $info['meta']['last_change'] : getRevisionInfo($ID,$info['lastmod'],1024);
+ }
+
+ $info['ip'] = $revinfo['ip'];
+ $info['user'] = $revinfo['user'];
+ $info['sum'] = $revinfo['sum'];
+ // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
+ // Use $INFO['meta']['last_change']['type']==='e' in place of $info['minor'].
+
+ if($revinfo['user']){
+ $info['editor'] = $revinfo['user'];
+ }else{
+ $info['editor'] = $revinfo['ip'];
+ }
+
+ // draft
+ $draft = getCacheName($info['client'].$ID,'.draft');
+ if(@file_exists($draft)){
+ if(@filemtime($draft) < @filemtime(wikiFN($ID))){
+ // remove stale draft
+ @unlink($draft);
+ }else{
+ $info['draft'] = $draft;
+ }
+ }
+
+ return $info;
+}
+
+/**
+ * Build an string of URL parameters
+ *
+ * @author Andreas Gohr
+ */
+function buildURLparams($params, $sep='&amp;'){
+ $url = '';
+ $amp = false;
+ foreach($params as $key => $val){
+ if($amp) $url .= $sep;
+
+ $url .= $key.'=';
+ $url .= rawurlencode($val);
+ $amp = true;
+ }
+ return $url;
+}
+
+/**
+ * Build an string of html tag attributes
+ *
+ * Skips keys starting with '_', values get HTML encoded
+ *
+ * @author Andreas Gohr
+ */
+function buildAttributes($params){
+ $url = '';
+ foreach($params as $key => $val){
+ if($key{0} == '_') continue;
+
+ $url .= $key.'="';
+ $url .= htmlspecialchars ($val);
+ $url .= '" ';
+ }
+ return $url;
+}
+
+
+/**
+ * This builds the breadcrumb trail and returns it as array
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function breadcrumbs(){
+ // we prepare the breadcrumbs early for quick session closing
+ static $crumbs = null;
+ if($crumbs != null) return $crumbs;
+
+ global $ID;
+ global $ACT;
+ global $conf;
+ $crumbs = $_SESSION[DOKU_COOKIE]['bc'];
+
+ //first visit?
+ if (!is_array($crumbs)){
+ $crumbs = array();
+ }
+ //we only save on show and existing wiki documents
+ $file = wikiFN($ID);
+ if($ACT != 'show' || !@file_exists($file)){
+ $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
+ return $crumbs;
+ }
+
+ // page names
+ $name = noNS($ID);
+ if ($conf['useheading']) {
+ // get page title
+ $title = p_get_first_heading($ID);
+ if ($title) {
+ $name = $title;
+ }
+ }
+
+ //remove ID from array
+ if (isset($crumbs[$ID])) {
+ unset($crumbs[$ID]);
+ }
+
+ //add to array
+ $crumbs[$ID] = $name;
+ //reduce size
+ while(count($crumbs) > $conf['breadcrumbs']){
+ array_shift($crumbs);
+ }
+ //save to session
+ $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
+ return $crumbs;
+}
+
+/**
+ * Filter for page IDs
+ *
+ * This is run on a ID before it is outputted somewhere
+ * currently used to replace the colon with something else
+ * on Windows systems and to have proper URL encoding
+ *
+ * Urlencoding is ommitted when the second parameter is false
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function idfilter($id,$ue=true){
+ global $conf;
+ if ($conf['useslash'] && $conf['userewrite']){
+ $id = strtr($id,':','/');
+ }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
+ $conf['userewrite']) {
+ $id = strtr($id,':',';');
+ }
+ if($ue){
+ $id = rawurlencode($id);
+ $id = str_replace('%3A',':',$id); //keep as colon
+ $id = str_replace('%2F','/',$id); //keep as slash
+ }
+ return $id;
+}
+
+/**
+ * This builds a link to a wikipage
+ *
+ * It handles URL rewriting and adds additional parameter if
+ * given in $more
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function wl($id='',$more='',$abs=false,$sep='&amp;'){
+ global $conf;
+ if(is_array($more)){
+ $more = buildURLparams($more,$sep);
+ }else{
+ $more = str_replace(',',$sep,$more);
+ }
+
+ $id = idfilter($id);
+ if($abs){
+ $xlink = DOKU_URL;
+ }else{
+ $xlink = DOKU_BASE;
+ }
+
+ if($conf['userewrite'] == 2){
+ $xlink .= DOKU_SCRIPT.'/'.$id;
+ if($more) $xlink .= '?'.$more;
+ }elseif($conf['userewrite']){
+ $xlink .= $id;
+ if($more) $xlink .= '?'.$more;
+ }else{
+ $xlink .= DOKU_SCRIPT.'?id='.$id;
+ if($more) $xlink .= $sep.$more;
+ }
+
+ return $xlink;
+}
+
+/**
+ * This builds a link to an alternate page format
+ *
+ * Handles URL rewriting if enabled. Follows the style of wl().
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&amp;'){
+ global $conf;
+ if(is_array($more)){
+ $more = buildURLparams($more,$sep);
+ }else{
+ $more = str_replace(',',$sep,$more);
+ }
+
+ $format = rawurlencode($format);
+ $id = idfilter($id);
+ if($abs){
+ $xlink = DOKU_URL;
+ }else{
+ $xlink = DOKU_BASE;
+ }
+
+ if($conf['userewrite'] == 2){
+ $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
+ if($more) $xlink .= $sep.$more;
+ }elseif($conf['userewrite'] == 1){
+ $xlink .= '_export/'.$format.'/'.$id;
+ if($more) $xlink .= '?'.$more;
+ }else{
+ $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
+ if($more) $xlink .= $sep.$more;
+ }
+
+ return $xlink;
+}
+
+/**
+ * Build a link to a media file
+ *
+ * Will return a link to the detail page if $direct is false
+ */
+function ml($id='',$more='',$direct=true,$sep='&amp;'){
+ global $conf;
+ if(is_array($more)){
+ $more = buildURLparams($more,$sep);
+ }else{
+ $more = str_replace(',',$sep,$more);
+ }
+
+ $xlink = DOKU_BASE;
+
+ // external URLs are always direct without rewriting
+ if(preg_match('#^(https?|ftp)://#i',$id)){
+ $xlink .= 'lib/exe/fetch.php';
+ if($more){
+ $xlink .= '?'.$more;
+ $xlink .= $sep.'media='.rawurlencode($id);
+ }else{
+ $xlink .= '?media='.rawurlencode($id);
+ }
+ return $xlink;
+ }
+
+ $id = idfilter($id);
+
+ // decide on scriptname
+ if($direct){
+ if($conf['userewrite'] == 1){
+ $script = '_media';
+ }else{
+ $script = 'lib/exe/fetch.php';
+ }
+ }else{
+ if($conf['userewrite'] == 1){
+ $script = '_detail';
+ }else{
+ $script = 'lib/exe/detail.php';
+ }
+ }
+
+ // build URL based on rewrite mode
+ if($conf['userewrite']){
+ $xlink .= $script.'/'.$id;
+ if($more) $xlink .= '?'.$more;
+ }else{
+ if($more){
+ $xlink .= $script.'?'.$more;
+ $xlink .= $sep.'media='.$id;
+ }else{
+ $xlink .= $script.'?media='.$id;
+ }
+ }
+
+ return $xlink;
+}
+
+
+
+/**
+ * Just builds a link to a script
+ *
+ * @todo maybe obsolete
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function script($script='doku.php'){
+# $link = getBaseURL();
+# $link .= $script;
+# return $link;
+ return DOKU_BASE.DOKU_SCRIPT;
+}
+
+/**
+ * Spamcheck against wordlist
+ *
+ * Checks the wikitext against a list of blocked expressions
+ * returns true if the text contains any bad words
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function checkwordblock(){
+ global $TEXT;
+ global $conf;
+
+ if(!$conf['usewordblock']) return false;
+
+ // we prepare the text a tiny bit to prevent spammers circumventing URL checks
+ $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$TEXT);
+
+ $wordblocks = getWordblocks();
+ //how many lines to read at once (to work around some PCRE limits)
+ if(version_compare(phpversion(),'4.3.0','<')){
+ //old versions of PCRE define a maximum of parenthesises even if no
+ //backreferences are used - the maximum is 99
+ //this is very bad performancewise and may even be too high still
+ $chunksize = 40;
+ }else{
+ //read file in chunks of 200 - this should work around the
+ //MAX_PATTERN_SIZE in modern PCRE
+ $chunksize = 200;
+ }
+ while($blocks = array_splice($wordblocks,0,$chunksize)){
+ $re = array();
+ #build regexp from blocks
+ foreach($blocks as $block){
+ $block = preg_replace('/#.*$/','',$block);
+ $block = trim($block);
+ if(empty($block)) continue;
+ $re[] = $block;
+ }
+ if(preg_match('#('.join('|',$re).')#si',$text, $match=array())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return the IP of the client
+ *
+ * Honours X-Forwarded-For and X-Real-IP Proxy Headers
+ *
+ * It returns a comma separated list of IPs if the above mentioned
+ * headers are set. If the single parameter is set, it tries to return
+ * a routable public address, prefering the ones suplied in the X
+ * headers
+ *
+ * @param boolean $single If set only a single IP is returned
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function clientIP($single=false){
+ $ip = array();
+ $ip[] = $_SERVER['REMOTE_ADDR'];
+ if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
+ $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
+ if(!empty($_SERVER['HTTP_X_REAL_IP']))
+ $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP']));
+
+ // remove any non-IP stuff
+ $cnt = count($ip);
+ $match = array();
+ for($i=0; $i<$cnt; $i++){
+ if(preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/',$ip[$i],$match)) {
+ $ip[$i] = $match[0];
+ } else {
+ $ip[$i] = '';
+ }
+ if(empty($ip[$i])) unset($ip[$i]);
+ }
+ $ip = array_values(array_unique($ip));
+ if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
+
+ if(!$single) return join(',',$ip);
+
+ // decide which IP to use, trying to avoid local addresses
+ $ip = array_reverse($ip);
+ foreach($ip as $i){
+ if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){
+ continue;
+ }else{
+ return $i;
+ }
+ }
+ // still here? just use the first (last) address
+ return $ip[0];
+}
+
+/**
+ * Checks if a given page is currently locked.
+ *
+ * removes stale lockfiles
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function checklock($id){
+ global $conf;
+ $lock = wikiLockFN($id);
+
+ //no lockfile
+ if(!@file_exists($lock)) return false;
+
+ //lockfile expired
+ if((time() - filemtime($lock)) > $conf['locktime']){
+ @unlink($lock);
+ return false;
+ }
+
+ //my own lock
+ $ip = io_readFile($lock);
+ if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
+ return false;
+ }
+
+ return $ip;
+}
+
+/**
+ * Lock a page for editing
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function lock($id){
+ $lock = wikiLockFN($id);
+ if($_SERVER['REMOTE_USER']){
+ io_saveFile($lock,$_SERVER['REMOTE_USER']);
+ }else{
+ io_saveFile($lock,clientIP());
+ }
+}
+
+/**
+ * Unlock a page if it was locked by the user
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool true if a lock was removed
+ */
+function unlock($id){
+ $lock = wikiLockFN($id);
+ if(@file_exists($lock)){
+ $ip = io_readFile($lock);
+ if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
+ @unlink($lock);
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * convert line ending to unix format
+ *
+ * @see formText() for 2crlf conversion
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function cleanText($text){
+ $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
+ return $text;
+}
+
+/**
+ * Prepares text for print in Webforms by encoding special chars.
+ * It also converts line endings to Windows format which is
+ * pseudo standard for webforms.
+ *
+ * @see cleanText() for 2unix conversion
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function formText($text){
+ $text = preg_replace("/\012/","\015\012",$text);
+ return htmlspecialchars($text);
+}
+
+/**
+ * Returns the specified local text in raw format
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function rawLocale($id){
+ return io_readFile(localeFN($id));
+}
+
+/**
+ * Returns the raw WikiText
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function rawWiki($id,$rev=''){
+ return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
+}
+
+/**
+ * Returns the pagetemplate contents for the ID's namespace
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function pageTemplate($data){
+ $id = $data[0];
+ global $conf;
+ global $INFO;
+ $tpl = io_readFile(dirname(wikiFN($id)).'/_template.txt');
+ $tpl = str_replace('@ID@',$id,$tpl);
+ $tpl = str_replace('@NS@',getNS($id),$tpl);
+ $tpl = str_replace('@PAGE@',strtr(noNS($id),'_',' '),$tpl);
+ $tpl = str_replace('@USER@',$_SERVER['REMOTE_USER'],$tpl);
+ $tpl = str_replace('@NAME@',$INFO['userinfo']['name'],$tpl);
+ $tpl = str_replace('@MAIL@',$INFO['userinfo']['mail'],$tpl);
+ $tpl = str_replace('@DATE@',date($conf['dformat']),$tpl);
+ return $tpl;
+}
+
+
+/**
+ * Returns the raw Wiki Text in three slices.
+ *
+ * The range parameter needs to have the form "from-to"
+ * and gives the range of the section in bytes - no
+ * UTF-8 awareness is needed.
+ * The returned order is prefix, section and suffix.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function rawWikiSlices($range,$id,$rev=''){
+ list($from,$to) = explode('-',$range,2);
+ $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
+ if(!$from) $from = 0;
+ if(!$to) $to = strlen($text)+1;
+
+ $slices[0] = substr($text,0,$from-1);
+ $slices[1] = substr($text,$from-1,$to-$from);
+ $slices[2] = substr($text,$to);
+
+ return $slices;
+}
+
+/**
+ * Joins wiki text slices
+ *
+ * function to join the text slices with correct lineendings again.
+ * When the pretty parameter is set to true it adds additional empty
+ * lines between sections if needed (used on saving).
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function con($pre,$text,$suf,$pretty=false){
+
+ if($pretty){
+ if($pre && substr($pre,-1) != "\n") $pre .= "\n";
+ if($suf && substr($text,-1) != "\n") $text .= "\n";
+ }
+
+ if($pre) $pre .= "\n";
+ if($suf) $text .= "\n";
+ return $pre.$text.$suf;
+}
+
+/**
+ * Saves a wikitext by calling io_writeWikiPage.
+ * Also directs changelog and attic updates.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function saveWikiText($id,$text,$summary,$minor=false){
+ /* Note to developers:
+ This code is subtle and delicate. Test the behavior of
+ the attic and changelog with dokuwiki and external edits
+ after any changes. External edits change the wiki page
+ directly without using php or dokuwiki.
+ */
+ global $conf;
+ global $lang;
+ global $REV;
+ // ignore if no changes were made
+ if($text == rawWiki($id,'')){
+ return;
+ }
+
+ $file = wikiFN($id);
+ $old = @filemtime($file); // from page
+ $wasRemoved = empty($text);
+ $wasCreated = !@file_exists($file);
+ $wasReverted = ($REV==true);
+ $newRev = false;
+ $oldRev = getRevisions($id, -1, 1, 1024); // from changelog
+ $oldRev = (int)(empty($oldRev)?0:$oldRev[0]);
+ if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) {
+ // add old revision to the attic if missing
+ saveOldRevision($id);
+ // add a changelog entry if this edit came from outside dokuwiki
+ if ($old>$oldRev) {
+ addLogEntry($old, $id);
+ // send notify mails
+ notify($id,'admin',$oldRev,'',false);
+ notify($id,'subscribers',$oldRev,'',false);
+ // remove soon to be stale instructions
+ $cache = new cache_instructions($id, $file);
+ $cache->removeCache();
+ }
+ }
+
+ if ($wasRemoved){
+ // pre-save deleted revision
+ @touch($file);
+ clearstatcache();
+ $newRev = saveOldRevision($id);
+ // remove empty file
+ @unlink($file);
+ // remove old meta info...
+ $mfiles = metaFiles($id);
+ $changelog = metaFN($id, '.changes');
+ foreach ($mfiles as $mfile) {
+ // but keep per-page changelog to preserve page history
+ if (@file_exists($mfile) && $mfile!==$changelog) { @unlink($mfile); }
+ }
+ $del = true;
+ // autoset summary on deletion
+ if(empty($summary)) $summary = $lang['deleted'];
+ // remove empty namespaces
+ io_sweepNS($id, 'datadir');
+ io_sweepNS($id, 'mediadir');
+ }else{
+ // save file (namespace dir is created in io_writeWikiPage)
+ io_writeWikiPage($file, $text, $id);
+ // pre-save the revision, to keep the attic in sync
+ $newRev = saveOldRevision($id);
+ $del = false;
+ }
+
+ // select changelog line type
+ $extra = '';
+ $type = 'E';
+ if ($wasReverted) {
+ $type = 'R';
+ $extra = $REV;
+ }
+ else if ($wasCreated) { $type = 'C'; }
+ else if ($wasRemoved) { $type = 'D'; }
+ else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = 'e'; } //minor edits only for logged in users
+
+ addLogEntry($newRev, $id, $type, $summary, $extra);
+ // send notify mails
+ notify($id,'admin',$old,$summary,$minor);
+ notify($id,'subscribers',$old,$summary,$minor);
+
+ // update the purgefile (timestamp of the last time anything within the wiki was changed)
+ io_saveFile($conf['cachedir'].'/purgefile',time());
+}
+
+/**
+ * moves the current version to the attic and returns its
+ * revision date
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function saveOldRevision($id){
+ global $conf;
+ $oldf = wikiFN($id);
+ if(!@file_exists($oldf)) return '';
+ $date = filemtime($oldf);
+ $newf = wikiFN($id,$date);
+ io_writeWikiPage($newf, rawWiki($id), $id, $date);
+ return $date;
+}
+
+/**
+ * Sends a notify mail on page change
+ *
+ * @param string $id The changed page
+ * @param string $who Who to notify (admin|subscribers)
+ * @param int $rev Old page revision
+ * @param string $summary What changed
+ * @param boolean $minor Is this a minor edit?
+ * @param array $replace Additional string substitutions, @KEY@ to be replaced by value
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
+ global $lang;
+ global $conf;
+
+ // decide if there is something to do
+ if($who == 'admin'){
+ if(empty($conf['notify'])) return; //notify enabled?
+ $text = rawLocale('mailtext');
+ $to = $conf['notify'];
+ $bcc = '';
+ }elseif($who == 'subscribers'){
+ if(!$conf['subscribers']) return; //subscribers enabled?
+ if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
+ $bcc = subscriber_addresslist($id);
+ if(empty($bcc)) return;
+ $to = '';
+ $text = rawLocale('subscribermail');
+ }elseif($who == 'register'){
+ if(empty($conf['registernotify'])) return;
+ $text = rawLocale('registermail');
+ $to = $conf['registernotify'];
+ $bcc = '';
+ }else{
+ return; //just to be safe
+ }
+
+ $text = str_replace('@DATE@',date($conf['dformat']),$text);
+ $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
+ $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
+ $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
+ $text = str_replace('@NEWPAGE@',wl($id,'',true),$text);
+ $text = str_replace('@PAGE@',$id,$text);
+ $text = str_replace('@TITLE@',$conf['title'],$text);
+ $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
+ $text = str_replace('@SUMMARY@',$summary,$text);
+ $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
+
+ foreach ($replace as $key => $substitution) {
+ $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
+ }
+
+ if($who == 'register'){
+ $subject = $lang['mail_new_user'].' '.$summary;
+ }elseif($rev){
+ $subject = $lang['mail_changed'].' '.$id;
+ $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true),$text);
+ require_once(DOKU_INC.'inc/DifferenceEngine.php');
+ $df = new Diff(preg_split('/\n/',rawWiki($id,$rev)),
+ preg_split('/\n/',rawWiki($id)));
+ $dformat = new UnifiedDiffFormatter();
+ $diff = $dformat->format($df);
+ }else{
+ $subject=$lang['mail_newpage'].' '.$id;
+ $text = str_replace('@OLDPAGE@','none',$text);
+ $diff = rawWiki($id);
+ }
+ $text = str_replace('@DIFF@',$diff,$text);
+ $subject = '['.$conf['title'].'] '.$subject;
+
+ mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc);
+}
+
+/**
+ * extracts the query from a google referer
+ *
+ * @todo should be more generic and support yahoo et al
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getGoogleQuery(){
+ $url = parse_url($_SERVER['HTTP_REFERER']);
+ if(!$url) return '';
+
+ if(!preg_match("#google\.#i",$url['host'])) return '';
+ $query = array();
+ parse_str($url['query'],$query);
+
+ return $query['q'];
+}
+
+/**
+ * Try to set correct locale
+ *
+ * @deprecated No longer used
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function setCorrectLocale(){
+ global $conf;
+ global $lang;
+
+ $enc = strtoupper($lang['encoding']);
+ foreach ($lang['locales'] as $loc){
+ //try locale
+ if(@setlocale(LC_ALL,$loc)) return;
+ //try loceale with encoding
+ if(@setlocale(LC_ALL,"$loc.$enc")) return;
+ }
+ //still here? try to set from environment
+ @setlocale(LC_ALL,"");
+}
+
+/**
+ * Return the human readable size of a file
+ *
+ * @param int $size A file size
+ * @param int $dec A number of decimal places
+ * @author Martin Benjamin <b.martin@cybernet.ch>
+ * @author Aidan Lister <aidan@php.net>
+ * @version 1.0.0
+ */
+function filesize_h($size, $dec = 1){
+ $sizes = array('B', 'KB', 'MB', 'GB');
+ $count = count($sizes);
+ $i = 0;
+
+ while ($size >= 1024 && ($i < $count - 1)) {
+ $size /= 1024;
+ $i++;
+ }
+
+ return round($size, $dec) . ' ' . $sizes[$i];
+}
+
+/**
+ * return an obfuscated email address in line with $conf['mailguard'] setting
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+function obfuscate($email) {
+ global $conf;
+
+ switch ($conf['mailguard']) {
+ case 'visible' :
+ $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
+ return strtr($email, $obfuscate);
+
+ case 'hex' :
+ $encode = '';
+ for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';';
+ return $encode;
+
+ case 'none' :
+ default :
+ return $email;
+ }
+}
+
+/**
+ * Let us know if a user is tracking a page
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function is_subscribed($id,$uid){
+ $file=metaFN($id,'.mlist');
+ if (@file_exists($file)) {
+ $mlist = file($file);
+ $pos = array_search($uid."\n",$mlist);
+ return is_int($pos);
+ }
+
+ return false;
+}
+
+/**
+ * Return a string with the email addresses of all the
+ * users subscribed to a page
+ *
+ * @author Steven Danz <steven-danz@kc.rr.com>
+ */
+function subscriber_addresslist($id){
+ global $conf;
+ global $auth;
+
+ $emails = '';
+
+ if (!$conf['subscribers']) return;
+
+ $mlist = array();
+ $file=metaFN($id,'.mlist');
+ if (@file_exists($file)) {
+ $mlist = file($file);
+ }
+ if(count($mlist) > 0) {
+ foreach ($mlist as $who) {
+ $who = rtrim($who);
+ $info = $auth->getUserData($who);
+ $level = auth_aclcheck($id,$who,$info['grps']);
+ if ($level >= AUTH_READ) {
+ if (strcasecmp($info['mail'],$conf['notify']) != 0) {
+ if (empty($emails)) {
+ $emails = $info['mail'];
+ } else {
+ $emails = "$emails,".$info['mail'];
+ }
+ }
+ }
+ }
+ }
+
+ return $emails;
+}
+
+/**
+ * Removes quoting backslashes
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function unslash($string,$char="'"){
+ return str_replace('\\'.$char,$char,$string);
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/confutils.php b/plugins/dokuwiki/inc/confutils.php
new file mode 100644
index 0000000..c668e80
--- /dev/null
+++ b/plugins/dokuwiki/inc/confutils.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Utilities for collecting data from config files
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+
+/**
+ * Returns the (known) extension and mimetype of a given filename
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function mimetype($file){
+ $ret = array(false,false); // return array
+ $mtypes = getMimeTypes(); // known mimetypes
+ $exts = join('|',array_keys($mtypes)); // known extensions (regexp)
+ if(preg_match('#\.('.$exts.')$#i',$file,$matches)){
+ $ext = strtolower($matches[1]);
+ }
+
+ if($ext && $mtypes[$ext]){
+ $ret = array($ext, $mtypes[$ext]);
+ }
+
+ return $ret;
+}
+
+/**
+ * returns a hash of mimetypes
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getMimeTypes() {
+ static $mime = NULL;
+ if ( !$mime ) {
+ $mime = confToHash(DOKU_CONF.'mime.conf');
+ if (@file_exists(DOKU_CONF.'mime.local.conf')) {
+ $local = confToHash(DOKU_CONF.'mime.local.conf');
+ $mime = array_merge($mime, $local);
+ }
+ }
+ return $mime;
+}
+
+/**
+ * returns a hash of acronyms
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function getAcronyms() {
+ static $acronyms = NULL;
+ if ( !$acronyms ) {
+ $acronyms = confToHash(DOKU_CONF.'acronyms.conf');
+ if (@file_exists(DOKU_CONF.'acronyms.local.conf')) {
+ $local = confToHash(DOKU_CONF.'acronyms.local.conf');
+ $acronyms = array_merge($acronyms, $local);
+ }
+ }
+ return $acronyms;
+}
+
+/**
+ * returns a hash of smileys
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function getSmileys() {
+ static $smileys = NULL;
+ if ( !$smileys ) {
+ $smileys = confToHash(DOKU_CONF.'smileys.conf');
+ if (@file_exists(DOKU_CONF.'smileys.local.conf')) {
+ $local = confToHash(DOKU_CONF.'smileys.local.conf');
+ $smileys = array_merge($smileys, $local);
+ }
+ }
+ return $smileys;
+}
+
+/**
+ * returns a hash of entities
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function getEntities() {
+ static $entities = NULL;
+ if ( !$entities ) {
+ $entities = confToHash(DOKU_CONF.'entities.conf');
+ if (@file_exists(DOKU_CONF.'entities.local.conf')) {
+ $local = confToHash(DOKU_CONF.'entities.local.conf');
+ $entities = array_merge($entities, $local);
+ }
+ }
+ return $entities;
+}
+
+/**
+ * returns a hash of interwikilinks
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function getInterwiki() {
+ static $wikis = NULL;
+ if ( !$wikis ) {
+ $wikis = confToHash(DOKU_CONF.'interwiki.conf',true);
+ if (@file_exists(DOKU_CONF.'interwiki.local.conf')) {
+ $local = confToHash(DOKU_CONF.'interwiki.local.conf');
+ $wikis = array_merge($wikis, $local);
+ }
+ }
+ //add sepecial case 'this'
+ $wikis['this'] = DOKU_URL.'{NAME}';
+ return $wikis;
+}
+
+/**
+ * returns array of wordblock patterns
+ *
+ */
+function getWordblocks() {
+ static $wordblocks = NULL;
+ if ( !$wordblocks ) {
+ $wordblocks = file(DOKU_CONF.'wordblock.conf');
+ if (@file_exists(DOKU_CONF.'wordblock.local.conf')) {
+ $local = file(DOKU_CONF.'wordblock.local.conf');
+ $wordblocks = array_merge($wordblocks, $local);
+ }
+ }
+ return $wordblocks;
+}
+
+
+/**
+ * Builds a hash from a configfile
+ *
+ * If $lower is set to true all hash keys are converted to
+ * lower case.
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function confToHash($file,$lower=false) {
+ $conf = array();
+ $lines = @file( $file );
+ if ( !$lines ) return $conf;
+
+ foreach ( $lines as $line ) {
+ //ignore comments
+ $line = preg_replace('/(?<!&)#.*$/','',$line);
+ $line = trim($line);
+ if(empty($line)) continue;
+ $line = preg_split('/\s+/',$line,2);
+ // Build the associative array
+ if($lower){
+ $conf[strtolower($line[0])] = $line[1];
+ }else{
+ $conf[$line[0]] = $line[1];
+ }
+ }
+
+ return $conf;
+}
+
+/**
+ * check if the given action was disabled in config
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @returns boolean true if enabled, false if disabled
+ */
+function actionOK($action){
+ static $disabled = null;
+ if(is_null($disabled)){
+ global $conf;
+
+ // prepare disabled actions array and handle legacy options
+ $disabled = explode(',',$conf['disableactions']);
+ $disabled = array_map('trim',$disabled);
+ if(isset($conf['openregister']) && !$conf['openregister']) $disabled[] = 'register';
+ if(isset($conf['resendpasswd']) && !$conf['resendpasswd']) $disabled[] = 'resendpwd';
+ $disabled = array_unique($disabled);
+ }
+
+ return !in_array($action,$disabled);
+}
+
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/events.php b/plugins/dokuwiki/inc/events.php
new file mode 100644
index 0000000..1231f4c
--- /dev/null
+++ b/plugins/dokuwiki/inc/events.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * DokuWiki Events
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+require_once(DOKU_INC.'inc/pluginutils.php');
+
+class Doku_Event {
+
+ // public properties
+ var $name = ''; // READONLY event name, objects must register against this name to see the event
+ var $data = NULL; // READWRITE data relevant to the event, no standardised format (YET!)
+ var $result = NULL; // READWRITE the results of the event action, only relevant in "_AFTER" advise
+ // event handlers may modify this if they are preventing the default action
+ // to provide the after event handlers with event results
+ var $canPreventDefault = true; // READONLY if true, event handlers can prevent the events default action
+
+ // private properties, event handlers can effect these through the provided methods
+ var $_default = true; // whether or not to carry out the default action associated with the event
+ var $_continue = true; // whether or not to continue propagating the event to other handlers
+
+ /**
+ * event constructor
+ */
+ function __construct($name, &$data) {
+
+ $this->name = $name;
+ $this->data =& $data;
+
+ }
+
+ /**
+ * advise functions
+ *
+ * advise all registered handlers of this event
+ *
+ * if these methods are used by functions outside of this object, they must
+ * properly handle correct processing of any default action and issue an
+ * advise_after() signal. e.g.
+ * $evt = new Doku_Event(name, data);
+ * if ($evt->advise_before(canPreventDefault) {
+ * // default action code block
+ * }
+ * $evt->advise_after();
+ * unset($evt);
+ *
+ * @return results of processing the event, usually $this->_default
+ */
+ function advise_before($enablePreventDefault=true) {
+ global $EVENT_HANDLER;
+
+ $this->canPreventDefault = $enablePreventDefault;
+ $EVENT_HANDLER->process_event($this,'BEFORE');
+
+ return (!$enablePreventDefault || $this->_default);
+ }
+
+ function advise_after() {
+ global $EVENT_HANDLER;
+
+ $this->_continue = true;
+ $EVENT_HANDLER->process_event($this,'AFTER');
+ }
+
+ /**
+ * trigger
+ *
+ * - advise all registered (<event>_BEFORE) handlers that this event is about to take place
+ * - carry out the default action using $this->data based on $enablePrevent and
+ * $this->_default, all of which may have been modified by the event handlers.
+ * - advise all registered (<event>_AFTER) handlers that the event has taken place
+ *
+ * @return $event->results
+ * the value set by any <event>_before or <event> handlers if the default action is prevented
+ * or the results of the default action (as modified by <event>_after handlers)
+ * or NULL no action took place and no handler modified the value
+ */
+ function trigger($action=NULL, $enablePrevent=true) {
+
+ if (!is_callable($action)) $enablePrevent = false;
+
+ if ($this->advise_before($enablePrevent) && is_callable($action)) {
+ if (is_array($action)) {
+ list($obj,$method) = $action;
+ $this->result = $obj->$method($this->data);
+ } else {
+ $this->result = $action($this->data);
+ }
+ }
+
+ $this->advise_after();
+
+ return $this->result;
+ }
+
+ /**
+ * stopPropagation
+ *
+ * stop any further processing of the event by event handlers
+ * this function does not prevent the default action taking place
+ */
+ function stopPropagation() { $this->_continue = false; }
+
+ /**
+ * preventDefault
+ *
+ * prevent the default action taking place
+ */
+ function preventDefault() { $this->_default = false; }
+}
+
+class Doku_Event_Handler {
+
+ // public properties: none
+
+ // private properties
+ var $_hooks = array(); // array of events and their registered handlers
+
+ /**
+ * event_handler
+ *
+ * constructor, loads all action plugins and calls their register() method giving them
+ * an opportunity to register any hooks they require
+ */
+ function __construct() {
+
+ // load action plugins
+ $plugin = NULL;
+ $pluginlist = plugin_list('action');
+
+ foreach ($pluginlist as $plugin_name) {
+ $plugin =& plugin_load('action',$plugin_name);
+
+ if ($plugin !== NULL) $plugin->register($this);
+ }
+ }
+
+ /**
+ * register_hook
+ *
+ * register a hook for an event
+ *
+ * @PARAM $event (string) name used by the event, (incl '_before' or '_after' for triggers)
+ * @PARAM $obj (obj) object in whose scope method is to be executed,
+ * if NULL, method is assumed to be a globally available function
+ * @PARAM $method (function) event handler function
+ * @PARAM $param (mixed) data passed to the event handler
+ */
+ function register_hook($event, $advise, &$obj, $method, $param=NULL) {
+ $this->_hooks[$event.'_'.$advise][] = array(&$obj, $method, $param);
+ }
+
+ function process_event(&$event,$advise='') {
+
+ $evt_name = $event->name . ($advise ? '_'.$advise : '_BEFORE');
+
+ if (!empty($this->_hooks[$evt_name])) {
+ $hook = reset($this->_hooks[$evt_name]);
+ do {
+// list($obj, $method, $param) = $hook;
+ $obj =& $hook[0];
+ $method = $hook[1];
+ $param = $hook[2];
+
+ if (is_null($obj)) {
+ $method($event, $param);
+ } else {
+ $obj->$method($event, $param);
+ }
+
+ } while ($event->_continue && $hook = next($this->_hooks[$evt_name]));
+ }
+ }
+}
+
+/**
+ * trigger_event
+ *
+ * function wrapper to process (create, trigger and destroy) an event
+ *
+ * @PARAM $name (string) name for the event
+ * @PARAM $data (mixed) event data
+ * @PARAM $action (callback) (optional, default=NULL) default action, a php callback function
+ * @PARAM $canPreventDefault (bool) (optional, default=true) can hooks prevent the default action
+ *
+ * @RETURN (mixed) the event results value after all event processing is complete
+ * by default this is the return value of the default action however
+ * it can be set or modified by event handler hooks
+ */
+function trigger_event($name, &$data, $action=NULL, $canPreventDefault=true) {
+
+ $evt = new Doku_Event($name, $data);
+ return $evt->trigger($action, $canPreventDefault);
+}
+
+// create the event handler
+global $EVENT_HANDLER;
+$EVENT_HANDLER = new Doku_Event_Handler();
diff --git a/plugins/dokuwiki/inc/geshi.php b/plugins/dokuwiki/inc/geshi.php
new file mode 100644
index 0000000..1a3bc25
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi.php
@@ -0,0 +1,4775 @@
+<?php
+/**
+ * GeSHi - Generic Syntax Highlighter
+ *
+ * The GeSHi class for Generic Syntax Highlighting. Please refer to the
+ * documentation at http://qbnz.com/highlighter/documentation.php for more
+ * information about how to use this class.
+ *
+ * For changes, release notes, TODOs etc, see the relevant files in the docs/
+ * directory.
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * @package geshi
+ * @subpackage core
+ * @author Nigel McNie <nigel@geshi.org>, Benny Baumann <BenBE@omorphia.de>
+ * @copyright (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
+ * @license http://gnu.org/copyleft/gpl.html GNU GPL
+ *
+ */
+
+//
+// GeSHi Constants
+// You should use these constant names in your programs instead of
+// their values - you never know when a value may change in a future
+// version
+//
+
+/** The version of this GeSHi file */
+/**
+ * peterdd: Took the easybook/geshi fork ( https://github.com/easybook/geshi )
+ * and corrected/adapted their version here.
+ * easybook/geshi release date of 1.0.8.17: 2016-03-29
+*/
+define('GESHI_VERSION', '1.0.8.17');
+
+// Define the root directory for the GeSHi code tree
+if (!defined('GESHI_ROOT')) {
+ /** The root directory for GeSHi */
+ define('GESHI_ROOT', dirname(__FILE__) . DIRECTORY_SEPARATOR);
+}
+/** The language file directory for GeSHi
+ @access private */
+define('GESHI_LANG_ROOT', GESHI_ROOT . 'geshi' . DIRECTORY_SEPARATOR);
+
+// Define if GeSHi should be paranoid about security
+if (!defined('GESHI_SECURITY_PARANOID')) {
+ /** Tells GeSHi to be paranoid about security settings */
+ define('GESHI_SECURITY_PARANOID', false);
+}
+
+// Line numbers - use with enable_line_numbers()
+/** Use no line numbers when building the result */
+define('GESHI_NO_LINE_NUMBERS', 0);
+/** Use normal line numbers when building the result */
+define('GESHI_NORMAL_LINE_NUMBERS', 1);
+/** Use fancy line numbers when building the result */
+define('GESHI_FANCY_LINE_NUMBERS', 2);
+
+// Container HTML type
+/** Use nothing to surround the source */
+define('GESHI_HEADER_NONE', 0);
+/** Use a "div" to surround the source */
+define('GESHI_HEADER_DIV', 1);
+/** Use a "pre" to surround the source */
+define('GESHI_HEADER_PRE', 2);
+/** Use a pre to wrap lines when line numbers are enabled or to wrap the whole code. */
+define('GESHI_HEADER_PRE_VALID', 3);
+/**
+ * Use a "table" to surround the source:
+ *
+ * <table>
+ * <thead><tr><td colspan="2">$header</td></tr></thead>
+ * <tbody><tr><td><pre>$linenumbers</pre></td><td><pre>$code></pre></td></tr></tbody>
+ * <tfooter><tr><td colspan="2">$footer</td></tr></tfoot>
+ * </table>
+ *
+ * this is essentially only a workaround for Firefox, see sf#1651996 or take a look at
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=365805
+ * @note when linenumbers are disabled this is essentially the same as GESHI_HEADER_PRE
+ */
+define('GESHI_HEADER_PRE_TABLE', 4);
+
+// Capatalisation constants
+/** Lowercase keywords found */
+define('GESHI_CAPS_NO_CHANGE', 0);
+/** Uppercase keywords found */
+define('GESHI_CAPS_UPPER', 1);
+/** Leave keywords found as the case that they are */
+define('GESHI_CAPS_LOWER', 2);
+
+// Link style constants
+/** Links in the source in the :link state */
+define('GESHI_LINK', 0);
+/** Links in the source in the :hover state */
+define('GESHI_HOVER', 1);
+/** Links in the source in the :active state */
+define('GESHI_ACTIVE', 2);
+/** Links in the source in the :visited state */
+define('GESHI_VISITED', 3);
+
+// Important string starter/finisher
+// Note that if you change these, they should be as-is: i.e., don't
+// write them as if they had been run through htmlentities()
+/** The starter for important parts of the source */
+define('GESHI_START_IMPORTANT', '<BEGIN GeSHi>');
+/** The ender for important parts of the source */
+define('GESHI_END_IMPORTANT', '<END GeSHi>');
+
+/**#@+
+ * @access private
+ */
+// When strict mode applies for a language
+/** Strict mode never applies (this is the most common) */
+define('GESHI_NEVER', 0);
+/** Strict mode *might* apply, and can be enabled or
+ disabled by {@link GeSHi->enable_strict_mode()} */
+define('GESHI_MAYBE', 1);
+/** Strict mode always applies */
+define('GESHI_ALWAYS', 2);
+
+// Advanced regexp handling constants, used in language files
+/** The key of the regex array defining what to search for */
+define('GESHI_SEARCH', 0);
+/** The key of the regex array defining what bracket group in a
+ matched search to use as a replacement */
+define('GESHI_REPLACE', 1);
+/** The key of the regex array defining any modifiers to the regular expression */
+define('GESHI_MODIFIERS', 2);
+/** The key of the regex array defining what bracket group in a
+ matched search to put before the replacement */
+define('GESHI_BEFORE', 3);
+/** The key of the regex array defining what bracket group in a
+ matched search to put after the replacement */
+define('GESHI_AFTER', 4);
+/** The key of the regex array defining a custom keyword to use
+ for this regexp's html tag class */
+define('GESHI_CLASS', 5);
+
+/** Used in language files to mark comments */
+define('GESHI_COMMENTS', 0);
+
+/** Used to work around missing PHP features **/
+define('GESHI_PHP_PRE_433', !(version_compare(PHP_VERSION, '4.3.3') === 1));
+
+/** make sure we can call stripos **/
+if (!function_exists('stripos')) {
+ // the offset param of preg_match is not supported below PHP 4.3.3
+ if (GESHI_PHP_PRE_433) {
+ /**
+ * @ignore
+ */
+ function stripos($haystack, $needle, $offset = null) {
+ if (!is_null($offset)) {
+ $haystack = substr($haystack, $offset);
+ }
+ if (preg_match('/'. preg_quote($needle, '/') . '/', $haystack, $match, PREG_OFFSET_CAPTURE)) {
+ return $match[0][1];
+ }
+ return false;
+ }
+ }
+ else {
+ /**
+ * @ignore
+ */
+ function stripos($haystack, $needle, $offset = null) {
+ if (preg_match('/'. preg_quote($needle, '/') . '/', $haystack, $match, PREG_OFFSET_CAPTURE, $offset)) {
+ return $match[0][1];
+ }
+ return false;
+ }
+ }
+}
+
+/** some old PHP / PCRE subpatterns only support up to xxx subpatterns in
+ regular expressions. Set this to false if your PCRE lib is up to date
+ @see GeSHi->optimize_regexp_list()
+ **/
+define('GESHI_MAX_PCRE_SUBPATTERNS', 500);
+/** it's also important not to generate too long regular expressions
+ be generous here... but keep in mind, that when reaching this limit we
+ still have to close open patterns. 12k should do just fine on a 16k limit.
+ @see GeSHi->optimize_regexp_list()
+ **/
+define('GESHI_MAX_PCRE_LENGTH', 12288);
+
+//Number format specification
+/** Basic number format for integers */
+define('GESHI_NUMBER_INT_BASIC', 1); //Default integers \d+
+/** Enhanced number format for integers like seen in C */
+define('GESHI_NUMBER_INT_CSTYLE', 2); //Default C-Style \d+[lL]?
+/** Number format to highlight binary numbers with a suffix "b" */
+define('GESHI_NUMBER_BIN_SUFFIX', 16); //[01]+[bB]
+/** Number format to highlight binary numbers with a prefix % */
+define('GESHI_NUMBER_BIN_PREFIX_PERCENT', 32); //%[01]+
+/** Number format to highlight binary numbers with a prefix 0b (C) */
+define('GESHI_NUMBER_BIN_PREFIX_0B', 64); //0b[01]+
+/** Number format to highlight octal numbers with a leading zero */
+define('GESHI_NUMBER_OCT_PREFIX', 256); //0[0-7]+
+/** Number format to highlight octal numbers with a prefix 0o (logtalk) */
+define('GESHI_NUMBER_OCT_PREFIX_0O', 512); //0[0-7]+
+/** Number format to highlight octal numbers with a leading @ (Used in HiSofts Devpac series). */
+define('GESHI_NUMBER_OCT_PREFIX_AT', 1024); //@[0-7]+
+/** Number format to highlight octal numbers with a suffix of o */
+define('GESHI_NUMBER_OCT_SUFFIX', 2048); //[0-7]+[oO]
+/** Number format to highlight hex numbers with a prefix 0x */
+define('GESHI_NUMBER_HEX_PREFIX', 4096); //0x[0-9a-fA-F]+
+/** Number format to highlight hex numbers with a prefix $ */
+define('GESHI_NUMBER_HEX_PREFIX_DOLLAR', 8192); //$[0-9a-fA-F]+
+/** Number format to highlight hex numbers with a suffix of h */
+define('GESHI_NUMBER_HEX_SUFFIX', 16384); //[0-9][0-9a-fA-F]*h
+/** Number format to highlight floating-point numbers without support for scientific notation */
+define('GESHI_NUMBER_FLT_NONSCI', 65536); //\d+\.\d+
+/** Number format to highlight floating-point numbers without support for scientific notation */
+define('GESHI_NUMBER_FLT_NONSCI_F', 131072); //\d+(\.\d+)?f
+/** Number format to highlight floating-point numbers with support for scientific notation (E) and optional leading zero */
+define('GESHI_NUMBER_FLT_SCI_SHORT', 262144); //\.\d+e\d+
+/** Number format to highlight floating-point numbers with support for scientific notation (E) and required leading digit */
+define('GESHI_NUMBER_FLT_SCI_ZERO', 524288); //\d+(\.\d+)?e\d+
+//Custom formats are passed by RX array
+
+// Error detection - use these to analyse faults
+/** No sourcecode to highlight was specified
+ * @deprecated
+ */
+define('GESHI_ERROR_NO_INPUT', 1);
+/** The language specified does not exist */
+define('GESHI_ERROR_NO_SUCH_LANG', 2);
+/** GeSHi could not open a file for reading (generally a language file) */
+define('GESHI_ERROR_FILE_NOT_READABLE', 3);
+/** The header type passed to {@link GeSHi->set_header_type()} was invalid */
+define('GESHI_ERROR_INVALID_HEADER_TYPE', 4);
+/** The line number type passed to {@link GeSHi->enable_line_numbers()} was invalid */
+define('GESHI_ERROR_INVALID_LINE_NUMBER_TYPE', 5);
+/**#@-*/
+
+
+/**
+ * The GeSHi Class.
+ *
+ * Please refer to the documentation for GeSHi 1.0.X that is available
+ * at http://qbnz.com/highlighter/documentation.php for more information
+ * about how to use this class.
+ *
+ * @package geshi
+ * @author Nigel McNie <nigel@geshi.org>, Benny Baumann <BenBE@omorphia.de>
+ * @copyright (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
+ */
+class GeSHi {
+ /**#@+
+ * @access private
+ */
+ /**
+ * The source code to highlight
+ * @var string
+ */
+ var $source = '';
+
+ /**
+ * The language to use when highlighting
+ * @var string
+ */
+ var $language = '';
+
+ /**
+ * The data for the language used
+ * @var array
+ */
+ var $language_data = array();
+
+ /**
+ * The path to the language files
+ * @var string
+ */
+ var $language_path = GESHI_LANG_ROOT;
+
+ /**
+ * The error message associated with an error
+ * @var string
+ * @todo check err reporting works
+ */
+ var $error = false;
+
+ /**
+ * Possible error messages
+ * @var array
+ */
+ var $error_messages = array(
+ GESHI_ERROR_NO_SUCH_LANG => 'GeSHi could not find the language {LANGUAGE} (using path {PATH})',
+ GESHI_ERROR_FILE_NOT_READABLE => 'The file specified for load_from_file was not readable',
+ GESHI_ERROR_INVALID_HEADER_TYPE => 'The header type specified is invalid',
+ GESHI_ERROR_INVALID_LINE_NUMBER_TYPE => 'The line number type specified is invalid'
+ );
+
+ /**
+ * Whether highlighting is strict or not
+ * @var boolean
+ */
+ var $strict_mode = false;
+
+ /**
+ * Whether to use CSS classes in output
+ * @var boolean
+ */
+ var $use_classes = false;
+
+ /**
+ * The type of header to use. Can be one of the following
+ * values:
+ *
+ * - GESHI_HEADER_PRE: Source is outputted in a "pre" HTML element.
+ * - GESHI_HEADER_DIV: Source is outputted in a "div" HTML element.
+ * - GESHI_HEADER_NONE: No header is outputted.
+ *
+ * @var int
+ */
+ var $header_type = GESHI_HEADER_PRE;
+
+ /**
+ * Array of permissions for which lexics should be highlighted
+ * @var array
+ */
+ var $lexic_permissions = array(
+ 'KEYWORDS' => array(),
+ 'COMMENTS' => array('MULTI' => true),
+ 'REGEXPS' => array(),
+ 'ESCAPE_CHAR' => true,
+ 'BRACKETS' => true,
+ 'SYMBOLS' => false,
+ 'STRINGS' => true,
+ 'NUMBERS' => true,
+ 'METHODS' => true,
+ 'SCRIPT' => true
+ );
+
+ /**
+ * The time it took to parse the code
+ * @var double
+ */
+ var $time = 0;
+
+ /**
+ * The content of the header block
+ * @var string
+ */
+ var $header_content = '';
+
+ /**
+ * The content of the footer block
+ * @var string
+ */
+ var $footer_content = '';
+
+ /**
+ * The style of the header block
+ * @var string
+ */
+ var $header_content_style = '';
+
+ /**
+ * The style of the footer block
+ * @var string
+ */
+ var $footer_content_style = '';
+
+ /**
+ * Tells if a block around the highlighted source should be forced
+ * if not using line numbering
+ * @var boolean
+ */
+ var $force_code_block = false;
+
+ /**
+ * The styles for hyperlinks in the code
+ * @var array
+ */
+ var $link_styles = array();
+
+ /**
+ * Whether important blocks should be recognised or not
+ * @var boolean
+ * @deprecated
+ * @todo REMOVE THIS FUNCTIONALITY!
+ */
+ var $enable_important_blocks = false;
+
+ /**
+ * Styles for important parts of the code
+ * @var string
+ * @deprecated
+ * @todo As above - rethink the whole idea of important blocks as it is buggy and
+ * will be hard to implement in 1.2
+ */
+ var $important_styles = 'font-weight: bold; color: red;'; // Styles for important parts of the code
+
+ /**
+ * Whether CSS IDs should be added to the code
+ * @var boolean
+ */
+ var $add_ids = false;
+
+ /**
+ * Lines that should be highlighted extra
+ * @var array
+ */
+ var $highlight_extra_lines = array();
+
+ /**
+ * Styles of lines that should be highlighted extra
+ * @var array
+ */
+ var $highlight_extra_lines_styles = array();
+
+ /**
+ * Styles of extra-highlighted lines
+ * @var string
+ */
+ var $highlight_extra_lines_style = 'background-color: #ffc;';
+
+ /**
+ * The line ending
+ * If null, nl2br() will be used on the result string.
+ * Otherwise, all instances of \n will be replaced with $line_ending
+ * @var string
+ */
+ var $line_ending = null;
+
+ /**
+ * Number at which line numbers should start at
+ * @var int
+ */
+ var $line_numbers_start = 1;
+
+ /**
+ * The overall style for this code block
+ * @var string
+ */
+ var $overall_style = 'font-family:monospace;';
+
+ /**
+ * The style for the actual code
+ * @var string
+ */
+ var $code_style = 'font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;';
+
+ /**
+ * The overall class for this code block
+ * @var string
+ */
+ var $overall_class = '';
+
+ /**
+ * The overall ID for this code block
+ * @var string
+ */
+ var $overall_id = '';
+
+ /**
+ * Line number styles
+ * @var string
+ */
+ var $line_style1 = 'font-weight: normal; vertical-align:top;';
+
+ /**
+ * Line number styles for fancy lines
+ * @var string
+ */
+ var $line_style2 = 'font-weight: bold; vertical-align:top;';
+
+ /**
+ * Style for line numbers when GESHI_HEADER_PRE_TABLE is chosen
+ * @var string
+ */
+ var $table_linenumber_style = 'width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;';
+
+ /**
+ * Flag for how line numbers are displayed
+ * @var boolean
+ */
+ var $line_numbers = GESHI_NO_LINE_NUMBERS;
+
+ /**
+ * Flag to decide if multi line spans are allowed. Set it to false to make sure
+ * each tag is closed before and reopened after each linefeed.
+ * @var boolean
+ */
+ var $allow_multiline_span = true;
+
+ /**
+ * The "nth" value for fancy line highlighting
+ * @var int
+ */
+ var $line_nth_row = 0;
+
+ /**
+ * The size of tab stops
+ * @var int
+ */
+ var $tab_width = 8;
+
+ /**
+ * Should we use language-defined tab stop widths?
+ * @var int
+ */
+ var $use_language_tab_width = false;
+
+ /**
+ * Default target for keyword links
+ * @var string
+ */
+ var $link_target = '';
+
+ /**
+ * The encoding to use for entity encoding
+ * NOTE: Used with Escape Char Sequences to fix UTF-8 handling (cf. SF#2037598)
+ * @var string
+ */
+ var $encoding = 'utf-8';
+
+ /**
+ * Should keywords be linked?
+ * @var boolean
+ */
+ var $keyword_links = true;
+
+ /**
+ * Currently loaded language file
+ * @var string
+ * @since 1.0.7.22
+ */
+ var $loaded_language = '';
+
+ /**
+ * Wether the caches needed for parsing are built or not
+ *
+ * @var bool
+ * @since 1.0.8
+ */
+ var $parse_cache_built = false;
+
+ /**
+ * Work around for Suhosin Patch with disabled /e modifier
+ *
+ * Note from suhosins author in config file:
+ * <blockquote>
+ * The /e modifier inside <code>preg_replace()</code> allows code execution.
+ * Often it is the cause for remote code execution exploits. It is wise to
+ * deactivate this feature and test where in the application it is used.
+ * The developer using the /e modifier should be made aware that he should
+ * use <code>preg_replace_callback()</code> instead
+ * </blockquote>
+ *
+ * @var array
+ * @since 1.0.8
+ */
+ var $_kw_replace_group = 0;
+ var $_rx_key = 0;
+
+ /**
+ * some "callback parameters" for handle_multiline_regexps
+ *
+ * @since 1.0.8
+ * @access private
+ * @var string
+ */
+ var $_hmr_before = '';
+ var $_hmr_replace = '';
+ var $_hmr_after = '';
+ var $_hmr_key = 0;
+
+ /**#@-*/
+
+ /**
+ * Creates a new GeSHi object, with source and language
+ *
+ * @param string The source code to highlight
+ * @param string The language to highlight the source with
+ * @param string The path to the language file directory. <b>This
+ * is deprecated!</b> I've backported the auto path
+ * detection from the 1.1.X dev branch, so now it
+ * should be automatically set correctly. If you have
+ * renamed the language directory however, you will
+ * still need to set the path using this parameter or
+ * {@link GeSHi->set_language_path()}
+ * @since 1.0.0
+ */
+ function __construct($source = '', $language = '', $path = '') {
+ if (!empty($source)) {
+ $this->set_source($source);
+ }
+ if (!empty($language)) {
+ $this->set_language($language);
+ }
+ $this->set_language_path($path);
+ }
+
+ /**
+ * Returns the version of GeSHi
+ *
+ * @return string
+ * @since 1 0.8.11
+ */
+ function get_version()
+ {
+ return GESHI_VERSION;
+ }
+
+ /**
+ * Returns an error message associated with the last GeSHi operation,
+ * or false if no error has occured
+ *
+ * @return string|false An error message if there has been an error, else false
+ * @since 1.0.0
+ */
+ function error() {
+ if ($this->error) {
+ //Put some template variables for debugging here ...
+ $debug_tpl_vars = array(
+ '{LANGUAGE}' => $this->language,
+ '{PATH}' => $this->language_path
+ );
+ $msg = str_replace(
+ array_keys($debug_tpl_vars),
+ array_values($debug_tpl_vars),
+ $this->error_messages[$this->error]);
+
+ return "<br /><strong>GeSHi Error:</strong> $msg (code {$this->error})<br />";
+ }
+ return false;
+ }
+
+ /**
+ * Gets a human-readable language name (thanks to Simon Patterson
+ * for the idea :))
+ *
+ * @return string The name for the current language
+ * @since 1.0.2
+ */
+ function get_language_name() {
+ if (GESHI_ERROR_NO_SUCH_LANG == $this->error) {
+ return $this->language_data['LANG_NAME'] . ' (Unknown Language)';
+ }
+ return $this->language_data['LANG_NAME'];
+ }
+
+ /**
+ * Sets the source code for this object
+ *
+ * @param string The source code to highlight
+ * @since 1.0.0
+ */
+ function set_source($source) {
+ $this->source = $source;
+ $this->highlight_extra_lines = array();
+ }
+
+ /**
+ * Sets the language for this object
+ *
+ * @note since 1.0.8 this function won't reset language-settings by default anymore!
+ * if you need this set $force_reset = true
+ *
+ * @param string The name of the language to use
+ * @since 1.0.0
+ */
+ function set_language($language, $force_reset = false) {
+ if ($force_reset) {
+ $this->loaded_language = false;
+ }
+
+ //Clean up the language name to prevent malicious code injection
+ $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
+
+ $language = strtolower($language);
+
+ //Retreive the full filename
+ $file_name = $this->language_path . $language . '.php';
+ if ($file_name == $this->loaded_language) {
+ // this language is already loaded!
+ return;
+ }
+
+ $this->language = $language;
+
+ $this->error = false;
+ $this->strict_mode = GESHI_NEVER;
+
+ //Check if we can read the desired file
+ if (!is_readable($file_name)) {
+ $this->error = GESHI_ERROR_NO_SUCH_LANG;
+ return;
+ }
+
+ // Load the language for parsing
+ $this->load_language($file_name);
+ }
+
+ /**
+ * Sets the path to the directory containing the language files. Note
+ * that this path is relative to the directory of the script that included
+ * geshi.php, NOT geshi.php itself.
+ *
+ * @param string The path to the language directory
+ * @since 1.0.0
+ * @deprecated The path to the language files should now be automatically
+ * detected, so this method should no longer be needed. The
+ * 1.1.X branch handles manual setting of the path differently
+ * so this method will disappear in 1.2.0.
+ */
+ function set_language_path($path) {
+ if(strpos($path,':')) {
+ //Security Fix to prevent external directories using fopen wrappers.
+ if(DIRECTORY_SEPARATOR == "\\") {
+ if(!preg_match('#^[a-zA-Z]:#', $path) || false !== strpos($path, ':', 2)) {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ if(preg_match('#[^/a-zA-Z0-9_\.\-\\\s:]#', $path)) {
+ //Security Fix to prevent external directories using fopen wrappers.
+ return;
+ }
+ if(GESHI_SECURITY_PARANOID && false !== strpos($path, '/.')) {
+ //Security Fix to prevent external directories using fopen wrappers.
+ return;
+ }
+ if(GESHI_SECURITY_PARANOID && false !== strpos($path, '..')) {
+ //Security Fix to prevent external directories using fopen wrappers.
+ return;
+ }
+ if ($path) {
+ $this->language_path = ('/' == $path[strlen($path) - 1]) ? $path : $path . '/';
+ $this->set_language($this->language); // otherwise set_language_path has no effect
+ }
+ }
+
+ /**
+ * Get supported langs or an associative array lang=>full_name.
+ * @param boolean $longnames
+ * @return array
+ */
+ function get_supported_languages($full_names=false)
+ {
+ // return array
+ $back = array();
+
+ // we walk the lang root
+ $dir = dir($this->language_path);
+
+ // foreach entry
+ while (false !== ($entry = $dir->read()))
+ {
+ $full_path = $this->language_path.$entry;
+
+ // Skip all dirs
+ if (is_dir($full_path)) {
+ continue;
+ }
+
+ // we only want lang.php files
+ if (!preg_match('/^([^.]+)\.php$/', $entry, $matches)) {
+ continue;
+ }
+
+ // Raw lang name is here
+ $langname = $matches[1];
+
+ // We want the fullname too?
+ if ($full_names === true)
+ {
+ if (false !== ($fullname = $this->get_language_fullname($langname)))
+ {
+ $back[$langname] = $fullname; // we go associative
+ }
+ }
+ else
+ {
+ // just store raw langname
+ $back[] = $langname;
+ }
+ }
+
+ $dir->close();
+
+ return $back;
+ }
+
+ /**
+ * Get full_name for a lang or false.
+ * @param string $language short langname (html4strict for example)
+ * @return mixed
+ */
+ function get_language_fullname($language)
+ {
+ //Clean up the language name to prevent malicious code injection
+ $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
+
+ $language = strtolower($language);
+
+ // get fullpath-filename for a langname
+ $fullpath = $this->language_path.$language.'.php';
+
+ // we need to get contents :S
+ if (false === ($data = file_get_contents($fullpath))) {
+ $this->error = sprintf('Geshi::get_lang_fullname() Unknown Language: %s', $language);
+ return false;
+ }
+
+ // match the langname
+ if (!preg_match('/\'LANG_NAME\'\s*=>\s*\'((?:[^\']|\\\')+?)\'/', $data, $matches)) {
+ $this->error = sprintf('Geshi::get_lang_fullname(%s): Regex can not detect language', $language);
+ return false;
+ }
+
+ // return fullname for langname
+ return stripcslashes($matches[1]);
+ }
+
+ /**
+ * Sets the type of header to be used.
+ *
+ * If GESHI_HEADER_DIV is used, the code is surrounded in a "div".This
+ * means more source code but more control over tab width and line-wrapping.
+ * GESHI_HEADER_PRE means that a "pre" is used - less source, but less
+ * control. Default is GESHI_HEADER_PRE.
+ *
+ * From 1.0.7.2, you can use GESHI_HEADER_NONE to specify that no header code
+ * should be outputted.
+ *
+ * @param int The type of header to be used
+ * @since 1.0.0
+ */
+ function set_header_type($type) {
+ //Check if we got a valid header type
+ if (!in_array($type, array(GESHI_HEADER_NONE, GESHI_HEADER_DIV,
+ GESHI_HEADER_PRE, GESHI_HEADER_PRE_VALID, GESHI_HEADER_PRE_TABLE))) {
+ $this->error = GESHI_ERROR_INVALID_HEADER_TYPE;
+ return;
+ }
+
+ //Set that new header type
+ $this->header_type = $type;
+ }
+
+ /**
+ * Sets the styles for the code that will be outputted
+ * when this object is parsed. The style should be a
+ * string of valid stylesheet declarations
+ *
+ * @param string The overall style for the outputted code block
+ * @param boolean Whether to merge the styles with the current styles or not
+ * @since 1.0.0
+ */
+ function set_overall_style($style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->overall_style = $style;
+ } else {
+ $this->overall_style .= $style;
+ }
+ }
+
+ /**
+ * Sets the overall classname for this block of code. This
+ * class can then be used in a stylesheet to style this object's
+ * output
+ *
+ * @param string The class name to use for this block of code
+ * @since 1.0.0
+ */
+ function set_overall_class($class) {
+ $this->overall_class = $class;
+ }
+
+ /**
+ * Sets the overall id for this block of code. This id can then
+ * be used in a stylesheet to style this object's output
+ *
+ * @param string The ID to use for this block of code
+ * @since 1.0.0
+ */
+ function set_overall_id($id) {
+ $this->overall_id = $id;
+ }
+
+ /**
+ * Sets whether CSS classes should be used to highlight the source. Default
+ * is off, calling this method with no arguments will turn it on
+ *
+ * @param boolean Whether to turn classes on or not
+ * @since 1.0.0
+ */
+ function enable_classes($flag = true) {
+ $this->use_classes = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the style for the actual code. This should be a string
+ * containing valid stylesheet declarations. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * Note: Use this method to override any style changes you made to
+ * the line numbers if you are using line numbers, else the line of
+ * code will have the same style as the line number! Consult the
+ * GeSHi documentation for more information about this.
+ *
+ * @param string The style to use for actual code
+ * @param boolean Whether to merge the current styles with the new styles
+ * @since 1.0.2
+ */
+ function set_code_style($style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->code_style = $style;
+ } else {
+ $this->code_style .= $style;
+ }
+ }
+
+ /**
+ * Sets the styles for the line numbers.
+ *
+ * @param string The style for the line numbers that are "normal"
+ * @param string|boolean If a string, this is the style of the line
+ * numbers that are "fancy", otherwise if boolean then this
+ * defines whether the normal styles should be merged with the
+ * new normal styles or not
+ * @param boolean If set, is the flag for whether to merge the "fancy"
+ * styles with the current styles or not
+ * @since 1.0.2
+ */
+ function set_line_style($style1, $style2 = '', $preserve_defaults = false) {
+ //Check if we got 2 or three parameters
+ if (is_bool($style2)) {
+ $preserve_defaults = $style2;
+ $style2 = '';
+ }
+
+ //Actually set the new styles
+ if (!$preserve_defaults) {
+ $this->line_style1 = $style1;
+ $this->line_style2 = $style2;
+ } else {
+ $this->line_style1 .= $style1;
+ $this->line_style2 .= $style2;
+ }
+ }
+
+ /**
+ * Sets whether line numbers should be displayed.
+ *
+ * Valid values for the first parameter are:
+ *
+ * - GESHI_NO_LINE_NUMBERS: Line numbers will not be displayed
+ * - GESHI_NORMAL_LINE_NUMBERS: Line numbers will be displayed
+ * - GESHI_FANCY_LINE_NUMBERS: Fancy line numbers will be displayed
+ *
+ * For fancy line numbers, the second parameter is used to signal which lines
+ * are to be fancy. For example, if the value of this parameter is 5 then every
+ * 5th line will be fancy.
+ *
+ * @param int How line numbers should be displayed
+ * @param int Defines which lines are fancy
+ * @since 1.0.0
+ */
+ function enable_line_numbers($flag, $nth_row = 5) {
+ if (GESHI_NO_LINE_NUMBERS != $flag && GESHI_NORMAL_LINE_NUMBERS != $flag
+ && GESHI_FANCY_LINE_NUMBERS != $flag) {
+ $this->error = GESHI_ERROR_INVALID_LINE_NUMBER_TYPE;
+ }
+ $this->line_numbers = $flag;
+ $this->line_nth_row = $nth_row;
+ }
+
+ /**
+ * Sets wether spans and other HTML markup generated by GeSHi can
+ * span over multiple lines or not. Defaults to true to reduce overhead.
+ * Set it to false if you want to manipulate the output or manually display
+ * the code in an ordered list.
+ *
+ * @param boolean Wether multiline spans are allowed or not
+ * @since 1.0.7.22
+ */
+ function enable_multiline_span($flag) {
+ $this->allow_multiline_span = (bool) $flag;
+ }
+
+ /**
+ * Get current setting for multiline spans, see GeSHi->enable_multiline_span().
+ *
+ * @see enable_multiline_span
+ * @return bool
+ */
+ function get_multiline_span() {
+ return $this->allow_multiline_span;
+ }
+
+ /**
+ * Sets the style for a keyword group. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param int The key of the keyword group to change the styles of
+ * @param string The style to make the keywords
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ */
+ function set_keyword_group_style($key, $style, $preserve_defaults = false) {
+ //Set the style for this keyword group
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['KEYWORDS'][$key] = $style;
+ } else {
+ $this->language_data['STYLES']['KEYWORDS'][$key] .= $style;
+ }
+
+ //Update the lexic permissions
+ if (!isset($this->lexic_permissions['KEYWORDS'][$key])) {
+ $this->lexic_permissions['KEYWORDS'][$key] = true;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for a keyword group
+ *
+ * @param int The key of the keyword group to turn on or off
+ * @param boolean Whether to turn highlighting for that group on or off
+ * @since 1.0.0
+ */
+ function set_keyword_group_highlighting($key, $flag = true) {
+ $this->lexic_permissions['KEYWORDS'][$key] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for comment groups. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param int The key of the comment group to change the styles of
+ * @param string The style to make the comments
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ */
+ function set_comments_style($key, $style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['COMMENTS'][$key] = $style;
+ } else {
+ $this->language_data['STYLES']['COMMENTS'][$key] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for comment groups
+ *
+ * @param int The key of the comment group to turn on or off
+ * @param boolean Whether to turn highlighting for that group on or off
+ * @since 1.0.0
+ */
+ function set_comments_highlighting($key, $flag = true) {
+ $this->lexic_permissions['COMMENTS'][$key] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for escaped characters. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the escape characters
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ */
+ function set_escape_characters_style($style, $preserve_defaults = false, $group = 0) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['ESCAPE_CHAR'][$group] = $style;
+ } else {
+ $this->language_data['STYLES']['ESCAPE_CHAR'][$group] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for escaped characters
+ *
+ * @param boolean Whether to turn highlighting for escape characters on or off
+ * @since 1.0.0
+ */
+ function set_escape_characters_highlighting($flag = true) {
+ $this->lexic_permissions['ESCAPE_CHAR'] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for brackets. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * This method is DEPRECATED: use set_symbols_style instead.
+ * This method will be removed in 1.2.X
+ *
+ * @param string The style to make the brackets
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ * @deprecated In favour of set_symbols_style
+ */
+ function set_brackets_style($style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['BRACKETS'][0] = $style;
+ } else {
+ $this->language_data['STYLES']['BRACKETS'][0] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for brackets
+ *
+ * This method is DEPRECATED: use set_symbols_highlighting instead.
+ * This method will be remove in 1.2.X
+ *
+ * @param boolean Whether to turn highlighting for brackets on or off
+ * @since 1.0.0
+ * @deprecated In favour of set_symbols_highlighting
+ */
+ function set_brackets_highlighting($flag) {
+ $this->lexic_permissions['BRACKETS'] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for symbols. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the symbols
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @param int Tells the group of symbols for which style should be set.
+ * @since 1.0.1
+ */
+ function set_symbols_style($style, $preserve_defaults = false, $group = 0) {
+ // Update the style of symbols
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['SYMBOLS'][$group] = $style;
+ } else {
+ $this->language_data['STYLES']['SYMBOLS'][$group] .= $style;
+ }
+
+ // For backward compatibility
+ if (0 == $group) {
+ $this->set_brackets_style ($style, $preserve_defaults);
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for symbols
+ *
+ * @param boolean Whether to turn highlighting for symbols on or off
+ * @since 1.0.0
+ */
+ function set_symbols_highlighting($flag) {
+ // Update lexic permissions for this symbol group
+ $this->lexic_permissions['SYMBOLS'] = ($flag) ? true : false;
+
+ // For backward compatibility
+ $this->set_brackets_highlighting ($flag);
+ }
+
+ /**
+ * Sets the styles for strings. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the escape characters
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @param int Tells the group of strings for which style should be set.
+ * @since 1.0.0
+ */
+ function set_strings_style($style, $preserve_defaults = false, $group = 0) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['STRINGS'][$group] = $style;
+ } else {
+ $this->language_data['STYLES']['STRINGS'][$group] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for strings
+ *
+ * @param boolean Whether to turn highlighting for strings on or off
+ * @since 1.0.0
+ */
+ function set_strings_highlighting($flag) {
+ $this->lexic_permissions['STRINGS'] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for strict code blocks. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the script blocks
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @param int Tells the group of script blocks for which style should be set.
+ * @since 1.0.8.4
+ */
+ function set_script_style($style, $preserve_defaults = false, $group = 0) {
+ // Update the style of symbols
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['SCRIPT'][$group] = $style;
+ } else {
+ $this->language_data['STYLES']['SCRIPT'][$group] .= $style;
+ }
+ }
+
+ /**
+ * Sets the styles for numbers. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the numbers
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @param int Tells the group of numbers for which style should be set.
+ * @since 1.0.0
+ */
+ function set_numbers_style($style, $preserve_defaults = false, $group = 0) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['NUMBERS'][$group] = $style;
+ } else {
+ $this->language_data['STYLES']['NUMBERS'][$group] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for numbers
+ *
+ * @param boolean Whether to turn highlighting for numbers on or off
+ * @since 1.0.0
+ */
+ function set_numbers_highlighting($flag) {
+ $this->lexic_permissions['NUMBERS'] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for methods. $key is a number that references the
+ * appropriate "object splitter" - see the language file for the language
+ * you are highlighting to get this number. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param int The key of the object splitter to change the styles of
+ * @param string The style to make the methods
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ */
+ function set_methods_style($key, $style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['METHODS'][$key] = $style;
+ } else {
+ $this->language_data['STYLES']['METHODS'][$key] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for methods
+ *
+ * @param boolean Whether to turn highlighting for methods on or off
+ * @since 1.0.0
+ */
+ function set_methods_highlighting($flag) {
+ $this->lexic_permissions['METHODS'] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets the styles for regexps. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * @param string The style to make the regular expression matches
+ * @param boolean Whether to merge the new styles with the old or just
+ * to overwrite them
+ * @since 1.0.0
+ */
+ function set_regexps_style($key, $style, $preserve_defaults = false) {
+ if (!$preserve_defaults) {
+ $this->language_data['STYLES']['REGEXPS'][$key] = $style;
+ } else {
+ $this->language_data['STYLES']['REGEXPS'][$key] .= $style;
+ }
+ }
+
+ /**
+ * Turns highlighting on/off for regexps
+ *
+ * @param int The key of the regular expression group to turn on or off
+ * @param boolean Whether to turn highlighting for the regular expression group on or off
+ * @since 1.0.0
+ */
+ function set_regexps_highlighting($key, $flag) {
+ $this->lexic_permissions['REGEXPS'][$key] = ($flag) ? true : false;
+ }
+
+ /**
+ * Sets whether a set of keywords are checked for in a case sensitive manner
+ *
+ * @param int The key of the keyword group to change the case sensitivity of
+ * @param boolean Whether to check in a case sensitive manner or not
+ * @since 1.0.0
+ */
+ function set_case_sensitivity($key, $case) {
+ $this->language_data['CASE_SENSITIVE'][$key] = ($case) ? true : false;
+ }
+
+ /**
+ * Sets the case that keywords should use when found. Use the constants:
+ *
+ * - GESHI_CAPS_NO_CHANGE: leave keywords as-is
+ * - GESHI_CAPS_UPPER: convert all keywords to uppercase where found
+ * - GESHI_CAPS_LOWER: convert all keywords to lowercase where found
+ *
+ * @param int A constant specifying what to do with matched keywords
+ * @since 1.0.1
+ */
+ function set_case_keywords($case) {
+ if (in_array($case, array(
+ GESHI_CAPS_NO_CHANGE, GESHI_CAPS_UPPER, GESHI_CAPS_LOWER))) {
+ $this->language_data['CASE_KEYWORDS'] = $case;
+ }
+ }
+
+ /**
+ * Sets how many spaces a tab is substituted for
+ *
+ * Widths below zero are ignored
+ *
+ * @param int The tab width
+ * @since 1.0.0
+ */
+ function set_tab_width($width) {
+ $this->tab_width = intval($width);
+
+ //Check if it fit's the constraints:
+ if ($this->tab_width < 1) {
+ //Return it to the default
+ $this->tab_width = 8;
+ }
+ }
+
+ /**
+ * Sets whether or not to use tab-stop width specifed by language
+ *
+ * @param boolean Whether to use language-specific tab-stop widths
+ * @since 1.0.7.20
+ */
+ function set_use_language_tab_width($use) {
+ $this->use_language_tab_width = (bool) $use;
+ }
+
+ /**
+ * Returns the tab width to use, based on the current language and user
+ * preference
+ *
+ * @return int Tab width
+ * @since 1.0.7.20
+ */
+ function get_real_tab_width() {
+ if (!$this->use_language_tab_width ||
+ !isset($this->language_data['TAB_WIDTH'])) {
+ return $this->tab_width;
+ } else {
+ return $this->language_data['TAB_WIDTH'];
+ }
+ }
+
+ /**
+ * Enables/disables strict highlighting. Default is off, calling this
+ * method without parameters will turn it on. See documentation
+ * for more details on strict mode and where to use it.
+ *
+ * @param boolean Whether to enable strict mode or not
+ * @since 1.0.0
+ */
+ function enable_strict_mode($mode = true) {
+ if (GESHI_MAYBE == $this->language_data['STRICT_MODE_APPLIES']) {
+ $this->strict_mode = ($mode) ? GESHI_ALWAYS : GESHI_NEVER;
+ }
+ }
+
+ /**
+ * Disables all highlighting
+ *
+ * @since 1.0.0
+ * @todo Rewrite with array traversal
+ * @deprecated In favour of enable_highlighting
+ */
+ function disable_highlighting() {
+ $this->enable_highlighting(false);
+ }
+
+ /**
+ * Enables all highlighting
+ *
+ * The optional flag parameter was added in version 1.0.7.21 and can be used
+ * to enable (true) or disable (false) all highlighting.
+ *
+ * @since 1.0.0
+ * @param boolean A flag specifying whether to enable or disable all highlighting
+ * @todo Rewrite with array traversal
+ */
+ function enable_highlighting($flag = true) {
+ $flag = $flag ? true : false;
+ foreach ($this->lexic_permissions as $key => $value) {
+ if (is_array($value)) {
+ foreach ($value as $k => $v) {
+ $this->lexic_permissions[$key][$k] = $flag;
+ }
+ } else {
+ $this->lexic_permissions[$key] = $flag;
+ }
+ }
+
+ // Context blocks
+ $this->enable_important_blocks = $flag;
+ }
+
+ /**
+ * Given a file extension, this method returns either a valid geshi language
+ * name, or the empty string if it couldn't be found
+ *
+ * @param string The extension to get a language name for
+ * @param array A lookup array to use instead of the default one
+ * @since 1.0.5
+ * @todo Re-think about how this method works (maybe make it private and/or make it
+ * a extension->lang lookup?)
+ * @todo static?
+ */
+ function get_language_name_from_extension( $extension, $lookup = array() ) {
+ $extension = strtolower($extension);
+
+ if ( !is_array($lookup) || empty($lookup)) {
+ $lookup = array(
+ '6502acme' => array( 'a', 's', 'asm', 'inc' ),
+ '6502tasm' => array( 'a', 's', 'asm', 'inc' ),
+ '6502kickass' => array( 'a', 's', 'asm', 'inc' ),
+ '68000devpac' => array( 'a', 's', 'asm', 'inc' ),
+ 'abap' => array('abap'),
+ 'actionscript' => array('as'),
+ 'ada' => array('a', 'ada', 'adb', 'ads'),
+ 'apache' => array('conf'),
+ 'asm' => array('ash', 'asm', 'inc'),
+ 'asp' => array('asp'),
+ 'bash' => array('sh'),
+ 'bf' => array('bf'),
+ 'c' => array('c', 'h'),
+ 'c_mac' => array('c', 'h'),
+ 'caddcl' => array(),
+ 'cadlisp' => array(),
+ 'cdfg' => array('cdfg'),
+ 'cobol' => array('cbl'),
+ 'cpp' => array('cpp', 'hpp', 'C', 'H', 'CPP', 'HPP'),
+ 'csharp' => array('cs'),
+ 'css' => array('css'),
+ 'd' => array('d'),
+ 'delphi' => array('dpk', 'dpr', 'pp', 'pas'),
+ 'diff' => array('diff', 'patch'),
+ 'dos' => array('bat', 'cmd'),
+ 'gdb' => array('kcrash', 'crash', 'bt'),
+ 'gettext' => array('po', 'pot'),
+ 'gml' => array('gml'),
+ 'gnuplot' => array('plt'),
+ 'groovy' => array('groovy'),
+ 'haskell' => array('hs'),
+ 'haxe' => array('hx'),
+ 'html4strict' => array('html', 'htm'),
+ 'ini' => array('ini', 'desktop'),
+ 'java' => array('java'),
+ 'javascript' => array('js'),
+ 'klonec' => array('kl1'),
+ 'klonecpp' => array('klx'),
+ 'latex' => array('tex'),
+ 'lisp' => array('lisp'),
+ 'lua' => array('lua'),
+ 'matlab' => array('m'),
+ 'mpasm' => array(),
+ 'mysql' => array('sql'),
+ 'nsis' => array(),
+ 'objc' => array(),
+ 'oobas' => array(),
+ 'oracle8' => array(),
+ 'oracle10' => array(),
+ 'pascal' => array('pas'),
+ 'perl' => array('pl', 'pm'),
+ 'php' => array('php', 'php5', 'phtml', 'phps'),
+ 'povray' => array('pov'),
+ 'providex' => array('pvc', 'pvx'),
+ 'prolog' => array('pl'),
+ 'python' => array('py'),
+ 'qbasic' => array('bi'),
+ 'reg' => array('reg'),
+ 'ruby' => array('rb'),
+ 'sas' => array('sas'),
+ 'scala' => array('scala'),
+ 'scheme' => array('scm'),
+ 'scilab' => array('sci'),
+ 'smalltalk' => array('st'),
+ 'smarty' => array(),
+ 'tcl' => array('tcl'),
+ 'text' => array('txt'),
+ 'vb' => array('bas'),
+ 'vbnet' => array(),
+ 'visualfoxpro' => array(),
+ 'whitespace' => array('ws'),
+ 'xml' => array('xml', 'svg', 'xrc'),
+ 'z80' => array('z80', 'asm', 'inc')
+ );
+ }
+
+ foreach ($lookup as $lang => $extensions) {
+ if (in_array($extension, $extensions)) {
+ return $lang;
+ }
+ }
+
+ return 'text';
+ }
+
+ /**
+ * Given a file name, this method loads its contents in, and attempts
+ * to set the language automatically. An optional lookup table can be
+ * passed for looking up the language name. If not specified a default
+ * table is used
+ *
+ * The language table is in the form
+ * <pre>array(
+ * 'lang_name' => array('extension', 'extension', ...),
+ * 'lang_name' ...
+ * );</pre>
+ *
+ * @param string The filename to load the source from
+ * @param array A lookup array to use instead of the default one
+ * @todo Complete rethink of this and above method
+ * @since 1.0.5
+ */
+ function load_from_file($file_name, $lookup = array()) {
+ if (is_readable($file_name)) {
+ $this->set_source(file_get_contents($file_name));
+ $this->set_language($this->get_language_name_from_extension(substr(strrchr($file_name, '.'), 1), $lookup));
+ } else {
+ $this->error = GESHI_ERROR_FILE_NOT_READABLE;
+ }
+ }
+
+ /**
+ * Adds a keyword to a keyword group for highlighting
+ *
+ * @param int The key of the keyword group to add the keyword to
+ * @param string The word to add to the keyword group
+ * @since 1.0.0
+ */
+ function add_keyword($key, $word) {
+ if (!is_array($this->language_data['KEYWORDS'][$key])) {
+ $this->language_data['KEYWORDS'][$key] = array();
+ }
+ if (!in_array($word, $this->language_data['KEYWORDS'][$key])) {
+ $this->language_data['KEYWORDS'][$key][] = $word;
+
+ //NEW in 1.0.8 don't recompile the whole optimized regexp, simply append it
+ if ($this->parse_cache_built) {
+ $subkey = count($this->language_data['CACHED_KEYWORD_LISTS'][$key]) - 1;
+ $this->language_data['CACHED_KEYWORD_LISTS'][$key][$subkey] .= '|' . preg_quote($word, '/');
+ }
+ }
+ }
+
+ /**
+ * Removes a keyword from a keyword group
+ *
+ * @param int The key of the keyword group to remove the keyword from
+ * @param string The word to remove from the keyword group
+ * @param bool Wether to automatically recompile the optimized regexp list or not.
+ * Note: if you set this to false and @see GeSHi->parse_code() was already called once,
+ * for the current language, you have to manually call @see GeSHi->optimize_keyword_group()
+ * or the removed keyword will stay in cache and still be highlighted! On the other hand
+ * it might be too expensive to recompile the regexp list for every removal if you want to
+ * remove a lot of keywords.
+ * @since 1.0.0
+ */
+ function remove_keyword($key, $word, $recompile = true) {
+ $key_to_remove = array_search($word, $this->language_data['KEYWORDS'][$key]);
+ if ($key_to_remove !== false) {
+ unset($this->language_data['KEYWORDS'][$key][$key_to_remove]);
+
+ //NEW in 1.0.8, optionally recompile keyword group
+ if ($recompile && $this->parse_cache_built) {
+ $this->optimize_keyword_group($key);
+ }
+ }
+ }
+
+ /**
+ * Creates a new keyword group
+ *
+ * @param int The key of the keyword group to create
+ * @param string The styles for the keyword group
+ * @param boolean Whether the keyword group is case sensitive ornot
+ * @param array The words to use for the keyword group
+ * @since 1.0.0
+ */
+ function add_keyword_group($key, $styles, $case_sensitive = true, $words = array()) {
+ $words = (array) $words;
+ if (empty($words)) {
+ // empty word lists mess up highlighting
+ return false;
+ }
+
+ //Add the new keyword group internally
+ $this->language_data['KEYWORDS'][$key] = $words;
+ $this->lexic_permissions['KEYWORDS'][$key] = true;
+ $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive;
+ $this->language_data['STYLES']['KEYWORDS'][$key] = $styles;
+
+ //NEW in 1.0.8, cache keyword regexp
+ if ($this->parse_cache_built) {
+ $this->optimize_keyword_group($key);
+ }
+ }
+
+ /**
+ * Removes a keyword group
+ *
+ * @param int The key of the keyword group to remove
+ * @since 1.0.0
+ */
+ function remove_keyword_group ($key) {
+ //Remove the keyword group internally
+ unset($this->language_data['KEYWORDS'][$key]);
+ unset($this->lexic_permissions['KEYWORDS'][$key]);
+ unset($this->language_data['CASE_SENSITIVE'][$key]);
+ unset($this->language_data['STYLES']['KEYWORDS'][$key]);
+
+ //NEW in 1.0.8
+ unset($this->language_data['CACHED_KEYWORD_LISTS'][$key]);
+ }
+
+ /**
+ * compile optimized regexp list for keyword group
+ *
+ * @param int The key of the keyword group to compile & optimize
+ * @since 1.0.8
+ */
+ function optimize_keyword_group($key) {
+ $this->language_data['CACHED_KEYWORD_LISTS'][$key] =
+ $this->optimize_regexp_list($this->language_data['KEYWORDS'][$key]);
+ $space_as_whitespace = false;
+ if(isset($this->language_data['PARSER_CONTROL'])) {
+ if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'])) {
+ if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['SPACE_AS_WHITESPACE'])) {
+ $space_as_whitespace = $this->language_data['PARSER_CONTROL']['KEYWORDS']['SPACE_AS_WHITESPACE'];
+ }
+ if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE'])) {
+ if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE'])) {
+ $space_as_whitespace = $this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE'];
+ }
+ }
+ }
+ }
+ if($space_as_whitespace) {
+ foreach($this->language_data['CACHED_KEYWORD_LISTS'][$key] as $rxk => $rxv) {
+ $this->language_data['CACHED_KEYWORD_LISTS'][$key][$rxk] =
+ str_replace(" ", "\\s+", $rxv);
+ }
+ }
+ }
+
+ /**
+ * Sets the content of the header block
+ *
+ * @param string The content of the header block
+ * @since 1.0.2
+ */
+ function set_header_content($content) {
+ $this->header_content = $content;
+ }
+
+ /**
+ * Sets the content of the footer block
+ *
+ * @param string The content of the footer block
+ * @since 1.0.2
+ */
+ function set_footer_content($content) {
+ $this->footer_content = $content;
+ }
+
+ /**
+ * Sets the style for the header content
+ *
+ * @param string The style for the header content
+ * @since 1.0.2
+ */
+ function set_header_content_style($style) {
+ $this->header_content_style = $style;
+ }
+
+ /**
+ * Sets the style for the footer content
+ *
+ * @param string The style for the footer content
+ * @since 1.0.2
+ */
+ function set_footer_content_style($style) {
+ $this->footer_content_style = $style;
+ }
+
+ /**
+ * Sets whether to force a surrounding block around
+ * the highlighted code or not
+ *
+ * @param boolean Tells whether to enable or disable this feature
+ * @since 1.0.7.20
+ */
+ function enable_inner_code_block($flag) {
+ $this->force_code_block = (bool)$flag;
+ }
+
+ /**
+ * Sets the base URL to be used for keywords
+ *
+ * @param int The key of the keyword group to set the URL for
+ * @param string The URL to set for the group. If {FNAME} is in
+ * the url somewhere, it is replaced by the keyword
+ * that the URL is being made for
+ * @since 1.0.2
+ */
+ function set_url_for_keyword_group($group, $url) {
+ $this->language_data['URLS'][$group] = $url;
+ }
+
+ /**
+ * Sets styles for links in code
+ *
+ * @param int A constant that specifies what state the style is being
+ * set for - e.g. :hover or :visited
+ * @param string The styles to use for that state
+ * @since 1.0.2
+ */
+ function set_link_styles($type, $styles) {
+ $this->link_styles[$type] = $styles;
+ }
+
+ /**
+ * Sets the target for links in code
+ *
+ * @param string The target for links in the code, e.g. _blank
+ * @since 1.0.3
+ */
+ function set_link_target($target) {
+ if (!$target) {
+ $this->link_target = '';
+ } else {
+ $this->link_target = ' target="' . $target . '"';
+ }
+ }
+
+ /**
+ * Sets styles for important parts of the code
+ *
+ * @param string The styles to use on important parts of the code
+ * @since 1.0.2
+ */
+ function set_important_styles($styles) {
+ $this->important_styles = $styles;
+ }
+
+ /**
+ * Sets whether context-important blocks are highlighted
+ *
+ * @param boolean Tells whether to enable or disable highlighting of important blocks
+ * @todo REMOVE THIS SHIZ FROM GESHI!
+ * @deprecated
+ * @since 1.0.2
+ */
+ function enable_important_blocks($flag) {
+ $this->enable_important_blocks = ( $flag ) ? true : false;
+ }
+
+ /**
+ * Whether CSS IDs should be added to each line
+ *
+ * @param boolean If true, IDs will be added to each line.
+ * @since 1.0.2
+ */
+ function enable_ids($flag = true) {
+ $this->add_ids = ($flag) ? true : false;
+ }
+
+ /**
+ * Specifies which lines to highlight extra
+ *
+ * The extra style parameter was added in 1.0.7.21.
+ *
+ * @param mixed An array of line numbers to highlight, or just a line
+ * number on its own.
+ * @param string A string specifying the style to use for this line.
+ * If null is specified, the default style is used.
+ * If false is specified, the line will be removed from
+ * special highlighting
+ * @since 1.0.2
+ * @todo Some data replication here that could be cut down on
+ */
+ function highlight_lines_extra($lines, $style = null) {
+ if (is_array($lines)) {
+ //Split up the job using single lines at a time
+ foreach ($lines as $line) {
+ $this->highlight_lines_extra($line, $style);
+ }
+ } else {
+ //Mark the line as being highlighted specially
+ $lines = intval($lines);
+ $this->highlight_extra_lines[$lines] = $lines;
+
+ //Decide on which style to use
+ if ($style === null) { //Check if we should use default style
+ unset($this->highlight_extra_lines_styles[$lines]);
+ } elseif ($style === false) { //Check if to remove this line
+ unset($this->highlight_extra_lines[$lines]);
+ unset($this->highlight_extra_lines_styles[$lines]);
+ } else {
+ $this->highlight_extra_lines_styles[$lines] = $style;
+ }
+ }
+ }
+
+ /**
+ * Sets the style for extra-highlighted lines
+ *
+ * @param string The style for extra-highlighted lines
+ * @since 1.0.2
+ */
+ function set_highlight_lines_extra_style($styles) {
+ $this->highlight_extra_lines_style = $styles;
+ }
+
+ /**
+ * Sets the line-ending
+ *
+ * @param string The new line-ending
+ * @since 1.0.2
+ */
+ function set_line_ending($line_ending) {
+ $this->line_ending = (string)$line_ending;
+ }
+
+ /**
+ * Sets what number line numbers should start at. Should
+ * be a positive integer, and will be converted to one.
+ *
+ * <b>Warning:</b> Using this method will add the "start"
+ * attribute to the &lt;ol&gt; that is used for line numbering.
+ * This is <b>not</b> valid XHTML strict, so if that's what you
+ * care about then don't use this method. Firefox is getting
+ * support for the CSS method of doing this in 1.1 and Opera
+ * has support for the CSS method, but (of course) IE doesn't
+ * so it's not worth doing it the CSS way yet.
+ *
+ * @param int The number to start line numbers at
+ * @since 1.0.2
+ */
+ function start_line_numbers_at($number) {
+ $this->line_numbers_start = abs(intval($number));
+ }
+
+ /**
+ * Sets the encoding used for htmlspecialchars(), for international
+ * support.
+ *
+ * NOTE: This is not needed for now because htmlspecialchars() is not
+ * being used (it has a security hole in PHP4 that has not been patched).
+ * Maybe in a future version it may make a return for speed reasons, but
+ * I doubt it.
+ *
+ * @param string The encoding to use for the source
+ * @since 1.0.3
+ */
+ function set_encoding($encoding) {
+ if ($encoding) {
+ $this->encoding = strtolower($encoding);
+ }
+ }
+
+ /**
+ * Turns linking of keywords on or off.
+ *
+ * @param boolean If true, links will be added to keywords
+ * @since 1.0.2
+ */
+ function enable_keyword_links($enable = true) {
+ $this->keyword_links = (bool) $enable;
+ }
+
+ /**
+ * Setup caches needed for styling. This is automatically called in
+ * parse_code() and get_stylesheet() when appropriate. This function helps
+ * stylesheet generators as they rely on some style information being
+ * preprocessed
+ *
+ * @since 1.0.8
+ * @access private
+ */
+ function build_style_cache() {
+ //Build the style cache needed to highlight numbers appropriate
+ if($this->lexic_permissions['NUMBERS']) {
+ //First check what way highlighting information for numbers are given
+ if(!isset($this->language_data['NUMBERS'])) {
+ $this->language_data['NUMBERS'] = 0;
+ }
+
+ if(is_array($this->language_data['NUMBERS'])) {
+ $this->language_data['NUMBERS_CACHE'] = $this->language_data['NUMBERS'];
+ } else {
+ $this->language_data['NUMBERS_CACHE'] = array();
+ if(!$this->language_data['NUMBERS']) {
+ $this->language_data['NUMBERS'] =
+ GESHI_NUMBER_INT_BASIC |
+ GESHI_NUMBER_FLT_NONSCI;
+ }
+
+ for($i = 0, $j = $this->language_data['NUMBERS']; $j > 0; ++$i, $j>>=1) {
+ //Rearrange style indices if required ...
+ if(isset($this->language_data['STYLES']['NUMBERS'][1<<$i])) {
+ $this->language_data['STYLES']['NUMBERS'][$i] =
+ $this->language_data['STYLES']['NUMBERS'][1<<$i];
+ unset($this->language_data['STYLES']['NUMBERS'][1<<$i]);
+ }
+
+ //Check if this bit is set for highlighting
+ if($j&1) {
+ //So this bit is set ...
+ //Check if it belongs to group 0 or the actual stylegroup
+ if(isset($this->language_data['STYLES']['NUMBERS'][$i])) {
+ $this->language_data['NUMBERS_CACHE'][$i] = 1 << $i;
+ } else {
+ if(!isset($this->language_data['NUMBERS_CACHE'][0])) {
+ $this->language_data['NUMBERS_CACHE'][0] = 0;
+ }
+ $this->language_data['NUMBERS_CACHE'][0] |= 1 << $i;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Setup caches needed for parsing. This is automatically called in parse_code() when appropriate.
+ * This function makes stylesheet generators much faster as they do not need these caches.
+ *
+ * @since 1.0.8
+ * @access private
+ */
+ function build_parse_cache() {
+ // cache symbol regexp
+ //As this is a costy operation, we avoid doing it for multiple groups ...
+ //Instead we perform it for all symbols at once.
+ //
+ //For this to work, we need to reorganize the data arrays.
+ if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
+ $this->language_data['MULTIPLE_SYMBOL_GROUPS'] = count($this->language_data['STYLES']['SYMBOLS']) > 1;
+
+ $this->language_data['SYMBOL_DATA'] = array();
+ $symbol_preg_multi = array(); // multi char symbols
+ $symbol_preg_single = array(); // single char symbols
+ foreach ($this->language_data['SYMBOLS'] as $key => $symbols) {
+ if (is_array($symbols)) {
+ foreach ($symbols as $sym) {
+ $sym = $this->hsc($sym);
+ if (!isset($this->language_data['SYMBOL_DATA'][$sym])) {
+ $this->language_data['SYMBOL_DATA'][$sym] = $key;
+ if (isset($sym[1])) { // multiple chars
+ $symbol_preg_multi[] = preg_quote($sym, '/');
+ } else { // single char
+ if ($sym == '-') {
+ // don't trigger range out of order error
+ $symbol_preg_single[] = '\-';
+ } else {
+ $symbol_preg_single[] = preg_quote($sym, '/');
+ }
+ }
+ }
+ }
+ } else {
+ $symbols = $this->hsc($symbols);
+ if (!isset($this->language_data['SYMBOL_DATA'][$symbols])) {
+ $this->language_data['SYMBOL_DATA'][$symbols] = 0;
+ if (isset($symbols[1])) { // multiple chars
+ $symbol_preg_multi[] = preg_quote($symbols, '/');
+ } elseif ($symbols == '-') {
+ // don't trigger range out of order error
+ $symbol_preg_single[] = '\-';
+ } else { // single char
+ $symbol_preg_single[] = preg_quote($symbols, '/');
+ }
+ }
+ }
+ }
+
+ //Now we have an array with each possible symbol as the key and the style as the actual data.
+ //This way we can set the correct style just the moment we highlight ...
+ //
+ //Now we need to rewrite our array to get a search string that
+ $symbol_preg = array();
+ if (!empty($symbol_preg_multi)) {
+ rsort($symbol_preg_multi);
+ $symbol_preg[] = implode('|', $symbol_preg_multi);
+ }
+ if (!empty($symbol_preg_single)) {
+ rsort($symbol_preg_single);
+ $symbol_preg[] = '[' . implode('', $symbol_preg_single) . ']';
+ }
+ $this->language_data['SYMBOL_SEARCH'] = implode("|", $symbol_preg);
+ }
+
+ // cache optimized regexp for keyword matching
+ // remove old cache
+ $this->language_data['CACHED_KEYWORD_LISTS'] = array();
+ foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
+ if (!isset($this->lexic_permissions['KEYWORDS'][$key]) ||
+ $this->lexic_permissions['KEYWORDS'][$key]) {
+ $this->optimize_keyword_group($key);
+ }
+ }
+
+ // brackets
+ if ($this->lexic_permissions['BRACKETS']) {
+ $this->language_data['CACHE_BRACKET_MATCH'] = array('[', ']', '(', ')', '{', '}');
+ if (!$this->use_classes && isset($this->language_data['STYLES']['BRACKETS'][0])) {
+ $this->language_data['CACHE_BRACKET_REPLACE'] = array(
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#91;|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#93;|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#40;|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#41;|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#123;|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#125;|>',
+ );
+ }
+ else {
+ $this->language_data['CACHE_BRACKET_REPLACE'] = array(
+ '<| class="br0">&#91;|>',
+ '<| class="br0">&#93;|>',
+ '<| class="br0">&#40;|>',
+ '<| class="br0">&#41;|>',
+ '<| class="br0">&#123;|>',
+ '<| class="br0">&#125;|>',
+ );
+ }
+ }
+
+ //Build the parse cache needed to highlight numbers appropriate
+ if($this->lexic_permissions['NUMBERS']) {
+ //Check if the style rearrangements have been processed ...
+ //This also does some preprocessing to check which style groups are useable ...
+ if(!isset($this->language_data['NUMBERS_CACHE'])) {
+ $this->build_style_cache();
+ }
+
+ //Number format specification
+ //All this formats are matched case-insensitively!
+ static $numbers_format = array(
+ GESHI_NUMBER_INT_BASIC =>
+ '(?:(?<![0-9a-z_\.%$@])|(?<=\.\.))(?<![\d\.]e[+\-])([1-9]\d*?|0)(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_INT_CSTYLE =>
+ '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])([1-9]\d*?|0)l(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_BIN_SUFFIX =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[01]+?[bB](?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_BIN_PREFIX_PERCENT =>
+ '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])%[01]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_BIN_PREFIX_0B =>
+ '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])0b[01]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_OCT_PREFIX =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0[0-7]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_OCT_PREFIX_0O =>
+ '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])0o[0-7]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_OCT_PREFIX_AT =>
+ '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])\@[0-7]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_OCT_SUFFIX =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[0-7]+?o(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_HEX_PREFIX =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0x[0-9a-fA-F]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_HEX_PREFIX_DOLLAR =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\$[0-9a-fA-F]+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_HEX_SUFFIX =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d[0-9a-fA-F]*?[hH](?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_FLT_NONSCI =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d+?\.\d+?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_FLT_NONSCI_F =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)f(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_FLT_SCI_SHORT =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\.\d+?(?:e[+\-]?\d+?)?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)',
+ GESHI_NUMBER_FLT_SCI_ZERO =>
+ '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)(?:e[+\-]?\d+?)?(?![0-9a-z]|\.(?:[eE][+\-]?)?\d)'
+ );
+
+ //At this step we have an associative array with flag groups for a
+ //specific style or an string denoting a regexp given its index.
+ $this->language_data['NUMBERS_RXCACHE'] = array();
+ foreach($this->language_data['NUMBERS_CACHE'] as $key => $rxdata) {
+ if(is_string($rxdata)) {
+ $regexp = $rxdata;
+ } else {
+ //This is a bitfield of number flags to highlight:
+ //Build an array, implode them together and make this the actual RX
+ $rxuse = array();
+ for($i = 1; $i <= $rxdata; $i<<=1) {
+ if($rxdata & $i) {
+ $rxuse[] = $numbers_format[$i];
+ }
+ }
+ $regexp = implode("|", $rxuse);
+ }
+
+ $this->language_data['NUMBERS_RXCACHE'][$key] =
+ "/(?<!<\|\/)(?<!<\|!REG3XP)(?<!<\|\/NUM!)(?<!\d\/>)($regexp)(?!(?:<DOT>|(?>[^\<]))+>)(?![^<]*>)(?!\|>)(?!\/>)/i"; //
+ }
+
+ if(!isset($this->language_data['PARSER_CONTROL']['NUMBERS']['PRECHECK_RX'])) {
+ $this->language_data['PARSER_CONTROL']['NUMBERS']['PRECHECK_RX'] = '#\d#';
+ }
+ }
+
+ $this->parse_cache_built = true;
+ }
+
+ /**
+ * Returns the code in $this->source, highlighted and surrounded by the
+ * nessecary HTML.
+ *
+ * This should only be called ONCE, cos it's SLOW! If you want to highlight
+ * the same source multiple times, you're better off doing a whole lot of
+ * str_replaces to replace the &lt;span&gt;s
+ *
+ * @since 1.0.0
+ */
+ function parse_code () {
+ // Start the timer
+ $start_time = microtime();
+
+ // Replace all newlines to a common form.
+ $code = str_replace("\r\n", "\n", $this->source);
+ $code = str_replace("\r", "\n", $code);
+
+ // Firstly, if there is an error, we won't highlight
+ if ($this->error) {
+ //Escape the source for output
+ $result = $this->hsc($this->source);
+
+ //This fix is related to SF#1923020, but has to be applied regardless of
+ //actually highlighting symbols.
+ $result = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $result);
+
+ // Timing is irrelevant
+ $this->set_time($start_time, $start_time);
+ $this->finalise($result);
+ return $result;
+ }
+
+ // make sure the parse cache is up2date
+ if (!$this->parse_cache_built) {
+ $this->build_parse_cache();
+ }
+
+ // Initialise various stuff
+ $length = strlen($code);
+ $COMMENT_MATCHED = false;
+ $stuff_to_parse = '';
+ $endresult = '';
+
+ // "Important" selections are handled like multiline comments
+ // @todo GET RID OF THIS SHIZ
+ if ($this->enable_important_blocks) {
+ $this->language_data['COMMENT_MULTI'][GESHI_START_IMPORTANT] = GESHI_END_IMPORTANT;
+ }
+
+ if ($this->strict_mode) {
+ // Break the source into bits. Each bit will be a portion of the code
+ // within script delimiters - for example, HTML between < and >
+ $k = 0;
+ $parts = array();
+ $matches = array();
+ $next_match_pointer = null;
+ // we use a copy to unset delimiters on demand (when they are not found)
+ $delim_copy = $this->language_data['SCRIPT_DELIMITERS'];
+ $i = 0;
+ while ($i < $length) {
+ $next_match_pos = $length + 1; // never true
+ foreach ($delim_copy as $dk => $delimiters) {
+ if(is_array($delimiters)) {
+ foreach ($delimiters as $open => $close) {
+ // make sure the cache is setup properly
+ if (!isset($matches[$dk][$open])) {
+ $matches[$dk][$open] = array(
+ 'next_match' => -1,
+ 'dk' => $dk,
+
+ 'open' => $open, // needed for grouping of adjacent code blocks (see below)
+ 'open_strlen' => strlen($open),
+
+ 'close' => $close,
+ 'close_strlen' => strlen($close),
+ );
+ }
+ // Get the next little bit for this opening string
+ if ($matches[$dk][$open]['next_match'] < $i) {
+ // only find the next pos if it was not already cached
+ $open_pos = strpos($code, $open, $i);
+ if ($open_pos === false) {
+ // no match for this delimiter ever
+ unset($delim_copy[$dk][$open]);
+ continue;
+ }
+ $matches[$dk][$open]['next_match'] = $open_pos;
+ }
+ if ($matches[$dk][$open]['next_match'] < $next_match_pos) {
+ //So we got a new match, update the close_pos
+ $matches[$dk][$open]['close_pos'] =
+ strpos($code, $close, $matches[$dk][$open]['next_match']+1);
+
+ $next_match_pointer =& $matches[$dk][$open];
+ $next_match_pos = $matches[$dk][$open]['next_match'];
+ }
+ }
+ } else {
+ //So we should match an RegExp as Strict Block ...
+ /**
+ * The value in $delimiters is expected to be an RegExp
+ * containing exactly 2 matching groups:
+ * - Group 1 is the opener
+ * - Group 2 is the closer
+ */
+ if(!GESHI_PHP_PRE_433 && //Needs proper rewrite to work with PHP >=4.3.0; 4.3.3 is guaranteed to work.
+ preg_match($delimiters, $code, $matches_rx, PREG_OFFSET_CAPTURE, $i)) {
+ //We got a match ...
+ if(isset($matches_rx['start']) && isset($matches_rx['end']))
+ {
+ $matches[$dk] = array(
+ 'next_match' => $matches_rx['start'][1],
+ 'dk' => $dk,
+
+ 'close_strlen' => strlen($matches_rx['end'][0]),
+ 'close_pos' => $matches_rx['end'][1],
+ );
+ } else {
+ $matches[$dk] = array(
+ 'next_match' => $matches_rx[1][1],
+ 'dk' => $dk,
+
+ 'close_strlen' => strlen($matches_rx[2][0]),
+ 'close_pos' => $matches_rx[2][1],
+ );
+ }
+ } else {
+ // no match for this delimiter ever
+ unset($delim_copy[$dk]);
+ continue;
+ }
+
+ if ($matches[$dk]['next_match'] <= $next_match_pos) {
+ $next_match_pointer =& $matches[$dk];
+ $next_match_pos = $matches[$dk]['next_match'];
+ }
+ }
+ }
+
+ // non-highlightable text
+ $parts[$k] = array(
+ 1 => substr($code, $i, $next_match_pos - $i)
+ );
+ ++$k;
+
+ if ($next_match_pos > $length) {
+ // out of bounds means no next match was found
+ break;
+ }
+
+ // highlightable code
+ $parts[$k][0] = $next_match_pointer['dk'];
+
+ //Only combine for non-rx script blocks
+ if(is_array($delim_copy[$next_match_pointer['dk']])) {
+ // group adjacent script blocks, e.g. <foobar><asdf> should be one block, not three!
+ $i = $next_match_pos + $next_match_pointer['open_strlen'];
+ while (true) {
+ $close_pos = strpos($code, $next_match_pointer['close'], $i);
+ if ($close_pos == false) {
+ break;
+ }
+ $i = $close_pos + $next_match_pointer['close_strlen'];
+ if ($i == $length) {
+ break;
+ }
+ if ($code[$i] == $next_match_pointer['open'][0] && ($next_match_pointer['open_strlen'] == 1 ||
+ substr($code, $i, $next_match_pointer['open_strlen']) == $next_match_pointer['open'])) {
+ // merge adjacent but make sure we don't merge things like <tag><!-- comment -->
+ foreach ($matches as $submatches) {
+ foreach ($submatches as $match) {
+ if ($match['next_match'] == $i) {
+ // a different block already matches here!
+ break 3;
+ }
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ } else {
+ $close_pos = $next_match_pointer['close_pos'] + $next_match_pointer['close_strlen'];
+ $i = $close_pos;
+ }
+
+ if ($close_pos === false) {
+ // no closing delimiter found!
+ $parts[$k][1] = substr($code, $next_match_pos);
+ ++$k;
+ break;
+ } else {
+ $parts[$k][1] = substr($code, $next_match_pos, $i - $next_match_pos);
+ ++$k;
+ }
+ }
+ unset($delim_copy, $next_match_pointer, $next_match_pos, $matches);
+ $num_parts = $k;
+
+ if ($num_parts == 1 && $this->strict_mode == GESHI_MAYBE) {
+ // when we have only one part, we don't have anything to highlight at all.
+ // if we have a "maybe" strict language, this should be handled as highlightable code
+ $parts = array(
+ 0 => array(
+ 0 => '',
+ 1 => ''
+ ),
+ 1 => array(
+ 0 => null,
+ 1 => $parts[0][1]
+ )
+ );
+ $num_parts = 2;
+ }
+
+ } else {
+ // Not strict mode - simply dump the source into
+ // the array at index 1 (the first highlightable block)
+ $parts = array(
+ 0 => array(
+ 0 => '',
+ 1 => ''
+ ),
+ 1 => array(
+ 0 => null,
+ 1 => $code
+ )
+ );
+ $num_parts = 2;
+ }
+
+ //Unset variables we won't need any longer
+ unset($code);
+
+ //Preload some repeatedly used values regarding hardquotes ...
+ $hq = isset($this->language_data['HARDQUOTE']) ? $this->language_data['HARDQUOTE'][0] : false;
+ $hq_strlen = strlen($hq);
+
+ //Preload if line numbers are to be generated afterwards
+ //Added a check if line breaks should be forced even without line numbers, fixes SF#1727398
+ $check_linenumbers = $this->line_numbers != GESHI_NO_LINE_NUMBERS ||
+ !empty($this->highlight_extra_lines) || !$this->allow_multiline_span;
+
+ //preload the escape char for faster checking ...
+ $escaped_escape_char = $this->hsc($this->language_data['ESCAPE_CHAR']);
+
+ // this is used for single-line comments
+ $sc_disallowed_before = "";
+ $sc_disallowed_after = "";
+
+ if (isset($this->language_data['PARSER_CONTROL'])) {
+ if (isset($this->language_data['PARSER_CONTROL']['COMMENTS'])) {
+ if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'])) {
+ $sc_disallowed_before = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'];
+ }
+ if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'])) {
+ $sc_disallowed_after = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'];
+ }
+ }
+ }
+
+ //Fix for SF#1932083: Multichar Quotemarks unsupported
+ $is_string_starter = array();
+ if ($this->lexic_permissions['STRINGS']) {
+ foreach ($this->language_data['QUOTEMARKS'] as $quotemark) {
+ if (!isset($is_string_starter[$quotemark[0]])) {
+ $is_string_starter[$quotemark[0]] = (string)$quotemark;
+ } elseif (is_string($is_string_starter[$quotemark[0]])) {
+ $is_string_starter[$quotemark[0]] = array(
+ $is_string_starter[$quotemark[0]],
+ $quotemark);
+ } else {
+ $is_string_starter[$quotemark[0]][] = $quotemark;
+ }
+ }
+ }
+
+ // Now we go through each part. We know that even-indexed parts are
+ // code that shouldn't be highlighted, and odd-indexed parts should
+ // be highlighted
+ for ($key = 0; $key < $num_parts; ++$key) {
+ $STRICTATTRS = '';
+
+ // If this block should be highlighted...
+ if (!($key & 1)) {
+ // Else not a block to highlight
+ $endresult .= $this->hsc($parts[$key][1]);
+ unset($parts[$key]);
+ continue;
+ }
+
+ $result = '';
+ $part = $parts[$key][1];
+
+ $highlight_part = true;
+ if ($this->strict_mode && !is_null($parts[$key][0])) {
+ // get the class key for this block of code
+ $script_key = $parts[$key][0];
+ $highlight_part = $this->language_data['HIGHLIGHT_STRICT_BLOCK'][$script_key];
+ if ($this->language_data['STYLES']['SCRIPT'][$script_key] != '' &&
+ $this->lexic_permissions['SCRIPT']) {
+ // Add a span element around the source to
+ // highlight the overall source block
+ if (!$this->use_classes &&
+ $this->language_data['STYLES']['SCRIPT'][$script_key] != '') {
+ $attributes = ' style="' . $this->language_data['STYLES']['SCRIPT'][$script_key] . '"';
+ } else {
+ $attributes = ' class="sc' . $script_key . '"';
+ }
+ $result .= "<span$attributes>";
+ $STRICTATTRS = $attributes;
+ }
+ }
+
+ if ($highlight_part) {
+ // Now, highlight the code in this block. This code
+ // is really the engine of GeSHi (along with the method
+ // parse_non_string_part).
+
+ // cache comment regexps incrementally
+ $next_comment_regexp_key = '';
+ $next_comment_regexp_pos = -1;
+ $next_comment_multi_pos = -1;
+ $next_comment_single_pos = -1;
+ $comment_regexp_cache_per_key = array();
+ $comment_multi_cache_per_key = array();
+ $comment_single_cache_per_key = array();
+ $next_open_comment_multi = '';
+ $next_comment_single_key = '';
+ $escape_regexp_cache_per_key = array();
+ $next_escape_regexp_key = '';
+ $next_escape_regexp_pos = -1;
+
+ $length = strlen($part);
+ for ($i = 0; $i < $length; ++$i) {
+ // Get the next char
+ $char = $part[$i];
+ $char_len = 1;
+
+ // update regexp comment cache if needed
+ if (isset($this->language_data['COMMENT_REGEXP']) && $next_comment_regexp_pos < $i) {
+ $next_comment_regexp_pos = $length;
+ foreach ($this->language_data['COMMENT_REGEXP'] as $comment_key => $regexp) {
+ $match_i = false;
+ if (isset($comment_regexp_cache_per_key[$comment_key]) &&
+ ($comment_regexp_cache_per_key[$comment_key]['pos'] >= $i ||
+ $comment_regexp_cache_per_key[$comment_key]['pos'] === false)) {
+ // we have already matched something
+ if ($comment_regexp_cache_per_key[$comment_key]['pos'] === false) {
+ // this comment is never matched
+ continue;
+ }
+ $match_i = $comment_regexp_cache_per_key[$comment_key]['pos'];
+ } elseif (
+ //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
+ (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $i), $match, PREG_OFFSET_CAPTURE)) ||
+ (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $i))
+ ) {
+ $match_i = $match[0][1];
+ if (GESHI_PHP_PRE_433) {
+ $match_i += $i;
+ }
+
+ $comment_regexp_cache_per_key[$comment_key] = array(
+ 'key' => $comment_key,
+ 'length' => strlen($match[0][0]),
+ 'pos' => $match_i
+ );
+ } else {
+ $comment_regexp_cache_per_key[$comment_key]['pos'] = false;
+ continue;
+ }
+
+ if ($match_i !== false && $match_i < $next_comment_regexp_pos) {
+ $next_comment_regexp_pos = $match_i;
+ $next_comment_regexp_key = $comment_key;
+ if ($match_i === $i) {
+ break;
+ }
+ }
+ }
+ }
+
+ $string_started = false;
+
+ if (isset($is_string_starter[$char])) {
+ // Possibly the start of a new string ...
+
+ //Check which starter it was ...
+ //Fix for SF#1932083: Multichar Quotemarks unsupported
+ if (is_array($is_string_starter[$char])) {
+ $char_new = '';
+ foreach ($is_string_starter[$char] as $testchar) {
+ if ($testchar === substr($part, $i, strlen($testchar)) &&
+ strlen($testchar) > strlen($char_new)) {
+ $char_new = $testchar;
+ $string_started = true;
+ }
+ }
+ if ($string_started) {
+ $char = $char_new;
+ }
+ } else {
+ $testchar = $is_string_starter[$char];
+ if ($testchar === substr($part, $i, strlen($testchar))) {
+ $char = $testchar;
+ $string_started = true;
+ }
+ }
+ $char_len = strlen($char);
+ }
+
+ if ($string_started && ($i != $next_comment_regexp_pos)) {
+ // Hand out the correct style information for this string
+ $string_key = array_search($char, $this->language_data['QUOTEMARKS']);
+ if (!isset($this->language_data['STYLES']['STRINGS'][$string_key]) ||
+ !isset($this->language_data['STYLES']['ESCAPE_CHAR'][$string_key])) {
+ $string_key = 0;
+ }
+
+ // parse the stuff before this
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+
+ if (!$this->use_classes) {
+ $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][$string_key] . '"';
+ } else {
+ $string_attributes = ' class="st'.$string_key.'"';
+ }
+
+ // now handle the string
+ $string = "<span$string_attributes>" . GeSHi::hsc($char);
+ $start = $i + $char_len;
+ $string_open = true;
+
+ if(empty($this->language_data['ESCAPE_REGEXP'])) {
+ $next_escape_regexp_pos = $length;
+ }
+
+ do {
+ //Get the regular ending pos ...
+ $close_pos = strpos($part, $char, $start);
+ if(false === $close_pos) {
+ $close_pos = $length;
+ }
+
+ if($this->lexic_permissions['ESCAPE_CHAR']) {
+ // update escape regexp cache if needed
+ if (isset($this->language_data['ESCAPE_REGEXP']) && $next_escape_regexp_pos < $start) {
+ $next_escape_regexp_pos = $length;
+ foreach ($this->language_data['ESCAPE_REGEXP'] as $escape_key => $regexp) {
+ $match_i = false;
+ if (isset($escape_regexp_cache_per_key[$escape_key]) &&
+ ($escape_regexp_cache_per_key[$escape_key]['pos'] >= $start ||
+ $escape_regexp_cache_per_key[$escape_key]['pos'] === false)) {
+ // we have already matched something
+ if ($escape_regexp_cache_per_key[$escape_key]['pos'] === false) {
+ // this comment is never matched
+ continue;
+ }
+ $match_i = $escape_regexp_cache_per_key[$escape_key]['pos'];
+ } elseif (
+ //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
+ (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $start), $match, PREG_OFFSET_CAPTURE)) ||
+ (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $start))
+ ) {
+ $match_i = $match[0][1];
+ if (GESHI_PHP_PRE_433) {
+ $match_i += $start;
+ }
+
+ $escape_regexp_cache_per_key[$escape_key] = array(
+ 'key' => $escape_key,
+ 'length' => strlen($match[0][0]),
+ 'pos' => $match_i
+ );
+ } else {
+ $escape_regexp_cache_per_key[$escape_key]['pos'] = false;
+ continue;
+ }
+
+ if ($match_i !== false && $match_i < $next_escape_regexp_pos) {
+ $next_escape_regexp_pos = $match_i;
+ $next_escape_regexp_key = $escape_key;
+ if ($match_i === $start) {
+ break;
+ }
+ }
+ }
+ }
+
+ //Find the next simple escape position
+ if('' != $this->language_data['ESCAPE_CHAR']) {
+ $simple_escape = strpos($part, $this->language_data['ESCAPE_CHAR'], $start);
+ if(false === $simple_escape) {
+ $simple_escape = $length;
+ }
+ } else {
+ $simple_escape = $length;
+ }
+ } else {
+ $next_escape_regexp_pos = $length;
+ $simple_escape = $length;
+ }
+
+ if($simple_escape < $next_escape_regexp_pos &&
+ $simple_escape < $length &&
+ $simple_escape < $close_pos) {
+ //The nexxt escape sequence is a simple one ...
+ $es_pos = $simple_escape;
+
+ //Add the stuff not in the string yet ...
+ $string .= $this->hsc(substr($part, $start, $es_pos - $start));
+
+ //Get the style for this escaped char ...
+ if (!$this->use_classes) {
+ $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][0] . '"';
+ } else {
+ $escape_char_attributes = ' class="es0"';
+ }
+
+ //Add the style for the escape char ...
+ $string .= "<span$escape_char_attributes>" .
+ GeSHi::hsc($this->language_data['ESCAPE_CHAR']);
+
+ //Get the byte AFTER the ESCAPE_CHAR we just found
+ $es_char = $part[$es_pos + 1];
+ if ($es_char == "\n") {
+ // don't put a newline around newlines
+ $string .= "</span>\n";
+ $start = $es_pos + 2;
+ } elseif (ord($es_char) >= 128) {
+ //This is an non-ASCII char (UTF8 or single byte)
+ //This code tries to work around SF#2037598 ...
+ if(function_exists('mb_substr')) {
+ $es_char_m = mb_substr(substr($part, $es_pos+1, 16), 0, 1, $this->encoding);
+ $string .= $es_char_m . '</span>';
+ } elseif (!GESHI_PHP_PRE_433 && 'utf-8' == $this->encoding) {
+ if(preg_match("/[\xC2-\xDF][\x80-\xBF]".
+ "|\xE0[\xA0-\xBF][\x80-\xBF]".
+ "|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}".
+ "|\xED[\x80-\x9F][\x80-\xBF]".
+ "|\xF0[\x90-\xBF][\x80-\xBF]{2}".
+ "|[\xF1-\xF3][\x80-\xBF]{3}".
+ "|\xF4[\x80-\x8F][\x80-\xBF]{2}/s",
+ $part, $es_char_m, null, $es_pos + 1)) {
+ $es_char_m = $es_char_m[0];
+ } else {
+ $es_char_m = $es_char;
+ }
+ $string .= $this->hsc($es_char_m) . '</span>';
+ } else {
+ $es_char_m = $this->hsc($es_char);
+ }
+ $start = $es_pos + strlen($es_char_m) + 1;
+ } else {
+ $string .= $this->hsc($es_char) . '</span>';
+ $start = $es_pos + 2;
+ }
+ } elseif ($next_escape_regexp_pos < $length &&
+ $next_escape_regexp_pos < $close_pos) {
+ $es_pos = $next_escape_regexp_pos;
+ //Add the stuff not in the string yet ...
+ $string .= $this->hsc(substr($part, $start, $es_pos - $start));
+
+ //Get the key and length of this match ...
+ $escape = $escape_regexp_cache_per_key[$next_escape_regexp_key];
+ $escape_str = substr($part, $es_pos, $escape['length']);
+ $escape_key = $escape['key'];
+
+ //Get the style for this escaped char ...
+ if (!$this->use_classes) {
+ $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][$escape_key] . '"';
+ } else {
+ $escape_char_attributes = ' class="es' . $escape_key . '"';
+ }
+
+ //Add the style for the escape char ...
+ $string .= "<span$escape_char_attributes>" .
+ $this->hsc($escape_str) . '</span>';
+
+ $start = $es_pos + $escape['length'];
+ } else {
+ //Copy the remainder of the string ...
+ $string .= $this->hsc(substr($part, $start, $close_pos - $start + $char_len)) . '</span>';
+ $start = $close_pos + $char_len;
+ $string_open = false;
+ }
+ } while($string_open);
+
+ if ($check_linenumbers) {
+ // Are line numbers used? If, we should end the string before
+ // the newline and begin it again (so when <li>s are put in the source
+ // remains XHTML compliant)
+ // note to self: This opens up possibility of config files specifying
+ // that languages can/cannot have multiline strings???
+ $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
+ }
+
+ $result .= $string;
+ $string = '';
+ $i = $start - 1;
+ continue;
+ } elseif ($this->lexic_permissions['STRINGS'] && $hq && $hq[0] == $char &&
+ substr($part, $i, $hq_strlen) == $hq && ($i != $next_comment_regexp_pos)) {
+ // The start of a hard quoted string
+ if (!$this->use_classes) {
+ $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS']['HARD'] . '"';
+ $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR']['HARD'] . '"';
+ } else {
+ $string_attributes = ' class="st_h"';
+ $escape_char_attributes = ' class="es_h"';
+ }
+ // parse the stuff before this
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+
+ // now handle the string
+ $string = '';
+
+ // look for closing quote
+ $start = $i + $hq_strlen;
+ while ($close_pos = strpos($part, $this->language_data['HARDQUOTE'][1], $start)) {
+ $start = $close_pos + 1;
+ if ($this->lexic_permissions['ESCAPE_CHAR'] && $part[$close_pos - 1] == $this->language_data['HARDCHAR'] &&
+ (($i + $hq_strlen) != ($close_pos))) { //Support empty string for HQ escapes if Starter = Escape
+ // make sure this quote is not escaped
+ foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
+ if (substr($part, $close_pos - 1, strlen($hardescape)) == $hardescape) {
+ // check wether this quote is escaped or if it is something like '\\'
+ $escape_char_pos = $close_pos - 1;
+ while ($escape_char_pos > 0
+ && $part[$escape_char_pos - 1] == $this->language_data['HARDCHAR']) {
+ --$escape_char_pos;
+ }
+ if (($close_pos - $escape_char_pos) & 1) {
+ // uneven number of escape chars => this quote is escaped
+ continue 2;
+ }
+ }
+ }
+ }
+
+ // found closing quote
+ break;
+ }
+
+ //Found the closing delimiter?
+ if (!$close_pos) {
+ // span till the end of this $part when no closing delimiter is found
+ $close_pos = $length;
+ }
+
+ //Get the actual string
+ $string = substr($part, $i, $close_pos - $i + 1);
+ $i = $close_pos;
+
+ // handle escape chars and encode html chars
+ // (special because when we have escape chars within our string they may not be escaped)
+ if ($this->lexic_permissions['ESCAPE_CHAR'] && $this->language_data['ESCAPE_CHAR']) {
+ $start = 0;
+ $new_string = '';
+ while ($es_pos = strpos($string, $this->language_data['ESCAPE_CHAR'], $start)) {
+ // hmtl escape stuff before
+ $new_string .= $this->hsc(substr($string, $start, $es_pos - $start));
+ // check if this is a hard escape
+ foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
+ if (substr($string, $es_pos, strlen($hardescape)) == $hardescape) {
+ // indeed, this is a hardescape
+ $new_string .= "<span$escape_char_attributes>" .
+ $this->hsc($hardescape) . '</span>';
+ $start = $es_pos + strlen($hardescape);
+ continue 2;
+ }
+ }
+ // not a hard escape, but a normal escape
+ // they come in pairs of two
+ $c = 0;
+ while (isset($string[$es_pos + $c]) && isset($string[$es_pos + $c + 1])
+ && $string[$es_pos + $c] == $this->language_data['ESCAPE_CHAR']
+ && $string[$es_pos + $c + 1] == $this->language_data['ESCAPE_CHAR']) {
+ $c += 2;
+ }
+ if ($c) {
+ $new_string .= "<span$escape_char_attributes>" .
+ str_repeat($escaped_escape_char, $c) .
+ '</span>';
+ $start = $es_pos + $c;
+ } else {
+ // this is just a single lonely escape char...
+ $new_string .= $escaped_escape_char;
+ $start = $es_pos + 1;
+ }
+ }
+ $string = $new_string . $this->hsc(substr($string, $start));
+ } else {
+ $string = $this->hsc($string);
+ }
+
+ if ($check_linenumbers) {
+ // Are line numbers used? If, we should end the string before
+ // the newline and begin it again (so when <li>s are put in the source
+ // remains XHTML compliant)
+ // note to self: This opens up possibility of config files specifying
+ // that languages can/cannot have multiline strings???
+ $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
+ }
+
+ $result .= "<span$string_attributes>" . $string . '</span>';
+ $string = '';
+ continue;
+ } else {
+ //Have a look for regexp comments
+ if ($i == $next_comment_regexp_pos) {
+ $COMMENT_MATCHED = true;
+ $comment = $comment_regexp_cache_per_key[$next_comment_regexp_key];
+ $test_str = $this->hsc(substr($part, $i, $comment['length']));
+
+ //@todo If remove important do remove here
+ if ($this->lexic_permissions['COMMENTS']['MULTI']) {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment['key']] . '"';
+ } else {
+ $attributes = ' class="co' . $comment['key'] . '"';
+ }
+
+ $test_str = "<span$attributes>" . $test_str . "</span>";
+
+ // Short-cut through all the multiline code
+ if ($check_linenumbers) {
+ // strreplace to put close span and open span around multiline newlines
+ $test_str = str_replace(
+ "\n", "</span>\n<span$attributes>",
+ str_replace("\n ", "\n&nbsp;", $test_str)
+ );
+ }
+ }
+
+ $i += $comment['length'] - 1;
+
+ // parse the rest
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+ }
+
+ // If we haven't matched a regexp comment, try multi-line comments
+ if (!$COMMENT_MATCHED) {
+ // Is this a multiline comment?
+ if (!empty($this->language_data['COMMENT_MULTI']) && $next_comment_multi_pos < $i) {
+ $next_comment_multi_pos = $length;
+ foreach ($this->language_data['COMMENT_MULTI'] as $open => $close) {
+ $match_i = false;
+ if (isset($comment_multi_cache_per_key[$open]) &&
+ ($comment_multi_cache_per_key[$open] >= $i ||
+ $comment_multi_cache_per_key[$open] === false)) {
+ // we have already matched something
+ if ($comment_multi_cache_per_key[$open] === false) {
+ // this comment is never matched
+ continue;
+ }
+ $match_i = $comment_multi_cache_per_key[$open];
+ } elseif (($match_i = stripos($part, $open, $i)) !== false) {
+ $comment_multi_cache_per_key[$open] = $match_i;
+ } else {
+ $comment_multi_cache_per_key[$open] = false;
+ continue;
+ }
+ if ($match_i !== false && $match_i < $next_comment_multi_pos) {
+ $next_comment_multi_pos = $match_i;
+ $next_open_comment_multi = $open;
+ if ($match_i === $i) {
+ break;
+ }
+ }
+ }
+ }
+ if ($i == $next_comment_multi_pos) {
+ $open = $next_open_comment_multi;
+ $close = $this->language_data['COMMENT_MULTI'][$open];
+ $open_strlen = strlen($open);
+ $close_strlen = strlen($close);
+ $COMMENT_MATCHED = true;
+ $test_str_match = $open;
+ //@todo If remove important do remove here
+ if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
+ $open == GESHI_START_IMPORTANT) {
+ if ($open != GESHI_START_IMPORTANT) {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS']['MULTI'] . '"';
+ } else {
+ $attributes = ' class="coMULTI"';
+ }
+ $test_str = "<span$attributes>" . $this->hsc($open);
+ } else {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->important_styles . '"';
+ } else {
+ $attributes = ' class="imp"';
+ }
+
+ // We don't include the start of the comment if it's an
+ // "important" part
+ $test_str = "<span$attributes>";
+ }
+ } else {
+ $test_str = $this->hsc($open);
+ }
+
+ $close_pos = strpos( $part, $close, $i + $open_strlen );
+
+ if ($close_pos === false) {
+ $close_pos = $length;
+ }
+
+ // Short-cut through all the multiline code
+ $rest_of_comment = $this->hsc(substr($part, $i + $open_strlen, $close_pos - $i - $open_strlen + $close_strlen));
+ if (($this->lexic_permissions['COMMENTS']['MULTI'] ||
+ $test_str_match == GESHI_START_IMPORTANT) &&
+ $check_linenumbers) {
+
+ // strreplace to put close span and open span around multiline newlines
+ $test_str .= str_replace(
+ "\n", "</span>\n<span$attributes>",
+ str_replace("\n ", "\n&nbsp;", $rest_of_comment)
+ );
+ } else {
+ $test_str .= $rest_of_comment;
+ }
+
+ if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
+ $test_str_match == GESHI_START_IMPORTANT) {
+ $test_str .= '</span>';
+ }
+
+ $i = $close_pos + $close_strlen - 1;
+
+ // parse the rest
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+ }
+ }
+
+ // If we haven't matched a multiline comment, try single-line comments
+ if (!$COMMENT_MATCHED) {
+ // cache potential single line comment occurances
+ if (!empty($this->language_data['COMMENT_SINGLE']) && $next_comment_single_pos < $i) {
+ $next_comment_single_pos = $length;
+ foreach ($this->language_data['COMMENT_SINGLE'] as $comment_key => $comment_mark) {
+ $match_i = false;
+ if (isset($comment_single_cache_per_key[$comment_key]) &&
+ ($comment_single_cache_per_key[$comment_key] >= $i ||
+ $comment_single_cache_per_key[$comment_key] === false)) {
+ // we have already matched something
+ if ($comment_single_cache_per_key[$comment_key] === false) {
+ // this comment is never matched
+ continue;
+ }
+ $match_i = $comment_single_cache_per_key[$comment_key];
+ } elseif (
+ // case sensitive comments
+ ($this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
+ ($match_i = stripos($part, $comment_mark, $i)) !== false) ||
+ // non case sensitive
+ (!$this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
+ (($match_i = strpos($part, $comment_mark, $i)) !== false))) {
+ $comment_single_cache_per_key[$comment_key] = $match_i;
+ } else {
+ $comment_single_cache_per_key[$comment_key] = false;
+ continue;
+ }
+ if ($match_i !== false && $match_i < $next_comment_single_pos) {
+ $next_comment_single_pos = $match_i;
+ $next_comment_single_key = $comment_key;
+ if ($match_i === $i) {
+ break;
+ }
+ }
+ }
+ }
+ if ($next_comment_single_pos == $i) {
+ $comment_key = $next_comment_single_key;
+ $comment_mark = $this->language_data['COMMENT_SINGLE'][$comment_key];
+ $com_len = strlen($comment_mark);
+
+ // This check will find special variables like $# in bash
+ // or compiler directives of Delphi beginning {$
+ if ((empty($sc_disallowed_before) || ($i == 0) ||
+ (false === strpos($sc_disallowed_before, $part[$i-1]))) &&
+ (empty($sc_disallowed_after) || ($length <= $i + $com_len) ||
+ (false === strpos($sc_disallowed_after, $part[$i + $com_len]))))
+ {
+ // this is a valid comment
+ $COMMENT_MATCHED = true;
+ if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment_key] . '"';
+ } else {
+ $attributes = ' class="co' . $comment_key . '"';
+ }
+ $test_str = "<span$attributes>" . $this->hsc($this->change_case($comment_mark));
+ } else {
+ $test_str = $this->hsc($comment_mark);
+ }
+
+ //Check if this comment is the last in the source
+ $close_pos = strpos($part, "\n", $i);
+ $oops = false;
+ if ($close_pos === false) {
+ $close_pos = $length;
+ $oops = true;
+ }
+ $test_str .= $this->hsc(substr($part, $i + $com_len, $close_pos - $i - $com_len));
+ if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
+ $test_str .= "</span>";
+ }
+
+ // Take into account that the comment might be the last in the source
+ if (!$oops) {
+ $test_str .= "\n";
+ }
+
+ $i = $close_pos;
+
+ // parse the rest
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+ }
+ }
+ }
+ }
+
+ // Where are we adding this char?
+ if (!$COMMENT_MATCHED) {
+ $stuff_to_parse .= $char;
+ } else {
+ $result .= $test_str;
+ unset($test_str);
+ $COMMENT_MATCHED = false;
+ }
+ }
+ // Parse the last bit
+ $result .= $this->parse_non_string_part($stuff_to_parse);
+ $stuff_to_parse = '';
+ } else {
+ $result .= $this->hsc($part);
+ }
+ // Close the <span> that surrounds the block
+ if ($STRICTATTRS != '') {
+ $result = str_replace("\n", "</span>\n<span$STRICTATTRS>", $result);
+ $result .= '</span>';
+ }
+
+ $endresult .= $result;
+ unset($part, $parts[$key], $result);
+ }
+
+ //This fix is related to SF#1923020, but has to be applied regardless of
+ //actually highlighting symbols.
+ /** NOTE: memorypeak #3 */
+ $endresult = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $endresult);
+
+// // Parse the last stuff (redundant?)
+// $result .= $this->parse_non_string_part($stuff_to_parse);
+
+ // Lop off the very first and last spaces
+// $result = substr($result, 1, -1);
+
+ // We're finished: stop timing
+ $this->set_time($start_time, microtime());
+
+ $this->finalise($endresult);
+ return $endresult;
+ }
+
+ /**
+ * Swaps out spaces and tabs for HTML indentation. Not needed if
+ * the code is in a pre block...
+ *
+ * @param string The source to indent (reference!)
+ * @since 1.0.0
+ * @access private
+ */
+ function indent(&$result) {
+ /// Replace tabs with the correct number of spaces
+ if (false !== strpos($result, "\t")) {
+ $lines = explode("\n", $result);
+ $result = null;//Save memory while we process the lines individually
+ $tab_width = $this->get_real_tab_width();
+ $tab_string = '&nbsp;' . str_repeat(' ', $tab_width);
+
+ for ($key = 0, $n = count($lines); $key < $n; $key++) {
+ $line = $lines[$key];
+ if (false === strpos($line, "\t")) {
+ continue;
+ }
+
+ $pos = 0;
+ $length = strlen($line);
+ $lines[$key] = ''; // reduce memory
+
+ $IN_TAG = false;
+ for ($i = 0; $i < $length; ++$i) {
+ $char = $line[$i];
+ // Simple engine to work out whether we're in a tag.
+ // If we are we modify $pos. This is so we ignore HTML
+ // in the line and only workout the tab replacement
+ // via the actual content of the string
+ // This test could be improved to include strings in the
+ // html so that < or > would be allowed in user's styles
+ // (e.g. quotes: '<' '>'; or similar)
+ if ($IN_TAG) {
+ if ('>' == $char) {
+ $IN_TAG = false;
+ }
+ $lines[$key] .= $char;
+ } elseif ('<' == $char) {
+ $IN_TAG = true;
+ $lines[$key] .= '<';
+ } elseif ('&' == $char) {
+ $substr = substr($line, $i + 3, 5);
+ $posi = strpos($substr, ';');
+ if (false === $posi) {
+ ++$pos;
+ } else {
+ $pos -= $posi+2;
+ }
+ $lines[$key] .= $char;
+ } elseif ("\t" == $char) {
+ $str = '';
+ // OPTIMISE - move $strs out. Make an array:
+ // $tabs = array(
+ // 1 => '&nbsp;',
+ // 2 => '&nbsp; ',
+ // 3 => '&nbsp; &nbsp;' etc etc
+ // to use instead of building a string every time
+ $tab_end_width = $tab_width - ($pos % $tab_width); //Moved out of the look as it doesn't change within the loop
+ if (($pos & 1) || 1 == $tab_end_width) {
+ $str .= substr($tab_string, 6, $tab_end_width);
+ } else {
+ $str .= substr($tab_string, 0, $tab_end_width+5);
+ }
+ $lines[$key] .= $str;
+ $pos += $tab_end_width;
+
+ if (false === strpos($line, "\t", $i + 1)) {
+ $lines[$key] .= substr($line, $i + 1);
+ break;
+ }
+ } elseif (0 == $pos && ' ' == $char) {
+ $lines[$key] .= '&nbsp;';
+ ++$pos;
+ } else {
+ $lines[$key] .= $char;
+ ++$pos;
+ }
+ }
+ }
+ $result = implode("\n", $lines);
+ unset($lines);//We don't need the lines separated beyond this --- free them!
+ }
+ // Other whitespace
+ // BenBE: Fix to reduce the number of replacements to be done
+ $result = preg_replace('/^ /m', '&nbsp;', $result);
+ $result = str_replace(' ', ' &nbsp;', $result);
+
+ if ($this->line_numbers == GESHI_NO_LINE_NUMBERS && $this->header_type != GESHI_HEADER_PRE_TABLE) {
+ if ($this->line_ending === null) {
+ $result = nl2br($result);
+ } else {
+ $result = str_replace("\n", $this->line_ending, $result);
+ }
+ }
+ }
+
+ /**
+ * Changes the case of a keyword for those languages where a change is asked for
+ *
+ * @param string The keyword to change the case of
+ * @return string The keyword with its case changed
+ * @since 1.0.0
+ * @access private
+ */
+ function change_case($instr) {
+ switch ($this->language_data['CASE_KEYWORDS']) {
+ case GESHI_CAPS_UPPER:
+ return strtoupper($instr);
+ case GESHI_CAPS_LOWER:
+ return strtolower($instr);
+ default:
+ return $instr;
+ }
+ }
+
+ /**
+ * Handles replacements of keywords to include markup and links if requested
+ *
+ * @param string The keyword to add the Markup to
+ * @return The HTML for the match found
+ * @since 1.0.8
+ * @access private
+ *
+ * @todo Get rid of ender in keyword links
+ */
+ function handle_keyword_replace($match) {
+ $k = $this->_kw_replace_group;
+ $keyword = $match[0];
+ $keyword_match = $match[1];
+
+ $before = '';
+ $after = '';
+
+ if ($this->keyword_links) {
+ // Keyword links have been ebabled
+
+ if (isset($this->language_data['URLS'][$k]) &&
+ $this->language_data['URLS'][$k] != '') {
+ // There is a base group for this keyword
+
+ // Old system: strtolower
+ //$keyword = ( $this->language_data['CASE_SENSITIVE'][$group] ) ? $keyword : strtolower($keyword);
+ // New system: get keyword from language file to get correct case
+ if (!$this->language_data['CASE_SENSITIVE'][$k] &&
+ strpos($this->language_data['URLS'][$k], '{FNAME}') !== false) {
+ foreach ($this->language_data['KEYWORDS'][$k] as $word) {
+ if (strcasecmp($word, $keyword_match) == 0) {
+ break;
+ }
+ }
+ } else {
+ $word = $keyword_match;
+ }
+
+ $before = '<|UR1|"' .
+ str_replace(
+ array(
+ '{FNAME}',
+ '{FNAMEL}',
+ '{FNAMEU}',
+ '.'),
+ array(
+ str_replace('+', '%20', urlencode($this->hsc($word))),
+ str_replace('+', '%20', urlencode($this->hsc(strtolower($word)))),
+ str_replace('+', '%20', urlencode($this->hsc(strtoupper($word)))),
+ '<DOT>'),
+ $this->language_data['URLS'][$k]
+ ) . '">';
+ $after = '</a>';
+ }
+ }
+
+ return $before . '<|/'. $k .'/>' . $this->change_case($keyword) . '|>' . $after;
+ }
+
+ /**
+ * handles regular expressions highlighting-definitions with callback functions
+ *
+ * @note this is a callback, don't use it directly
+ *
+ * @param array the matches array
+ * @return The highlighted string
+ * @since 1.0.8
+ * @access private
+ */
+ function handle_regexps_callback($matches) {
+ // before: "' style=\"' . call_user_func(\"$func\", '\\1') . '\"\\1|>'",
+ return ' style="' . call_user_func($this->language_data['STYLES']['REGEXPS'][$this->_rx_key], $matches[1]) . '"'. $matches[1] . '|>';
+ }
+
+ /**
+ * handles newlines in REGEXPS matches. Set the _hmr_* vars before calling this
+ *
+ * @note this is a callback, don't use it directly
+ *
+ * @param array the matches array
+ * @return string
+ * @since 1.0.8
+ * @access private
+ */
+ function handle_multiline_regexps($matches) {
+ $before = $this->_hmr_before;
+ $after = $this->_hmr_after;
+ if ($this->_hmr_replace) {
+ $replace = $this->_hmr_replace;
+ $search = array();
+
+ foreach (array_keys($matches) as $k) {
+ $search[] = '\\' . $k;
+ }
+
+ $before = str_replace($search, $matches, $before);
+ $after = str_replace($search, $matches, $after);
+ $replace = str_replace($search, $matches, $replace);
+ } else {
+ $replace = $matches[0];
+ }
+ return $before
+ . '<|!REG3XP' . $this->_hmr_key .'!>'
+ . str_replace("\n", "|>\n<|!REG3XP" . $this->_hmr_key . '!>', $replace)
+ . '|>'
+ . $after;
+ }
+
+ /**
+ * Takes a string that has no strings or comments in it, and highlights
+ * stuff like keywords, numbers and methods.
+ *
+ * @param string The string to parse for keyword, numbers etc.
+ * @since 1.0.0
+ * @access private
+ * @todo BUGGY! Why? Why not build string and return?
+ */
+ function parse_non_string_part($stuff_to_parse) {
+ $stuff_to_parse = ' ' . $this->hsc($stuff_to_parse);
+
+ // Highlight keywords
+ $disallowed_before = "(?<![a-zA-Z0-9\$_\|\#|^&";
+ $disallowed_after = "(?![a-zA-Z0-9_\|%\\-&;";
+ if ($this->lexic_permissions['STRINGS']) {
+ $quotemarks = preg_quote(implode($this->language_data['QUOTEMARKS']), '/');
+ $disallowed_before .= $quotemarks;
+ $disallowed_after .= $quotemarks;
+ }
+ $disallowed_before .= "])";
+ $disallowed_after .= "])";
+
+ $parser_control_pergroup = false;
+ if (isset($this->language_data['PARSER_CONTROL'])) {
+ if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'])) {
+ $x = 0; // check wether per-keyword-group parser_control is enabled
+ if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'])) {
+ $disallowed_before = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'];
+ ++$x;
+ }
+ if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'])) {
+ $disallowed_after = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'];
+ ++$x;
+ }
+ $parser_control_pergroup = (count($this->language_data['PARSER_CONTROL']['KEYWORDS']) - $x) > 0;
+ }
+ }
+
+ foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
+ if (!isset($this->lexic_permissions['KEYWORDS'][$k]) ||
+ $this->lexic_permissions['KEYWORDS'][$k]) {
+
+ $case_sensitive = $this->language_data['CASE_SENSITIVE'][$k];
+ $modifiers = $case_sensitive ? '' : 'i';
+
+ // NEW in 1.0.8 - per-keyword-group parser control
+ $disallowed_before_local = $disallowed_before;
+ $disallowed_after_local = $disallowed_after;
+ if ($parser_control_pergroup && isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k])) {
+ if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'])) {
+ $disallowed_before_local =
+ $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'];
+ }
+
+ if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'])) {
+ $disallowed_after_local =
+ $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'];
+ }
+ }
+
+ $this->_kw_replace_group = $k;
+
+ //NEW in 1.0.8, the cached regexp list
+ // since we don't want PHP / PCRE to crash due to too large patterns we split them into smaller chunks
+ for ($set = 0, $set_length = count($this->language_data['CACHED_KEYWORD_LISTS'][$k]); $set < $set_length; ++$set) {
+ $keywordset =& $this->language_data['CACHED_KEYWORD_LISTS'][$k][$set];
+ // Might make a more unique string for putting the number in soon
+ // Basically, we don't put the styles in yet because then the styles themselves will
+ // get highlighted if the language has a CSS keyword in it (like CSS, for example ;))
+ $stuff_to_parse = preg_replace_callback(
+ "/$disallowed_before_local({$keywordset})(?!\<DOT\>(?:htm|php|aspx?))$disallowed_after_local/$modifiers",
+ array($this, 'handle_keyword_replace'),
+ $stuff_to_parse
+ );
+ }
+ }
+ }
+
+ // Regular expressions
+ foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
+ if ($this->lexic_permissions['REGEXPS'][$key]) {
+ if (is_array($regexp)) {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ // produce valid HTML when we match multiple lines
+ $this->_hmr_replace = $regexp[GESHI_REPLACE];
+ $this->_hmr_before = $regexp[GESHI_BEFORE];
+ $this->_hmr_key = $key;
+ $this->_hmr_after = $regexp[GESHI_AFTER];
+ $stuff_to_parse = preg_replace_callback(
+ "/" . $regexp[GESHI_SEARCH] . "/{$regexp[GESHI_MODIFIERS]}",
+ array($this, 'handle_multiline_regexps'),
+ $stuff_to_parse);
+ $this->_hmr_replace = false;
+ $this->_hmr_before = '';
+ $this->_hmr_after = '';
+ } else {
+ $stuff_to_parse = preg_replace(
+ '/' . $regexp[GESHI_SEARCH] . '/' . $regexp[GESHI_MODIFIERS],
+ $regexp[GESHI_BEFORE] . '<|!REG3XP'. $key .'!>' . $regexp[GESHI_REPLACE] . '|>' . $regexp[GESHI_AFTER],
+ $stuff_to_parse);
+ }
+ } else {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ // produce valid HTML when we match multiple lines
+ $this->_hmr_key = $key;
+ $stuff_to_parse = preg_replace_callback( "/(" . $regexp . ")/",
+ array($this, 'handle_multiline_regexps'), $stuff_to_parse);
+ $this->_hmr_key = '';
+ } else {
+ $stuff_to_parse = preg_replace( "/(" . $regexp . ")/", "<|!REG3XP$key!>\\1|>", $stuff_to_parse);
+ }
+ }
+ }
+ }
+
+ // Highlight numbers. As of 1.0.8 we support different types of numbers
+ $numbers_found = false;
+
+ if ($this->lexic_permissions['NUMBERS'] && preg_match($this->language_data['PARSER_CONTROL']['NUMBERS']['PRECHECK_RX'], $stuff_to_parse )) {
+ $numbers_found = true;
+
+ //For each of the formats ...
+ foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
+ //Check if it should be highlighted ...
+ $stuff_to_parse = preg_replace($regexp, "<|/NUM!$id/>\\1|>", $stuff_to_parse);
+ }
+ }
+
+ //
+ // Now that's all done, replace /[number]/ with the correct styles
+ //
+ foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
+ if (!$this->use_classes) {
+ $attributes = ' style="' .
+ (isset($this->language_data['STYLES']['KEYWORDS'][$k]) ?
+ $this->language_data['STYLES']['KEYWORDS'][$k] : "") . '"';
+ } else {
+ $attributes = ' class="kw' . $k . '"';
+ }
+ $stuff_to_parse = str_replace("<|/$k/>", "<|$attributes>", $stuff_to_parse);
+ }
+
+ if ($numbers_found) {
+ // Put number styles in
+ foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
+ //Commented out for now, as this needs some review ...
+ // if ($numbers_permissions & $id) {
+ //Get the appropriate style ...
+ //Checking for unset styles is done by the style cache builder ...
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['NUMBERS'][$id] . '"';
+ } else {
+ $attributes = ' class="nu'.$id.'"';
+ }
+
+ //Set in the correct styles ...
+ $stuff_to_parse = str_replace("/NUM!$id/", $attributes, $stuff_to_parse);
+ // }
+ }
+ }
+
+ // Highlight methods and fields in objects
+ if ($this->lexic_permissions['METHODS'] && $this->language_data['OOLANG']) {
+ $oolang_spaces = "[\s]*";
+ $oolang_before = "";
+ $oolang_after = "[a-zA-Z][a-zA-Z0-9_]*";
+ if (isset($this->language_data['PARSER_CONTROL'])) {
+ if (isset($this->language_data['PARSER_CONTROL']['OOLANG'])) {
+ if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'])) {
+ $oolang_before = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'];
+ }
+ if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'])) {
+ $oolang_after = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'];
+ }
+ if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'])) {
+ $oolang_spaces = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'];
+ }
+ }
+ }
+
+ foreach ($this->language_data['OBJECT_SPLITTERS'] as $key => $splitter) {
+ if (false !== strpos($stuff_to_parse, $splitter)) {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['METHODS'][$key] . '"';
+ } else {
+ $attributes = ' class="me' . $key . '"';
+ }
+ $stuff_to_parse = preg_replace("/($oolang_before)(" . preg_quote($this->language_data['OBJECT_SPLITTERS'][$key], '/') . ")($oolang_spaces)($oolang_after)/", "\\1\\2\\3<|$attributes>\\4|>", $stuff_to_parse);
+ }
+ }
+ }
+
+ //
+ // Highlight brackets. Yes, I've tried adding a semi-colon to this list.
+ // You try it, and see what happens ;)
+ // TODO: Fix lexic permissions not converting entities if shouldn't
+ // be highlighting regardless
+ //
+ if ($this->lexic_permissions['BRACKETS']) {
+ $stuff_to_parse = str_replace( $this->language_data['CACHE_BRACKET_MATCH'],
+ $this->language_data['CACHE_BRACKET_REPLACE'], $stuff_to_parse );
+ }
+
+
+ //FIX for symbol highlighting ...
+ if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
+ //Get all matches and throw away those witin a block that is already highlighted... (i.e. matched by a regexp)
+ $n_symbols = preg_match_all("/<\|(?:<DOT>|[^>])+>(?:(?!\|>).*?)\|>|<\/a>|(?:" . $this->language_data['SYMBOL_SEARCH'] . ")+(?![^<]+?>)/", $stuff_to_parse, $pot_symbols, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
+ $global_offset = 0;
+ for ($s_id = 0; $s_id < $n_symbols; ++$s_id) {
+ $symbol_match = $pot_symbols[$s_id][0][0];
+ if (strpos($symbol_match, '<') !== false || strpos($symbol_match, '>') !== false) {
+ // already highlighted blocks _must_ include either < or >
+ // so if this conditional applies, we have to skip this match
+ // BenBE: UNLESS the block contains <SEMI> or <PIPE>
+ if(strpos($symbol_match, '<SEMI>') === false &&
+ strpos($symbol_match, '<PIPE>') === false) {
+ continue;
+ }
+ }
+
+ // if we reach this point, we have a valid match which needs to be highlighted
+
+ $symbol_length = strlen($symbol_match);
+ $symbol_offset = $pot_symbols[$s_id][0][1];
+ unset($pot_symbols[$s_id]);
+ $symbol_end = $symbol_length + $symbol_offset;
+ $symbol_hl = "";
+
+ // if we have multiple styles, we have to handle them properly
+ if ($this->language_data['MULTIPLE_SYMBOL_GROUPS']) {
+ $old_sym = -1;
+ // Split the current stuff to replace into its atomic symbols ...
+ preg_match_all("/" . $this->language_data['SYMBOL_SEARCH'] . "/", $symbol_match, $sym_match_syms, PREG_PATTERN_ORDER);
+ foreach ($sym_match_syms[0] as $sym_ms) {
+ //Check if consequtive symbols belong to the same group to save output ...
+ if (isset($this->language_data['SYMBOL_DATA'][$sym_ms])
+ && ($this->language_data['SYMBOL_DATA'][$sym_ms] != $old_sym)) {
+ if (-1 != $old_sym) {
+ $symbol_hl .= "|>";
+ }
+ $old_sym = $this->language_data['SYMBOL_DATA'][$sym_ms];
+ if (!$this->use_classes) {
+ $symbol_hl .= '<| style="' . $this->language_data['STYLES']['SYMBOLS'][$old_sym] . '">';
+ } else {
+ $symbol_hl .= '<| class="sy' . $old_sym . '">';
+ }
+ }
+ $symbol_hl .= $sym_ms;
+ }
+ unset($sym_match_syms);
+
+ //Close remaining tags and insert the replacement at the right position ...
+ //Take caution if symbol_hl is empty to avoid doubled closing spans.
+ if (-1 != $old_sym) {
+ $symbol_hl .= "|>";
+ }
+ } else {
+ if (!$this->use_classes) {
+ $symbol_hl = '<| style="' . $this->language_data['STYLES']['SYMBOLS'][0] . '">';
+ } else {
+ $symbol_hl = '<| class="sy0">';
+ }
+ $symbol_hl .= $symbol_match . '|>';
+ }
+
+ $stuff_to_parse = substr_replace($stuff_to_parse, $symbol_hl, $symbol_offset + $global_offset, $symbol_length);
+
+ // since we replace old text with something of different size,
+ // we'll have to keep track of the differences
+ $global_offset += strlen($symbol_hl) - $symbol_length;
+ }
+ }
+ //FIX for symbol highlighting ...
+
+ // Add class/style for regexps
+ foreach (array_keys($this->language_data['REGEXPS']) as $key) {
+ if ($this->lexic_permissions['REGEXPS'][$key]) {
+ if (is_callable($this->language_data['STYLES']['REGEXPS'][$key])) {
+ $this->_rx_key = $key;
+ $stuff_to_parse = preg_replace_callback("/!REG3XP$key!(.*)\|>/U",
+ array($this, 'handle_regexps_callback'),
+ $stuff_to_parse);
+ } else {
+ if (!$this->use_classes) {
+ $attributes = ' style="' . $this->language_data['STYLES']['REGEXPS'][$key] . '"';
+ } else {
+ if (is_array($this->language_data['REGEXPS'][$key]) &&
+ array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$key])) {
+ $attributes = ' class="' .
+ $this->language_data['REGEXPS'][$key][GESHI_CLASS] . '"';
+ } else {
+ $attributes = ' class="re' . $key . '"';
+ }
+ }
+ $stuff_to_parse = str_replace("!REG3XP$key!", "$attributes", $stuff_to_parse);
+ }
+ }
+ }
+
+ // Replace <DOT> with . for urls
+ $stuff_to_parse = str_replace('<DOT>', '.', $stuff_to_parse);
+ // Replace <|UR1| with <a href= for urls also
+ if (isset($this->link_styles[GESHI_LINK])) {
+ if ($this->use_classes) {
+ $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
+ } else {
+ $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' style="' . $this->link_styles[GESHI_LINK] . '" href=', $stuff_to_parse);
+ }
+ } else {
+ $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
+ }
+
+ //
+ // NOW we add the span thingy ;)
+ //
+
+ $stuff_to_parse = str_replace('<|', '<span', $stuff_to_parse);
+ $stuff_to_parse = str_replace ( '|>', '</span>', $stuff_to_parse );
+ return substr($stuff_to_parse, 1);
+ }
+
+ /**
+ * Sets the time taken to parse the code
+ *
+ * @param microtime The time when parsing started
+ * @param microtime The time when parsing ended
+ * @since 1.0.2
+ * @access private
+ */
+ function set_time($start_time, $end_time) {
+ $start = explode(' ', $start_time);
+ $end = explode(' ', $end_time);
+ $this->time = $end[0] + $end[1] - $start[0] - $start[1];
+ }
+
+ /**
+ * Gets the time taken to parse the code
+ *
+ * @return double The time taken to parse the code
+ * @since 1.0.2
+ */
+ function get_time() {
+ return $this->time;
+ }
+
+ /**
+ * Merges arrays recursively, overwriting values of the first array with values of later arrays
+ *
+ * @since 1.0.8
+ * @access private
+ */
+ function merge_arrays() {
+ $arrays = func_get_args();
+ $narrays = count($arrays);
+
+ // check arguments
+ // comment out if more performance is necessary (in this case the foreach loop will trigger a warning if the argument is not an array)
+ for ($i = 0; $i < $narrays; $i ++) {
+ if (!is_array($arrays[$i])) {
+ // also array_merge_recursive returns nothing in this case
+ trigger_error('Argument #' . ($i+1) . ' is not an array - trying to merge array with scalar! Returning false!', E_USER_WARNING);
+ return false;
+ }
+ }
+
+ // the first array is in the output set in every case
+ $ret = $arrays[0];
+
+ // merege $ret with the remaining arrays
+ for ($i = 1; $i < $narrays; $i ++) {
+ foreach ($arrays[$i] as $key => $value) {
+ if (is_array($value) && isset($ret[$key])) {
+ // if $ret[$key] is not an array you try to merge an scalar value with an array - the result is not defined (incompatible arrays)
+ // in this case the call will trigger an E_USER_WARNING and the $ret[$key] will be false.
+ $ret[$key] = $this->merge_arrays($ret[$key], $value);
+ } else {
+ $ret[$key] = $value;
+ }
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Gets language information and stores it for later use
+ *
+ * @param string The filename of the language file you want to load
+ * @since 1.0.0
+ * @access private
+ * @todo Needs to load keys for lexic permissions for keywords, regexps etc
+ */
+ function load_language($file_name) {
+ if ($file_name == $this->loaded_language) {
+ // this file is already loaded!
+ return;
+ }
+
+ //Prepare some stuff before actually loading the language file
+ $this->loaded_language = $file_name;
+ $this->parse_cache_built = false;
+ $this->enable_highlighting();
+ $language_data = array();
+
+ //Load the language file
+ require $file_name;
+
+ // Perhaps some checking might be added here later to check that
+ // $language data is a valid thing but maybe not
+ $this->language_data = $language_data;
+
+ // Set strict mode if should be set
+ $this->strict_mode = $this->language_data['STRICT_MODE_APPLIES'];
+
+ // Set permissions for all lexics to true
+ // so they'll be highlighted by default
+ foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
+ if (!empty($this->language_data['KEYWORDS'][$key])) {
+ $this->lexic_permissions['KEYWORDS'][$key] = true;
+ } else {
+ $this->lexic_permissions['KEYWORDS'][$key] = false;
+ }
+ }
+
+ foreach (array_keys($this->language_data['COMMENT_SINGLE']) as $key) {
+ $this->lexic_permissions['COMMENTS'][$key] = true;
+ }
+ foreach (array_keys($this->language_data['REGEXPS']) as $key) {
+ $this->lexic_permissions['REGEXPS'][$key] = true;
+ }
+
+ // for BenBE and future code reviews:
+ // we can use empty here since we only check for existance and emptiness of an array
+ // if it is not an array at all but rather false or null this will work as intended as well
+ // even if $this->language_data['PARSER_CONTROL'] is undefined this won't trigger a notice
+ if (!empty($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'])) {
+ foreach ($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'] as $flag => $value) {
+ // it's either true or false and maybe is true as well
+ $perm = $value !== GESHI_NEVER;
+ if ($flag == 'ALL') {
+ $this->enable_highlighting($perm);
+ continue;
+ }
+ if (!isset($this->lexic_permissions[$flag])) {
+ // unknown lexic permission
+ continue;
+ }
+ if (is_array($this->lexic_permissions[$flag])) {
+ foreach ($this->lexic_permissions[$flag] as $key => $val) {
+ $this->lexic_permissions[$flag][$key] = $perm;
+ }
+ } else {
+ $this->lexic_permissions[$flag] = $perm;
+ }
+ }
+ unset($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS']);
+ }
+
+ //Fix: Problem where hardescapes weren't handled if no ESCAPE_CHAR was given
+ //You need to set one for HARDESCAPES only in this case.
+ if(!isset($this->language_data['HARDCHAR'])) {
+ $this->language_data['HARDCHAR'] = $this->language_data['ESCAPE_CHAR'];
+ }
+
+ //NEW in 1.0.8: Allow styles to be loaded from a separate file to override defaults
+ $style_filename = substr($file_name, 0, -4) . '.style.php';
+ if (is_readable($style_filename)) {
+ //Clear any style_data that could have been set before ...
+ if (isset($style_data)) {
+ unset($style_data);
+ }
+
+ //Read the Style Information from the style file
+ include $style_filename;
+
+ //Apply the new styles to our current language styles
+ if (isset($style_data) && is_array($style_data)) {
+ $this->language_data['STYLES'] =
+ $this->merge_arrays($this->language_data['STYLES'], $style_data);
+ }
+ }
+ }
+
+ /**
+ * Takes the parsed code and various options, and creates the HTML
+ * surrounding it to make it look nice.
+ *
+ * @param string The code already parsed (reference!)
+ * @since 1.0.0
+ * @access private
+ */
+ function finalise(&$parsed_code) {
+ // Remove end parts of important declarations
+ // This is BUGGY!! My fault for bad code: fix coming in 1.2
+ // @todo Remove this crap
+ if ($this->enable_important_blocks &&
+ (strpos($parsed_code, $this->hsc(GESHI_START_IMPORTANT)) === false)) {
+ $parsed_code = str_replace($this->hsc(GESHI_END_IMPORTANT), '', $parsed_code);
+ }
+
+ // Add HTML whitespace stuff if we're using the <div> header
+ if ($this->header_type != GESHI_HEADER_PRE && $this->header_type != GESHI_HEADER_PRE_VALID) {
+ $this->indent($parsed_code);
+ }
+
+ // purge some unnecessary stuff
+ /** NOTE: memorypeak #1 */
+ $parsed_code = preg_replace('#<span[^>]+>(\s*)</span>#', '\\1', $parsed_code);
+
+ // If we are using IDs for line numbers, there needs to be an overall
+ // ID set to prevent collisions.
+ if ($this->add_ids && !$this->overall_id) {
+ $this->overall_id = 'geshi-' . substr(md5(microtime()), 0, 4);
+ }
+
+ // Get code into lines
+ /** NOTE: memorypeak #2 */
+ $code = explode("\n", $parsed_code);
+ $parsed_code = $this->header();
+
+ // If we're using line numbers, we insert <li>s and appropriate
+ // markup to style them (otherwise we don't need to do anything)
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS && $this->header_type != GESHI_HEADER_PRE_TABLE) {
+ // If we're using the <pre> header, we shouldn't add newlines because
+ // the <pre> will line-break them (and the <li>s already do this for us)
+ $ls = ($this->header_type != GESHI_HEADER_PRE && $this->header_type != GESHI_HEADER_PRE_VALID) ? "\n" : '';
+
+ // Set vars to defaults for following loop
+ $i = 0;
+
+ // Foreach line...
+ for ($i = 0, $n = count($code); $i < $n;) {
+ //Reset the attributes for a new line ...
+ $attrs = array();
+
+ // Make lines have at least one space in them if they're empty
+ // BenBE: Checking emptiness using trim instead of relying on blanks
+ if ('' == trim($code[$i])) {
+ $code[$i] = '&nbsp;';
+ }
+
+ // If this is a "special line"...
+ if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS &&
+ $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
+ // Set the attributes to style the line
+ if ($this->use_classes) {
+ //$attr = ' class="li2"';
+ $attrs['class'][] = 'li2';
+ $def_attr = ' class="de2"';
+ } else {
+ //$attr = ' style="' . $this->line_style2 . '"';
+ $attrs['style'][] = $this->line_style2;
+ // This style "covers up" the special styles set for special lines
+ // so that styles applied to special lines don't apply to the actual
+ // code on that line
+ $def_attr = ' style="' . $this->code_style . '"';
+ }
+ } else {
+ if ($this->use_classes) {
+ //$attr = ' class="li1"';
+ $attrs['class'][] = 'li1';
+ $def_attr = ' class="de1"';
+ } else {
+ //$attr = ' style="' . $this->line_style1 . '"';
+ $attrs['style'][] = $this->line_style1;
+ $def_attr = ' style="' . $this->code_style . '"';
+ }
+ }
+
+ //Check which type of tag to insert for this line
+ if ($this->header_type == GESHI_HEADER_PRE_VALID) {
+ $start = "<pre$def_attr>";
+ $end = '</pre>';
+ } else {
+ // Span or div?
+ $start = "<div$def_attr>";
+ $end = '</div>';
+ }
+
+ ++$i;
+
+ // Are we supposed to use ids? If so, add them
+ if ($this->add_ids) {
+ $attrs['id'][] = "$this->overall_id-$i";
+ }
+
+ //Is this some line with extra styles???
+ if (in_array($i, $this->highlight_extra_lines)) {
+ if ($this->use_classes) {
+ if (isset($this->highlight_extra_lines_styles[$i])) {
+ $attrs['class'][] = "lx$i";
+ } else {
+ $attrs['class'][] = "ln-xtra";
+ }
+ } else {
+ array_push($attrs['style'], $this->get_line_style($i));
+ }
+ }
+
+ // Add in the line surrounded by appropriate list HTML
+ $attr_string = '';
+ foreach ($attrs as $key => $attr) {
+ $attr_string .= ' ' . $key . '="' . implode(' ', $attr) . '"';
+ }
+
+ $parsed_code .= "<li$attr_string>$start{$code[$i-1]}$end</li>$ls";
+ unset($code[$i - 1]);
+ }
+ } else {
+ $n = count($code);
+ if ($this->use_classes) {
+ $attributes = ' class="de1"';
+ } else {
+ $attributes = ' style="'. $this->code_style .'"';
+ }
+ if ($this->header_type == GESHI_HEADER_PRE_VALID) {
+ $parsed_code .= '<pre'. $attributes .'>';
+ } elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ if ($this->use_classes) {
+ $attrs = ' class="ln"';
+ } else {
+ $attrs = ' style="'. $this->table_linenumber_style .'"';
+ }
+ $parsed_code .= '<td'.$attrs.'><pre'.$attributes.'>';
+ // get linenumbers
+ // we don't merge it with the for below, since it should be better for
+ // memory consumption this way
+ // @todo: but... actually it would still be somewhat nice to merge the two loops
+ // the mem peaks are at different positions
+ for ($i = 0; $i < $n; ++$i) {
+ $close = 0;
+ // fancy lines
+ if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS &&
+ $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
+ // Set the attributes to style the line
+ if ($this->use_classes) {
+ $parsed_code .= '<span class="xtra li2"><span class="de2">';
+ } else {
+ // This style "covers up" the special styles set for special lines
+ // so that styles applied to special lines don't apply to the actual
+ // code on that line
+ $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
+ .'<span style="' . $this->code_style .'">';
+ }
+ $close += 2;
+ }
+ //Is this some line with extra styles???
+ if (in_array($i + 1, $this->highlight_extra_lines)) {
+ if ($this->use_classes) {
+ if (isset($this->highlight_extra_lines_styles[$i])) {
+ $parsed_code .= "<span class=\"xtra lx$i\">";
+ } else {
+ $parsed_code .= "<span class=\"xtra ln-xtra\">";
+ }
+ } else {
+ $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
+ }
+ ++$close;
+ }
+ $parsed_code .= $this->line_numbers_start + $i;
+ if ($close) {
+ $parsed_code .= str_repeat('</span>', $close);
+ } elseif ($i != $n) {
+ $parsed_code .= "\n";
+ }
+ }
+ $parsed_code .= '</pre></td><td'.$attributes.'>';
+ }
+ $parsed_code .= '<pre'. $attributes .'>';
+ }
+ // No line numbers, but still need to handle highlighting lines extra.
+ // Have to use divs so the full width of the code is highlighted
+ $close = 0;
+ for ($i = 0; $i < $n; ++$i) {
+ // Make lines have at least one space in them if they're empty
+ // BenBE: Checking emptiness using trim instead of relying on blanks
+ if ('' == trim($code[$i])) {
+ $code[$i] = '&nbsp;';
+ }
+ // fancy lines
+ if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS &&
+ $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
+ // Set the attributes to style the line
+ if ($this->use_classes) {
+ $parsed_code .= '<span class="xtra li2"><span class="de2">';
+ } else {
+ // This style "covers up" the special styles set for special lines
+ // so that styles applied to special lines don't apply to the actual
+ // code on that line
+ $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
+ .'<span style="' . $this->code_style .'">';
+ }
+ $close += 2;
+ }
+ //Is this some line with extra styles???
+ if (in_array($i + 1, $this->highlight_extra_lines)) {
+ if ($this->use_classes) {
+ if (isset($this->highlight_extra_lines_styles[$i])) {
+ $parsed_code .= "<span class=\"xtra lx$i\">";
+ } else {
+ $parsed_code .= "<span class=\"xtra ln-xtra\">";
+ }
+ } else {
+ $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
+ }
+ ++$close;
+ }
+
+ $parsed_code .= $code[$i];
+
+ if ($close) {
+ $parsed_code .= str_repeat('</span>', $close);
+ $close = 0;
+ }
+ elseif ($i + 1 < $n) {
+ $parsed_code .= "\n";
+ }
+ unset($code[$i]);
+ }
+
+ if ($this->header_type == GESHI_HEADER_PRE_VALID || $this->header_type == GESHI_HEADER_PRE_TABLE) {
+ $parsed_code .= '</pre>';
+ }
+ if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ $parsed_code .= '</td>';
+ }
+ }
+
+ $parsed_code .= $this->footer();
+ }
+
+ /**
+ * Creates the header for the code block (with correct attributes)
+ *
+ * @return string The header for the code block
+ * @since 1.0.0
+ * @access private
+ */
+ function header() {
+ // Get attributes needed
+ /**
+ * @todo Document behaviour change - class is outputted regardless of whether
+ * we're using classes or not. Same with style
+ */
+ $attributes = ' class="' . $this->_genCSSName($this->language);
+ if ($this->overall_class != '') {
+ $attributes .= " ".$this->_genCSSName($this->overall_class);
+ }
+ $attributes .= '"';
+
+ if ($this->overall_id != '') {
+ $attributes .= " id=\"{$this->overall_id}\"";
+ }
+ if ($this->overall_style != '' && !$this->use_classes) {
+ $attributes .= ' style="' . $this->overall_style . '"';
+ }
+
+ $ol_attributes = '';
+
+ if ($this->line_numbers_start != 1) {
+ $ol_attributes .= ' start="' . $this->line_numbers_start . '"';
+ }
+
+ // Get the header HTML
+ $header = $this->header_content;
+ if ($header) {
+ if ($this->header_type == GESHI_HEADER_PRE || $this->header_type == GESHI_HEADER_PRE_VALID) {
+ $header = str_replace("\n", '', $header);
+ }
+ $header = $this->replace_keywords($header);
+
+ if ($this->use_classes) {
+ $attr = ' class="head"';
+ } else {
+ $attr = " style=\"{$this->header_content_style}\"";
+ }
+ if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ $header = "<thead><tr><td colspan=\"2\" $attr>$header</td></tr></thead>";
+ } else {
+ $header = "<div$attr>$header</div>";
+ }
+ }
+
+ if (GESHI_HEADER_NONE == $this->header_type) {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ return "$header<ol$attributes$ol_attributes>";
+ }
+ return $header . ($this->force_code_block ? '<div>' : '');
+ }
+
+ // Work out what to return and do it
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ if ($this->header_type == GESHI_HEADER_PRE) {
+ return "<pre$attributes>$header<ol$ol_attributes>";
+ } elseif ($this->header_type == GESHI_HEADER_DIV ||
+ $this->header_type == GESHI_HEADER_PRE_VALID) {
+ return "<div$attributes>$header<ol$ol_attributes>";
+ } elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) {
+ return "<table$attributes>$header<tbody><tr class=\"li1\">";
+ }
+ } else {
+ if ($this->header_type == GESHI_HEADER_PRE) {
+ return "<pre$attributes>$header" .
+ ($this->force_code_block ? '<div>' : '');
+ } else {
+ return "<div$attributes>$header" .
+ ($this->force_code_block ? '<div>' : '');
+ }
+ }
+ }
+
+ /**
+ * Returns the footer for the code block.
+ *
+ * @return string The footer for the code block
+ * @since 1.0.0
+ * @access private
+ */
+ function footer() {
+ $footer = $this->footer_content;
+ if ($footer) {
+ if ($this->header_type == GESHI_HEADER_PRE) {
+ $footer = str_replace("\n", '', $footer);;
+ }
+ $footer = $this->replace_keywords($footer);
+
+ if ($this->use_classes) {
+ $attr = ' class="foot"';
+ } else {
+ $attr = " style=\"{$this->footer_content_style}\"";
+ }
+ if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ $footer = "<tfoot><tr><td colspan=\"2\">$footer</td></tr></tfoot>";
+ } else {
+ $footer = "<div$attr>$footer</div>";
+ }
+ }
+
+ if (GESHI_HEADER_NONE == $this->header_type) {
+ return ($this->line_numbers != GESHI_NO_LINE_NUMBERS) ? '</ol>' . $footer : $footer;
+ }
+
+ if ($this->header_type == GESHI_HEADER_DIV || $this->header_type == GESHI_HEADER_PRE_VALID) {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ return "</ol>$footer</div>";
+ }
+ return ($this->force_code_block ? '</div>' : '') .
+ "$footer</div>";
+ }
+ elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ return "</tr></tbody>$footer</table>";
+ }
+ return ($this->force_code_block ? '</div>' : '') .
+ "$footer</div>";
+ }
+ else {
+ if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ return "</ol>$footer</pre>";
+ }
+ return ($this->force_code_block ? '</div>' : '') .
+ "$footer</pre>";
+ }
+ }
+
+ /**
+ * Replaces certain keywords in the header and footer with
+ * certain configuration values
+ *
+ * @param string The header or footer content to do replacement on
+ * @return string The header or footer with replaced keywords
+ * @since 1.0.2
+ * @access private
+ */
+ function replace_keywords($instr) {
+ $keywords = $replacements = array();
+
+ $keywords[] = '<TIME>';
+ $keywords[] = '{TIME}';
+ $replacements[] = $replacements[] = number_format($time = $this->get_time(), 3);
+
+ $keywords[] = '<LANGUAGE>';
+ $keywords[] = '{LANGUAGE}';
+ $replacements[] = $replacements[] = $this->language_data['LANG_NAME'];
+
+ $keywords[] = '<VERSION>';
+ $keywords[] = '{VERSION}';
+ $replacements[] = $replacements[] = GESHI_VERSION;
+
+ $keywords[] = '<SPEED>';
+ $keywords[] = '{SPEED}';
+ if ($time <= 0) {
+ $speed = 'N/A';
+ } else {
+ $speed = strlen($this->source) / $time;
+ if ($speed >= 1024) {
+ $speed = sprintf("%.2f KB/s", $speed / 1024.0);
+ } else {
+ $speed = sprintf("%.0f B/s", $speed);
+ }
+ }
+ $replacements[] = $replacements[] = $speed;
+
+ return str_replace($keywords, $replacements, $instr);
+ }
+
+ /**
+ * Secure replacement for PHP built-in function htmlspecialchars().
+ *
+ * See ticket #427 (http://wush.net/trac/wikka/ticket/427) for the rationale
+ * for this replacement function.
+ *
+ * The INTERFACE for this function is almost the same as that for
+ * htmlspecialchars(), with the same default for quote style; however, there
+ * is no 'charset' parameter. The reason for this is as follows:
+ *
+ * The PHP docs say:
+ * "The third argument charset defines character set used in conversion."
+ *
+ * I suspect PHP's htmlspecialchars() is working at the byte-value level and
+ * thus _needs_ to know (or asssume) a character set because the special
+ * characters to be replaced could exist at different code points in
+ * different character sets. (If indeed htmlspecialchars() works at
+ * byte-value level that goes some way towards explaining why the
+ * vulnerability would exist in this function, too, and not only in
+ * htmlentities() which certainly is working at byte-value level.)
+ *
+ * This replacement function however works at character level and should
+ * therefore be "immune" to character set differences - so no charset
+ * parameter is needed or provided. If a third parameter is passed, it will
+ * be silently ignored.
+ *
+ * In the OUTPUT there is a minor difference in that we use '&#39;' instead
+ * of PHP's '&#039;' for a single quote: this provides compatibility with
+ * get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)
+ * (see comment by mikiwoz at yahoo dot co dot uk on
+ * http://php.net/htmlspecialchars); it also matches the entity definition
+ * for XML 1.0
+ * (http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters).
+ * Like PHP we use a numeric character reference instead of '&apos;' for the
+ * single quote. For the other special characters we use the named entity
+ * references, as PHP is doing.
+ *
+ * @author {@link http://wikkawiki.org/JavaWoman Marjolein Katsma}
+ *
+ * @license http://www.gnu.org/copyleft/lgpl.html
+ * GNU Lesser General Public License
+ * @copyright Copyright 2007, {@link http://wikkawiki.org/CreditsPage
+ * Wikka Development Team}
+ *
+ * @access private
+ * @param string $string string to be converted
+ * @param integer $quote_style
+ * - ENT_COMPAT: escapes &, <, > and double quote (default)
+ * - ENT_NOQUOTES: escapes only &, < and >
+ * - ENT_QUOTES: escapes &, <, >, double and single quotes
+ * @return string converted string
+ * @since 1.0.7.18
+ */
+ function hsc($string, $quote_style = ENT_COMPAT) {
+ // init
+ static $aTransSpecchar = array(
+ '&' => '&amp;',
+ '"' => '&quot;',
+ '<' => '&lt;',
+ '>' => '&gt;',
+
+ //This fix is related to SF#1923020, but has to be applied
+ //regardless of actually highlighting symbols.
+
+ //Circumvent a bug with symbol highlighting
+ //This is required as ; would produce undesirable side-effects if it
+ //was not to be processed as an entity.
+ ';' => '<SEMI>', // Force ; to be processed as entity
+ '|' => '<PIPE>' // Force | to be processed as entity
+ ); // ENT_COMPAT set
+
+ switch ($quote_style) {
+ case ENT_NOQUOTES: // don't convert double quotes
+ unset($aTransSpecchar['"']);
+ break;
+ case ENT_QUOTES: // convert single quotes as well
+ $aTransSpecchar["'"] = '&#39;'; // (apos) htmlspecialchars() uses '&#039;'
+ break;
+ }
+
+ // return translated string
+ return strtr($string, $aTransSpecchar);
+ }
+
+ function _genCSSName($name){
+ return (is_numeric($name[0]) ? '_' : '') . $name;
+ }
+
+ /**
+ * Returns a stylesheet for the highlighted code. If $economy mode
+ * is true, we only return the stylesheet declarations that matter for
+ * this code block instead of the whole thing
+ *
+ * @param boolean Whether to use economy mode or not
+ * @return string A stylesheet built on the data for the current language
+ * @since 1.0.0
+ */
+ function get_stylesheet($economy_mode = true) {
+ // If there's an error, chances are that the language file
+ // won't have populated the language data file, so we can't
+ // risk getting a stylesheet...
+ if ($this->error) {
+ return '';
+ }
+
+ //Check if the style rearrangements have been processed ...
+ //This also does some preprocessing to check which style groups are useable ...
+ if(!isset($this->language_data['NUMBERS_CACHE'])) {
+ $this->build_style_cache();
+ }
+
+ // First, work out what the selector should be. If there's an ID,
+ // that should be used, the same for a class. Otherwise, a selector
+ // of '' means that these styles will be applied anywhere
+ if ($this->overall_id) {
+ $selector = '#' . $this->_genCSSName($this->overall_id);
+ } else {
+ $selector = '.' . $this->_genCSSName($this->language);
+ if ($this->overall_class) {
+ $selector .= '.' . $this->_genCSSName($this->overall_class);
+ }
+ }
+ $selector .= ' ';
+
+ // Header of the stylesheet
+ if (!$economy_mode) {
+ $stylesheet = "/**\n".
+ " * GeSHi Dynamically Generated Stylesheet\n".
+ " * --------------------------------------\n".
+ " * Dynamically generated stylesheet for {$this->language}\n".
+ " * CSS class: {$this->overall_class}, CSS id: {$this->overall_id}\n".
+ " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
+ " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
+ " * --------------------------------------\n".
+ " */\n";
+ } else {
+ $stylesheet = "/**\n".
+ " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
+ " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
+ " */\n";
+ }
+
+ // Set the <ol> to have no effect at all if there are line numbers
+ // (<ol>s have margins that should be destroyed so all layout is
+ // controlled by the set_overall_style method, which works on the
+ // <pre> or <div> container). Additionally, set default styles for lines
+ if (!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
+ //$stylesheet .= "$selector, {$selector}ol, {$selector}ol li {margin: 0;}\n";
+ $stylesheet .= "$selector.de1, $selector.de2 {{$this->code_style}}\n";
+ }
+
+ // Add overall styles
+ // note: neglect economy_mode, empty styles are meaningless
+ if ($this->overall_style != '') {
+ $stylesheet .= "$selector {{$this->overall_style}}\n";
+ }
+
+ // Add styles for links
+ // note: economy mode does not make _any_ sense here
+ // either the style is empty and thus no selector is needed
+ // or the appropriate key is given.
+ foreach ($this->link_styles as $key => $style) {
+ if ($style != '') {
+ switch ($key) {
+ case GESHI_LINK:
+ $stylesheet .= "{$selector}a:link {{$style}}\n";
+ break;
+ case GESHI_HOVER:
+ $stylesheet .= "{$selector}a:hover {{$style}}\n";
+ break;
+ case GESHI_ACTIVE:
+ $stylesheet .= "{$selector}a:active {{$style}}\n";
+ break;
+ case GESHI_VISITED:
+ $stylesheet .= "{$selector}a:visited {{$style}}\n";
+ break;
+ }
+ }
+ }
+
+ // Header and footer
+ // note: neglect economy_mode, empty styles are meaningless
+ if ($this->header_content_style != '') {
+ $stylesheet .= "$selector.head {{$this->header_content_style}}\n";
+ }
+ if ($this->footer_content_style != '') {
+ $stylesheet .= "$selector.foot {{$this->footer_content_style}}\n";
+ }
+
+ // Styles for important stuff
+ // note: neglect economy_mode, empty styles are meaningless
+ if ($this->important_styles != '') {
+ $stylesheet .= "$selector.imp {{$this->important_styles}}\n";
+ }
+
+ // Simple line number styles
+ if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->line_style1 != '') {
+ $stylesheet .= "{$selector}li, {$selector}.li1 {{$this->line_style1}}\n";
+ }
+ if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->table_linenumber_style != '') {
+ $stylesheet .= "{$selector}.ln {{$this->table_linenumber_style}}\n";
+ }
+ // If there is a style set for fancy line numbers, echo it out
+ if ((!$economy_mode || $this->line_numbers == GESHI_FANCY_LINE_NUMBERS) && $this->line_style2 != '') {
+ $stylesheet .= "{$selector}.li2 {{$this->line_style2}}\n";
+ }
+
+ // note: empty styles are meaningless
+ foreach ($this->language_data['STYLES']['KEYWORDS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode ||
+ (isset($this->lexic_permissions['KEYWORDS'][$group]) &&
+ $this->lexic_permissions['KEYWORDS'][$group]))) {
+ $stylesheet .= "$selector.kw$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['COMMENTS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode ||
+ (isset($this->lexic_permissions['COMMENTS'][$group]) &&
+ $this->lexic_permissions['COMMENTS'][$group]) ||
+ (!empty($this->language_data['COMMENT_REGEXP']) &&
+ !empty($this->language_data['COMMENT_REGEXP'][$group])))) {
+ $stylesheet .= "$selector.co$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['ESCAPE_CHAR'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['ESCAPE_CHAR'])) {
+ // NEW: since 1.0.8 we have to handle hardescapes
+ if ($group === 'HARD') {
+ $group = '_h';
+ }
+ $stylesheet .= "$selector.es$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['BRACKETS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['BRACKETS'])) {
+ $stylesheet .= "$selector.br$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['SYMBOLS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['SYMBOLS'])) {
+ $stylesheet .= "$selector.sy$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['STRINGS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['STRINGS'])) {
+ // NEW: since 1.0.8 we have to handle hardquotes
+ if ($group === 'HARD') {
+ $group = '_h';
+ }
+ $stylesheet .= "$selector.st$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['NUMBERS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['NUMBERS'])) {
+ $stylesheet .= "$selector.nu$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['METHODS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode || $this->lexic_permissions['METHODS'])) {
+ $stylesheet .= "$selector.me$group {{$styles}}\n";
+ }
+ }
+ // note: neglect economy_mode, empty styles are meaningless
+ foreach ($this->language_data['STYLES']['SCRIPT'] as $group => $styles) {
+ if ($styles != '') {
+ $stylesheet .= "$selector.sc$group {{$styles}}\n";
+ }
+ }
+ foreach ($this->language_data['STYLES']['REGEXPS'] as $group => $styles) {
+ if ($styles != '' && (!$economy_mode ||
+ (isset($this->lexic_permissions['REGEXPS'][$group]) &&
+ $this->lexic_permissions['REGEXPS'][$group]))) {
+ if (is_array($this->language_data['REGEXPS'][$group]) &&
+ array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$group])) {
+ $stylesheet .= "$selector.";
+ $stylesheet .= $this->language_data['REGEXPS'][$group][GESHI_CLASS];
+ $stylesheet .= " {{$styles}}\n";
+ } else {
+ $stylesheet .= "$selector.re$group {{$styles}}\n";
+ }
+ }
+ }
+ // Styles for lines being highlighted extra
+ if (!$economy_mode || (count($this->highlight_extra_lines)!=count($this->highlight_extra_lines_styles))) {
+ $stylesheet .= "{$selector}.ln-xtra, {$selector}li.ln-xtra, {$selector}div.ln-xtra {{$this->highlight_extra_lines_style}}\n";
+ }
+ $stylesheet .= "{$selector}span.xtra { display:block; }\n";
+ foreach ($this->highlight_extra_lines_styles as $lineid => $linestyle) {
+ $stylesheet .= "{$selector}.lx$lineid, {$selector}li.lx$lineid, {$selector}div.lx$lineid {{$linestyle}}\n";
+ }
+
+ return $stylesheet;
+ }
+
+ /**
+ * Get's the style that is used for the specified line
+ *
+ * @param int The line number information is requested for
+ * @access private
+ * @since 1.0.7.21
+ */
+ function get_line_style($line) {
+ //$style = null;
+ $style = null;
+ if (isset($this->highlight_extra_lines_styles[$line])) {
+ $style = $this->highlight_extra_lines_styles[$line];
+ } else { // if no "extra" style assigned
+ $style = $this->highlight_extra_lines_style;
+ }
+
+ return $style;
+ }
+
+ /**
+ * this functions creates an optimized regular expression list
+ * of an array of strings.
+ *
+ * Example:
+ * <code>$list = array('faa', 'foo', 'foobar');
+ * => string 'f(aa|oo(bar)?)'</code>
+ *
+ * @param $list array of (unquoted) strings
+ * @param $regexp_delimiter your regular expression delimiter, @see preg_quote()
+ * @return string for regular expression
+ * @author Milian Wolff <mail@milianw.de>
+ * @since 1.0.8
+ * @access private
+ */
+ function optimize_regexp_list($list, $regexp_delimiter = '/') {
+ $regex_chars = array('.', '\\', '+', '-', '*', '?', '[', '^', ']', '$',
+ '(', ')', '{', '}', '=', '!', '<', '>', '|', ':', $regexp_delimiter);
+ sort($list);
+ $regexp_list = array('');
+ $num_subpatterns = 0;
+ $list_key = 0;
+
+ // the tokens which we will use to generate the regexp list
+ $tokens = array();
+ $prev_keys = array();
+ // go through all entries of the list and generate the token list
+ $cur_len = 0;
+ for ($i = 0, $i_max = count($list); $i < $i_max; ++$i) {
+ if ($cur_len > GESHI_MAX_PCRE_LENGTH) {
+ // seems like the length of this pcre is growing exorbitantly
+ $regexp_list[++$list_key] = $this->_optimize_regexp_list_tokens_to_string($tokens);
+ $num_subpatterns = substr_count($regexp_list[$list_key], '(?:');
+ $tokens = array();
+ $cur_len = 0;
+ }
+ $level = 0;
+ $entry = preg_quote((string) $list[$i], $regexp_delimiter);
+ $pointer = &$tokens;
+ // properly assign the new entry to the correct position in the token array
+ // possibly generate smaller common denominator keys
+ while (true) {
+ // get the common denominator
+ if (isset($prev_keys[$level])) {
+ if ($prev_keys[$level] == $entry) {
+ // this is a duplicate entry, skip it
+ continue 2;
+ }
+ $char = 0;
+ while (isset($entry[$char]) && isset($prev_keys[$level][$char])
+ && $entry[$char] == $prev_keys[$level][$char]) {
+ ++$char;
+ }
+ if ($char > 0) {
+ // this entry has at least some chars in common with the current key
+ if ($char == strlen($prev_keys[$level])) {
+ // current key is totally matched, i.e. this entry has just some bits appended
+ $pointer = &$pointer[$prev_keys[$level]];
+ } else {
+ // only part of the keys match
+ $new_key_part1 = substr($prev_keys[$level], 0, $char);
+ $new_key_part2 = substr($prev_keys[$level], $char);
+
+ if (in_array($new_key_part1[0], $regex_chars)
+ || in_array($new_key_part2[0], $regex_chars)) {
+ // this is bad, a regex char as first character
+ $pointer[$entry] = array('' => true);
+ array_splice($prev_keys, $level, count($prev_keys), $entry);
+ $cur_len += strlen($entry);
+ continue;
+ } else {
+ // relocate previous tokens
+ $pointer[$new_key_part1] = array($new_key_part2 => $pointer[$prev_keys[$level]]);
+ unset($pointer[$prev_keys[$level]]);
+ $pointer = &$pointer[$new_key_part1];
+ // recreate key index
+ array_splice($prev_keys, $level, count($prev_keys), array($new_key_part1, $new_key_part2));
+ $cur_len += strlen($new_key_part2);
+ }
+ }
+ ++$level;
+ $entry = substr($entry, $char);
+ continue;
+ }
+ // else: fall trough, i.e. no common denominator was found
+ }
+ if ($level == 0 && !empty($tokens)) {
+ // we can dump current tokens into the string and throw them away afterwards
+ $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens);
+ $new_subpatterns = substr_count($new_entry, '(?:');
+ if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + $new_subpatterns > GESHI_MAX_PCRE_SUBPATTERNS) {
+ $regexp_list[++$list_key] = $new_entry;
+ $num_subpatterns = $new_subpatterns;
+ } else {
+ if (!empty($regexp_list[$list_key])) {
+ $new_entry = '|' . $new_entry;
+ }
+ $regexp_list[$list_key] .= $new_entry;
+ $num_subpatterns += $new_subpatterns;
+ }
+ $tokens = array();
+ $cur_len = 0;
+ }
+ // no further common denominator found
+ $pointer[$entry] = array('' => true);
+ array_splice($prev_keys, $level, count($prev_keys), $entry);
+
+ $cur_len += strlen($entry);
+ break;
+ }
+ unset($list[$i]);
+ }
+ // make sure the last tokens get converted as well
+ $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens);
+ if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + substr_count($new_entry, '(?:') > GESHI_MAX_PCRE_SUBPATTERNS) {
+ if ( !empty($regexp_list[$list_key]) ) {
+ ++$list_key;
+ }
+ $regexp_list[$list_key] = $new_entry;
+ } else {
+ if (!empty($regexp_list[$list_key])) {
+ $new_entry = '|' . $new_entry;
+ }
+ $regexp_list[$list_key] .= $new_entry;
+ }
+ return $regexp_list;
+ }
+ /**
+ * this function creates the appropriate regexp string of an token array
+ * you should not call this function directly, @see $this->optimize_regexp_list().
+ *
+ * @param &$tokens array of tokens
+ * @param $recursed bool to know wether we recursed or not
+ * @return string
+ * @author Milian Wolff <mail@milianw.de>
+ * @since 1.0.8
+ * @access private
+ */
+ function _optimize_regexp_list_tokens_to_string(&$tokens, $recursed = false) {
+ $list = '';
+ foreach ($tokens as $token => $sub_tokens) {
+ $list .= $token;
+ $close_entry = isset($sub_tokens['']);
+ unset($sub_tokens['']);
+ if (!empty($sub_tokens)) {
+ $list .= '(?:' . $this->_optimize_regexp_list_tokens_to_string($sub_tokens, true) . ')';
+ if ($close_entry) {
+ // make sub_tokens optional
+ $list .= '?';
+ }
+ }
+ $list .= '|';
+ }
+ if (!$recursed) {
+ // do some optimizations
+ // common trailing strings
+ // BUGGY!
+ //$list = preg_replace_callback('#(?<=^|\:|\|)\w+?(\w+)(?:\|.+\1)+(?=\|)#', create_function(
+ // '$matches', 'return "(?:" . preg_replace("#" . preg_quote($matches[1], "#") . "(?=\||$)#", "", $matches[0]) . ")" . $matches[1];'), $list);
+ // (?:p)? => p?
+ $list = preg_replace('#\(\?\:(.)\)\?#', '\1?', $list);
+ // (?:a|b|c|d|...)? => [abcd...]?
+ // TODO: a|bb|c => [ac]|bb
+ $list = preg_replace_callback('#\(\?\:((?:.\|)+.)\)#', function ($matches) { return "[" . str_replace("|", "", $matches[1]) . "]"; }, $list);
+ }
+ // return $list without trailing pipe
+ return substr($list, 0, -1);
+ }
+} // End Class GeSHi
+
+
+if (!function_exists('geshi_highlight')) {
+ /**
+ * Easy way to highlight stuff. Behaves just like highlight_string
+ *
+ * @param string The code to highlight
+ * @param string The language to highlight the code in
+ * @param string The path to the language files. You can leave this blank if you need
+ * as from version 1.0.7 the path should be automatically detected
+ * @param boolean Whether to return the result or to echo
+ * @return string The code highlighted (if $return is true)
+ * @since 1.0.2
+ */
+ function geshi_highlight($string, $language, $path = null, $return = false) {
+ $geshi = new GeSHi($string, $language, $path);
+ $geshi->set_header_type(GESHI_HEADER_NONE);
+
+ if ($return) {
+ return '<code>' . $geshi->parse_code() . '</code>';
+ }
+
+ echo '<code>' . $geshi->parse_code() . '</code>';
+
+ if ($geshi->error()) {
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/plugins/dokuwiki/inc/geshi/actionscript-french.php b/plugins/dokuwiki/inc/geshi/actionscript-french.php
new file mode 100644
index 0000000..e816050
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/actionscript-french.php
@@ -0,0 +1,957 @@
+<?php
+/*************************************************************************************
+ * actionscript.php
+ * ----------------
+ * Author: Steffen Krause (Steffen.krause@muse.de)
+ * Copyright: (c) 2004 Steffen Krause, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.9
+ * CVS Revision Version: $Revision: 1.9 $
+ * Date Started: 2004/06/20
+ * Last Modified: $Date: 2006/04/23 01:14:41 $
+ *
+ * Actionscript language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/25 (1.0.2)
+ * Author [ NikO ] - http://niko.informatif.org
+ * - add full link for myInstance.methods to http://wiki.media-box.net/documentation/flash
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Actionscript',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '#include',
+ 'for',
+ 'foreach',
+ 'if',
+ 'elseif',
+ 'else',
+ 'while',
+ 'do',
+ 'dowhile',
+ 'endwhile',
+ 'endif',
+ 'switch',
+ 'case',
+ 'endswitch',
+ 'break',
+ 'continue',
+ 'in',
+ 'null',
+ 'false',
+ 'true',
+ 'var',
+ 'default',
+ 'new',
+ '_global',
+ 'undefined',
+ 'super'
+ ),
+ 2 => array(
+ 'static',
+ 'private',
+ 'public',
+ 'class',
+ 'extends',
+ 'implements',
+ 'import',
+ 'return',
+ 'trace',
+ '_quality',
+ '_root',
+ 'set',
+ 'setInterval',
+ 'setProperty',
+ 'stopAllSounds',
+ 'targetPath',
+ 'this',
+ 'typeof',
+ 'unescape',
+ 'updateAfterEvent'
+ ),
+ 3 => array (
+ 'Accessibility',
+ 'Array',
+ 'Boolean',
+ 'Button',
+ 'Camera',
+ 'Color',
+ 'ContextMenuItem',
+ 'ContextMenu',
+ 'Cookie',
+ 'Date',
+ 'Error',
+ 'function',
+ 'FWEndCommand',
+ 'FWJavascript',
+ 'Key',
+ 'LoadMovieNum',
+ 'LoadMovie',
+ 'LoadVariablesNum',
+ 'LoadVariables',
+ 'LoadVars',
+ 'LocalConnection',
+ 'Math',
+ 'Microphone',
+ 'MMExecute',
+ 'MMEndCommand',
+ 'MMSave',
+ 'Mouse',
+ 'MovieClipLoader',
+ 'MovieClip',
+ 'NetConnexion',
+ 'NetStream',
+ 'Number',
+ 'Object',
+ 'printAsBitmapNum',
+ 'printNum',
+ 'printAsBitmap',
+ 'printJob',
+ 'print',
+ 'Selection',
+ 'SharedObject',
+ 'Sound',
+ 'Stage',
+ 'String',
+ 'System',
+ 'TextField',
+ 'TextFormat',
+ 'Tween',
+ 'Video',
+ 'XMLUI',
+ 'XMLNode',
+ 'XMLSocket',
+ 'XML'
+ ),
+ 4 => array (
+ 'isactive',
+ 'updateProperties'
+ ),
+ 5 => array (
+ 'callee',
+ 'caller',
+ ),
+ 6 => array (
+ 'concat',
+ 'join',
+ 'pop',
+ 'push',
+ 'reverse',
+ 'shift',
+ 'slice',
+ 'sort',
+ 'sortOn',
+ 'splice',
+ 'toString',
+ 'unshift'
+ ),
+ 7 => array (
+ 'valueOf'
+ ),
+ 8 => array (
+ 'onDragOut',
+ 'onDragOver',
+ 'onKeyUp',
+ 'onKillFocus',
+ 'onPress',
+ 'onRelease',
+ 'onReleaseOutside',
+ 'onRollOut',
+ 'onRollOver',
+ 'onSetFocus'
+ ),
+ 9 => array (
+ 'setMode',
+ 'setMotionLevel',
+ 'setQuality',
+ 'activityLevel',
+ 'bandwidth',
+ 'currentFps',
+ 'fps',
+ 'index',
+ 'motionLevel',
+ 'motionTimeOut',
+ 'muted',
+ 'names',
+ 'quality',
+ 'onActivity',
+ 'onStatus'
+ ),
+ 10 => array (
+ 'getRGB',
+ 'setRGB',
+ 'getTransform',
+ 'setTransform'
+ ),
+ 11 => array (
+ 'caption',
+ 'enabled',
+ 'separatorBefore',
+ 'visible',
+ 'onSelect'
+ ),
+ 12 => array (
+ 'setCookie',
+ 'getcookie'
+ ),
+ 13 => array (
+ 'hideBuiltInItems',
+ 'builtInItems',
+ 'customItems',
+ 'onSelect'
+ ),
+ 14 => array (
+ 'CustomActions.get',
+ 'CustomActions.install',
+ 'CustomActions.list',
+ 'CustomActions.uninstall',
+ ),
+ 15 => array (
+ 'getDate',
+ 'getDay',
+ 'getFullYear',
+ 'getHours',
+ 'getMilliseconds',
+ 'getMinutes',
+ 'getMonth',
+ 'getSeconds',
+ 'getTime',
+ 'getTimezoneOffset',
+ 'getUTCDate',
+ 'getUTCDay',
+ 'getUTCFullYear',
+ 'getUTCHours',
+ 'getUTCMinutes',
+ 'getUTCMilliseconds',
+ 'getUTCMonth',
+ 'getUTCSeconds',
+ 'getYear',
+ 'setDate',
+ 'setFullYear',
+ 'setHours',
+ 'setMilliseconds',
+ 'setMinutes',
+ 'setMonth',
+ 'setSeconds',
+ 'setTime',
+ 'setUTCDate',
+ 'setUTCDay',
+ 'setUTCFullYear',
+ 'setUTCHours',
+ 'setUTCMinutes',
+ 'setUTCMilliseconds',
+ 'setUTCMonth',
+ 'setUTCSeconds',
+ 'setYear',
+ 'UTC'
+ ),
+ 16 => array (
+ 'message',
+ 'name',
+ 'throw',
+ 'try',
+ 'catch',
+ 'finally'
+ ),
+ 17 => array (
+ 'apply',
+ 'call'
+ ),
+ 18 => array (
+ 'BACKSPACE',
+ 'CAPSLOCK',
+ 'CONTROL',
+ 'DELETEKEY',
+ 'DOWN',
+ 'END',
+ 'ENTER',
+ 'ESCAPE',
+ 'getAscii',
+ 'getCode',
+ 'HOME',
+ 'INSERT',
+ 'isDown',
+ 'isToggled',
+ 'LEFT',
+ 'onKeyDown',
+ 'onKeyUp',
+ 'PGDN',
+ 'PGUP',
+ 'RIGHT',
+ 'SPACE',
+ 'TAB',
+ 'UP'
+ ),
+ 19 => array (
+ 'addRequestHeader',
+ 'contentType',
+ 'decode'
+ ),
+ 20 => array (
+ 'allowDomain',
+ 'allowInsecureDomain',
+ 'close',
+ 'domain'
+ ),
+ 21 => array (
+ 'abs',
+ 'acos',
+ 'asin',
+ 'atan',
+ 'atan2',
+ 'ceil',
+ 'cos',
+ 'exp',
+ 'floor',
+ 'log',
+ 'LN2',
+ 'LN10',
+ 'LOG2E',
+ 'LOG10E',
+ 'max',
+ 'min',
+ 'PI',
+ 'pow',
+ 'random',
+ 'sin',
+ 'SQRT1_2',
+ 'sqrt',
+ 'tan',
+ 'round',
+ 'SQRT2'
+ ),
+ 22 => array (
+ 'activityLevel',
+ 'muted',
+ 'names',
+ 'onActivity',
+ 'onStatus',
+ 'setRate',
+ 'setGain',
+ 'gain',
+ 'rate',
+ 'setSilenceLevel',
+ 'setUseEchoSuppression',
+ 'silenceLevel',
+ 'silenceTimeOut',
+ 'useEchoSuppression'
+ ),
+ 23 => array (
+ 'hide',
+ 'onMouseDown',
+ 'onMouseMove',
+ 'onMouseUp',
+ 'onMouseWeel',
+ 'show'
+ ),
+ 24 => array (
+ '_alpha',
+ 'attachAudio',
+ 'attachMovie',
+ 'beginFill',
+ 'beginGradientFill',
+ 'clear',
+ 'createEmptyMovieClip',
+ 'createTextField',
+ '_current',
+ 'curveTo',
+ '_dropTarget',
+ 'duplicateMovieClip',
+ 'endFill',
+ 'focusEnabled',
+ 'enabled',
+ '_focusrec',
+ '_framesLoaded',
+ 'getBounds',
+ 'getBytesLoaded',
+ 'getBytesTotal',
+ 'getDepth',
+ 'getInstanceAtDepth',
+ 'getNextHighestDepth',
+ 'getSWFVersion',
+ 'getTextSnapshot',
+ 'getURL',
+ 'globalToLocal',
+ 'gotoAndPlay',
+ 'gotoAndStop',
+ '_height',
+ 'hitArea',
+ 'hitTest',
+ 'lineStyle',
+ 'lineTo',
+ 'localToGlobal',
+ '_lockroot',
+ 'menu',
+ 'onUnload',
+ '_parent',
+ 'play',
+ 'prevFrame',
+ '_quality',
+ 'removeMovieClip',
+ '_rotation',
+ 'setMask',
+ '_soundbuftime',
+ 'startDrag',
+ 'stopDrag',
+ 'stop',
+ 'swapDepths',
+ 'tabChildren',
+ '_target',
+ '_totalFrames',
+ 'trackAsMenu',
+ 'unloadMovie',
+ 'useHandCursor',
+ '_visible',
+ '_width',
+ '_xmouse',
+ '_xscale',
+ '_x',
+ '_ymouse',
+ '_yscale',
+ '_y'
+ ),
+ 25 => array (
+ 'getProgress',
+ 'loadClip',
+ 'onLoadComplete',
+ 'onLoadError',
+ 'onLoadInit',
+ 'onLoadProgress',
+ 'onLoadStart'
+ ),
+ 26 => array (
+ 'bufferLength',
+ 'currentFps',
+ 'seek',
+ 'setBufferTime',
+ 'bufferTime',
+ 'time',
+ 'pause'
+ ),
+ 27 => array (
+ 'MAX_VALUE',
+ 'MIN_VALUE',
+ 'NEGATIVE_INFINITY',
+ 'POSITIVE_INFINITY'
+ ),
+ 28 => array (
+ 'addProperty',
+ 'constructor',
+ '__proto__',
+ 'registerClass',
+ '__resolve',
+ 'unwatch',
+ 'watch',
+ 'onUpDate'
+ ),
+ 29 => array (
+ 'addPage'
+ ),
+ 30 => array (
+ 'getBeginIndex',
+ 'getCaretIndex',
+ 'getEndIndex',
+ 'setSelection'
+ ),
+ 31 => array (
+ 'flush',
+ 'getLocal',
+ 'getSize'
+ ),
+ 32 => array (
+ 'attachSound',
+ 'duration',
+ 'getPan',
+ 'getVolume',
+ 'onID3',
+ 'loadSound',
+ 'id3',
+ 'onSoundComplete',
+ 'position',
+ 'setPan',
+ 'setVolume'
+ ),
+ 33 => array (
+ 'getBeginIndex',
+ 'getCaretIndex',
+ 'getEndIndex',
+ 'setSelection'
+ ),
+ 34 => array (
+ 'getEndIndex',
+ ),
+ 35 => array (
+ 'align',
+ 'height',
+ 'width',
+ 'onResize',
+ 'scaleMode',
+ 'showMenu'
+ ),
+ 36 => array (
+ 'charAt',
+ 'charCodeAt',
+ 'concat',
+ 'fromCharCode',
+ 'indexOf',
+ 'lastIndexOf',
+ 'substr',
+ 'substring',
+ 'toLowerCase',
+ 'toUpperCase'
+ ),
+ 37 => array (
+ 'avHardwareDisable',
+ 'hasAccessibility',
+ 'hasAudioEncoder',
+ 'hasAudio',
+ 'hasEmbeddedVideo',
+ 'hasMP3',
+ 'hasPrinting',
+ 'hasScreenBroadcast',
+ 'hasScreenPlayback',
+ 'hasStreamingAudio',
+ 'hasStreamingVideo',
+ 'hasVideoEncoder',
+ 'isDebugger',
+ 'language',
+ 'localFileReadDisable',
+ 'manufacturer',
+ 'os',
+ 'pixelAspectRatio',
+ 'playerType',
+ 'screenColor',
+ 'screenDPI',
+ 'screenResolutionX',
+ 'screenResolutionY',
+ 'serverString',
+ 'version'
+ ),
+ 38 => array (
+ 'allowDomain',
+ 'allowInsecureDomain',
+ 'loadPolicyFile'
+ ),
+ 39 => array (
+ 'exactSettings',
+ 'setClipboard',
+ 'showSettings',
+ 'useCodepage'
+ ),
+ 40 => array (
+ 'getStyle',
+ 'getStyleNames',
+ 'parseCSS',
+ 'setStyle',
+ 'transform'
+ ),
+ 41 => array (
+ 'autoSize',
+ 'background',
+ 'backgroundColor',
+ 'border',
+ 'borderColor',
+ 'bottomScroll',
+ 'condenseWhite',
+ 'embedFonts',
+ 'getFontList',
+ 'getNewTextFormat',
+ 'getTextFormat',
+ 'hscroll',
+ 'htmlText',
+ 'html',
+ 'maxChars',
+ 'maxhscroll',
+ 'maxscroll',
+ 'mouseWheelEnabled',
+ 'multiline',
+ 'onScroller',
+ 'password',
+ 'removeTextField',
+ 'replaceSel',
+ 'replaceText',
+ 'restrict',
+ 'scroll',
+ 'selectable',
+ 'setNewTextFormat',
+ 'setTextFormat',
+ 'styleSheet',
+ 'tabEnabled',
+ 'tabIndex',
+ 'textColor',
+ 'textHeight',
+ 'textWidth',
+ 'text',
+ 'type',
+ '_url',
+ 'variable',
+ 'wordWrap'
+ ),
+ 42 => array (
+ 'blockIndent',
+ 'bold',
+ 'bullet',
+ 'font',
+ 'getTextExtent',
+ 'indent',
+ 'italic',
+ 'leading',
+ 'leftMargin',
+ 'rightMargin',
+ 'size',
+ 'tabStops',
+ 'underline'
+ ),
+ 43 => array (
+ 'findText',
+ 'getCount',
+ 'getSelected',
+ 'getSelectedText',
+ 'getText',
+ 'hitTestTextNearPos',
+ 'setSelectColor',
+ 'setSelected'
+ ),
+ 44 => array (
+ 'begin',
+ 'change',
+ 'continueTo',
+ 'fforward',
+ 'finish',
+ 'func',
+ 'FPS',
+ 'getPosition',
+ 'isPlaying',
+ 'looping',
+ 'obj',
+ 'onMotionChanged',
+ 'onMotionFinished',
+ 'onMotionLooped',
+ 'onMotionStarted',
+ 'onMotionResumed',
+ 'onMotionStopped',
+ 'prop',
+ 'rewind',
+ 'resume',
+ 'setPosition',
+ 'time',
+ 'userSeconds',
+ 'yoyo'
+ ),
+ 45 => array (
+ 'attachVideo',
+ 'deblocking',
+ 'smoothing'
+ ),
+ 46 => array (
+ 'addRequestHeader',
+ 'appendChild',
+ 'attributes',
+ 'childNodes',
+ 'cloneNode',
+ 'contentType',
+ 'createElement',
+ 'createTextNode',
+ 'docTypeDecl',
+ 'firstChild',
+ 'hasChildNodes',
+ 'ignoreWhite',
+ 'insertBefore',
+ 'lastChild',
+ 'nextSibling',
+ 'nodeName',
+ 'nodeType',
+ 'nodeValue',
+ 'parentNode',
+ 'parseXML',
+ 'previousSibling',
+ 'removeNode',
+ 'xmlDecl'
+ ),
+ 47 => array (
+ 'onClose',
+ 'onXML'
+ ),
+ 48 => array (
+ 'add',
+ 'and',
+ '_highquality',
+ 'chr',
+ 'eq',
+ 'ge',
+ 'ifFrameLoaded',
+ 'int',
+ 'le',
+ 'it',
+ 'mbchr',
+ 'mblength',
+ 'mbord',
+ 'ne',
+ 'not',
+ 'or',
+ 'ord',
+ 'tellTarget',
+ 'toggleHighQuality'
+ ),
+ 49 => array (
+ 'ASSetPropFlags',
+ 'ASnative',
+ 'ASconstructor',
+ 'AsSetupError',
+ 'FWEndCommand',
+ 'FWJavascript',
+ 'MMEndCommand',
+ 'MMSave',
+ 'XMLUI'
+ ),
+ 50 => array (
+ 'System.capabilities'
+ ),
+ 51 => array (
+ 'System.security'
+ ),
+ 52 => array (
+ 'TextField.StyleSheet'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '!', '@', '%', '&', '*', '|', '/', '<', '>','='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ 4 => true,
+ 5 => true,
+ 6 => true,
+ 7 => true,
+ 8 => true,
+ 9 => true,
+ 10 => true,
+ 11 => true,
+ 12 => true,
+ 13 => true,
+ 14 => true,
+ 15 => true,
+ 16 => true,
+ 17 => true,
+ 18 => true,
+ 19 => true,
+ 20 => true,
+ 21 => true,
+ 22 => true,
+ 23 => true,
+ 24 => true,
+ 25 => true,
+ 26 => true,
+ 27 => true,
+ 28 => true,
+ 29 => true,
+ 30 => true,
+ 31 => true,
+ 32 => true,
+ 33 => true,
+ 34 => true,
+ 35 => true,
+ 36 => true,
+ 37 => true,
+ 38 => true,
+ 39 => true,
+ 40 => true,
+ 41 => true,
+ 42 => true,
+ 43 => true,
+ 44 => true,
+ 45 => true,
+ 46 => true,
+ 47 => true,
+ 48 => true,
+ 49 => true,
+ 50 => true,
+ 51 => true,
+ 52 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000ff;',
+ 2 => 'color: #006600;',
+ 3 => 'color: #000080;',
+ 4 => 'color: #006600;',
+ 5 => 'color: #006600;',
+ 6 => 'color: #006600;',
+ 7 => 'color: #006600;',
+ 8 => 'color: #006600;',
+ 9 => 'color: #006600;',
+ 10 => 'color: #006600;',
+ 11 => 'color: #006600;',
+ 12 => 'color: #006600;',
+ 13 => 'color: #006600;',
+ 14 => 'color: #006600;',
+ 15 => 'color: #006600;',
+ 16 => 'color: #006600;',
+ 17 => 'color: #006600;',
+ 18 => 'color: #006600;',
+ 19 => 'color: #006600;',
+ 20 => 'color: #006600;',
+ 21 => 'color: #006600;',
+ 22 => 'color: #006600;',
+ 23 => 'color: #006600;',
+ 24 => 'color: #006600;',
+ 25 => 'color: #006600;',
+ 26 => 'color: #006600;',
+ 27 => 'color: #006600;',
+ 28 => 'color: #006600;',
+ 29 => 'color: #006600;',
+ 30 => 'color: #006600;',
+ 31 => 'color: #006600;',
+ 32 => 'color: #006600;',
+ 33 => 'color: #006600;',
+ 34 => 'color: #006600;',
+ 35 => 'color: #006600;',
+ 36 => 'color: #006600;',
+ 37 => 'color: #006600;',
+ 38 => 'color: #006600;',
+ 39 => 'color: #006600;',
+ 40 => 'color: #006600;',
+ 41 => 'color: #006600;',
+ 42 => 'color: #006600;',
+ 43 => 'color: #006600;',
+ 44 => 'color: #006600;',
+ 45 => 'color: #006600;',
+ 46 => 'color: #006600;',
+ 47 => 'color: #006600;',
+ 48 => 'color: #CC0000;',
+ 49 => 'color: #5700d1;',
+ 50 => 'color: #006600;',
+ 51 => 'color: #006600;',
+ 52 => 'color: #CC0000;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #ff8000; font-style: italic;',
+ 2 => 'color: #ff8000; font-style: italic;',
+ 'MULTI' => 'color: #ff8000; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #333333;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #333333; background-color: #eeeeee;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #c50000;'
+ ),
+
+ 'SYMBOLS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => 'http://wiki.media-box.net/documentation/flash/{FNAME}',
+ 2 => 'http://wiki.media-box.net/documentation/flash/{FNAME}',
+ 3 => 'http://wiki.media-box.net/documentation/flash/{FNAME}',
+ 4 => 'http://wiki.media-box.net/documentation/flash/accessibility/{FNAME}',
+ 5 => 'http://wiki.media-box.net/documentation/flash/arguments/{FNAME}',
+ 6 => 'http://wiki.media-box.net/documentation/flash/array/{FNAME}',
+ 7 => 'http://wiki.media-box.net/documentation/flash/boolean/{FNAME}',
+ 8 => 'http://wiki.media-box.net/documentation/flash/button/{FNAME}',
+ 9 => 'http://wiki.media-box.net/documentation/flash/camera/{FNAME}',
+ 10 => 'http://wiki.media-box.net/documentation/flash/color/{FNAME}',
+ 11 => 'http://wiki.media-box.net/documentation/flash/contextmenuitem/{FNAME}',
+ 12 => 'http://wiki.media-box.net/documentation/flash/contextmenu/{FNAME}',
+ 13 => 'http://wiki.media-box.net/documentation/flash/cookie/{FNAME}',
+ 14 => 'http://wiki.media-box.net/documentation/flash/customactions/{FNAME}',
+ 15 => 'http://wiki.media-box.net/documentation/flash/date/{FNAME}',
+ 16 => 'http://wiki.media-box.net/documentation/flash/error/{FNAME}',
+ 17 => 'http://wiki.media-box.net/documentation/flash/function/{FNAME}',
+ 18 => 'http://wiki.media-box.net/documentation/flash/key/{FNAME}',
+ 19 => 'http://wiki.media-box.net/documentation/flash/loadvars/{FNAME}',
+ 20 => 'http://wiki.media-box.net/documentation/flash/localconnection/{FNAME}',
+ 21 => 'http://wiki.media-box.net/documentation/flash/math/{FNAME}',
+ 22 => 'http://wiki.media-box.net/documentation/flash/microphone/{FNAME}',
+ 23 => 'http://wiki.media-box.net/documentation/flash/mouse/{FNAME}',
+ 24 => 'http://wiki.media-box.net/documentation/flash/movieclip/{FNAME}',
+ 25 => 'http://wiki.media-box.net/documentation/flash/moviecliploader/{FNAME}',
+ 26 => 'http://wiki.media-box.net/documentation/flash/netstream/{FNAME}',
+ 27 => 'http://wiki.media-box.net/documentation/flash/number/{FNAME}',
+ 28 => 'http://wiki.media-box.net/documentation/flash/object/{FNAME}',
+ 29 => 'http://wiki.media-box.net/documentation/flash/printJob/{FNAME}',
+ 30 => 'http://wiki.media-box.net/documentation/flash/selection/{FNAME}',
+ 31 => 'http://wiki.media-box.net/documentation/flash/sharedobject/{FNAME}',
+ 32 => 'http://wiki.media-box.net/documentation/flash/sound/{FNAME}',
+ 33 => 'http://wiki.media-box.net/documentation/flash/selection/{FNAME}',
+ 34 => 'http://wiki.media-box.net/documentation/flash/sharedobject/{FNAME}',
+ 35 => 'http://wiki.media-box.net/documentation/flash/stage/{FNAME}',
+ 36 => 'http://wiki.media-box.net/documentation/flash/string/{FNAME}',
+ 37 => 'http://wiki.media-box.net/documentation/flash/system/capabilities/{FNAME}',
+ 38 => 'http://wiki.media-box.net/documentation/flash/system/security/{FNAME}',
+ 39 => 'http://wiki.media-box.net/documentation/flash/system/{FNAME}',
+ 40 => 'http://wiki.media-box.net/documentation/flash/textfield/stylesheet/{FNAME}',
+ 41 => 'http://wiki.media-box.net/documentation/flash/textfield/{FNAME}',
+ 42 => 'http://wiki.media-box.net/documentation/flash/textformat/{FNAME}',
+ 43 => 'http://wiki.media-box.net/documentation/flash/textsnapshot/{FNAME}',
+ 44 => 'http://wiki.media-box.net/documentation/flash/tween/{FNAME}',
+ 45 => 'http://wiki.media-box.net/documentation/flash/video/{FNAME}',
+ 46 => 'http://wiki.media-box.net/documentation/flash/xml/{FNAME}',
+ 47 => 'http://wiki.media-box.net/documentation/flash/xmlsocket/{FNAME}',
+ 48 => 'http://wiki.media-box.net/documentation/flash/{FNAME}',
+ 49 => 'http://wiki.media-box.net/documentation/flash/{FNAME}',
+ 50 => 'http://wiki.media-box.net/documentation/flash/system/capabilities',
+ 51 => 'http://wiki.media-box.net/documentation/flash/system/security',
+ 52 => 'http://wiki.media-box.net/documentation/flash/textfield/stylesheet'
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(),
+ 'HIGHLIGHT_STRICT_BLOCK' => array()
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/actionscript.php b/plugins/dokuwiki/inc/geshi/actionscript.php
new file mode 100644
index 0000000..30df1fd
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/actionscript.php
@@ -0,0 +1,199 @@
+<?php
+/*************************************************************************************
+ * actionscript.php
+ * ----------------
+ * Author: Steffen Krause (Steffen.krause@muse.de)
+ * Copyright: (c) 2004 Steffen Krause, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/06/20
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * Actionscript language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'ActionScript',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '#include', 'for', 'foreach', 'if', 'elseif', 'else', 'while', 'do', 'dowhile',
+ 'endwhile', 'endif', 'switch', 'case', 'endswitch', 'return', 'break', 'continue', 'in'
+ ),
+ 2 => array(
+ 'null', 'false', 'true', 'var',
+ 'default', 'function', 'class',
+ 'new', '_global'
+ ),
+ 3 => array(
+ '#endinitclip', '#initclip', '__proto__', '_accProps', '_alpha', '_currentframe',
+ '_droptarget', '_focusrect', '_framesloaded', '_height', '_highquality', '_lockroot',
+ '_name', '_parent', '_quality', '_root', '_rotation', '_soundbuftime', '_target', '_totalframes',
+ '_url', '_visible', '_width', '_x', '_xmouse', '_xscale', '_y', '_ymouse', '_yscale', 'abs',
+ 'Accessibility', 'acos', 'activityLevel', 'add', 'addListener', 'addPage', 'addProperty',
+ 'addRequestHeader', 'align', 'allowDomain', 'allowInsecureDomain', 'and', 'appendChild',
+ 'apply', 'Arguments', 'Array', 'asfunction', 'asin', 'atan', 'atan2', 'attachAudio', 'attachMovie',
+ 'attachSound', 'attachVideo', 'attributes', 'autosize', 'avHardwareDisable', 'background',
+ 'backgroundColor', 'BACKSPACE', 'bandwidth', 'beginFill', 'beginGradientFill', 'blockIndent',
+ 'bold', 'Boolean', 'border', 'borderColor', 'bottomScroll', 'bufferLength', 'bufferTime',
+ 'builtInItems', 'bullet', 'Button', 'bytesLoaded', 'bytesTotal', 'call', 'callee', 'caller',
+ 'Camera', 'capabilities', 'CAPSLOCK', 'caption', 'catch', 'ceil', 'charAt', 'charCodeAt',
+ 'childNodes', 'chr', 'clear', 'clearInterval', 'cloneNode', 'close', 'Color', 'concat',
+ 'connect', 'condenseWhite', 'constructor', 'contentType', 'ContextMenu', 'ContextMenuItem',
+ 'CONTROL', 'copy', 'cos', 'createElement', 'createEmptyMovieClip', 'createTextField',
+ 'createTextNode', 'currentFps', 'curveTo', 'CustomActions', 'customItems', 'data', 'Date',
+ 'deblocking', 'delete', 'DELETEKEY', 'docTypeDecl', 'domain', 'DOWN',
+ 'duplicateMovieClip', 'duration', 'dynamic', 'E', 'embedFonts', 'enabled',
+ 'END', 'endFill', 'ENTER', 'eq', 'Error', 'ESCAPE(Konstante)', 'escape(Funktion)', 'eval',
+ 'exactSettings', 'exp', 'extends', 'finally', 'findText', 'firstChild', 'floor',
+ 'flush', 'focusEnabled', 'font', 'fps', 'fromCharCode', 'fscommand',
+ 'gain', 'ge', 'get', 'getAscii', 'getBeginIndex', 'getBounds', 'getBytesLoaded', 'getBytesTotal',
+ 'getCaretIndex', 'getCode', 'getCount', 'getDate', 'getDay', 'getDepth', 'getEndIndex', 'getFocus',
+ 'getFontList', 'getFullYear', 'getHours', 'getInstanceAtDepth', 'getLocal', 'getMilliseconds',
+ 'getMinutes', 'getMonth', 'getNewTextFormat', 'getNextHighestDepth', 'getPan', 'getProgress',
+ 'getProperty', 'getRGB', 'getSeconds', 'getSelected', 'getSelectedText', 'getSize', 'getStyle',
+ 'getStyleNames', 'getSWFVersion', 'getText', 'getTextExtent', 'getTextFormat', 'getTextSnapshot',
+ 'getTime', 'getTimer', 'getTimezoneOffset', 'getTransform', 'getURL', 'getUTCDate', 'getUTCDay',
+ 'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
+ 'getVersion', 'getVolume', 'getYear', 'globalToLocal', 'goto', 'gotoAndPlay', 'gotoAndStop',
+ 'hasAccessibility', 'hasAudio', 'hasAudioEncoder', 'hasChildNodes', 'hasEmbeddedVideo', 'hasMP3',
+ 'hasPrinting', 'hasScreenBroadcast', 'hasScreenPlayback', 'hasStreamingAudio', 'hasStreamingVideo',
+ 'hasVideoEncoder', 'height', 'hide', 'hideBuiltInItems', 'hitArea', 'hitTest', 'hitTestTextNearPos',
+ 'HOME', 'hscroll', 'html', 'htmlText', 'ID3', 'ifFrameLoaded', 'ignoreWhite', 'implements',
+ 'import', 'indent', 'index', 'indexOf', 'Infinity', '-Infinity', 'INSERT', 'insertBefore', 'install',
+ 'instanceof', 'int', 'interface', 'isActive', 'isDebugger', 'isDown', 'isFinite', 'isNaN', 'isToggled',
+ 'italic', 'join', 'Key', 'language', 'lastChild', 'lastIndexOf', 'le', 'leading', 'LEFT', 'leftMargin',
+ 'length', 'level', 'lineStyle', 'lineTo', 'list', 'LN10', 'LN2', 'load', 'loadClip', 'loaded', 'loadMovie',
+ 'loadMovieNum', 'loadSound', 'loadVariables', 'loadVariablesNum', 'LoadVars', 'LocalConnection',
+ 'localFileReadDisable', 'localToGlobal', 'log', 'LOG10E', 'LOG2E', 'manufacturer', 'Math', 'max',
+ 'MAX_VALUE', 'maxChars', 'maxhscroll', 'maxscroll', 'mbchr', 'mblength', 'mbord', 'mbsubstring', 'menu',
+ 'message', 'Microphone', 'min', 'MIN_VALUE', 'MMExecute', 'motionLevel', 'motionTimeOut', 'Mouse',
+ 'mouseWheelEnabled', 'moveTo', 'Movieclip', 'MovieClipLoader', 'multiline', 'muted', 'name', 'names', 'NaN',
+ 'ne', 'NEGATIVE_INFINITY', 'NetConnection', 'NetStream', 'newline', 'nextFrame',
+ 'nextScene', 'nextSibling', 'nodeName', 'nodeType', 'nodeValue', 'not', 'Number', 'Object',
+ 'on', 'onActivity', 'onChanged', 'onClipEvent', 'onClose', 'onConnect', 'onData', 'onDragOut',
+ 'onDragOver', 'onEnterFrame', 'onID3', 'onKeyDown', 'onKeyUp', 'onKillFocus', 'onLoad', 'onLoadComplete',
+ 'onLoadError', 'onLoadInit', 'onLoadProgress', 'onLoadStart', 'onMouseDown', 'onMouseMove', 'onMouseUp',
+ 'onMouseWheel', 'onPress', 'onRelease', 'onReleaseOutside', 'onResize', 'onRollOut', 'onRollOver',
+ 'onScroller', 'onSelect', 'onSetFocus', 'onSoundComplete', 'onStatus', 'onUnload', 'onUpdate', 'onXML',
+ 'or(logischesOR)', 'ord', 'os', 'parentNode', 'parseCSS', 'parseFloat', 'parseInt', 'parseXML', 'password',
+ 'pause', 'PGDN', 'PGUP', 'PI', 'pixelAspectRatio', 'play', 'playerType', 'pop', 'position',
+ 'POSITIVE_INFINITY', 'pow', 'prevFrame', 'previousSibling', 'prevScene', 'print', 'printAsBitmap',
+ 'printAsBitmapNum', 'PrintJob', 'printNum', 'private', 'prototype', 'public', 'push', 'quality',
+ 'random', 'rate', 'registerClass', 'removeListener', 'removeMovieClip', 'removeNode', 'removeTextField',
+ 'replaceSel', 'replaceText', 'resolutionX', 'resolutionY', 'restrict', 'reverse', 'RIGHT',
+ 'rightMargin', 'round', 'scaleMode', 'screenColor', 'screenDPI', 'screenResolutionX', 'screenResolutionY',
+ 'scroll', 'seek', 'selectable', 'Selection', 'send', 'sendAndLoad', 'separatorBefore', 'serverString',
+ 'set', 'setvariable', 'setBufferTime', 'setClipboard', 'setDate', 'setFocus', 'setFullYear', 'setGain',
+ 'setHours', 'setInterval', 'setMask', 'setMilliseconds', 'setMinutes', 'setMode', 'setMonth',
+ 'setMotionLevel', 'setNewTextFormat', 'setPan', 'setProperty', 'setQuality', 'setRate', 'setRGB',
+ 'setSeconds', 'setSelectColor', 'setSelected', 'setSelection', 'setSilenceLevel', 'setStyle',
+ 'setTextFormat', 'setTime', 'setTransform', 'setUseEchoSuppression', 'setUTCDate', 'setUTCFullYear',
+ 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 'setVolume',
+ 'setYear', 'SharedObject', 'SHIFT(Konstante)', 'shift(Methode)', 'show', 'showMenu', 'showSettings',
+ 'silenceLevel', 'silenceTimeout', 'sin', 'size', 'slice', 'smoothing', 'sort', 'sortOn', 'Sound', 'SPACE',
+ 'splice', 'split', 'sqrt', 'SQRT1_2', 'SQRT2', 'Stage', 'start', 'startDrag', 'static', 'status', 'stop',
+ 'stopAllSounds', 'stopDrag', 'String', 'StyleSheet(Klasse)', 'styleSheet(Eigenschaft)', 'substr',
+ 'substring', 'super', 'swapDepths', 'System', 'TAB', 'tabChildren', 'tabEnabled', 'tabIndex',
+ 'tabStops', 'tan', 'target', 'targetPath', 'tellTarget', 'text', 'textColor', 'TextField', 'TextFormat',
+ 'textHeight', 'TextSnapshot', 'textWidth', 'this', 'throw', 'time', 'toggleHighQuality', 'toLowerCase',
+ 'toString', 'toUpperCase', 'trace', 'trackAsMenu', 'try', 'type', 'typeof', 'undefined',
+ 'underline', 'unescape', 'uninstall', 'unloadClip', 'unloadMovie', 'unLoadMovieNum', 'unshift', 'unwatch',
+ 'UP', 'updateAfterEvent', 'updateProperties', 'url', 'useCodePage', 'useEchoSuppression', 'useHandCursor',
+ 'UTC', 'valueOf', 'variable', 'version', 'Video', 'visible', 'void', 'watch', 'width',
+ 'with', 'wordwrap', 'XML', 'xmlDecl', 'XMLNode', 'XMLSocket'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '!', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #0066CC;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(),
+ 'HIGHLIGHT_STRICT_BLOCK' => array()
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/ada.php b/plugins/dokuwiki/inc/geshi/ada.php
new file mode 100644
index 0000000..461a165
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/ada.php
@@ -0,0 +1,135 @@
+<?php
+/*************************************************************************************
+ * ada.php
+ * -------
+ * Author: Tux (tux@inmail.cz)
+ * Copyright: (c) 2004 Tux (http://tux.a4.cz/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/07/29
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * Ada language file for GeSHi.
+ * Words are from SciTe configuration file
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Removed apostrophe as string delimiter
+ * - Added URL support
+ * 2004/08/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Ada',
+ 'COMMENT_SINGLE' => array(1 => '--'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'begin', 'declare', 'do', 'else', 'elsif', 'exception', 'for', 'if',
+ 'is', 'loop', 'while', 'then', 'is', 'end', 'select', 'case', 'while', 'until',
+ 'goto', 'return'
+ ),
+ 2 => array(
+ 'abs', 'and', 'mod', 'not', 'or', 'rem', 'xor'
+ ),
+ 3 => array(
+ 'abort', 'abstract', 'accept', 'access', 'aliased', 'all', 'array', 'at', 'body',
+ 'constant', 'delay', 'delta', 'digits', 'entry', 'exit',
+ 'function', 'generic', 'in', 'limited', 'new', 'null', 'of', 'others', 'out', 'package', 'pragma',
+ 'private', 'procedure', 'protected', 'raise', 'range', 'record', 'renames', 'requeue', 'reverse',
+ 'separate', 'subtype', 'tagged', 'task', 'terminate', 'type', 'use', 'when', 'with'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #00007f;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #46aa03; font-weight:bold;',
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #adadad; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #7f007f;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #202020;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/apache.php b/plugins/dokuwiki/inc/geshi/apache.php
new file mode 100644
index 0000000..028a92a
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/apache.php
@@ -0,0 +1,173 @@
+<?php
+/*************************************************************************************
+ * apache.php
+ * ----------
+ * Author: Tux (tux@inmail.cz)
+ * Copyright: (c) 2004 Tux (http://tux.a4.cz/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.5 $
+ * Date Started: 2004/29/07
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * Apache language file for GeSHi.
+ * Words are from SciTe configuration file
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/07/29)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Apache Log',
+ 'COMMENT_SINGLE' => array(1 => '#'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ /*keywords*/
+ 1 => array(
+ 'accessconfig','accessfilename','action','addalt',
+ 'addaltbyencoding','addaltbytype','addcharset',
+ 'adddefaultcharset','adddescription',
+ 'addencoding','addhandler','addicon','addiconbyencoding',
+ 'addiconbytype','addlanguage','addmodule','addmoduleinfo',
+ 'addtype','agentlog','alias','aliasmatch',
+ 'allow','allowconnect','allowoverride','anonymous',
+ 'anonymous_authoritative','anonymous_logemail','anonymous_mustgiveemail',
+ 'anonymous_nouserid','anonymous_verifyemail','authauthoritative',
+ 'authdbauthoritative','authdbgroupfile','authdbmauthoritative',
+ 'authdbmgroupfile','authdbmgroupfile','authdbuserfile','authdbmuserfile',
+ 'authdigestfile','authgroupfile','authname','authtype',
+ 'authuserfile','bindaddress','browsermatch','browsermatchnocase',
+ 'bs2000account','cachedefaultexpire','cachedirlength','cachedirlevels',
+ 'cacheforcecompletion','cachegcinterval','cachelastmodifiedfactor','cachemaxexpire',
+ 'cachenegotiateddocs','cacheroot','cachesize','checkspelling',
+ 'clearmodulelist','contentdigest','cookieexpires','cookielog',
+ 'cookielog','cookietracking','coredumpdirectory','customlog',
+ 'defaulticon','defaultlanguage','defaulttype','define',
+ 'deny','directory','directorymatch','directoryindex',
+ 'documentroot','errordocument','errorlog','example',
+ 'expiresactive','expiresbytype','expiresdefault','extendedstatus',
+ 'fancyindexing','files','filesmatch','forcetype',
+ 'group','header','headername','hostnamelookups',
+ 'identitycheck','ifdefine','ifmodule','imapbase',
+ 'imapdefault','imapmenu','include','indexignore',
+ 'indexoptions','keepalive','keepalivetimeout','languagepriority',
+ 'limit','limitexcept','limitrequestbody','limitrequestfields',
+ 'limitrequestfieldsize','limitrequestline','listen','listenbacklog',
+ 'loadfile','loadmodule','location','locationmatch',
+ 'lockfile','logformat','loglevel','maxclients',
+ 'maxkeepaliverequests','maxrequestsperchild','maxspareservers','metadir',
+ 'metafiles','metasuffix','mimemagicfile','minspareservers',
+ 'mmapfile','namevirtualhost','nocache','options','order',
+ 'passenv','pidfile','port','proxyblock','proxydomain',
+ 'proxypass','proxypassreverse','proxyreceivebuffersize','proxyremote',
+ 'proxyrequests','proxyvia','qsc','readmename',
+ 'redirect','redirectmatch','redirectpermanent','redirecttemp',
+ 'refererignore','refererlog','removehandler','require',
+ 'resourceconfig','rewritebase','rewritecond','rewriteengine',
+ 'rewritelock','rewritelog','rewriteloglevel','rewritemap',
+ 'rewriteoptions','rewriterule','rlimitcpu','rlimitmem',
+ 'rlimitnproc','satisfy','scoreboardfile','script',
+ 'scriptalias','scriptaliasmatch','scriptinterpretersource','scriptlog',
+ 'scriptlogbuffer','scriptloglength','sendbuffersize',
+ 'serveradmin','serveralias','servername','serverpath',
+ 'serverroot','serversignature','servertokens','servertype',
+ 'setenv','setenvif','setenvifnocase','sethandler',
+ 'singlelisten','startservers','threadsperchild','timeout',
+ 'transferlog','typesconfig','unsetenv','usecanonicalname',
+ 'user','userdir','virtualhost','virtualdocumentroot',
+ 'virtualdocumentrootip','virtualscriptalias','virtualscriptaliasip',
+ 'xbithack','from','all'
+ ),
+ /*keyords 2*/
+ 2 => array(
+ 'on','off','standalone','inetd',
+ 'force-response-1.0','downgrade-1.0','nokeepalive',
+ 'ndexes','includes','followsymlinks','none',
+ 'x-compress','x-gzip'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #00007f;',
+ 2 => 'color: #0000ff;',
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #adadad; font-style: italic;',
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #7f007f;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/applescript.php b/plugins/dokuwiki/inc/geshi/applescript.php
new file mode 100644
index 0000000..33ab4f9
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/applescript.php
@@ -0,0 +1,136 @@
+<?php
+/*************************************************************************************
+ * applescript.php
+ * --------
+ * Author: Stephan Klimek (http://www.initware.org)
+ * Copyright: Stephan Klimek (http://www.initware.org)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.12.2.5 $
+ * Date Started: 2005/07/20
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * AppleScript language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ *
+ * TODO
+ * -------------------------
+ * URL settings to references
+ *
+ **************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'AppleScript',
+ 'COMMENT_SINGLE' => array(1 => '--'),
+ 'COMMENT_MULTI' => array( '(*' => '*)'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"',"'"),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'script','property','prop','end','copy','to','set','global','local','on','to','of',
+ 'in','given','with','without','return','continue','tell','if','then','else','repeat',
+ 'times','while','until','from','exit','try','error','considering','ignoring','timeout',
+ 'transaction','my','get','put','into','is'
+ ),
+ 2 => array(
+ 'each','some','every','whose','where','id','index','first','second','third','fourth',
+ 'fifth','sixth','seventh','eighth','ninth','tenth','last','front','back','st','nd',
+ 'rd','th','middle','named','through','thru','before','after','beginning','the'
+ ),
+ 3 => array(
+ 'close','copy','count','delete','duplicate','exists','launch','make','move','open',
+ 'print','quit','reopen','run','save','saving',
+ 'it','me','version','pi','result','space','tab','anything','case','diacriticals','expansion',
+ 'hyphens','punctuation','bold','condensed','expanded','hidden','italic','outline','plain',
+ 'shadow','strikethrough','subscript','superscript','underline','ask','no','yes','false',
+ 'true','weekday','monday','mon','tuesday','tue','wednesday','wed','thursday','thu','friday',
+ 'fri','saturday','sat','sunday','sun','month','january','jan','february','feb','march',
+ 'mar','april','apr','may','june','jun','july','jul','august','aug','september',
+ 'sep','october','oct','november','nov','december','dec','minutes','hours',
+ 'days','weeks','div','mod','and','not','or','as','contains','equal','equals','isnt'
+ )
+ ),
+ 'SYMBOLS' => array(
+ ')','+','-','^','*','/','&','<','>=','<','<=','=','�'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0000ff;',
+ 4 => 'color: #009999;',
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => ',+-=&lt;&gt;/?^&amp;*'
+ ),
+ 'REGEXPS' => array(
+ 0 => '[\\$%@]+[a-zA-Z_][a-zA-Z0-9_]*',
+ 4 => '&lt;[a-zA-Z_][a-zA-Z0-9_]*&gt;',
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/asm.php b/plugins/dokuwiki/inc/geshi/asm.php
new file mode 100644
index 0000000..cb2bfe7
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/asm.php
@@ -0,0 +1,200 @@
+<?php
+/*************************************************************************************
+ * asm.php
+ * -------
+ * Author: Tux (tux@inmail.cz)
+ * Copyright: (c) 2004 Tux (http://tux.a4.cz/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.5 $
+ * Date Started: 2004/07/27
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * x86 Assembler language file for GeSHi.
+ * Words are from SciTe configuration file (based on NASM syntax)
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * - Added binary and hexadecimal regexps
+ * 2004/08/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'ASM',
+ 'COMMENT_SINGLE' => array(1 => ';'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ /*CPU*/
+ 1 => array(
+ 'aaa','aad','aam','aas','adc','add','and','call','cbw','clc','cld','cli','cmc','cmp',
+ 'cmps','cmpsb','cmpsw','cwd','daa','das','dec','div','esc','hlt','idiv','imul','in','inc',
+ 'int','into','iret','ja','jae','jb','jbe','jc','jcxz','je','jg','jge','jl','jle','jmp',
+ 'jna','jnae','jnb','jnbe','jnc','jne','jng','jnge','jnl','jnle','jno','jnp','jns','jnz',
+ 'jo','jp','jpe','jpo','js','jz','lahf','lds','lea','les','lods','lodsb','lodsw','loop',
+ 'loope','loopew','loopne','loopnew','loopnz','loopnzw','loopw','loopz','loopzw','mov',
+ 'movs','movsb','movsw','mul','neg','nop','not','or','out','pop','popf','push','pushf',
+ 'rcl','rcr','ret','retf','retn','rol','ror','sahf','sal','sar','sbb','scas','scasb','scasw',
+ 'shl','shr','stc','std','sti','stos','stosb','stosw','sub','test','wait','xchg','xlat',
+ 'xlatb','xor','bound','enter','ins','insb','insw','leave','outs','outsb','outsw','popa','pusha','pushw',
+ 'arpl','lar','lsl','sgdt','sidt','sldt','smsw','str','verr','verw','clts','lgdt','lidt','lldt','lmsw','ltr',
+ 'bsf','bsr','bt','btc','btr','bts','cdq','cmpsd','cwde','insd','iretd','iretdf','iretf',
+ 'jecxz','lfs','lgs','lodsd','loopd','looped','loopned','loopnzd','loopzd','lss','movsd',
+ 'movsx','movzx','outsd','popad','popfd','pushad','pushd','pushfd','scasd','seta','setae',
+ 'setb','setbe','setc','sete','setg','setge','setl','setle','setna','setnae','setnb','setnbe',
+ 'setnc','setne','setng','setnge','setnl','setnle','setno','setnp','setns','setnz','seto','setp',
+ 'setpe','setpo','sets','setz','shld','shrd','stosd','bswap','cmpxchg','invd','invlpg','wbinvd','xadd','lock',
+ 'rep','repe','repne','repnz','repz'
+ ),
+ /*FPU*/
+ 2 => array(
+ 'f2xm1','fabs','fadd','faddp','fbld','fbstp','fchs','fclex','fcom','fcomp','fcompp','fdecstp',
+ 'fdisi','fdiv','fdivp','fdivr','fdivrp','feni','ffree','fiadd','ficom','ficomp','fidiv',
+ 'fidivr','fild','fimul','fincstp','finit','fist','fistp','fisub','fisubr','fld','fld1',
+ 'fldcw','fldenv','fldenvw','fldl2e','fldl2t','fldlg2','fldln2','fldpi','fldz','fmul',
+ 'fmulp','fnclex','fndisi','fneni','fninit','fnop','fnsave','fnsavew','fnstcw','fnstenv',
+ 'fnstenvw','fnstsw','fpatan','fprem','fptan','frndint','frstor','frstorw','fsave',
+ 'fsavew','fscale','fsqrt','fst','fstcw','fstenv','fstenvw','fstp','fstsw','fsub','fsubp',
+ 'fsubr','fsubrp','ftst','fwait','fxam','fxch','fxtract','fyl2x','fyl2xp1',
+ 'fsetpm','fcos','fldenvd','fnsaved','fnstenvd','fprem1','frstord','fsaved','fsin','fsincos',
+ 'fstenvd','fucom','fucomp','fucompp'
+ ),
+ /*registers*/
+ 3 => array(
+ 'ah','al','ax','bh','bl','bp','bx','ch','cl','cr0','cr2','cr3','cs','cx','dh','di','dl',
+ 'dr0','dr1','dr2','dr3','dr6','dr7','ds','dx','eax','ebp','ebx','ecx','edi','edx',
+ 'es','esi','esp','fs','gs','si','sp','ss','st','tr3','tr4','tr5','tr6','tr7', 'ah', 'bh', 'ch', 'dh'
+ ),
+ /*Directive*/
+ 4 => array(
+ '186','286','286c','286p','287','386','386c','386p','387','486','486p',
+ '8086','8087','alpha','break','code','const','continue','cref','data','data?',
+ 'dosseg','else','elseif','endif','endw','err','err1','err2','errb',
+ 'errdef','errdif','errdifi','erre','erridn','erridni','errnb','errndef',
+ 'errnz','exit','fardata','fardata?','if','lall','lfcond','list','listall',
+ 'listif','listmacro','listmacroall',' model','no87','nocref','nolist',
+ 'nolistif','nolistmacro','radix','repeat','sall','seq','sfcond','stack',
+ 'startup','tfcond','type','until','untilcxz','while','xall','xcref',
+ 'xlist','alias','align','assume','catstr','comm','comment','db','dd','df','dosseg','dq',
+ 'dt','dup','dw','echo','else','elseif','elseif1','elseif2','elseifb','elseifdef','elseifdif',
+ 'elseifdifi','elseife','elseifidn','elseifidni','elseifnb','elseifndef','end',
+ 'endif','endm','endp','ends','eq',' equ','even','exitm','extern','externdef','extrn','for',
+ 'forc','ge','goto','group','high','highword','if','if1','if2','ifb','ifdef','ifdif',
+ 'ifdifi','ife',' ifidn','ifidni','ifnb','ifndef','include','includelib','instr','invoke',
+ 'irp','irpc','label','le','length','lengthof','local','low','lowword','lroffset',
+ 'macro','mask','mod','msfloat','name','ne','offset','opattr','option','org','%out',
+ 'page','popcontext','proc','proto','ptr','public','purge','pushcontext','record',
+ 'repeat','rept','seg','segment','short','size','sizeof','sizestr','struc','struct',
+ 'substr','subtitle','subttl','textequ','this','title','type','typedef','union','while','width',
+ '.model', '.stack', '.code', '.data'
+
+ ),
+
+ /*Operands*/
+ 5 => array(
+ '@b','@f','addr','basic','byte','c','carry?','dword',
+ 'far','far16','fortran','fword','near','near16','overflow?','parity?','pascal','qword',
+ 'real4',' real8','real10','sbyte','sdword','sign?','stdcall','sword','syscall','tbyte',
+ 'vararg','word','zero?','flat','near32','far32',
+ 'abs','all','assumes','at','casemap','common','compact',
+ 'cpu','dotname','emulator','epilogue','error','export','expr16','expr32','farstack','flat',
+ 'forceframe','huge','language','large','listing','ljmp','loadds','m510','medium','memory',
+ 'nearstack','nodotname','noemulator','nokeyword','noljmp','nom510','none','nonunique',
+ 'nooldmacros','nooldstructs','noreadonly','noscoped','nosignextend','nothing',
+ 'notpublic','oldmacros','oldstructs','os_dos','para','private','prologue','radix',
+ 'readonly','req','scoped','setif2','smallstack','tiny','use16','use32','uses'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '[', ']', '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #00007f;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #46aa03; font-weight:bold;',
+ 4 => 'color: #0000ff;',
+ 5 => 'color: #0000ff;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #adadad; font-style: italic;',
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #7f007f;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #ff0000;',
+ 1 => 'color: #ff0000;'
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '0[0-9a-fA-F][0-9a-fA-F]*[hH]',
+ 1 => '[01][01]*[bB]'
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/asp.php b/plugins/dokuwiki/inc/geshi/asp.php
new file mode 100644
index 0000000..8f3c06b
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/asp.php
@@ -0,0 +1,155 @@
+<?php
+/*************************************************************************************
+ * asp.php
+ * --------
+ * Author: Amit Gupta (http://blog.igeek.info/)
+ * Copyright: (c) 2004 Amit Gupta (http://blog.igeek.info/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/08/13
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * ASP language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/30 (1.0.3)
+ * - Strings only delimited by ", comments by '
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * 2004/08/13 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Include all the functions, keywords etc that I have missed
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'ASP',
+ 'COMMENT_SINGLE' => array(1 => "'", 2 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => 0,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'include', 'file', 'Dim', 'Option', 'Explicit', 'Implicit', 'Set', 'Select', 'ReDim', 'Preserve',
+ 'ByVal', 'ByRef', 'End', 'Private', 'Public', 'If', 'Then', 'Else', 'ElseIf', 'Case', 'With', 'NOT',
+ 'While', 'Wend', 'For', 'Loop', 'Do', 'Request', 'Response', 'Server', 'ADODB', 'Session', 'Application',
+ 'Each', 'In', 'Get', 'Next', 'INT', 'CINT', 'CBOOL', 'CDATE', 'CBYTE', 'CCUR', 'CDBL', 'CLNG', 'CSNG',
+ 'CSTR', 'Fix', 'Is', 'Sgn', 'String', 'Boolean', 'Currency', 'Me', 'Single', 'Long', 'Integer', 'Byte',
+ 'Variant', 'Double', 'To', 'Let', 'Xor', 'Resume', 'On', 'Error', 'Imp', 'GoTo', 'Call', 'Global'
+ ),
+ 2 => array(
+ 'Null', 'Nothing', 'And',
+ 'False', '&lt;%', '%&gt;',
+ '&lt;script language=', '&lt;/script&gt;',
+ 'True', 'var', 'Or', 'BOF', 'EOF',
+ 'Function', 'Class', 'New', 'Sub'
+ ),
+ 3 => array(
+ 'CreateObject', 'Write', 'Redirect', 'Cookies', 'BinaryRead', 'ClientCertificate', 'Form', 'QueryString',
+ 'ServerVariables', 'TotalBytes', 'AddHeader', 'AppendToLog', 'BinaryWrite', 'Buffer', 'CacheControl',
+ 'Charset', 'Clear', 'ContentType', 'End()', 'Expires', 'ExpiresAbsolute', 'Flush()', 'IsClientConnected',
+ 'PICS', 'Status', 'Connection', 'Recordset', 'Execute', 'Abandon', 'Lock', 'UnLock', 'Command', 'Fields',
+ 'Properties', 'Property', 'Send', 'Replace', 'InStr', 'TRIM', 'NOW', 'Day', 'Month', 'Hour', 'Minute', 'Second',
+ 'Year', 'MonthName', 'LCase', 'UCase', 'Abs', 'Array', 'As', 'LEN', 'MoveFirst', 'MoveLast', 'MovePrevious',
+ 'MoveNext', 'LBound', 'UBound', 'Transfer', 'Open', 'Close', 'MapPath', 'FileExists', 'OpenTextFile', 'ReadAll'
+ )
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #990099; font-weight: bold;',
+ 2 => 'color: #0000ff; font-weight: bold;',
+ 3 => 'color: #330066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #008000;',
+ 2 => 'color: #ff6600;',
+ 'MULTI' => 'color: #008000;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #006600; font-weight:bold'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #cc0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #800000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #9900cc;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #006600; font-weight: bold'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<%' => '%>'
+ ),
+ 1 => array(
+ '<script language="vbscript" runat="server">' => '</script>'
+ ),
+ 2 => array(
+ '<script language="javascript" runat="server">' => '</script>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/autoit.php b/plugins/dokuwiki/inc/geshi/autoit.php
new file mode 100644
index 0000000..e2e1e27
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/autoit.php
@@ -0,0 +1,196 @@
+<?php
+/*************************************************************************************
+ * autoit.php
+ * --------
+ * Author: mastrboy
+ * Copyright: (c) 2006 and to GESHi ;)
+ * Release Version: 1.0.7.15
+ * Date Started: 26.01.2006
+ *
+ * Current bugs & todo:
+ * ----------
+ * - can't get #cs and #ce to work as multiple comments while still #comments-start/end working
+ * - dosn't highlight symbols (Please note that in 1.0.X these are not used. Hopefully they will be used in 1.2.X.)
+ * - not sure how to get sendkeys to work " {!}, {SPACE} etc... "
+ * - jut copyied the regexp for variable from php so this HAVE to be checked and fixed to a better one ;)
+ *
+ * Reference: http://www.autoitscript.com/autoit3/docs/
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'AutoIT',
+ 'COMMENT_SINGLE' => array(';'),
+ 'COMMENT_MULTI' => array('#comments-start' => '#comments-end'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'continueloop', 'and', 'byref', 'case', 'const', 'dim', 'do', 'else',
+ 'elseif', 'endfunc', 'endif', 'endselect', 'exit', 'exitloop', 'for',
+ 'func', 'global', 'if', 'local', 'next', 'not', 'or', 'redim', 'return',
+ 'select', 'step', 'then', 'to', 'until', 'wend', 'while'
+ ),
+ 2 => array(
+ '@appdatacommondir','@appdatadir','@autoitexe','@autoitversion','@commonfilesdir',
+ '@compiled','@computername','@comspec','@cr','@crlf','@desktopcommondir','@desktopdepth','@desktopdir',
+ '@desktopheight','@desktoprefresh','@desktopwidth','@documentscommondir','@error','@extended',
+ '@favoritescommondir','@favoritesdir','@gui_ctrlhandle','@gui_ctrlid','@gui_winhandle','@homedrive',
+ '@homepath','@homeshare','@hour','@inetgetactive','@inetgetbytesread','@ipaddress1','@ipaddress2',
+ '@ipaddress3','@ipaddress4','@lf','@logondnsdomain','@logondomain','@logonserver','@mday','@min',
+ '@mon','@mydocumentsdir','@numparams','@osbuild','@oslang','@osservicepack','@ostype','@osversion',
+ '@programfilesdir','@programscommondir','@programsdir','@scriptdir','@scriptfullpath','@scriptname',
+ '@sec','@startmenucommondir','@startmenudir','@startupcommondir','@startupdir','@sw_disable',
+ '@sw_enable','@sw_hide','@sw_maximize','@sw_minimize','@sw_restore','@sw_show','@sw_showdefault',
+ '@sw_showmaximized','@sw_showminimized','@sw_showminnoactive','@sw_showna','@sw_shownoactivate',
+ '@sw_shownormal','@systemdir','@tab','@tempdir','@username','@userprofiledir','@wday','@windowsdir',
+ '@workingdir','@yday','@year'
+ ),
+ 3 => array(
+ 'abs','acos','adlibdisable','adlibenable','asc','asin','assign','atan','autoitsetoption',
+ 'autoitwingettitle','autoitwinsettitle','bitand','bitnot','bitor','bitshift','bitxor','blockinput',
+ 'break','call','cdtray','chr','clipget','clipput','consolewrite','controlclick','controlcommand','controldisable',
+ 'controlenable','controlfocus','controlgetfocus','controlgethandle','controlgetpos','controlgettext',
+ 'controlhide','controllistview','controlmove','controlsend','controlsettext','controlshow','cos',
+ 'dec','dircopy','dircreate','dirgetsize','dirmove','dirremove','dllcall','dllclose','dllopen','drivegetdrive',
+ 'drivegetfilesystem','drivegetlabel','drivegetserial','drivegettype','drivemapadd','drivemapdel',
+ 'drivemapget','drivesetlabel','drivespacefree','drivespacetotal','drivestatus','envget','envset',
+ 'envupdate','eval','exp','filechangedir','fileclose','filecopy','filecreateshortcut','filedelete',
+ 'fileexists','filefindfirstfile','filefindnextfile','filegetattrib','filegetlongname','filegetshortcut',
+ 'filegetshortname','filegetsize','filegettime','filegetversion','fileinstall','filemove','fileopen',
+ 'fileopendialog','fileread','filereadline','filerecycle','filerecycleempty','filesavedialog',
+ 'fileselectfolder','filesetattrib','filesettime','filewrite','filewriteline','ftpsetproxy','guicreate',
+ 'guictrlcreateavi','guictrlcreatebutton','guictrlcreatecheckbox','guictrlcreatecombo','guictrlcreatecontextmenu',
+ 'guictrlcreatedate','guictrlcreatedummy','guictrlcreateedit','guictrlcreategroup','guictrlcreateicon',
+ 'guictrlcreateinput','guictrlcreatelabel','guictrlcreatelist','guictrlcreatelistview','guictrlcreatelistviewitem',
+ 'guictrlcreatemenu','guictrlcreatemenuitem','guictrlcreatepic','guictrlcreateprogress','guictrlcreateradio',
+ 'guictrlcreateslider','guictrlcreatetab','guictrlcreatetabitem','guictrlcreatetreeview','guictrlcreatetreeviewitem',
+ 'guictrlcreateupdown','guictrldelete','guictrlgetstate','guictrlread','guictrlrecvmsg','guictrlsendmsg',
+ 'guictrlsendtodummy','guictrlsetbkcolor','guictrlsetcolor','guictrlsetcursor','guictrlsetdata',
+ 'guictrlsetfont','guictrlsetimage','guictrlsetlimit','guictrlsetonevent','guictrlsetpos','guictrlsetresizing',
+ 'guictrlsetstate','guictrlsetstyle','guictrlsettip','guidelete','guigetcursorinfo','guigetmsg',
+ 'guisetbkcolor','guisetcoord','guisetcursor','guisetfont','guisethelp','guiseticon','guisetonevent',
+ 'guisetstate','guistartgroup','guiswitch','hex','hotkeyset','httpsetproxy','inetget','inetgetsize',
+ 'inidelete','iniread','inireadsection','inireadsectionnames','iniwrite','inputbox','int','isadmin',
+ 'isarray','isdeclared','isfloat','isint','isnumber','isstring','log','memgetstats','mod','mouseclick',
+ 'mouseclickdrag','mousedown','mousegetcursor','mousegetpos','mousemove','mouseup','mousewheel',
+ 'msgbox','number','opt','ping','pixelchecksum','pixelgetcolor','pixelsearch','processclose','processexists',
+ 'processlist','processsetpriority','processwait','processwaitclose','progressoff','progresson',
+ 'progressset','random','regdelete','regenumkey','regenumval','regread','regwrite','round','run','runasset',
+ 'runwait','send','seterror','setextended','shutdown','sin','sleep','soundplay','soundsetwavevolume',
+ 'splashimageon','splashoff','splashtexton','sqrt','statusbargettext','string','stringaddcr','stringformat',
+ 'stringinstr','stringisalnum','stringisalpha','stringisascii','stringisdigit','stringisfloat',
+ 'stringisint','stringislower','stringisspace','stringisupper','stringisxdigit','stringleft','stringlen',
+ 'stringlower','stringmid','stringregexp','stringregexpreplace','stringreplace','stringright',
+ 'stringsplit','stringstripcr','stringstripws','stringtrimleft','stringtrimright','stringupper',
+ 'tan','timerdiff','timerinit','timerstart','timerstop','tooltip','traytip','ubound','winactivate','winactive',
+ 'winclose','winexists','wingetcaretpos','wingetclasslist','wingetclientsize','wingethandle','wingetpos',
+ 'wingetprocess','wingetstate','wingettext','wingettitle','winkill','winlist','winmenuselectitem',
+ 'winminimizeall','winminimizeallundo','winmove','winsetontop','winsetstate','winsettitle','winsettrans',
+ 'winshow','winwait','winwaitactive','winwaitclose','winwaitnotactive'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '&', '*', '/', '<', '>', '+', '-', '^', '='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000FF; font-weight: bold;',
+ 2 => 'color: #FF33FF; font-weight: bold;',
+ 3 => 'color: #000090; font-style: italic; font-weight: bold;',
+ ),
+ 'COMMENTS' => array(
+ 0 => 'font-style: italic; color: #669900;', 'MULTI' => 'font-style: italic; color: #669900;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => ''
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #FF0000; font-weight: bold;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'font-weight: bold; color: #9999CC;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'font-style: italic; font-weight: bold; color: #AC00A9;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #FF0000; font-weight: bold;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'font-weight: bold; color: #AA0000;'
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ )
+ ),
+ 'URLS' => array(
+ 1 => 'http://www.autoitscript.com/autoit3/docs/keywords.htm',
+ 2 => 'http://www.autoitscript.com/autoit3/docs/macros.htm',
+ 3 => 'http://www.autoitscript.com/autoit3/docs/functions/{FNAME}.htm',
+ 4 => ''
+ ),
+
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => "[\\$]{1,2}[a-zA-Z_][a-zA-Z0-9_]*",
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+/* 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<?php' => '?>'
+ ),
+ 1 => array(
+ '<?' => '?>'
+ ),
+ 2 => array(
+ '<%' => '%>'
+ ),
+ 3 => array(
+ '<script language="php">' => '</script>'
+ )
+ ),*/
+
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ 3 => true
+ )
+);
+
+?>
+
diff --git a/plugins/dokuwiki/inc/geshi/bash.php b/plugins/dokuwiki/inc/geshi/bash.php
new file mode 100644
index 0000000..dc27c40
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/bash.php
@@ -0,0 +1,137 @@
+<?php
+/*************************************************************************************
+ * bash.php
+ * --------
+ * Author: Andreas Gohr (andi@splitbrain.org)
+ * Copyright: (c) 2004 Andreas Gohr, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/08/20
+ * Last Modified: $Date: 2006/09/25 05:29:50 $
+ *
+ * BASH language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * 2004/08/20 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Get symbols working
+ * * Highlight builtin vars
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Bash',
+ // Bash DOES have single line comments with # markers. But bash also has
+ // the $# variable, so comments need special handling (see sf.net
+ // 1564839)
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'case', 'do', 'done', 'elif', 'else', 'esac', 'fi', 'for', 'function',
+ 'if', 'in', 'select', 'then', 'until', 'while', 'time'
+ ),
+ 3 => array(
+ 'source', 'alias', 'bg', 'bind', 'break', 'builtin', 'cd', 'command',
+ 'compgen', 'complete', 'continue', 'declare', 'typeset', 'dirs',
+ 'disown', 'echo', 'enable', 'eval', 'exec', 'exit', 'export', 'fc',
+ 'fg', 'getopts', 'hash', 'help', 'history', 'jobs', 'kill', 'let',
+ 'local', 'logout', 'popd', 'printf', 'pushd', 'pwd', 'read', 'readonly',
+ 'return', 'set', 'shift', 'shopt', 'suspend', 'test', 'times', 'trap',
+ 'type', 'ulimit', 'umask', 'unalias', 'unset', 'wait'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '!', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => true,
+ 3 => true,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0000ff;',
+ 1 => 'color: #0000ff;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #808080; font-style: italic;',
+ 4 => 'color: #0000ff;'
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => "\\$\\{[a-zA-Z_][a-zA-Z0-9_]*?\\}",
+ 1 => "\\$[a-zA-Z_][a-zA-Z0-9_]*",
+ 2 => "([a-zA-Z_][a-zA-Z0-9_]*)=",
+ 3 => "(?<!\\$)#.*\n",
+ 4 => "\\$#"
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/c.php b/plugins/dokuwiki/inc/geshi/c.php
new file mode 100644
index 0000000..43ce60a
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/c.php
@@ -0,0 +1,144 @@
+<?php
+/*************************************************************************************
+ * c.php
+ * -----
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Contributors:
+ * - Jack Lloyd (lloyd@randombit.net)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * C language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/XX/XX (1.0.4)
+ * - Added a couple of new keywords (Jack Lloyd)
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * - Get a list of inbuilt functions to add (and explore C more
+ * to complete this rather bare language file
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'C',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'if', 'return', 'while', 'case', 'continue', 'default',
+ 'do', 'else', 'for', 'switch', 'goto'
+ ),
+ 2 => array(
+ 'null', 'false', 'break', 'true', 'function', 'enum', 'extern', 'inline'
+ ),
+ 3 => array(
+ 'printf', 'cout'
+ ),
+ 4 => array(
+ 'auto', 'char', 'const', 'double', 'float', 'int', 'long',
+ 'register', 'short', 'signed', 'sizeof', 'static', 'string', 'struct',
+ 'typedef', 'union', 'unsigned', 'void', 'volatile', 'wchar_t'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '=', '+', '-', '*', '/', '!', '%', '^', '&', ':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #339933;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #202020;',
+ 2 => 'color: #202020;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.opengroup.org/onlinepubs/009695399/functions/{FNAME}.html',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/c_mac.php b/plugins/dokuwiki/inc/geshi/c_mac.php
new file mode 100644
index 0000000..3239467
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/c_mac.php
@@ -0,0 +1,176 @@
+<?php
+/*************************************************************************************
+ * c_mac.php
+ * ---------
+ * Author: M. Uli Kusterer (witness.of.teachtext@gmx.net)
+ * Copyright: (c) 2004 M. Uli Kusterer, Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * C for Macs language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'C (Mac)',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'if', 'return', 'while', 'case', 'continue', 'default',
+ 'do', 'else', 'for', 'switch', 'goto'
+ ),
+ 2 => array(
+ 'NULL', 'false', 'break', 'true', 'enum', 'errno', 'EDOM',
+ 'ERANGE', 'FLT_RADIX', 'FLT_ROUNDS', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG',
+ 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON', 'FLT_MANT_DIG', 'DBL_MANT_DIG',
+ 'LDBL_MANT_DIG', 'FLT_MAX', 'DBL_MAX', 'LDBL_MAX', 'FLT_MAX_EXP', 'DBL_MAX_EXP',
+ 'LDBL_MAX_EXP', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', 'FLT_MIN_EXP', 'DBL_MIN_EXP',
+ 'LDBL_MIN_EXP', 'CHAR_BIT', 'CHAR_MAX', 'CHAR_MIN', 'SCHAR_MAX', 'SCHAR_MIN',
+ 'UCHAR_MAX', 'SHRT_MAX', 'SHRT_MIN', 'USHRT_MAX', 'INT_MAX', 'INT_MIN',
+ 'UINT_MAX', 'LONG_MAX', 'LONG_MIN', 'ULONG_MAX', 'HUGE_VAL', 'SIGABRT',
+ 'SIGFPE', 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'SIG_DFL', 'SIG_ERR',
+ 'SIG_IGN', 'BUFSIZ', 'EOF', 'FILENAME_MAX', 'FOPEN_MAX', 'L_tmpnam', 'NULL',
+ 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'stdin', 'stdout', 'stderr',
+ 'EXIT_FAILURE', 'EXIT_SUCCESS', 'RAND_MAX', 'CLOCKS_PER_SEC',
+ // Mac-specific constants:
+ 'kCFAllocatorDefault'
+ ),
+ 3 => array(
+ 'printf', 'fprintf', 'snprintf', 'sprintf', 'assert',
+ 'isalnum', 'isalpha', 'isdigit', 'iscntrl', 'isgraph', 'islower', 'isprint',
+ 'ispunct', 'isspace', 'ispunct', 'isupper', 'isxdigit', 'tolower', 'toupper',
+ 'exp', 'log', 'log10', 'pow', 'sqrt', 'ceil', 'floor', 'fabs', 'ldexp',
+ 'frexp', 'modf', 'fmod', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2',
+ 'sinh', 'cosh', 'tanh', 'setjmp', 'longjmp', 'asin', 'acos', 'atan', 'atan2',
+ 'va_start', 'va_arg', 'va_end', 'offsetof', 'sizeof', 'fopen', 'freopen',
+ 'fflush', 'fclose', 'remove', 'rename', 'tmpfile', 'tmpname', 'setvbuf',
+ 'setbuf', 'vfprintf', 'vprintf', 'vsprintf', 'fscanf', 'scanf', 'sscanf',
+ 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc',
+ 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind',
+ 'fgetpos', 'fsetpos', 'clearerr', 'feof', 'ferror', 'perror', 'abs', 'labs',
+ 'div', 'ldiv', 'atof', 'atoi', 'atol', 'strtod', 'strtol', 'strtoul', 'calloc',
+ 'malloc', 'realloc', 'free', 'abort', 'exit', 'atexit', 'system', 'getenv',
+ 'bsearch', 'qsort', 'rand', 'srand', 'strcpy', 'strncpy', 'strcat', 'strncat',
+ 'strcmp', 'strncmp', 'strcoll', 'strchr', 'strrchr', 'strspn', 'strcspn',
+ 'strpbrk', 'strstr', 'strlen', 'strerror', 'strtok', 'strxfrm', 'memcpy',
+ 'memmove', 'memcmp', 'memchr', 'memset', 'clock', 'time', 'difftime', 'mktime',
+ 'asctime', 'ctime', 'gmtime', 'localtime', 'strftime'
+ ),
+ 4 => array(
+ 'auto', 'char', 'const', 'double', 'float', 'int', 'long',
+ 'register', 'short', 'signed', 'sizeof', 'static', 'string', 'struct',
+ 'typedef', 'union', 'unsigned', 'void', 'volatile', 'extern', 'jmp_buf',
+ 'signal', 'raise', 'va_list', 'ptrdiff_t', 'size_t', 'FILE', 'fpos_t',
+ 'div_t', 'ldiv_t', 'clock_t', 'time_t', 'tm',
+ // Mac-specific types:
+ 'CFArrayRef', 'CFDictionaryRef', 'CFMutableDictionaryRef', 'CFBundleRef', 'CFSetRef', 'CFStringRef',
+ 'CFURLRef', 'CFLocaleRef', 'CFDateFormatterRef', 'CFNumberFormatterRef', 'CFPropertyListRef',
+ 'CFTreeRef', 'CFWriteStreamRef', 'CFCharacterSetRef', 'CFMutableStringRef', 'CFNotificationRef',
+ 'CFNotificationRef', 'CFReadStreamRef', 'CFNull', 'CFAllocatorRef', 'CFBagRef', 'CFBinaryHeapRef',
+ 'CFBitVectorRef', 'CFBooleanRef', 'CFDataRef', 'CFDateRef', 'CFMachPortRef', 'CFMessagePortRef',
+ 'CFMutableArrayRef', 'CFMutableBagRef', 'CFMutableBitVectorRef', 'CFMutableCharacterSetRef',
+ 'CFMutableDataRef', 'CFMutableSetRef', 'CFNumberRef', 'CFPlugInRef', 'CFPlugInInstanceRef',
+ 'CFRunLoopRef', 'CFRunLoopObserverRef', 'CFRunLoopSourceRef', 'CFRunLoopTimerRef', 'CFSocketRef',
+ 'CFTimeZoneRef', 'CFTypeRef', 'CFUserNotificationRef', 'CFUUIDRef', 'CFXMLNodeRef', 'CFXMLParserRef',
+ 'CFXMLTreeRef'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '=', '+', '-', '*', '/', '!', '%', '^', '&', ':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000ff;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #0000dd;',
+ 4 => 'color: #0000ff;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #ff0000;',
+ 2 => 'color: #339900;',
+ 'MULTI' => 'color: #ff0000; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #666666; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #666666;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #0000dd;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #00eeff;',
+ 2 => 'color: #00eeff;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.opengroup.org/onlinepubs/009695399/functions/{FNAME}.html',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/caddcl.php b/plugins/dokuwiki/inc/geshi/caddcl.php
new file mode 100644
index 0000000..444f774
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/caddcl.php
@@ -0,0 +1,127 @@
+<?php
+/*************************************************************************************
+ * caddcl.php
+ * ----------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * CAD DCL (Dialog Control Language) file for GeSHi.
+ *
+ * DCL for AutoCAD 12 or later and IntelliCAD all versions.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/1!/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'CAD DCL',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'boxed_column','boxed_radio_column','boxed_radio_row','boxed_row',
+ 'column','concatenation','button','dialog','edit_box','image','image_button',
+ 'errtile','list_box','ok_cancel','ok_cancel_help','ok_cancel_help_errtile',
+ 'ok_cancel_help_info','ok_only','paragraph','popup_list','radio_button',
+ 'radio_column','radio_row','row','slider','spacer','spacer_0','spacer_1','text',
+ 'text_part','toggle',
+ 'action','alignment','allow_accept','aspect_ratio','big_increment',
+ 'children_alignment','children_fixed_height',
+ 'children_fixed_width','color',
+ 'edit_limit','edit_width','fixed_height','fixed_width',
+ 'height','initial_focus','is_cancel','is_default',
+ 'is_enabled','is_tab_stop','is-bold','key','label','layout','list',
+ 'max_value','min_value','mnemonic','multiple_select','password_char',
+ 'small_increment','tabs','tab_truncate','value','width',
+ 'false','true','left','right','centered','top','bottom',
+ 'dialog_line','dialog_foreground','dialog_background',
+ 'graphics_background','black','red','yellow','green','cyan',
+ 'blue','magenta','whitegraphics_foreground',
+ 'horizontal','vertical'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '=', '+', '-', '*', '/', '!', '%', '^', '&', ':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/cadlisp.php b/plugins/dokuwiki/inc/geshi/cadlisp.php
new file mode 100644
index 0000000..d57e5ed
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/cadlisp.php
@@ -0,0 +1,187 @@
+<?php
+/*************************************************************************************
+ * cadlisp.php
+ * -----------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/blog)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * AutoCAD/IntelliCAD Lisp language file for GeSHi.
+ *
+ * For AutoCAD V.12..2005 and IntelliCAD all versions.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'CAD Lisp',
+ 'COMMENT_SINGLE' => array(1 => ";"),
+ 'COMMENT_MULTI' => array(";|" => "|;"),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'abs','acad_colordlg','acad_helpdlg','acad_strlsort','action_tile',
+ 'add_list','alert','alloc','and','angle','angtof','angtos','append','apply',
+ 'arx','arxload','arxunload','ascii','assoc','atan','atof','atoi','atom',
+ 'atoms-family','autoarxload','autoload','Boole','boundp','caddr',
+ 'cadr','car','cdr','chr','client_data_tile','close','command','cond',
+ 'cons','cos','cvunit','defun','defun-q','defun-q-list-ref',
+ 'defun-q-list-set','dictadd','dictnext','dictremove','dictrename',
+ 'dictsearch','dimx_tile','dimy_tile','distance','distof','done_dialog',
+ 'end_image','end_list','entdel','entget','entlast','entmake',
+ 'entmakex','entmod','entnext','entsel','entupd','eq','equal','eval','exit',
+ 'exp','expand','expt','fill_image','findfile','fix','float','foreach','function',
+ 'gc','gcd','get_attr','get_tile','getangle','getcfg','getcname','getcorner',
+ 'getdist','getenv','getfiled','getint','getkword','getorient','getpoint',
+ 'getreal','getstring','getvar','graphscr','grclear','grdraw','grread','grtext',
+ 'grvecs','handent','help','if','initdia','initget','inters','itoa','lambda','last',
+ 'layoutlist','length','list','listp','load','load_dialog','log','logand','logior',
+ 'lsh','mapcar','max','mem','member','menucmd','menugroup','min','minusp','mode_tile',
+ 'namedobjdict','nentsel','nentselp','new_dialog','nil','not','nth','null',
+ 'numberp','open','or','osnap','polar','prin1','princ','print','progn','prompt',
+ 'quit','quote','read','read-char','read-line','redraw','regapp','rem','repeat',
+ 'reverse','rtos','set','set_tile','setcfg','setenv','setfunhelp','setq','setvar',
+ 'setview','sin','slide_image','snvalid','sqrt','ssadd','ssdel','ssget','ssgetfirst',
+ 'sslength','ssmemb','ssname','ssnamex','sssetfirst','start_dialog','start_image',
+ 'start_list','startapp','strcase','strcat','strlen','subst','substr','t','tablet',
+ 'tblnext','tblobjname','tblsearch','term_dialog','terpri','textbox','textpage',
+ 'textscr','trace','trans','type','unload_dialog','untrace','vector_image','ver',
+ 'vports','wcmatch','while','write-char','write-line','xdroom','xdsize','zerop',
+ 'vl-acad-defun','vl-acad-undefun','vl-arx-import','vlax-3D-point',
+ 'vlax-add-cmd','vlax-create-object','vlax-curve-getArea',
+ 'vlax-curve-getClosestPointTo','vlax-curve-getClosestPointToProjection',
+ 'vlax-curve-getDistAtParam','vlax-curve-getDistAtPoint',
+ 'vlax-curve-getEndParam','vlax-curve-getEndPoint',
+ 'vlax-curve-getFirstDeriv','vlax-curve-getParamAtDist',
+ 'vlax-curve-getParamAtPoint','vlax-curve-getPointAtDist',
+ 'vlax-curve-getPointAtParam','vlax-curve-getSecondDeriv',
+ 'vlax-curve-getStartParam','vlax-curve-getStartPoint',
+ 'vlax-curve-isClosed','vlax-curve-isPeriodic','vlax-curve-isPlanar',
+ 'vlax-dump-object','vlax-erased-p','vlax-for','vlax-get-acad-object',
+ 'vlax-get-object','vlax-get-or-create-object','vlax-get-property',
+ 'vlax-import-type-library','vlax-invoke-method','vlax-ldata-delete',
+ 'vlax-ldata-get','vlax-ldata-list','vlax-ldata-put','vlax-ldata-test',
+ 'vlax-make-safearray','vlax-make-variant','vlax-map-collection',
+ 'vlax-method-applicable-p','vlax-object-released-p','vlax-product-key',
+ 'vlax-property-available-p','vlax-put-property','vlax-read-enabled-p',
+ 'vlax-release-object','vlax-remove-cmd','vlax-safearray-fill',
+ 'vlax-safearray-get-dim','vlax-safearray-get-element',
+ 'vlax-safearray-get-l-bound','vlax-safearray-get-u-bound',
+ 'vlax-safearray-put-element','vlax-safearray-type','vlax-tmatrix',
+ 'vlax-typeinfo-available-p','vlax-variant-change-type',
+ 'vlax-variant-type','vlax-variant-value','vlax-write-enabled-p',
+ 'vl-bb-ref','vl-bb-set','vl-catch-all-apply','vl-catch-all-error-message',
+ 'vl-catch-all-error-p','vl-cmdf','vl-consp','vl-directory-files','vl-doc-export',
+ 'vl-doc-import','vl-doc-ref','vl-doc-set','vl-every','vl-exit-with-error',
+ 'vl-exit-with-value','vl-file-copy','vl-file-delete','vl-file-directory-p',
+ 'vl-filename-base','vl-filename-directory','vl-filename-extension',
+ 'vl-filename-mktemp','vl-file-rename','vl-file-size','vl-file-systime',
+ 'vl-get-resource','vlisp-compile','vl-list-exported-functions',
+ 'vl-list-length','vl-list-loaded-vlx','vl-load-all','vl-load-com',
+ 'vl-load-reactors','vl-member-if','vl-member-if-not','vl-position',
+ 'vl-prin1-to-string','vl-princ-to-string','vl-propagate','vlr-acdb-reactor',
+ 'vlr-add','vlr-added-p','vlr-beep-reaction','vlr-command-reactor',
+ 'vlr-current-reaction-name','vlr-data','vlr-data-set',
+ 'vlr-deepclone-reactor','vlr-docmanager-reactor','vlr-dwg-reactor',
+ 'vlr-dxf-reactor','vlr-editor-reactor','vl-registry-delete',
+ 'vl-registry-descendents','vl-registry-read','vl-registry-write',
+ 'vl-remove','vl-remove-if','vl-remove-if-not','vlr-insert-reactor',
+ 'vlr-linker-reactor','vlr-lisp-reactor','vlr-miscellaneous-reactor',
+ 'vlr-mouse-reactor','vlr-notification','vlr-object-reactor',
+ 'vlr-owner-add','vlr-owner-remove','vlr-owners','vlr-pers','vlr-pers-list',
+ 'vlr-pers-p','vlr-pers-release','vlr-reaction-names','vlr-reactions',
+ 'vlr-reaction-set','vlr-reactors','vlr-remove','vlr-remove-all',
+ 'vlr-set-notification','vlr-sysvar-reactor','vlr-toolbar-reactor',
+ 'vlr-trace-reaction','vlr-type','vlr-types','vlr-undo-reactor',
+ 'vlr-wblock-reactor','vlr-window-reactor','vlr-xref-reactor',
+ 'vl-some','vl-sort','vl-sort-i','vl-string-elt','vl-string-left-trim',
+ 'vl-string-mismatch','vl-string-position','vl-string-right-trim',
+ 'vl-string-search','vl-string-subst','vl-string-translate','vl-string-trim',
+ 'vl-symbol-name','vl-symbolp','vl-symbol-value','vl-unload-vlx','vl-vbaload',
+ 'vl-vbarun','vl-vlx-loaded-p'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '!', '%', '^', '&', '/','+','-','*','=','<','>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/cpp.php b/plugins/dokuwiki/inc/geshi/cpp.php
new file mode 100644
index 0000000..3df350e
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/cpp.php
@@ -0,0 +1,172 @@
+<?php
+/*************************************************************************************
+ * cpp.php
+ * -------
+ * Author: Dennis Bayer (Dennis.Bayer@mnifh-giessen.de)
+ * Contributors:
+ * - M. Uli Kusterer (witness.of.teachtext@gmx.net)
+ * - Jack Lloyd (lloyd@randombit.net)
+ * Copyright: (c) 2004 Dennis Bayer, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2004/09/27
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * C++ language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/XX/XX (1.0.2)
+ * - Added several new keywords (Jack Lloyd)
+ * 2004/11/27 (1.0.1)
+ * - Added StdCLib function and constant names, changed color scheme to
+ * a cleaner one. (M. Uli Kusterer)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'C++',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'case', 'continue', 'default', 'do', 'else', 'for', 'goto', 'if', 'return',
+ 'switch', 'while'
+ ),
+ 2 => array(
+ 'NULL', 'false', 'break', 'true', 'enum', 'errno', 'EDOM',
+ 'ERANGE', 'FLT_RADIX', 'FLT_ROUNDS', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG',
+ 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON', 'FLT_MANT_DIG', 'DBL_MANT_DIG',
+ 'LDBL_MANT_DIG', 'FLT_MAX', 'DBL_MAX', 'LDBL_MAX', 'FLT_MAX_EXP', 'DBL_MAX_EXP',
+ 'LDBL_MAX_EXP', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', 'FLT_MIN_EXP', 'DBL_MIN_EXP',
+ 'LDBL_MIN_EXP', 'CHAR_BIT', 'CHAR_MAX', 'CHAR_MIN', 'SCHAR_MAX', 'SCHAR_MIN',
+ 'UCHAR_MAX', 'SHRT_MAX', 'SHRT_MIN', 'USHRT_MAX', 'INT_MAX', 'INT_MIN',
+ 'UINT_MAX', 'LONG_MAX', 'LONG_MIN', 'ULONG_MAX', 'HUGE_VAL', 'SIGABRT',
+ 'SIGFPE', 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'SIG_DFL', 'SIG_ERR',
+ 'SIG_IGN', 'BUFSIZ', 'EOF', 'FILENAME_MAX', 'FOPEN_MAX', 'L_tmpnam', 'NULL',
+ 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'stdin', 'stdout', 'stderr',
+ 'EXIT_FAILURE', 'EXIT_SUCCESS', 'RAND_MAX', 'CLOCKS_PER_SEC',
+ 'virtual', 'public', 'private', 'protected', 'template', 'using', 'namespace',
+ 'try', 'catch', 'inline', 'dynamic_cast', 'const_cast', 'reinterpret_cast',
+ 'static_cast', 'explicit', 'friend', 'wchar_t', 'typename', 'typeid', 'class'
+ ),
+ 3 => array(
+ 'cin', 'cerr', 'clog', 'cout', 'delete', 'new', 'this',
+ 'printf', 'fprintf', 'snprintf', 'sprintf', 'assert',
+ 'isalnum', 'isalpha', 'isdigit', 'iscntrl', 'isgraph', 'islower', 'isprint',
+ 'ispunct', 'isspace', 'ispunct', 'isupper', 'isxdigit', 'tolower', 'toupper',
+ 'exp', 'log', 'log10', 'pow', 'sqrt', 'ceil', 'floor', 'fabs', 'ldexp',
+ 'frexp', 'modf', 'fmod', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2',
+ 'sinh', 'cosh', 'tanh', 'setjmp', 'longjmp', 'asin', 'acos', 'atan', 'atan2',
+ 'va_start', 'va_arg', 'va_end', 'offsetof', 'sizeof', 'fopen', 'freopen',
+ 'fflush', 'fclose', 'remove', 'rename', 'tmpfile', 'tmpname', 'setvbuf',
+ 'setbuf', 'vfprintf', 'vprintf', 'vsprintf', 'fscanf', 'scanf', 'sscanf',
+ 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc',
+ 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind',
+ 'fgetpos', 'fsetpos', 'clearerr', 'feof', 'ferror', 'perror', 'abs', 'labs',
+ 'div', 'ldiv', 'atof', 'atoi', 'atol', 'strtod', 'strtol', 'strtoul', 'calloc',
+ 'malloc', 'realloc', 'free', 'abort', 'exit', 'atexit', 'system', 'getenv',
+ 'bsearch', 'qsort', 'rand', 'srand', 'strcpy', 'strncpy', 'strcat', 'strncat',
+ 'strcmp', 'strncmp', 'strcoll', 'strchr', 'strrchr', 'strspn', 'strcspn',
+ 'strpbrk', 'strstr', 'strlen', 'strerror', 'strtok', 'strxfrm', 'memcpy',
+ 'memmove', 'memcmp', 'memchr', 'memset', 'clock', 'time', 'difftime', 'mktime',
+ 'asctime', 'ctime', 'gmtime', 'localtime', 'strftime'
+ ),
+ 4 => array(
+ 'auto', 'bool', 'char', 'const', 'double', 'float', 'int', 'long', 'longint',
+ 'register', 'short', 'shortint', 'signed', 'static', 'struct',
+ 'typedef', 'union', 'unsigned', 'void', 'volatile', 'extern', 'jmp_buf',
+ 'signal', 'raise', 'va_list', 'ptrdiff_t', 'size_t', 'FILE', 'fpos_t',
+ 'div_t', 'ldiv_t', 'clock_t', 'time_t', 'tm',
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '=', '+', '-', '*', '/', '!', '%', '^', '&', ':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000ff;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #0000dd;',
+ 4 => 'color: #0000ff;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #ff0000;',
+ 2 => 'color: #339900;',
+ 'MULTI' => 'color: #ff0000; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #666666; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #666666;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #0000dd;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #00eeff;',
+ 2 => 'color: #00eeff;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/csharp.php b/plugins/dokuwiki/inc/geshi/csharp.php
new file mode 100644
index 0000000..6371405
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/csharp.php
@@ -0,0 +1,233 @@
+<?php
+/*************************************************************************************
+ * csharp.php
+ * ----------
+ * Author: Alan Juden (alan@judenware.org)
+ * Copyright: (c) 2004 Alan Juden, Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.6 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * C# language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/01/05 (1.0.1)
+ * - Used hardquote support for @"..." strings (Cliff Stanford)
+ * 2004/11/27 (1.0.0)
+ * - Initial release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+ $language_data = array (
+ 'LANG_NAME' => 'C#',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'HARDQUOTE' => array('@"', '"'),
+ 'HARDESCAPE' => array('""'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'as', 'auto', 'base', 'break', 'case', 'catch', 'const', 'continue',
+ 'default', 'do', 'else', 'event', 'explicit', 'extern', 'false',
+ 'finally', 'fixed', 'for', 'foreach', 'goto', 'if', 'implicit',
+ 'in', 'internal', 'lock', 'namespace', 'null', 'operator', 'out',
+ 'override', 'params', 'private', 'protected', 'public', 'readonly',
+ 'ref', 'return', 'sealed', 'stackalloc', 'static', 'switch', 'this',
+ 'throw', 'true', 'try', 'unsafe', 'using', 'virtual', 'void', 'while'
+ ),
+ 2 => array(
+ '#elif', '#endif', '#endregion', '#else', '#error', '#define', '#if',
+ '#line', '#region', '#undef', '#warning'
+ ),
+ 3 => array(
+ 'checked', 'is', 'new', 'sizeof', 'typeof', 'unchecked'
+ ),
+ 4 => array(
+ 'bool', 'byte', 'char', 'class', 'decimal', 'delegate', 'double',
+ 'enum', 'float', 'int', 'interface', 'long', 'object', 'sbyte',
+ 'short', 'string', 'struct', 'uint', 'ulong', 'ushort'
+ ),
+ 5 => array(
+ 'Microsoft.Win32',
+ 'System',
+ 'System.CodeDOM',
+ 'System.CodeDOM.Compiler',
+ 'System.Collections',
+ 'System.Collections.Bases',
+ 'System.ComponentModel',
+ 'System.ComponentModel.Design',
+ 'System.ComponentModel.Design.CodeModel',
+ 'System.Configuration',
+ 'System.Configuration.Assemblies',
+ 'System.Configuration.Core',
+ 'System.Configuration.Install',
+ 'System.Configuration.Interceptors',
+ 'System.Configuration.Schema',
+ 'System.Configuration.Web',
+ 'System.Core',
+ 'System.Data',
+ 'System.Data.ADO',
+ 'System.Data.Design',
+ 'System.Data.Internal',
+ 'System.Data.SQL',
+ 'System.Data.SQLTypes',
+ 'System.Data.XML',
+ 'System.Data.XML.DOM',
+ 'System.Data.XML.XPath',
+ 'System.Data.XML.XSLT',
+ 'System.Diagnostics',
+ 'System.Diagnostics.SymbolStore',
+ 'System.DirectoryServices',
+ 'System.Drawing',
+ 'System.Drawing.Design',
+ 'System.Drawing.Drawing2D',
+ 'System.Drawing.Imaging',
+ 'System.Drawing.Printing',
+ 'System.Drawing.Text',
+ 'System.Globalization',
+ 'System.IO',
+ 'System.IO.IsolatedStorage',
+ 'System.Messaging',
+ 'System.Net',
+ 'System.Net.Sockets',
+ 'System.NewXml',
+ 'System.NewXml.XPath',
+ 'System.NewXml.Xsl',
+ 'System.Reflection',
+ 'System.Reflection.Emit',
+ 'System.Resources',
+ 'System.Runtime.InteropServices',
+ 'System.Runtime.InteropServices.Expando',
+ 'System.Runtime.Remoting',
+ 'System.Runtime.Serialization',
+ 'System.Runtime.Serialization.Formatters',
+ 'System.Runtime.Serialization.Formatters.Binary',
+ 'System.Security',
+ 'System.Security.Cryptography',
+ 'System.Security.Cryptography.X509Certificates',
+ 'System.Security.Permissions',
+ 'System.Security.Policy',
+ 'System.Security.Principal',
+ 'System.ServiceProcess',
+ 'System.Text',
+ 'System.Text.RegularExpressions',
+ 'System.Threading',
+ 'System.Timers',
+ 'System.Web',
+ 'System.Web.Caching',
+ 'System.Web.Configuration',
+ 'System.Web.Security',
+ 'System.Web.Services',
+ 'System.Web.Services.Description',
+ 'System.Web.Services.Discovery',
+ 'System.Web.Services.Protocols',
+ 'System.Web.UI',
+ 'System.Web.UI.Design',
+ 'System.Web.UI.Design.WebControls',
+ 'System.Web.UI.Design.WebControls.ListControls',
+ 'System.Web.UI.HtmlControls',
+ 'System.Web.UI.WebControls',
+ 'System.WinForms',
+ 'System.WinForms.ComponentModel',
+ 'System.WinForms.Design',
+ 'System.Xml',
+ 'System.Xml.Serialization',
+ 'System.Xml.Serialization.Code',
+ 'System.Xml.Serialization.Schema'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '+', '-', '*', '?', '=', '/', '%', '&', '>', '<', '^', '!', '|', ':',
+ '(', ')', '{', '}', '[', ']'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0600FF;',
+ 2 => 'color: #FF8000; font-weight: bold;',
+ 3 => 'color: #008000;',
+ 4 => 'color: #FF0000;',
+ 5 => 'color: #000000;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #008080; font-style: italic;',
+ 2 => 'color: #008080;',
+ 'MULTI' => 'color: #008080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #008080; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #808080;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #FF0000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #0000FF;',
+ 2 => 'color: #0000FF;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #008000;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.google.com/search?q={FNAME}+msdn.microsoft.com',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/css.php b/plugins/dokuwiki/inc/geshi/css.php
new file mode 100644
index 0000000..3331027
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/css.php
@@ -0,0 +1,178 @@
+<?php
+/*************************************************************************************
+ * css.php
+ * -------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2004/06/18
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * CSS language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Changed regexps to catch "-" symbols
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Improve or drop regexps for class/id/psuedoclass highlighting
+ * * Re-look at keywords - possibly to make several CSS language
+ * files, all with different versions of CSS in them
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+
+$language_data = array (
+ 'LANG_NAME' => 'CSS',
+ 'COMMENT_SINGLE' => array(1 => '@'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"', "'"),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'aqua', 'azimuth', 'background-attachment', 'background-color',
+ 'background-image', 'background-position', 'background-repeat',
+ 'background', 'black', 'blue', 'border-bottom-color', 'border-bottom-style',
+ 'border-bottom-width', 'border-left-color', 'border-left-style',
+ 'border-left-width', 'border-right', 'border-right-color',
+ 'border-right-style', 'border-right-width', 'border-top-color',
+ 'border-top-style', 'border-top-width','border-bottom', 'border-collapse',
+ 'border-left', 'border-width', 'border-color', 'border-spacing',
+ 'border-style', 'border-top', 'border', 'caption-side',
+ 'clear', 'clip', 'color', 'content', 'counter-increment', 'counter-reset',
+ 'cue-after', 'cue-before', 'cue', 'cursor', 'direction', 'display',
+ 'elevation', 'empty-cells', 'float', 'font-family', 'font-size',
+ 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant',
+ 'font-weight', 'font', 'height', 'letter-spacing', 'line-height',
+ 'list-style', 'list-style-image', 'list-style-position', 'list-style-type',
+ 'margin-bottom', 'margin-left', 'margin-right', 'margin-top', 'margin',
+ 'marker-offset', 'marks', 'max-height', 'max-width', 'min-height',
+ 'min-width', 'orphans', 'outline', 'outline-color', 'outline-style',
+ 'outline-width', 'overflow', 'padding-bottom', 'padding-left',
+ 'padding-right', 'padding-top', 'padding', 'page', 'page-break-after',
+ 'page-break-before', 'page-break-inside', 'pause-after', 'pause-before',
+ 'pause', 'pitch', 'pitch-range', 'play-during', 'position', 'quotes',
+ 'richness', 'right', 'size', 'speak-header', 'speak-numeral', 'speak-punctuation',
+ 'speak', 'speech-rate', 'stress', 'table-layout', 'text-align', 'text-decoration',
+ 'text-indent', 'text-shadow', 'text-transform', 'top', 'unicode-bidi',
+ 'vertical-align', 'visibility', 'voice-family', 'volume', 'white-space', 'widows',
+ 'width', 'word-spacing', 'z-index', 'bottom', 'left'
+ ),
+ 2 => array(
+ 'above', 'absolute', 'always', 'armenian', 'aural', 'auto', 'avoid',
+ 'baseline', 'behind', 'below', 'bidi-override', 'blink', 'block', 'bold', 'bolder', 'both',
+ 'capitalize', 'center-left', 'center-right', 'center', 'circle', 'cjk-ideographic',
+ 'close-quote', 'collapse', 'condensed', 'continuous', 'crop', 'crosshair', 'cross', 'cursive',
+ 'dashed', 'decimal-leading-zero', 'decimal', 'default', 'digits', 'disc', 'dotted', 'double',
+ 'e-resize', 'embed', 'extra-condensed', 'extra-expanded', 'expanded',
+ 'fantasy', 'far-left', 'far-right', 'faster', 'fast', 'fixed', 'fuchsia',
+ 'georgian', 'gray', 'green', 'groove', 'hebrew', 'help', 'hidden', 'hide', 'higher',
+ 'high', 'hiragana-iroha', 'hiragana', 'icon', 'inherit', 'inline-table', 'inline',
+ 'inset', 'inside', 'invert', 'italic', 'justify', 'katakana-iroha', 'katakana',
+ 'landscape', 'larger', 'large', 'left-side', 'leftwards', 'level', 'lighter', 'lime', 'line-through', 'list-item', 'loud', 'lower-alpha', 'lower-greek', 'lower-roman', 'lowercase', 'ltr', 'lower', 'low',
+ 'maroon', 'medium', 'message-box', 'middle', 'mix', 'monospace',
+ 'n-resize', 'narrower', 'navy', 'ne-resize', 'no-close-quote', 'no-open-quote', 'no-repeat', 'none', 'normal', 'nowrap', 'nw-resize',
+ 'oblique', 'olive', 'once', 'open-quote', 'outset', 'outside', 'overline',
+ 'pointer', 'portrait', 'purple', 'px',
+ 'red', 'relative', 'repeat-x', 'repeat-y', 'repeat', 'rgb', 'ridge', 'right-side', 'rightwards',
+ 's-resize', 'sans-serif', 'scroll', 'se-resize', 'semi-condensed', 'semi-expanded', 'separate', 'serif', 'show', 'silent', 'silver', 'slow', 'slower', 'small-caps', 'small-caption', 'smaller', 'soft', 'solid', 'spell-out', 'square',
+ 'static', 'status-bar', 'super', 'sw-resize',
+ 'table-caption', 'table-cell', 'table-column', 'table-column-group', 'table-footer-group', 'table-header-group', 'table-row', 'table-row-group', 'teal', 'text', 'text-bottom', 'text-top', 'thick', 'thin', 'transparent',
+ 'ultra-condensed', 'ultra-expanded', 'underline', 'upper-alpha', 'upper-latin', 'upper-roman', 'uppercase', 'url',
+ 'visible',
+ 'w-resize', 'wait', 'white', 'wider',
+ 'x-fast', 'x-high', 'x-large', 'x-loud', 'x-low', 'x-small', 'x-soft', 'xx-large', 'xx-small',
+ 'yellow', 'yes'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', ':', ';'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => true,
+ 2 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000000; font-weight: bold;',
+ 2 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #a1a100;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #cc00cc;',
+ 1 => 'color: #6666ff;',
+ 2 => 'color: #3333ff;',
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '\#[a-zA-Z0-9\-]+\s+\{',
+ 1 => '\.[a-zA-Z0-9\-]+\s',
+ 2 => ':[a-zA-Z0-9\-]+\s'
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/d.php b/plugins/dokuwiki/inc/geshi/d.php
new file mode 100644
index 0000000..1cb8d74
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/d.php
@@ -0,0 +1,287 @@
+<?php
+/*************************************************************************************
+ * d.php
+ * -----
+ * Author: Thomas Kuehne (thomas@kuehne.cn)
+ * Copyright: (c) 2005 Thomas Kuehne (http://thomas.kuehne.cn/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2005/04/22
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * D language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/04/22 (0.0.2)
+ * - added _d_* and sizeof/ptrdiff_t
+ * 2005/04/20 (0.0.1)
+ * - First release
+ *
+ * TODO (updated 2005/04/22)
+ * -------------------------
+ * * nested comments
+ * * correct handling of r"" and ``
+ * * correct handling of ... and ..
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'D',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"', "'", '`'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'while',
+ 'switch',
+ 'if',
+ 'foreach',
+ 'for',
+ 'goto',
+ 'return',
+ 'else',
+ 'do',
+ 'case',
+ 'continue',
+ 'break'
+ ),
+ 2 => array(
+ 'with',
+ 'union',
+ 'typeof',
+ 'typeid',
+ 'typedef',
+ 'try',
+ 'true',
+ 'throw',
+ 'this',
+ 'super',
+ 'pragma',
+ 'out',
+ 'null',
+ 'new',
+ 'module',
+ 'mixin',
+ 'is',
+ 'invariant',
+ 'interface',
+ 'inout',
+ 'in',
+ 'import',
+ 'function',
+ 'finally',
+ 'false',
+ 'extern',
+ 'delete',
+ 'delegate',
+ 'default',
+ 'catch',
+ 'cast',
+ 'body',
+ 'assert',
+ 'asm',
+ 'alias'
+ ),
+ 3 => array(
+ 'TypeInfo',
+ 'SwitchError',
+ 'OutOfMemoryException',
+ 'Object',
+ 'ModuleInfo',
+ 'Interface',
+ 'Exception',
+ 'Error',
+ 'ClassInfo',
+ 'ArrayBoundsError',
+ 'AssertError',
+ '_d_throw',
+ '_d_switch_ustring',
+ '_d_switch_string',
+ '_d_switch_dstring',
+ '_d_OutOfMemory',
+ '_d_obj_eq',
+ '_d_obj_cmp',
+ '_d_newclass',
+ '_d_newbitarray',
+ '_d_newarrayi',
+ '_d_new',
+ '_d_monitorrelease',
+ '_d_monitor_prolog',
+ '_d_monitor_handler',
+ '_d_monitorexit',
+ '_d_monitor_epilog',
+ '_d_monitorenter',
+ '_d_local_unwind',
+ '_d_isbaseof2',
+ '_d_isbaseof',
+ '_d_invariant',
+ '_d_interface_vtbl',
+ '_d_interface_cast',
+ '_d_framehandler',
+ '_d_exception_filter',
+ '_d_exception',
+ '_d_dynamic_cast',
+ '_d_delmemory',
+ '_d_delinterface',
+ '_d_delclass',
+ '_d_delarray',
+ '_d_criticalexit',
+ '_d_criticalenter',
+ '_d_create_exception_object',
+ '_d_callfinalizer',
+ '_d_arraysetlengthb',
+ '_d_arraysetlength',
+ '_d_arraysetbit2',
+ '_d_arraysetbit',
+ '_d_arraycopybit',
+ '_d_arraycopy',
+ '_d_arraycatn',
+ '_d_arraycatb',
+ '_d_arraycat',
+ '_d_arraycast_frombit',
+ '_d_arraycast',
+ '_d_arrayappendcb',
+ '_d_arrayappendc',
+ '_d_arrayappendb',
+ '_d_arrayappend',
+ ),
+ 4 => array(
+ 'wchar',
+ 'volatile',
+ 'void',
+ 'version',
+ 'ushort',
+ 'unittest',
+ 'ulong',
+ 'uint',
+ 'ucent',
+ 'ubyte',
+ 'template',
+ 'struct',
+ 'static',
+ 'synchronized',
+ 'size_t',
+ 'short',
+ 'real',
+ 'public',
+ 'protected',
+ 'private',
+ 'ptrdiff_t',
+ 'package',
+ 'override',
+ 'long',
+ 'int',
+ 'ireal',
+ 'ifloat',
+ 'idouble',
+ 'float',
+ 'final',
+ 'export',
+ 'enum',
+ 'double',
+ 'deprecated',
+ 'debug',
+ 'dchar',
+ 'creal',
+ 'const',
+ 'class',
+ 'char',
+ 'cfloat',
+ 'cent',
+ 'cdouble',
+ 'byte',
+ 'bool',
+ 'bit',
+ 'auto',
+ 'align',
+ 'abstract'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '?', '!', ';', ':', ',', '...', '..',
+ '+', '-', '*', '/', '%', '&', '|', '^', '<', '>', '=', '~',
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ 4 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #aaaadd; font-weight: bold;',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1=> 'color: #808080; font-style: italic;',
+ 2=> 'color: #a1a100;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/delphi.php b/plugins/dokuwiki/inc/geshi/delphi.php
new file mode 100644
index 0000000..3c85f30
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/delphi.php
@@ -0,0 +1,272 @@
+<?php
+/*************************************************************************************
+ * delphi.php
+ * ----------
+ * Author: Járja Norbert (jnorbi@vipmail.hu)
+ * Copyright: (c) 2004 Járja Norbert, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.17.2.4 $
+ * Date Started: 2004/07/26
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * Delphi (Object Pascal) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/11/19 (1.0.3)
+ * - Updated the very incomplete keyword and type lists
+ * 2005/09/03 (1.0.2)
+ * - Added support for hex numbers and string entities
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Delphi',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('(*' => '*)', '{' => '}'),
+ 'CASE_KEYWORDS' => 0,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'Abstract', 'And', 'Array', 'As', 'Asm', 'At', 'Begin', 'Case', 'Class',
+ 'Const', 'Constructor', 'Contains', 'Destructor', 'DispInterface', 'Div',
+ 'Do', 'DownTo', 'Else', 'End', 'Except', 'File', 'Finalization',
+ 'Finally', 'For', 'Function', 'Goto', 'If', 'Implementation', 'In',
+ 'Inherited', 'Initialization', 'Inline', 'Interface', 'Is', 'Label',
+ 'Mod', 'Not', 'Object', 'Of', 'On', 'Or', 'Overload', 'Override',
+ 'Package', 'Packed', 'Private', 'Procedure', 'Program', 'Property',
+ 'Protected', 'Public', 'Published', 'Raise', 'Record', 'Repeat',
+ 'Requires', 'Resourcestring', 'Set', 'Shl', 'Shr', 'Then', 'ThreadVar',
+ 'To', 'Try', 'Type', 'Unit', 'Until', 'Uses', 'Var', 'Virtual', 'While',
+ 'With', 'Xor', 'assembler', 'cdecl', 'far', 'near', 'pascal', 'register',
+ 'safecall', 'stdcall', 'varargs'
+ ),
+ 2 => array(
+ 'nil', 'false', 'self', 'true', 'var', 'type', 'const'
+ ),
+ 3 => array(
+ 'Abs', 'AcquireExceptionObject', 'Addr', 'AnsiToUtf8', 'Append', 'ArcTan',
+ 'Assert', 'AssignFile', 'Assigned', 'BeginThread', 'BlockRead',
+ 'BlockWrite', 'Break', 'ChDir', 'Chr', 'Close', 'CloseFile',
+ 'CompToCurrency', 'CompToDouble', 'Concat', 'Continue', 'Copy', 'Cos',
+ 'Dec', 'Delete', 'Dispose', 'DoubleToComp', 'EndThread', 'EnumModules',
+ 'EnumResourceModules', 'Eof', 'Eoln', 'Erase', 'ExceptAddr',
+ 'ExceptObject', 'Exclude', 'Exit', 'Exp', 'FilePos', 'FileSize',
+ 'FillChar', 'Finalize', 'FindClassHInstance', 'FindHInstance',
+ 'FindResourceHInstance', 'Flush', 'Frac', 'FreeMem', 'Get8087CW',
+ 'GetDir', 'GetLastError', 'GetMem', 'GetMemoryManager',
+ 'GetModuleFileName', 'GetVariantManager', 'Halt', 'Hi', 'High',
+ 'IOResult', 'Inc', 'Include', 'Initialize', 'Insert', 'Int',
+ 'IsMemoryManagerSet', 'IsVariantManagerSet', 'Length', 'Ln', 'Lo', 'Low',
+ 'MkDir', 'Move', 'New', 'Odd', 'OleStrToStrVar', 'OleStrToString', 'Ord',
+ 'PUCS4Chars', 'ParamCount', 'ParamStr', 'Pi', 'Pos', 'Pred', 'Ptr',
+ 'Random', 'Randomize', 'Read', 'ReadLn', 'ReallocMem',
+ 'ReleaseExceptionObject', 'Rename', 'Reset', 'Rewrite', 'RmDir', 'Round',
+ 'RunError', 'Seek', 'SeekEof', 'SeekEoln', 'Set8087CW', 'SetLength',
+ 'SetLineBreakStyle', 'SetMemoryManager', 'SetString', 'SetTextBuf',
+ 'SetVariantManager', 'Sin', 'SizeOf', 'Slice', 'Sqr', 'Sqrt', 'Str',
+ 'StringOfChar', 'StringToOleStr', 'StringToWideChar', 'Succ', 'Swap',
+ 'Trunc', 'Truncate', 'TypeInfo', 'UCS4StringToWideString', 'UTF8Decode',
+ 'UTF8Encode', 'UnicodeToUtf8', 'UniqueString', 'UpCase', 'Utf8ToAnsi',
+ 'Utf8ToUnicode', 'Val', 'VarArrayRedim', 'VarClear',
+ 'WideCharLenToStrVar', 'WideCharLenToString', 'WideCharToStrVar',
+ 'WideCharToString', 'WideStringToUCS4String', 'Write', 'WriteLn',
+
+ 'Abort', 'AddExitProc', 'AddTerminateProc', 'AdjustLineBreaks', 'AllocMem',
+ 'AnsiCompareFileName', 'AnsiCompareStr', 'AnsiCompareText',
+ 'AnsiDequotedStr', 'AnsiExtractQuotedStr', 'AnsiLastChar',
+ 'AnsiLowerCase', 'AnsiLowerCaseFileName', 'AnsiPos', 'AnsiQuotedStr',
+ 'AnsiSameStr', 'AnsiSameText', 'AnsiStrComp', 'AnsiStrIComp',
+ 'AnsiStrLComp', 'AnsiStrLIComp', 'AnsiStrLastChar', 'AnsiStrLower',
+ 'AnsiStrPos', 'AnsiStrRScan', 'AnsiStrScan', 'AnsiStrUpper',
+ 'AnsiUpperCase', 'AnsiUpperCaseFileName', 'AppendStr', 'AssignStr',
+ 'Beep', 'BoolToStr', 'ByteToCharIndex', 'ByteToCharLen', 'ByteType',
+ 'CallTerminateProcs', 'ChangeFileExt', 'CharLength', 'CharToByteIndex',
+ 'CharToByteLen', 'CompareMem', 'CompareStr', 'CompareText', 'CreateDir',
+ 'CreateGUID', 'CurrToStr', 'CurrToStrF', 'CurrentYear', 'Date',
+ 'DateTimeToFileDate', 'DateTimeToStr', 'DateTimeToString',
+ 'DateTimeToSystemTime', 'DateTimeToTimeStamp', 'DateToStr', 'DayOfWeek',
+ 'DecodeDate', 'DecodeDateFully', 'DecodeTime', 'DeleteFile',
+ 'DirectoryExists', 'DiskFree', 'DiskSize', 'DisposeStr', 'EncodeDate',
+ 'EncodeTime', 'ExceptionErrorMessage', 'ExcludeTrailingBackslash',
+ 'ExcludeTrailingPathDelimiter', 'ExpandFileName', 'ExpandFileNameCase',
+ 'ExpandUNCFileName', 'ExtractFileDir', 'ExtractFileDrive',
+ 'ExtractFileExt', 'ExtractFileName', 'ExtractFilePath',
+ 'ExtractRelativePath', 'ExtractShortPathName', 'FileAge', 'FileClose',
+ 'FileCreate', 'FileDateToDateTime', 'FileExists', 'FileGetAttr',
+ 'FileGetDate', 'FileIsReadOnly', 'FileOpen', 'FileRead', 'FileSearch',
+ 'FileSeek', 'FileSetAttr', 'FileSetDate', 'FileSetReadOnly', 'FileWrite',
+ 'FinalizePackage', 'FindClose', 'FindCmdLineSwitch', 'FindFirst',
+ 'FindNext', 'FloatToCurr', 'FloatToDateTime', 'FloatToDecimal',
+ 'FloatToStr', 'FloatToStrF', 'FloatToText', 'FloatToTextFmt',
+ 'FmtLoadStr', 'FmtStr', 'ForceDirectories', 'Format', 'FormatBuf',
+ 'FormatCurr', 'FormatDateTime', 'FormatFloat', 'FreeAndNil',
+ 'GUIDToString', 'GetCurrentDir', 'GetEnvironmentVariable',
+ 'GetFileVersion', 'GetFormatSettings', 'GetLocaleFormatSettings',
+ 'GetModuleName', 'GetPackageDescription', 'GetPackageInfo', 'GetTime',
+ 'IncAMonth', 'IncMonth', 'IncludeTrailingBackslash',
+ 'IncludeTrailingPathDelimiter', 'InitializePackage', 'IntToHex',
+ 'IntToStr', 'InterlockedDecrement', 'InterlockedExchange',
+ 'InterlockedExchangeAdd', 'InterlockedIncrement', 'IsDelimiter',
+ 'IsEqualGUID', 'IsLeapYear', 'IsPathDelimiter', 'IsValidIdent',
+ 'Languages', 'LastDelimiter', 'LoadPackage', 'LoadStr', 'LowerCase',
+ 'MSecsToTimeStamp', 'NewStr', 'NextCharIndex', 'Now', 'OutOfMemoryError',
+ 'QuotedStr', 'RaiseLastOSError', 'RaiseLastWin32Error', 'RemoveDir',
+ 'RenameFile', 'ReplaceDate', 'ReplaceTime', 'SafeLoadLibrary',
+ 'SameFileName', 'SameText', 'SetCurrentDir', 'ShowException', 'Sleep',
+ 'StrAlloc', 'StrBufSize', 'StrByteType', 'StrCat', 'StrCharLength',
+ 'StrComp', 'StrCopy', 'StrDispose', 'StrECopy', 'StrEnd', 'StrFmt',
+ 'StrIComp', 'StrLCat', 'StrLComp', 'StrLCopy', 'StrLFmt', 'StrLIComp',
+ 'StrLen', 'StrLower', 'StrMove', 'StrNew', 'StrNextChar', 'StrPCopy',
+ 'StrPLCopy', 'StrPas', 'StrPos', 'StrRScan', 'StrScan', 'StrToBool',
+ 'StrToBoolDef', 'StrToCurr', 'StrToCurrDef', 'StrToDate', 'StrToDateDef',
+ 'StrToDateTime', 'StrToDateTimeDef', 'StrToFloat', 'StrToFloatDef',
+ 'StrToInt', 'StrToInt64', 'StrToInt64Def', 'StrToIntDef', 'StrToTime',
+ 'StrToTimeDef', 'StrUpper', 'StringReplace', 'StringToGUID', 'Supports',
+ 'SysErrorMessage', 'SystemTimeToDateTime', 'TextToFloat', 'Time',
+ 'TimeStampToDateTime', 'TimeStampToMSecs', 'TimeToStr', 'Trim',
+ 'TrimLeft', 'TrimRight', 'TryEncodeDate', 'TryEncodeTime',
+ 'TryFloatToCurr', 'TryFloatToDateTime', 'TryStrToBool', 'TryStrToCurr',
+ 'TryStrToDate', 'TryStrToDateTime', 'TryStrToFloat', 'TryStrToInt',
+ 'TryStrToInt64', 'TryStrToTime', 'UnloadPackage', 'UpperCase',
+ 'WideCompareStr', 'WideCompareText', 'WideFmtStr', 'WideFormat',
+ 'WideFormatBuf', 'WideLowerCase', 'WideSameStr', 'WideSameText',
+ 'WideUpperCase', 'Win32Check', 'WrapText',
+
+ 'ActivateClassGroup', 'AllocateHwnd', 'BinToHex', 'CheckSynchronize',
+ 'CollectionsEqual', 'CountGenerations', 'DeallocateHwnd', 'EqualRect',
+ 'ExtractStrings', 'FindClass', 'FindGlobalComponent', 'GetClass',
+ 'GroupDescendantsWith', 'HexToBin', 'IdentToInt',
+ 'InitInheritedComponent', 'IntToIdent', 'InvalidPoint',
+ 'IsUniqueGlobalComponentName', 'LineStart', 'ObjectBinaryToText',
+ 'ObjectResourceToText', 'ObjectTextToBinary', 'ObjectTextToResource',
+ 'PointsEqual', 'ReadComponentRes', 'ReadComponentResEx',
+ 'ReadComponentResFile', 'Rect', 'RegisterClass', 'RegisterClassAlias',
+ 'RegisterClasses', 'RegisterComponents', 'RegisterIntegerConsts',
+ 'RegisterNoIcon', 'RegisterNonActiveX', 'SmallPoint', 'StartClassGroup',
+ 'TestStreamFormat', 'UnregisterClass', 'UnregisterClasses',
+ 'UnregisterIntegerConsts', 'UnregisterModuleClasses',
+ 'WriteComponentResFile',
+
+ 'ArcCos', 'ArcCosh', 'ArcCot', 'ArcCotH', 'ArcCsc', 'ArcCscH', 'ArcSec',
+ 'ArcSecH', 'ArcSin', 'ArcSinh', 'ArcTan2', 'ArcTanh', 'Ceil',
+ 'CompareValue', 'Cosecant', 'Cosh', 'Cot', 'CotH', 'Cotan', 'Csc', 'CscH',
+ 'CycleToDeg', 'CycleToGrad', 'CycleToRad', 'DegToCycle', 'DegToGrad',
+ 'DegToRad', 'DivMod', 'DoubleDecliningBalance', 'EnsureRange', 'Floor',
+ 'Frexp', 'FutureValue', 'GetExceptionMask', 'GetPrecisionMode',
+ 'GetRoundMode', 'GradToCycle', 'GradToDeg', 'GradToRad', 'Hypot',
+ 'InRange', 'IntPower', 'InterestPayment', 'InterestRate',
+ 'InternalRateOfReturn', 'IsInfinite', 'IsNan', 'IsZero', 'Ldexp', 'LnXP1',
+ 'Log10', 'Log2', 'LogN', 'Max', 'MaxIntValue', 'MaxValue', 'Mean',
+ 'MeanAndStdDev', 'Min', 'MinIntValue', 'MinValue', 'MomentSkewKurtosis',
+ 'NetPresentValue', 'Norm', 'NumberOfPeriods', 'Payment', 'PeriodPayment',
+ 'Poly', 'PopnStdDev', 'PopnVariance', 'Power', 'PresentValue',
+ 'RadToCycle', 'RadToDeg', 'RadToGrad', 'RandG', 'RandomRange', 'RoundTo',
+ 'SLNDepreciation', 'SYDDepreciation', 'SameValue', 'Sec', 'SecH',
+ 'Secant', 'SetExceptionMask', 'SetPrecisionMode', 'SetRoundMode', 'Sign',
+ 'SimpleRoundTo', 'SinCos', 'Sinh', 'StdDev', 'Sum', 'SumInt',
+ 'SumOfSquares', 'SumsAndSquares', 'Tan', 'Tanh', 'TotalVariance',
+ 'Variance'
+ ),
+ 4 => array(
+ 'AnsiChar', 'AnsiString', 'Bool', 'Boolean', 'Byte', 'ByteBool', 'Cardinal', 'Char',
+ 'Comp', 'Currency', 'DWORD', 'Double', 'Extended', 'Int64', 'Integer', 'IUnknown',
+ 'LongBool', 'LongInt', 'LongWord', 'PAnsiChar', 'PAnsiString', 'PBool', 'PBoolean', 'PByte',
+ 'PByteArray', 'PCardinal', 'PChar', 'PComp', 'PCurrency', 'PDWORD', 'PDate', 'PDateTime',
+ 'PDouble', 'PExtended', 'PInt64', 'PInteger', 'PLongInt', 'PLongWord', 'Pointer', 'PPointer',
+ 'PShortInt', 'PShortString', 'PSingle', 'PSmallInt', 'PString', 'PHandle', 'PVariant', 'PWord',
+ 'PWordArray', 'PWordBool', 'PWideChar', 'PWideString', 'Real', 'Real48', 'ShortInt', 'ShortString',
+ 'Single', 'SmallInt', 'String', 'TClass', 'TDate', 'TDateTime', 'TextFile', 'THandle',
+ 'TObject', 'TTime', 'Variant', 'WideChar', 'WideString', 'Word', 'WordBool'
+ ),
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000000; font-weight: bold;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #9ac;',
+ 1 => 'color: #ff0000;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ 0 => '\$[0-9a-fA-F]+',
+ 1 => '\#\$?[0-9]{1,3}'
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/diff.php b/plugins/dokuwiki/inc/geshi/diff.php
new file mode 100644
index 0000000..96085e9
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/diff.php
@@ -0,0 +1,186 @@
+<?php
+/*************************************************************************************
+ * diff.php
+ * --------
+ * Author: Conny Brunnkvist (conny@fuchsia.se), W. Tasin (tasin@fhm.edu)
+ * Copyright: (c) 2004 Fuchsia Open Source Solutions (http://www.fuchsia.se/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/12/29
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * Diff-output language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2006/02/27
+ * - changing language file to use matching of start (^) and end ($) (wt)
+ *
+ * 2004/12/29 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2006/02/27)
+ * -------------------------
+ *
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+
+$language_data = array (
+ 'LANG_NAME' => 'Diff',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array(),
+ 'ESCAPE_CHAR' => ' ',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '\ No newline at end of file'
+ ),
+ 2 => array(
+ '***************' /* This only seems to works in some cases? */
+ ),
+ ),
+ 'SYMBOLS' => array(
+ ),
+ 'CASE_SENSITIVE' => array(
+ 1 => false,
+ 2 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #aaaaaa; font-style: italic;',
+ 2 => 'color: #dd6611;',
+ ),
+ 'COMMENTS' => array(
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => ''
+ ),
+ 'BRACKETS' => array(
+ 0 => ''
+ ),
+ 'STRINGS' => array(
+ 0 => ''
+ ),
+ 'NUMBERS' => array(
+ 0 => ''
+ ),
+ 'METHODS' => array(
+ 0 => ''
+ ),
+ 'SYMBOLS' => array(
+ 0 => ''
+ ),
+ 'SCRIPT' => array(
+ 0 => ''
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #440088;',
+ 1 => 'color: #991111;',
+ 2 => 'color: #00b000;',
+ 3 => 'color: #888822;',
+ 4 => 'color: #888822;',
+ 5 => 'color: #0011dd;',
+ 6 => 'color: #440088;',
+ 7 => 'color: #991111;',
+ 8 => 'color: #00b000;',
+ 9 => 'color: #888822;',
+ ),
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTER' => '',
+ 'REGEXPS' => array(
+ 0 => "[0-9,]+[acd][0-9,]+",
+ 1 => array(
+ GESHI_SEARCH => '^\\&lt;.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 2 => array(
+ GESHI_SEARCH => '^\\&gt;.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 3 => array(
+ GESHI_SEARCH => '^[\\-]{3}\\s.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 4 => array(
+ GESHI_SEARCH => '^(\\+){3}\\s.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 5 => array(
+ GESHI_SEARCH => '^\\!.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 6 => array(
+ GESHI_SEARCH => '^[\\@]{2}.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 7 => array(
+ GESHI_SEARCH => '^\\-.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 8 => array(
+ GESHI_SEARCH => '^\\+.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 9 => array(
+ GESHI_SEARCH => '^(\\*){3}\\s.*$',
+ GESHI_REPLACE => '\\0',
+ GESHI_MODIFIERS => 'm',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/div.php b/plugins/dokuwiki/inc/geshi/div.php
new file mode 100644
index 0000000..eb5a45e
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/div.php
@@ -0,0 +1,128 @@
+<?php
+/*************************************************************************************
+ * div.php
+ * ---------------------------------
+ * Author: Gabriel Lorenzo (ermakina@gmail.com)
+ * Copyright: (c) 2005 Gabriel Lorenzo (http://ermakina.gazpachito.net)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2005/06/19
+ * Last Modified: $Date: 2006/09/23 02:05:46 $
+ *
+ * DIV language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/06/22 (1.0.0)
+ * - First Release, includes "2nd gen" ELSEIF statement
+ *
+ * TODO (updated 2005/06/22)
+ * -------------------------
+ * - I'm pretty satisfied with this, so nothing for now... :P
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'DIV',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_UPPER,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'while','until','to','switch','step','return','repeat','loop','if','from','frame','for','end','elseif',
+ 'else','default','debug','continue','clone','case','break','begin'
+ ),
+ 2 => array(
+ 'xor','whoami','type','sizeof','pointer','or','offset','not','neg','mod','id','dup','and','_ne','_lt',
+ '_le','_gt','_ge','_eq'
+ ),
+ 3 => array(
+ 'setup_program','program','process','private','local','import','global','function','const',
+ 'compiler_options'
+ ),
+ 4 => array(
+ 'word','struct','string','int','byte'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(',')','[',']','=','+','-','*','/','!','%','^','&',':',';',',','<','>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0040b1;',
+ 2 => 'color: #000000;',
+ 3 => 'color: #000066; font-weight: bold;',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => ''
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #44aa44;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #202020;',
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #44aa44;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTER' => '',
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/dos.php b/plugins/dokuwiki/inc/geshi/dos.php
new file mode 100644
index 0000000..e939baf
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/dos.php
@@ -0,0 +1,185 @@
+<?php
+/*************************************************************************************
+ * dos.php
+ * -------
+ * Author: Alessandro Staltari (staltari@geocities.com)
+ * Copyright: (c) 2005 Alessandro Staltari (http://www.geocities.com/SiliconValley/Vista/8155/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2005/07/05
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * DOS language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/07/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/07/05)
+ * -------------------------
+ *
+ * - Find a way to higlight %*
+ * - Highlight pipes and redirection (do we really need this?)
+ * - Add missing keywords.
+ * - Find a good hyperlink for keywords.
+ * - Improve styles.
+ *
+ * KNOWN ISSUES (updated 2005/07/07)
+ * ---------------------------------
+ *
+ * - Doesn't even try to handle spaces in variables name or labels (I can't
+ * find a reliable way to establish if a sting is a name or not, in some
+ * cases it depends on the contex or enviroment status).
+ * - Doesn't handle %%[letter] pseudo variable used inside FOR constructs
+ * (it should be done only into its scope: how to handle variable it?).
+ * - Doesn't handle %~[something] pseudo arguments.
+ * - If the same keyword is placed at the end of the line and the
+ * beginning of the next, the second occourrence is not highlighted
+ * (this should be a GeSHi bug, not related to the language definition).
+ * - I can't avoid to have keyword highlighted even when they are not used
+ * as keywords but, for example, as arguments to the echo command.
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'DOS',
+ 'COMMENT_SINGLE' => array(1 =>'REM', 2 => '@REM'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array(),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ /* Flow control keywords */
+ 1 => array(
+ 'if', 'else', 'goto',
+ 'for', 'in', 'do',
+ 'call', 'exit'
+ ),
+ /* IF statement keywords */
+ 2 => array(
+ 'not', 'exist', 'errorlevel',
+ 'defined',
+ 'equ', 'neq', 'lss', 'leq', 'gtr', 'geq'
+ ),
+ /* Internal commands */
+ 3 => array(
+ 'shift',
+ 'cd', 'dir', 'echo',
+ 'setlocal', 'endlocal', 'set',
+ 'pause'
+ ),
+ /* Special files */
+
+ 4 => array(
+ 'prn', 'nul', 'lpt3', 'lpt2', 'lpt1', 'con',
+ 'com4', 'com3', 'com2', 'com1', 'aux'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #00b100; font-weight: bold;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #b1b100; font-weight: bold;',
+ 4 => 'color: #0000ff; font-weight: bold;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+/* 0 => 'color: #cc66cc;' */
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #33cc33;',
+ 1 => 'color: #33cc33;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #b100b1; font-weight: bold;',
+ 1 => 'color: #448844;',
+ 2 => 'color: #448888;'
+ )
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'URLS' => array(
+ 1 => 'http://www.ss64.com/nt/{FNAME}.html',
+ 2 => 'http://www.ss64.com/nt/{FNAME}.html',
+ 3 => 'http://www.ss64.com/nt/{FNAME}.html',
+ 4 => 'http://www.ss64.com/nt/{FNAME}.html'
+ ),
+ 'REGEXPS' => array(
+ /* Label */
+ 0 => array(
+/* GESHI_SEARCH => '((?si:[@\s]+GOTO\s+|\s+:)[\s]*)((?<!\n)[^\s\n]*)',*/
+ GESHI_SEARCH => '((?si:[@\s]+GOTO\s+|\s+:)[\s]*)((?<!\n)[^\n]*)',
+ GESHI_REPLACE => '\\2',
+ GESHI_MODIFIERS => 'si',
+ GESHI_BEFORE => '\\1',
+ GESHI_AFTER => ''
+ ),
+ /* Variable assignement */
+ 1 => array(
+/* GESHI_SEARCH => '(SET[\s]+(?si:/A[\s]+|/P[\s]+|))([^=\s\n]+)([\s]*=)',*/
+ GESHI_SEARCH => '(SET[\s]+(?si:/A[\s]+|/P[\s]+|))([^=\n]+)([\s]*=)',
+ GESHI_REPLACE => '\\2',
+ GESHI_MODIFIERS => 'si',
+ GESHI_BEFORE => '\\1',
+ GESHI_AFTER => '\\3'
+ ),
+ /* Arguments or variable evaluation */
+ 2 => array(
+/* GESHI_SEARCH => '(%)([\d*]|[^%\s]*(?=%))((?<!%\d)%|)',*/
+ GESHI_SEARCH => '(%)([\d*]|[^%]*(?=%))((?<!%\d)%|)',
+ GESHI_REPLACE => '\\2',
+ GESHI_MODIFIERS => 'si',
+ GESHI_BEFORE => '\\1',
+ GESHI_AFTER => '\\3'
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/eiffel.php b/plugins/dokuwiki/inc/geshi/eiffel.php
new file mode 100644
index 0000000..635e0ab
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/eiffel.php
@@ -0,0 +1,397 @@
+<?php
+/*************************************************************************************
+ * eiffel.php
+ * ----------
+ * Author: Zoran Simic (zsimic@axarosenberg.com)
+ * Copyright: (c) 2005 Zoran Simic
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2005/06/30
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Eiffel language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/06/30 (1.0.7)
+ * - Initial release
+ *
+ * TODO (updated 2005/06/30)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+ $language_data = array (
+ 'LANG_NAME' => 'Eiffel',
+ 'COMMENT_SINGLE' => array(1 => '--'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '%',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'separate',
+ 'invariant',
+ 'inherit',
+ 'indexing',
+ 'feature',
+ 'expanded',
+ 'deferred',
+ 'class'
+ ),
+ 2 => array(
+ 'xor',
+ 'when',
+ 'variant',
+ 'until',
+ 'unique',
+ 'undefine',
+ 'then',
+ 'strip',
+ 'select',
+ 'retry',
+ 'rescue',
+ 'require',
+ 'rename',
+ 'reference',
+ 'redefine',
+ 'prefix',
+ 'or',
+ 'once',
+ 'old',
+ 'obsolete',
+ 'not',
+ 'loop',
+ 'local',
+ 'like',
+ 'is',
+ 'inspect',
+ 'infix',
+ 'include',
+ 'implies',
+ 'if',
+ 'frozen',
+ 'from',
+ 'external',
+ 'export',
+ 'ensure',
+ 'end',
+ 'elseif',
+ 'else',
+ 'do',
+ 'creation',
+ 'create',
+ 'check',
+ 'as',
+ 'and',
+ 'alias',
+ 'agent'
+ ),
+ 3 => array(
+ 'Void',
+ 'True',
+ 'Result',
+ 'Precursor',
+ 'False',
+ 'Current'
+ ),
+ 4 => array(
+ 'UNIX_SIGNALS',
+ 'UNIX_FILE_INFO',
+ 'UNBOUNDED',
+ 'TWO_WAY_TREE_CURSOR',
+ 'TWO_WAY_TREE',
+ 'TWO_WAY_SORTED_SET',
+ 'TWO_WAY_LIST',
+ 'TWO_WAY_CURSOR_TREE',
+ 'TWO_WAY_CIRCULAR',
+ 'TWO_WAY_CHAIN_ITERATOR',
+ 'TUPLE',
+ 'TREE',
+ 'TRAVERSABLE',
+ 'TO_SPECIAL',
+ 'THREAD_CONTROL',
+ 'THREAD_ATTRIBUTES',
+ 'THREAD',
+ 'TABLE',
+ 'SUBSET',
+ 'STRING_HANDLER',
+ 'STRING',
+ 'STREAM',
+ 'STORABLE',
+ 'STD_FILES',
+ 'STACK',
+ 'SPECIAL',
+ 'SORTED_TWO_WAY_LIST',
+ 'SORTED_STRUCT',
+ 'SORTED_LIST',
+ 'SINGLE_MATH',
+ 'SET',
+ 'SEQUENCE',
+ 'SEQ_STRING',
+ 'SEMAPHORE',
+ 'ROUTINE',
+ 'RESIZABLE',
+ 'RECURSIVE_TREE_CURSOR',
+ 'RECURSIVE_CURSOR_TREE',
+ 'REAL_REF',
+ 'REAL',
+ 'RAW_FILE',
+ 'RANDOM',
+ 'QUEUE',
+ 'PROXY',
+ 'PROFILING_SETTING',
+ 'PROCEDURE',
+ 'PRIORITY_QUEUE',
+ 'PRIMES',
+ 'PRECOMP',
+ 'POINTER_REF',
+ 'POINTER',
+ 'PLATFORM',
+ 'PLAIN_TEXT_FILE',
+ 'PATH_NAME',
+ 'PART_SORTED_TWO_WAY_LIST',
+ 'PART_SORTED_SET',
+ 'PART_SORTED_LIST',
+ 'PART_COMPARABLE',
+ 'OPERATING_ENVIRONMENT',
+ 'ONCE_CONTROL',
+ 'OBJECT_OWNER',
+ 'OBJECT_CONTROL',
+ 'NUMERIC',
+ 'NONE',
+ 'MUTEX',
+ 'MULTI_ARRAY_LIST',
+ 'MULTAR_LIST_CURSOR',
+ 'MEMORY',
+ 'MEM_INFO',
+ 'MEM_CONST',
+ 'MATH_CONST',
+ 'LIST',
+ 'LINKED_TREE_CURSOR',
+ 'LINKED_TREE',
+ 'LINKED_STACK',
+ 'LINKED_SET',
+ 'LINKED_QUEUE',
+ 'LINKED_PRIORITY_QUEUE',
+ 'LINKED_LIST_CURSOR',
+ 'LINKED_LIST',
+ 'LINKED_CURSOR_TREE',
+ 'LINKED_CIRCULAR',
+ 'LINKABLE',
+ 'LINEAR_ITERATOR',
+ 'LINEAR',
+ 'ITERATOR',
+ 'IO_MEDIUM',
+ 'INTERNAL',
+ 'INTEGER_REF',
+ 'INTEGER_INTERVAL',
+ 'INTEGER',
+ 'INFINITE',
+ 'INDEXABLE',
+ 'IDENTIFIED_CONTROLLER',
+ 'IDENTIFIED',
+ 'HIERARCHICAL',
+ 'HEAP_PRIORITY_QUEUE',
+ 'HASHABLE',
+ 'HASH_TABLE_CURSOR',
+ 'HASH_TABLE',
+ 'GENERAL',
+ 'GC_INFO',
+ 'FUNCTION',
+ 'FORMAT_INTEGER',
+ 'FORMAT_DOUBLE',
+ 'FIXED_TREE',
+ 'FIXED_LIST',
+ 'FIXED',
+ 'FINITE',
+ 'FILE_NAME',
+ 'FILE',
+ 'FIBONACCI',
+ 'EXECUTION_ENVIRONMENT',
+ 'EXCEPTIONS',
+ 'EXCEP_CONST',
+ 'DYNAMIC_TREE',
+ 'DYNAMIC_LIST',
+ 'DYNAMIC_CIRCULAR',
+ 'DYNAMIC_CHAIN',
+ 'DOUBLE_REF',
+ 'DOUBLE_MATH',
+ 'DOUBLE',
+ 'DISPENSER',
+ 'DIRECTORY_NAME',
+ 'DIRECTORY',
+ 'DECLARATOR',
+ 'DEBUG_OUTPUT',
+ 'CURSOR_TREE_ITERATOR',
+ 'CURSOR_TREE',
+ 'CURSOR_STRUCTURE',
+ 'CURSOR',
+ 'COUNTABLE_SEQUENCE',
+ 'COUNTABLE',
+ 'CONTAINER',
+ 'CONSOLE',
+ 'CONDITION_VARIABLE',
+ 'COMPARABLE_STRUCT',
+ 'COMPARABLE_SET',
+ 'COMPARABLE',
+ 'COMPACT_TREE_CURSOR',
+ 'COMPACT_CURSOR_TREE',
+ 'COLLECTION',
+ 'CIRCULAR_CURSOR',
+ 'CIRCULAR',
+ 'CHARACTER_REF',
+ 'CHARACTER',
+ 'CHAIN',
+ 'CELL',
+ 'BOX',
+ 'BOUNDED_STACK',
+ 'BOUNDED_QUEUE',
+ 'BOUNDED',
+ 'BOOLEAN_REF',
+ 'BOOLEAN',
+ 'BOOL_STRING',
+ 'BIT_REF',
+ 'BINARY_TREE',
+ 'BINARY_SEARCH_TREE_SET',
+ 'BINARY_SEARCH_TREE',
+ 'BILINEAR',
+ 'BI_LINKABLE',
+ 'BASIC_ROUTINES',
+ 'BAG',
+ 'ASCII',
+ 'ARRAYED_TREE',
+ 'ARRAYED_STACK',
+ 'ARRAYED_QUEUE',
+ 'ARRAYED_LIST_CURSOR',
+ 'ARRAYED_LIST',
+ 'ARRAYED_CIRCULAR',
+ 'ARRAY2',
+ 'ARRAY',
+ 'ARGUMENTS',
+ 'ANY',
+ 'ACTIVE'
+ ),
+ 5 => array(
+ 'yes',
+ 'visible',
+ 'trace',
+ 'system',
+ 'root',
+ 'profile',
+ 'override_cluster',
+ 'object',
+ 'no',
+ 'multithreaded',
+ 'msil_generation_type',
+ 'line_generation',
+ 'library',
+ 'inlining_size',
+ 'inlining',
+ 'include_path',
+ 'il_verifiable',
+ 'exclude',
+ 'exception_trace',
+ 'dynamic_runtime',
+ 'dotnet_naming_convention',
+ 'disabled_debug',
+ 'default',
+ 'debug',
+ 'dead_code_removal',
+ 'console_application',
+ 'cluster',
+ 'cls_compliant',
+ 'check_vape',
+ 'assertion',
+ 'array_optimization',
+ 'all',
+ 'address_expression'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '+', '-', '*', '?', '=', '/', '%', '&', '>', '<', '^', '!', '|', ':',
+ '(', ')', '{', '}', '[', ']', '#'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => true,
+ 5 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0600FF; background-color: #FFF0E0; font-weight: bold;',
+ 2 => 'color: #0600FF; font-weight: bold;',
+ 3 => 'color: #800080;',
+ 4 => 'color: #800000',
+ 5 => 'color: #603000;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #008000; font-style: italic;',
+ 'MULTI' => ''
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #005070; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #FF0000;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #0080A0;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #FF0000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #000060;',
+ 2 => 'color: #000050;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #600000;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => 'http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+{FNAME}&btnI=I%27m+Feeling+Lucky'
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
+
diff --git a/plugins/dokuwiki/inc/geshi/freebasic.php b/plugins/dokuwiki/inc/geshi/freebasic.php
new file mode 100644
index 0000000..cfd2f8a
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/freebasic.php
@@ -0,0 +1,137 @@
+<?php
+/*************************************************************************************
+ * freebasic.php
+ * -------------
+ * Author: Roberto Rossi
+ * Copyright: (c) 2005 Roberto Rossi (http://rsoftware.altervista.org)
+ * Release Version: 1.0.7.15
+ * Date Started: 2005/08/19
+ *
+ * FreeBasic (http://www.freebasic.net/) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/19 (1.0.0)
+ * - First Release
+ *
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+ $language_data = array (
+ 'LANG_NAME' => 'FreeBasic',
+ 'COMMENT_SINGLE' => array(1 => "'", 2 => '#'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ "append", "as", "asc", "asin", "asm", "atan2", "atn", "beep", "bin", "binary", "bit",
+ "bitreset", "bitset", "bload", "bsave", "byref", "byte", "byval", "call",
+ "callocate", "case", "cbyte", "cdbl", "cdecl", "chain", "chdir", "chr", "cint",
+ "circle", "clear", "clng", "clngint", "close", "cls", "color", "command",
+ "common", "cons", "const", "continue", "cos", "cshort", "csign", "csng",
+ "csrlin", "cubyte", "cuint", "culngint", "cunsg", "curdir", "cushort", "custom",
+ "cvd", "cvi", "cvl", "cvlongint", "cvs", "cvshort", "data", "date",
+ "deallocate", "declare", "defbyte", "defdbl", "defined", "defint", "deflng",
+ "deflngint", "defshort", "defsng", "defstr", "defubyte", "defuint",
+ "defulngint", "defushort", "dim", "dir", "do", "double", "draw", "dylibload",
+ "dylibsymbol", "else", "elseif", "end", "enum", "environ", 'environ$', "eof",
+ "eqv", "erase", "err", "error", "exec", "exepath", "exit", "exp", "export",
+ "extern", "field", "fix", "flip", "for", "fre", "freefile", "function", "get",
+ "getjoystick", "getkey", "getmouse", "gosub", "goto", "hex", "hibyte", "hiword",
+ "if", "iif", "imagecreate", "imagedestroy", "imp", "inkey", "inp", "input",
+ "instr", "int", "integer", "is", "kill", "lbound", "lcase", "left", "len",
+ "let", "lib", "line", "lobyte", "loc", "local", "locate", "lock", "lof", "log",
+ "long", "longint", "loop", "loword", "lset", "ltrim", "mid", "mkd", "mkdir",
+ "mki", "mkl", "mklongint", "mks", "mkshort", "mod", "multikey", "mutexcreate",
+ "mutexdestroy", "mutexlock", "mutexunlock", "name", "next", "not", "oct", "on",
+ "once", "open", "option", "or", "out", "output", "overload", "paint", "palette",
+ "pascal", "pcopy", "peek", "peeki", "peeks", "pipe", "pmap", "point", "pointer",
+ "poke", "pokei", "pokes", "pos", "preserve", "preset", "print", "private",
+ "procptr", "pset", "ptr", "public", "put", "random", "randomize", "read",
+ "reallocate", "redim", "rem", "reset", "restore", "resume", "resume", "next",
+ "return", "rgb", "rgba", "right", "rmdir", "rnd", "rset", "rtrim", "run",
+ "sadd", "screen", "screencopy", "screeninfo", "screenlock", "screenptr",
+ "screenres", "screenset", "screensync", "screenunlock", "seek", "statement",
+ "seek", "function", "selectcase", "setdate", "setenviron", "setmouse",
+ "settime", "sgn", "shared", "shell", "shl", "short", "shr", "sin", "single",
+ "sizeof", "sleep", "space", "spc", "sqr", "static", "stdcall", "step", "stop",
+ "str", "string", "string", "strptr", "sub", "swap", "system", "tab", "tan",
+ "then", "threadcreate", "threadwait", "time", "time", "timer", "to", "trans",
+ "trim", "type", "ubound", "ubyte", "ucase", "uinteger", "ulongint", "union",
+ "unlock", "unsigned", "until", "ushort", "using", "va_arg", "va_first",
+ "va_next", "val", "val64", "valint", "varptr", "view", "viewprint", "wait",
+ "wend", "while", "width", "window", "windowtitle", "with", "write", "xor",
+ "zstring", "explicit", "escape", "true", "false"
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080;',
+ 2 => 'color: #339933;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/gml.php b/plugins/dokuwiki/inc/geshi/gml.php
new file mode 100644
index 0000000..6482cda
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/gml.php
@@ -0,0 +1,504 @@
+<?php
+/*************************************************************************************
+ * gml.php
+ * --------
+ * Author: José Jorge Enríquez (jenriquez@users.sourceforge.net)
+ * Copyright: (c) 2005 José Jorge Enríquez Rodríguez (http://www.zonamakers.com)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.5 $
+ * Date Started: 2005/06/21
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * GML language file for GeSHi.
+ *
+ * GML (Game Maker Language) is a script language that is built-in into Game Maker,
+ * a game creation program, more info about Game Maker can be found at
+ * http://www.gamemaker.nl/
+ * All GML keywords were extracted from the Game Maker HTML Help file using a PHP
+ * script (one section at a time). I love PHP for saving me that bunch of work :P!.
+ * I think all GML functions have been indexed here, but I'm not sure about it, so
+ * please let me know of any issue you may find.
+ *
+ * CHANGES
+ * -------
+ * 2005/11/11
+ * - Changed 'CASE_KEYWORDS' fom 'GESHI_CAPS_LOWER' to 'GESHI_CAPS_NO_CHANGE',
+ * so that MCI_command appears correctly (the only GML function using capitals).
+ * - Changed 'CASE_SENSITIVE' options, 'GESHI_COMMENTS' from true to false and all
+ * of the others from false to true.
+ * - Deleted repeated entries.
+ * - div and mod are language keywords, moved (from symbols) to the appropiate section (1).
+ * - Moved self, other, all, noone and global identifiers to language keywords section 1.
+ * - Edited this file lines to a maximum width of 100 characters (as stated in
+ * the GeSHi docs). Well, not strictly to 100 but around it.
+ * - Corrected some minor issues (the vk_f1...vk_f12 keys and similar).
+ * - Deleted the KEYWORDS=>5 and KEYWORDS=>6 sections (actually, they were empty).
+ * I was planning of using those for the GML functions available only in the
+ * registered version of the program, but not anymore.
+ *
+ * 2005/06/26 (1.0.3)
+ * - First Release.
+ *
+ * TODO (updated 2005/11/11)
+ * -------------------------
+ * - Test it for a while and make the appropiate corrections.
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'GML',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'"),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ // language keywords
+ 1 => array(
+ 'break', 'continue', 'do', 'until', 'if', 'else',
+ 'exit', 'for', 'for', 'repeat', 'return', 'switch',
+ 'case', 'default', 'var', 'while', 'with', 'div', 'mod',
+ // GML Language overview
+ 'self', 'other', 'all', 'noone', 'global',
+ ),
+ // modifiers and built-in variables
+ 2 => array(
+ // Game play
+ 'x','y','xprevious','yprevious','xstart','ystart','hspeed','vspeed','direction','speed',
+ 'friction','gravity','gravity_direction',
+ 'path_index','path_position','path_positionprevious','path_speed','path_orientation',
+ 'path_scale','path_endaction',
+ 'object_index','id','mask_index','solid','persistent','instance_count','instance_id',
+ 'room_speed','fps','current_time','current_year','current_month','current_day','current_weekday',
+ 'current_hour','current_minute','current_second','alarm','timeline_index','timeline_position',
+ 'timeline_speed',
+ 'room','room_first','room_last','room_width','room_height','room_caption','room_persistent',
+ 'score','lives','health','show_score','show_lives','show_health','caption_score','caption_lives',
+ 'caption_health',
+ 'event_type','event_number','event_object','event_action',
+ 'error_occurred','error_last',
+ // User interaction
+ 'keyboard_lastkey','keyboard_key','keyboard_lastchar','keyboard_string',
+ 'mouse_x','mouse_y','mouse_button','mouse_lastbutton',
+ // Game Graphics
+ 'visible','sprite_index','sprite_width','sprite_height','sprite_xoffset','sprite_yoffset',
+ 'image_number','image_index','image_speed','depth','image_xscale','image_yscale','image_angle',
+ 'image_alpha','image_blend','bbox_left','bbox_right','bbox_top','bbox_bottom',
+ 'background_color','background_showcolor','background_visible','background_foreground',
+ 'background_index','background_x','background_y','background_width','background_height',
+ 'background_htiled','background_vtiled','background_xscale','background_yscale',
+ 'background_hspeed','background_vspeed','background_blend','background_alpha',
+ 'background','left, top, width, height','x,y','depth','visible','xscale, yscale','blend','alpha',
+ 'view_enabled','view_current','view_visible','view_yview','view_wview','view_hview','view_xport',
+ 'view_yport','view_wport','view_hport','view_angle','view_hborder','view_vborder','view_hspeed',
+ 'view_vspeed','view_object',
+ 'transition_kind',
+ // Files, registry and executing programs
+ 'game_id','working_directory','temp_directory',
+ 'secure_mode',
+ // Creating particles
+ 'xmin', 'xmax', 'ymin', 'ymax','shape','distribution','particle type','number',
+ 'x', 'y', 'force','dist','kind','additive', 'friction', 'parttype1', 'parttype2'
+ ),
+ // functions
+ 3 => array(
+ // Computing things
+ 'random','choose','abs','sign','round','floor','ceil','frac','sqrt','sqr','power','exp','ln',
+ 'log2','log10','logn','sin','cos','tan','arcsin','arccos','arctan','arctan2','degtorad',
+ 'radtodeg','min','max','mean','median','point_distance','point_direction','lengthdir_x',
+ 'lengthdir_y','is_real','is_string',
+ 'chr','ord','real','string','string_format','string_length','string_pos','string_copy',
+ 'string_char_at','string_delete','string_insert','string_replace','string_replace_all',
+ 'string_count','string_lower','string_upper','string_repeat','string_letters','string_digits',
+ 'string_lettersdigits','clipboard_has_text','clipboard_get_text','clipboard_set_text',
+ 'date_current_datetime','date_current_date','date_current_time','date_create_datetime',
+ 'date_create_date','date_create_time','date_valid_datetime','date_valid_date','date_valid_time',
+ 'date_inc_year','date_inc_month','date_inc_week','date_inc_day','date_inc_hour',
+ 'date_inc_minute','date_inc_second','date_get_year','date_get_month','date_get_week',
+ 'date_get_day','date_get_hour', 'date_get_minute','date_get_second','date_get_weekday',
+ 'date_get_day_of_year','date_get_hour_of_year','date_get_minute_of_year',
+ 'date_get_second_of_year','date_year_span','date_month_span','date_week_span','date_day_span',
+ 'date_hour_span','date_minute_span','date_second_span','date_compare_datetime',
+ 'date_compare_date','date_compare_time','date_date_of','date_time_of','date_datetime_string',
+ 'date_date_string','date_time_string','date_days_in_month','date_days_in_year','date_leap_year',
+ 'date_is_today',
+ // Game play
+ 'motion_set','motion_add','place_free','place_empty','place_meeting','place_snapped',
+ 'move_random','move_snap','move_wrap','move_towards_point','move_bounce_solid','move_bounce_all',
+ 'move_contact_solid','move_contact_all','move_outside_solid','move_outside_all',
+ 'distance_to_point','distance_to_object','position_empty','position_meeting',
+ 'path_start','path_end',
+ 'mp_linear_step','mp_linear_step_object','mp_potential_step','mp_potential_step_object',
+ 'mp_potential_settings','mp_linear_path','mp_linear_path_object', 'mp_potential_path',
+ 'mp_potential_path_object','mp_grid_create','mp_grid_destroy','mp_grid_clear_all',
+ 'mp_grid_clear_cell','mp_grid_clear_rectangle','mp_grid_add_cell','mp_grid_add_rectangle',
+ 'mp_grid_add_instances','mp_grid_path','mp_grid_draw',
+ 'collision_point','collision_rectangle','collision_circle','collision_ellipse','collision_line',
+ 'instance_find','instance_exists','instance_number','instance_position','instance_nearest',
+ 'instance_furthest','instance_place','instance_create','instance_copy','instance_destroy',
+ 'instance_change','position_destroy','position_change',
+ 'instance_deactivate_all','instance_deactivate_object','instance_deactivate_region',
+ 'instance_activate_all','instance_activate_object','instance_activate_region',
+ 'sleep',
+ 'room_goto','room_goto_previous','room_goto_next','room_restart','room_previous','room_next',
+ 'game_end','game_restart','game_save','game_load',
+ 'event_perform', 'event_perform_object','event_user','event_inherited',
+ 'show_debug_message','variable_global_exists','variable_local_exists','variable_global_get',
+ 'variable_global_array_get','variable_global_array2_get','variable_local_get',
+ 'variable_local_array_get','variable_local_array2_get','variable_global_set',
+ 'variable_global_array_set','variable_global_array2_set','variable_local_set',
+ 'variable_local_array_set','variable_local_array2_set','set_program_priority',
+ // User interaction
+ 'keyboard_set_map','keyboard_get_map','keyboard_unset_map','keyboard_check',
+ 'keyboard_check_pressed','keyboard_check_released','keyboard_check_direct',
+ 'keyboard_get_numlock','keyboard_set_numlock','keyboard_key_press','keyboard_key_release',
+ 'keyboard_clear','io_clear','io_handle','keyboard_wait',
+ 'mouse_check_button','mouse_check_button_pressed','mouse_check_button_released','mouse_clear',
+ 'io_clear','io_handle','mouse_wait',
+ 'joystick_exists','joystick_name','joystick_axes','joystick_buttons','joystick_has_pov',
+ 'joystick_direction','joystick_check_button','joystick_xpos','joystick_ypos','joystick_zpos',
+ 'joystick_rpos','joystick_upos','joystick_vpos','joystick_pov',
+ // Game Graphics
+ 'draw_sprite','draw_sprite_stretched','draw_sprite_tiled','draw_sprite_part','draw_background',
+ 'draw_background_stretched','draw_background_tiled','draw_background_part','draw_sprite_ext',
+ 'draw_sprite_stretched_ext','draw_sprite_tiled_ext','draw_sprite_part_ext','draw_sprite_general',
+ 'draw_background_ext','draw_background_stretched_ext','draw_background_tiled_ext',
+ 'draw_background_part_ext','draw_background_general',
+ 'draw_clear','draw_clear_alpha','draw_point','draw_line','draw_rectangle','draw_roundrect',
+ 'draw_triangle','draw_circle','draw_ellipse','draw_arrow','draw_button','draw_path',
+ 'draw_healthbar','draw_set_color','draw_set_alpha','draw_get_color','draw_get_alpha',
+ 'make_color_rgb','make_color_hsv','color_get_red','color_get_green','color_get_blue',
+ 'color_get_hue','color_get_saturation','color_get_value','merge_color','draw_getpixel',
+ 'screen_save','screen_save_part',
+ 'draw_set_font','draw_set_halign','draw_set_valign','draw_text','draw_text_ext','string_width',
+ 'string_height','string_width_ext','string_height_ext','draw_text_transformed',
+ 'draw_text_ext_transformed','draw_text_color','draw_text_ext_color',
+ 'draw_text_transformed_color','draw_text_ext_transformed_color',
+ 'draw_point_color','draw_line_color','draw_rectangle_color','draw_roundrect_color',
+ 'draw_triangle_color','draw_circle_color','draw_ellipse_color','draw_primitive_begin',
+ 'draw_vertex','draw_vertex_color','draw_primitive_end','sprite_get_texture',
+ 'background_get_texture','texture_preload','texture_set_priority',
+ 'texture_get_width','texture_get_height','draw_primitive_begin_texture','draw_vertex_texture',
+ 'draw_vertex_texture_color','draw_primitive_end','texture_set_interpolation',
+ 'texture_set_blending','texture_set_repeat','draw_set_blend_mode','draw_set_blend_mode_ext',
+ 'surface_create','surface_free','surface_exists','surface_get_width','surface_get_height',
+ 'surface_get_texture','surface_set_target','surface_reset_target','surface_getpixel',
+ 'surface_save','surface_save_part','draw_surface','draw_surface_stretched','draw_surface_tiled',
+ 'draw_surface_part','draw_surface_ext','draw_surface_stretched_ext','draw_surface_tiled_ext',
+ 'draw_surface_part_ext','draw_surface_general','surface_copy','surface_copy_part',
+ 'tile_add','tile_delete','tile_exists','tile_get_x','tile_get_y','tile_get_left','tile_get_top',
+ 'tile_get_width','tile_get_height','tile_get_depth','tile_get_visible','tile_get_xscale',
+ 'tile_get_yscale','tile_get_background','tile_get_blend','tile_get_alpha','tile_set_position',
+ 'tile_set_region','tile_set_background','tile_set_visible','tile_set_depth','tile_set_scale',
+ 'tile_set_blend','tile_set_alpha','tile_layer_hide','tile_layer_show','tile_layer_delete',
+ 'tile_layer_shift','tile_layer_find','tile_layer_delete_at','tile_layer_depth',
+ 'display_get_width','display_get_height','display_get_colordepth','display_get_frequency',
+ 'display_set_size','display_set_colordepth','display_set_frequency','display_set_all',
+ 'display_test_all','display_reset','display_mouse_get_x','display_mouse_get_y','display_mouse_set',
+ 'window_set_visible','window_get_visible','window_set_fullscreen','window_get_fullscreen',
+ 'window_set_showborder','window_get_showborder','window_set_showicons','window_get_showicons',
+ 'window_set_stayontop','window_get_stayontop','window_set_sizeable','window_get_sizeable',
+ 'window_set_caption','window_get_caption','window_set_cursor', 'window_get_cursor',
+ 'window_set_color','window_get_color','window_set_region_scale','window_get_region_scale',
+ 'window_set_position','window_set_size','window_set_rectangle','window_center','window_default',
+ 'window_get_x','window_get_y','window_get_width','window_get_height','window_mouse_get_x',
+ 'window_mouse_get_y','window_mouse_set',
+ 'window_set_region_size','window_get_region_width','window_get_region_height',
+ 'window_view_mouse_get_x','window_view_mouse_get_y','window_view_mouse_set',
+ 'window_views_mouse_get_x','window_views_mouse_get_y','window_views_mouse_set',
+ 'screen_redraw','screen_refresh','set_automatic_draw','set_synchronization','screen_wait_vsync',
+ // Sound and music)
+ 'sound_play','sound_loop','sound_stop','sound_stop_all','sound_isplaying','sound_volume',
+ 'sound_global_volume','sound_fade','sound_pan','sound_background_tempo','sound_set_search_directory',
+ 'sound_effect_set','sound_effect_chorus','sound_effect_echo', 'sound_effect_flanger',
+ 'sound_effect_gargle','sound_effect_reverb','sound_effect_compressor','sound_effect_equalizer',
+ 'sound_3d_set_sound_position','sound_3d_set_sound_velocity','sound_3d_set_sound_distance',
+ 'sound_3d_set_sound_cone',
+ 'cd_init','cd_present','cd_number','cd_playing','cd_paused','cd_track','cd_length',
+ 'cd_track_length','cd_position','cd_track_position','cd_play','cd_stop','cd_pause','cd_resume',
+ 'cd_set_position','cd_set_track_position','cd_open_door','cd_close_door','MCI_command',
+ // Splash screens, highscores, and other pop-ups
+ 'show_text','show_image','show_video','show_info','load_info',
+ 'show_message','show_message_ext','show_question','get_integer','get_string',
+ 'message_background','message_alpha','message_button','message_text_font','message_button_font',
+ 'message_input_font','message_mouse_color','message_input_color','message_caption',
+ 'message_position','message_size','show_menu','show_menu_pos','get_color','get_open_filename',
+ 'get_save_filename','get_directory','get_directory_alt','show_error',
+ 'highscore_show','highscore_set_background','highscore_set_border','highscore_set_font',
+ 'highscore_set_colors','highscore_set_strings','highscore_show_ext','highscore_clear',
+ 'highscore_add','highscore_add_current','highscore_value','highscore_name','draw_highscore',
+ // Resources
+ 'sprite_exists','sprite_get_name','sprite_get_number','sprite_get_width','sprite_get_height',
+ 'sprite_get_transparent','sprite_get_smooth','sprite_get_preload','sprite_get_xoffset',
+ 'sprite_get_yoffset','sprite_get_bbox_left','sprite_get_bbox_right','sprite_get_bbox_top',
+ 'sprite_get_bbox_bottom','sprite_get_bbox_mode','sprite_get_precise',
+ 'sound_exists','sound_get_name','sound_get_kind','sound_get_preload','sound_discard',
+ 'sound_restore',
+ 'background_exists','background_get_name','background_get_width','background_get_height',
+ 'background_get_transparent','background_get_smooth','background_get_preload',
+ 'font_exists','font_get_name','font_get_fontname','font_get_bold','font_get_italic',
+ 'font_get_first','font_get_last',
+ 'path_exists','path_get_name','path_get_length','path_get_kind','path_get_closed',
+ 'path_get_precision','path_get_number','path_get_point_x','path_get_point_y',
+ 'path_get_point_speed','path_get_x','path_get_y','path_get_speed',
+ 'script_exists','script_get_name','script_get_text',
+ 'timeline_exists','timeline_get_name',
+ 'object_exists','object_get_name','object_get_sprite','object_get_solid','object_get_visible',
+ 'object_get_depth','object_get_persistent','object_get_mask','object_get_parent',
+ 'object_is_ancestor',
+ 'room_exists','room_get_name',
+ // Changing resources
+ 'sprite_set_offset','sprite_set_bbox_mode','sprite_set_bbox','sprite_set_precise',
+ 'sprite_duplicate','sprite_assign','sprite_merge','sprite_add','sprite_replace',
+ 'sprite_create_from_screen','sprite_add_from_screen','sprite_create_from_surface',
+ 'sprite_add_from_surface','sprite_delete','sprite_set_alpha_from_sprite',
+ 'sound_add','sound_replace','sound_delete',
+ 'background_duplicate','background_assign','background_add','background_replace',
+ 'background_create_color','background_create_gradient','background_create_from_screen',
+ 'background_create_from_surface','background_delete','background_set_alpha_from_background',
+ 'font_add','font_add_sprite','font_replace_sprite','font_delete',
+ 'path_set_kind','path_set_closed','path_set_precision','path_add','path_delete','path_duplicate',
+ 'path_assign','path_append','path_add_point','path_insert_point','path_change_point',
+ 'path_delete_point','path_clear_points','path_reverse','path_mirror','path_flip','path_rotate',
+ 'path_scale','path_shift',
+ 'execute_string','execute_file','script_execute',
+ 'timeline_add','timeline_delete','timeline_moment_add','timeline_moment_clear',
+ 'object_set_sprite','object_set_solid','object_set_visible','object_set_depth',
+ 'object_set_persistent','object_set_mask','object_set_parent','object_add','object_delete',
+ 'object_event_add','object_event_clear',
+ 'room_set_width','room_set_height','room_set_caption','room_set_persistent','room_set_code',
+ 'room_set_background_color','room_set_background','room_set_view','room_set_view_enabled',
+ 'room_add','room_duplicate','room_assign','room_instance_add','room_instance_clear',
+ 'room_tile_add','room_tile_add_ext','room_tile_clear',
+ // Files, registry and executing programs
+ 'file_text_open_read','file_text_open_write','file_text_open_append','file_text_close',
+ 'file_text_write_string','file_text_write_real','file_text_writeln','file_text_read_string',
+ 'file_text_read_real','file_text_readln','file_text_eof','file_exists','file_delete',
+ 'file_rename','file_copy','directory_exists','directory_create','file_find_first',
+ 'file_find_next','file_find_close','file_attributes', 'filename_name','filename_path',
+ 'filename_dir','filename_drive','filename_ext','filename_change_ext','file_bin_open',
+ 'file_bin_rewrite','file_bin_close','file_bin_size','file_bin_position','file_bin_seek',
+ 'file_bin_write_byte','file_bin_read_byte','parameter_count','parameter_string',
+ 'environment_get_variable',
+ 'registry_write_string','registry_write_real','registry_read_string','registry_read_real',
+ 'registry_exists','registry_write_string_ext','registry_write_real_ext',
+ 'registry_read_string_ext','registry_read_real_ext','registry_exists_ext','registry_set_root',
+ 'ini_open','ini_close','ini_read_string','ini_read_real','ini_write_string','ini_write_real',
+ 'ini_key_exists','ini_section_exists','ini_key_delete','ini_section_delete',
+ 'execute_program','execute_shell',
+ // Data structures
+ 'ds_stack_create','ds_stack_destroy','ds_stack_clear','ds_stack_size','ds_stack_empty',
+ 'ds_stack_push','ds_stack_pop','ds_stack_top',
+ 'ds_queue_create','ds_queue_destroy','ds_queue_clear','ds_queue_size','ds_queue_empty',
+ 'ds_queue_enqueue','ds_queue_dequeue','ds_queue_head','ds_queue_tail',
+ 'ds_list_create','ds_list_destroy','ds_list_clear','ds_list_size','ds_list_empty','ds_list_add',
+ 'ds_list_insert','ds_list_replace','ds_list_delete','ds_list_find_index','ds_list_find_value',
+ 'ds_list_sort',
+ 'ds_map_create','ds_map_destroy','ds_map_clear','ds_map_size','ds_map_empty','ds_map_add',
+ 'ds_map_replace','ds_map_delete','ds_map_exists','ds_map_find_value','ds_map_find_previous',
+ 'ds_map_find_next','ds_map_find_first','ds_map_find_last',
+ 'ds_priority_create','ds_priority_destroy','ds_priority_clear','ds_priority_size',
+ 'ds_priority_empty','ds_priority_add','ds_priority_change_priority','ds_priority_find_priority',
+ 'ds_priority_delete_value','ds_priority_delete_min','ds_priority_find_min',
+ 'ds_priority_delete_max','ds_priority_find_max',
+ 'ds_grid_create','ds_grid_destroy','ds_grid_resize','ds_grid_width','ds_grid_height',
+ 'ds_grid_clear','ds_grid_set','ds_grid_add','ds_grid_multiply','ds_grid_set_region',
+ 'ds_grid_add_region','ds_grid_multiply_region','ds_grid_set_disk','ds_grid_add_disk',
+ 'ds_grid_multiply_disk','ds_grid_get','ds_grid_get_sum','ds_grid_get_max','ds_grid_get_min',
+ 'ds_grid_get_mean','ds_grid_get_disk_sum','ds_grid_get_disk_min','ds_grid_get_disk_max',
+ 'ds_grid_get_disk_mean','ds_grid_value_exists','ds_grid_value_x','ds_grid_value_y',
+ 'ds_grid_value_disk_exists','ds_grid_value_disk_x','ds_grid_value_disk_y',
+ // Creating particles
+ 'effect_create_below','effect_create_above','effect_clear',
+ 'part_type_create','part_type_destroy','part_type_exists','part_type_clear','part_type_shape',
+ 'part_type_sprite','part_type_size','part_type_scale',
+ 'part_type_orientation','part_type_color1','part_type_color2','part_type_color3',
+ 'part_type_color_mix','part_type_color_rgb','part_type_color_hsv',
+ 'part_type_alpha1','part_type_alpha2','part_type_alpha3','part_type_blend','part_type_life',
+ 'part_type_step','part_type_death','part_type_speed','part_type_direction','part_type_gravity',
+ 'part_system_create','part_system_destroy','part_system_exists','part_system_clear',
+ 'part_system_draw_order','part_system_depth','part_system_position',
+ 'part_system_automatic_update','part_system_automatic_draw','part_system_update',
+ 'part_system_drawit','part_particles_create','part_particles_create_color',
+ 'part_particles_clear','part_particles_count',
+ 'part_emitter_create','part_emitter_destroy','part_emitter_destroy_all','part_emitter_exists',
+ 'part_emitter_clear','part_emitter_region','part_emitter_burst','part_emitter_stream',
+ 'part_attractor_create','part_attractor_destroy','part_attractor_destroy_all',
+ 'part_attractor_exists','part_attractor_clear','part_attractor_position','part_attractor_force',
+ 'part_destroyer_create','part_destroyer_destroy','part_destroyer_destroy_all',
+ 'part_destroyer_exists','part_destroyer_clear','part_destroyer_region',
+ 'part_deflector_create','part_deflector_destroy','part_deflector_destroy_all',
+ 'part_deflector_exists','part_deflector_clear','part_deflector_region','part_deflector_kind',
+ 'part_deflector_friction',
+ 'part_changer_create','part_changer_destroy','part_changer_destroy_all','part_changer_exists',
+ 'part_changer_clear','part_changer_region','part_changer_types','part_changer_kind',
+ // Multiplayer games
+ 'mplay_init_ipx','mplay_init_tcpip','mplay_init_modem','mplay_init_serial',
+ 'mplay_connect_status','mplay_end','mplay_ipaddress',
+ 'mplay_session_create','mplay_session_find','mplay_session_name','mplay_session_join',
+ 'mplay_session_mode','mplay_session_status','mplay_session_end',
+ 'mplay_player_find','mplay_player_name','mplay_player_id',
+ 'mplay_data_write','mplay_data_read','mplay_data_mode',
+ 'mplay_message_send','mplay_message_send_guaranteed','mplay_message_receive','mplay_message_id',
+ 'mplay_message_value','mplay_message_player','mplay_message_name','mplay_message_count',
+ 'mplay_message_clear',
+ // Using DLL's
+ 'external_define','external_call','external_free','execute_string','execute_file','window_handle',
+ // 3D Graphics
+ 'd3d_start','d3d_end','d3d_set_hidden','d3d_set_perspective',
+ 'd3d_set_depth',
+ 'd3d_primitive_begin','d3d_vertex','d3d_vertex_color','d3d_primitive_end',
+ 'd3d_primitive_begin_texture','d3d_vertex_texture','d3d_vertex_texture_color','d3d_set_culling',
+ 'd3d_draw_block','d3d_draw_cylinder','d3d_draw_cone','d3d_draw_ellipsoid','d3d_draw_wall',
+ 'd3d_draw_floor',
+ 'd3d_set_projection','d3d_set_projection_ext','d3d_set_projection_ortho',
+ 'd3d_set_projection_perspective',
+ 'd3d_transform_set_identity','d3d_transform_set_translation','d3d_transform_set_scaling',
+ 'd3d_transform_set_rotation_x','d3d_transform_set_rotation_y','d3d_transform_set_rotation_z',
+ 'd3d_transform_set_rotation_axis','d3d_transform_add_translation','d3d_transform_add_scaling',
+ 'd3d_transform_add_rotation_x','d3d_transform_add_rotation_y','d3d_transform_add_rotation_z',
+ 'd3d_transform_add_rotation_axis','d3d_transform_stack_clear','d3d_transform_stack_empty',
+ 'd3d_transform_stack_push','d3d_transform_stack_pop','d3d_transform_stack_top',
+ 'd3d_transform_stack_discard',
+ 'd3d_set_fog',
+ 'd3d_set_lighting','d3d_set_shading','d3d_light_define_direction','d3d_light_define_point',
+ 'd3d_light_enable','d3d_vertex_normal','d3d_vertex_normal_color','d3d_vertex_normal_texture',
+ 'd3d_vertex_normal_texture_color',
+ 'd3d_model_create','d3d_model_destroy','d3d_model_clear','d3d_model_save','d3d_model_load',
+ 'd3d_model_draw','d3d_model_primitive_begin','d3d_model_vertex','d3d_model_vertex_color',
+ 'd3d_model_vertex_texture','d3d_model_vertex_texture_color','d3d_model_vertex_normal',
+ 'd3d_model_vertex_normal_color','d3d_model_vertex_normal_texture',
+ 'd3d_model_vertex_normal_texture_color','d3d_model_primitive_end','d3d_model_block',
+ 'd3d_model_cylinder','d3d_model_cone','d3d_model_ellipsoid','d3d_model_wall','d3d_model_floor'
+ ),
+ // constants
+ 4 => array(
+ 'true', 'false', 'pi',
+ 'ev_destroy','ev_step','ev_alarm','ev_keyboard','ev_mouse','ev_collision','ev_other','ev_draw',
+ 'ev_keypress','ev_keyrelease','ev_left_button','ev_right_button','ev_middle_button',
+ 'ev_no_button','ev_left_press','ev_right_press','ev_middle_press','ev_left_release',
+ 'ev_right_release','ev_middle_release','ev_mouse_enter','ev_mouse_leave','ev_mouse_wheel_up',
+ 'ev_mouse_wheel_down','ev_global_left_button','ev_global_right_button','ev_global_middle_button',
+ 'ev_global_left_press','ev_global_right_press','ev_global_middle_press','ev_global_left_release',
+ 'ev_global_right_release','ev_global_middle_release','ev_joystick1_left','ev_joystick1_right',
+ 'ev_joystick1_up','ev_joystick1_down','ev_joystick1_button1','ev_joystick1_button2',
+ 'ev_joystick1_button3','ev_joystick1_button4','ev_joystick1_button5','ev_joystick1_button6',
+ 'ev_joystick1_button7','ev_joystick1_button8','ev_joystick2_left','ev_joystick2_right',
+ 'ev_joystick2_up','ev_joystick2_down','ev_joystick2_button1','ev_joystick2_button2',
+ 'ev_joystick2_button3','ev_joystick2_button4','ev_joystick2_button5','ev_joystick2_button6',
+ 'ev_joystick2_button7','ev_joystick2_button8',
+ 'ev_outside','ev_boundary','ev_game_start','ev_game_end','ev_room_start','ev_room_end',
+ 'ev_no_more_lives','ev_no_more_health','ev_animation_end','ev_end_of_path','ev_user0','ev_user1',
+ 'ev_user2','ev_user3','ev_user4','ev_user5','ev_user6','ev_user7','ev_user8','ev_user9',
+ 'ev_user10','ev_user11','ev_user12','ev_user13','ev_user14','ev_user15','ev_step_normal',
+ 'ev_step_begin','ev_step_end',
+ 'vk_nokey','vk_anykey','vk_left','vk_right','vk_up','vk_down','vk_enter','vk_escape','vk_space',
+ 'vk_shift','vk_control','vk_alt','vk_backspace','vk_tab','vk_home','vk_end','vk_delete',
+ 'vk_insert','vk_pageup','vk_pagedown','vk_pause','vk_printscreen',
+ 'vk_f1','vk_f2','vk_f3','vk_f4','vk_f5','vk_f6','vk_f7','vk_f8','vk_f9','vk_f10','vk_f11','vk_f12',
+ 'vk_numpad0','vk_numpad1','vk_numpad2','vk_numpad3','vk_numpad4','vk_numpad5','vk_numpad6',
+ 'vk_numpad7','vk_numpad8','vk_numpad9', 'vk_multiply','vk_divide','vk_add','vk_subtract',
+ 'vk_decimal','vk_lshift','vk_lcontrol','vk_lalt','vk_rshift','vk_rcontrol','vk_ralt',
+ 'c_aqua','c_black','c_blue','c_dkgray','c_fuchsia','c_gray','c_green','c_lime','c_ltgray',
+ 'c_maroon','c_navy','c_olive','c_purple','c_red','c_silver','c_teal','c_white','c_yellow',
+ 'fa_left', 'fa_center','fa_right','fa_top','fa_middle','fa_bottom',
+ 'pr_pointlist','pr_linelist','pr_linestrip','pr_trianglelist','pr_trianglestrip',
+ 'pr_trianglefan',
+ 'cr_none','cr_arrow','cr_cross','cr_beam','cr_size_nesw','cr_size_ns','cr_size_nwse',
+ 'cr_size_we','cr_uparrow','cr_hourglass','cr_drag','cr_nodrop','cr_hsplit','cr_vsplit',
+ 'cr_multidrag','cr_sqlwait','cr_no','cr_appstart','cr_help','cr_handpoint','cr_size_all',
+ 'se_chorus','se_echo','se_flanger','se_gargle','se_reverb','se_compressor','se_equalizer',
+ 'fa_readonly','fa_hidden','fa_sysfile','fa_volumeid','fa_directory','fa_archive',
+ 'pt_shape_pixel','pt_shape_disk','pt_shape_square','pt_shape_line','pt_shape_star',
+ 'pt_shape_circle','pt_shape_ring','pt_shape_sphere','pt_shape_flare','pt_shape_spark',
+ 'pt_shape_explosion','pt_shape_cloud','pt_shape_smoke','pt_shape_snow',
+ 'ps_shape_rectangle','ps_shape_ellipse ','ps_shape_diamond','ps_shape_line',
+ 'ps_distr_linear','ps_distr_gaussian','ps_force_constant','ps_force_linear','ps_force_quadratic',
+ 'ps_deflect_horizontal', 'ps_deflect_vertical',
+ 'ps_change_motion','ps_change_shape','ps_change_all'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '&&', '||', '^^', '<', '<=', '==', '!=', '>', '>=',
+ '|', '&', '^', '<<', '>>', '+', '-', '*', '/', '!', '-', '~'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ 4 => true,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'font-weight: bold; color: #000000;',
+ 2 => 'font-weight: bold; color: #000000;',
+ 3 => 'color: navy;',
+ 4 => 'color: brown',
+ ),
+ 'COMMENTS' => array(
+ 1 => 'font-style: italic; color: green;',
+ 'MULTI' => 'font-style: italic; color: green;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #000000;' //'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #202020;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66; font-weight: bold;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ // All GML functions have been indexed, but need some corrections.
+ 3 => 'http://www.zonamakers.com/gmlreference/{FNAME}.html', // (provisional, could change soon!)
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/html4strict.php b/plugins/dokuwiki/inc/geshi/html4strict.php
new file mode 100644
index 0000000..2f9bf74
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/html4strict.php
@@ -0,0 +1,256 @@
+<?php
+/*************************************************************************************
+ * html4strict.php
+ * ---------------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2004/07/10
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * HTML 4.01 strict language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/28 (1.0.4)
+ * - Removed escape character for strings
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added INS and DEL
+ * - Removed the background colour from tags' styles
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Check that only HTML4 strict attributes are highlighted
+ * * Eliminate empty tags that aren't allowed in HTML4 strict
+ * * Split to several files - html4trans, xhtml1 etc
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'HTML',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array('<!--' => '-->'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ ),
+ 2 => array(
+ '&lt;a&gt;', '&lt;abbr&gt;', '&lt;acronym&gt;', '&lt;address&gt;', '&lt;applet&gt;',
+ '&lt;a', '&lt;abbr', '&lt;acronym', '&lt;address', '&lt;applet',
+ '&lt;/a&gt;', '&lt;/abbr&gt;', '&lt;/acronym&gt;', '&lt;/address&gt;', '&lt;/applet&gt;',
+ '&lt;/a', '&lt;/abbr', '&lt;/acronym', '&lt;/address', '&lt;/applet',
+
+ '&lt;base&gt;', '&lt;basefont&gt;', '&lt;bdo&gt;', '&lt;big&gt;', '&lt;blockquote&gt;', '&lt;body&gt;', '&lt;br&gt;', '&lt;button&gt;', '&lt;b&gt;',
+ '&lt;base', '&lt;basefont', '&lt;bdo', '&lt;big', '&lt;blockquote', '&lt;body', '&lt;br', '&lt;button', '&lt;b',
+ '&lt;/base&gt;', '&lt;/basefont&gt;', '&lt;/bdo&gt;', '&lt;/big&gt;', '&lt;/blockquote&gt;', '&lt;/body&gt;', '&lt;/br&gt;', '&lt;/button&gt;', '&lt;/b&gt;',
+ '&lt;/base', '&lt;/basefont', '&lt;/bdo', '&lt;/big', '&lt;/blockquote', '&lt;/body', '&lt;/br', '&lt;/button', '&lt;/b',
+
+ '&lt;caption&gt;', '&lt;center&gt;', '&lt;cite&gt;', '&lt;code&gt;', '&lt;colgroup&gt;', '&lt;col&gt;',
+ '&lt;caption', '&lt;center', '&lt;cite', '&lt;code', '&lt;colgroup', '&lt;col',
+ '&lt;/caption&gt;', '&lt;/center&gt;', '&lt;/cite&gt;', '&lt;/code&gt;', '&lt;/colgroup&gt;', '&lt;/col&gt;',
+ '&lt;/caption', '&lt;/center', '&lt;/cite', '&lt;/code', '&lt;/colgroup', '&lt;/col',
+
+ '&lt;dd&gt;', '&lt;del&gt;', '&lt;dfn&gt;', '&lt;dir&gt;', '&lt;div&gt;', '&lt;dl&gt;', '&lt;dt&gt;',
+ '&lt;dd', '&lt;del', '&lt;dfn', '&lt;dir', '&lt;div', '&lt;dl', '&lt;dt',
+ '&lt;/dd&gt;', '&lt;/del&gt;', '&lt;/dfn&gt;', '&lt;/dir&gt;', '&lt;/div&gt;', '&lt;/dl&gt;', '&lt;/dt&gt;',
+ '&lt;/dd', '&lt;/del', '&lt;/dfn', '&lt;/dir', '&lt;/div', '&lt;/dl', '&lt;/dt',
+
+ '&lt;em&gt;',
+ '&lt;em',
+ '&lt;/em&gt;',
+ '&lt;/em',
+
+ '&lt;fieldset&gt;', '&lt;font&gt;', '&lt;form&gt;', '&lt;frame&gt;', '&lt;frameset&gt;',
+ '&lt;fieldset', '&lt;font', '&lt;form', '&lt;frame', '&lt;frameset',
+ '&lt;/fieldset&gt;', '&lt;/font&gt;', '&lt;/form&gt;', '&lt;/frame&gt;', '&lt;/frameset&gt;',
+ '&lt;/fieldset', '&lt;/font', '&lt;/form', '&lt;/frame', '&lt;/frameset',
+
+ '&lt;h1&gt;', '&lt;h2&gt;', '&lt;h3&gt;', '&lt;h4&gt;', '&lt;h5&gt;', '&lt;h6&gt;', '&lt;head&gt;', '&lt;hr&gt;', '&lt;html&gt;',
+ '&lt;h1', '&lt;h2', '&lt;h3', '&lt;h4', '&lt;h5', '&lt;h6', '&lt;head', '&lt;hr', '&lt;html',
+ '&lt;/h1&gt;', '&lt;/h2&gt;', '&lt;/h3&gt;', '&lt;/h4&gt;', '&lt;/h5&gt;', '&lt;/h6&gt;', '&lt;/head&gt;', '&lt;/hr&gt;', '&lt;/html&gt;',
+ '&lt;/h1', '&lt;/h2', '&lt;/h3', '&lt;/h4', '&lt;/h5', '&lt;/h6', '&lt;/head', '&lt;/hr', '&lt;/html',
+
+ '&lt;iframe&gt;', '&lt;ilayer&gt;', '&lt;img&gt;', '&lt;input&gt;', '&lt;ins&gt;', '&lt;isindex&gt;', '&lt;i&gt;',
+ '&lt;iframe', '&lt;ilayer', '&lt;img', '&lt;input', '&lt;ins', '&lt;isindex', '&lt;i',
+ '&lt;/iframe&gt;', '&lt;/ilayer&gt;', '&lt;/img&gt;', '&lt;/input&gt;', '&lt;/ins&gt;', '&lt;/isindex&gt;', '&lt;/i&gt;',
+ '&lt;/iframe', '&lt;/ilayer', '&lt;/img', '&lt;/input', '&lt;/ins', '&lt;/isindex', '&lt;/i',
+
+ '&lt;kbd&gt;',
+ '&lt;kbd',
+ '&t;/kbd&gt;',
+ '&lt;/kbd',
+
+ '&lt;label&gt;', '&lt;legend&gt;', '&lt;link&gt;', '&lt;li&gt;',
+ '&lt;label', '&lt;legend', '&lt;link', '&lt;li',
+ '&lt;/label&gt;', '&lt;/legend&gt;', '&lt;/link&gt;', '&lt;/li&gt;',
+ '&lt;/label', '&lt;/legend', '&lt;/link', '&lt;/li',
+
+ '&lt;map&gt;', '&lt;meta&gt;',
+ '&lt;map', '&lt;meta',
+ '&lt;/map&gt;', '&lt;/meta&gt;',
+ '&lt;/map', '&lt;/meta',
+
+ '&lt;noframes&gt;', '&lt;noscript&gt;',
+ '&lt;noframes', '&lt;noscript',
+ '&lt;/noframes&gt;', '&lt;/noscript&gt;',
+ '&lt;/noframes', '&lt;/noscript',
+
+ '&lt;object&gt;', '&lt;ol&gt;', '&lt;optgroup&gt;', '&lt;option&gt;',
+ '&lt;object', '&lt;ol', '&lt;optgroup', '&lt;option',
+ '&lt;/object&gt;', '&lt;/ol&gt;', '&lt;/optgroup&gt;', '&lt;/option&gt;',
+ '&lt;/object', '&lt;/ol', '&lt;/optgroup', '&lt;/option',
+
+ '&lt;param&gt;', '&lt;pre&gt;', '&lt;p&gt;',
+ '&lt;param', '&lt;pre', '&lt;p',
+ '&lt;/param&gt;', '&lt;/pre&gt;', '&lt;/p&gt;',
+ '&lt;/param', '&lt;/pre', '&lt;/p',
+
+ '&lt;q&gt;',
+ '&lt;q',
+ '&lt;/q&gt;',
+ '&lt;/q',
+
+ '&lt;samp&gt;', '&lt;script&gt;', '&lt;select&gt;', '&lt;small&gt;', '&lt;span&gt;', '&lt;strike&gt;', '&lt;strong&gt;', '&lt;style&gt;', '&lt;sub&gt;', '&lt;sup&gt;', '&lt;s&gt;',
+ '&lt;samp', '&lt;script', '&lt;select', '&lt;small', '&lt;span', '&lt;strike', '&lt;strong', '&lt;style', '&lt;sub', '&lt;sup', '&lt;s',
+ '&lt;/samp&gt;', '&lt;/script&gt;', '&lt;/select&gt;', '&lt;/small&gt;', '&lt;/span&gt;', '&lt;/strike&gt;', '&lt;/strong&gt;', '&lt;/style&gt;', '&lt;/sub&gt;', '&lt;/sup&gt;', '&lt;/s&gt;',
+ '&lt;/samp', '&lt;/script', '&lt;/select', '&lt;/small', '&lt;/span', '&lt;/strike', '&lt;/strong', '&lt;/style', '&lt;/sub', '&lt;/sup', '&lt;/s',
+
+ '&lt;table&gt;', '&lt;tbody&gt;', '&lt;td&gt;', '&lt;textarea&gt;', '&lt;text&gt;', '&lt;tfoot&gt;', '&lt;thead&gt;', '&lt;th&gt;', '&lt;title&gt;', '&lt;tr&gt;', '&lt;tt&gt;',
+ '&lt;table', '&lt;tbody', '&lt;td', '&lt;textarea', '&lt;text', '&lt;tfoot', '&lt;tfoot', '&lt;thead', '&lt;th', '&lt;title', '&lt;tr', '&lt;tt',
+ '&lt;/table&gt;', '&lt;/tbody&gt;', '&lt;/td&gt;', '&lt;/textarea&gt;', '&lt;/text&gt;', '&lt;/tfoot&gt;', '&lt;/thead', '&lt;/tfoot', '&lt;/th&gt;', '&lt;/title&gt;', '&lt;/tr&gt;', '&lt;/tt&gt;',
+ '&lt;/table', '&lt;/tbody', '&lt;/td', '&lt;/textarea', '&lt;/text', '&lt;/tfoot', '&lt;/tfoot', '&lt;/thead', '&lt;/th', '&lt;/title', '&lt;/tr', '&lt;/tt',
+
+ '&lt;ul&gt;', '&lt;u&gt;',
+ '&lt;ul', '&lt;u',
+ '&lt;/ul&gt;', '&lt;/ul&gt;',
+ '&lt;/ul', '&lt;/u',
+
+ '&lt;var&gt;',
+ '&lt;var',
+ '&lt;/var&gt;',
+ '&lt;/var',
+
+ '&gt;', '&lt;'
+ ),
+ 3 => array(
+ 'abbr', 'accept-charset', 'accept', 'accesskey', 'action', 'align', 'alink', 'alt', 'archive', 'axis',
+ 'background', 'bgcolor', 'border',
+ 'cellpadding', 'cellspacing', 'char', 'char', 'charoff', 'charset', 'checked', 'cite', 'class', 'classid', 'clear', 'code', 'codebase', 'codetype', 'color', 'cols', 'colspan', 'compact', 'content', 'coords',
+ 'data', 'datetime', 'declare', 'defer', 'dir', 'disabled',
+ 'enctype',
+ 'face', 'for', 'frame', 'frameborder',
+ 'headers', 'height', 'href', 'hreflang', 'hspace', 'http-equiv',
+ 'id', 'ismap',
+ 'label', 'lang', 'language', 'link', 'longdesc',
+ 'marginheight', 'marginwidth', 'maxlength', 'media', 'method', 'multiple',
+ 'name', 'nohref', 'noresize', 'noshade', 'nowrap',
+ 'object', 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onreset', 'onselect', 'onsubmit', 'onunload',
+ 'profile', 'prompt',
+ 'readonly', 'rel', 'rev', 'rowspan', 'rows', 'rules',
+ 'scheme', 'scope', 'scrolling', 'selected', 'shape', 'size', 'span', 'src', 'standby', 'start', 'style', 'summary',
+ 'tabindex', 'target', 'text', 'title', 'type',
+ 'usemap',
+ 'valign', 'value', 'valuetype', 'version', 'vlink', 'vspace',
+ 'width'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '/', '='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ 0 => 'color: #00bbdd;',
+ 1 => 'color: #ddbb00;',
+ 2 => 'color: #009900;'
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => 'http://december.com/html/4/element/{FNAME}.html',
+ 3 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_ALWAYS,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<!DOCTYPE' => '>'
+ ),
+ 1 => array(
+ '&' => ';'
+ ),
+ 2 => array(
+ '<' => '>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => false,
+ 1 => false,
+ 2 => true
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/html5.php b/plugins/dokuwiki/inc/geshi/html5.php
new file mode 100644
index 0000000..64101b8
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/html5.php
@@ -0,0 +1,210 @@
+<?php
+/*************************************************************************************
+ * html5.php
+ * ---------------
+ * Author: Nigel McNie (nigel@geshi.org)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.8.11
+ * Date Started: 2004/07/10
+ *
+ * HTML 5 language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/28 (1.0.4)
+ * - Removed escape character for strings
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added INS and DEL
+ * - Removed the background colour from tags' styles
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Check that only HTML4 strict attributes are highlighted
+ * * Eliminate empty tags that aren't allowed in HTML4 strict
+ * * Split to several files - html4trans, xhtml1 etc
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'HTML5',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 2 => array(
+ 'a', 'abbr', 'address', 'article', 'area', 'aside', 'audio',
+
+ 'base', 'bdo', 'blockquote', 'body', 'br', 'button', 'b',
+
+ 'caption', 'cite', 'code', 'colgroup', 'col', 'canvas', 'command', 'datalist', 'details',
+
+ 'dd', 'del', 'dfn', 'div', 'dl', 'dt',
+
+ 'em', 'embed',
+
+ 'fieldset', 'form', 'figcaption', 'figure', 'footer',
+
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html', 'header', 'hgroup',
+
+ 'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'i',
+
+ 'kbd', 'keygen',
+
+ 'label', 'legend', 'link', 'li',
+
+ 'map', 'meta', 'mark', 'meter',
+
+ 'noscript', 'nav',
+
+ 'object', 'ol', 'optgroup', 'option', 'output',
+
+ 'param', 'pre', 'p', 'progress',
+
+ 'q',
+
+ 'rp', 'rt', 'ruby',
+
+ 'samp', 'script', 'select', 'small', 'span', 'strong', 'style', 'sub', 'sup', 's', 'section', 'source', 'summary',
+
+ 'table', 'tbody', 'td', 'textarea', 'text', 'tfoot', 'thead', 'th', 'title', 'tr', 'time',
+
+ 'ul',
+
+ 'var', 'video',
+
+ 'wbr',
+ ),
+ 3 => array(
+ 'abbr', 'accept-charset', 'accept', 'accesskey', 'action', 'align', 'alink', 'alt', 'archive', 'axis', 'autocomplete', 'autofocus',
+ 'background', 'bgcolor', 'border',
+ 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class', 'classid', 'clear', 'code', 'codebase', 'codetype', 'color', 'cols', 'colspan', 'compact', 'content', 'coords', 'contenteditable', 'contextmenu',
+ 'data', 'datetime', 'declare', 'defer', 'dir', 'disabled', 'draggable', 'dropzone',
+ 'enctype',
+ 'face', 'for', 'frame', 'frameborder', 'form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget',
+ 'headers', 'height', 'href', 'hreflang', 'hspace', 'http-equiv', 'hidden',
+ 'id', 'ismap',
+ 'label', 'lang', 'language', 'link', 'longdesc',
+ 'marginheight', 'marginwidth', 'maxlength', 'media', 'method', 'multiple', 'min', 'max',
+ 'name', 'nohref', 'noresize', 'noshade', 'nowrap', 'novalidate',
+ 'object', 'onblur', 'onchange', 'onclick', 'ondblclick', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onselect', 'onsubmit', 'onunload', 'onafterprint', 'onbeforeprint', 'onbeforeonload', 'onerror', 'onhaschange', 'onmessage', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpopstate', 'onredo', 'onresize', 'onstorage', 'onundo', 'oncontextmenu', 'onformchange', 'onforminput', 'oninput', 'oninvalid', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onmousewheel', 'onscroll', 'oncanplay', 'oncanplaythrough', 'ondurationchange', 'onemptied', 'onended', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onpause', 'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreadystatechange', 'onseeked', 'onseeking', 'onstalled', 'onsuspend', 'ontimeupdate', 'onvolumechange', 'onwaiting',
+ 'profile', 'prompt', 'pattern', 'placeholder',
+ 'readonly', 'rel', 'rev', 'rowspan', 'rows', 'rules', 'required',
+ 'scheme', 'scope', 'scrolling', 'selected', 'shape', 'size', 'span', 'src', 'standby', 'start', 'style', 'summary', 'spellcheck', 'step',
+ 'tabindex', 'target', 'text', 'title', 'type',
+ 'usemap',
+ 'valign', 'value', 'valuetype', 'version', 'vlink', 'vspace',
+ 'width'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '/', '='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ -2 => 'color: #404040;', // CDATA
+ -1 => 'color: #808080; font-style: italic;', // comments
+ 0 => 'color: #00bbdd;',
+ 1 => 'color: #ddbb00;',
+ 2 => 'color: #009900;'
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 2 => 'http://december.com/html/4/element/{FNAMEL}.html',
+ 3 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_ALWAYS,
+ 'SCRIPT_DELIMITERS' => array(
+ -2 => array(
+ '<![CDATA[' => ']]>'
+ ),
+ -1 => array(
+ '<!--' => '-->'
+ ),
+ 0 => array(
+ '<!DOCTYPE' => '>'
+ ),
+ 1 => array(
+ '&' => ';'
+ ),
+ 2 => array(
+ '<' => '>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ -2 => false,
+ -1 => false,
+ 0 => false,
+ 1 => false,
+ 2 => true
+ ),
+ 'TAB_WIDTH' => 4,
+ 'PARSER_CONTROL' => array(
+ 'KEYWORDS' => array(
+ 2 => array(
+ 'DISALLOWED_BEFORE' => '(?<=&lt;|&lt;\/)',
+ 'DISALLOWED_AFTER' => '(?=\s|\/|&gt;)',
+ )
+ )
+ )
+);
diff --git a/plugins/dokuwiki/inc/geshi/ini.php b/plugins/dokuwiki/inc/geshi/ini.php
new file mode 100644
index 0000000..6792829
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/ini.php
@@ -0,0 +1,125 @@
+<?php
+/*************************************************************************************
+ * ini.php
+ * --------
+ * Author: deguix (cevo_deguix@yahoo.com.br)
+ * Copyright: (c) 2005 deguix
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.5 $
+ * Date Started: 2005/03/27
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * INI language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/28 (1.0.1)
+ * - Removed unnecessary keyword style index
+ * - Added support for " strings
+ * 2005/04/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/03/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'INI',
+ 'COMMENT_SINGLE' => array(0 => ';'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ ),
+ 'SYMBOLS' => array(
+ '[', ']', '='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ ),
+ 'COMMENTS' => array(
+ 0 => 'color: #666666; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => ''
+ ),
+ 'BRACKETS' => array(
+ 0 => ''
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #933;'
+ ),
+ 'NUMBERS' => array(
+ 0 => ''
+ ),
+ 'METHODS' => array(
+ 0 => ''
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #000066; font-weight:bold;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #000066; font-weight:bold;',
+ 1 => 'color: #000099;',
+ 2 => 'color: #660066;'
+ ),
+ 'SCRIPT' => array(
+ 0 => ''
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '\[.+\]',
+ 1 => array(
+ GESHI_SEARCH => '([a-zA-Z0-9_]+\s*)=(.+)',
+ GESHI_REPLACE => '\\1',
+ GESHI_MODIFIERS => '',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => '=\\2'
+ ),
+ 2 => array(
+ // Evil hackery to get around GeSHi bug: <>" and ; are added so <span>s can be matched
+ // Explicit match on variable names because if a comment is before the first < of the span
+ // gets chewed up...
+ GESHI_SEARCH => '([<>";a-zA-Z0-9_]+\s*)=(.+)',
+ GESHI_REPLACE => '\\2',
+ GESHI_MODIFIERS => '',
+ GESHI_BEFORE => '\\1=',
+ GESHI_AFTER => ''
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/inno.php b/plugins/dokuwiki/inc/geshi/inno.php
new file mode 100644
index 0000000..91273b4
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/inno.php
@@ -0,0 +1,215 @@
+<?php
+/*************************************************************************************
+ * Inno.php
+ * ----------
+ * Author: Thomas Klingler (hotline@theratech.de) based on delphi.php from Járja Norbert (jnorbi@vipmail.hu)
+ * Copyright: (c) 2004 Járja Norbert, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.12.2.4 $
+ * Date Started: 2005/07/29
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Inno Script language inkl. Delphi (Object Pascal) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/09/03
+ * - First Release
+ *
+ * TODO (updated 2005/07/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Inno',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('(*' => '*)'),
+ 'CASE_KEYWORDS' => 0,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array('Setup','Types','Components','Tasks','Dirs','Files','Icons','INI','InstallDelete','Languages','Messages',
+ 'CustomMessage','LangOptions','Registry','RUN','UninstallDelete','UninstallRun'
+ ,'app','win','sys','syswow64','src','sd','pf','pf32','pf64','cf','cf32','cf64','tmp','fonts','dao',
+ 'group','localappdata','sendto','userappdata','commonappdata','userdesktop','commondesktop','userdocs',
+ 'commondocs','userfavorites','commonfavorites','userprograms','commonprograms','userstartmenu',
+ 'commonstartmenu','userstartup','commonstartup','usertemplates','commontemplates'
+ ),
+ 2 => array(
+ 'nil', 'false', 'true', 'var', 'type', 'const','And', 'Array', 'As', 'Begin', 'Case', 'Class', 'Constructor', 'Destructor', 'Div', 'Do', 'DownTo', 'Else',
+ 'End', 'Except', 'File', 'Finally', 'For', 'Function', 'Goto', 'If', 'Implementation', 'In', 'Inherited', 'Interface',
+ 'Is', 'Mod', 'Not', 'Object', 'Of', 'On', 'Or', 'Packed', 'Procedure', 'Property', 'Raise', 'Record',
+ 'Repeat', 'Set', 'Shl', 'Shr', 'Then', 'ThreadVar', 'To', 'Try', 'Unit', 'Until', 'Uses', 'While', 'With', 'Xor',
+
+ 'HKCC','HKCR','HKCU','HKLM','HKU','alwaysoverwrite','alwaysskipifsameorolder','append',
+ 'binary','classic','closeonexit','comparetimestamp','confirmoverwrite',
+ 'createkeyifdoesntexist','createonlyiffileexists','createvalueifdoesntexist',
+ 'deleteafterinstall','deletekey','deletevalue','dirifempty','dontcloseonexit',
+ 'dontcopy','dontcreatekey','disablenouninstallwarning','dword','exclusive','expandsz',
+ 'external','files','filesandordirs','fixed','fontisnttruetype','ignoreversion','iscustom','isreadme',
+ 'modern','multisz','new','noerror','none','normal','nowait','onlyifdestfileexists',
+ 'onlyifdoesntexist','onlyifnewer','overwrite','overwritereadonly','postinstall',
+ 'preservestringtype','promptifolder','regserver','regtypelib','restart','restartreplace',
+ 'runhidden','runmaximized','runminimized','sharedfile','shellexec','showcheckbox',
+ 'skipifnotsilent','skipifsilent','silent','skipifdoesntexist',
+ 'skipifsourcedoesntexist','sortfilesbyextension','unchecked','uninsalwaysuninstall',
+ 'uninsclearvalue','uninsdeleteentry','uninsdeletekey','uninsdeletekeyifempty',
+ 'uninsdeletesection','uninsdeletesectionifempty','uninsdeletevalue',
+ 'uninsneveruninstall','useapppaths','verysilent','waituntilidle'
+
+
+ ),
+ 3 => array(
+ 'Abs', 'Addr', 'AnsiCompareStr', 'AnsiCompareText', 'AnsiContainsStr', 'AnsiEndsStr', 'AnsiIndexStr', 'AnsiLeftStr',
+ 'AnsiLowerCase', 'AnsiMatchStr', 'AnsiMidStr', 'AnsiPos', 'AnsiReplaceStr', 'AnsiReverseString', 'AnsiRightStr',
+ 'AnsiStartsStr', 'AnsiUpperCase', 'ArcCos', 'ArcSin', 'ArcTan', 'Assigned', 'BeginThread', 'Bounds', 'CelsiusToFahrenheit',
+ 'ChangeFileExt', 'Chr', 'CompareStr', 'CompareText', 'Concat', 'Convert', 'Copy', 'Cos', 'CreateDir', 'CurrToStr',
+ 'CurrToStrF', 'Date', 'DateTimeToFileDate', 'DateTimeToStr', 'DateToStr', 'DayOfTheMonth', 'DayOfTheWeek', 'DayOfTheYear',
+ 'DayOfWeek', 'DaysBetween', 'DaysInAMonth', 'DaysInAYear', 'DaySpan', 'DegToRad', 'DeleteFile', 'DiskFree', 'DiskSize',
+ 'DupeString', 'EncodeDate', 'EncodeDateTime', 'EncodeTime', 'EndOfADay', 'EndOfAMonth', 'Eof', 'Eoln', 'Exp', 'ExtractFileDir',
+ 'ExtractFileDrive', 'ExtractFileExt', 'ExtractFileName', 'ExtractFilePath', 'FahrenheitToCelsius', 'FileAge',
+ 'FileDateToDateTime', 'FileExists', 'FilePos', 'FileSearch', 'FileSetDate', 'FileSize', 'FindClose', 'FindCmdLineSwitch',
+ 'FindFirst', 'FindNext', 'FloatToStr', 'FloatToStrF', 'Format', 'FormatCurr', 'FormatDateTime', 'FormatFloat', 'Frac',
+ 'GetCurrentDir', 'GetLastError', 'GetMem', 'High', 'IncDay', 'IncMinute', 'IncMonth', 'IncYear', 'InputBox',
+ 'InputQuery', 'Int', 'IntToHex', 'IntToStr', 'IOResult', 'IsInfinite', 'IsLeapYear', 'IsMultiThread', 'IsNaN',
+ 'LastDelimiter', 'Length', 'Ln', 'Lo', 'Log10', 'Low', 'LowerCase', 'Max', 'Mean', 'MessageDlg', 'MessageDlgPos',
+ 'MonthOfTheYear', 'Now', 'Odd', 'Ord', 'ParamCount', 'ParamStr', 'Pi', 'Point', 'PointsEqual', 'Pos', 'Pred',
+ 'Printer', 'PromptForFileName', 'PtInRect', 'RadToDeg', 'Random', 'RandomRange', 'RecodeDate', 'RecodeTime', 'Rect',
+ 'RemoveDir', 'RenameFile', 'Round', 'SeekEof', 'SeekEoln', 'SelectDirectory', 'SetCurrentDir', 'Sin', 'SizeOf',
+ 'Slice', 'Sqr', 'Sqrt', 'StringOfChar', 'StringReplace', 'StringToWideChar', 'StrToCurr', 'StrToDate', 'StrToDateTime',
+ 'StrToFloat', 'StrToInt', 'StrToInt64', 'StrToInt64Def', 'StrToIntDef', 'StrToTime', 'StuffString', 'Succ', 'Sum', 'Tan',
+ 'Time', 'TimeToStr', 'Tomorrow', 'Trunc', 'UpCase', 'UpperCase', 'VarType', 'WideCharToString', 'WrapText', 'Yesterday',
+ 'Append', 'AppendStr', 'Assign', 'AssignFile', 'AssignPrn', 'Beep', 'BlockRead', 'BlockWrite', 'Break',
+ 'ChDir', 'Close', 'CloseFile', 'Continue', 'DateTimeToString', 'Dec', 'DecodeDate', 'DecodeDateTime',
+ 'DecodeTime', 'Delete', 'Dispose', 'EndThread', 'Erase', 'Exclude', 'Exit', 'FillChar', 'Flush', 'FreeAndNil',
+ 'FreeMem', 'GetDir', 'GetLocaleFormatSettings', 'Halt', 'Inc', 'Include', 'Insert', 'MkDir', 'Move', 'New',
+ 'ProcessPath', 'Randomize', 'Read', 'ReadLn', 'ReallocMem', 'Rename', 'ReplaceDate', 'ReplaceTime',
+ 'Reset', 'ReWrite', 'RmDir', 'RunError', 'Seek', 'SetLength', 'SetString', 'ShowMessage', 'ShowMessageFmt',
+ 'ShowMessagePos', 'Str', 'Truncate', 'Val', 'Write', 'WriteLn',
+
+ 'AdminPrivilegesRequired','AfterInstall','AllowCancelDuringInstall','AllowNoIcons','AllowRootDirectory','AllowUNCPath','AlwaysRestart','AlwaysShowComponentsList','AlwaysShowDirOnReadyPage','AlwaysShowGroupOnReadyPage ','AlwaysUsePersonalGroup','AppComments','AppContact','AppCopyright','AppendDefaultDirName',
+ 'AppendDefaultGroupName','AppId','AppModifyPath','AppMutex','AppName','AppPublisher',
+ 'AppPublisherURL','AppReadmeFile','AppSupportURL','AppUpdatesURL','AppVerName','AppVersion',
+ 'Attribs','BackColor','BackColor2','BackColorDirection','BackSolid','BeforeInstall',
+ 'ChangesAssociations','ChangesEnvironment','Check','CodeFile','Comment','Components','Compression','CopyMode',
+ 'CreateAppDir','CreateUninstallRegKey','DefaultDirName','DefaultGroupName',
+ 'DefaultUserInfoName','DefaultUserInfoOrg','DefaultUserInfoSerial',
+ 'Description','DestDir','DestName','DirExistsWarning',
+ 'DisableDirPage','DisableFinishedPage',
+ 'DisableProgramGroupPage','DisableReadyMemo','DisableReadyPage',
+ 'DisableStartupPrompt','DiskClusterSize','DiskSliceSize','DiskSpaceMBLabel',
+ 'DiskSpanning','DontMergeDuplicateFiles','EnableDirDoesntExistWarning','Encryption',
+ 'Excludes','ExtraDiskSpaceRequired','Filename','Flags','FlatComponentsList','FontInstall',
+ 'GroupDescription','HotKey','IconFilename','IconIndex','InfoAfterFile','InfoBeforeFile',
+ 'InternalCompressLevel','Key','LanguageDetectionMethod','Languages',
+ 'LicenseFile','MergeDuplicateFiles','MessagesFile','MinVersion','Name',
+ 'OnlyBelowVersion','OutputBaseFilename','OutputManifestFile','OutputDir',
+ 'Parameters','Password','Permissions','PrivilegesRequired','ReserveBytes',
+ 'RestartIfNeededByRun','Root','RunOnceId','Section','SetupIconFile',
+ 'ShowComponentSizes','ShowLanguageDialog','ShowTasksTreeLines','SlicesPerDisk',
+ 'SolidCompression','Source','SourceDir','StatusMsg','Subkey','Tasks',
+ 'TimeStampRounding','TimeStampsInUTC','TouchDate','TouchTime','Type','Types',
+ 'UninstallDisplayIcon','UninstallDisplayName','UninstallFilesDir','UninstallIconFile',
+ 'UninstallLogMode','UninstallRestartComputer','UninstallStyle','Uninstallable',
+ 'UpdateUninstallLogAppName','UsePreviousAppDir','UsePreviousGroup',
+ 'UsePreviousTasks','UsePreviousSetupType','UsePreviousUserInfo',
+ 'UserInfoPage','UseSetupLdr','ValueData','ValueName','ValueType',
+ 'VersionInfoVersion','VersionInfoCompany','VersionInfoDescription','VersionInfoTextVersion',
+ 'WindowResizable','WindowShowCaption','WindowStartMaximized',
+ 'WindowVisible','WizardImageBackColor','WizardImageFile','WizardImageStretch','WizardSmallImageBackColor','WizardSmallImageFile','WizardStyle','WorkingDir'
+
+
+ ),
+ 4 => array(
+ 'AnsiChar', 'AnsiString', 'Boolean', 'Byte', 'Cardinal', 'Char', 'Comp', 'Currency', 'Double', 'Extended',
+ 'Int64', 'Integer', 'LongInt', 'LongWord', 'PAnsiChar', 'PAnsiString', 'PChar', 'PCurrency', 'PDateTime',
+ 'PExtended', 'PInt64', 'Pointer', 'PShortString', 'PString', 'PVariant', 'PWideChar', 'PWideString',
+ 'Real', 'Real48', 'ShortInt', 'ShortString', 'Single', 'SmallInt', 'String', 'TBits', 'TConvType', 'TDateTime',
+ 'Text', 'TextFile', 'TFloatFormat', 'TFormatSettings', 'TList', 'TObject', 'TOpenDialog', 'TPoint',
+ 'TPrintDialog', 'TRect', 'TReplaceFlags', 'TSaveDialog', 'TSearchRec', 'TStringList', 'TSysCharSet',
+ 'TThreadFunc', 'Variant', 'WideChar', 'WideString', 'Word'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000000; font-weight: bold;',/*bold Black*/
+ 2 => 'color: #000000;font-style: italic;',/*Black*/
+ 3 => 'color: #0000FF;',/*blue*/
+ 4 => 'color: #CC0000;'/*red*/
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #33FF00; font-style: italic;',
+ 'MULTI' => 'color: #33FF00; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #000000; font-weight: bold;',
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/java.php b/plugins/dokuwiki/inc/geshi/java.php
new file mode 100644
index 0000000..5ef9e9d
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/java.php
@@ -0,0 +1,1390 @@
+<?php
+/*************************************************************************************
+ * java.php
+ * --------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.19.2.5 $
+ * Date Started: 2004/07/10
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Java language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/28 (1.0.4)
+ * - Added instanceof keyword
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/08/05 (1.0.2)
+ * - Added URL support
+ * - Added keyword "this", as bugs in GeSHi class ironed out
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * - Added extra missed keywords
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Compact the class names like the first few have been
+ * and eliminate repeats
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Java',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => 'import'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'for', 'foreach', 'if', 'else', 'while', 'do',
+ 'switch', 'case'
+ ),
+ 2 => array(
+ 'null', 'return', 'false', 'final', 'true', 'public',
+ 'private', 'protected', 'extends', 'break', 'class',
+ 'new', 'try', 'catch', 'throws', 'finally', 'implements',
+ 'interface', 'throw', 'native', 'synchronized', 'this',
+ 'abstract', 'transient', 'instanceof', 'assert', 'continue',
+ 'default', 'enum', 'package', 'static', 'strictfp', 'super',
+ 'volatile', 'const', 'goto'
+ ),
+ 3 => array(
+ 'AbstractAction', 'AbstractBorder', 'AbstractButton', 'AbstractCellEditor',
+ 'AbstractCollection', 'AbstractColorChooserPanel', 'AbstractDocument',
+ 'AbstractDocument.AttributeContext', 'AbstractDocument.Content',
+ 'AbstractDocument.ElementEdit', 'AbstractLayoutCache',
+ 'AbstractLayoutCache.NodeDimensions', 'AbstractList', 'AbstractListModel',
+ 'AbstractMap', 'AbstractMethodError', 'AbstractSequentialList',
+ 'AbstractSet', 'AbstractTableModel', 'AbstractUndoableEdit', 'AbstractWriter',
+ 'AccessControlContext', 'AccessControlException', 'AccessController',
+ 'AccessException', 'Accessible', 'AccessibleAction', 'AccessibleBundle',
+ 'AccessibleComponent', 'AccessibleContext', 'AccessibleHyperlink',
+ 'AccessibleHypertext', 'AccessibleIcon', 'AccessibleObject',
+ 'AccessibleRelation', 'AccessibleRelationSet', 'AccessibleResourceBundle',
+ 'AccessibleRole', 'AccessibleSelection', 'AccessibleState',
+ 'AccessibleStateSet', 'AccessibleTable', 'AccessibleTableModelChange',
+ 'AccessibleText', 'AccessibleValue', 'Acl', 'AclEntry', 'AclNotFoundException',
+ 'Action', 'ActionEvent', 'ActionListener', 'ActionMap', 'ActionMapUIResource',
+ 'Activatable', 'ActivateFailedException', 'ActivationDesc',
+ 'ActivationException', 'ActivationGroup', 'ActivationGroupDesc',
+ 'ActivationGroupDesc.CommandEnvironment', 'ActivationGroupID', 'ActivationID',
+ 'ActivationInstantiator', 'ActivationMonitor', 'ActivationSystem',
+ 'Activator', 'ActiveEvent', 'Adjustable', 'AdjustmentEvent', 'AdjustmentListener',
+ 'Adler32', 'AffineTransform', 'AffineTransformOp', 'AlgorithmParameterGenerator',
+ 'AlgorithmParameterGeneratorSpi', 'AlgorithmParameters', 'AlgorithmParameterSpec',
+ 'AlgorithmParametersSpi', 'AllPermission', 'AlphaComposite', 'AlreadyBound',
+ 'AlreadyBoundException', 'AlreadyBoundHelper', 'AlreadyBoundHolder',
+ 'AncestorEvent', 'AncestorListener', 'Annotation', 'Any', 'AnyHolder',
+ 'AnySeqHelper', 'AnySeqHolder', 'Applet', 'AppletContext', 'AppletInitializer',
+ 'AppletStub', 'ApplicationException', 'Arc2D', 'Arc2D.Double', 'Arc2D.Float',
+ 'Area', 'AreaAveragingScaleFilter', 'ARG_IN', 'ARG_INOUT', 'ARG_OUT',
+ 'ArithmeticException', 'Array', 'ArrayIndexOutOfBoundsException',
+ 'ArrayList', 'Arrays', 'ArrayStoreException', 'AsyncBoxView',
+ 'Attribute', 'AttributedCharacterIterator', 'AttributedCharacterIterator.Attribute',
+ 'AttributedString', 'AttributeInUseException', 'AttributeList',
+ 'AttributeModificationException', 'Attributes', 'Attributes.Name',
+ 'AttributeSet', 'AttributeSet.CharacterAttribute', 'AttributeSet.ColorAttribute',
+ 'AttributeSet.FontAttribute', 'AttributeSet.ParagraphAttribute',
+ 'AudioClip', 'AudioFileFormat', 'AudioFileFormat.Type', 'AudioFileReader',
+ 'AudioFileWriter', 'AudioFormat', 'AudioFormat.Encoding', 'AudioInputStream',
+ 'AudioPermission', 'AudioSystem', 'AuthenticationException',
+ 'AuthenticationNotSupportedException', 'Authenticator', 'Autoscroll',
+ 'AWTError', 'AWTEvent', 'AWTEventListener', 'AWTEventMulticaster',
+ 'AWTException', 'AWTPermission', 'BAD_CONTEXT', 'BAD_INV_ORDER', 'BAD_OPERATION',
+ 'BAD_PARAM', 'BAD_POLICY', 'BAD_POLICY_TYPE', 'BAD_POLICY_VALUE', 'BAD_TYPECODE',
+ 'BadKind', 'BadLocationException', 'BandCombineOp', 'BandedSampleModel','BasicArrowButton',
+ 'BasicAttribute', 'BasicAttributes', 'BasicBorders', 'BasicBorders.ButtonBorder',
+ 'BasicBorders.FieldBorder', 'BasicBorders.MarginBorder', 'BasicBorders.MenuBarBorder',
+ 'BasicBorders.RadioButtonBorder', 'BasicBorders.SplitPaneBorder',
+ 'BasicBorders.ToggleButtonBorder', 'BasicButtonListener', 'BasicButtonUI',
+ 'BasicCheckBoxMenuItemUI', 'BasicCheckBoxUI', 'BasicColorChooserUI', 'BasicComboBoxEditor',
+ 'BasicComboBoxEditor.UIResource', 'BasicComboBoxRenderer', 'BasicComboBoxRenderer.UIResource',
+ 'BasicComboBoxUI', 'BasicComboPopup', 'BasicDesktopIconUI', 'BasicDesktopPaneUI',
+ 'BasicDirectoryModel', 'BasicEditorPaneUI', 'BasicFileChooserUI',
+ 'BasicGraphicsUtils', 'BasicHTML', 'BasicIconFactory', 'BasicInternalFrameTitlePane',
+ 'BasicInternalFrameUI', 'BasicLabelUI', 'BasicListUI', 'BasicLookAndFeel',
+ 'BasicMenuBarUI', 'BasicMenuItemUI', 'BasicMenuUI', 'BasicOptionPaneUI',
+ 'BasicOptionPaneUI.ButtonAreaLayout', 'BasicPanelUI', 'BasicPasswordFieldUI',
+ 'BasicPermission', 'BasicPopupMenuSeparatorUI', 'BasicPopupMenuUI',
+ 'BasicProgressBarUI', 'BasicRadioButtonMenuItemUI', 'BasicRadioButtonUI',
+ 'BasicRootPaneUI', 'BasicScrollBarUI', 'BasicScrollPaneUI', 'BasicSeparatorUI',
+ 'BasicSliderUI', 'BasicSplitPaneDivider', 'BasicSplitPaneUI', 'BasicStroke',
+ 'BasicTabbedPaneUI', 'BasicTableHeaderUI', 'BasicTableUI', 'BasicTextAreaUI',
+ 'BasicTextFieldUI', 'BasicTextPaneUI', 'BasicTextUI', 'BasicTextUI.BasicCaret',
+ 'BasicTextUI.BasicHighlighter', 'BasicToggleButtonUI', 'BasicToolBarSeparatorUI',
+ 'BasicToolBarUI', 'BasicToolTipUI', 'BasicTreeUI', 'BasicViewportUI',
+ 'BatchUpdateException', 'BeanContext', 'BeanContextChild',
+ 'BeanContextChildComponentProxy', 'BeanContextChildSupport', 'BeanContextContainerProxy',
+ 'BeanContextEvent', 'BeanContextMembershipEvent', 'BeanContextMembershipListener',
+ 'BeanContextProxy', 'BeanContextServiceAvailableEvent', 'BeanContextServiceProvider',
+ 'BeanContextServiceProviderBeanInfo', 'BeanContextServiceRevokedEvent',
+ 'BeanContextServiceRevokedListener', 'BeanContextServices',
+ 'BeanContextServicesListener', 'BeanContextServicesSupport',
+ 'BeanContextServicesSupport.BCSSServiceProvider', 'BeanContextSupport',
+ 'BeanContextSupport.BCSIterator', 'BeanDescriptor', 'BeanInfo', 'Beans',
+ 'BevelBorder', 'BigDecimal', 'BigInteger', 'BinaryRefAddr', 'BindException',
+ 'Binding', 'BindingHelper', 'BindingHolder', 'BindingIterator',
+ 'BindingIteratorHelper', 'BindingIteratorHolder', 'BindingIteratorOperations',
+ 'BindingListHelper', 'BindingListHolder', 'BindingType', 'BindingTypeHelper',
+ 'BindingTypeHolder', 'BitSet', 'Blob', 'BlockView', 'Book', 'Boolean',
+ 'BooleanControl', 'BooleanControl.Type', 'BooleanHolder', 'BooleanSeqHelper',
+ 'BooleanSeqHolder', 'Border', 'BorderFactory', 'BorderLayout', 'BorderUIResource',
+ 'BorderUIResource.BevelBorderUIResource', 'BorderUIResource.CompoundBorderUIResource',
+ 'BorderUIResource.EmptyBorderUIResource', 'BorderUIResource.EtchedBorderUIResource',
+ 'BorderUIResource.LineBorderUIResource', 'BorderUIResource.MatteBorderUIResource',
+ 'BorderUIResource.TitledBorderUIResource', 'BoundedRangeModel', 'Bounds',
+ 'Box', 'Box.Filler', 'BoxedValueHelper', 'BoxLayout', 'BoxView',
+ 'BreakIterator', 'BufferedImage', 'BufferedImageFilter', 'BufferedImageOp',
+ 'BufferedInputStream', 'BufferedOutputStream', 'BufferedReader', 'BufferedWriter',
+ 'Button', 'ButtonGroup', 'ButtonModel', 'ButtonUI', 'Byte', 'ByteArrayInputStream',
+ 'ByteArrayOutputStream', 'ByteHolder', 'ByteLookupTable', 'Calendar',
+ 'CallableStatement', 'CannotProceed', 'CannotProceedException', 'CannotProceedHelper',
+ 'CannotProceedHolder', 'CannotRedoException', 'CannotUndoException',
+ 'Canvas', 'CardLayout', 'Caret', 'CaretEvent', 'CaretListener', 'CellEditor',
+ 'CellEditorListener', 'CellRendererPane', 'Certificate', 'Certificate.CertificateRep',
+ 'CertificateEncodingException', 'CertificateException', 'CertificateExpiredException',
+ 'CertificateFactory', 'CertificateFactorySpi', 'CertificateNotYetValidException',
+ 'CertificateParsingException', 'ChangedCharSetException', 'ChangeEvent',
+ 'ChangeListener', 'Character', 'Character.Subset', 'Character.UnicodeBlock',
+ 'CharacterIterator', 'CharArrayReader', 'CharArrayWriter', 'CharConversionException',
+ 'CharHolder', 'CharSeqHelper', 'CharSeqHolder', 'Checkbox', 'CheckboxGroup',
+ 'CheckboxMenuItem', 'CheckedInputStream', 'CheckedOutputStream', 'Checksum',
+ 'Choice', 'ChoiceFormat', 'Class', 'ClassCastException', 'ClassCircularityError',
+ 'ClassDesc', 'ClassFormatError', 'ClassLoader', 'ClassNotFoundException',
+ 'Clip', 'Clipboard', 'ClipboardOwner', 'Clob', 'Cloneable', 'CloneNotSupportedException',
+ 'CMMException', 'CodeSource', 'CollationElementIterator', 'CollationKey',
+ 'Collator', 'Collection', 'Collections', 'Color', 'ColorChooserComponentFactory',
+ 'ColorChooserUI', 'ColorConvertOp', 'ColorModel', 'ColorSelectionModel',
+ 'ColorSpace', 'ColorUIResource', 'ComboBoxEditor', 'ComboBoxModel', 'ComboBoxUI',
+ 'ComboPopup', 'COMM_FAILURE', 'CommunicationException', 'Comparable',
+ 'Comparator', 'Compiler', 'CompletionStatus', 'CompletionStatusHelper',
+ 'Component', 'ComponentAdapter', 'ComponentColorModel', 'ComponentEvent',
+ 'ComponentInputMap', 'ComponentInputMapUIResource', 'ComponentListener',
+ 'ComponentOrientation', 'ComponentSampleModel', 'ComponentUI', 'ComponentView',
+ 'Composite', 'CompositeContext', 'CompositeName','CompositeView', 'CompoundBorder',
+ 'CompoundControl', 'CompoundControl.Type', 'CompoundEdit', 'CompoundName',
+ 'ConcurrentModificationException', 'ConfigurationException', 'ConnectException',
+ 'ConnectException', 'ConnectIOException', 'Connection', 'Constructor',
+ 'Container', 'ContainerAdapter', 'ContainerEvent', 'ContainerListener',
+ 'ContentHandler', 'ContentHandlerFactory', 'ContentModel', 'Context', 'ContextList',
+ 'ContextNotEmptyException', 'ContextualRenderedImageFactory', 'Control',
+ 'Control.Type', 'ControlFactory', 'ControllerEventListener', 'ConvolveOp',
+ 'CRC32', 'CRL', 'CRLException', 'CropImageFilter', 'CSS', 'CSS.Attribute',
+ 'CTX_RESTRICT_SCOPE', 'CubicCurve2D', 'CubicCurve2D.Double', 'CubicCurve2D.Float',
+ 'Current', 'CurrentHelper', 'CurrentHolder', 'CurrentOperations', 'Cursor',
+ 'Customizer', 'CustomMarshal', 'CustomValue', 'DATA_CONVERSION', 'DatabaseMetaData',
+ 'DataBuffer', 'DataBufferByte', 'DataBufferInt', 'DataBufferShort', 'DataBufferUShort',
+ 'DataFlavor', 'DataFormatException', 'DatagramPacket', 'DatagramSocket',
+ 'DatagramSocketImpl', 'DatagramSocketImplFactory', 'DataInput', 'DataInputStream',
+ 'DataLine', 'DataLine.Info', 'DataOutput', 'DataOutputStream', 'DataOutputStream',
+ 'DataTruncation', 'Date', 'DateFormat', 'DateFormatSymbols', 'DebugGraphics',
+ 'DecimalFormat', 'DecimalFormatSymbols', 'DefaultBoundedRangeModel',
+ 'DefaultButtonModel', 'DefaultCaret', 'DefaultCellEditor', 'DefaultColorSelectionModel',
+ 'DefaultComboBoxModel', 'DefaultDesktopManager', 'DefaultEditorKit',
+ 'DefaultEditorKit.BeepAction', 'DefaultEditorKit.CopyAction',
+ 'DefaultEditorKit.CutAction', 'DefaultEditorKit.DefaultKeyTypedAction',
+ 'DefaultEditorKit.InsertBreakAction', 'DefaultEditorKit.InsertContentAction',
+ 'DefaultEditorKit.InsertTabAction', 'DefaultEditorKit.PasteAction,',
+ 'DefaultFocusManager', 'DefaultHighlighter', 'DefaultHighlighter.DefaultHighlightPainter',
+ 'DefaultListCellRenderer', 'DefaultListCellRenderer.UIResource', 'DefaultListModel',
+ 'DefaultListSelectionModel', 'DefaultMenuLayout', 'DefaultMetalTheme',
+ 'DefaultMutableTreeNode', 'DefaultSingleSelectionModel', 'DefaultStyledDocument',
+ 'DefaultStyledDocument.AttributeUndoableEdit', 'DefaultStyledDocument.ElementSpec',
+ 'DefaultTableCellRenderer', 'DefaultTableCellRenderer.UIResource', 'DefaultTableColumnModel',
+ 'DefaultTableModel', 'DefaultTextUI', 'DefaultTreeCellEditor', 'DefaultTreeCellRenderer',
+ 'DefaultTreeModel', 'DefaultTreeSelectionModel', 'DefinitionKind', 'DefinitionKindHelper',
+ 'Deflater', 'DeflaterOutputStream', 'Delegate', 'DesignMode', 'DesktopIconUI',
+ 'DesktopManager', 'DesktopPaneUI', 'DGC', 'Dialog', 'Dictionary', 'DigestException',
+ 'DigestInputStream', 'DigestOutputStream', 'Dimension', 'Dimension2D',
+ 'DimensionUIResource', 'DirContext', 'DirectColorModel', 'DirectoryManager',
+ 'DirObjectFactory', 'DirStateFactory', 'DirStateFactory.Result', 'DnDConstants',
+ 'Document', 'DocumentEvent', 'DocumentEvent.ElementChange', 'DocumentEvent.EventType',
+ 'DocumentListener', 'DocumentParser', 'DomainCombiner', 'DomainManager',
+ 'DomainManagerOperations', 'Double', 'DoubleHolder', 'DoubleSeqHelper',
+ 'DoubleSeqHolder', 'DragGestureEvent', 'DragGestureListener', 'DragGestureRecognizer',
+ 'DragSource', 'DragSourceContext', 'DragSourceDragEvent', 'DragSourceDropEvent',
+ 'DragSourceEvent', 'DragSourceListener', 'Driver', 'DriverManager',
+ 'DriverPropertyInfo', 'DropTarget', 'DropTarget.DropTargetAutoScroller',
+ 'DropTargetContext', 'DropTargetDragEvent', 'DropTargetDropEvent',
+ 'DropTargetEvent', 'DropTargetListener', 'DSAKey', 'DSAKeyPairGenerator',
+ 'DSAParameterSpec', 'DSAParams', 'DSAPrivateKey', 'DSAPrivateKeySpec',
+ 'DSAPublicKey', 'DSAPublicKeySpec', 'DTD', 'DTDConstants', 'DynamicImplementation',
+ 'DynAny', 'DynArray', 'DynEnum', 'DynFixed', 'DynSequence', 'DynStruct',
+ 'DynUnion', 'DynValue', 'EditorKit', 'Element', 'ElementIterator', 'Ellipse2D',
+ 'Ellipse2D.Double', 'Ellipse2D.Float', 'EmptyBorder', 'EmptyStackException',
+ 'EncodedKeySpec', 'Entity', 'EnumControl', 'EnumControl.Type','Enumeration',
+ 'Environment', 'EOFException', 'Error', 'EtchedBorder', 'Event', 'EventContext',
+ 'EventDirContext', 'EventListener', 'EventListenerList', 'EventObject', 'EventQueue',
+ 'EventSetDescriptor', 'Exception', 'ExceptionInInitializerError', 'ExceptionList',
+ 'ExpandVetoException', 'ExportException', 'ExtendedRequest', 'ExtendedResponse',
+ 'Externalizable', 'FeatureDescriptor', 'Field', 'FieldNameHelper',
+ 'FieldPosition', 'FieldView', 'File', 'FileChooserUI', 'FileDescriptor',
+ 'FileDialog', 'FileFilter', 'FileFilter', 'FileInputStream', 'FilenameFilter',
+ 'FileNameMap', 'FileNotFoundException', 'FileOutputStream', 'FilePermission',
+ 'FileReader', 'FileSystemView', 'FileView', 'FileWriter', 'FilteredImageSource',
+ 'FilterInputStream', 'FilterOutputStream', 'FilterReader', 'FilterWriter',
+ 'FixedHeightLayoutCache', 'FixedHolder', 'FlatteningPathIterator', 'FlavorMap',
+ 'Float', 'FloatControl', 'FloatControl.Type', 'FloatHolder', 'FloatSeqHelper',
+ 'FloatSeqHolder', 'FlowLayout', 'FlowView', 'FlowView.FlowStrategy', 'FocusAdapter',
+ 'FocusEvent', 'FocusListener', 'FocusManager', 'Font', 'FontFormatException',
+ 'FontMetrics', 'FontRenderContext', 'FontUIResource', 'Format', 'FormatConversionProvider',
+ 'FormView', 'Frame', 'FREE_MEM', 'GapContent', 'GeneralPath', 'GeneralSecurityException',
+ 'GlyphJustificationInfo', 'GlyphMetrics', 'GlyphVector', 'GlyphView', 'GlyphView.GlyphPainter',
+ 'GradientPaint', 'GraphicAttribute', 'Graphics', 'Graphics2D', 'GraphicsConfigTemplate',
+ 'GraphicsConfiguration', 'GraphicsDevice', 'GraphicsEnvironment', 'GrayFilter',
+ 'GregorianCalendar', 'GridBagConstraints', 'GridBagLayout', 'GridLayout', 'Group', 'Guard',
+ 'GuardedObject', 'GZIPInputStream', 'GZIPOutputStream',
+ 'HasControls',
+ 'HashMap',
+ 'HashSet',
+ 'Hashtable',
+ 'HierarchyBoundsAdapter',
+ 'HierarchyBoundsListener',
+ 'HierarchyEvent',
+ 'HierarchyListener',
+ 'Highlighter',
+ 'Highlighter.Highlight',
+ 'Highlighter.HighlightPainter',
+ 'HTML',
+ 'HTML.Attribute',
+ 'HTML.Tag',
+ 'HTML.UnknownTag',
+ 'HTMLDocument',
+ 'HTMLDocument.Iterator',
+ 'HTMLEditorKit',
+ 'HTMLEditorKit.HTMLFactory',
+ 'HTMLEditorKit.HTMLTextAction',
+ 'HTMLEditorKit.InsertHTMLTextAction',
+ 'HTMLEditorKit.LinkController',
+ 'HTMLEditorKit.Parser',
+ 'HTMLEditorKit.ParserCallback',
+ 'HTMLFrameHyperlinkEvent',
+ 'HTMLWriter',
+ 'HttpURLConnection',
+ 'HyperlinkEvent',
+ 'HyperlinkEvent.EventType',
+ 'HyperlinkListener',
+ 'ICC_ColorSpace',
+ 'ICC_Profile',
+ 'ICC_ProfileGray',
+ 'ICC_ProfileRGB',
+ 'Icon',
+ 'IconUIResource',
+ 'IconView',
+ 'IdentifierHelper',
+ 'Identity',
+ 'IdentityScope',
+ 'IDLEntity',
+ 'IDLType',
+ 'IDLTypeHelper', 'IDLTypeOperations',
+ 'IllegalAccessError',
+ 'IllegalAccessException',
+ 'IllegalArgumentException',
+ 'IllegalComponentStateException',
+ 'IllegalMonitorStateException',
+ 'IllegalPathStateException',
+ 'IllegalStateException',
+ 'IllegalThreadStateException',
+ 'Image',
+ 'ImageConsumer',
+ 'ImageFilter',
+ 'ImageGraphicAttribute',
+ 'ImageIcon',
+ 'ImageObserver',
+ 'ImageProducer',
+ 'ImagingOpException',
+ 'IMP_LIMIT',
+ 'IncompatibleClassChangeError',
+ 'InconsistentTypeCode',
+ 'IndexColorModel',
+ 'IndexedPropertyDescriptor',
+ 'IndexOutOfBoundsException',
+ 'IndirectionException',
+ 'InetAddress',
+ 'Inflater',
+ 'InflaterInputStream',
+ 'InheritableThreadLocal',
+ 'InitialContext',
+ 'InitialContextFactory',
+ 'InitialContextFactoryBuilder',
+ 'InitialDirContext',
+ 'INITIALIZE',
+ 'Initializer',
+ 'InitialLdapContext',
+ 'InlineView',
+ 'InputContext',
+ 'InputEvent',
+ 'InputMap',
+ 'InputMapUIResource',
+ 'InputMethod',
+ 'InputMethodContext',
+ 'InputMethodDescriptor',
+ 'InputMethodEvent',
+ 'InputMethodHighlight',
+ 'InputMethodListener',
+ 'InputMethodRequests',
+ 'InputStream',
+ 'InputStream',
+ 'InputStream',
+ 'InputStreamReader',
+ 'InputSubset',
+ 'InputVerifier',
+ 'Insets',
+ 'InsetsUIResource',
+ 'InstantiationError',
+ 'InstantiationException',
+ 'Instrument',
+ 'InsufficientResourcesException',
+ 'Integer',
+ 'INTERNAL',
+ 'InternalError', 'InternalFrameAdapter',
+ 'InternalFrameEvent',
+ 'InternalFrameListener',
+ 'InternalFrameUI',
+ 'InterruptedException',
+ 'InterruptedIOException',
+ 'InterruptedNamingException',
+ 'INTF_REPOS',
+ 'IntHolder',
+ 'IntrospectionException',
+ 'Introspector',
+ 'INV_FLAG',
+ 'INV_IDENT',
+ 'INV_OBJREF',
+ 'INV_POLICY',
+ 'Invalid',
+ 'INVALID_TRANSACTION',
+ 'InvalidAlgorithmParameterException',
+ 'InvalidAttributeIdentifierException',
+ 'InvalidAttributesException',
+ 'InvalidAttributeValueException',
+ 'InvalidClassException',
+ 'InvalidDnDOperationException',
+ 'InvalidKeyException',
+ 'InvalidKeySpecException',
+ 'InvalidMidiDataException',
+ 'InvalidName',
+ 'InvalidName',
+ 'InvalidNameException',
+ 'InvalidNameHelper',
+ 'InvalidNameHolder',
+ 'InvalidObjectException',
+ 'InvalidParameterException',
+ 'InvalidParameterSpecException',
+ 'InvalidSearchControlsException',
+ 'InvalidSearchFilterException',
+ 'InvalidSeq',
+ 'InvalidTransactionException',
+ 'InvalidValue',
+ 'InvocationEvent',
+ 'InvocationHandler',
+ 'InvocationTargetException',
+ 'InvokeHandler',
+ 'IOException',
+ 'IRObject',
+ 'IRObjectOperations', 'IstringHelper', 'ItemEvent', 'ItemListener',
+ 'ItemSelectable', 'Iterator', 'JApplet', 'JarEntry', 'JarException',
+ 'JarFile', 'JarInputStream', 'JarOutputStream', 'JarURLConnection',
+ 'JButton', 'JCheckBox', 'JCheckBoxMenuItem', 'JColorChooser',
+ 'JComboBox',
+ 'JComboBox.KeySelectionManager',
+ 'JComponent',
+ 'JDesktopPane',
+ 'JDialog',
+ 'JEditorPane',
+ 'JFileChooser',
+ 'JFrame',
+ 'JInternalFrame',
+ 'JInternalFrame.JDesktopIcon',
+ 'JLabel',
+ 'JLayeredPane',
+ 'JList',
+ 'JMenu',
+ 'JMenuBar',
+ 'JMenuItem',
+ 'JobAttributes',
+ 'JobAttributes.DefaultSelectionType',
+ 'JobAttributes.DestinationType',
+ 'JobAttributes.DialogType',
+ 'JobAttributes.MultipleDocumentHandlingType',
+ 'JobAttributes.SidesType',
+ 'JOptionPane',
+ 'JPanel',
+ 'JPasswordField',
+ 'JPopupMenu',
+ 'JPopupMenu.Separator',
+ 'JProgressBar',
+ 'JRadioButton',
+ 'JRadioButtonMenuItem',
+ 'JRootPane',
+ 'JScrollBar',
+ 'JScrollPane',
+ 'JSeparator',
+ 'JSlider',
+ 'JSplitPane',
+ 'JTabbedPane',
+ 'JTable',
+ 'JTableHeader',
+ 'JTextArea',
+ 'JTextComponent',
+ 'JTextComponent.KeyBinding', 'JTextField',
+ 'JTextPane',
+ 'JToggleButton',
+ 'JToggleButton.ToggleButtonModel',
+ 'JToolBar',
+ 'JToolBar.Separator',
+ 'JToolTip',
+ 'JTree',
+ 'JTree.DynamicUtilTreeNode',
+ 'JTree.EmptySelectionModel',
+ 'JViewport',
+ 'JWindow',
+ 'Kernel',
+ 'Key',
+ 'KeyAdapter',
+ 'KeyEvent',
+ 'KeyException',
+ 'KeyFactory',
+ 'KeyFactorySpi',
+ 'KeyListener',
+ 'KeyManagementException',
+ 'Keymap',
+ 'KeyPair',
+ 'KeyPairGenerator',
+ 'KeyPairGeneratorSpi',
+ 'KeySpec',
+ 'KeyStore',
+ 'KeyStoreException',
+ 'KeyStoreSpi',
+ 'KeyStroke',
+ 'Label',
+ 'LabelUI',
+ 'LabelView',
+ 'LastOwnerException',
+ 'LayeredHighlighter',
+ 'LayeredHighlighter.LayerPainter',
+ 'LayoutManager',
+ 'LayoutManager2',
+ 'LayoutQueue',
+ 'LdapContext',
+ 'LdapReferralException',
+ 'Lease',
+ 'LimitExceededException',
+ 'Line',
+ 'Line.Info',
+ 'Line2D',
+ 'Line2D.Double',
+ 'Line2D.Float',
+ 'LineBorder',
+ 'LineBreakMeasurer',
+ 'LineEvent',
+ 'LineEvent.Type',
+ 'LineListener',
+ 'LineMetrics',
+ 'LineNumberInputStream',
+ 'LineNumberReader',
+ 'LineUnavailableException',
+ 'LinkageError',
+ 'LinkedList',
+ 'LinkException',
+ 'LinkLoopException',
+ 'LinkRef',
+ 'List',
+ 'List',
+ 'ListCellRenderer',
+ 'ListDataEvent',
+ 'ListDataListener',
+ 'ListIterator',
+ 'ListModel',
+ 'ListResourceBundle',
+ 'ListSelectionEvent',
+ 'ListSelectionListener',
+ 'ListSelectionModel',
+ 'ListUI',
+ 'ListView',
+ 'LoaderHandler',
+ 'Locale',
+ 'LocateRegistry',
+ 'LogStream',
+ 'Long',
+ 'LongHolder',
+ 'LongLongSeqHelper',
+ 'LongLongSeqHolder',
+ 'LongSeqHelper',
+ 'LongSeqHolder',
+ 'LookAndFeel',
+ 'LookupOp',
+ 'LookupTable',
+ 'MalformedLinkException',
+ 'MalformedURLException',
+ 'Manifest', 'Map',
+ 'Map.Entry',
+ 'MARSHAL',
+ 'MarshalException',
+ 'MarshalledObject',
+ 'Math',
+ 'MatteBorder',
+ 'MediaTracker',
+ 'Member',
+ 'MemoryImageSource',
+ 'Menu',
+ 'MenuBar',
+ 'MenuBarUI',
+ 'MenuComponent',
+ 'MenuContainer',
+ 'MenuDragMouseEvent',
+ 'MenuDragMouseListener',
+ 'MenuElement',
+ 'MenuEvent',
+ 'MenuItem',
+ 'MenuItemUI',
+ 'MenuKeyEvent',
+ 'MenuKeyListener',
+ 'MenuListener',
+ 'MenuSelectionManager',
+ 'MenuShortcut',
+ 'MessageDigest',
+ 'MessageDigestSpi',
+ 'MessageFormat',
+ 'MetaEventListener',
+ 'MetalBorders',
+ 'MetalBorders.ButtonBorder',
+ 'MetalBorders.Flush3DBorder',
+ 'MetalBorders.InternalFrameBorder',
+ 'MetalBorders.MenuBarBorder',
+ 'MetalBorders.MenuItemBorder',
+ 'MetalBorders.OptionDialogBorder',
+ 'MetalBorders.PaletteBorder',
+ 'MetalBorders.PopupMenuBorder',
+ 'MetalBorders.RolloverButtonBorder',
+ 'MetalBorders.ScrollPaneBorder',
+ 'MetalBorders.TableHeaderBorder',
+ 'MetalBorders.TextFieldBorder',
+ 'MetalBorders.ToggleButtonBorder',
+ 'MetalBorders.ToolBarBorder',
+ 'MetalButtonUI',
+ 'MetalCheckBoxIcon',
+ 'MetalCheckBoxUI',
+ 'MetalComboBoxButton',
+ 'MetalComboBoxEditor',
+ 'MetalComboBoxEditor.UIResource',
+ 'MetalComboBoxIcon',
+ 'MetalComboBoxUI',
+ 'MetalDesktopIconUI',
+ 'MetalFileChooserUI',
+ 'MetalIconFactory',
+ 'MetalIconFactory.FileIcon16',
+ 'MetalIconFactory.FolderIcon16',
+ 'MetalIconFactory.PaletteCloseIcon',
+ 'MetalIconFactory.TreeControlIcon',
+ 'MetalIconFactory.TreeFolderIcon',
+ 'MetalIconFactory.TreeLeafIcon',
+ 'MetalInternalFrameTitlePane',
+ 'MetalInternalFrameUI',
+ 'MetalLabelUI',
+ 'MetalLookAndFeel',
+ 'MetalPopupMenuSeparatorUI',
+ 'MetalProgressBarUI',
+ 'MetalRadioButtonUI',
+ 'MetalScrollBarUI',
+ 'MetalScrollButton',
+ 'MetalScrollPaneUI',
+ 'MetalSeparatorUI',
+ 'MetalSliderUI',
+ 'MetalSplitPaneUI',
+ 'MetalTabbedPaneUI',
+ 'MetalTextFieldUI',
+ 'MetalTheme',
+ 'MetalToggleButtonUI',
+ 'MetalToolBarUI',
+ 'MetalToolTipUI',
+ 'MetalTreeUI',
+ 'MetaMessage',
+ 'Method',
+ 'MethodDescriptor',
+ 'MidiChannel',
+ 'MidiDevice',
+ 'MidiDevice.Info',
+ 'MidiDeviceProvider',
+ 'MidiEvent',
+ 'MidiFileFormat',
+ 'MidiFileReader',
+ 'MidiFileWriter',
+ 'MidiMessage',
+ 'MidiSystem',
+ 'MidiUnavailableException',
+ 'MimeTypeParseException',
+ 'MinimalHTMLWriter',
+ 'MissingResourceException',
+ 'Mixer',
+ 'Mixer.Info',
+ 'MixerProvider',
+ 'ModificationItem',
+ 'Modifier',
+ 'MouseAdapter',
+ 'MouseDragGestureRecognizer',
+ 'MouseEvent',
+ 'MouseInputAdapter',
+ 'MouseInputListener',
+ 'MouseListener',
+ 'MouseMotionAdapter',
+ 'MouseMotionListener',
+ 'MultiButtonUI',
+ 'MulticastSocket',
+ 'MultiColorChooserUI',
+ 'MultiComboBoxUI',
+ 'MultiDesktopIconUI',
+ 'MultiDesktopPaneUI',
+ 'MultiFileChooserUI',
+ 'MultiInternalFrameUI',
+ 'MultiLabelUI', 'MultiListUI',
+ 'MultiLookAndFeel',
+ 'MultiMenuBarUI',
+ 'MultiMenuItemUI',
+ 'MultiOptionPaneUI',
+ 'MultiPanelUI',
+ 'MultiPixelPackedSampleModel',
+ 'MultipleMaster',
+ 'MultiPopupMenuUI',
+ 'MultiProgressBarUI',
+ 'MultiScrollBarUI',
+ 'MultiScrollPaneUI',
+ 'MultiSeparatorUI',
+ 'MultiSliderUI',
+ 'MultiSplitPaneUI',
+ 'MultiTabbedPaneUI',
+ 'MultiTableHeaderUI',
+ 'MultiTableUI',
+ 'MultiTextUI',
+ 'MultiToolBarUI',
+ 'MultiToolTipUI',
+ 'MultiTreeUI',
+ 'MultiViewportUI',
+ 'MutableAttributeSet',
+ 'MutableComboBoxModel',
+ 'MutableTreeNode',
+ 'Name',
+ 'NameAlreadyBoundException',
+ 'NameClassPair',
+ 'NameComponent',
+ 'NameComponentHelper',
+ 'NameComponentHolder',
+ 'NamedValue',
+ 'NameHelper',
+ 'NameHolder',
+ 'NameNotFoundException',
+ 'NameParser',
+ 'NamespaceChangeListener',
+ 'NameValuePair',
+ 'NameValuePairHelper',
+ 'Naming',
+ 'NamingContext',
+ 'NamingContextHelper',
+ 'NamingContextHolder',
+ 'NamingContextOperations',
+ 'NamingEnumeration',
+ 'NamingEvent',
+ 'NamingException',
+ 'NamingExceptionEvent',
+ 'NamingListener',
+ 'NamingManager',
+ 'NamingSecurityException',
+ 'NegativeArraySizeException',
+ 'NetPermission',
+ 'NO_IMPLEMENT',
+ 'NO_MEMORY',
+ 'NO_PERMISSION',
+ 'NO_RESOURCES',
+ 'NO_RESPONSE',
+ 'NoClassDefFoundError',
+ 'NoInitialContextException', 'NoninvertibleTransformException',
+ 'NoPermissionException',
+ 'NoRouteToHostException',
+ 'NoSuchAlgorithmException',
+ 'NoSuchAttributeException',
+ 'NoSuchElementException',
+ 'NoSuchFieldError',
+ 'NoSuchFieldException',
+ 'NoSuchMethodError',
+ 'NoSuchMethodException',
+ 'NoSuchObjectException',
+ 'NoSuchProviderException',
+ 'NotActiveException',
+ 'NotBoundException',
+ 'NotContextException',
+ 'NotEmpty',
+ 'NotEmptyHelper',
+ 'NotEmptyHolder',
+ 'NotFound',
+ 'NotFoundHelper',
+ 'NotFoundHolder',
+ 'NotFoundReason',
+ 'NotFoundReasonHelper',
+ 'NotFoundReasonHolder',
+ 'NotOwnerException',
+ 'NotSerializableException',
+ 'NullPointerException',
+ 'Number',
+ 'NumberFormat', 'NumberFormatException', 'NVList',
+ 'OBJ_ADAPTER', 'Object', 'OBJECT_NOT_EXIST', 'ObjectChangeListener',
+ 'ObjectFactory',
+ 'ObjectFactoryBuilder',
+ 'ObjectHelper',
+ 'ObjectHolder',
+ 'ObjectImpl', 'ObjectImpl',
+ 'ObjectInput',
+ 'ObjectInputStream',
+ 'ObjectInputStream.GetField',
+ 'ObjectInputValidation',
+ 'ObjectOutput',
+ 'ObjectOutputStream',
+ 'ObjectOutputStream.PutField',
+ 'ObjectStreamClass',
+ 'ObjectStreamConstants',
+ 'ObjectStreamException',
+ 'ObjectStreamField',
+ 'ObjectView',
+ 'ObjID',
+ 'Observable',
+ 'Observer',
+ 'OctetSeqHelper',
+ 'OctetSeqHolder',
+ 'OMGVMCID',
+ 'OpenType',
+ 'Operation',
+ 'OperationNotSupportedException',
+ 'Option',
+ 'OptionalDataException',
+ 'OptionPaneUI',
+ 'ORB',
+ 'OutOfMemoryError',
+ 'OutputStream',
+ 'OutputStreamWriter',
+ 'OverlayLayout',
+ 'Owner',
+ 'Package',
+ 'PackedColorModel',
+ 'Pageable',
+ 'PageAttributes',
+ 'PageAttributes.ColorType',
+ 'PageAttributes.MediaType',
+ 'PageAttributes.OrientationRequestedType',
+ 'PageAttributes.OriginType',
+ 'PageAttributes.PrintQualityType',
+ 'PageFormat',
+ 'Paint',
+ 'PaintContext',
+ 'PaintEvent',
+ 'Panel',
+ 'PanelUI',
+ 'Paper',
+ 'ParagraphView',
+ 'ParagraphView',
+ 'ParameterBlock',
+ 'ParameterDescriptor',
+ 'ParseException',
+ 'ParsePosition',
+ 'Parser',
+ 'ParserDelegator',
+ 'PartialResultException',
+ 'PasswordAuthentication',
+ 'PasswordView',
+ 'Patch',
+ 'PathIterator',
+ 'Permission',
+ 'Permission',
+ 'PermissionCollection',
+ 'Permissions',
+ 'PERSIST_STORE',
+ 'PhantomReference',
+ 'PipedInputStream',
+ 'PipedOutputStream',
+ 'PipedReader',
+ 'PipedWriter',
+ 'PixelGrabber',
+ 'PixelInterleavedSampleModel',
+ 'PKCS8EncodedKeySpec',
+ 'PlainDocument',
+ 'PlainView',
+ 'Point',
+ 'Point2D',
+ 'Point2D.Double',
+ 'Point2D.Float',
+ 'Policy',
+ 'Policy',
+ 'PolicyError',
+ 'PolicyHelper',
+ 'PolicyHolder',
+ 'PolicyListHelper',
+ 'PolicyListHolder',
+ 'PolicyOperations', 'PolicyTypeHelper',
+ 'Polygon',
+ 'PopupMenu',
+ 'PopupMenuEvent',
+ 'PopupMenuListener',
+ 'PopupMenuUI',
+ 'Port',
+ 'Port.Info',
+ 'PortableRemoteObject',
+ 'PortableRemoteObjectDelegate',
+ 'Position',
+ 'Position.Bias',
+ 'PreparedStatement',
+ 'Principal',
+ 'Principal',
+ 'PrincipalHolder',
+ 'Printable',
+ 'PrinterAbortException',
+ 'PrinterException',
+ 'PrinterGraphics',
+ 'PrinterIOException',
+ 'PrinterJob',
+ 'PrintGraphics',
+ 'PrintJob',
+ 'PrintStream',
+ 'PrintWriter',
+ 'PRIVATE_MEMBER',
+ 'PrivateKey',
+ 'PrivilegedAction',
+ 'PrivilegedActionException',
+ 'PrivilegedExceptionAction',
+ 'Process',
+ 'ProfileDataException',
+ 'ProgressBarUI',
+ 'ProgressMonitor',
+ 'ProgressMonitorInputStream',
+ 'Properties',
+ 'PropertyChangeEvent',
+ 'PropertyChangeListener',
+ 'PropertyChangeSupport',
+ 'PropertyDescriptor',
+ 'PropertyEditor',
+ 'PropertyEditorManager',
+ 'PropertyEditorSupport',
+ 'PropertyPermission',
+ 'PropertyResourceBundle',
+ 'PropertyVetoException',
+ 'ProtectionDomain',
+ 'ProtocolException',
+ 'Provider',
+ 'ProviderException',
+ 'Proxy',
+ 'PUBLIC_MEMBER',
+ 'PublicKey',
+ 'PushbackInputStream',
+ 'PushbackReader',
+ 'QuadCurve2D',
+ 'QuadCurve2D.Double',
+ 'QuadCurve2D.Float',
+ 'Random',
+ 'RandomAccessFile', 'Raster', 'RasterFormatException', 'RasterOp',
+ 'Reader', 'Receiver', 'Rectangle', 'Rectangle2D', 'Rectangle2D.Double',
+ 'Rectangle2D.Float', 'RectangularShape', 'Ref', 'RefAddr', 'Reference',
+ 'Referenceable', 'ReferenceQueue', 'ReferralException',
+ 'ReflectPermission', 'Registry', 'RegistryHandler', 'RemarshalException',
+ 'Remote', 'RemoteCall', 'RemoteException', 'RemoteObject', 'RemoteRef',
+ 'RemoteServer',
+ 'RemoteStub',
+ 'RenderableImage',
+ 'RenderableImageOp',
+ 'RenderableImageProducer',
+ 'RenderContext',
+ 'RenderedImage',
+ 'RenderedImageFactory',
+ 'Renderer',
+ 'RenderingHints',
+ 'RenderingHints.Key',
+ 'RepaintManager',
+ 'ReplicateScaleFilter',
+ 'Repository',
+ 'RepositoryIdHelper',
+ 'Request',
+ 'RescaleOp',
+ 'Resolver',
+ 'ResolveResult',
+ 'ResourceBundle',
+ 'ResponseHandler',
+ 'ResultSet',
+ 'ResultSetMetaData',
+ 'ReverbType',
+ 'RGBImageFilter',
+ 'RMIClassLoader',
+ 'RMIClientSocketFactory',
+ 'RMIFailureHandler',
+ 'RMISecurityException',
+ 'RMISecurityManager',
+ 'RMIServerSocketFactory',
+ 'RMISocketFactory',
+ 'Robot',
+ 'RootPaneContainer',
+ 'RootPaneUI',
+ 'RoundRectangle2D',
+ 'RoundRectangle2D.Double',
+ 'RoundRectangle2D.Float',
+ 'RowMapper',
+ 'RSAKey',
+ 'RSAKeyGenParameterSpec',
+ 'RSAPrivateCrtKey',
+ 'RSAPrivateCrtKeySpec',
+ 'RSAPrivateKey',
+ 'RSAPrivateKeySpec',
+ 'RSAPublicKey',
+ 'RSAPublicKeySpec',
+ 'RTFEditorKit',
+ 'RuleBasedCollator',
+ 'Runnable',
+ 'Runtime',
+ 'RunTime',
+ 'RuntimeException',
+ 'RunTimeOperations',
+ 'RuntimePermission',
+ 'SampleModel',
+ 'SchemaViolationException',
+ 'Scrollable',
+ 'Scrollbar',
+ 'ScrollBarUI',
+ 'ScrollPane',
+ 'ScrollPaneConstants',
+ 'ScrollPaneLayout',
+ 'ScrollPaneLayout.UIResource',
+ 'ScrollPaneUI',
+ 'SearchControls',
+ 'SearchResult',
+ 'SecureClassLoader',
+ 'SecureRandom',
+ 'SecureRandomSpi',
+ 'Security',
+ 'SecurityException',
+ 'SecurityManager',
+ 'SecurityPermission',
+ 'Segment',
+ 'SeparatorUI',
+ 'Sequence',
+ 'SequenceInputStream',
+ 'Sequencer',
+ 'Sequencer.SyncMode',
+ 'Serializable',
+ 'SerializablePermission',
+ 'ServantObject',
+ 'ServerCloneException',
+ 'ServerError', 'ServerException',
+ 'ServerNotActiveException',
+ 'ServerRef',
+ 'ServerRequest',
+ 'ServerRuntimeException',
+ 'ServerSocket',
+ 'ServiceDetail',
+ 'ServiceDetailHelper',
+ 'ServiceInformation',
+ 'ServiceInformationHelper',
+ 'ServiceInformationHolder',
+ 'ServiceUnavailableException',
+ 'Set',
+ 'SetOverrideType',
+ 'SetOverrideTypeHelper',
+ 'Shape',
+ 'ShapeGraphicAttribute',
+ 'Short',
+ 'ShortHolder',
+ 'ShortLookupTable',
+ 'ShortMessage',
+ 'ShortSeqHelper',
+ 'ShortSeqHolder',
+ 'Signature',
+ 'SignatureException',
+ 'SignatureSpi',
+ 'SignedObject',
+ 'Signer',
+ 'SimpleAttributeSet',
+ 'SimpleBeanInfo',
+ 'SimpleDateFormat',
+ 'SimpleTimeZone',
+ 'SinglePixelPackedSampleModel',
+ 'SingleSelectionModel',
+ 'SizeLimitExceededException',
+ 'SizeRequirements',
+ 'SizeSequence',
+ 'Skeleton',
+ 'SkeletonMismatchException',
+ 'SkeletonNotFoundException',
+ 'SliderUI',
+ 'Socket',
+ 'SocketException',
+ 'SocketImpl',
+ 'SocketImplFactory',
+ 'SocketOptions',
+ 'SocketPermission',
+ 'SocketSecurityException',
+ 'SoftBevelBorder',
+ 'SoftReference',
+ 'SortedMap',
+ 'SortedSet',
+ 'Soundbank',
+ 'SoundbankReader',
+ 'SoundbankResource',
+ 'SourceDataLine',
+ 'SplitPaneUI',
+ 'SQLData',
+ 'SQLException',
+ 'SQLInput',
+ 'SQLOutput', 'SQLPermission',
+ 'SQLWarning',
+ 'Stack',
+ 'StackOverflowError',
+ 'StateEdit',
+ 'StateEditable',
+ 'StateFactory',
+ 'Statement',
+ 'Streamable',
+ 'StreamableValue',
+ 'StreamCorruptedException',
+ 'StreamTokenizer',
+ 'StrictMath',
+ 'String',
+ 'StringBuffer',
+ 'StringBufferInputStream',
+ 'StringCharacterIterator',
+ 'StringContent',
+ 'StringHolder',
+ 'StringIndexOutOfBoundsException',
+ 'StringReader',
+ 'StringRefAddr',
+ 'StringSelection',
+ 'StringTokenizer',
+ 'StringValueHelper',
+ 'StringWriter',
+ 'Stroke',
+ 'Struct',
+ 'StructMember',
+ 'StructMemberHelper',
+ 'Stub',
+ 'StubDelegate',
+ 'StubNotFoundException',
+ 'Style',
+ 'StyleConstants',
+ 'StyleConstants.CharacterConstants',
+ 'StyleConstants.ColorConstants',
+ 'StyleConstants.FontConstants',
+ 'StyleConstants.ParagraphConstants',
+ 'StyleContext',
+ 'StyledDocument',
+ 'StyledEditorKit',
+ 'StyledEditorKit.AlignmentAction',
+ 'StyledEditorKit.BoldAction',
+ 'StyledEditorKit.FontFamilyAction',
+ 'StyledEditorKit.FontSizeAction',
+ 'StyledEditorKit.ForegroundAction',
+ 'StyledEditorKit.ItalicAction',
+ 'StyledEditorKit.StyledTextAction',
+ 'StyledEditorKit.UnderlineAction',
+ 'StyleSheet',
+ 'StyleSheet.BoxPainter',
+ 'StyleSheet.ListPainter',
+ 'SwingConstants',
+ 'SwingPropertyChangeSupport',
+ 'SwingUtilities',
+ 'SyncFailedException',
+ 'Synthesizer',
+ 'SysexMessage',
+ 'System',
+ 'SystemColor', 'SystemException',
+ 'SystemFlavorMap',
+ 'TabableView',
+ 'TabbedPaneUI',
+ 'TabExpander',
+ 'TableCellEditor',
+ 'TableCellRenderer',
+ 'TableColumn',
+ 'TableColumnModel',
+ 'TableColumnModelEvent',
+ 'TableColumnModelListener',
+ 'TableHeaderUI',
+ 'TableModel',
+ 'TableModelEvent',
+ 'TableModelListener',
+ 'TableUI',
+ 'TableView',
+ 'TabSet',
+ 'TabStop',
+ 'TagElement',
+ 'TargetDataLine',
+ 'TCKind',
+ 'TextAction',
+ 'TextArea',
+ 'TextAttribute',
+ 'TextComponent',
+ 'TextEvent',
+ 'TextField',
+ 'TextHitInfo',
+ 'TextLayout',
+ 'TextLayout.CaretPolicy',
+ 'TextListener',
+ 'TextMeasurer',
+ 'TextUI',
+ 'TexturePaint',
+ 'Thread',
+ 'ThreadDeath',
+ 'ThreadGroup',
+ 'ThreadLocal',
+ 'Throwable',
+ 'Tie',
+ 'TileObserver',
+ 'Time',
+ 'TimeLimitExceededException',
+ 'Timer',
+ 'Timer',
+ 'TimerTask',
+ 'Timestamp',
+ 'TimeZone',
+ 'TitledBorder',
+ 'ToolBarUI',
+ 'Toolkit',
+ 'ToolTipManager',
+ 'ToolTipUI',
+ 'TooManyListenersException',
+ 'Track',
+ 'TRANSACTION_REQUIRED',
+ 'TRANSACTION_ROLLEDBACK',
+ 'TransactionRequiredException',
+ 'TransactionRolledbackException',
+ 'Transferable',
+ 'TransformAttribute',
+ 'TRANSIENT',
+ 'Transmitter',
+ 'Transparency',
+ 'TreeCellEditor',
+ 'TreeCellRenderer',
+ 'TreeExpansionEvent',
+ 'TreeExpansionListener',
+ 'TreeMap',
+ 'TreeModel',
+ 'TreeModelEvent',
+ 'TreeModelListener',
+ 'TreeNode',
+ 'TreePath',
+ 'TreeSelectionEvent',
+ 'TreeSelectionListener',
+ 'TreeSelectionModel',
+ 'TreeSet',
+ 'TreeUI',
+ 'TreeWillExpandListener',
+ 'TypeCode',
+ 'TypeCodeHolder',
+ 'TypeMismatch',
+ 'Types',
+ 'UID',
+ 'UIDefaults',
+ 'UIDefaults.ActiveValue',
+ 'UIDefaults.LazyInputMap',
+ 'UIDefaults.LazyValue',
+ 'UIDefaults.ProxyLazyValue', 'UIManager',
+ 'UIManager.LookAndFeelInfo',
+ 'UIResource',
+ 'ULongLongSeqHelper',
+ 'ULongLongSeqHolder',
+ 'ULongSeqHelper',
+ 'ULongSeqHolder',
+ 'UndeclaredThrowableException',
+ 'UndoableEdit',
+ 'UndoableEditEvent',
+ 'UndoableEditListener',
+ 'UndoableEditSupport',
+ 'UndoManager',
+ 'UnexpectedException',
+ 'UnicastRemoteObject',
+ 'UnionMember',
+ 'UnionMemberHelper',
+ 'UNKNOWN',
+ 'UnknownError',
+ 'UnknownException',
+ 'UnknownGroupException',
+ 'UnknownHostException',
+ 'UnknownHostException',
+ 'UnknownObjectException',
+ 'UnknownServiceException',
+ 'UnknownUserException',
+ 'UnmarshalException',
+ 'UnrecoverableKeyException',
+ 'Unreferenced',
+ 'UnresolvedPermission',
+ 'UnsatisfiedLinkError',
+ 'UnsolicitedNotification',
+ 'UnsolicitedNotificationEvent',
+ 'UnsolicitedNotificationListener',
+ 'UNSUPPORTED_POLICY',
+ 'UNSUPPORTED_POLICY_VALUE',
+ 'UnsupportedAudioFileException',
+ 'UnsupportedClassVersionError',
+ 'UnsupportedEncodingException',
+ 'UnsupportedFlavorException',
+ 'UnsupportedLookAndFeelException',
+ 'UnsupportedOperationException',
+ 'URL',
+ 'URLClassLoader',
+ 'URLConnection',
+ 'URLDecoder',
+ 'URLEncoder',
+ 'URLStreamHandler',
+ 'URLStreamHandlerFactory',
+ 'UserException',
+ 'UShortSeqHelper',
+ 'UShortSeqHolder',
+ 'UTFDataFormatException',
+ 'Util',
+ 'UtilDelegate',
+ 'Utilities',
+ 'ValueBase',
+ 'ValueBaseHelper',
+ 'ValueBaseHolder',
+ 'ValueFactory',
+ 'ValueHandler',
+ 'ValueMember',
+ 'ValueMemberHelper',
+ 'VariableHeightLayoutCache',
+ 'Vector',
+ 'VerifyError',
+ 'VersionSpecHelper',
+ 'VetoableChangeListener',
+ 'VetoableChangeSupport',
+ 'View',
+ 'ViewFactory',
+ 'ViewportLayout',
+ 'ViewportUI',
+ 'VirtualMachineError',
+ 'Visibility',
+ 'VisibilityHelper',
+ 'VM_ABSTRACT',
+ 'VM_CUSTOM',
+ 'VM_NONE',
+ 'VM_TRUNCATABLE',
+ 'VMID',
+ 'VoiceStatus',
+ 'Void',
+ 'WCharSeqHelper',
+ 'WCharSeqHolder',
+ 'WeakHashMap',
+ 'WeakReference',
+ 'Window',
+ 'WindowAdapter',
+ 'WindowConstants',
+ 'WindowEvent', 'WindowListener',
+ 'WrappedPlainView',
+ 'WritableRaster',
+ 'WritableRenderedImage',
+ 'WriteAbortedException',
+ 'Writer',
+ 'WrongTransaction',
+ 'WStringValueHelper',
+ 'X509Certificate',
+ 'X509CRL',
+ 'X509CRLEntry',
+ 'X509EncodedKeySpec',
+ 'X509Extension',
+ 'ZipEntry',
+ 'ZipException',
+ 'ZipFile',
+ 'ZipInputStream',
+ 'ZipOutputStream',
+ 'ZoneView',
+ '_BindingIteratorImplBase',
+ '_BindingIteratorStub',
+ '_IDLTypeStub',
+ '_NamingContextImplBase',
+ '_NamingContextStub',
+ '_PolicyStub',
+ '_Remote_Stub '
+ ),
+ 4 => array(
+ 'void', 'double', 'int', 'boolean', 'byte', 'short', 'long', 'char', 'float'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '*', '&', '%', '!', ';', '<', '>', '?'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => true,
+ 4 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #aaaadd; font-weight: bold;',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1=> 'color: #808080; font-style: italic;',
+ 2=> 'color: #a1a100;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.google.com/search?hl=en&amp;q=allinurl%3A{FNAME}+java.sun.com&amp;bntI=I%27m%20Feeling%20Lucky',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/javascript.php b/plugins/dokuwiki/inc/geshi/javascript.php
new file mode 100644
index 0000000..e585c63
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/javascript.php
@@ -0,0 +1,146 @@
+<?php
+/*************************************************************************************
+ * javascript.php
+ * --------------
+ * Author: Ben Keen (ben.keen@gmail.com)
+ * Copyright: (c) 2004 Ben Keen (ben.keen@gmail.com), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/06/20
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * JavaScript language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Javascript',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'as', 'break', 'case', 'catch', 'continue', 'decodeURI', 'delete', 'do',
+ 'else', 'encodeURI', 'eval', 'finally', 'for', 'if', 'in', 'is', 'item',
+ 'instanceof', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'void',
+ 'while', 'write', 'with'
+ ),
+ 2 => array(
+ 'class', 'const', 'default', 'debugger', 'export', 'extends', 'false',
+ 'function', 'import', 'namespace', 'new', 'null', 'package', 'private',
+ 'protected', 'public', 'super', 'true', 'use', 'var'
+ ),
+ 3 => array(
+
+ // common functions for Window object
+ 'alert', 'back', 'blur', 'close', 'confirm', 'focus', 'forward', 'home',
+ 'name', 'navigate', 'onblur', 'onerror', 'onfocus', 'onload', 'onmove',
+ 'onresize', 'onunload', 'open', 'print', 'prompt', 'scroll', 'status',
+ 'stop',
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '!', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000066; font-weight: bold;',
+ 2 => 'color: #003366; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #009900; font-style: italic;',
+ 'MULTI' => 'color: #009900; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #3366CC;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #CC0000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0066FF;'
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ 0 => "/.*/([igm]*)?" // matches js reg exps
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<script type="text/javascript">' => '</script>'
+ ),
+ 1 => array(
+ '<script language="javascript">' => '</script>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/lisp.php b/plugins/dokuwiki/inc/geshi/lisp.php
new file mode 100644
index 0000000..1ba6405
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/lisp.php
@@ -0,0 +1,135 @@
+<?php
+/*************************************************************************************
+ * lisp.php
+ * --------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.5 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Generic Lisp language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/9 (1.0.2)
+ * - Added support for :keywords and ::access (Denis Mashkevich)
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/08/30 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Lisp',
+ 'COMMENT_SINGLE' => array(1 => ';'),
+ 'COMMENT_MULTI' => array(';|' => '|;'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'not','defun','princ',
+ 'eval','apply','funcall','quote','identity','function',
+ 'complement','backquote','lambda','set','setq','setf',
+ 'defun','defmacro','gensym','make','symbol','intern',
+ 'symbol','name','symbol','value','symbol','plist','get',
+ 'getf','putprop','remprop','hash','make','array','aref',
+ 'car','cdr','caar','cadr','cdar','cddr','caaar','caadr','cadar',
+ 'caddr','cdaar','cdadr','cddar','cdddr','caaaar','caaadr',
+ 'caadar','caaddr','cadaar','cadadr','caddar','cadddr',
+ 'cdaaar','cdaadr','cdadar','cdaddr','cddaar','cddadr',
+ 'cdddar','cddddr','cons','list','append','reverse','last','nth',
+ 'nthcdr','member','assoc','subst','sublis','nsubst',
+ 'nsublis','remove','length','list','length',
+ 'mapc','mapcar','mapl','maplist','mapcan','mapcon','rplaca',
+ 'rplacd','nconc','delete','atom','symbolp','numberp',
+ 'boundp','null','listp','consp','minusp','zerop','plusp',
+ 'evenp','oddp','eq','eql','equal','cond','case','and','or',
+ 'let','l','if','prog','prog1','prog2','progn','go','return',
+ 'do','dolist','dotimes','catch','throw','error','cerror','break',
+ 'continue','errset','baktrace','evalhook','truncate','float',
+ 'rem','min','max','abs','sin','cos','tan','expt','exp','sqrt',
+ 'random','logand','logior','logxor','lognot','bignums','logeqv',
+ 'lognand','lognor','logorc2','logtest','logbitp','logcount',
+ 'integer','length','nil'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '!', '%', '^', '&', '/','+','-','*','=','<','>',';','|'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #555;',
+ 1 => 'color: #555;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ '::', ':'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/lua.php b/plugins/dokuwiki/inc/geshi/lua.php
new file mode 100644
index 0000000..df14d8c
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/lua.php
@@ -0,0 +1,137 @@
+<?php
+/*************************************************************************************
+ * lua.php
+ * -------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.5 $
+ * Date Started: 2004/07/10
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * LUA language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/26 (1.0.2)
+ * - Added support for objects and methods
+ * - Removed unusable keywords
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Lua',
+ 'COMMENT_SINGLE' => array(1 => "--"),
+ 'COMMENT_MULTI' => array('--[[' => ']]'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'and','break','do','else','elseif','end','false','for','function','if',
+ 'in','local','nil','not','or','repeat','return','then','true','until','while',
+ '_VERSION','assert','collectgarbage','dofile','error','gcinfo','loadfile','loadstring',
+ 'print','tonumber','tostring','type','unpack',
+ '_ALERT','_ERRORMESSAGE','_INPUT','_PROMPT','_OUTPUT',
+ '_STDERR','_STDIN','_STDOUT','call','dostring','foreach','foreachi','getn','globals','newtype',
+ 'rawget','rawset','require','sort','tinsert','tremove',
+ 'abs','acos','asin','atan','atan2','ceil','cos','deg','exp',
+ 'floor','format','frexp','gsub','ldexp','log','log10','max','min','mod','rad','random','randomseed',
+ 'sin','sqrt','strbyte','strchar','strfind','strlen','strlower','strrep','strsub','strupper','tan',
+ 'openfile','closefile','readfrom','writeto','appendto',
+ 'remove','rename','flush','seek','tmpfile','tmpname','read','write',
+ 'clock','date','difftime','execute','exit','getenv','setlocale','time',
+ '_G','getfenv','getmetatable','ipairs','loadlib','next','pairs','pcall',
+ 'rawegal','rawget','rawset','require','setfenv','setmetatable','xpcall',
+ 'string.byte','string.char','string.dump','string.find','string.len',
+ 'string.lower','string.rep','string.sub','string.upper','string.format','string.gfind','string.gsub',
+ 'table.concat','table.foreach','table.foreachi','table.getn','table.sort','table.insert','table.remove','table.setn',
+ 'math.abs','math.acos','math.asin','math.atan','math.atan2','math.ceil','math.cos','math.deg','math.exp',
+ 'math.floor','math.frexp','math.ldexp','math.log','math.log10','math.max','math.min','math.mod',
+ 'math.pi','math.rad','math.random','math.randomseed','math.sin','math.sqrt','math.tan',
+ 'coroutine.create','coroutine.resume','coroutine.status',
+ 'coroutine.wrap','coroutine.yield',
+ 'io.close','io.flush','io.input','io.lines','io.open','io.output','io.read','io.tmpfile','io.type','io.write',
+ 'io.stdin','io.stdout','io.stderr',
+ 'os.clock','os.date','os.difftime','os.execute','os.exit','os.getenv','os.remove','os.rename',
+ 'os.setlocale','os.time','os.tmpname',
+ 'string','table','math','coroutine','io','os','debug'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '!', '@', '%', '&', '*', '|', '/', '<', '>', '=', ';'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #b1b100;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
+
diff --git a/plugins/dokuwiki/inc/geshi/matlab.php b/plugins/dokuwiki/inc/geshi/matlab.php
new file mode 100644
index 0000000..6f70a86
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/matlab.php
@@ -0,0 +1,869 @@
+<?php
+/*************************************************************************************
+ * matlab.php
+ * -----------
+ * Author: Florian Knorn (floz@gmx.de)
+ * Copyright: (c) 2004 Florian Knorn (http://www.florian-knorn.com)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2005/02/09
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Matlab M-file language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2006-03-25
+ * - support for the transpose operator
+ * - many keywords added
+ * - links to the matlab documentation at mathworks
+ * by: Olivier Verdier (olivier.verdier@free.fr)
+ * 2005/05/07 (1.0.0)
+ * - First Release
+ *
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Matlab M',
+ 'COMMENT_SINGLE' => array(1 => '%'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array(),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'break', 'case', 'catch', 'continue', 'elseif', 'else', 'end', 'for',
+ 'function', 'global', 'if', 'otherwise', 'persistent', 'return',
+ 'switch', 'try', 'while','...'
+ ),
+ 2 => array(
+ 'all',
+ 'any',
+ 'exist',
+ 'find',
+ 'is',
+ 'isa',
+ 'logical',
+ 'mislocked',
+
+ 'builtin',
+ 'eval',
+ 'evalc',
+ 'evalin',
+ 'feval',
+ 'function',
+ 'global',
+ 'nargchk',
+ 'persistent',
+ 'script',
+ 'break',
+ 'case',
+ 'catch',
+ 'else',
+ 'elseif',
+ 'end',
+ 'error',
+ 'for',
+ 'if',
+ 'otherwise',
+ 'return',
+ 'switch',
+ 'try',
+ 'warning',
+ 'while',
+ 'input',
+ 'keyboard',
+ 'menu',
+ 'pause',
+ 'class',
+ 'double',
+ 'inferiorto',
+ 'inline',
+ 'int8',
+ 'int16',
+ 'int32',
+ 'isa',
+ 'loadobj',
+ 'saveobj',
+ 'single',
+ 'superiorto',
+ 'uint8',
+ 'int16',
+ 'uint32',
+ 'dbclear',
+ 'dbcont',
+ 'dbdown',
+ 'dbmex',
+ 'dbquit',
+ 'dbstack',
+ 'dbstatus',
+ 'dbstep',
+ 'dbstop',
+ 'dbtype',
+ 'dbup',
+
+ 'blkdiag',
+ 'eye',
+ 'linspace',
+ 'logspace',
+ 'ones',
+ 'rand',
+ 'randn',
+ 'zeros',
+ 'ans',
+ 'computer',
+ 'eps',
+ 'flops',
+ 'i',
+ 'Inf',
+ 'inputname',
+ 'j',
+ 'NaN',
+ 'nargin',
+ 'nargout',
+ 'pi',
+ 'realmax',
+ 'realmin',
+ 'varargin',
+ 'varargout',
+ 'calendar',
+ 'clock',
+ 'cputime',
+ 'date',
+ 'datenum',
+ 'datestr',
+ 'datevec',
+ 'eomday',
+ 'etime',
+ 'now',
+ 'tic',
+ 'toc',
+ 'weekday',
+ 'cat',
+ 'diag',
+ 'fliplr',
+ 'flipud',
+ 'repmat',
+ 'reshape',
+ 'rot90',
+ 'tril',
+ 'triu',
+ 'compan',
+ 'gallery',
+ 'hadamard',
+ 'hankel',
+ 'hilb',
+ 'invhilb',
+ 'magic',
+ 'pascal',
+ 'toeplitz',
+ 'wilkinson',
+ 'abs',
+ 'acos',
+ 'acosh',
+ 'acot',
+ 'acoth',
+ 'acsc',
+ 'acsch',
+ 'angle',
+ 'asec',
+ 'asech',
+ 'asin',
+ 'asinh',
+ 'atan',
+ 'atanh',
+ 'atan2',
+ 'ceil',
+ 'complex',
+ 'conj',
+ 'cos',
+ 'cosh',
+ 'cot',
+ 'coth',
+ 'csc',
+ 'csch',
+ 'exp',
+ 'fix',
+ 'floor',
+ 'gcd',
+ 'imag',
+ 'lcm',
+ 'log',
+ 'log2',
+ 'log10',
+ 'mod',
+ 'nchoosek',
+ 'real',
+ 'rem',
+ 'round',
+ 'sec',
+ 'sech',
+ 'sign',
+ 'sin',
+ 'sinh',
+ 'sqrt',
+ 'tan',
+ 'tanh',
+ 'airy',
+ 'besselh',
+ 'besseli',
+ 'besselk',
+ 'besselj',
+ 'Bessely',
+ 'beta',
+ 'betainc',
+ 'betaln',
+ 'ellipj',
+ 'ellipke',
+ 'erf',
+ 'erfc',
+ 'erfcx',
+ 'erfiny',
+ 'expint',
+ 'factorial',
+ 'gamma',
+ 'gammainc',
+ 'gammaln',
+ 'legendre',
+ 'pow2',
+ 'rat',
+ 'rats',
+ 'cart2pol',
+ 'cart2sph',
+ 'pol2cart',
+ 'sph2cart',
+ 'abs',
+ 'eval',
+ 'real',
+ 'strings',
+ 'deblank',
+ 'findstr',
+ 'lower',
+ 'strcat',
+ 'strcmp',
+ 'strcmpi',
+ 'strjust',
+ 'strmatch',
+ 'strncmp',
+ 'strrep',
+ 'strtok',
+ 'strvcat',
+ 'symvar',
+ 'texlabel',
+ 'upper',
+ 'char',
+ 'int2str',
+ 'mat2str',
+ 'num2str',
+ 'sprintf',
+ 'sscanf',
+ 'str2double',
+ 'str2num',
+ 'bin2dec',
+ 'dec2bin',
+ 'dec2hex',
+ 'hex2dec',
+ 'hex2num',
+ 'fclose',
+ 'fopen',
+ 'fread',
+ 'fwrite',
+ 'fgetl',
+ 'fgets',
+ 'fprintf',
+ 'fscanf',
+ 'feof',
+ 'ferror',
+ 'frewind',
+ 'fseek',
+ 'ftell',
+ 'sprintf',
+ 'sscanf',
+ 'dlmread',
+ 'dlmwrite',
+ 'hdf',
+ 'imfinfo',
+ 'imread',
+ 'imwrite',
+ 'textread',
+ 'wk1read',
+ 'wk1write',
+ 'bitand',
+ 'bitcmp',
+ 'bitor',
+ 'bitmax',
+ 'bitset',
+ 'bitshift',
+ 'bitget',
+ 'bitxor',
+ 'fieldnames',
+ 'getfield',
+ 'rmfield',
+ 'setfield',
+ 'struct',
+ 'struct2cell',
+ 'class',
+ 'isa',
+ 'cell',
+ 'cellfun',
+ 'cellstr',
+ 'cell2struct',
+ 'celldisp',
+ 'cellplot',
+ 'num2cell',
+ 'cat',
+ 'flipdim',
+ 'ind2sub',
+ 'ipermute',
+ 'ndgrid',
+ 'ndims',
+ 'permute',
+ 'reshape',
+ 'shiftdim',
+ 'squeeze',
+ 'sub2ind',
+ 'cond',
+ 'condeig',
+ 'det',
+ 'norm',
+ 'null',
+ 'orth',
+ 'rank',
+ 'rcond',
+ 'rref',
+ 'rrefmovie',
+ 'subspace',
+ 'trace',
+ 'chol',
+ 'inv',
+ 'lscov',
+ 'lu',
+ 'nnls',
+ 'pinv',
+ 'qr',
+ 'balance',
+ 'cdf2rdf',
+ 'eig',
+ 'gsvd',
+ 'hess',
+ 'poly',
+ 'qz',
+ 'rsf2csf',
+ 'schur',
+ 'svd',
+ 'expm',
+ 'funm',
+ 'logm',
+ 'sqrtm',
+ 'qrdelete',
+ 'qrinsert',
+ 'bar',
+ 'barh',
+ 'hist',
+ 'hold',
+ 'loglog',
+ 'pie',
+ 'plot',
+ 'polar',
+ 'semilogx',
+ 'semilogy',
+ 'subplot',
+ 'bar3',
+ 'bar3h',
+ 'comet3',
+ 'cylinder',
+ 'fill3',
+ 'plot3',
+ 'quiver3',
+ 'slice',
+ 'sphere',
+ 'stem3',
+ 'waterfall',
+ 'clabel',
+ 'datetick',
+ 'grid',
+ 'gtext',
+ 'legend',
+ 'plotyy',
+ 'title',
+ 'xlabel',
+ 'ylabel',
+ 'zlabel',
+ 'contour',
+ 'contourc',
+ 'contourf',
+ 'hidden',
+ 'meshc',
+ 'mesh',
+ 'peaks',
+ 'surf',
+ 'surface',
+ 'surfc',
+ 'surfl',
+ 'trimesh',
+ 'trisurf',
+ 'coneplot',
+ 'contourslice',
+ 'isocaps',
+ 'isonormals',
+ 'isosurface',
+ 'reducepatch',
+ 'reducevolume',
+ 'shrinkfaces',
+ 'smooth3',
+ 'stream2',
+ 'stream3',
+ 'streamline',
+ 'surf2patch',
+ 'subvolume',
+ 'griddata',
+ 'meshgrid',
+ 'area',
+ 'box',
+ 'comet',
+ 'compass',
+ 'errorbar',
+ 'ezcontour',
+ 'ezcontourf',
+ 'ezmesh',
+ 'ezmeshc',
+ 'ezplot',
+ 'ezplot3',
+ 'ezpolar',
+ 'ezsurf',
+ 'ezsurfc',
+ 'feather',
+ 'fill',
+ 'fplot',
+ 'pareto',
+ 'pie3',
+ 'plotmatrix',
+ 'pcolor',
+ 'rose',
+ 'quiver',
+ 'ribbon',
+ 'stairs',
+ 'scatter',
+ 'scatter3',
+ 'stem',
+ 'convhull',
+ 'delaunay',
+ 'dsearch',
+ 'inpolygon',
+ 'polyarea',
+ 'tsearch',
+ 'voronoi',
+ 'camdolly',
+ 'camlookat',
+ 'camorbit',
+ 'campan',
+ 'campos',
+ 'camproj',
+ 'camroll',
+ 'camtarget',
+ 'camup',
+ 'camva',
+ 'camzoom',
+ 'daspect',
+ 'pbaspect',
+ 'view',
+ 'viewmtx',
+ 'xlim',
+ 'ylim',
+ 'zlim',
+ 'camlight',
+ 'diffuse',
+ 'lighting',
+ 'lightingangle',
+ 'material',
+ 'specular',
+ 'brighten',
+ 'bwcontr',
+ 'caxis',
+ 'colorbar',
+ 'colorcube',
+ 'colordef',
+ 'colormap',
+ 'graymon',
+ 'hsv2rgb',
+ 'rgb2hsv',
+ 'rgbplot',
+ 'shading',
+ 'spinmap',
+ 'surfnorm',
+ 'whitebg',
+ 'autumn',
+ 'bone',
+ 'contrast',
+ 'cool',
+ 'copper',
+ 'flag',
+ 'gray',
+ 'hot',
+ 'hsv',
+ 'jet',
+ 'lines',
+ 'prism',
+ 'spring',
+ 'summer',
+ 'winter',
+ 'orient',
+ 'print',
+ 'printopt',
+ 'saveas',
+ 'copyobj',
+ 'findobj',
+ 'gcbo',
+ 'gco',
+ 'get',
+ 'rotate',
+ 'ishandle',
+ 'set',
+ 'axes',
+ 'figure',
+ 'image',
+ 'light',
+ 'line',
+ 'patch',
+ 'rectangle',
+ 'surface',
+ 'text Create',
+ 'uicontext Create',
+ 'capture',
+ 'clc',
+ 'clf',
+ 'clg',
+ 'close',
+ 'gcf',
+ 'newplot',
+ 'refresh',
+ 'saveas',
+ 'axis',
+ 'cla',
+ 'gca',
+ 'propedit',
+ 'reset',
+ 'rotate3d',
+ 'selectmoveresize',
+ 'shg',
+ 'ginput',
+ 'zoom',
+ 'dragrect',
+ 'drawnow',
+ 'rbbox',
+ 'dialog',
+ 'errordlg',
+ 'helpdlg',
+ 'inputdlg',
+ 'listdlg',
+ 'msgbox',
+ 'pagedlg',
+ 'printdlg',
+ 'questdlg',
+ 'uigetfile',
+ 'uiputfile',
+ 'uisetcolor',
+ 'uisetfont',
+ 'warndlg',
+ 'menu',
+ 'menuedit',
+ 'uicontextmenu',
+ 'uicontrol',
+ 'uimenu',
+ 'dragrect',
+ 'findfigs',
+ 'gcbo',
+ 'rbbox',
+ 'selectmoveresize',
+ 'textwrap',
+ 'uiresume',
+ 'uiwait Used',
+ 'waitbar',
+ 'waitforbuttonpress',
+ 'convhull',
+ 'cumprod',
+ 'cumsum',
+ 'cumtrapz',
+ 'delaunay',
+ 'dsearch',
+ 'factor',
+ 'inpolygon',
+ 'max',
+ 'mean',
+ 'median',
+ 'min',
+ 'perms',
+ 'polyarea',
+ 'primes',
+ 'prod',
+ 'sort',
+ 'sortrows',
+ 'std',
+ 'sum',
+ 'trapz',
+ 'tsearch',
+ 'var',
+ 'voronoi',
+ 'del2',
+ 'diff',
+ 'gradient',
+ 'corrcoef',
+ 'cov',
+ 'conv',
+ 'conv2',
+ 'deconv',
+ 'filter',
+ 'filter2',
+ 'abs',
+ 'angle',
+ 'cplxpair',
+ 'fft',
+ 'fft2',
+ 'fftshift',
+ 'ifft',
+ 'ifft2',
+ 'ifftn',
+ 'ifftshift',
+ 'nextpow2',
+ 'unwrap',
+ 'cross',
+ 'intersect',
+ 'ismember',
+ 'setdiff',
+ 'setxor',
+ 'union',
+ 'unique',
+ 'conv',
+ 'deconv',
+ 'poly',
+ 'polyder',
+ 'polyeig',
+ 'polyfit',
+ 'polyval',
+ 'polyvalm',
+ 'residue',
+ 'roots',
+ 'griddata',
+ 'interp1',
+ 'interp2',
+ 'interp3',
+ 'interpft',
+ 'interpn',
+ 'meshgrid',
+ 'ndgrid',
+ 'spline',
+ 'dblquad',
+ 'fmin',
+ 'fmins',
+ 'fzero',
+ 'ode45,',
+ 'ode113,',
+ 'ode15s,',
+ 'ode23s,',
+ 'ode23t,',
+ 'ode23tb',
+ 'odefile',
+ 'odeget',
+ 'odeset',
+ 'quad,',
+ 'vectorize',
+ 'spdiags',
+ 'speye',
+ 'sprand',
+ 'sprandn',
+ 'sprandsym',
+ 'find',
+ 'full',
+ 'sparse',
+ 'spconvert',
+ 'nnz',
+ 'nonzeros',
+ 'nzmax',
+ 'spalloc',
+ 'spfun',
+ 'spones',
+ 'colmmd',
+ 'colperm',
+ 'dmperm',
+ 'randperm',
+ 'symmmd',
+ 'symrcm',
+ 'condest',
+ 'normest',
+ 'bicg',
+ 'bicgstab',
+ 'cgs',
+ 'cholinc',
+ 'cholupdate',
+ 'gmres',
+ 'luinc',
+ 'pcg',
+ 'qmr',
+ 'qr',
+ 'qrdelete',
+ 'qrinsert',
+ 'qrupdate',
+ 'eigs',
+ 'svds',
+ 'spparms',
+ 'lin2mu',
+ 'mu2lin',
+ 'sound',
+ 'soundsc',
+ 'auread',
+ 'auwrite',
+ 'wavread',
+ 'wavwrite',
+ '[Keywords 6]',
+ 'addpath',
+ 'doc',
+ 'docopt',
+ 'help',
+ 'helpdesk',
+ 'helpwin',
+ 'lasterr',
+ 'lastwarn',
+ 'lookfor',
+ 'partialpath',
+ 'path',
+ 'pathtool',
+ 'profile',
+ 'profreport',
+ 'rmpath',
+ 'type',
+ 'ver',
+ 'version',
+ 'web',
+ 'what',
+ 'whatsnew',
+ 'which',
+ 'clear',
+ 'disp',
+ 'length',
+ 'load',
+ 'mlock',
+ 'munlock',
+ 'openvar',
+ 'pack',
+ 'save',
+ 'saveas',
+ 'size',
+ 'who',
+ 'whos',
+ 'workspace',
+ 'clc',
+ 'echo',
+ 'format',
+ 'home',
+ 'more',
+ 'cd',
+ 'copyfile',
+ 'delete',
+ 'diary',
+ 'dir',
+ 'edit',
+ 'fileparts',
+ 'fullfile',
+ 'inmem',
+ 'ls',
+ 'matlabroot',
+ 'mkdir',
+ 'open',
+ 'pwd',
+ 'tempdir',
+ 'tempname',
+ 'matlabrc',
+ 'quit',
+)
+ ),
+ 'SYMBOLS' => array(
+ '...'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ //3 => false,
+ //4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000FF;',
+ 2 => 'color: #0000FF;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #228B22;',
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => ''
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #080;'
+ ),
+ 'STRINGS' => array(
+ //0 => 'color: #A020F0;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #33f;'
+ ),
+ 'METHODS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #080;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color:#A020F0;'
+ ),
+ 'SCRIPT' => array(
+ 0 => ''
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => 'http://www.mathworks.com/access/helpdesk/help/techdoc/ref/{FNAME}.html',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ 0 => array(
+ GESHI_SEARCH => "([^\w])'([^\\n\\r']*)'",
+ GESHI_REPLACE => '\\2',
+ GESHI_MODIFIERS => '',
+ GESHI_BEFORE => "\\1'",
+ GESHI_AFTER => "'"
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/mpasm.php b/plugins/dokuwiki/inc/geshi/mpasm.php
new file mode 100644
index 0000000..3acc09d
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/mpasm.php
@@ -0,0 +1,160 @@
+<?php
+/*************************************************************************************
+ * mpasm.php
+ * ---------
+ * Author: Bakalex (bakalex@gmail.com)
+ * Copyright: (c) 2004 Bakalex, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2004/12/6
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Microchip Assembler language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/01/29 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/12/6)
+ * -------------------------
+ *
+ * For the moment, i've only added PIC16C6X registers. We need more (PIC16F/C7x/8x,
+ * PIC10, PIC18 and dsPIC registers).
+ * Must take a look to dsPIC instructions.
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Microchip Assembler',
+ 'COMMENT_SINGLE' => array(1 => ';'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ /*Directive Language*/
+ 4 => array(
+ 'CONSTANT', '#DEFINE', 'END', 'EQU', 'ERROR', 'ERROR-LEVEL', '#INCLUDE', 'LIST',
+ 'MESSG', 'NOLIST', 'ORG', 'PAGE', 'PROCESSOR', 'RADIX', 'SET', 'SPACE', 'SUBTITLE',
+ 'TITLE', '#UNDEFINE', 'VARIABLE', 'ELSE', 'ENDIF', 'ENDW', 'IF', 'IFDEF', 'IFNDEF',
+ 'WHILE', '__BADRAM', 'CBLOCK', '__CONFIG', 'DA', 'DATA', 'DB', 'DE', 'DT', 'DW',
+ 'ENDC', 'FILL', '__IDLOCS', '__MAXRAM', 'RES', 'ENDM', 'EXITM', 'EXPAND', 'LOCAL',
+ 'MACRO', 'NOEXPAND', 'BANKISEL', 'BANKSEL', 'CODE', 'EXTERN', 'GLOBAL', 'IDATA',
+ 'PAGESEL', 'UDATA', 'UDATA_ACS', 'UDATA_OVR', 'UDATA_SHR'
+ ),
+ /* 12&14-bit Specific Instruction Set*/
+ 1 => array(
+ 'andlw', 'call', 'clrwdt', 'goto', 'iorlw', 'movlw', 'option', 'retlw', 'sleep',
+ 'tris', 'xorlw', 'addwf', 'andwf', 'clrf', 'clrw', 'comf', 'decf', 'decfsz', 'incf',
+ 'incfsz', 'iorwf', 'movf', 'movwf', 'nop', 'rlf', 'rrf', 'subwf', 'swapf', 'xorwf',
+ 'bcf', 'bsf', 'btfsc', 'btfss',
+ 'addlw', 'iorlw', 'retfie', 'return', 'sublw', 'xorlw', 'addcf', 'adddcf', 'b', 'bc', 'bdc',
+ 'bnc', 'bndc', 'bnz', 'bz', 'clrc', 'clrdc', 'clrz', 'lcall', 'lgoto', 'movfw',
+ 'negf', 'setc', 'setdc', 'setz', 'skpc', 'skpdc', 'skpnc', 'skpndc', 'skpnz', 'skpz',
+ 'subcf', 'subdcf', 'tstf'
+ ),
+ /* 16-bit Specific Instructiob Set */
+ 2 => array (
+ 'movfp', 'movlb', 'movlp', 'movpf', 'movwf', 'tablrd', 'tablwt', 'tlrd', 'tlwt',
+ 'addwfc', 'daw', 'mullw', 'negw', 'rlcf', 'rlncf', 'rrcf', 'rrncf', 'setf', 'subwfb',
+ 'btg', 'cpfseq', 'cpfsgt', 'cpfslt', 'dcfsnz', 'infsnz', 'tstfsz', 'lfsr', 'bnn',
+ 'bnov', 'bra', 'pop', 'push', 'rcall', 'reset'
+ ),
+ /* Registers */
+ 3 => array(
+ 'INDF', 'TMR0', 'PCL', 'STATUS', 'FSR', 'PORTA', 'PORTB', 'PORTC', 'PORTD', 'PORTE',
+ 'PCLATH', 'INTCON', 'PIR1', 'PIR2', 'TMR1L', 'TMR1H', 'T1CON', 'TMR2', 'T2CON', 'TMR2L',
+ 'TMR2H', 'TMR0H', 'TMR0L', 'SSPBUF', 'SSPCON', 'CCPR1L', 'CCPR1H', 'CCP1CON', 'RCSTA',
+ 'TXREG', 'RCREG', 'CCPR2L', 'CCPR2H', 'CCP2CON', 'OPTION', 'TRISA', 'TRISB', 'TRISC',
+ 'TRISD', 'TRISE', 'PIE2', 'PIE1', 'PR2', 'SSPADD', 'SSPSTAT', 'TXSTA', 'SPBRG'
+ ),
+ /*Operands*/
+ 5 => array(
+ 'high','low'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '[', ']', '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #00007f;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #007f00;',
+ 4 => 'color: #46aa03; font-weight:bold;',
+ 5 => 'color: #7f0000;',
+ 6 => 'color: #7f0000;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #adadad; font-style: italic;',
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #7f007f;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #ff0000;',
+ 1 => 'color: #ff0000;'
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '[0-9a-fA-F][0-9a-fA-F]*[hH]',
+ 1 => '[01][01]*[bB]'
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/nsis.php b/plugins/dokuwiki/inc/geshi/nsis.php
new file mode 100644
index 0000000..3c7086d
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/nsis.php
@@ -0,0 +1,354 @@
+<?php
+/*************************************************************************************
+ * nsis.php
+ * --------
+ * Author: deguix (cevo_deguix@yahoo.com.br), Tux (http://tux.a4.cz/)
+ * Copyright: (c) 2005 deguix, 2004 Tux (http://tux.a4.cz/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.5 $
+ * Date Started: 2005/12/03
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Nullsoft Scriptable Install System language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/12/03 (2.0.2)
+ * - Updated to NSIS 2.11.
+ * 2005/06/17 (2.0.1)
+ * - Updated to NSIS 2.07b0.
+ * 2005/04/05 (2.0.0)
+ * - Updated to NSIS 2.06.
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'NSIS',
+ 'COMMENT_SINGLE' => array(1 => ';', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'",'"','`'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '!appendfile', '!addIncludeDir', '!addplugindir', '!cd', '!define', '!delfile', '!echo', '!else',
+ '!endif', '!error', '!execute', '!ifdef', '!ifmacrodef', '!ifmacrondef', '!ifndef', '!include',
+ '!insertmacro', '!macro', '!macroend', '!packhdr', '!tempfile', '!system', '!undef', '!verbose',
+ '!warning'
+ ),
+ 2 => array(
+ 'AddBrandingImage', 'AllowRootDirInstall', 'AutoCloseWindow', 'BGFont',
+ 'BGGradient', 'BrandingText', 'Caption', 'ChangeUI', 'CheckBitmap', 'CompletedText', 'ComponentText',
+ 'CRCCheck', 'DetailsButtonText', 'DirShow', 'DirText', 'DirVar', 'DirVerify', 'FileErrorText',
+ 'Function', 'FunctionEnd', 'Icon', 'InstallButtonText', 'InstallColors', 'InstallDir',
+ 'InstallDirRegKey', 'InstProgressFlags', 'InstType', 'LangString', 'LangStringUP', 'LicenseBkColor',
+ 'LicenseData', 'LicenseForceSelection', 'LicenseLangString', 'LicenseText', 'LoadLanguageFile',
+ 'MiscButtonText', 'Name', 'OutFile', 'Page', 'PageEx', 'PageExEnd', 'Section',
+ 'SectionEnd', 'SectionGroup', 'SectionGroupEnd', 'SetCompressor', 'SetFont', 'ShowInstDetails',
+ 'ShowUninstDetails', 'SilentInstall', 'SilentUnInstall', 'SpaceTexts', 'SubCaption', 'SubSection',
+ 'SubSectionEnd', 'UninstallButtonText', 'UninstallCaption', 'UninstallIcon', 'UninstallSubCaption',
+ 'UninstallText', 'UninstPage', 'Var', 'VIAddVersionKey', 'VIProductVersion', 'WindowIcon', 'XPStyle'
+ ),
+ 3 => array(
+ 'AddSize', 'AllowSkipFiles', 'AutoCloseWindow', 'FileBufSize', 'GetInstDirError', 'PageCallbacks',
+ 'SectionIn', 'SetCompress', 'SetCompressionLevel', 'SetCompressorDictSize',
+ 'SetDatablockOptimize', 'SetDateSave', 'SetOverwrite', 'SetPluginUnload'
+ ),
+ 4 => array(
+ 'Abort', 'BringToFront', 'Call', 'CallInstDLL', 'ClearErrors', 'CopyFiles','CreateDirectory',
+ 'CreateFont', 'CreateShortCut', 'Delete', 'DeleteINISec', 'DeleteINIStr', 'DeleteRegKey',
+ 'DeleteRegValue', 'DetailPrint', 'EnableWindow', 'EnumRegKey', 'EnumRegValue', 'Exch', 'Exec',
+ 'ExecShell', 'ExecWait', 'ExpandEnvStrings', 'File', 'FileClose', 'FileOpen', 'FileRead',
+ 'FileReadByte', 'FileSeek', 'FileWrite', 'FileWriteByte', 'FindClose', 'FindFirst', 'FindNext',
+ 'FindWindow', 'FlushINI', 'GetCurInstType', 'GetCurrentAddress', 'GetDlgItem', 'GetDLLVersion',
+ 'GetDLLVersionLocal', 'GetErrorLevel', 'GetFileTime', 'GetFileTimeLocal', 'GetFullPathName',
+ 'GetFunctionAddress', 'GetLabelAddress', 'GetTempFileName', 'GetWindowText', 'Goto', 'HideWindow',
+ 'IfAbort', 'IfErrors', 'IfFileExists', 'IfRebootFlag', 'IfSilent', 'InitPluginsDir', 'InstTypeGetText',
+ 'InstTypeSetText', 'IntCmp', 'IntCmpU', 'IntFmt', 'IntOp', 'IsWindow', 'LockWindow', 'LogSet', 'LogText',
+ 'MessageBox', 'Nop', 'Pop', 'Push', 'Quit', 'ReadEnvStr', 'ReadIniStr', 'ReadRegDWORD', 'ReadRegStr',
+ 'Reboot', 'RegDLL', 'Rename', 'ReserveFile', 'Return', 'RMDir', 'SearchPath', 'SectionGetFlags',
+ 'SectionGetInstTypes', 'SectionGetSize', 'SectionGetText', 'SectionSetFlags', 'SectionSetInstTypes',
+ 'SectionSetSize', 'SectionSetText', 'SendMessage', 'SetAutoClose', 'SetBrandingImage', 'SetCtlColors',
+ 'SetCurInstType', 'SetDetailsPrint', 'SetDetailsView', 'SetErrorLevel', 'SetErrors', 'SetFileAttributes',
+ 'SetOutPath', 'SetRebootFlag', 'SetShellVarContext', 'SetSilent', 'ShowWindow', 'Sleep', 'StrCmp',
+ 'StrCpy', 'StrLen', 'UnRegDLL', 'WriteINIStr', 'WriteRegBin', 'WriteRegDWORD', 'WriteRegExpandStr',
+ 'WriteRegStr', 'WriteUninstaller'
+ ),
+ 5 => array(
+ 'all', 'alwaysoff', 'ARCHIVE', 'auto', 'both', 'bzip2', 'checkbox', 'components', 'current',
+ 'custom', 'directory', 'false', 'FILE_ATTRIBUTE_ARCHIVE', 'FILE_ATTRIBUTE_HIDDEN', 'FILE_ATTRIBUTE_NORMAL',
+ 'FILE_ATTRIBUTE_OFFLINE', 'FILE_ATTRIBUTE_READONLY', 'FILE_ATTRIBUTE_SYSTEM,TEMPORARY',
+ 'FILE_ATTRIBUTE_TEMPORARY', 'force', 'HIDDEN', 'hide', 'HKCC', 'HKCR', 'HKCU', 'HKDD', 'HKEY_CLASSES_ROOT',
+ 'HKEY_CURRENT_CONFIG', 'HKEY_CURRENT_USER', 'HKEY_DYN_DATA', 'HKEY_LOCAL_MACHINE', 'HKEY_PERFORMANCE_DATA',
+ 'HKEY_USERS', 'HKLM', 'HKPD', 'HKU', 'IDABORT', 'IDCANCEL', 'IDIGNORE', 'IDNO', 'IDOK', 'IDRETRY', 'IDYES',
+ 'ifdiff', 'ifnewer', 'instfiles', 'lastused', 'leave', 'license', 'listonly', 'lzma', 'manual',
+ 'MB_ABORTRETRYIGNORE', 'MB_DEFBUTTON1', 'MB_DEFBUTTON2', 'MB_DEFBUTTON3', 'MB_DEFBUTTON4',
+ 'MB_ICONEXCLAMATION', 'MB_ICONINFORMATION', 'MB_ICONQUESTION', 'MB_ICONSTOP', 'MB_OK', 'MB_OKCANCEL',
+ 'MB_RETRYCANCEL', 'MB_RIGHT', 'MB_SETFOREGROUND', 'MB_TOPMOST', 'MB_YESNO', 'MB_YESNOCANCEL', 'nevershow',
+ 'none', 'normal', 'off', 'OFFLINE', 'on', 'radiobuttons', 'READONLY', 'RO', 'SHCTX', 'SHELL_CONTEXT', 'show',
+ 'silent', 'silentlog', 'SW_HIDE', 'SW_SHOWMAXIMIZED', 'SW_SHOWMINIMIZED', 'SW_SHOWNORMAL', 'SYSTEM',
+ 'textonly', 'true', 'try', 'uninstConfirm', 'zlib'
+ ),
+ 6 => array(
+ '/a', '/components', '/COMPONENTSONLYONCUSTOM', '/CUSTOMSTRING', '/e', '/FILESONLY', '/FINAL', '/gray', '/GLOBAL',
+ '/ifempty', '/IMGID', '/ITALIC', '/lang', '/NOCUSTOM', '/nonfatal', '/NOUNLOAD', '/oname', '/r', '/REBOOTOK',
+ '/RESIZETOFIT', '/SOLID', '/SD', '/SHORT', '/silent', '/SOLID', '/STRIKE', '/TIMEOUT', '/TRIMCENTER', '/TRIMLEFT',
+ '/TRIMRIGHT', '/UNDERLINE', '/windows', '/x'
+ ),
+ 7 => array(
+ '.onGUIEnd', '.onGUIInit', '.onInit', '.onInstFailed', '.onInstSuccess', '.onMouseOverSection',
+ '.onRebootFailed', '.onSelChange', '.onUserAbort', '.onVerifyInstDir', 'un.onGUIEnd', 'un.onGUIInit',
+ 'un.onInit', 'un.onRebootFailed', 'un.onUninstFailed', 'un.onUninstSuccess', 'un.onUserAbort'
+ ),
+ 8 => array(
+ 'MUI.nsh', '"${NSISDIR}\Contrib\Modern UI\System.nsh"', 'MUI_SYSVERSION', 'MUI_ICON', 'MUI_UNICON',
+ 'MUI_HEADERIMAGE', 'MUI_HEADERIMAGE_BITMAP', 'MUI_HEADERIMAGE_BITMAP_NOSTRETCH', 'MUI_HEADERIMAGE_BITMAP_RTL',
+ 'MUI_HEADERIMAGE_BITMAP_RTL_NOSTRETCH', 'MUI_HEADERIMAGE_UNBITMAP', 'MUI_HEADERIMAGE_UNBITMAP_NOSTRETCH',
+ 'MUI_HEADERIMAGE_UNBITMAP_RTL', 'MUI_HEADERIMAGE_UNBITMAP_RTL_NOSTRETCH', 'MUI_HEADERIMAGE_RIGHT', 'MUI_BGCOLOR',
+ 'MUI_UI', 'MUI_UI_HEADERIMAGE', 'MUI_UI_HEADERIMAGE_RIGHT', 'MUI_UI_COMPONENTSPAGE_SMALLDESC',
+ 'MUI_UI_COMPONENTSPAGE_NODESC', 'MUI_WELCOMEFINISHPAGE_BITMAP', 'MUI_WELCOMEFINISHPAGE_BITMAP_NOSTRETCH',
+ 'MUI_WELCOMEFINISHPAGE_INI', 'MUI_UNWELCOMEFINISHPAGE_BITMAP', 'MUI_UNWELCOMEFINISHPAGE_BITMAP_NOSTRETCH',
+ 'MUI_UNWELCOMEFINISHPAGE_INI', 'MUI_LICENSEPAGE_BGCOLOR', 'MUI_COMPONENTSPAGE_CHECKBITMAP',
+ 'MUI_COMPONENTSPAGE_SMALLDESC', 'MUI_COMPONENTSPAGE_NODESC', 'MUI_INSTFILESPAGE_COLORS',
+ 'MUI_INSTFILESPAGE_PROGRESSBAR', 'MUI_FINISHPAGE_NOAUTOCLOSE', 'MUI_UNFINISHPAGE_NOAUTOCLOSE',
+ 'MUI_ABORTWARNING', 'MUI_ABORTWARNING_TEXT', 'MUI_UNABORTWARNING', 'MUI_UNABORTWARNING_TEXT',
+ 'MUI_PAGE_WELCOME', 'MUI_PAGE_LICENSE', 'MUI_PAGE_COMPONENTS', 'MUI_PAGE_DIRECTORY',
+ 'MUI_PAGE_STARTMENU', 'MUI_PAGE_INSTFILES', 'MUI_PAGE_FINISH', 'MUI_UNPAGE_WELCOME',
+ 'MUI_UNPAGE_CONFIRM', 'MUI_UNPAGE_LICENSE', 'MUI_UNPAGE_COMPONENTS', 'MUI_UNPAGE_DIRECTORY',
+ 'MUI_UNPAGE_INSTFILES', 'MUI_UNPAGE_FINISH', 'MUI_PAGE_HEADER_TEXT', 'MUI_PAGE_HEADER_SUBTEXT',
+ 'MUI_WELCOMEPAGE_TITLE', 'MUI_WELCOMEPAGE_TITLE_3LINES', 'MUI_WELCOMEPAGE_TEXT',
+ 'MUI_LICENSEPAGE_TEXT_TOP', 'MUI_LICENSEPAGE_TEXT_BOTTOM', 'MUI_LICENSEPAGE_BUTTON',
+ 'MUI_LICENSEPAGE_CHECKBOX', 'MUI_LICENSEPAGE_CHECKBOX_TEXT', 'MUI_LICENSEPAGE_RADIOBUTTONS',
+ 'MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_ACCEPT', 'MUI_LICENSEPAGE_RADIOBUTTONS_TEXT_DECLINE',
+ 'MUI_COMPONENTSPAGE_TEXT_TOP', 'MUI_COMPONENTSPAGE_TEXT_COMPLIST', 'MUI_COMPONENTSPAGE_TEXT_INSTTYPE',
+ 'MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_TITLE', 'MUI_COMPONENTSPAGE_TEXT_DESCRIPTION_INFO',
+ 'MUI_DIRECTORYPAGE_TEXT_TOP', 'MUI_DIRECTORYPAGE_TEXT_DESTINATION', 'MUI_DIRECTORYPAGE_VARIABLE',
+ 'MUI_DIRECTORYPAGE_VERIFYONLEAVE', 'MUI_STARTMENU_WRITE_BEGIN', 'MUI_STARTMENU_WRITE_END',
+ 'MUI_STARTMENUPAGE_TEXT_TOP', 'MUI_STARTMENUPAGE_TEXT_CHECKBOX', 'MUI_STARTMENUPAGE_DEFAULTFOLDER',
+ 'MUI_STARTMENUPAGE_NODISABLE', 'MUI_STARTMENUPAGE_REGISTRY_ROOT', 'MUI_STARTMENUPAGE_REGISTRY_KEY',
+ 'MUI_STARTMENUPAGE_REGISTRY_VALUENAME', 'MUI_INSTFILESPAGE_FINISHHEADER_TEXT',
+ 'MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT', 'MUI_INSTFILESPAGE_ABORTHEADER_TEXT',
+ 'MUI_INSTFILESPAGE_ABORTHEADER_SUBTEXT', 'MUI_FINISHPAGE_TITLE', 'MUI_FINISHPAGE_TITLE_3LINES',
+ 'MUI_FINISHPAGE_TEXT', 'MUI_FINISHPAGE_TEXT_LARGE', 'MUI_FINISHPAGE_BUTTON',
+ 'MUI_FINISHPAGE_TEXT_REBOOT', 'MUI_FINISHPAGE_TEXT_REBOOTNOW', 'MUI_FINISHPAGE_TEXT_REBOOTLATER',
+ 'MUI_FINISHPAGE_RUN', 'MUI_FINISHPAGE_RUN_TEXT', 'MUI_FINISHPAGE_RUN_PARAMETERS',
+ 'MUI_FINISHPAGE_RUN_NOTCHECKED', 'MUI_FINISHPAGE_RUN_FUNCTION', 'MUI_FINISHPAGE_SHOWREADME',
+ 'MUI_FINISHPAGE_SHOWREADME_TEXT', 'MUI_FINISHPAGE_SHOWREADME_NOTCHECKED',
+ 'MUI_FINISHPAGE_SHOWREADME_FUNCTION', 'MUI_FINISHPAGE_LINK', 'MUI_FINISHPAGE_LINK_LOCATION',
+ 'MUI_FINISHPAGE_LINK_COLOR', 'MUI_FINISHPAGE_NOREBOOTSUPPORT', 'MUI_UNCONFIRMPAGE_TEXT_TOP',
+ 'MUI_UNCONFIRMPAGE_TEXT_LOCATION', 'MUI_LANGUAGE', 'MUI_LANGDLL_DISPLAY',
+ 'MUI_LANGDLL_REGISTRY_ROOT', 'MUI_LANGDLL_REGISTRY_KEY', 'MUI_LANGDLL_REGISTRY_VALUENAME',
+ 'MUI_LANGDLL_WINDOWTITLE', 'MUI_LANGDLL_INFO', 'MUI_LANGDLL_ALWAYSSHOW',
+ 'MUI_RESERVEFILE_INSTALLOPTIONS', 'MUI_RESERVEFILE_LANGDLL', 'MUI_FUNCTION_DESCRIPTION_BEGIN',
+ 'MUI_DESCRIPTION_TEXT', 'MUI_FUNCTION_DESCRIPTION_END', 'MUI_INSTALLOPTIONS_EXTRACT',
+ 'MUI_INSTALLOPTIONS_EXTRACT_AS', 'MUI_HEADER_TEXT', 'MUI_INSTALLOPTIONS_DISPLAY',
+ 'MUI_INSTALLOPTIONS_INITDIALOG', 'MUI_INSTALLOPTIONS_SHOW',
+ 'MUI_INSTALLOPTIONS_DISPLAY_RETURN', 'MUI_INSTALLOPTIONS_SHOW_RETURN',
+ 'MUI_INSTALLOPTIONS_READ', 'MUI_INSTALLOPTIONS_WRITE',
+ 'MUI_CUSTOMFUNCTION_GUIINIT', 'MUI_CUSTOMFUNCTION_GUIINIT',
+ 'MUI_CUSTOMFUNCTION_UNGUIINIT', 'MUI_CUSTOMFUNCTION_ABORT', 'MUI_CUSTOMFUNCTION_UNABORT',
+ 'MUI_PAGE_CUSTOMFUNCTION_PRE', 'MUI_PAGE_CUSTOMFUNCTION_SHOW', 'MUI_PAGE_CUSTOMFUNCTION_LEAVE',
+ 'MUI_WELCOMEFINISHPAGE_CUSTOMFUNCTION_INIT'
+ ),
+ 9 => array(
+ 'LogicLib.nsh', '${LOGICLIB}', 'LOGICLIB_STRCMP', 'LOGICLIB_INT64CMP', 'LOGICLIB_SECTIONCMP', '${If}', '${Unless}',
+ '${ElseIf}', '${ElseUnless}', '${Else}', '${EndIf}', '${EndUnless}', '${AndIf}', '${AndUnless}',
+ '${OrIf}', '${OrUnless}', '${IfThen}', '${IfCmd}', '${Select}', '${Case2}', '${Case3}',
+ '${Case4}', '${Case5}', '${CaseElse}', '${Default}', '${EndSelect}', '${Switch}',
+ '${Case}', '${EndSwitch}', '${Do}', '${DoWhile}', '${UntilWhile}', '${Continue}', '${Break}',
+ '${Loop}', '${LoopWhile}', '${LoopUntil}', '${While}', '${ExitWhile}', '${EndWhile}', '${For}',
+ '${ForEach}', '${ExitFor}', '${Next}', '${Abort}', '${Errors}', '${RebootFlag}', '${Silent}',
+ '${FileExists}', '${Cmd}', '${SectionIsSelected}', '${SectionIsSectionGroup}',
+ '${SectionIsSectionGroupEnd}', '${SectionIsBold}', '${SectionIsReadOnly}',
+ '${SectionIsExpanded}', '${SectionIsPartiallySelected}'
+ ),
+ 10 => array(
+ 'StrFunc.nsh', '${STRFUNC}', '${StrCase}', '${StrClb}', '${StrIOToNSIS}', '${StrLoc}', '${StrNSISToIO}', '${StrRep}',
+ '${StrSort}', '${StrStr}', '${StrStrAdv}', '${StrTok}', '${StrTrimNewLines}'
+ ),
+ 11 => array(
+ 'UpgradeDLL.nsh', 'UPGRADEDLL_INCLUDED', 'UpgradeDLL'
+ ),
+ 12 => array(
+ 'Sections.nsh', 'SECTIONS_INCLUDED', '${SF_SELECTED}', '${SF_SECGRP}', '${SF_SUBSEC}', '${SF_SECGRPEND}',
+ '${SF_SUBSECEND}', '${SF_BOLD}', '${SF_RO}', '${SF_EXPAND}', '${SF_PSELECTED}', '${SF_TOGGLED}',
+ '${SF_NAMECHG}', '${SECTION_OFF}', 'SelectSection', 'UnselectSection', 'ReverseSection',
+ 'StartRadioButtons', 'RadioButton', 'EndRadioButtons', '${INSTTYPE_1}', '${INSTTYPE_1}', '${INSTTYPE_2}',
+ '${INSTTYPE_3}', '${INSTTYPE_4}', '${INSTTYPE_5}', '${INSTTYPE_6}', '${INSTTYPE_7}', '${INSTTYPE_8}',
+ '${INSTTYPE_9}', '${INSTTYPE_10}', '${INSTTYPE_11}', '${INSTTYPE_12}', '${INSTTYPE_13}', '${INSTTYPE_14}',
+ '${INSTTYPE_15}', '${INSTTYPE_16}', '${INSTTYPE_17}', '${INSTTYPE_18}', '${INSTTYPE_19}', '${INSTTYPE_20}',
+ '${INSTTYPE_21}', '${INSTTYPE_22}', '${INSTTYPE_23}', '${INSTTYPE_24}', '${INSTTYPE_25}', '${INSTTYPE_26}',
+ '${INSTTYPE_27}', '${INSTTYPE_28}', '${INSTTYPE_29}', '${INSTTYPE_30}', '${INSTTYPE_31}', '${INSTTYPE_32}',
+ 'SetSectionInInstType', 'ClearSectionInInstType', 'SetSectionFlag', 'ClearSectionFlag', 'SectionFlagIsSet'
+ ),
+ 13 => array(
+ 'Colors.nsh', 'WHITE', 'BLACK', 'YELLOW', 'RED', 'GREEN', 'BLUE', 'MAGENTA', 'CYAN', 'rgb2hex'
+ ),
+ 14 => array(
+ 'FileFunc.nsh', '${Locate}', '${GetSize}', '${DriveSpace}', '${GetDrives}', '${GetTime}', '${GetFileAttributes}', '${GetFileVersion}', '${GetExeName}', '${GetExePath}', '${GetParameters}', '${GetOptions}', '${GetRoot}', '${GetParent}', '${GetFileName}', '${GetBaseName}', '${GetFileExt}', '${BannerTrimPath}', '${DirState}', '${RefreshShellIcons}'
+ ),
+ 15 => array(
+ 'TextFunc.nsh', '${LineFind}', '${LineRead}', '${FileReadFromEnd}', '${LineSum}', '${FileJoin}', '${TextCompare}', '${ConfigRead}', '${ConfigWrite}', '${FileRecode}', '${TrimNewLines}'
+ ),
+ 16 => array(
+ 'WordFunc.nsh', '${WordFind}', '${WordFind2X}', '${WordFind3X}', '${WordReplace}', '${WordAdd}', '${WordInsert}', '${StrFilter}', '${VersionCompare}', '${VersionConvert}'
+ )
+ ),
+ 'SYMBOLS' => array(
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false,
+ 6 => false,
+ 7 => false,
+ 8 => false,
+ 9 => false,
+ 10 => false,
+ 11 => false,
+ 12 => false,
+ 13 => false,
+ 14 => false,
+ 15 => false,
+ 16 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000066; font-weight:bold;',
+ 2 => 'color: #000066;',
+ 3 => 'color: #003366;',
+ 4 => 'color: #000099;',
+ 5 => 'color: #ff6600;',
+ 6 => 'color: #ff6600;',
+ 7 => 'color: #006600;',
+ 8 => 'color: #006600;',
+ 9 => 'color: #006600;',
+ 10 => 'color: #006600;',
+ 11 => 'color: #006600;',
+ 12 => 'color: #006600;',
+ 13 => 'color: #006600;',
+ 14 => 'color: #006600;',
+ 15 => 'color: #006600;',
+ 16 => 'color: #006600;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #666666; font-style: italic;',
+ 2 => 'color: #666666; font-style: italic;',
+ 'MULTI' => 'color: #666666; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #660066; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => ''
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #660066;'
+ ),
+ 'NUMBERS' => array(
+ 0 => ''
+ ),
+ 'METHODS' => array(
+ 0 => ''
+ ),
+ 'SYMBOLS' => array(
+ 0 => ''
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #660000;',
+ 1 => 'color: #660000;',
+ 2 => 'color: #660000;',
+ 3 => 'color: #660000;',
+ 4 => 'color: #660000;',
+ 5 => 'color: #660000;',
+ 6 => 'color: #660000;',
+ 7 => 'color: #000099;',
+ 8 => 'color: #003399;'
+ ),
+ 'SCRIPT' => array(
+ 0 => ''
+ )
+ ),
+ 'URLS' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => '',
+ 5 => '',
+ 6 => '',
+ 7 => '',
+ 8 => '',
+ 9 => '',
+ 10 => '',
+ 11 => '',
+ 12 => '',
+ 13 => '',
+ 14 => '',
+ 15 => '',
+ 16 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '\$\$',
+ 1 => '\$\\r',
+ 2 => '\$\\n',
+ 3 => '\$\\t',
+ 4 => '\$[a-zA-Z0-9_]+',
+ 5 => '\$\{.{1,256}\}',
+ 6 => '\$\\\(.{1,256}\\\)',
+ 7 => array(
+ GESHI_SEARCH => '([^:/\\\*\?\"\<\>\|\s]*?)(::)([^:/\\\*\?\"\<\>\|\s]*?)',
+ GESHI_REPLACE => '\\1',
+ GESHI_MODIFIERS => '',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => '\\2\\3'
+ ),
+ 8 => array(
+ GESHI_SEARCH => '([^:/\\\*\?\"\<\>\|\s]*?)(::)([^:/\\\*\?\"\<\>\|]*?\s)',
+ GESHI_REPLACE => '\\3',
+ GESHI_MODIFIERS => '',
+ GESHI_BEFORE => '\\1\\2',
+ GESHI_AFTER => ''
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/objc.php b/plugins/dokuwiki/inc/geshi/objc.php
new file mode 100644
index 0000000..a1abf43
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/objc.php
@@ -0,0 +1,241 @@
+<?php
+/*************************************************************************************
+ * objc.php
+ * --------
+ * Author: M. Uli Kusterer (witness.of.teachtext@gmx.net)
+ * Copyright: (c) 2004 M. Uli Kusterer, Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Objective C language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Objective C',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'if', 'return', 'while', 'case', 'continue', 'default',
+ 'do', 'else', 'for', 'switch', 'goto'
+ ),
+ 2 => array(
+ 'NULL', 'false', 'break', 'true', 'enum', 'nil', 'Nil', 'errno', 'EDOM',
+ 'ERANGE', 'FLT_RADIX', 'FLT_ROUNDS', 'FLT_DIG', 'DBL_DIG', 'LDBL_DIG',
+ 'FLT_EPSILON', 'DBL_EPSILON', 'LDBL_EPSILON', 'FLT_MANT_DIG', 'DBL_MANT_DIG',
+ 'LDBL_MANT_DIG', 'FLT_MAX', 'DBL_MAX', 'LDBL_MAX', 'FLT_MAX_EXP', 'DBL_MAX_EXP',
+ 'LDBL_MAX_EXP', 'FLT_MIN', 'DBL_MIN', 'LDBL_MIN', 'FLT_MIN_EXP', 'DBL_MIN_EXP',
+ 'LDBL_MIN_EXP', 'CHAR_BIT', 'CHAR_MAX', 'CHAR_MIN', 'SCHAR_MAX', 'SCHAR_MIN',
+ 'UCHAR_MAX', 'SHRT_MAX', 'SHRT_MIN', 'USHRT_MAX', 'INT_MAX', 'INT_MIN',
+ 'UINT_MAX', 'LONG_MAX', 'LONG_MIN', 'ULONG_MAX', 'HUGE_VAL', 'SIGABRT',
+ 'SIGFPE', 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM', 'SIG_DFL', 'SIG_ERR',
+ 'SIG_IGN', 'BUFSIZ', 'EOF', 'FILENAME_MAX', 'FOPEN_MAX', 'L_tmpnam', 'NULL',
+ 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'stdin', 'stdout', 'stderr',
+ 'EXIT_FAILURE', 'EXIT_SUCCESS', 'RAND_MAX', 'CLOCKS_PER_SEC'
+ ),
+ 3 => array(
+ 'printf', 'fprintf', 'snprintf', 'sprintf', 'assert',
+ 'isalnum', 'isalpha', 'isdigit', 'iscntrl', 'isgraph', 'islower', 'isprint',
+ 'ispunct', 'isspace', 'ispunct', 'isupper', 'isxdigit', 'tolower', 'toupper',
+ 'exp', 'log', 'log10', 'pow', 'sqrt', 'ceil', 'floor', 'fabs', 'ldexp',
+ 'frexp', 'modf', 'fmod', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2',
+ 'sinh', 'cosh', 'tanh', 'setjmp', 'longjmp', 'asin', 'acos', 'atan', 'atan2',
+ 'va_start', 'va_arg', 'va_end', 'offsetof', 'sizeof', 'fopen', 'freopen',
+ 'fflush', 'fclose', 'remove', 'rename', 'tmpfile', 'tmpname', 'setvbuf',
+ 'setbuf', 'vfprintf', 'vprintf', 'vsprintf', 'fscanf', 'scanf', 'sscanf',
+ 'fgetc', 'fgets', 'fputc', 'fputs', 'getc', 'getchar', 'gets', 'putc',
+ 'putchar', 'puts', 'ungetc', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind',
+ 'fgetpos', 'fsetpos', 'clearerr', 'feof', 'ferror', 'perror', 'abs', 'labs',
+ 'div', 'ldiv', 'atof', 'atoi', 'atol', 'strtod', 'strtol', 'strtoul', 'calloc',
+ 'malloc', 'realloc', 'free', 'abort', 'exit', 'atexit', 'system', 'getenv',
+ 'bsearch', 'qsort', 'rand', 'srand', 'strcpy', 'strncpy', 'strcat', 'strncat',
+ 'strcmp', 'strncmp', 'strcoll', 'strchr', 'strrchr', 'strspn', 'strcspn',
+ 'strpbrk', 'strstr', 'strlen', 'strerror', 'strtok', 'strxfrm', 'memcpy',
+ 'memmove', 'memcmp', 'memchr', 'memset', 'clock', 'time', 'difftime', 'mktime',
+ 'asctime', 'ctime', 'gmtime', 'localtime', 'strftime'
+ ),
+ 4 => array( // Data types:
+ 'auto', 'char', 'const', 'double', 'float', 'int', 'long',
+ 'register', 'short', 'signed', 'sizeof', 'static', 'string', 'struct',
+ 'typedef', 'union', 'unsigned', 'void', 'volatile', 'extern', 'jmp_buf',
+ 'signal', 'raise', 'va_list', 'ptrdiff_t', 'size_t', 'FILE', 'fpos_t',
+ 'div_t', 'ldiv_t', 'clock_t', 'time_t', 'tm',
+ // OpenStep/GNUstep/Cocoa:
+ 'SEL', 'id', 'NSRect', 'NSRange', 'NSPoint', 'NSZone', 'Class', 'IMP', 'BOOL',
+ // OpenStep/GNUstep/Cocoa @identifiers
+ '@selector', '@class', '@protocol', '@interface', '@implementation', '@end',
+ '@private', '@protected', '@public', '@try', '@throw', '@catch', '@finally',
+ '@encode', '@defs', '@synchronized'
+ ),
+ 5 => array( // OpenStep/GNUstep/Cocoa Foundation
+ 'NSAppleEventDescriptor', 'NSNetService', 'NSAppleEventManager',
+ 'NSNetServiceBrowser', 'NSAppleScript', 'NSNotification', 'NSArchiver',
+ 'NSNotificationCenter', 'NSArray', 'NSNotificationQueue', 'NSAssertionHandler',
+ 'NSNull', 'NSAttributedString', 'NSNumber', 'NSAutoreleasePool',
+ 'NSNumberFormatter', 'NSBundle', 'NSObject', 'NSCachedURLResponse',
+ 'NSOutputStream', 'NSCalendarDate', 'NSPipe', 'NSCharacterSet', 'NSPort',
+ 'NSClassDescription', 'NSPortCoder', 'NSCloneCommand', 'NSPortMessage',
+ 'NSCloseCommand', 'NSPortNameServer', 'NSCoder', 'NSPositionalSpecifier',
+ 'NSConditionLock', 'NSProcessInfo', 'NSConnection', 'NSPropertyListSerialization',
+ 'NSCountCommand', 'NSPropertySpecifier', 'NSCountedSet', 'NSProtocolChecker',
+ 'NSCreateCommand', 'NSProxy', 'NSData', 'NSQuitCommand', 'NSDate',
+ 'NSRandomSpecifier', 'NSDateFormatter', 'NSRangeSpecifier', 'NSDecimalNumber',
+ 'NSRecursiveLock', 'NSDecimalNumberHandler', 'NSRelativeSpecifier',
+ 'NSDeleteCommand', 'NSRunLoop', 'NSDeserializer', 'NSScanner', 'NSDictionary',
+ 'NSScriptClassDescription', 'NSDirectoryEnumerator', 'NSScriptCoercionHandler',
+ 'NSDistantObject', 'NSScriptCommand', 'NSDistantObjectRequest',
+ 'NSScriptCommandDescription', 'NSDistributedLock', 'NSScriptExecutionContext',
+ 'NSDistributedNotificationCenter', 'NSScriptObjectSpecifier', 'NSEnumerator',
+ 'NSScriptSuiteRegistry', 'NSError', 'NSScriptWhoseTest', 'NSException',
+ 'NSSerializer', 'NSExistsCommand', 'NSSet', 'NSFileHandle', 'NSSetCommand',
+ 'NSFileManager', 'NSSocketPort', 'NSFormatter', 'NSSocketPortNameServer',
+ 'NSGetCommand', 'NSSortDescriptor', 'NSHost', 'NSSpecifierTest', 'NSHTTPCookie',
+ 'NSSpellServer', 'NSHTTPCookieStorage', 'NSStream', 'NSHTTPURLResponse',
+ 'NSString', 'NSIndexSet', 'NSTask', 'NSIndexSpecifier', 'NSThread',
+ 'NSInputStream', 'NSTimer', 'NSInvocation', 'NSTimeZone', 'NSKeyedArchiver',
+ 'NSUnarchiver', 'NSKeyedUnarchiver', 'NSUndoManager', 'NSLock',
+ 'NSUniqueIDSpecifier', 'NSLogicalTest', 'NSURL', 'NSMachBootstrapServer',
+ 'NSURLAuthenticationChallenge', 'NSMachPort', 'NSURLCache', 'NSMessagePort',
+ 'NSURLConnection', 'NSMessagePortNameServer', 'NSURLCredential',
+ 'NSMethodSignature', 'NSURLCredentialStorage', 'NSMiddleSpecifier',
+ 'NSURLDownload', 'NSMoveCommand', 'NSURLHandle', 'NSMutableArray',
+ 'NSURLProtectionSpace', 'NSMutableAttributedString', 'NSURLProtocol',
+ 'NSMutableCharacterSet', 'NSURLRequest', 'NSMutableData', 'NSURLResponse',
+ 'NSMutableDictionary', 'NSUserDefaults', 'NSMutableIndexSet', 'NSValue',
+ 'NSMutableSet', 'NSValueTransformer', 'NSMutableString', 'NSWhoseSpecifier',
+ 'NSMutableURLRequest', 'NSXMLParser', 'NSNameSpecifier'
+ ),
+ 6 => array( // OpenStep/GNUstep/Cocoa AppKit
+ 'NSActionCell', 'NSOpenGLPixelFormat', 'NSAffineTransform', 'NSOpenGLView',
+ 'NSAlert', 'NSOpenPanel', 'NSAppleScript Additions', 'NSOutlineView',
+ 'NSApplication', 'NSPageLayout', 'NSArrayController', 'NSPanel',
+ 'NSATSTypesetter', 'NSParagraphStyle', 'NSPasteboard', 'NSBezierPath',
+ 'NSPDFImageRep', 'NSBitmapImageRep', 'NSPICTImageRep', 'NSBox', 'NSPopUpButton',
+ 'NSBrowser', 'NSPopUpButtonCell', 'NSBrowserCell', 'NSPrinter', 'NSPrintInfo',
+ 'NSButton', 'NSPrintOperation', 'NSButtonCell', 'NSPrintPanel', 'NSCachedImageRep',
+ 'NSProgressIndicator', 'NSCell', 'NSQuickDrawView', 'NSClipView', 'NSResponder',
+ 'NSRulerMarker', 'NSColor', 'NSRulerView', 'NSColorList', 'NSSavePanel',
+ 'NSColorPanel', 'NSScreen', 'NSColorPicker', 'NSScroller', 'NSColorWell',
+ 'NSScrollView', 'NSComboBox', 'NSSearchField', 'NSComboBoxCell',
+ 'NSSearchFieldCell', 'NSControl', 'NSSecureTextField', 'NSController',
+ 'NSSecureTextFieldCell', 'NSCursor', 'NSSegmentedCell', 'NSCustomImageRep',
+ 'NSSegmentedControl', 'NSDocument', 'NSShadow', 'NSDocumentController',
+ 'NSSimpleHorizontalTypesetter', 'NSDrawer', 'NSSlider', 'NSEPSImageRep',
+ 'NSSliderCell', 'NSEvent', 'NSSound', 'NSFileWrapper', 'NSSpeechRecognizer',
+ 'NSFont', 'NSSpeechSynthesizer', 'NSFontDescriptor', 'NSSpellChecker',
+ 'NSFontManager', 'NSSplitView', 'NSFontPanel', 'NSStatusBar', 'NSForm',
+ 'NSStatusItem', 'NSFormCell', 'NSStepper', 'NSGlyphGenerator', 'NSStepperCell',
+ 'NSGlyphInfo', 'NSGraphicsContext', 'NSTableColumn', 'NSHelpManager',
+ 'NSTableHeaderCell', 'NSImage', 'NSTableHeaderView', 'NSImageCell', 'NSTableView',
+ 'NSImageRep', 'NSTabView', 'NSImageView', 'NSTabViewItem', 'NSInputManager',
+ 'NSText', 'NSInputServer', 'NSTextAttachment', 'NSLayoutManager',
+ 'NSTextAttachmentCell', 'NSMatrix', 'NSTextContainer', 'NSMenu', 'NSTextField',
+ 'NSMenuItem', 'NSTextFieldCell', 'NSMenuItemCell', 'NSTextStorage', 'NSMenuView',
+ 'NSTextTab', 'NSMovie', 'NSTextView', 'NSMovieView', 'NSToolbar', 'NSToolbarItem',
+ 'NSMutableParagraphStyle', 'NSTypesetter', 'NSNib', 'NSNibConnector',
+ 'NSUserDefaultsController', 'NSNibControlConnector', 'NSView',
+ 'NSNibOutletConnector', 'NSWindow', 'NSObjectController', 'NSWindowController',
+ 'NSOpenGLContext', 'NSWorkspace', 'NSOpenGLPixelBuffer'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '=', '+', '-', '*', '/', '!', '%', '^', '&', ':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false,
+ 6 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0000ff;',
+ 2 => 'color: #0000ff;',
+ 3 => 'color: #0000dd;',
+ 4 => 'color: #0000ff;',
+ 5 => 'color: #0000ff;',
+ 6 => 'color: #0000ff;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #ff0000;',
+ 2 => 'color: #339900;',
+ 'MULTI' => 'color: #ff0000; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #666666; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #002200;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #666666;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #0000dd;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #002200;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.opengroup.org/onlinepubs/009695399/functions/{FNAME}.html',
+ 4 => '',
+ 5 => 'http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/{FNAME}.html',
+ 6 => 'http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/{FNAME}.html'
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/ocaml-brief.php b/plugins/dokuwiki/inc/geshi/ocaml-brief.php
new file mode 100644
index 0000000..940bd53
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/ocaml-brief.php
@@ -0,0 +1,114 @@
+<?php
+/*************************************************************************************
+ * ocaml.php
+ * ----------
+ * Author: Flaie (fireflaie@gmail.com)
+ * Copyright: (c) 2005 Flaie, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.11.2.4 $
+ * Date Started: 2005/08/27
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * OCaml (Objective Caml) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/08/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'OCaml',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array('(*' => '*)'),
+ 'CASE_KEYWORDS' => 0,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => "",
+ 'KEYWORDS' => array(
+ /* main OCaml keywords */
+ 1 => array(
+ 'and', 'As', 'asr', 'begin', 'Class', 'Closed', 'constraint', 'do', 'done', 'downto', 'else',
+ 'end', 'exception', 'external', 'failwith', 'false', 'flush', 'for', 'fun', 'function', 'functor',
+ 'if', 'in', 'include', 'inherit', 'incr', 'land', 'let', 'load', 'los', 'lsl', 'lsr', 'lxor',
+ 'match', 'method', 'mod', 'module', 'mutable', 'new', 'not', 'of', 'open', 'option', 'or', 'parser',
+ 'private', 'ref', 'rec', 'raise', 'regexp', 'sig', 'struct', 'stdout', 'stdin', 'stderr', 'then',
+ 'to', 'true', 'try', 'type', 'val', 'virtual', 'when', 'while', 'with'
+ )
+ ),
+ /* highlighting symbols is really important in OCaml */
+ 'SYMBOLS' => array(
+ ';', '!', ':', '.', '=', '%', '^', '*', '-', '/', '+',
+ '>', '<', '(', ')', '[', ']', '&', '|', '#', "'"
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #06c; font-weight: bold;' /* nice blue */
+ ),
+ 'COMMENTS' => array(
+ 'MULTI' => 'color: #5d478b; font-style: italic;' /* light purple */
+ ),
+ 'ESCAPE_CHAR' => array(
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #6c6;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #3cb371;' /* nice green */
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #c6c;' /* pink */
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #060;' /* dark green */
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #a52a2a;' /* maroon */
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/ocaml.php b/plugins/dokuwiki/inc/geshi/ocaml.php
new file mode 100644
index 0000000..013cac3
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/ocaml.php
@@ -0,0 +1,163 @@
+<?php
+/*************************************************************************************
+ * ocaml.php
+ * ----------
+ * Author: Flaie (fireflaie@gmail.com)
+ * Copyright: (c) 2005 Flaie, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.11.2.4 $
+ * Date Started: 2005/08/27
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * OCaml (Objective Caml) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/08/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'OCaml',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array('(*' => '*)'),
+ 'CASE_KEYWORDS' => 0,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => "",
+ 'KEYWORDS' => array(
+ /* main OCaml keywords */
+ 1 => array(
+ 'and', 'As', 'asr', 'begin', 'Class', 'Closed', 'constraint', 'do', 'done', 'downto', 'else',
+ 'end', 'exception', 'external', 'failwith', 'false', 'flush', 'for', 'fun', 'function', 'functor',
+ 'if', 'in', 'include', 'inherit', 'incr', 'land', 'let', 'load', 'los', 'lsl', 'lsr', 'lxor',
+ 'match', 'method', 'mod', 'module', 'mutable', 'new', 'not', 'of', 'open', 'option', 'or', 'parser',
+ 'private', 'ref', 'rec', 'raise', 'regexp', 'sig', 'struct', 'stdout', 'stdin', 'stderr', 'then',
+ 'to', 'true', 'try', 'type', 'val', 'virtual', 'when', 'while', 'with'
+ ),
+ /* define names of main librarys, so we can link to it */
+ 2 => array(
+ 'Arg', 'Arith_status', 'Array', 'ArrayLabels', 'Big_int', 'Bigarray', 'Buffer', 'Callback',
+ 'CamlinternalOO', 'Char', 'Complex', 'Condition', 'Dbm', 'Digest', 'Dynlink', 'Event',
+ 'Filename', 'Format', 'Gc', 'Genlex', 'Graphics', 'GraphicsX11', 'Hashtbl', 'Int32', 'Int64',
+ 'Lazy', 'Lexing', 'List', 'ListLabels', 'Map', 'Marshal', 'MoreLabels', 'Mutex', 'Nativeint',
+ 'Num', 'Obj', 'Oo', 'Parsing', 'Pervasives', 'Printexc', 'Printf', 'Queue', 'Random', 'Scanf',
+ 'Set', 'Sort', 'Stack', 'StdLabels', 'Str', 'Stream', 'String', 'StringLabels', 'Sys', 'Thread',
+ 'ThreadUnix', 'Tk'
+ ),
+ /* just link to the Pervasives functions library, cause it's the default opened library when starting OCaml */
+ 3 => array(
+ 'raise', 'invalid_arg', 'failwith', 'compare', 'min', 'max', 'succ', 'pred', 'mod', 'abs',
+ 'max_int', 'min_int', 'sqrt', 'exp', 'log', 'log10', 'cos', 'sin', 'tan', 'acos', 'asin',
+ 'atan', 'atan2', 'cosh', 'sinh', 'tanh', 'ceil', 'floor', 'abs_float', 'mod_float', 'frexp',
+ 'ldexp', 'modf', 'float', 'float_of_int', 'truncate', 'int_of_float', 'infinity', 'nan',
+ 'max_float', 'min_float', 'epsilon_float', 'classify_float', 'int_of_char', 'char_of_int',
+ 'ignore', 'string_of_bool', 'bool_of_string', 'string_of_int', 'int_of_string',
+ 'string_of_float', 'float_of_string', 'fst', 'snd', 'stdin', 'stdout', 'stderr', 'print_char',
+ 'print_string', 'print_int', 'print_float', 'print_endline', 'print_newline', 'prerr_char',
+ 'prerr_string', 'prerr_int', 'prerr_float', 'prerr_endline', 'prerr_newline', 'read_line',
+ 'read_int', 'read_float', 'open_out', 'open_out_bin', 'open_out_gen', 'flush', 'flush_all',
+ 'output_char', 'output_string', 'output', 'output_byte', 'output_binary_int', 'output_value',
+ 'seek_out', 'pos_out', 'out_channel_length', 'close_out', 'close_out_noerr', 'set_binary_mode_out',
+ 'open_in', 'open_in_bin', 'open_in_gen', 'input_char', 'input_line', 'input', 'really_input',
+ 'input_byte', 'input_binary_int', 'input_value', 'seek_in', 'pos_in', 'in_channel_length',
+ 'close_in', 'close_in_noerr', 'set_binary_mode_in', 'incr', 'decr', 'string_of_format',
+ 'format_of_string', 'exit', 'at_exit'
+ ),
+ /* here Pervasives Types */
+ 4 => array (
+ 'fpclass', 'in_channel', 'out_channel', 'open_flag', 'Sys_error', 'ref', 'format'
+ ),
+ /* finally Pervasives Exceptions */
+ 5 => array (
+ 'Exit', 'Invalid_Argument', 'Failure', 'Division_by_zero'
+ )
+ ),
+ /* highlighting symbols is really important in OCaml */
+ 'SYMBOLS' => array(
+ ';', '!', ':', '.', '=', '%', '^', '*', '-', '/', '+',
+ '>', '<', '(', ')', '[', ']', '&', '|', '#', "'"
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => true, /* functions name are case seinsitive */
+ 3 => true, /* types name too */
+ 4 => true /* finally exceptions too */
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #06c; font-weight: bold;' /* nice blue */
+ ),
+ 'COMMENTS' => array(
+ 'MULTI' => 'color: #5d478b; font-style: italic;' /* light purple */
+ ),
+ 'ESCAPE_CHAR' => array(
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #6c6;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #3cb371;' /* nice green */
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #c6c;' /* pink */
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #060;' /* dark green */
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #a52a2a;' /* maroon */
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ /* some of keywords are Pervasives functions (land, lxor, asr, ...) */
+ 1 => '',
+ /* link to the wanted library */
+ 2 => 'http://caml.inria.fr/pub/docs/manual-ocaml/libref/{FNAME}.html',
+ /* link to Pervasives functions */
+ 3 => 'http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#VAL{FNAME}',
+ /* link to Pervasives type */
+ 4 => 'http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#TYPE{FNAME}',
+ /* link to Pervasives exceptions */
+ 5 => 'http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#EXCEPTION{FNAME}'
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/oobas.php b/plugins/dokuwiki/inc/geshi/oobas.php
new file mode 100644
index 0000000..2be8fa6
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/oobas.php
@@ -0,0 +1,132 @@
+<?php
+/*************************************************************************************
+ * oobas.php
+ * ---------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * OpenOffice.org Basic language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'OpenOffice.org Basic',
+ 'COMMENT_SINGLE' => array(1 => "'"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'dim','private','public','global','as','if','redim','true','set',
+ 'byval',
+ 'false','bool','double','integer','long','object','single','variant',
+ 'msgbox','print','inputbox','green','blue','red','qbcolor',
+ 'rgb','open','close','reset','freefile','get','input','line',
+ 'put','write','loc','seek','eof','lof','chdir','chdrive',
+ 'curdir','dir','fileattr','filecopy','filedatetime','fileexists',
+ 'filelen','getattr','kill','mkdir','name','rmdir','setattr',
+ 'dateserial','datevalue','day','month','weekday','year','cdatetoiso',
+ 'cdatefromiso','hour','minute','second','timeserial','timevalue',
+ 'date','now','time','timer','erl','err','error','on','error','goto','resume',
+ 'and','eqv','imp','not','or','xor','mod','','atn','cos','sin','tan','log',
+ 'exp','rnd','randomize','sqr','fix','int','abs','sgn','hex','oct',
+ 'it','then','else','select','case','iif','do','loop','for','next',
+ 'while','wend','gosub','return','goto','on','goto','call','choose','declare',
+ 'end','exit','freelibrary','function','rem','stop','sub','switch','with',
+ 'cbool','cdate','cdbl','cint','clng','const','csng','cstr','defbool',
+ 'defdate','defdbl','defint','deflng','asc','chr','str','val','cbyte',
+ 'space','string','format','lcase','left','lset','ltrim','mid','right',
+ 'rset','rtrim','trim','ucase','split','join','converttourl','convertfromurl',
+ 'instr','len','strcomp','beep','shell','wait','getsystemticks','environ',
+ 'getsolarversion','getguitype','twipsperpixelx','twipsperpixely',
+ 'createunostruct','createunoservice','getprocessservicemanager',
+ 'createunodialog','createunolistener','createunovalue','thiscomponent',
+ 'globalscope'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/oracle8.php b/plugins/dokuwiki/inc/geshi/oracle8.php
new file mode 100644
index 0000000..6ef798c
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/oracle8.php
@@ -0,0 +1,489 @@
+<?php
+/*************************************************************************************
+ * oracle8.php
+ * -----------
+ * Author: Guy Wicks (Guy.Wicks@rbs.co.uk)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Oracle 8 language file for GeSHi
+ *
+ * CHANGES
+ * -------
+ * 2005/01/29 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Oracle 8 SQL',
+ 'COMMENT_SINGLE' => array(1 => '--'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_UPPER,
+ 'QUOTEMARKS' => array("'", '"', '`'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+//Put your package names here - e.g. select distinct ''''|| lower(name) || ''',' from user_source;
+ 6 => array(
+ ),
+
+//Put your table names here - e.g. select distinct ''''|| lower(table_name) || ''',' from user_tables;
+ 5 => array(
+ ),
+
+//Put your view names here - e.g. select distinct ''''|| lower(view_name) || ''',' from user_views;
+ 4 => array(
+ ),
+
+//Put your table field names here - e.g. select distinct ''''|| lower(column_name) || ''',' from user_tab_columns;
+ 3 => array(
+ ),
+//Put ORACLE reserved keywords here (8.1.7). I like mine uppercase.
+ 1 => array(
+ 'ABS',
+ 'ACCESS',
+ 'ACOS',
+ 'ADD',
+ 'ADD_MONTHS',
+ 'ALL',
+ 'ALTER',
+ 'ANALYZE',
+ 'AND',
+ 'ANY',
+ 'ARRAY',
+ 'AS',
+ 'ASC',
+ 'ASCII',
+ 'ASIN',
+ 'ASSOCIATE',
+ 'AT',
+ 'ATAN',
+ 'ATAN2',
+ 'AUDIT',
+ 'AUTHID',
+ 'AVG',
+ 'BEGIN',
+ 'BETWEEN',
+ 'BFILENAME',
+ 'BINARY_INTEGER',
+ 'BITAND',
+ 'BODY',
+ 'BOOLEAN',
+ 'BULK',
+ 'BY',
+ 'CALL',
+ 'CASCADE',
+ 'CASE',
+ 'CEIL',
+ 'CHAR',
+ 'CHAR_BASE',
+ 'CHARTOROWID',
+ 'CHECK',
+ 'CHR',
+ 'CLOSE',
+ 'CLUSTER',
+ 'COALESCE',
+ 'COLLECT',
+ 'COLUMN',
+ 'COMMENT',
+ 'COMMIT',
+ 'COMPRESS',
+ 'CONCAT',
+ 'CONNECT',
+ 'CONSTANT',
+ 'CONSTRAINT',
+ 'CONSTRAINTS',
+ 'CONTEXT',
+ 'CONTROLFILE',
+ 'CONVERT',
+ 'CORR',
+ 'COS',
+ 'COSH',
+ 'COST',
+ 'COUNT',
+ 'COVAR_POP',
+ 'COVAR_SAMP',
+ 'CREATE',
+ 'CUME_DIST',
+ 'CURRENT',
+ 'CURRVAL',
+ 'CURSOR',
+ 'DATABASE',
+ 'DATE',
+ 'DAY',
+ 'DECIMAL',
+ 'DECLARE',
+ 'DECODE',
+ 'DEFAULT',
+ 'DELETE',
+ 'DENSE_RANK',
+ 'DEREF',
+ 'DESC',
+ 'DIMENSION',
+ 'DIRECTORY',
+ 'DISASSOCIATE',
+ 'DISTINCT',
+ 'DO',
+ 'DROP',
+ 'DUMP',
+ 'ELSE',
+ 'ELSIF',
+ 'EMPTY_BLOB',
+ 'EMPTY_CLOB',
+ 'END',
+ 'EXCEPTION',
+ 'EXCLUSIVE',
+ 'EXEC',
+ 'EXECUTE',
+ 'EXISTS',
+ 'EXIT',
+ 'EXP',
+ 'EXPLAIN',
+ 'EXTENDS',
+ 'EXTRACT',
+ 'FALSE',
+ 'FETCH',
+ 'FILE',
+ 'FIRST_VALUE',
+ 'FLOAT',
+ 'FLOOR',
+ 'FOR',
+ 'FORALL',
+ 'FROM',
+ 'FUNCTION',
+ 'GOTO',
+ 'GRANT',
+ 'GREATEST',
+ 'GROUP',
+ 'GROUPING',
+ 'HAVING',
+ 'HEAP',
+ 'HEXTORAW',
+ 'HOUR',
+ 'IDENTIFIED',
+ 'IF',
+ 'IMMEDIATE',
+ 'IN',
+ 'INCREMENT',
+ 'INDEX',
+ 'INDEXTYPE',
+ 'INDICATOR',
+ 'INITCAP',
+ 'INITIAL',
+ 'INSERT',
+ 'INSTR',
+ 'INSTRB',
+ 'INTEGER',
+ 'INTERFACE',
+ 'INTERSECT',
+ 'INTERVAL',
+ 'INTO',
+ 'IS',
+ 'ISOLATION',
+ 'JAVA',
+ 'KEY',
+ 'LAG',
+ 'LAST_DAY',
+ 'LAST_VALUE',
+ 'LEAD',
+ 'LEAST',
+ 'LENGTH',
+ 'LENGTHB',
+ 'LEVEL',
+ 'LIBRARY',
+ 'LIKE',
+ 'LIMITED',
+ 'LINK',
+ 'LN',
+ 'LOCK',
+ 'LOG',
+ 'LONG',
+ 'LOOP',
+ 'LOWER',
+ 'LPAD',
+ 'LTRIM',
+ 'MAKE_REF',
+ 'MATERIALIZED',
+ 'MAX',
+ 'MAXEXTENTS',
+ 'MIN',
+ 'MINUS',
+ 'MINUTE',
+ 'MLSLABEL',
+ 'MOD',
+ 'MODE',
+ 'MODIFY',
+ 'MONTH',
+ 'MONTHS_BETWEEN',
+ 'NATURAL',
+ 'NATURALN',
+ 'NEW',
+ 'NEW_TIME',
+ 'NEXT_DAY',
+ 'NEXTVAL',
+ 'NLS_CHARSET_DECL_LEN',
+ 'NLS_CHARSET_ID',
+ 'NLS_CHARSET_NAME',
+ 'NLS_INITCAP',
+ 'NLS_LOWER',
+ 'NLS_UPPER',
+ 'NLSSORT',
+ 'NOAUDIT',
+ 'NOCOMPRESS',
+ 'NOCOPY',
+ 'NOT',
+ 'NOWAIT',
+ 'NTILE',
+ 'NULL',
+ 'NULLIF',
+ 'NUMBER',
+ 'NUMBER_BASE',
+ 'NUMTODSINTERVAL',
+ 'NUMTOYMINTERVAL',
+ 'NVL',
+ 'NVL2',
+ 'OCIROWID',
+ 'OF',
+ 'OFFLINE',
+ 'ON',
+ 'ONLINE',
+ 'OPAQUE',
+ 'OPEN',
+ 'OPERATOR',
+ 'OPTION',
+ 'OR',
+ 'ORDER',
+ 'ORGANIZATION',
+ 'OTHERS',
+ 'OUT',
+ 'OUTLINE',
+ 'PACKAGE',
+ 'PARTITION',
+ 'PCTFREE',
+ 'PERCENT_RANK',
+ 'PLAN',
+ 'PLS_INTEGER',
+ 'POSITIVE',
+ 'POSITIVEN',
+ 'POWER',
+ 'PRAGMA',
+ 'PRIMARY',
+ 'PRIOR',
+ 'PRIVATE',
+ 'PRIVILEGES',
+ 'PROCEDURE',
+ 'PROFILE',
+ 'PUBLIC',
+ 'RAISE',
+ 'RANGE',
+ 'RANK',
+ 'RATIO_TO_REPORT',
+ 'RAW',
+ 'RAWTOHEX',
+ 'REAL',
+ 'RECORD',
+ 'REF',
+ 'REFTOHEX',
+ 'REGR_AVGX',
+ 'REGR_AVGY',
+ 'REGR_COUNT',
+ 'REGR_INTERCEPT',
+ 'REGR_R2',
+ 'REGR_SLOPE',
+ 'REGR_SXX',
+ 'REGR_SXY',
+ 'REGR_SYY',
+ 'RELEASE',
+ 'RENAME',
+ 'REPLACE',
+ 'RESOURCE',
+ 'RETURN',
+ 'RETURNING',
+ 'REVERSE',
+ 'REVOKE',
+ 'ROLE',
+ 'ROLLBACK',
+ 'ROUND',
+ 'ROW',
+ 'ROW_NUMBER',
+ 'ROWID',
+ 'ROWIDTOCHAR',
+ 'ROWNUM',
+ 'ROWS',
+ 'ROWTYPE',
+ 'RPAD',
+ 'RTRIM',
+ 'SAVEPOINT',
+ 'SCHEMA',
+ 'SECOND',
+ 'SEGMENT',
+ 'SELECT',
+ 'SEPERATE',
+ 'SEQUENCE',
+ 'SESSION',
+ 'SET',
+ 'SHARE',
+ 'SIGN',
+ 'SIN',
+ 'SINH',
+ 'SIZE',
+ 'SMALLINT',
+ 'SOUNDEX',
+ 'SPACE',
+ 'SQL',
+ 'SQLCODE',
+ 'SQLERRM',
+ 'SQRT',
+ 'START',
+ 'STATISTICS',
+ 'STDDEV',
+ 'STDDEV_POP',
+ 'STDDEV_SAMP',
+ 'STOP',
+ 'SUBSTR',
+ 'SUBSTRB',
+ 'SUBTYPE',
+ 'SUCCESSFUL',
+ 'SUM',
+ 'SYNONYM',
+ 'SYS_CONTEXT',
+ 'SYS_GUID',
+ 'SYSDATE',
+ 'SYSTEM',
+ 'TABLE',
+ 'TABLESPACE',
+ 'TAN',
+ 'TANH',
+ 'TEMPORARY',
+ 'THEN',
+ 'TIME',
+ 'TIMESTAMP',
+ 'TIMEZONE_ABBR',
+ 'TIMEZONE_HOUR',
+ 'TIMEZONE_MINUTE',
+ 'TIMEZONE_REGION',
+ 'TIMING',
+ 'TO',
+ 'TO_CHAR',
+ 'TO_DATE',
+ 'TO_LOB',
+ 'TO_MULTI_BYTE',
+ 'TO_NUMBER',
+ 'TO_SINGLE_BYTE',
+ 'TRANSACTION',
+ 'TRANSLATE',
+ 'TRIGGER',
+ 'TRIM',
+ 'TRUE',
+ 'TRUNC',
+ 'TRUNCATE',
+ 'TYPE',
+ 'UI',
+ 'UID',
+ 'UNION',
+ 'UNIQUE',
+ 'UPDATE',
+ 'UPPER',
+ 'USE',
+ 'USER',
+ 'USERENV',
+ 'USING',
+ 'VALIDATE',
+ 'VALUE',
+ 'VALUES',
+ 'VAR_POP',
+ 'VAR_SAMP',
+ 'VARCHAR',
+ 'VARCHAR2',
+ 'VARIANCE',
+ 'VIEW',
+ 'VSIZE',
+ 'WHEN',
+ 'WHENEVER',
+ 'WHERE',
+ 'WHILE',
+ 'WITH',
+ 'WORK',
+ 'WRITE',
+ 'YEAR',
+ 'ZONE'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '=', '<', '>', '|'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #993333; font-weight: bold; text-transform: uppercase;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #ff0000;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/pascal.php b/plugins/dokuwiki/inc/geshi/pascal.php
new file mode 100644
index 0000000..40f1c7b
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/pascal.php
@@ -0,0 +1,145 @@
+<?php
+/*************************************************************************************
+ * pascal.php
+ * ----------
+ * Author: Tux (tux@inamil.cz)
+ * Copyright: (c) 2004 Tux (http://tux.a4.cz/), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2004/07/26
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Pascal language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.2)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.1)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.0)
+ * - Added support for symbols
+ * 2004/07/27 (0.9.1)
+ * - Pascal is OO language. Some new words.
+ * 2004/07/26 (0.9.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Pascal',
+ 'COMMENT_SINGLE' => array(1 => '//'),
+ 'COMMENT_MULTI' => array('{' => '}','(*' => '*)'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'if', 'while', 'until', 'repeat', 'default',
+ 'do', 'else', 'for', 'switch', 'goto','label','asm','begin','end',
+ 'assembler','case', 'downto', 'to','div','mod','far','forward','in','inherited',
+ 'inline','interrupt','label','library','not','var','of','then','stdcall',
+ 'cdecl','end.','raise','try','except','name','finally','resourcestring','override','overload',
+ 'default','public','protected','private','property','published','stored','catch'
+ ),
+ 2 => array(
+ 'nil', 'false', 'break', 'true', 'function', 'procedure','implementation','interface',
+ 'unit','program','initialization','finalization','uses'
+ ),
+ 3 => array(
+ 'abs', 'absolute','and','arc','arctan','chr','constructor','destructor',
+ 'dispose','cos','eof','eoln','exp','get','index','ln','new','xor','write','writeln',
+ 'shr','sin','sqrt','succ','pred','odd','read','readln','ord','ordinal','blockread','blockwrite'
+ ),
+ 4 => array(
+ 'array', 'char', 'const', 'boolean', 'real', 'integer', 'longint',
+ 'word', 'shortint', 'record','byte','bytebool','string',
+ 'type','object','export','exports','external','file','longbool','pointer','set',
+ 'packed','ansistring','union'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => '',
+ 4 => 'color: #993333;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #339933;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #202020;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => '',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/perl.php b/plugins/dokuwiki/inc/geshi/perl.php
new file mode 100644
index 0000000..6398f2c
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/perl.php
@@ -0,0 +1,169 @@
+<?php
+/*************************************************************************************
+ * perl.php
+ * --------
+ * Author: Andreas Gohr (andi@splitbrain.org), Ben Keen (ben.keen@gmail.com)
+ * Copyright: (c) 2004 Andreas Gohr, Ben Keen (http://www.benjaminkeen.org/), Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/08/20
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Perl language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2006/01/05 (1.0.2)
+ * - Used hardescape feature for ' strings (Cliff Stanford)
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/08/20 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * LABEL:
+ * * string comparison operators
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Perl',
+ 'COMMENT_SINGLE' => array(1 => '#'),
+ 'COMMENT_MULTI' => array( '=pod' => '=cut'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'HARDQUOTE' => array("'", "'"), // An optional 2-element array defining the beginning and end of a hard-quoted string
+ 'HARDESCAPE' => array('\\\'', "\\\\"), // Things that must still be escaped inside a hard-quoted string
+ // If HARDQUOTE is defined, HARDESCAPE must be defined
+ // This will not work unless the first character of each element is either in the
+ // QUOTEMARKS array or is the ESCAPE_CHAR
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'case', 'do', 'else', 'elsif', 'for', 'if', 'then', 'until', 'while', 'foreach', 'my',
+ 'or', 'and', 'unless', 'next', 'last', 'redo', 'not', 'our',
+ 'reset', 'continue','and', 'cmp', 'ne'
+ ),
+ 2 => array(
+ 'use', 'sub', 'new', '__END__', '__DATA__', '__DIE__', '__WARN__', 'BEGIN',
+ 'STDIN', 'STDOUT', 'STDERR'
+ ),
+ 3 => array(
+ 'abs', 'accept', 'alarm', 'atan2', 'bind', 'binmode', 'bless',
+ 'caller', 'chdir', 'chmod', 'chomp', 'chop', 'chown', 'chr',
+ 'chroot', 'close', 'closedir', 'connect', 'continue', 'cos',
+ 'crypt', 'dbmclose', 'dbmopen', 'defined', 'delete', 'die',
+ 'dump', 'each', 'endgrent', 'endhostent', 'endnetent', 'endprotoent',
+ 'endpwent', 'endservent', 'eof', 'eval', 'exec', 'exists', 'exit',
+ 'exp', 'fcntl', 'fileno', 'flock', 'fork', 'format', 'formline',
+ 'getc', 'getgrent', 'getgrgid', 'getgrnam', 'gethostbyaddr',
+ 'gethostbyname', 'gethostent', 'getlogin', 'getnetbyaddr', 'getnetbyname',
+ 'getnetent', 'getpeername', 'getpgrp', 'getppid', 'getpriority',
+ 'getprotobyname', 'getprotobynumber', 'getprotoent', 'getpwent',
+ 'getpwnam', 'getpwuid', 'getservbyname', 'getservbyport', 'getservent',
+ 'getsockname', 'getsockopt', 'glob', 'gmtime', 'goto', 'grep',
+ 'hex', 'import', 'index', 'int', 'ioctl', 'join', 'keys', 'kill',
+ 'last', 'lc', 'lcfirst', 'length', 'link', 'listen', 'local',
+ 'localtime', 'log', 'lstat', 'm', 'map', 'mkdir', 'msgctl', 'msgget',
+ 'msgrcv', 'msgsnd', 'my', 'next', 'no', 'oct', 'open', 'opendir',
+ 'ord', 'our', 'pack', 'package', 'pipe', 'pop', 'pos', 'print',
+ 'printf', 'prototype', 'push', 'qq', 'qr', 'quotemeta', 'qw',
+ 'qx', 'q', 'rand', 'read', 'readdir', 'readline', 'readlink', 'readpipe',
+ 'recv', 'redo', 'ref', 'rename', 'require', 'return',
+ 'reverse', 'rewinddir', 'rindex', 'rmdir', 's', 'scalar', 'seek',
+ 'seekdir', 'select', 'semctl', 'semget', 'semop', 'send', 'setgrent',
+ 'sethostent', 'setnetent', 'setpgrp', 'setpriority', 'setprotoent',
+ 'setpwent', 'setservent', 'setsockopt', 'shift', 'shmctl', 'shmget',
+ 'shmread', 'shmwrite', 'shutdown', 'sin', 'sleep', 'socket', 'socketpair',
+ 'sort', 'splice', 'split', 'sprintf', 'sqrt', 'srand', 'stat',
+ 'study', 'substr', 'symlink', 'syscall', 'sysopen', 'sysread',
+ 'sysseek', 'system', 'syswrite', 'tell', 'telldir', 'tie', 'tied',
+ 'time', 'times', 'tr', 'truncate', 'uc', 'ucfirst', 'umask', 'undef',
+ 'unlink', 'unpack', 'unshift', 'untie', 'utime', 'values',
+ 'vec', 'wait', 'waitpid', 'wantarray', 'warn', 'write', 'y'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '!', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0000ff;',
+ 4 => 'color: #009999;',
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 3 => 'http://www.perldoc.com/perl5.6/pod/func/{FNAME}.html'
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '-&gt;',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ 0 => '[\\$%@]+[a-zA-Z_][a-zA-Z0-9_]*',
+ 4 => '&lt;[a-zA-Z_][a-zA-Z0-9_]*&gt;',
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/php-brief.php b/plugins/dokuwiki/inc/geshi/php-brief.php
new file mode 100644
index 0000000..6527112
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/php-brief.php
@@ -0,0 +1,162 @@
+<?php
+/*************************************************************************************
+ * php-brief.php
+ * -------------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/06/02
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * PHP language file for GeSHi (brief version).
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * - Fixed &new problem
+ * 2004/10/27 (1.0.2)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/07/14)
+ * -------------------------
+ * * Remove more functions that are hardly used
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'PHP',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'include', 'require', 'include_once', 'require_once',
+ 'for', 'as', 'foreach', 'if', 'elseif', 'else', 'while', 'do', 'endwhile', 'endif', 'switch', 'case', 'endswitch',
+ 'return', 'break'
+ ),
+ 2 => array(
+ 'null', '__LINE__', '__FILE__',
+ 'false', '&lt;?php', '?&gt;',
+ 'true', 'var', 'default',
+ 'function', 'class', 'new', '&amp;new', 'public', 'private', 'interface', 'extends',
+ ),
+ 3 => array(
+ 'func_num_args', 'func_get_arg', 'func_get_args', 'strlen', 'strcmp', 'strncmp', 'strcasecmp', 'strncasecmp', 'each', 'error_reporting', 'define', 'defined',
+ 'trigger_error', 'user_error', 'set_error_handler', 'restore_error_handler', 'get_declared_classes', 'get_loaded_extensions',
+ 'extension_loaded', 'get_extension_funcs', 'debug_backtrace',
+ 'constant', 'bin2hex', 'sleep', 'usleep', 'time', 'mktime', 'gmmktime', 'strftime', 'gmstrftime', 'strtotime', 'date', 'gmdate', 'getdate', 'localtime', 'checkdate', 'flush', 'wordwrap', 'htmlspecialchars', 'htmlentities', 'html_entity_decode', 'md5', 'md5_file', 'crc32', 'getimagesize', 'image_type_to_mime_type', 'phpinfo', 'phpversion', 'phpcredits', 'strnatcmp', 'strnatcasecmp', 'substr_count', 'strspn', 'strcspn', 'strtok', 'strtoupper', 'strtolower', 'strpos', 'strrpos', 'strrev', 'hebrev', 'hebrevc', 'nl2br', 'basename', 'dirname', 'pathinfo', 'stripslashes', 'stripcslashes', 'strstr', 'stristr', 'strrchr', 'str_shuffle', 'str_word_count', 'strcoll', 'substr', 'substr_replace', 'quotemeta', 'ucfirst', 'ucwords', 'strtr', 'addslashes', 'addcslashes', 'rtrim', 'str_replace', 'str_repeat', 'count_chars', 'chunk_split', 'trim', 'ltrim', 'strip_tags', 'similar_text', 'explode', 'implode', 'setlocale', 'localeconv',
+ 'parse_str', 'str_pad', 'chop', 'strchr', 'sprintf', 'printf', 'vprintf', 'vsprintf', 'sscanf', 'fscanf', 'parse_url', 'urlencode', 'urldecode', 'rawurlencode', 'rawurldecode', 'readlink', 'linkinfo', 'link', 'unlink', 'exec', 'system', 'escapeshellcmd', 'escapeshellarg', 'passthru', 'shell_exec', 'proc_open', 'proc_close', 'rand', 'srand', 'getrandmax', 'mt_rand', 'mt_srand', 'mt_getrandmax', 'base64_decode', 'base64_encode', 'abs', 'ceil', 'floor', 'round', 'is_finite', 'is_nan', 'is_infinite', 'bindec', 'hexdec', 'octdec', 'decbin', 'decoct', 'dechex', 'base_convert', 'number_format', 'fmod', 'ip2long', 'long2ip', 'getenv', 'putenv', 'getopt', 'microtime', 'gettimeofday', 'getrusage', 'uniqid', 'quoted_printable_decode', 'set_time_limit', 'get_cfg_var', 'magic_quotes_runtime', 'set_magic_quotes_runtime', 'get_magic_quotes_gpc', 'get_magic_quotes_runtime',
+ 'import_request_variables', 'error_log', 'serialize', 'unserialize', 'memory_get_usage', 'var_dump', 'var_export', 'debug_zval_dump', 'print_r','highlight_file', 'show_source', 'highlight_string', 'ini_get', 'ini_get_all', 'ini_set', 'ini_alter', 'ini_restore', 'get_include_path', 'set_include_path', 'restore_include_path', 'setcookie', 'header', 'headers_sent', 'connection_aborted', 'connection_status', 'ignore_user_abort', 'parse_ini_file', 'is_uploaded_file', 'move_uploaded_file', 'intval', 'floatval', 'doubleval', 'strval', 'gettype', 'settype', 'is_null', 'is_resource', 'is_bool', 'is_long', 'is_float', 'is_int', 'is_integer', 'is_double', 'is_real', 'is_numeric', 'is_string', 'is_array', 'is_object', 'is_scalar',
+ 'ereg', 'ereg_replace', 'eregi', 'eregi_replace', 'split', 'spliti', 'join', 'sql_regcase', 'dl', 'pclose', 'popen', 'readfile', 'rewind', 'rmdir', 'umask', 'fclose', 'feof', 'fgetc', 'fgets', 'fgetss', 'fread', 'fopen', 'fpassthru', 'ftruncate', 'fstat', 'fseek', 'ftell', 'fflush', 'fwrite', 'fputs', 'mkdir', 'rename', 'copy', 'tempnam', 'tmpfile', 'file', 'file_get_contents', 'stream_select', 'stream_context_create', 'stream_context_set_params', 'stream_context_set_option', 'stream_context_get_options', 'stream_filter_prepend', 'stream_filter_append', 'fgetcsv', 'flock', 'get_meta_tags', 'stream_set_write_buffer', 'set_file_buffer', 'set_socket_blocking', 'stream_set_blocking', 'socket_set_blocking', 'stream_get_meta_data', 'stream_register_wrapper', 'stream_wrapper_register', 'stream_set_timeout', 'socket_set_timeout', 'socket_get_status', 'realpath', 'fnmatch', 'fsockopen', 'pfsockopen', 'pack', 'unpack', 'get_browser', 'crypt', 'opendir', 'closedir', 'chdir', 'getcwd', 'rewinddir', 'readdir', 'dir', 'glob', 'fileatime', 'filectime', 'filegroup', 'fileinode', 'filemtime', 'fileowner', 'fileperms', 'filesize', 'filetype', 'file_exists', 'is_writable', 'is_writeable', 'is_readable', 'is_executable', 'is_file', 'is_dir', 'is_link', 'stat', 'lstat', 'chown',
+ 'touch', 'clearstatcache', 'mail', 'ob_start', 'ob_flush', 'ob_clean', 'ob_end_flush', 'ob_end_clean', 'ob_get_flush', 'ob_get_clean', 'ob_get_length', 'ob_get_level', 'ob_get_status', 'ob_get_contents', 'ob_implicit_flush', 'ob_list_handlers', 'ksort', 'krsort', 'natsort', 'natcasesort', 'asort', 'arsort', 'sort', 'rsort', 'usort', 'uasort', 'uksort', 'shuffle', 'array_walk', 'count', 'end', 'prev', 'next', 'reset', 'current', 'key', 'min', 'max', 'in_array', 'array_search', 'extract', 'compact', 'array_fill', 'range', 'array_multisort', 'array_push', 'array_pop', 'array_shift', 'array_unshift', 'array_splice', 'array_slice', 'array_merge', 'array_merge_recursive', 'array_keys', 'array_values', 'array_count_values', 'array_reverse', 'array_reduce', 'array_pad', 'array_flip', 'array_change_key_case', 'array_rand', 'array_unique', 'array_intersect', 'array_intersect_assoc', 'array_diff', 'array_diff_assoc', 'array_sum', 'array_filter', 'array_map', 'array_chunk', 'array_key_exists', 'pos', 'sizeof', 'key_exists', 'assert', 'assert_options', 'version_compare', 'ftok', 'str_rot13', 'aggregate',
+ 'session_name', 'session_module_name', 'session_save_path', 'session_id', 'session_regenerate_id', 'session_decode', 'session_register', 'session_unregister', 'session_is_registered', 'session_encode',
+ 'session_start', 'session_destroy', 'session_unset', 'session_set_save_handler', 'session_cache_limiter', 'session_cache_expire', 'session_set_cookie_params', 'session_get_cookie_params', 'session_write_close', 'preg_match', 'preg_match_all', 'preg_replace', 'preg_replace_callback', 'preg_split', 'preg_quote', 'preg_grep', 'overload', 'ctype_alnum', 'ctype_alpha', 'ctype_cntrl', 'ctype_digit', 'ctype_lower', 'ctype_graph', 'ctype_print', 'ctype_punct', 'ctype_space', 'ctype_upper', 'ctype_xdigit', 'virtual', 'apache_request_headers', 'apache_note', 'apache_lookup_uri', 'apache_child_terminate', 'apache_setenv', 'apache_response_headers', 'apache_get_version', 'getallheaders', 'mysql_connect', 'mysql_pconnect', 'mysql_close', 'mysql_select_db', 'mysql_create_db', 'mysql_drop_db', 'mysql_query', 'mysql_unbuffered_query', 'mysql_db_query', 'mysql_list_dbs', 'mysql_list_tables', 'mysql_list_fields', 'mysql_list_processes', 'mysql_error', 'mysql_errno', 'mysql_affected_rows', 'mysql_insert_id', 'mysql_result', 'mysql_num_rows', 'mysql_num_fields', 'mysql_fetch_row', 'mysql_fetch_array', 'mysql_fetch_assoc', 'mysql_fetch_object', 'mysql_data_seek', 'mysql_fetch_lengths', 'mysql_fetch_field', 'mysql_field_seek', 'mysql_free_result', 'mysql_field_name', 'mysql_field_table', 'mysql_field_len', 'mysql_field_type', 'mysql_field_flags', 'mysql_escape_string', 'mysql_real_escape_string', 'mysql_stat',
+ 'mysql_thread_id', 'mysql_client_encoding', 'mysql_get_client_info', 'mysql_get_host_info', 'mysql_get_proto_info', 'mysql_get_server_info', 'mysql_info', 'mysql', 'mysql_fieldname', 'mysql_fieldtable', 'mysql_fieldlen', 'mysql_fieldtype', 'mysql_fieldflags', 'mysql_selectdb', 'mysql_createdb', 'mysql_dropdb', 'mysql_freeresult', 'mysql_numfields', 'mysql_numrows', 'mysql_listdbs', 'mysql_listtables', 'mysql_listfields', 'mysql_db_name', 'mysql_dbname', 'mysql_tablename', 'mysql_table_name', 'pg_connect', 'pg_pconnect', 'pg_close', 'pg_connection_status', 'pg_connection_busy', 'pg_connection_reset', 'pg_host', 'pg_dbname', 'pg_port', 'pg_tty', 'pg_options', 'pg_ping', 'pg_query', 'pg_send_query', 'pg_cancel_query', 'pg_fetch_result', 'pg_fetch_row', 'pg_fetch_assoc', 'pg_fetch_array', 'pg_fetch_object', 'pg_fetch_all', 'pg_affected_rows', 'pg_get_result', 'pg_result_seek', 'pg_result_status', 'pg_free_result', 'pg_last_oid', 'pg_num_rows', 'pg_num_fields', 'pg_field_name', 'pg_field_num', 'pg_field_size', 'pg_field_type', 'pg_field_prtlen', 'pg_field_is_null', 'pg_get_notify', 'pg_get_pid', 'pg_result_error', 'pg_last_error', 'pg_last_notice', 'pg_put_line', 'pg_end_copy', 'pg_copy_to', 'pg_copy_from',
+ 'pg_trace', 'pg_untrace', 'pg_lo_create', 'pg_lo_unlink', 'pg_lo_open', 'pg_lo_close', 'pg_lo_read', 'pg_lo_write', 'pg_lo_read_all', 'pg_lo_import', 'pg_lo_export', 'pg_lo_seek', 'pg_lo_tell', 'pg_escape_string', 'pg_escape_bytea', 'pg_unescape_bytea', 'pg_client_encoding', 'pg_set_client_encoding', 'pg_meta_data', 'pg_convert', 'pg_insert', 'pg_update', 'pg_delete', 'pg_select', 'pg_exec', 'pg_getlastoid', 'pg_cmdtuples', 'pg_errormessage', 'pg_numrows', 'pg_numfields', 'pg_fieldname', 'pg_fieldsize', 'pg_fieldtype', 'pg_fieldnum', 'pg_fieldprtlen', 'pg_fieldisnull', 'pg_freeresult', 'pg_result', 'pg_loreadall', 'pg_locreate', 'pg_lounlink', 'pg_loopen', 'pg_loclose', 'pg_loread', 'pg_lowrite', 'pg_loimport', 'pg_loexport',
+ 'echo', 'print', 'global', 'static', 'exit', 'array', 'empty', 'eval', 'isset', 'unset', 'die'
+ )
+ ),
+ 'SYMBOLS' => array(
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0000ff;'
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.php.net/{FNAME}',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '-&gt;',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ 0 => "[\\$]{1,2}[a-zA-Z_][a-zA-Z0-9_]*"
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+ 'SCRIPT_DELIMITERS' => array(
+ '<?php' => '?>',
+ '<?' => '?>',
+ '<%' => '%>',
+ '<script language="php">' => '</script>'
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ 3 => true
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/php.php b/plugins/dokuwiki/inc/geshi/php.php
new file mode 100644
index 0000000..9a3b7c1
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/php.php
@@ -0,0 +1,356 @@
+<?php
+/*************************************************************************************
+ * php.php
+ * --------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.7 $
+ * Date Started: 2004/06/20
+ * Last Modified: $Date: 2006/10/08 00:11:00 $
+ *
+ * PHP language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/25 (1.0.3)
+ * - Added support for multiple object splitters
+ * - Fixed &new problem
+ * 2004/10/27 (1.0.2)
+ * - Added URL support
+ * - Added extra constants
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/07/14)
+ * -------------------------
+ * * Make sure the last few function I may have missed
+ * (like eval()) are included for highlighting
+ * * Split to several files - php4, php5 etc
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'PHP',
+ 'COMMENT_SINGLE' => array(1 => '//', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'include', 'require', 'include_once', 'require_once',
+ 'for', 'foreach', 'as', 'if', 'elseif', 'else', 'while', 'do', 'endwhile',
+ 'endif', 'switch', 'case', 'endswitch', 'endfor', 'endforeach',
+ 'return', 'break', 'continue'
+ ),
+ 2 => array(
+ 'null', '__LINE__', '__FILE__',
+ 'false', '&lt;?php', '?&gt;', '&lt;?',
+ '&lt;script language', '&lt;/script&gt;',
+ 'true', 'var', 'default',
+ 'function', 'class', 'new', '&amp;new', 'public', 'private', 'interface', 'extends',
+ '__FUNCTION__', '__CLASS__', '__METHOD__', 'PHP_VERSION',
+ 'PHP_OS', 'DEFAULT_INCLUDE_PATH', 'PEAR_INSTALL_DIR', 'PEAR_EXTENSION_DIR',
+ 'PHP_EXTENSION_DIR', 'PHP_BINDIR', 'PHP_LIBDIR', 'PHP_DATADIR', 'PHP_SYSCONFDIR',
+ 'PHP_LOCALSTATEDIR', 'PHP_CONFIG_FILE_PATH', 'PHP_OUTPUT_HANDLER_START', 'PHP_OUTPUT_HANDLER_CONT',
+ 'PHP_OUTPUT_HANDLER_END', 'E_ERROR', 'E_WARNING', 'E_PARSE', 'E_NOTICE',
+ 'E_CORE_ERROR', 'E_CORE_WARNING', 'E_COMPILE_ERROR', 'E_COMPILE_WARNING', 'E_USER_ERROR',
+ 'E_USER_WARNING', 'E_USER_NOTICE', 'E_ALL'
+ ),
+ 3 => array(
+ 'zlib_get_coding_type','zend_version','zend_logo_guid','yp_order','yp_next',
+ 'yp_match','yp_master','yp_get_default_domain','yp_first','yp_errno','yp_err_string',
+ 'yp_cat','yp_all','xml_set_unparsed_entity_decl_handler','xml_set_start_namespace_decl_handler','xml_set_processing_instruction_handler','xml_set_object',
+ 'xml_set_notation_decl_handler','xml_set_external_entity_ref_handler','xml_set_end_namespace_decl_handler','xml_set_element_handler','xml_set_default_handler','xml_set_character_data_handler',
+ 'xml_parser_set_option','xml_parser_get_option','xml_parser_free','xml_parser_create_ns','xml_parser_create','xml_parse_into_struct',
+ 'xml_parse','xml_get_error_code','xml_get_current_line_number','xml_get_current_column_number','xml_get_current_byte_index','xml_error_string',
+ 'wordwrap','wddx_serialize_vars','wddx_serialize_value','wddx_packet_start','wddx_packet_end','wddx_deserialize',
+ 'wddx_add_vars','vsprintf','vprintf','virtual','version_compare','var_export',
+ 'var_dump','utf8_encode','utf8_decode','usort','usleep','user_error',
+ 'urlencode','urldecode','unserialize','unregister_tick_function','unpack','unlink',
+ 'unixtojd','uniqid','umask','uksort','ucwords','ucfirst',
+ 'uasort','trim','trigger_error','touch','token_name','token_get_all',
+ 'tmpfile','time','textdomain','tempnam','tanh','tan',
+ 'system','syslog','symlink','substr_replace','substr_count','substr',
+ 'strval','strtr','strtoupper','strtotime','strtolower','strtok',
+ 'strstr','strspn','strrpos','strrev','strrchr','strpos',
+ 'strncmp','strncasecmp','strnatcmp','strnatcasecmp','strlen','stristr',
+ 'stripslashes','stripcslashes','strip_tags','strftime','stream_wrapper_register','stream_set_write_buffer',
+ 'stream_set_timeout','stream_set_blocking','stream_select','stream_register_wrapper','stream_get_meta_data','stream_filter_prepend',
+ 'stream_filter_append','stream_context_set_params','stream_context_set_option','stream_context_get_options','stream_context_create','strcspn',
+ 'strcoll','strcmp','strchr','strcasecmp','str_word_count','str_shuffle',
+ 'str_rot13','str_replace','str_repeat','str_pad','stat','sscanf',
+ 'srand','sqrt','sql_regcase','sprintf','spliti','split',
+ 'soundex','sort','socket_writev','socket_write','socket_strerror','socket_shutdown',
+ 'socket_setopt','socket_set_timeout','socket_set_option','socket_set_nonblock','socket_set_blocking','socket_set_block',
+ 'socket_sendto','socket_sendmsg','socket_send','socket_select','socket_recvmsg','socket_recvfrom',
+ 'socket_recv','socket_readv','socket_read','socket_listen','socket_last_error','socket_iovec_set',
+ 'socket_iovec_free','socket_iovec_fetch','socket_iovec_delete','socket_iovec_alloc','socket_iovec_add','socket_getsockname',
+ 'socket_getpeername','socket_getopt','socket_get_status','socket_get_option','socket_create_pair','socket_create_listen',
+ 'socket_create','socket_connect','socket_close','socket_clear_error','socket_bind','socket_accept',
+ 'sleep','sizeof','sinh','sin','similar_text','shuffle',
+ 'show_source','shmop_write','shmop_size','shmop_read','shmop_open','shmop_delete',
+ 'shmop_close','shm_remove_var','shm_remove','shm_put_var','shm_get_var','shm_detach',
+ 'shm_attach','shell_exec','sha1_file','sha1','settype','setlocale',
+ 'setcookie','set_time_limit','set_socket_blocking','set_magic_quotes_runtime','set_include_path','set_file_buffer',
+ 'set_error_handler','session_write_close','session_unset','session_unregister','session_start','session_set_save_handler',
+ 'session_set_cookie_params','session_save_path','session_register','session_regenerate_id','session_name','session_module_name',
+ 'session_is_registered','session_id','session_get_cookie_params','session_encode','session_destroy','session_decode',
+ 'session_cache_limiter','session_cache_expire','serialize','sem_remove','sem_release','sem_get',
+ 'sem_acquire','rtrim','rsort','round','rmdir','rewinddir',
+ 'rewind','restore_include_path','restore_error_handler','reset','rename','register_tick_function',
+ 'register_shutdown_function','realpath','readlink','readgzfile','readfile','readdir',
+ 'read_exif_data','rawurlencode','rawurldecode','range','rand','rad2deg',
+ 'quotemeta','quoted_printable_decode','putenv','proc_open','proc_close','printf',
+ 'print_r','prev','preg_split','preg_replace_callback','preg_replace','preg_quote',
+ 'preg_match_all','preg_match','preg_grep','pow','posix_uname','posix_ttyname',
+ 'posix_times','posix_strerror','posix_setuid','posix_setsid','posix_setpgid','posix_setgid',
+ 'posix_seteuid','posix_setegid','posix_mkfifo','posix_kill','posix_isatty','posix_getuid',
+ 'posix_getsid','posix_getrlimit','posix_getpwuid','posix_getpwnam','posix_getppid','posix_getpid',
+ 'posix_getpgrp','posix_getpgid','posix_getlogin','posix_getgroups','posix_getgrnam','posix_getgrgid',
+ 'posix_getgid','posix_geteuid','posix_getegid','posix_getcwd','posix_get_last_error','posix_errno',
+ 'posix_ctermid','pos','popen','pi','phpversion','phpinfo',
+ 'phpcredits','php_uname','php_sapi_name','php_logo_guid','php_ini_scanned_files','pg_update',
+ 'pg_untrace','pg_unescape_bytea','pg_tty','pg_trace','pg_setclientencoding','pg_set_client_encoding',
+ 'pg_send_query','pg_select','pg_result_status','pg_result_seek','pg_result_error','pg_result',
+ 'pg_query','pg_put_line','pg_port','pg_ping','pg_pconnect','pg_options',
+ 'pg_numrows','pg_numfields','pg_num_rows','pg_num_fields','pg_meta_data','pg_lowrite',
+ 'pg_lounlink','pg_loreadall','pg_loread','pg_loopen','pg_loimport','pg_loexport',
+ 'pg_locreate','pg_loclose','pg_lo_write','pg_lo_unlink','pg_lo_tell','pg_lo_seek',
+ 'pg_lo_read_all','pg_lo_read','pg_lo_open','pg_lo_import','pg_lo_export','pg_lo_create',
+ 'pg_lo_close','pg_last_oid','pg_last_notice','pg_last_error','pg_insert','pg_host',
+ 'pg_getlastoid','pg_get_result','pg_get_pid','pg_get_notify','pg_freeresult','pg_free_result',
+ 'pg_fieldtype','pg_fieldsize','pg_fieldprtlen','pg_fieldnum','pg_fieldname','pg_fieldisnull',
+ 'pg_field_type','pg_field_size','pg_field_prtlen','pg_field_num','pg_field_name','pg_field_is_null',
+ 'pg_fetch_row','pg_fetch_result','pg_fetch_object','pg_fetch_assoc','pg_fetch_array','pg_fetch_all',
+ 'pg_exec','pg_escape_string','pg_escape_bytea','pg_errormessage','pg_end_copy','pg_delete',
+ 'pg_dbname','pg_copy_to','pg_copy_from','pg_convert','pg_connection_status','pg_connection_reset',
+ 'pg_connection_busy','pg_connect','pg_cmdtuples','pg_close','pg_clientencoding','pg_client_encoding',
+ 'pg_cancel_query','pg_affected_rows','pfsockopen','pclose','pathinfo','passthru',
+ 'parse_url','parse_str','parse_ini_file','pack','overload','output_reset_rewrite_vars',
+ 'output_add_rewrite_var','ord','openssl_x509_read','openssl_x509_parse','openssl_x509_free','openssl_x509_export_to_file',
+ 'openssl_x509_export','openssl_x509_checkpurpose','openssl_x509_check_private_key','openssl_verify','openssl_sign','openssl_seal',
+ 'openssl_public_encrypt','openssl_public_decrypt','openssl_private_encrypt','openssl_private_decrypt','openssl_pkey_new','openssl_pkey_get_public',
+ 'openssl_pkey_get_private','openssl_pkey_free','openssl_pkey_export_to_file','openssl_pkey_export','openssl_pkcs7_verify','openssl_pkcs7_sign',
+ 'openssl_pkcs7_encrypt','openssl_pkcs7_decrypt','openssl_open','openssl_get_publickey','openssl_get_privatekey','openssl_free_key',
+ 'openssl_error_string','openssl_csr_sign','openssl_csr_new','openssl_csr_export_to_file','openssl_csr_export','openlog',
+ 'opendir','octdec','ob_start','ob_list_handlers','ob_implicit_flush','ob_iconv_handler',
+ 'ob_gzhandler','ob_get_status','ob_get_level','ob_get_length','ob_get_flush','ob_get_contents',
+ 'ob_get_clean','ob_flush','ob_end_flush','ob_end_clean','ob_clean','number_format',
+ 'nl_langinfo','nl2br','ngettext','next','natsort','natcasesort',
+ 'mysql_unbuffered_query','mysql_thread_id','mysql_tablename','mysql_table_name','mysql_stat','mysql_selectdb',
+ 'mysql_select_db','mysql_result','mysql_real_escape_string','mysql_query','mysql_ping','mysql_pconnect',
+ 'mysql_numrows','mysql_numfields','mysql_num_rows','mysql_num_fields','mysql_listtables','mysql_listfields',
+ 'mysql_listdbs','mysql_list_tables','mysql_list_processes','mysql_list_fields','mysql_list_dbs','mysql_insert_id',
+ 'mysql_info','mysql_get_server_info','mysql_get_proto_info','mysql_get_host_info','mysql_get_client_info','mysql_freeresult',
+ 'mysql_free_result','mysql_fieldtype','mysql_fieldtable','mysql_fieldname','mysql_fieldlen','mysql_fieldflags',
+ 'mysql_field_type','mysql_field_table','mysql_field_seek','mysql_field_name','mysql_field_len','mysql_field_flags',
+ 'mysql_fetch_row','mysql_fetch_object','mysql_fetch_lengths','mysql_fetch_field','mysql_fetch_assoc','mysql_fetch_array',
+ 'mysql_escape_string','mysql_error','mysql_errno','mysql_dropdb','mysql_drop_db','mysql_dbname',
+ 'mysql_db_query','mysql_db_name','mysql_data_seek','mysql_createdb','mysql_create_db','mysql_connect',
+ 'mysql_close','mysql_client_encoding','mysql_affected_rows','mysql','mt_srand','mt_rand',
+ 'mt_getrandmax','move_uploaded_file','money_format','mktime','mkdir','min',
+ 'microtime','method_exists','metaphone','memory_get_usage','md5_file','md5',
+ 'mbsubstr','mbstrrpos','mbstrpos','mbstrlen','mbstrcut','mbsplit',
+ 'mbregex_encoding','mberegi_replace','mberegi','mbereg_search_setpos','mbereg_search_regs','mbereg_search_pos',
+ 'mbereg_search_init','mbereg_search_getregs','mbereg_search_getpos','mbereg_search','mbereg_replace','mbereg_match',
+ 'mbereg','mb_substr_count','mb_substr','mb_substitute_character','mb_strwidth','mb_strtoupper',
+ 'mb_strtolower','mb_strrpos','mb_strpos','mb_strlen','mb_strimwidth','mb_strcut',
+ 'mb_split','mb_send_mail','mb_regex_set_options','mb_regex_encoding','mb_preferred_mime_name','mb_parse_str',
+ 'mb_output_handler','mb_language','mb_internal_encoding','mb_http_output','mb_http_input','mb_get_info',
+ 'mb_eregi_replace','mb_eregi','mb_ereg_search_setpos','mb_ereg_search_regs','mb_ereg_search_pos','mb_ereg_search_init',
+ 'mb_ereg_search_getregs','mb_ereg_search_getpos','mb_ereg_search','mb_ereg_replace','mb_ereg_match','mb_ereg',
+ 'mb_encode_numericentity','mb_encode_mimeheader','mb_detect_order','mb_detect_encoding','mb_decode_numericentity','mb_decode_mimeheader',
+ 'mb_convert_variables','mb_convert_kana','mb_convert_encoding','mb_convert_case','max','mail',
+ 'magic_quotes_runtime','ltrim','lstat','long2ip','log1p','log10',
+ 'log','localtime','localeconv','linkinfo','link','levenshtein',
+ 'lcg_value','ksort','krsort','key_exists','key','juliantojd',
+ 'join','jewishtojd','jdtounix','jdtojulian','jdtojewish','jdtogregorian',
+ 'jdtofrench','jdmonthname','jddayofweek','is_writeable','is_writable','is_uploaded_file',
+ 'is_subclass_of','is_string','is_scalar','is_resource','is_real','is_readable',
+ 'is_object','is_numeric','is_null','is_nan','is_long','is_link',
+ 'is_integer','is_int','is_infinite','is_float','is_finite','is_file',
+ 'is_executable','is_double','is_dir','is_callable','is_bool','is_array',
+ 'is_a','iptcparse','iptcembed','ip2long','intval','ini_set',
+ 'ini_restore','ini_get_all','ini_get','ini_alter','in_array','import_request_variables',
+ 'implode','image_type_to_mime_type','ignore_user_abort','iconv_set_encoding','iconv_get_encoding','iconv',
+ 'i18n_mime_header_encode','i18n_mime_header_decode','i18n_ja_jp_hantozen','i18n_internal_encoding','i18n_http_output','i18n_http_input',
+ 'i18n_discover_encoding','i18n_convert','hypot','htmlspecialchars','htmlentities','html_entity_decode',
+ 'highlight_string','highlight_file','hexdec','hebrevc','hebrev','headers_sent',
+ 'header','gzwrite','gzuncompress','gztell','gzseek','gzrewind',
+ 'gzread','gzputs','gzpassthru','gzopen','gzinflate','gzgetss',
+ 'gzgets','gzgetc','gzfile','gzeof','gzencode','gzdeflate',
+ 'gzcompress','gzclose','gregoriantojd','gmstrftime','gmmktime','gmdate',
+ 'glob','gettype','gettimeofday','gettext','getservbyport','getservbyname',
+ 'getrusage','getrandmax','getprotobynumber','getprotobyname','getopt','getmyuid',
+ 'getmypid','getmyinode','getmygid','getmxrr','getlastmod','getimagesize',
+ 'gethostbynamel','gethostbyname','gethostbyaddr','getenv','getdate','getcwd',
+ 'getallheaders','get_resource_type','get_required_files','get_parent_class','get_object_vars','get_meta_tags',
+ 'get_magic_quotes_runtime','get_magic_quotes_gpc','get_loaded_extensions','get_included_files','get_include_path','get_html_translation_table',
+ 'get_extension_funcs','get_defined_vars','get_defined_functions','get_defined_constants','get_declared_classes','get_current_user',
+ 'get_class_vars','get_class_methods','get_class','get_cfg_var','get_browser','fwrite',
+ 'function_exists','func_num_args','func_get_args','func_get_arg','ftruncate','ftp_systype',
+ 'ftp_ssl_connect','ftp_size','ftp_site','ftp_set_option','ftp_rmdir','ftp_rename',
+ 'ftp_rawlist','ftp_quit','ftp_pwd','ftp_put','ftp_pasv','ftp_nlist',
+ 'ftp_nb_put','ftp_nb_get','ftp_nb_fput','ftp_nb_fget','ftp_nb_continue','ftp_mkdir',
+ 'ftp_mdtm','ftp_login','ftp_get_option','ftp_get','ftp_fput','ftp_fget',
+ 'ftp_exec','ftp_delete','ftp_connect','ftp_close','ftp_chdir','ftp_cdup',
+ 'ftok','ftell','fstat','fsockopen','fseek','fscanf',
+ 'frenchtojd','fread','fputs','fpassthru','fopen','fnmatch',
+ 'fmod','flush','floor','flock','floatval','filetype',
+ 'filesize','filepro_rowcount','filepro_retrieve','filepro_fieldwidth','filepro_fieldtype','filepro_fieldname',
+ 'filepro_fieldcount','filepro','fileperms','fileowner','filemtime','fileinode',
+ 'filegroup','filectime','fileatime','file_get_contents','file_exists','file',
+ 'fgetss','fgets','fgetcsv','fgetc','fflush','feof',
+ 'fclose','ezmlm_hash','extract','extension_loaded','expm1','explode',
+ 'exp','exif_thumbnail','exif_tagname','exif_read_data','exif_imagetype','exec',
+ 'escapeshellcmd','escapeshellarg','error_reporting','error_log','eregi_replace','eregi',
+ 'ereg_replace','ereg','end','easter_days','easter_date','each',
+ 'doubleval','dngettext','dl','diskfreespace','disk_total_space','disk_free_space',
+ 'dirname','dir','dgettext','deg2rad','defined','define_syslog_variables',
+ 'define','decoct','dechex','decbin','debug_zval_dump','debug_backtrace',
+ 'deaggregate','dcngettext','dcgettext','dba_sync','dba_replace','dba_popen',
+ 'dba_optimize','dba_open','dba_nextkey','dba_list','dba_insert','dba_handlers',
+ 'dba_firstkey','dba_fetch','dba_exists','dba_delete','dba_close','date',
+ 'current','ctype_xdigit','ctype_upper','ctype_space','ctype_punct','ctype_print',
+ 'ctype_lower','ctype_graph','ctype_digit','ctype_cntrl','ctype_alpha','ctype_alnum',
+ 'crypt','create_function','crc32','count_chars','count','cosh',
+ 'cos','copy','convert_cyr_string','constant','connection_status','connection_aborted',
+ 'compact','closelog','closedir','clearstatcache','class_exists','chunk_split',
+ 'chr','chown','chop','chmod','chgrp','checkdnsrr',
+ 'checkdate','chdir','ceil','call_user_method_array','call_user_method','call_user_func_array',
+ 'call_user_func','cal_to_jd','cal_info','cal_from_jd','cal_days_in_month','bzwrite',
+ 'bzread','bzopen','bzflush','bzerrstr','bzerror','bzerrno',
+ 'bzdecompress','bzcompress','bzclose','bindtextdomain','bindec','bind_textdomain_codeset',
+ 'bin2hex','bcsub','bcsqrt','bcscale','bcpow','bcmul',
+ 'bcmod','bcdiv','bccomp','bcadd','basename','base_convert',
+ 'base64_encode','base64_decode','atanh','atan2','atan','assert_options',
+ 'assert','asort','asinh','asin','arsort','array_walk',
+ 'array_values','array_unshift','array_unique','array_sum','array_splice','array_slice',
+ 'array_shift','array_search','array_reverse','array_reduce','array_rand','array_push',
+ 'array_pop','array_pad','array_multisort','array_merge_recursive','array_merge','array_map',
+ 'array_keys','array_key_exists','array_intersect_assoc','array_intersect','array_flip','array_filter',
+ 'array_fill','array_diff_assoc','array_diff','array_count_values','array_chunk','array_change_key_case',
+ 'apache_setenv','apache_response_headers','apache_request_headers','apache_note','apache_lookup_uri','apache_get_version',
+ 'apache_child_terminate','aggregation_info','aggregate_properties_by_regexp','aggregate_properties_by_list','aggregate_properties','aggregate_methods_by_regexp',
+ 'aggregate_methods_by_list','aggregate_methods','aggregate','addslashes','addcslashes','acosh',
+ 'acos','abs','_','echo', 'print', 'global', 'static', 'exit', 'array', 'empty',
+ 'eval', 'isset', 'unset', 'die', 'list'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '!', '@', '%', '&', '*', '|', '/', '<', '>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;',
+ 2 => 'color: #000000; font-weight: bold;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;',
+ 2 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #0000ff;',
+ 1 => 'color: #ff0000'
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.php.net/{FNAME}',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '-&gt;',
+ 2 => '::'
+ ),
+ 'REGEXPS' => array(
+ 0 => "[\\$]{1,2}[a-zA-Z_][a-zA-Z0-9_]*",
+ 1 => array(
+ GESHI_SEARCH => "([a-zA-Z]+)(\n)(.*)(\n)(\\1;?)",
+ GESHI_REPLACE => '\3',
+ GESHI_BEFORE => '\1\2',
+ GESHI_AFTER => '\4\5',
+ GESHI_MODIFIERS => 'siU'
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<?php' => '?>'
+ ),
+ 1 => array(
+ '<?' => '?>'
+ ),
+ 2 => array(
+ '<%' => '%>'
+ ),
+ 3 => array(
+ '<script language="php">' => '</script>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ 3 => true
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/python.php b/plugins/dokuwiki/inc/geshi/python.php
new file mode 100644
index 0000000..595d524
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/python.php
@@ -0,0 +1,229 @@
+<?php
+/*************************************************************************************
+ * python.php
+ * ----------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.5 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:47 $
+ *
+ * Python language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/05/26
+ * - Modifications by Tim (tim@skreak.com): added more keyword categories, tweaked colors
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/08/30 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Python',
+ 'COMMENT_SINGLE' => array(1 => '#'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"', "'", '"""'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+
+ /*
+ ** Set 1: reserved words
+ ** http://python.org/doc/current/ref/keywords.html
+ */
+ 1 => array(
+ 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda', 'return', 'break',
+ 'else', 'global', 'not', 'try', 'class', 'except', 'if', 'or', 'while', 'continue', 'exec',
+ 'import', 'pass', 'yield', 'def', 'finally', 'in', 'print'
+ ),
+
+ /*
+ ** Set 2: builtins
+ ** http://python.org/doc/current/lib/built-in-funcs.html
+ */
+ 2 => array(
+ '__import__', 'abs', 'basestring', 'bool', 'callable', 'chr', 'classmethod', 'cmp',
+ 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile',
+ 'file', 'filter', 'float', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help',
+ 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals',
+ 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
+ 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
+ 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode',
+ 'vars', 'xrange', 'zip',
+ // Built-in constants: http://python.org/doc/current/lib/node35.html
+ 'False', 'True', 'None', 'NotImplemented', 'Ellipsis',
+ // Built-in Exceptions: http://python.org/doc/current/lib/module-exceptions.html
+ 'Exception', 'StandardError', 'ArithmeticError', 'LookupError', 'EnvironmentError',
+ 'AssertionError', 'AttributeError', 'EOFError', 'FloatingPointError', 'IOError',
+ 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'MemoryError', 'NameError',
+ 'NotImplementedError', 'OSError', 'OverflowError', 'ReferenceError', 'RuntimeError',
+ 'StopIteration', 'SyntaxError', 'SystemError', 'SystemExit', 'TypeError',
+ 'UnboundlocalError', 'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError',
+ 'UnicodeTranslateError', 'ValueError', 'WindowsError', 'ZeroDivisionError', 'Warning',
+ 'UserWarning', 'DeprecationWarning', 'PendingDeprecationWarning', 'SyntaxWarning',
+ 'RuntimeWarning', 'FutureWarning',
+ // self: this is a common python convention (but not a reserved word)
+ 'self'
+ ),
+
+ /*
+ ** Set 3: standard library
+ ** http://python.org/doc/current/lib/modindex.html
+ */
+ 3 => array(
+ '__builtin__', '__future__', '__main__', '_winreg', 'aifc', 'AL', 'al', 'anydbm',
+ 'array', 'asynchat', 'asyncore', 'atexit', 'audioop', 'base64', 'BaseHTTPServer',
+ 'Bastion', 'binascii', 'binhex', 'bisect', 'bsddb', 'bz2', 'calendar', 'cd', 'cgi',
+ 'CGIHTTPServer', 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs', 'codeop',
+ 'collections', 'colorsys', 'commands', 'compileall', 'compiler', 'compiler',
+ 'ConfigParser', 'Cookie', 'cookielib', 'copy', 'copy_reg', 'cPickle', 'crypt',
+ 'cStringIO', 'csv', 'curses', 'datetime', 'dbhash', 'dbm', 'decimal', 'DEVICE',
+ 'difflib', 'dircache', 'dis', 'distutils', 'dl', 'doctest', 'DocXMLRPCServer', 'dumbdbm',
+ 'dummy_thread', 'dummy_threading', 'email', 'encodings', 'errno', 'exceptions', 'fcntl',
+ 'filecmp', 'fileinput', 'FL', 'fl', 'flp', 'fm', 'fnmatch', 'formatter', 'fpectl',
+ 'fpformat', 'ftplib', 'gc', 'gdbm', 'getopt', 'getpass', 'gettext', 'GL', 'gl', 'glob',
+ 'gopherlib', 'grp', 'gzip', 'heapq', 'hmac', 'hotshot', 'htmlentitydefs', 'htmllib',
+ 'HTMLParser', 'httplib', 'imageop', 'imaplib', 'imgfile', 'imghdr', 'imp', 'inspect',
+ 'itertools', 'jpeg', 'keyword', 'linecache', 'locale', 'logging', 'mailbox', 'mailcap',
+ 'marshal', 'math', 'md5', 'mhlib', 'mimetools', 'mimetypes', 'MimeWriter', 'mimify',
+ 'mmap', 'msvcrt', 'multifile', 'mutex', 'netrc', 'new', 'nis', 'nntplib', 'operator',
+ 'optparse', 'os', 'ossaudiodev', 'parser', 'pdb', 'pickle', 'pickletools', 'pipes',
+ 'pkgutil', 'platform', 'popen2', 'poplib', 'posix', 'posixfile', 'pprint', 'profile',
+ 'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc', 'Queue', 'quopri', 'random',
+ 're', 'readline', 'repr', 'resource', 'rexec', 'rfc822', 'rgbimg', 'rlcompleter',
+ 'robotparser', 'sched', 'ScrolledText', 'select', 'sets', 'sgmllib', 'sha', 'shelve',
+ 'shlex', 'shutil', 'signal', 'SimpleHTTPServer', 'SimpleXMLRPCServer', 'site', 'smtpd',
+ 'smtplib', 'sndhdr', 'socket', 'SocketServer', 'stat', 'statcache', 'statvfs', 'string',
+ 'StringIO', 'stringprep', 'struct', 'subprocess', 'sunau', 'SUNAUDIODEV', 'sunaudiodev',
+ 'symbol', 'sys', 'syslog', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios',
+ 'test', 'textwrap', 'thread', 'threading', 'time', 'timeit', 'Tix', 'Tkinter', 'token',
+ 'tokenize', 'traceback', 'tty', 'turtle', 'types', 'unicodedata', 'unittest', 'urllib2',
+ 'urllib', 'urlparse', 'user', 'UserDict', 'UserList', 'UserString', 'uu', 'warnings',
+ 'wave', 'weakref', 'webbrowser', 'whichdb', 'whrandom', 'winsound', 'xdrlib', 'xml',
+ 'xmllib', 'xmlrpclib', 'zipfile', 'zipimport', 'zlib'
+ ),
+
+ /*
+ ** Set 4: special methods
+ ** http://python.org/doc/current/ref/specialnames.html
+ */
+ 4 => array(
+ /*
+ // Iterator types: http://python.org/doc/current/lib/typeiter.html
+ '__iter__', 'next',
+ // String types: http://python.org/doc/current/lib/string-methods.html
+ 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs',
+ 'find', 'index', 'isalnum', 'isaplpha', 'isdigit', 'islower', 'isspace', 'istitle',
+ 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust',
+ 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title',
+ 'translate', 'upper', 'zfill',
+ */
+ // Basic customization: http://python.org/doc/current/ref/customization.html
+ '__new__', '__init__', '__del__', '__repr__', '__str__',
+ '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__cmp__', '__rcmp__',
+ '__hash__', '__nonzero__', '__unicode__', '__dict__',
+ // Attribute access: http://python.org/doc/current/ref/attribute-access.html
+ '__setattr__', '__delattr__', '__getattr__', '__getattribute__', '__get__', '__set__',
+ '__delete__', '__slots__',
+ // Class creation, callable objects
+ '__metaclass__', '__call__',
+ // Container types: http://python.org/doc/current/ref/sequence-types.html
+ '__len__', '__getitem__', '__setitem__', '__delitem__', '__iter__', '__contains__',
+ '__getslice__', '__setslice__', '__delslice__',
+ // Numeric types: http://python.org/doc/current/ref/numeric-types.html
+ '__abs__','__add__','__and__','__coerce__','__div__','__divmod__','__float__',
+ '__hex__','__iadd__','__isub__','__imod__','__idiv__','__ipow__','__iand__',
+ '__ior__','__ixor__', '__ilshift__','__irshift__','__invert__','__int__',
+ '__long__','__lshift__',
+ '__mod__','__mul__','__neg__','__oct__','__or__','__pos__','__pow__',
+ '__radd__','__rdiv__','__rdivmod__','__rmod__','__rpow__','__rlshift__','__rrshift__',
+ '__rshift__','__rsub__','__rmul__','__repr__','__rand__','__rxor__','__ror__',
+ '__sub__','__xor__'
+ )
+
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '*', '&', '%', '!', ';', '<', '>', '?', '`'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => true,
+ 2 => true,
+ 3 => true,
+ 4 => true
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #ff7700;font-weight:bold;', // Reserved
+ 2 => 'color: #008000;', // Built-ins + self
+ 3 => 'color: #dc143c;', // Standard lib
+ 4 => 'color: #0000cd;' // Special methods
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: black;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #483d8b;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff4500;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: black;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/qbasic.php b/plugins/dokuwiki/inc/geshi/qbasic.php
new file mode 100644
index 0000000..3ded1c0
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/qbasic.php
@@ -0,0 +1,147 @@
+<?php
+/*************************************************************************************
+ * qbasic.php
+ * ----------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/06/20
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * QBasic/QuickBASIC language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Added support for URLs
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * - Removed unnessecary slashes from some keywords
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Make sure all possible combinations of keywords with
+ * a space in them (EXIT FOR, END SELECT) are added
+ * to the first keyword group
+ * * Update colours, especially for the first keyword group
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+$language_data = array (
+ 'LANG_NAME' => 'QBasic/QuickBASIC',
+ 'COMMENT_SINGLE' => array(1 => "'", 2 => ' REM', 3 => "\tREM"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_UPPER,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'DO', 'LOOP', 'WHILE', 'WEND', 'THEN', 'ELSE', 'ELSEIF', 'IF',
+ 'FOR', 'TO', 'NEXT', 'STEP', 'GOTO', 'GOSUB', 'RETURN', 'RESUME', 'SELECT',
+ 'CASE', 'UNTIL'
+ ),
+ 3 => array(
+ 'ABS', 'ABSOLUTE', 'ACCESS', 'ALIAS', 'AND', 'ANY', 'APPEND', 'AS', 'ASC', 'ATN',
+ 'BASE', 'BEEP', 'BINARY', 'BLOAD', 'BSAVE', 'BYVAL', 'CALL', 'CALLS', 'CASE',
+ 'CDBL', 'CDECL', 'CHAIN', 'CHDIR', 'CHDIR', 'CHR$', 'CINT', 'CIRCLE', 'CLEAR',
+ 'CLNG', 'CLOSE', 'CLS', 'COM', 'COMMAND$', 'COMMON', 'CONST', 'COS', 'CSNG',
+ 'CSRLIN', 'CVD', 'CVDMBF', 'CVI', 'CVL', 'CVS', 'CVSMDF', 'DATA', 'DATE$',
+ 'DECLARE', 'DEF', 'FN', 'SEG', 'DEFDBL', 'DEFINT', 'DEFLNG', 'DEFSNG', 'DEFSTR',
+ 'DIM', 'DOUBLE', 'DRAW', 'END', 'ENVIRON', 'ENVIRON$', 'EOF', 'EQV', 'ERASE',
+ 'ERDEV', 'ERDEV$', 'ERL', 'ERR', 'ERROR', 'EXIT', 'EXP', 'FIELD', 'FILEATTR',
+ 'FILES', 'FIX', 'FRE', 'FREEFILE', 'FUNCTION', 'GET', 'HEX$', 'IMP', 'INKEY$',
+ 'INP', 'INPUT', 'INPUT$', 'INSTR', 'INT', 'INTEGER', 'IOCTL', 'IOCTL$', 'IS',
+ 'KEY', 'KILL', 'LBOUND', 'LCASE$', 'LEFT$', 'LEN', 'LET', 'LINE', 'LIST', 'LOC',
+ 'LOCAL', 'LOCATE', 'LOCK', 'LOF', 'LOG', 'UNLOCK', 'LONG', 'LPOS', 'LPRINT',
+ 'LSET', 'LTRIM$', 'MID$', 'MKD$', 'MKDIR', 'MKDMBF$', 'MKI$', 'MKL$',
+ 'MKS$', 'MKSMBF$', 'MOD', 'NAME', 'NOT', 'OCT$', 'OFF', 'ON', 'PEN', 'PLAY',
+ 'STRIG', 'TIMER', 'UEVENT', 'OPEN', 'OPTION', 'BASE', 'OR', 'OUT', 'OUTPUT',
+ 'PAINT', 'PALETTE', 'PCOPY', 'PEEK', 'PMAP', 'POINT', 'POKE', 'POS', 'PRESET',
+ 'PRINT', 'USING', 'PSET', 'PUT', 'RANDOM', 'RANDOMIZE', 'READ', 'REDIM', 'RESET',
+ 'RESTORE', 'RIGHT$', 'RMDIR', 'RND', 'RSET', 'RTRIM$', 'RUN', 'SADD', 'SCREEN',
+ 'SEEK', 'SETMEM', 'SGN', 'SHARED', 'SHELL', 'SIGNAL', 'SIN', 'SINGLE', 'SLEEP',
+ 'SOUND', 'SPACE$', 'SPC', 'SQR', 'STATIC', 'STICK', 'STOP', 'STR$', 'STRIG',
+ 'STRING', 'STRING$', 'SUB', 'SWAP', 'SYSTEM', 'TAB', 'TAN', 'TIME$', 'TIMER',
+ 'TROFF', 'TRON', 'TYPE', 'UBOUND', 'UCASE$', 'UEVENT', 'UNLOCK', 'USING', 'VAL',
+ 'VARPTR', 'VARPTR$', 'VARSEG', 'VIEW', 'WAIT', 'WIDTH', 'WINDOW', 'WRITE', 'XOR'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 3 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #a1a100;',
+ 3 => 'color: #000066;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080;',
+ 2 => 'color: #808080;',
+ 3 => 'color: #808080;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 3 => 'http://www.qbasicnews.com/qboho/qck{FNAME}.shtml'
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/ruby.php b/plugins/dokuwiki/inc/geshi/ruby.php
new file mode 100644
index 0000000..4c91033
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/ruby.php
@@ -0,0 +1,149 @@
+<?php
+/*************************************************************************************
+ * ruby.php
+ * --------
+ * Author: Amit Gupta (http://blog.igeek.info/)
+ * Copyright: (c) 2005 Amit Gupta (http://blog.igeek.info/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.4 $
+ * Date Started: 2005/09/05
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * Ruby language file for GeSHi
+ *
+ * CHANGES
+ * -------
+ * 2006/01/05 (1.0.1)
+ * - Add =begin multiline comments (Juan J. Martínez)
+ * - Add ` string (Juan J. Martínez)
+ * 2005/09/05 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/09/05)
+ * -------------------------
+ * * Add the remaining keywords, methods, classes as per
+ * v1.8.2(as listed in the online manual)
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Ruby',
+ 'COMMENT_SINGLE' => array(1 => "#"),
+ 'COMMENT_MULTI' => array( "=begin" => "=end"),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"', '`'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'alias', 'and', 'begin', 'break', 'case', 'class',
+ 'def', 'defined', 'do', 'else', 'elsif', 'end',
+ 'ensure', 'for', 'if', 'in', 'module', 'while',
+ 'next', 'not', 'or', 'redo', 'rescue', 'yield',
+ 'retry', 'super', 'then', 'undef', 'unless',
+ 'until', 'when', 'BEGIN', 'END', 'include'
+
+ ),
+ 2 => array(
+ '__FILE__', '__LINE__', 'false', 'nil', 'self', 'true', 'return'
+ ),
+ 3 => array(
+ 'Array', 'Float', 'Integer', 'String', 'at_exit',
+ 'autoload', 'binding', 'caller', 'catch', 'chop', 'chop!',
+ 'chomp', 'chomp!', 'eval', 'exec', 'exit', 'exit!', 'fail',
+ 'fork', 'format', 'gets', 'global_variables', 'gsub', 'gsub!',
+ 'iterator?', 'lambda', 'load', 'local_variables', 'loop', 'open',
+ 'p', 'print', 'printf', 'proc', 'putc', 'puts', 'raise',
+ 'rand', 'readline', 'readlines', 'require', 'select', 'sleep',
+ 'split', 'sprintf', 'srand', 'sub', 'sub!', 'syscall',
+ 'system', 'test', 'trace_var', 'trap', 'untrace_var'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '[', ']', '{', '}', '@', '%', '&', '*', '|', '/', '<', '>',
+ '+', '-', '=&gt;', '=>'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color:#9966CC; font-weight:bold;',
+ 2 => 'color:#0000FF; font-weight:bold;',
+ 3 => 'color:#CC0066; font-weight:bold;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color:#008000; font-style:italic;',
+ 'MULTI' => 'color:#000080; font-style:italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color:#000099;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color:#006600; font-weight:bold;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color:#996600;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color:#006666;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color:#9900CC;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color:#006600; font-weight:bold;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ 0 => '',
+ 1 => '',
+ 2 => '',
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_MAYBE,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<%' => '%>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true,
+ 1 => true,
+ 2 => true,
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/scheme.php b/plugins/dokuwiki/inc/geshi/scheme.php
new file mode 100644
index 0000000..093e9f0
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/scheme.php
@@ -0,0 +1,172 @@
+<?php
+/*************************************************************************************
+ * scheme.php
+ * ----------
+ * Author: Jon Raphaelson (jonraphaelson@gmail.com)
+ * Copyright: (c) 2005 Jon Raphaelson, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.11.2.4 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * Scheme language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/09/22 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2005/09/22)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Scheme',
+ 'COMMENT_SINGLE' => array(1 => ';'),
+ 'COMMENT_MULTI' => array(';|' => '|;'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'abs', 'acos', 'and', 'angle', 'append', 'appply', 'approximate',
+ 'asin', 'assoc', 'assq', 'assv', 'atan',
+
+ 'begin', 'boolean?', 'bound-identifier=?',
+
+ 'caar', 'caddr', 'cadr', 'call-with-current-continuation',
+ 'call-with-input-file', 'call-with-output-file', 'call/cc', 'car',
+ 'case', 'catch', 'cdddar', 'cddddr', 'cdr', 'ceiling', 'char->integer',
+ 'char-alphabetic?', 'char-ci<=?', 'char-ci<?', 'char-ci?', 'char-ci>=?',
+ 'char-ci>?', 'char-ci=?', 'char-downcase', 'char-lower-case?',
+ 'char-numeric', 'char-ready', 'char-ready?', 'char-upcase',
+ 'char-upper-case?', 'char-whitespace?', 'char<=?', 'char<?', 'char=?',
+ 'char>=?', 'char>?', 'char?', 'close-input-port', 'close-output-port',
+ 'complex?', 'cond', 'cons', 'construct-identifier', 'cos',
+ 'current-input-port', 'current-output-port',
+
+ 'd', 'define', 'define-syntax', 'delay', 'denominator', 'display', 'do',
+
+ 'e', 'eof-object?', 'eq?', 'equal?', 'eqv?', 'even?', 'exact->inexact',
+ 'exact?', 'exp', 'expt', 'else',
+
+ 'f', 'floor', 'for-each', 'force', 'free-identifer=?',
+
+ 'gcd', 'gen-counter', 'gen-loser', 'generate-identifier',
+
+ 'identifier->symbol', 'identifier', 'if', 'imag-part', 'inexact->exact',
+ 'inexact?', 'input-port?', 'integer->char', 'integer?', 'integrate-system',
+
+ 'l', 'lambda', 'last-pair', 'lcm', 'length', 'let', 'let*', 'letrec',
+ 'list', 'list->string', 'list->vector', 'list-ref', 'list-tail', 'list?',
+ 'load', 'log',
+
+ 'magnitude', 'make-polar', 'make-promise', 'make-rectangular',
+ 'make-string', 'make-vector', 'map', 'map-streams', 'max', 'member',
+ 'memq', 'memv', 'min', 'modulo',
+
+ 'negative', 'newline', 'nil', 'not', 'null?', 'number->string', 'number?',
+ 'numerator',
+
+ 'odd?', 'open-input-file', 'open-output-file', 'or', 'output-port',
+
+ 'pair?', 'peek-char', 'positive?', 'procedure?',
+
+ 'quasiquote', 'quote', 'quotient',
+
+ 'rational', 'rationalize', 'read', 'read-char', 'real-part', 'real?',
+ 'remainder', 'return', 'reverse',
+
+ 's', 'sequence', 'set!', 'set-char!', 'set-cdr!', 'sin', 'sqrt', 'string',
+ 'string->list', 'string->number', 'string->symbol', 'string-append',
+ 'string-ci<=?', 'string-ci<?', 'string-ci=?', 'string-ci>=?',
+ 'string-ci>?', 'string-copy', 'string-fill!', 'string-length',
+ 'string-ref', 'string-set!', 'string<=?', 'string<?', 'string=?',
+ 'string>=?', 'string>?', 'string?', 'substring', 'symbol->string',
+ 'symbol?', 'syntax', 'syntax-rules',
+
+ 't', 'tan', 'template', 'transcript-off', 'transcript-on', 'truncate',
+
+ 'unquote', 'unquote-splicing', 'unwrap-syntax',
+
+ 'vector', 'vector->list', 'vector-fill!', 'vector-length', 'vector-ref',
+ 'vector-set!', 'vector?',
+
+ 'with-input-from-file', 'with-output-to-file', 'write', 'write-char',
+
+ 'zero?'
+
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '{', '}', '[', ']', '!', '%', '^', '&', '/','+','-','*','=','<','>',';','|'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #202020;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/sdlbasic.php b/plugins/dokuwiki/inc/geshi/sdlbasic.php
new file mode 100644
index 0000000..f930a6f
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/sdlbasic.php
@@ -0,0 +1,163 @@
+<?php
+/*************************************************************************************
+ * sdlbasic.php
+ * ------------
+ * Author: Roberto Rossi
+ * Copyright: (c) 2005 Roberto Rossi (http://rsoftware.altervista.org)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.12.2.5 $
+ * Date Started: 2005/08/19
+ * Date Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * sdlBasic (http://sdlbasic.sf.net) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2005/08/19 (1.0.0)
+ * - First Release
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+ $language_data = array (
+ 'LANG_NAME' => 'sdlBasic',
+ 'COMMENT_SINGLE' => array(1 => "'", 2 => "rem", 3 => "!", 4 => "#"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'const', 'option', 'explicit', 'option', 'qbasic', 'include', 'argc',
+ 'argv', 'command', 'command$', 'run', 'shell', 'end', 'os', 'declare',
+ 'sub', 'function', 'return', 'while', 'wend', 'exit', 'while', 'end',
+ 'while', 'continue', 'if', 'then', 'else', 'elseif', 'end', 'if',
+ 'select', 'case', 'case', 'else', 'end', 'case', 'for', 'each', 'step',
+ 'next', 'to', 'continue', 'dim', 'shared', 'common', 'lbound', 'bound',
+ 'erase', 'asc', 'chr', 'chr$', 'insert', 'insert$', 'instr', 'lcase',
+ 'lcase$', 'left', 'left$', 'len', 'length', 'ltrim', 'ltrim$', 'mid',
+ 'mid$', 'replace', 'replace$', 'replacesubstr', 'replacesubstr$',
+ 'reverse', 'reverse$', 'right', 'right$', 'rinstr', 'rtrim', 'rtrim$',
+ 'space', 'space$', 'str', 'str$', 'strf', 'strf$', 'string', 'string$',
+ 'tally', 'trim', 'trim$', 'typeof', 'typeof$', 'ucase', 'ucase$', 'val',
+ 'abs', 'acos', 'andbit', 'asin', 'atan', 'bitwiseand', 'bitwiseor',
+ 'bitwisexor', 'cos', 'exp', 'fix', 'floor', 'frac', 'hex', 'hex$', 'int',
+ 'log', 'min', 'max', 'orbit', 'randomize', 'rnd', 'round', 'sgn', 'sin',
+ 'sqr', 'tan', 'xorbit', 'open', 'as', 'file', 'input', 'close', 'output',
+ 'append', 'eof', 'fileexists', 'filecopy', 'filemove', 'filerename',
+ 'freefile', 'kill', 'loc', 'lof', 'readbyte', 'rename', 'seek',
+ 'writebyte', 'chdir', 'dir', 'dir$', 'direxists', 'dirfirst', 'dirnext',
+ 'mkdir', 'rmdir', 'print', 'date', 'date$', 'time', 'time$', 'ticks',
+ 'data', 'read', 'reservebank', 'freebank', 'copybank', 'loadbank',
+ 'savebank', 'setbank', 'sizebank', 'poke', 'doke', 'loke', 'peek', 'deek',
+ 'leek', 'memcopy', 'setdisplay', 'setcaption', 'caption', 'displaywidth',
+ 'displayheight', 'displaybpp', 'screen', 'directscreen', 'screenopen',
+ 'screenclose', 'screenclone', 'screencopy', 'screenfade', 'screenfadein',
+ 'screencrossfade', 'screenalpha', 'screenlock', 'screenunlock',
+ 'screenrect', 'xscreenrect', 'yscreenrect', 'wscreenrect', 'hscreenrect',
+ 'flagscreenrect', 'screenwidth', 'screenheight', 'offset', 'xoffset',
+ 'yoffset', 'cls', 'screenswap', 'autoback', 'setautoback',
+ 'dualplayfield', 'waitvbl', 'fps', 'rgb', 'enablepalette', 'color',
+ 'palette', 'colorcycling', 'ink', 'point', 'dot', 'plot', 'line', 'box',
+ 'bar', 'circle', 'fillcircle', 'ellipse', 'fillellipse', 'paint',
+ 'loadimage', 'saveimage', 'loadsound', 'savesound', 'loadmusic',
+ 'hotspot', 'setcolorkey', 'imageexists', 'imagewidth', 'imageheight',
+ 'deleteimage', 'copyimage', 'setalpha', 'zoomimage', 'rotateimage',
+ 'rotozoomimage', 'blt', 'pastebob', 'pasteicon', 'grab', 'spriteclip',
+ 'sprite', 'deletesprite', 'xsprite', 'ysprite', 'spritewidth',
+ 'spriteheight', 'frsprite', 'livesprite', 'spritehit', 'autoupdatesprite',
+ 'updatesprite', 'setbob', 'bob', 'deletebob', 'xbob', 'ybob', 'bobwidth',
+ 'bobheight', 'frbob', 'livebob', 'bobhit', 'autoupdatebob', 'updatebob',
+ 'text', 'setfont', 'textrender', 'pen', 'paper', 'prints', 'locate',
+ 'atx', 'aty', 'curson', 'cursoff', 'inputs', 'zoneinputs',
+ 'isenabledsound', 'soundexists', 'deletesound', 'copysound',
+ 'musicexists', 'playsound', 'volumesound', 'stopsound', 'pausesound',
+ 'resumesound', 'vumetersound', 'positionsound', 'soundchannels',
+ 'playmusic', 'positionmusic', 'stopmusic', 'fademusic', 'pausemusic',
+ 'resumemusic', 'rewindmusic', 'volumemusic', 'speedmusic', 'numdrivescd',
+ 'namecd', 'getfreecd', 'opencd', 'indrivecd', 'trackscd', 'curtrackcd',
+ 'curframecd', 'playcd', 'playtrackscd', 'playtrackscd', 'playtrackscd',
+ 'pausecd', 'resumecd', 'stopcd', 'ejectcd', 'closecd', 'tracktypecd',
+ 'tracklengthcd', 'trackoffsetcd', 'key', 'inkey', 'waitkey', 'xmouse',
+ 'ymouse', 'xmousescreen', 'ymousescreen', 'bmouse', 'changemouse',
+ 'locatemouse', 'mouseshow', 'mousehide', 'mousezone', 'numjoysticks',
+ 'namejoystick', 'numaxesjoystick', 'numballsjoystick', 'numhatsjoystick',
+ 'numbuttonsjoystick', 'getaxisjoystick', 'gethatjoystick',
+ 'getbuttonjoystick', 'xgetballjoystick', 'ygetballjoystick', 'joy',
+ 'bjoy', 'wait', 'timer', 'isenabledsock', 'getfreesock', 'opensock',
+ 'acceptsock', 'isserverready', 'connectsock', 'connectionreadysock',
+ 'isclientready', 'losesock', 'peeksock', 'readsock', 'readbytesock',
+ 'readlinesock', 'writesock', 'writebytesock', 'writelinesock',
+ 'getremoteip', 'getremoteport', 'getlocalip'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080;',
+ 2 => 'color: #808080;',
+ 3 => 'color: #808080;',
+ 4 => 'color: #808080;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/smarty.php b/plugins/dokuwiki/inc/geshi/smarty.php
new file mode 100644
index 0000000..3daa439
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/smarty.php
@@ -0,0 +1,168 @@
+<?php
+/*************************************************************************************
+ * smarty.php
+ * ----------
+ * Author: Alan Juden (alan@judenware.org)
+ * Copyright: (c) 2004 Alan Juden, Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.5 $
+ * Date Started: 2004/07/10
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * Smarty template language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.0)
+ * - Initial Release
+ *
+ * TODO
+ * ----
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Smarty',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array('{*' => '*}'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '$smarty', 'now', 'const', 'capture', 'config', 'section', 'foreach', 'template', 'version', 'ldelim', 'rdelim',
+ 'config_load', 'foreachelse', 'include', 'include_php', 'insert', 'if', 'elseif', 'else', 'php',
+ 'sectionelse', 'clear_all_cache', 'clear_cache', 'is_cached',
+ ),
+ 2 => array(
+ 'capitalize', 'count_characters', 'cat', 'count_paragraphs', 'count_sentences', 'count_words', 'date_format',
+ 'default', 'escape', 'indent', 'lower', 'nl2br', 'regex_replace', 'replace', 'spacify', 'string_format',
+ 'strip', 'strip_tags', 'truncate', 'upper', 'wordwrap'
+ ),
+ 3 => array(
+ 'assign', 'counter', 'cycle', 'debug', 'eval', 'fetch', 'html_checkboxes', 'html_image', 'html_options',
+ 'html_radios', 'html_select_date', 'html_select_time', 'html_table', 'math', 'mailto', 'popup_init',
+ 'popup', 'textformat'
+ ),
+ 4 => array(
+ '$template_dir', '$compile_dir', '$config_dir', '$plugins_dir', '$debugging', '$debug_tpl',
+ '$debugging_ctrl', '$autoload_filters', '$compile_check', '$force_compile', '$caching', '$cache_dir',
+ '$cache_lifetime', '$cache_handler_func', '$cache_modified_check', '$config_overwrite',
+ '$config_booleanize', '$config_read_hidden', '$config_fix_newlines', '$default_template_handler_func',
+ '$php_handling', '$security', '$secure_dir', '$security_settings', '$trusted_dir', '$left_delimiter',
+ '$right_delimiter', '$compiler_class', '$request_vars_order', '$request_use_auto_globals',
+ '$error_reporting', '$compile_id', '$use_sub_dirs', '$default_modifiers', '$default_resource_type'
+ ),
+ 5 => array(
+ 'append', 'append_by_ref', 'assign', 'assign_by_ref', 'clear_all_assign', 'clear_all_cache',
+ 'clear_assign', 'clear_cache', 'clear_compiled_tpl', 'clear_config', 'config_load', 'display',
+ 'fetch', 'get_config_vars', 'get_registered_object', 'get_template_vars', 'is_cached',
+ 'load_filter', 'register_block', 'register_compiler_function', 'register_function',
+ 'register_modifier', 'register_object', 'register_outputfilter', 'register_postfilter',
+ 'register_prefilter', 'register_resource', 'trigger_error', 'template_exists', 'unregister_block',
+ 'unregister_compiler_function', 'unregister_function', 'unregister_modifier', 'unregister_object',
+ 'unregister_outputfilter', 'unregister_postfilter', 'unregister_prefilter', 'unregister_resource'
+ ),
+ 6 => array(
+ 'name', 'assign', 'file', 'scope', 'global', 'key', 'once', 'script',
+ 'loop', 'start', 'step', 'max', 'show', 'values', 'value', 'from', 'item'
+ ),
+ 7 => array(
+ 'eq', 'neq', 'ne', 'lte', 'gte', 'ge', 'le', 'not', 'mod'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '/', '=', '==', '!=', '>', '<', '>=', '<=', '!', '%'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false,
+ 6 => false,
+ 7 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0600FF;', //Functions
+ 2 => 'color: #008000;', //Modifiers
+ 3 => 'color: #0600FF;', //Custom Functions
+ 4 => 'color: #804040;', //Variables
+ 5 => 'color: #008000;', //Methods
+ 6 => 'color: #6A0A0A;', //Attributes
+ 7 => 'color: #D36900;' //Text-based symbols
+ ),
+ 'COMMENTS' => array(
+ 'MULTI' => 'color: #008080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #D36900;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #D36900;'
+ ),
+ 'SCRIPT' => array(
+ 0 => ''
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => 'http://smarty.php.net/{FNAME}',
+ 2 => 'http://smarty.php.net/{FNAME}',
+ 3 => 'http://smarty.php.net/{FNAME}',
+ 4 => 'http://smarty.php.net/{FNAME}',
+ 5 => 'http://smarty.php.net/{FNAME}',
+ 6 => '',
+ 7 => 'http://smarty.php.net/{FNAME}'
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_ALWAYS,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '{' => '}'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => true
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/sql.php b/plugins/dokuwiki/inc/geshi/sql.php
new file mode 100644
index 0000000..adbe795
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/sql.php
@@ -0,0 +1,137 @@
+<?php
+/*************************************************************************************
+ * sql.php
+ * -------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * SQL language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.3)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.2)
+ * - Added "`" string delimiter
+ * - Added "#" single comment starter
+ * 2004/08/05 (1.0.1)
+ * - Added support for symbols
+ * - Added many more keywords (mostly MYSQL keywords)
+ * 2004/07/14 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Add all keywords
+ * * Split this to several sql files - mysql-sql, ansi-sql etc
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'SQL',
+ 'COMMENT_SINGLE' => array(1 =>'--', 2 => '#'),
+ 'COMMENT_MULTI' => array('/*' => '*/'),
+ 'CASE_KEYWORDS' => 1,
+ 'QUOTEMARKS' => array("'", '"', '`'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'ALL', 'ASC', 'AS', 'ALTER', 'AND', 'ADD', 'AUTO_INCREMENT',
+ 'BETWEEN', 'BINARY', 'BOTH', 'BY', 'BOOLEAN',
+ 'CHANGE', 'CHECK', 'COLUMNS', 'COLUMN', 'CROSS','CREATE',
+ 'DATABASES', 'DATABASE', 'DATA', 'DELAYED', 'DESCRIBE', 'DESC', 'DISTINCT', 'DELETE', 'DROP', 'DEFAULT',
+ 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXPLAIN',
+ 'FIELDS', 'FIELD', 'FLUSH', 'FOR', 'FOREIGN', 'FUNCTION', 'FROM',
+ 'GROUP', 'GRANT',
+ 'HAVING',
+ 'IGNORE', 'INDEX', 'INFILE', 'INSERT', 'INNER', 'INTO', 'IDENTIFIED', 'IN', 'IS', 'IF',
+ 'JOIN',
+ 'KEYS', 'KILL','KEY',
+ 'LEADING', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOW_PRIORITY', 'LEFT', 'LANGUAGE',
+ 'MODIFY',
+ 'NATURAL', 'NOT', 'NULL', 'NEXTVAL',
+ 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'ORDER', 'OUTFILE', 'OR', 'OUTER', 'ON',
+ 'PROCEEDURE','PROCEDURAL', 'PRIMARY',
+ 'READ', 'REFERENCES', 'REGEXP', 'RENAME', 'REPLACE', 'RETURN', 'REVOKE', 'RLIKE', 'RIGHT',
+ 'SHOW', 'SONAME', 'STATUS', 'STRAIGHT_JOIN', 'SELECT', 'SETVAL', 'SET',
+ 'TABLES', 'TEMINATED', 'TO', 'TRAILING','TRUNCATE', 'TABLE', 'TEMPORARY', 'TRIGGER', 'TRUSTED',
+ 'UNIQUE', 'UNLOCK', 'USE', 'USING', 'UPDATE', 'UNSIGNED',
+ 'VALUES', 'VARIABLES', 'VIEW',
+ 'WITH', 'WRITE', 'WHERE',
+ 'ZEROFILL',
+ 'XOR',
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')', '=', '<', '>', '|'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #993333; font-weight: bold;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080; font-style: italic;',
+ 2 => 'color: #808080; font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/vb.php b/plugins/dokuwiki/inc/geshi/vb.php
new file mode 100644
index 0000000..0d2db77
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/vb.php
@@ -0,0 +1,150 @@
+<?php
+/*************************************************************************************
+ * vb.php
+ * ------
+ * Author: Roberto Rossi (rsoftware@altervista.org)
+ * Copyright: (c) 2004 Roberto Rossi (http://rsoftware.altervista.org), Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.14.2.4 $
+ * Date Started: 2004/08/30
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * Visual Basic language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/08/30 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+ $language_data = array (
+ 'LANG_NAME' => 'Visual Basic',
+ 'COMMENT_SINGLE' => array(1 => "'"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ 'as', 'err', 'boolean', 'and', 'or', 'recordset', 'unload', 'to',
+ 'integer','long','single','new','database','nothing','set','close',
+ 'open','print','split','line','field','querydef','instrrev',
+ 'abs','array','asc','ascb','ascw','atn','avg','me',
+ 'cbool','cbyte','ccur','cdate','cdbl','cdec','choose','chr','chrb','chrw','cint','clng',
+ 'command','cos','count','createobject','csng','cstr','curdir','cvar','cvdate','cverr',
+ 'date','dateadd','datediff','datepart','dateserial','datevalue','day','ddb','dir','doevents',
+ 'environ','eof','error','exp',
+ 'fileattr','filedatetime','filelen','fix','format','freefile','fv',
+ 'getallstrings','getattr','getautoserversettings','getobject','getsetting',
+ 'hex','hour','iif','imestatus','input','inputb','inputbox','instr','instb','int','ipmt',
+ 'isarray','isdate','isempty','iserror','ismissing','isnull','isnumeric','isobject',
+ 'lbound','lcase','left','leftb','len','lenb','loadpicture','loc','lof','log','ltrim',
+ 'max','mid','midb','min','minute','mirr','month','msgbox',
+ 'now','nper','npv','oct','partition','pmt','ppmt','pv','qbcolor',
+ 'rate','rgb','right','rightb','rnd','rtrim',
+ 'second','seek','sgn','shell','sin','sln','space','spc','sqr','stdev','stdevp','str',
+ 'strcomp','strconv','string','switch','sum','syd',
+ 'tab','tan','time','timer','timeserial','timevalue','trim','typename',
+ 'ubound','ucase','val','var','varp','vartype','weekday','year',
+ 'appactivate','base','beep','call','case','chdir','chdrive','const',
+ 'declare','defbool','defbyte','defcur','defdate','defdbl','defdec','defint',
+ 'deflng','defobj','defsng','defstr','deftype','defvar','deletesetting','dim','do',
+ 'else','elseif','end','enum','erase','event','exit','explicit',
+ 'false','filecopy','for','foreach','friend','function','get','gosub','goto',
+ 'if','implements','kill','let','lineinput','lock','loop','lset','mkdir','name','next','not',
+ 'onerror','on','option','private','property','public','put','raiseevent','randomize',
+ 'redim','rem','reset','resume','return','rmdir','rset',
+ 'savepicture','savesetting','sendkeys','setattr','static','sub',
+ 'then','true','type','unlock','wend','while','width','with','write',
+ 'vbabort','vbabortretryignore','vbapplicationmodal','vbarray',
+ 'vbbinarycompare','vbblack','vbblue','vbboolean','vbbyte','vbcancel',
+ 'vbcr','vbcritical','vbcrlf','vbcurrency','vbcyan','vbdataobject',
+ 'vbdate','vbdecimal','vbdefaultbutton1','vbdefaultbutton2',
+ 'vbdefaultbutton3','vbdefaultbutton4','vbdouble','vbempty',
+ 'vberror','vbexclamation','vbfirstfourdays','vbfirstfullweek',
+ 'vbfirstjan1','vbformfeed','vbfriday','vbgeneraldate','vbgreen',
+ 'vbignore','vbinformation','vbinteger','vblf','vblong','vblongdate',
+ 'vblongtime','vbmagenta','vbmonday','vbnewline','vbno','vbnull',
+ 'vbnullchar','vbnullstring','vbobject','vbobjecterror','vbok','vbokcancel',
+ 'vbokonly','vbquestion','vbred','vbretry','vbretrycancel','vbsaturday',
+ 'vbshortdate','vbshorttime','vbsingle','vbstring','vbsunday',
+ 'vbsystemmodal','vbtab','vbtextcompare','vbthursday','vbtuesday',
+ 'vbusesystem','vbusesystemdayofweek','vbvariant','vbverticaltab',
+ 'vbwednesday','vbwhite','vbyellow','vbyes','vbyesno','vbyesnocancel',
+ 'vbnormal','vbdirectory'
+ )
+ ),
+ 'SYMBOLS' => array(
+ '(', ')'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ 1 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #b1b100;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #808080;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #66cc66;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099;'
+ ),
+ 'SCRIPT' => array(
+ ),
+ 'REGEXPS' => array(
+ )
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/vbnet.php b/plugins/dokuwiki/inc/geshi/vbnet.php
new file mode 100644
index 0000000..984bcf8
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/vbnet.php
@@ -0,0 +1,199 @@
+<?php
+/*************************************************************************************
+ * vbnet.php
+ * ---------
+ * Author: Alan Juden (alan@judenware.org)
+ * Copyright: (c) 2004 Alan Juden, Nigel McNie (http://qbnz.com/highlighter)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.15.2.4 $
+ * Date Started: 2004/06/04
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * VB.NET language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.0)
+ * - Initial release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+ $language_data = array (
+ 'LANG_NAME' => 'vb.net',
+ 'COMMENT_SINGLE' => array(1 => "'"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ 1 => array(
+ '3DDKSHADOW', '3DHIGHLIGHT', '3DLIGHT', 'ABORT', 'ABORTRETRYIGNORE', 'ACTIVEBORDER',
+ 'ACTIVETITLEBAR', 'ALIAS', 'APPLICATIONMODAL', 'APPLICATIONWORKSPACE', 'ARCHIVE',
+ 'BACK', 'BINARYCOMPARE', 'BLACK', 'BLUE', 'BUTTONFACE', 'BUTTONSHADOW', 'BUTTONTEXT',
+ 'CANCEL', 'CDROM', 'CR', 'CRITICAL', 'CRLF', 'CYAN', 'DEFAULT', 'DEFAULTBUTTON1',
+ 'DEFAULTBUTTON2', 'DEFAULTBUTTON3', 'DESKTOP', 'DIRECTORY', 'EXCLAMATION', 'FALSE',
+ 'FIXED', 'FORAPPENDING', 'FORMFEED', 'FORREADING', 'FORWRITING', 'FROMUNICODE',
+ 'GRAYTEXT', 'GREEN', 'HIDDEN', 'HIDE', 'HIGHLIGHT', 'HIGHLIGHTTEXT', 'HIRAGANA',
+ 'IGNORE', 'INACTIVEBORDER', 'INACTIVECAPTIONTEXT', 'INACTIVETITLEBAR', 'INFOBACKGROUND',
+ 'INFORMATION', 'INFOTEXT', 'KATAKANALF', 'LOWERCASE', 'MAGENTA', 'MAXIMIZEDFOCUS',
+ 'MENUBAR', 'MENUTEXT', 'METHOD', 'MINIMIZEDFOCUS', 'MINIMIZEDNOFOCUS', 'MSGBOXRIGHT',
+ 'MSGBOXRTLREADING', 'MSGBOXSETFOREGROUND', 'NARROW', 'NEWLINE', 'NO', 'NORMAL',
+ 'NORMALFOCUS', 'NORMALNOFOCUS', 'NULLSTRING', 'OBJECTERROR', 'OK', 'OKCANCEL', 'OKONLY',
+ 'PROPERCASE', 'QUESTION', 'RAMDISK', 'READONLY', 'RED', 'REMOTE', 'REMOVABLE', 'RETRY',
+ 'RETRYCANCEL', 'SCROLLBARS', 'SYSTEMFOLDER', 'SYSTEMMODAL', 'TAB', 'TEMPORARYFOLDER',
+ 'TEXTCOMPARE', 'TITLEBARTEXT', 'TRUE', 'UNICODE', 'UNKNOWN', 'UPPERCASE', 'VERTICALTAB',
+ 'VOLUME', 'WHITE', 'WIDE', 'WIN16', 'WIN32', 'WINDOWBACKGROUND', 'WINDOWFRAME',
+ 'WINDOWSFOLDER', 'WINDOWTEXT', 'YELLOW', 'YES', 'YESNO', 'YESNOCANCEL'
+ ),
+ 2 => array(
+ 'As', 'ADDHANDLER', 'ASSEMBLY', 'AUTO', 'Binary', 'ByRef', 'ByVal', 'BEGINEPILOGUE',
+ 'Else', 'Empty', 'Error', 'ENDPROLOGUE', 'EXTERNALSOURCE', 'ENVIRON', 'For',
+ 'Friend', 'GET', 'HANDLES', 'Input', 'Is', 'Len', 'Lock', 'Me', 'Mid', 'MUSTINHERIT',
+ 'MYBASE', 'MYCLASS', 'New', 'Next', 'Nothing', 'Null', 'NOTINHERITABLE',
+ 'NOTOVERRIDABLE', 'OFF', 'On', 'Option', 'Optional', 'OVERRIDABLE', 'ParamArray',
+ 'Print', 'Private', 'Property', 'Public', 'Resume', 'Seek', 'Static', 'Step',
+ 'String', 'SHELL', 'SENDKEYS', 'SET', 'Then', 'Time', 'To', 'THROW', 'WithEvents'
+ ),
+ 3 => array(
+ 'COLLECTION', 'DEBUG', 'DICTIONARY', 'DRIVE', 'DRIVES', 'ERR', 'FILE', 'FILES',
+ 'FILESYSTEMOBJECT', 'FOLDER', 'FOLDERS', 'TEXTSTREAM'
+ ),
+ 4 => array(
+ 'BOOLEAN', 'BYTE', 'DATE', 'DECIMIAL', 'DOUBLE', 'INTEGER', 'LONG', 'OBJECT',
+ 'SINGLE STRING'
+ ),
+ 5 => array(
+ 'ADDRESSOF', 'AND', 'BITAND', 'BITNOT', 'BITOR', 'BITXOR',
+ 'GETTYPE', 'LIKE', 'MOD', 'NOT', 'ORXOR'
+ ),
+ 6 => array(
+ 'APPACTIVATE', 'BEEP', 'CALL', 'CHDIR', 'CHDRIVE', 'CLASS', 'CASE', 'CATCH',
+ 'DECLARE', 'DELEGATE', 'DELETESETTING', 'DIM', 'DO', 'DOEVENTS', 'END', 'ENUM',
+ 'EVENT', 'EXIT', 'EACH', 'FUNCTION', 'FINALLY', 'IF', 'IMPORTS', 'INHERITS',
+ 'INTERFACE', 'IMPLEMENTS', 'KILL', 'LOOP', 'MIDB', 'NAMESPACE', 'OPEN', 'PUT',
+ 'RAISEEVENT', 'RANDOMIZE', 'REDIM', 'REM', 'RESET', 'SAVESETTING', 'SELECT',
+ 'SETATTR', 'STOP', 'SUB', 'SYNCLOCK', 'STRUCTURE', 'SHADOWS', 'SWITCH',
+ 'TIMEOFDAY', 'TODAY', 'TRY', 'WIDTH', 'WITH', 'WRITE', 'WHILE'
+ ),
+ 7 => array(
+ 'ABS', 'ARRAY', 'ASC', 'ASCB', 'ASCW', 'CALLBYNAME', 'CBOOL', 'CBYTE', 'CCHAR',
+ 'CCHR', 'CDATE', 'CDBL', 'CDEC', 'CHOOSE', 'CHR', 'CHR$', 'CHRB', 'CHRB$', 'CHRW',
+ 'CINT', 'CLNG', 'CLNG8', 'CLOSE', 'COBJ', 'COMMAND', 'COMMAND$', 'CONVERSION',
+ 'COS', 'CREATEOBJECT', 'CSHORT', 'CSTR', 'CURDIR', 'CTYPE', 'CVDATE', 'DATEADD',
+ 'DATEDIFF', 'DATEPART', 'DATESERIAL', 'DATEVALUE', 'DAY', 'DDB', 'DIR', 'DIR$',
+ 'EOF', 'ERROR$', 'EXP', 'FILEATTR', 'FILECOPY', 'FILEDATATIME', 'FILELEN', 'FILTER',
+ 'FIX', 'FORMAT', 'FORMAT$', 'FORMATCURRENCY', 'FORMATDATETIME', 'FORMATNUMBER',
+ 'FORMATPERCENT', 'FREEFILE', 'FV', 'GETALLSETTINGS', 'GETATTRGETOBJECT', 'GETSETTING',
+ 'HEX', 'HEX$', 'HOUR', 'IIF', 'IMESTATUS', 'INPUT$', 'INPUTB', 'INPUTB$', 'INPUTBOX',
+ 'INSTR', 'INSTRB', 'INSTRREV', 'INT', 'IPMT', 'IRR', 'ISARRAY', 'ISDATE', 'ISEMPTY',
+ 'ISERROR', 'ISNULL', 'ISNUMERIC', 'ISOBJECT', 'JOIN', 'LBOUND', 'LCASE', 'LCASE$',
+ 'LEFT', 'LEFT$', 'LEFTB', 'LEFTB$', 'LENB', 'LINEINPUT', 'LOC', 'LOF', 'LOG', 'LTRIM',
+ 'LTRIM$', 'MID$', 'MIDB', 'MIDB$', 'MINUTE', 'MIRR', 'MKDIR', 'MONTH', 'MONTHNAME',
+ 'MSGBOX', 'NOW', 'NPER', 'NPV', 'OCT', 'OCT$', 'PARTITION', 'PMT', 'PPMT', 'PV',
+ 'RATE', 'REPLACE', 'RIGHT', 'RIGHT$', 'RIGHTB', 'RIGHTB$', 'RMDIR', 'RND', 'RTRIM',
+ 'RTRIM$', 'SECOND', 'SIN', 'SLN', 'SPACE', 'SPACE$', 'SPC', 'SPLIT', 'STR', 'STR$',
+ 'STRCOMP', 'STRCONV', 'STRING$', 'STRREVERSE', 'SYD', 'TAB', 'TAN', 'TIMEOFDAY',
+ 'TIMER', 'TIMESERIAL', 'TIMEVALUE', 'TODAY', 'TRIM', 'TRIM$', 'TYPENAME', 'UBOUND',
+ 'UCASE', 'UCASE$', 'VAL', 'WEEKDAY', 'WEEKDAYNAME', 'YEAR'
+ ),
+ 8 => array(
+ 'ANY', 'ATN', 'CALENDAR', 'CIRCLE', 'CURRENCY', 'DEFBOOL', 'DEFBYTE', 'DEFCUR',
+ 'DEFDATE', 'DEFDBL', 'DEFDEC', 'DEFINT', 'DEFLNG', 'DEFOBJ', 'DEFSNG', 'DEFSTR',
+ 'DEFVAR', 'EQV', 'GOSUB', 'IMP', 'INITIALIZE', 'ISMISSING', 'LET', 'LINE', 'LSET',
+ 'RSET', 'SGN', 'SQR', 'TERMINATE', 'VARIANT', 'VARTYPE', 'WEND'
+ ),
+ ),
+ 'SYMBOLS' => array(
+ '&', '&=', '*', '*=', '+', '+=', '-', '-=', '//', '/', '/=', '=', '\\', '\\=',
+ '^', '^='
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ 5 => false,
+ 6 => false,
+ 7 => false,
+ 8 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #0600FF;', //Constants
+ 2 => 'color: #FF8000;', //Keywords
+ 3 => 'color: #008000;', //Data Types
+ 4 => 'color: #FF0000;', //Objects
+ 5 => 'color: #804040;', //Operators
+ 6 => 'color: #0600FF;', //Statements
+ 7 => 'color: #0600FF;', //Functions
+ 8 => 'color: #0600FF;' //Deprecated
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #008080; font-style: italic;',
+ 'MULTI' => 'color: #008080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #008080; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #000000;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #808080;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #FF0000;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #0000FF;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #008000;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => '',
+ 3 => 'http://www.google.com/search?q={FNAME}+msdn.microsoft.com',
+ 4 => ''
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 =>'.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/vhdl.php b/plugins/dokuwiki/inc/geshi/vhdl.php
new file mode 100644
index 0000000..9ad7bab
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/vhdl.php
@@ -0,0 +1,140 @@
+<?php
+/*************************************************************************************
+ * vhdl.php
+ * --------
+ * Author: Alexander 'E-Razor' Krause (admin@erazor-zone.de)
+ * Copyright: (c) 2005 Alexander Krause
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2005/06/15
+ * Last Modified: $Date: 2006/09/23 02:05:48 $
+ *
+ * VHDL (VHSICADL, very high speed integrated circuit HDL) language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2006/06/15 (1.0.0)
+ * - First Release
+ *
+ * TODO
+ * ----
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'VHDL',
+ 'COMMENT_SINGLE' => array(1 => '--'),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ /*keywords*/
+ 1 => array(
+ 'access','after','alias','all','assert','architecture','begin',
+ 'block','body','buffer','bus','case','component','configuration','constant',
+ 'disconnect','downto','else','elsif','end','entity','exit','file','for',
+ 'function','generate','generic','group','guarded','if','impure','in',
+ 'inertial','inout','is','label','library','linkage','literal','loop',
+ 'map','new','next','null','of','on','open','others','out','package',
+ 'port','postponed','procedure','process','pure','range','record','register',
+ 'reject','report','return','select','severity','signal','shared','subtype',
+ 'then','to','transport','type','unaffected','units','until','use','variable',
+ 'wait','when','while','with','note','warning','error','failure','and',
+ 'or','xor','not','nor'
+ ),
+ /*types*/
+ 2 => array(
+ 'bit','bit_vector','character','boolean','integer','real','time','string',
+ 'severity_level','positive','natural','signed','unsigned','line','text',
+ 'std_logic','std_logic_vector','std_ulogic','std_ulogic_vector','qsim_state',
+ 'qsim_state_vector','qsim_12state','qsim_12state_vector','qsim_strength',
+ 'mux_bit','mux_vector','reg_bit','reg_vector','wor_bit','wor_vector'
+ ),
+ /*operators*/
+ 3 => array(
+ '=','<=',':=','=>','=='
+ )
+ ),
+ 'SYMBOLS' => array(
+ '[', ']', '(', ')',';','<','>',':'
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: #000000; font-weight: bold;',
+ 2 => 'color: #aa0000;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: #adadad; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #7f007f;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #ff0000;',
+ 1 => 'color: #ff0000;',
+ 2 => 'color: #ff0000;',
+ 3 => 'color: #ff0000;'
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'URLS' => array(
+ 1 => '',
+ 2 => ''
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => '(\b(0x)[0-9a-fA-F]{2,}[hH]?|\b(0x)?[0-9a-fA-F]{2,}[hH])|'.
+ '(\b[0-9]{1,}((\.){1}[0-9]{1,}){0,1}(E)[\-]{0,1}[0-9]{1,})|'.
+ '(\b(ns))|'.
+ "('[0-9a-zA-Z]+)",
+ 1 => "\b(''[0-9]'')"
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/geshi/visualfoxpro.php b/plugins/dokuwiki/inc/geshi/visualfoxpro.php
new file mode 100644
index 0000000..60190e4
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/visualfoxpro.php
@@ -0,0 +1,444 @@
+<?php
+/*************************************************************************************
+ * visualfoxpro.php
+ * ----------------
+ * Author: Roberto Armellin (r.armellin@tin.it)
+ * Copyright: (c) 2004 Roberto Armellin, Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.13.2.5 $
+ * Date Started: 2004/09/17
+ * Last Modified: 2004/09/18
+ *
+ * Visual FoxPro language file for GeSHi.
+ *
+ * CHANGES
+ * -------
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/10/27)
+ * -------------------------
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'Visual Fox Pro',
+ 'COMMENT_SINGLE' => array(1 => "//", 2 => "\n*"),
+ 'COMMENT_MULTI' => array(),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array('"'),
+ 'ESCAPE_CHAR' => '\\',
+ 'KEYWORDS' => array(
+ 1 => array('Case', 'Else', '#Else', 'Then',
+ 'Endcase', 'Enddefine', 'Enddo', 'Endfor', 'Endfunc', 'Endif', 'Endprintjob',
+ 'Endproc', 'Endscan', 'Endtext', 'Endwith', '#Endif',
+ '#Elif','#Else','#Endif','#Define','#If','#Include',
+ '#Itsexpression','#Readclauses','#Region','#Section','#Undef','#Wname',
+ 'Case','Define','Do','Else','Endcase','Enddefine',
+ 'Enddo','Endfor','Endfunc','Endif','Endprintjob','Endproc',
+ 'Endscan','Endtext','Endwith','For','Function','Hidden',
+ 'If','Local','Lparameter','Lparameters','Next','Otherwise',
+ 'Parameters','Printjob','Procedure','Protected','Public','Scan',
+ 'Text','Then','While','With','?','??',
+ '???','Abs','Accept','Access','Aclass','Acopy',
+ 'Acos','Adatabases','Adbobjects','Addbs','Addrelationtoenv','Addtabletoenv',
+ 'Adel','Adir','Aelement','Aerror','Afields','Afont',
+ 'Agetclass','Agetfileversion','Ains','Ainstance','Alen','Align',
+ 'Alines','Alltrim','Alter','Amembers','Amouseobj','Anetresources',
+ 'Ansitooem','Append','Aprinters','Ascan','Aselobj','Asin',
+ 'Asort','Assert','Asserts','Assist','Asubscript','Asynchronous',
+ 'At_c','Atan','Atc','Atcc','Atcline','Atline',
+ 'Atn2','Aused','Autoform','Autoreport','Avcxclasses','Average',
+ 'BarCount','BarPrompt','BatchMode','BatchUpdateCount','Begin','BellSound',
+ 'BinToC','Bintoc','Bitand','Bitclear','Bitlshift','Bitnot',
+ 'Bitor','Bitrshift','Bitset','Bittest','Bitxor','Bof',
+ 'Browse','BrowseRefresh','Buffering','BuilderLock','COMArray','COMReturnError',
+ 'CToBin','Calculate','Call','Capslock','Cd','Cdow',
+ 'Ceiling','Central','Change','Char','Chdir','Chr',
+ 'Chrsaw','Chrtran','Chrtranc','Close','Cmonth','Cntbar',
+ 'Cntpad','Col','Comclassinfo','CommandTargetQuery','Compile','Completed',
+ 'Compobj','Compute','Concat','ConnectBusy','ConnectHandle','ConnectName',
+ 'ConnectString','ConnectTimeOut','ContainerReleaseType','Continue','Copy','Cos',
+ 'Cot','Count','Coverage','Cpconvert','Cpcurrent','Cpdbf',
+ 'Cpnotrans','Create','CreateBinary','Createobject','Createobjectex','Createoffline',
+ 'CrsBuffering','CrsFetchMemo','CrsFetchSize','CrsMaxRows','CrsMethodUsed','CrsNumBatch',
+ 'CrsShareConnection','CrsUseMemoSize','CrsWhereClause','Ctobin','Ctod','Ctot',
+ 'Curdate','Curdir','CurrLeft','CurrSymbol','CursorGetProp','CursorSetProp',
+ 'Curtime','Curval','DBGetProp','DBSetProp','DB_BufLockRow','DB_BufLockTable',
+ 'DB_BufOff','DB_BufOptRow','DB_BufOptTable','DB_Complette','DB_DeleteInsert','DB_KeyAndModified',
+ 'DB_KeyAndTimestamp','DB_KeyAndUpdatable','DB_LocalSQL','DB_NoPrompt','DB_Prompt','DB_RemoteSQL',
+ 'DB_TransAuto','DB_TransManual','DB_TransNone','DB_Update','Datetime','Day',
+ 'Dayname','Dayofmonth','Dayofweek','Dayofyear','Dbalias','Dbused',
+ 'Ddeaborttrans','Ddeadvise','Ddeenabled','Ddeexecute','Ddeinitiate','Ddelasterror',
+ 'Ddepoke','Dderequest','Ddesetoption','Ddesetservice','Ddesettopic','Ddeterminate',
+ 'Debugout','Declare','DefOLELCid','DefaultValue','Defaultext','Degrees',
+ 'DeleteTrigger','Desc','Description','Difference','Dimension','Dir',
+ 'Directory','Diskspace','DispLogin','DispWarnings','Display','Dll',
+ 'Dmy','DoDefault','DoEvents','Doc','Doevents','Dow',
+ 'Drivetype','Drop','Dropoffline','Dtoc','Dtor','Dtos',
+ 'Dtot','DynamicInputMask','Each','Edit','Eject','Elif',
+ 'End','Eof','Erase','Evaluate','Event','Eventtracking',
+ 'Exclude','Exclusive','Exit','Exp','Export','External',
+ 'FDate','FTime','Fchsize','Fclose','Fcount','Fcreate',
+ 'Feof','Ferror','FetchMemo','FetchSize','Fflush','Fgets',
+ 'Filer','Filetostr','Find','Fklabel','Fkmax','Fldlist',
+ 'Flock','Floor','Flush','Fontmetric','Fopen','Forceext',
+ 'Forcepath','FormSetClass','FormSetLib','FormsClass','FormsLib','Found',
+ 'FoxPro','Foxcode','Foxdoc','Foxgen','Foxgraph','Foxview',
+ 'Fputs','Fread','French','Fseek','Fsize','Fv',
+ 'Fwrite','Gather','German','GetPem','Getbar','Getcolor',
+ 'Getcp','Getdir','Getenv','Getexpr','Getfile','Getfldstate',
+ 'Getfont','Gethost','Getnextmodified','Getobject','Getpad','Getpict',
+ 'Getprinter','Go','Gomonth','Goto','Graph','GridHorz',
+ 'GridShow','GridShowPos','GridSnap','GridVert','Help','HelpOn',
+ 'HelpTo','HighLightRow','Home','Hour','IMEStatus','IdleTimeOut',
+ 'Idxcollate','Ifdef','Ifndef','Iif','Import','Include',
+ 'Indbc','Index','Indexseek','Inkey','Inlist','Input',
+ 'Insert','InsertTrigger','Insmode','IsBlank','IsFLocked','IsLeadByte',
+ 'IsMouse','IsNull','IsRLocked','Isalpha','Iscolor','Isdigit',
+ 'Isexclusive','Isflocked','Ishosted','Islower','Isreadonly','Isrlocked',
+ 'Isupper','Italian','Japan','Join','Justdrive','Justext',
+ 'Justfname','Justpath','Juststem','KeyField','KeyFieldList','Keyboard'
+ ),
+ 2 => array('Keymatch','LastProject','Lastkey','Lcase','Leftc','Len',
+ 'Lenc','Length','Likec','Lineno','LoadPicture','Loadpicture',
+ 'Locate','Locfile','Log','Log10','Logout','Lookup',
+ 'Loop','Lower','Ltrim','Lupdate','Mail','MaxRecords',
+ 'Mcol','Md','Mdown','Mdx','Mdy','Memlines',
+ 'Menu','Messagebox','Minute','Mkdir','Mline','Modify',
+ 'Month','Monthname','Mouse','Mrkbar','Mrkpad','Mrow',
+ 'Mtdll','Mton','Mwindow','Native','Ndx','Network',
+ 'NoFilter','Nodefault','Normalize','Note','Now','Ntom',
+ 'NullString','Numlock','Nvl','ODBChdbc','ODBChstmt','OLEDropTextInsertion',
+ 'OLELCid','Objnum','Objref','Objtoclient','Objvar','Occurs',
+ 'Oemtoansi','Oldval','OlePublic','Olereturnerror','On','Open',
+ 'Oracle','Order','Os','Outer','PCount','Pack',
+ 'PacketSize','Padc','Padl','Padr','Payment','Pcol',
+ 'PemStatus','Pi','Pivot','Play','Pop','Popup',
+ 'Power','PrimaryKey','Printstatus','Private','Prmbar','Prmpad',
+ 'ProjectClick','Proper','Prow','Prtinfo','Push','Putfile',
+ 'Pv','Qpr','Quater','QueryTimeOut','Quit','Radians',
+ 'Rand','Rat','Ratc','Ratline','Rd','Rdlevel',
+ 'Read','Readkey','Recall','Reccount','RecentlyUsedFiles','Recno',
+ 'Recsize','Regional','Reindex','RelatedChild','RelatedTable','RelatedTag',
+ 'Remove','Rename','Repeat','Replace','Replicate','Report',
+ 'ResHeight','ResWidth','ResourceOn','ResourceTo','Resources','Restore',
+ 'Resume','Retry','Return','Revertoffline','Rgbscheme','Rightc',
+ 'Rlock','Rmdir','Rollback','Round','Rtod','Rtrim',
+ 'RuleExpression','RuleText','Run','Runscript','Rview','SQLAsynchronous',
+ 'SQLBatchMode','SQLCancel','SQLColumns','SQLConnect','SQLConnectTimeOut','SQLDisconnect',
+ 'SQLDispLogin','SQLDispWarnings','SQLExec','SQLGetProp','SQLIdleTimeOut','SQLMoreResults',
+ 'SQLPrepare','SQLQueryTimeOut','SQLSetProp','SQLTables','SQLTransactions','SQLWaitTime',
+ 'Save','SavePicture','Savepicture','ScaleUnits','Scatter','Scols',
+ 'Scroll','Sec','Second','Seek','Select','SendUpdates',
+ 'Set','SetDefault','Setfldstate','Setup','ShareConnection','ShowOLEControls',
+ 'ShowOLEInsertable','ShowVCXs','Sign','Sin','Size','SizeBox',
+ 'Skpbar','Skppad','Sort','Soundex','SourceName','Sqlcommit',
+ 'Sqll','Sqlrollback','Sqlstringconnect','Sqrt','Srows','StatusBar',
+ 'Store','Str','Strconv','Strtofile','Strtran','Stuff',
+ 'Stuffc','Substr','Substrc','Substring','Sum','Suspend',
+ 'Sys','Sysmetric','TabOrdering','Table','TableRefresh','Tablerevert',
+ 'Tableupdate','TagCount','TagNo','Tan','Target','This',
+ 'Thisform','Thisformset','Timestamp','Timestampdiff','Total','Transactions',
+ 'Transform','Trim','Truncate','Ttoc','Ttod','Txnlevel',
+ 'Txtwidth','Type','Ucase','Undefine','Unlock','Unpack',
+ 'Updatable','UpdatableFieldList','Update','UpdateName','UpdateNameList','UpdateTrigger',
+ 'UpdateType','Updated','Upper','Upsizing','Usa','Use',
+ 'UseMemoSize','Used','Val','Validate','Varread','Vartype',
+ 'Version','VersionLanguage','Wait','WaitTime','Wborder','Wchild',
+ 'Wcols','Week','Wexist','Wfont','WhereType','Windcmd',
+ 'Windhelp','Windmemo','Windmenu','Windmodify','Windquery','Windscreen',
+ 'Windsnip','Windstproc','WizardPrompt','Wlast','Wlcol','Wlrow',
+ 'Wmaximum','Wminimum','Wontop','Woutput','Wparent','Wread',
+ 'Wrows','Wtitle','Wvisible','Year','Zap','_Alignment',
+ '_Asciicols','_Asciirows','_Assist','_Beautify','_Box','_Browser',
+ '_Builder','_Calcmem','_Calcvalue','_Cliptext','_Converter','_Coverage',
+ '_Curobj','_Dblclick','_Diarydate','_Dos','_Foxdoc','_Foxgraph',
+ '_Gallery','_Gengraph','_Genhtml','_Genmenu','_Genpd','_Genscrn',
+ '_Genxtab','_Getexpr','_Include','_Indent','_Lmargin','_Mac',
+ '_Mbr_appnd','_Mbr_cpart','_Mbr_delet','_Mbr_font','_Mbr_goto','_Mbr_grid',
+ '_Mbr_link','_Mbr_mode','_Mbr_mvfld','_Mbr_mvprt','_Mbr_seek','_Mbr_sp100',
+ '_Mbr_sp200','_Mbr_szfld','_Mbrowse','_Mda_appnd','_Mda_avg','_Mda_brow',
+ '_Mda_calc','_Mda_copy','_Mda_count','_Mda_label','_Mda_pack','_Mda_reprt',
+ '_Mda_rindx','_Mda_setup','_Mda_sort','_Mda_sp100','_Mda_sp200','_Mda_sp300',
+ '_Mda_sum','_Mda_total','_Mdata','_Mdiary','_Med_clear','_Med_copy',
+ '_Med_cut','_Med_cvtst','_Med_find','_Med_finda','_Med_goto','_Med_insob',
+ '_Med_link','_Med_obj','_Med_paste','_Med_pref','_Med_pstlk','_Med_redo',
+ '_Med_repl','_Med_repla','_Med_slcta','_Med_sp100','_Med_sp200','_Med_sp300',
+ '_Med_sp400','_Med_sp500','_Med_undo','_Medit','_Mfi_clall','_Mfi_close',
+ '_Mfi_export','_Mfi_import','_Mfi_new','_Mfi_open','_Mfi_pgset','_Mfi_prevu',
+ '_Mfi_print','_Mfi_quit','_Mfi_revrt','_Mfi_savas','_Mfi_save','_Mfi_send',
+ '_Mfi_setup','_Mfi_sp100','_Mfi_sp200','_Mfi_sp300','_Mfi_sp400','_Mfile',
+ '_Mfiler','_Mfirst','_Mlabel','_Mlast','_Mline','_Mmacro',
+ '_Mmbldr','_Mpr_beaut','_Mpr_cancl','_Mpr_compl','_Mpr_do','_Mpr_docum',
+ '_Mpr_formwz','_Mpr_gener','_Mpr_graph','_Mpr_resum','_Mpr_sp100','_Mpr_sp200',
+ '_Mpr_sp300','_Mpr_suspend','_Mprog','_Mproj','_Mrc_appnd','_Mrc_chnge',
+ '_Mrc_cont','_Mrc_delet','_Mrc_goto','_Mrc_locat','_Mrc_recal','_Mrc_repl',
+ '_Mrc_seek','_Mrc_sp100','_Mrc_sp200','_Mrecord','_Mreport','_Mrqbe',
+ '_Mscreen','_Msm_data','_Msm_edit','_Msm_file','_Msm_format','_Msm_prog',
+ '_Msm_recrd','_Msm_systm','_Msm_text','_Msm_tools','_Msm_view','_Msm_windo',
+ '_Mst_about','_Mst_ascii','_Mst_calcu','_Mst_captr','_Mst_dbase','_Mst_diary',
+ '_Mst_filer','_Mst_help','_Mst_hphow','_Mst_hpsch','_Mst_macro','_Mst_office',
+ '_Mst_puzzl','_Mst_sp100','_Mst_sp200','_Mst_sp300','_Mst_specl','_Msysmenu',
+ '_Msystem','_Mtable','_Mtb_appnd','_Mtb_cpart','_Mtb_delet','_Mtb_delrc',
+ '_Mtb_goto','_Mtb_link','_Mtb_mvfld','_Mtb_mvprt','_Mtb_props','_Mtb_recal',
+ '_Mtb_sp100','_Mtb_sp200','_Mtb_sp300','_Mtb_sp400','_Mtb_szfld','_Mwi_arran',
+ '_Mwi_clear','_Mwi_cmd','_Mwi_color','_Mwi_debug','_Mwi_hide','_Mwi_hidea',
+ '_Mwi_min','_Mwi_move','_Mwi_rotat','_Mwi_showa','_Mwi_size','_Mwi_sp100',
+ '_Mwi_sp200','_Mwi_toolb','_Mwi_trace','_Mwi_view','_Mwi_zoom','_Mwindow',
+ '_Mwizards','_Mwz_all','_Mwz_form','_Mwz_foxdoc','_Mwz_import','_Mwz_label',
+ '_Mwz_mail','_Mwz_pivot','_Mwz_query','_Mwz_reprt','_Mwz_setup','_Mwz_table',
+ '_Mwz_upsizing','_Netware','_Oracle','_Padvance','_Pageno','_Pbpage',
+ '_Pcolno','_Pcopies','_Pdparms','_Pdriver','_Pdsetup','_Pecode',
+ '_Peject','_Pepage','_Pform','_Plength','_Plineno','_Ploffset',
+ '_Ppitch','_Pquality','_Pretext','_Pscode','_Pspacing','_Pwait',
+ '_Rmargin','_Runactivedoc','_Samples','_Screen','_Shell','_Spellchk',
+ '_Sqlserver','_Startup','_Tabs','_Tally','_Text','_Throttle',
+ '_Transport','_Triggerlevel','_Unix','_WebDevOnly','_WebMenu','_WebMsftHomePage',
+ '_WebVFPHomePage','_WebVfpOnlineSupport','_Windows','_Wizard','_Wrap','_scctext',
+ '_vfp','Additive','After','Again','Aindent','Alignright',
+ 'All','Alt','Alternate','And','Ansi','Any',
+ 'Aplabout','App','Array','As','Asc','Ascending',
+ 'Ascii','At','Attributes','Automatic','Autosave','Avg',
+ 'Bar','Before','Bell','Between','Bitmap','Blank',
+ 'Blink','Blocksize','Border','Bottom','Brstatus','Bucket',
+ 'Buffers','By','Candidate','Carry','Cascade','Catalog',
+ 'Cdx','Center','Century','Cga','Character','Check',
+ 'Classlib','Clock','Cnt','Codepage','Collate','Color',
+ 'Com1','Com2','Command','Compact','Compatible','Compress',
+ 'Confirm','Connection','Connections','Connstring','Console','Copies',
+ 'Cpcompile','Cpdialog','Csv','Currency','Cycle','Databases',
+ 'Datasource','Date','Db4','Dbc','Dbf','Dbmemo3',
+ 'Debug','Decimals','Defaultsource','Deletetables','Delimited','Delimiters',
+ 'Descending','Design','Development','Device','Dif','Disabled',
+ 'Distinct','Dlls','Dohistory','Dos','Dosmem','Double',
+ 'Driver','Duplex','Echo','Editwork','Ega25','Ega43',
+ 'Ems','Ems64','Encrypt','Encryption','Environment','Escape',
+ 'Events','Exact','Except','Exe','Exists','Expression',
+ 'Extended','F','Fdow','Fetch','Field','Fields',
+ 'File','Files','Fill','Fixed','Float','Foldconst',
+ 'Font','Footer','Force','Foreign','Fox2x','Foxplus',
+ 'Free','Freeze','From','Fullpath','Fw2','Fweek',
+ 'Get','Gets','Global','Group','Grow','Halfheight',
+ 'Having','Heading','Headings','Helpfilter','History','Hmemory',
+ 'Hours','Id','In','Indexes','Information','Instruct',
+ 'Int','Integer','Intensity','Intersect','Into','Is',
+ 'Isometric','Key','Keycolumns','Keycomp','Keyset','Last',
+ 'Ledit','Level','Library','Like','Linked','Lock',
+ 'Logerrors','Long','Lpartition','Mac','Macdesktop','Machelp',
+ 'Mackey','Macros','Mark','Master','Max','Maxmem',
+ 'Mdi','Memlimit','Memory','Memos','Memowidth','Memvar',
+ 'Menus','Messages','Middle','Min','Minimize','Minus',
+ 'Mod','Modal','Module','Mono43','Movers','Multilocks',
+ 'Mvarsiz','Mvcount','N','Near','Negotiate','Noalias',
+ 'Noappend','Noclear','Noclose','Noconsole','Nocptrans','Nodata',
+ 'Nodebug','Nodelete','Nodup','Noedit','Noeject','Noenvironment',
+ 'Nofloat','Nofollow','Nogrow','Noinit','Nolgrid','Nolink',
+ 'Nolock','Nomargin','Nomdi','Nomenu','Nominimize','Nomodify'
+ ),
+ 3 => array('Nomouse','None','Nooptimize','Nooverwrite','Noprojecthook','Noprompt',
+ 'Noread','Norefresh','Norequery','Norgrid','Norm','Normal',
+ 'Nosave','Noshadow','Noshow','Nospace','Not','Notab',
+ 'Notify','Noupdate','Novalidate','Noverify','Nowait','Nowindow',
+ 'Nowrap','Nozoom','Npv','Null','Number','Objects',
+ 'Odometer','Of','Off','Oleobjects','Only','Optimize',
+ 'Or','Orientation','Output','Outshow','Overlay','Overwrite',
+ 'Pad','Palette','Paperlength','Papersize','Paperwidth','Password',
+ 'Path','Pattern','Pause','Pdox','Pdsetup','Pen',
+ 'Pfs','Pixels','Plain','Popups','Precision','Preference',
+ 'Preview','Primary','Printer','Printquality','Procedures','Production',
+ 'Program','Progwork','Project','Prompt','Query','Random',
+ 'Range','Readborder','Readerror','Record','Recover','Redit',
+ 'Reference','References','Relative','Remote','Reprocess','Resource',
+ 'Rest','Restrict','Rgb','Right','Row','Rowset',
+ 'Rpd','Runtime','Safety','Same','Sample','Say',
+ 'Scale','Scheme','Scoreboard','Screen','Sdf','Seconds',
+ 'Selection','Shadows','Shared','Sheet','Shell','Shift',
+ 'Shutdown','Single','Some','Sortwork','Space','Sql',
+ 'Standalone','Status','Std','Step','Sticky','String',
+ 'Structure','Subclass','Summary','Sylk','Sysformats','Sysmenus',
+ 'System','T','Tab','Tables','Talk','Tedit',
+ 'Textmerge','Time','Timeout','Titles','Tmpfiles','To',
+ 'Topic','Transaction','Trap','Trbetween','Trigger','Ttoption',
+ 'Typeahead','Udfparms','Union','Unique','Userid','Users',
+ 'Values','Var','Verb','Vga25','Vga50','Views',
+ 'Volume','Where','Windows','Wk1','Wk3','Wks',
+ 'Workarea','Wp','Wr1','Wrap','Wrk','Xcmdfile',
+ 'Xl5','Xl8','Xls','Y','Yresolution','Zoom',
+ 'Activate','ActivateCell','Add','AddColumn','AddItem','AddListItem',
+ 'AddObject','AddProperty','AddToSCC','AfterBuild','AfterCloseTables','AfterDock',
+ 'AfterRowColChange','BeforeBuild','BeforeDock','BeforeOpenTables','BeforeRowColChange','Box',
+ 'Build','CheckIn','CheckOut','Circle','Clear','ClearData',
+ 'Cleanup','Click','CloneObject','CloseEditor','CloseTables','Cls',
+ 'CommandTargetExec','CommandTargetQueryStas','ContainerRelease','DataToClip','DblClick','Deactivate',
+ 'Delete','DeleteColumn','Deleted','Destroy','DoCmd','Dock',
+ 'DoScroll','DoVerb','DownClick','Drag','DragDrop','DragOver',
+ 'DropDown','Draw','EnterFocus','Error','ErrorMessage','Eval',
+ 'ExitFocus','FormatChange','GetData','GetFormat','GetLatestVersion','GoBack',
+ 'GotFocus','GoForward','GridHitTest','Hide','HideDoc','IndexToItemId',
+ 'Init','InteractiveChange','Item','ItemIdToIndex','KeyPress','Line',
+ 'Load','LostFocus','Message','MiddleClick','MouseDown','MouseMove',
+ 'MouseUp','MouseWheel','Move','Moved','NavigateTo','Newobject',
+ 'OLECompleteDrag','OLEDrag','OLEDragDrop','OLEDragOver','OLEGiveFeedback','OLESetData',
+ 'OLEStartDrag','OpenEditor','OpenTables','Paint','Point','Print',
+ 'ProgrammaticChange','PSet','QueryAddFile','QueryModifyFile','QueryRemoveFile','QueryRunFile',
+ 'QueryUnload','RangeHigh','RangeLow','ReadActivate','ReadExpression','ReadDeactivate',
+ 'ReadMethod','ReadShow','ReadValid','ReadWhen','Refresh','Release',
+ 'RemoveFromSCC','RemoveItem','RemoveListItem','RemoveObject','Requery','RequestData',
+ 'Reset','ResetToDefault','Resize','RightClick','SaveAs','SaveAsClass',
+ 'Scrolled','SetAll','SetData','SetFocus','SetFormat','SetMain',
+ 'SetVar','SetViewPort','ShowDoc','ShowWhatsThis','TextHeight','TextWidth',
+ 'Timer','UIEnable','UnDock','UndoCheckOut','Unload','UpClick',
+ 'Valid','WhatsThisMode','When','WriteExpression','WriteMethod','ZOrder',
+ 'ATGetColors','ATListColors','Accelerate','ActiveColumn','ActiveControl','ActiveForm',
+ 'ActiveObjectId','ActivePage','ActiveProject','ActiveRow','AddLineFeeds','Alias',
+ 'Alignment','AllowAddNew','AllowHeaderSizing','AllowResize','AllowRowSizing','AllowTabs',
+ 'AlwaysOnTop','Application','AutoActivate','AutoCenter','AutoCloseTables','AutoIncrement',
+ 'AutoOpenTables','AutoRelease','AutoSize','AutoVerbMenu','AutoYield','AvailNum',
+ 'BackColor','BackStyle','BaseClass','BorderColor','BorderStyle','BorderWidth',
+ 'Bound','BoundColumn','BoundTo','BrowseAlignment','BrowseCellMarg','BrowseDestWidth',
+ 'BufferMode','BufferModeOverride','BuildDateTime','ButtonCount','ButtonIndex','Buttons',
+ 'CLSID','CanAccelerate','CanGetFocus','CanLoseFocus','Cancel','Caption',
+ 'ChildAlias','ChildOrder','Class','ClassLibrary','ClipControls','ClipRect',
+ 'Closable','ColorScheme','ColorSource','ColumnCount','ColumnHeaders','ColumnLines',
+ 'ColumnOrder','ColumnWidths','Columns','Comment','ContinuousScroll','ControlBox',
+ 'ControlCount','ControlIndex','ControlSource','Controls','CurrentControl','CurrentX',
+ 'CurrentY','CursorSource','Curvature','DataSession','DataSessionId','DataSourceObj',
+ 'DataType','Database','DateFormat','DateMark','DefButton','DefButtonOrig',
+ 'DefHeight','DefLeft','DefTop','DefWidth','Default','DefaultFilePath',
+ 'DefineWindows','DeleteMark','Desktop','Dirty','DisabledBackColor','DisabledByEOF',
+ 'DisabledForeColor','DisabledItemBackColor','DisabledItemForeColor','DisabledPicture','DispPageHeight','DispPageWidth',
+ 'DisplayCount','DisplayValue','DoCreate','DockPosition','Docked','DocumentFile',
+ 'DownPicture','DragIcon','DragMode','DragState','DrawMode','DrawStyle',
+ 'DrawWidth','DynamicAlignment','DynamicBackColor','DynamicCurrentControl','DynamicFontBold','DynamicFontItalic',
+ 'DynamicFontName','DynamicFontOutline','DynamicFontShadow','DynamicFontSize','DynamicFontStrikethru','DynamicFontUnderline',
+ 'DynamicForeColor','EditFlags','Enabled','EnabledByReadLock','Encrypted','EnvLevel',
+ 'ErasePage','FileClass','FileClassLibrary','FillColor','FillStyle','Filter',
+ 'FirstElement','FontBold','FontItalic','FontName','FontOutline','FontShadow',
+ 'FontSize','FontStrikethru','FontUnderline','ForceFocus','ForeColor','FormCount',
+ 'FormIndex','FormPageCount','FormPageIndex','Format','Forms','FoxFont',
+ 'FullName','GoFirst','GoLast','GridLineColor','GridLineWidth','GridLines'
+ ),
+ 4 => array('HPROJ','HWnd','HalfHeightCaption','HasClip','HeaderGap','HeaderHeight',
+ 'Height','HelpContextID','HideSelection','Highlight','HomeDir','HostName',
+ 'HotKey','HscrollSmallChange','IMEMode','Icon','IgnoreInsert','InResize',
+ 'Increment','IncrementalSearch','InitialSelectedAlias','InputMask','Instancing','IntegralHeight',
+ 'Interval','ItemBackColor','ItemData','ItemForeColor','ItemIDData','ItemTips',
+ 'JustReadLocked','KeyPreview','KeyboardHighValue','KeyboardLowValue','LastModified','Left',
+ 'LeftColumn','LineSlant','LinkMaster','List','ListCount','ListIndex',
+ 'ListItem','ListItemId','LockDataSource','LockScreen','MDIForm','MainClass',
+ 'MainFile','Margin','MaxButton','MaxHeight','MaxLeft','MaxLength',
+ 'MaxTop','MaxWidth','MemoWindow','MinButton','MinHeight','MinWidth',
+ 'MouseIcon','MousePointer','Movable','MoverBars','MultiSelect','Name',
+ 'NapTime','NewIndex','NewItemId','NoDataOnLoad','NoDefine','NotifyContainer',
+ 'NullDisplay','NumberOfElements','OLEDragMode','OLEDragPicture','OLEDropEffects','OLEDropHasData',
+ 'OLEDropMode','OLERequestPendingTimeOut','OLEServerBusyRaiseError','OLEServerBusyTimeOut','OLETypeAllowed','OleClass',
+ 'OleClassId','OleControlContainer','OleIDispInValue','OleIDispOutValue','OleIDispatchIncoming','OleIDispatchOutgoing',
+ 'OnResize','OneToMany','OpenViews','OpenWindow','PageCount','PageHeight',
+ 'PageOrder','PageWidth','Pages','Panel','PanelLink','Parent',
+ 'ParentAlias','ParentClass','Partition','PasswordChar','Picture','ProcessID',
+ 'ProgID','ProjectHookClass','ProjectHookLibrary','Projects','ReadColors','ReadCycle',
+ 'ReadFiller','ReadLock','ReadMouse','ReadOnly','ReadSave','ReadSize',
+ 'ReadTimeout','RecordMark','RecordSource','RecordSourceType','Rect','RelationalExpr',
+ 'RelativeColumn','RelativeRow','ReleaseErase','ReleaseType','ReleaseWindows','Resizable',
+ 'RightToLeft','RowHeight','RowSource','RowSourceType','SCCProvider','SCCStatus',
+ 'SDIForm','ScaleMode','ScrollBars','SelLength','SelStart','SelText',
+ 'SelectOnEntry','Selected','SelectedBackColor','SelectedForeColor','SelectedID','SelectedItemBackColor',
+ 'SelectedItemForeColor','SelfEdit','ServerClass','ServerClassLibrary','ServerHelpFile','ServerName',
+ 'ServerProject','ShowTips','ShowWindow','Sizable','Size<height>','Size<maxlength>',
+ 'Size<width>','Skip','SkipForm','Sorted','SourceType','Sparse',
+ 'SpecialEffect','SpinnerHighValue','SpinnerLowValue','SplitBar','StartMode','StatusBarText',
+ 'Stretch','StrictDateEntry','Style','SystemRefCount','TabIndex','TabStop',
+ 'TabStretch','TabStyle','Tabhit','Tabs','Tag','TerminateRead',
+ 'ThreadID','TitleBar','ToolTipText','Top','TopIndex','TopItemId',
+ 'TypeLibCLSID','TypeLibDesc','TypeLibName','UnlockDataSource','Value','ValueDirty',
+ 'VersionComments','VersionCompany','VersionCopyright','VersionDescription','VersionNumber','VersionProduct',
+ 'VersionTrademarks','View','ViewPortHeight','ViewPortLeft','ViewPortTop','ViewPortWidth',
+ 'Visible','VscrollSmallChange','WasActive','WasOpen','WhatsThisButton','WhatsThisHelp',
+ 'WhatsThisHelpID','Width','WindowList','WindowNTIList','WindowState','WindowType',
+ 'WordWrap','ZOrderSet','ActiveDoc','Checkbox','Column','ComboBox',
+ 'CommandButton','CommandGroup','Container','Control','Cursor','Custom',
+ 'DataEnvironment','EditBox','Empty','FontClass','Form','Formset',
+ 'General','Grid','Header','HyperLink','Image','Label',
+ 'ListBox','Memo','OleBaseControl','OleBoundControl','OleClassIDispOut','OleControl',
+ 'OptionButton','OptionGroup','Page','PageFrame','ProjectHook','RectClass',
+ 'Relation','Session','Shape','Spinner','TextBox' ,'Toolbar'
+ ),
+ ),
+ 'SYMBOLS' => array("!", "@", "$", "%", "(", ")", "-", "+", "=", "/", "{", "}", "[", "]", ":", ";", ",", " ", ".", "*", "&"),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => true,
+ 1 => false,
+ 2 => false,
+ 3 => false,
+ 4 => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ 1 => 'color: blue;',
+ 2 => 'color: blue;',
+ 3 => 'color: blue;',
+ 4 => 'color: blue;'
+ ),
+ 'COMMENTS' => array(
+ 1 => 'color: green; font-style: italic;',
+ 2 => 'color: green font-style: italic;',
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: blue;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ 1 => 'color: #006600;'
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: blue;'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'SCRIPT' => array(
+ )
+ ),
+ 'OOLANG' => true,
+ 'OBJECT_SPLITTERS' => array(
+ 1 => '.'
+ ),
+ 'REGEXPS' => array(
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_NEVER,
+ 'SCRIPT_DELIMITERS' => array(
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ )
+);
+
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/inc/geshi/xml.php b/plugins/dokuwiki/inc/geshi/xml.php
new file mode 100644
index 0000000..eb42d6a
--- /dev/null
+++ b/plugins/dokuwiki/inc/geshi/xml.php
@@ -0,0 +1,147 @@
+<?php
+/*************************************************************************************
+ * xml.php
+ * -------
+ * Author: Nigel McNie (oracle.shinoda@gmail.com)
+ * Copyright: (c) 2004 Nigel McNie (http://qbnz.com/highlighter/)
+ * Release Version: 1.0.7.15
+ * CVS Revision Version: $Revision: 1.18.2.6 $
+ * Date Started: 2004/09/01
+ * Last Modified: $Date: 2006/10/11 12:24:50 $
+ *
+ * XML language file for GeSHi. Based on the idea/file by Christian Weiske
+ *
+ * CHANGES
+ * -------
+ * 2005/12/28 (1.0.2)
+ * - Removed escape character for strings
+ * 2004/11/27 (1.0.1)
+ * - Added support for multiple object splitters
+ * 2004/10/27 (1.0.0)
+ * - First Release
+ *
+ * TODO (updated 2004/11/27)
+ * -------------------------
+ * * Check regexps work and correctly highlight XML stuff and nothing else
+ *
+ *************************************************************************************
+ *
+ * This file is part of GeSHi.
+ *
+ * GeSHi 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.
+ *
+ * GeSHi 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 GeSHi; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ ************************************************************************************/
+
+$language_data = array (
+ 'LANG_NAME' => 'XML',
+ 'COMMENT_SINGLE' => array(),
+ 'COMMENT_MULTI' => array('<!--' => '-->'),
+ 'CASE_KEYWORDS' => GESHI_CAPS_NO_CHANGE,
+ 'QUOTEMARKS' => array("'", '"'),
+ 'ESCAPE_CHAR' => '',
+ 'KEYWORDS' => array(
+ ),
+ 'SYMBOLS' => array(
+ ),
+ 'CASE_SENSITIVE' => array(
+ GESHI_COMMENTS => false,
+ ),
+ 'STYLES' => array(
+ 'KEYWORDS' => array(
+ ),
+ 'COMMENTS' => array(
+ 'MULTI' => 'color: #808080; font-style: italic;'
+ ),
+ 'ESCAPE_CHAR' => array(
+ 0 => 'color: #000099; font-weight: bold;'
+ ),
+ 'BRACKETS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'STRINGS' => array(
+ 0 => 'color: #ff0000;'
+ ),
+ 'NUMBERS' => array(
+ 0 => 'color: #cc66cc;'
+ ),
+ 'METHODS' => array(
+ ),
+ 'SYMBOLS' => array(
+ 0 => 'color: #66cc66;'
+ ),
+ 'SCRIPT' => array(
+ 0 => 'color: #00bbdd;',
+ 1 => 'color: #ddbb00;',
+ 2 => 'color: #339933;',
+ 3 => 'color: #009900;'
+ ),
+ 'REGEXPS' => array(
+ 0 => 'color: #000066;',
+ 1 => 'font-weight: bold; color: black;',
+ 2 => 'font-weight: bold; color: black;',
+ )
+ ),
+ 'URLS' => array(
+ ),
+ 'OOLANG' => false,
+ 'OBJECT_SPLITTERS' => array(
+ ),
+ 'REGEXPS' => array(
+ 0 => array(
+ GESHI_SEARCH => '([a-z\-:]+)(=)',
+ GESHI_REPLACE => '\\1',
+ GESHI_MODIFIERS => 'i',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => '\\2'
+ ),
+ 1 => array(
+ GESHI_SEARCH => '(&lt;[/?|(\?xml)]?[a-z0-9_\-:]*(\??&gt;)?)',
+ GESHI_REPLACE => '\\1',
+ GESHI_MODIFIERS => 'i',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ ),
+ 2 => array(
+ GESHI_SEARCH => '(([/|\?])?&gt;)',
+ GESHI_REPLACE => '\\1',
+ GESHI_MODIFIERS => 'i',
+ GESHI_BEFORE => '',
+ GESHI_AFTER => ''
+ )
+ ),
+ 'STRICT_MODE_APPLIES' => GESHI_ALWAYS,
+ 'SCRIPT_DELIMITERS' => array(
+ 0 => array(
+ '<!DOCTYPE' => '>'
+ ),
+ 1 => array(
+ '&' => ';'
+ ),
+ 2 => array(
+ '<![CDATA[' => ']]>'
+ ),
+ 3 => array(
+ '<' => '>'
+ )
+ ),
+ 'HIGHLIGHT_STRICT_BLOCK' => array(
+ 0 => false,
+ 1 => false,
+ 2 => false,
+ 3 => true
+ )
+);
+
+?>
diff --git a/plugins/dokuwiki/inc/html.php b/plugins/dokuwiki/inc/html.php
new file mode 100644
index 0000000..795e357
--- /dev/null
+++ b/plugins/dokuwiki/inc/html.php
@@ -0,0 +1,1290 @@
+<?php
+/**
+ * HTML output functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+
+ require_once(DOKU_INC.'inc/parserutils.php');
+
+/**
+ * Convenience function to quickly build a wikilink
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_wikilink($id,$name=NULL,$search=''){
+ static $xhtml_renderer = NULL;
+ if(is_null($xhtml_renderer)){
+ require_once(DOKU_INC.'inc/parser/xhtml.php');
+ $xhtml_renderer = new Doku_Renderer_xhtml();
+ }
+
+ return $xhtml_renderer->internallink($id,$name,$search,true);
+}
+
+/**
+ * Helps building long attribute lists
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_attbuild($attributes){
+ $ret = '';
+ foreach ( $attributes as $key => $value ) {
+ $ret .= $key.'="'.formtext($value).'" ';
+ }
+ return trim($ret);
+}
+
+/**
+ * The loginform
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_login(){
+ global $lang;
+ global $conf;
+ global $ID;
+ global $auth;
+
+ print p_locale_xhtml('login');
+ ?>
+ <div class="centeralign">
+ <form action="<?php echo script()?>" accept-charset="<?php echo $lang['encoding']?>"
+ method="post" id="dw__login">
+ <fieldset>
+ <legend><?php echo $lang['btn_login']?></legend>
+ <input type="hidden" name="id" value="<?php echo $ID?>" />
+ <input type="hidden" name="do" value="login" />
+ <label class="block">
+ <span><?php echo $lang['user']?></span>
+ <input type="text" name="u" value="<?php echo formText($_REQUEST['u'])?>"
+ class="edit" id="focus__this" />
+ </label><br />
+ <label class="block">
+ <span><?php echo $lang['pass']?></span>
+ <input type="password" name="p" class="edit" />
+ </label><br />
+ <label for="remember__me" class="simple">
+ <input type="checkbox" name="r" id="remember__me" value="1" />
+ <span><?php echo $lang['remember']?></span>
+ </label>
+ <input type="submit" value="<?php echo $lang['btn_login']?>" class="button" />
+ </fieldset>
+ </form>
+ <?php
+ if($auth->canDo('addUser') && actionOK('register')){
+ print '<p>';
+ print $lang['reghere'];
+ print ': <a href="'.wl($ID,'do=register').'" rel="nofollow" class="wikilink1">'.$lang['register'].'</a>';
+ print '</p>';
+ }
+
+ if ($auth->canDo('modPass') && actionOK('resendpwd')) {
+ print '<p>';
+ print $lang['pwdforget'];
+ print ': <a href="'.wl($ID,'do=resendpwd').'" rel="nofollow" class="wikilink1">'.$lang['btn_resendpwd'].'</a>';
+ print '</p>';
+ }
+ ?>
+ </div>
+ <?php
+/*
+ FIXME provide new hook
+ if(@file_exists('includes/login.txt')){
+ print io_cacheParse('includes/login.txt');
+ }
+*/
+}
+
+/**
+ * prints a section editing button
+ * used as a callback in html_secedit
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_secedit_button($matches){
+ global $ID;
+ global $INFO;
+
+ $section = $matches[2];
+ $name = $matches[1];
+
+ $secedit = '';
+ $secedit .= '<div class="secedit">';
+ $secedit .= html_btn('secedit',$ID,'',
+ array('do' => 'edit',
+ 'lines' => "$section",
+ 'rev' => $INFO['lastmod']),
+ 'post', $name);
+ $secedit .= '</div>';
+ return $secedit;
+}
+
+/**
+ * inserts section edit buttons if wanted or removes the markers
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_secedit($text,$show=true){
+ global $INFO;
+
+ if($INFO['writable'] && $show && !$INFO['rev']){
+ $text = preg_replace_callback('#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#',
+ 'html_secedit_button', $text);
+ }else{
+ $text = preg_replace('#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#','',$text);
+ }
+
+ return $text;
+}
+
+/**
+ * Just the back to top button (in its own form)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_topbtn(){
+ global $lang;
+
+ $ret = '';
+ $ret = '<a class="nolink" href="#dokuwiki__top"><input type="button" class="button" value="'.$lang['btn_top'].'" onclick="window.scrollTo(0, 0)" title="'.$lang['btn_top'].'" /></a>';
+
+ return $ret;
+}
+
+/**
+ * Just the back to media window button in its own form
+ *
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ */
+function html_backtomedia_button($params,$akey=''){
+ global $conf;
+ global $lang;
+
+ $ret = '<form class="button" method="get" action="'.DOKU_BASE.'lib/exe/mediamanager.php"><div class="no">';
+
+ reset($params);
+ while (list($key, $val) = each($params)) {
+ $ret .= '<input type="hidden" name="'.$key.'" ';
+ $ret .= 'value="'.htmlspecialchars($val).'" />';
+ }
+
+ $ret .= '<input type="submit" value="'.htmlspecialchars($lang['btn_backtomedia']).'" class="button" ';
+ $tit = htmlspecialchars($lang['btn_backtomedia']);
+ if($akey){
+ $tit .= ' [ALT+'.strtoupper($akey).']';
+ $ret .= 'accesskey="'.$akey.'" ';
+ }
+ $ret .= 'title="'.$tit.'" ';
+ $ret .= '/>';
+ $ret .= '</div></form>';
+
+ return $ret;
+}
+
+/**
+ * Displays a button (using its own form)
+ * If tooltip exists, the access key tooltip is replaced.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_btn($name,$id,$akey,$params,$method='get',$tooltip=''){
+ global $conf;
+ global $lang;
+
+ $label = $lang['btn_'.$name];
+
+ $ret = '';
+ $tip = '';
+
+ //filter id (without urlencoding)
+ $id = idfilter($id,false);
+
+ //make nice URLs even for buttons
+ if($conf['userewrite'] == 2){
+ $script = DOKU_BASE.DOKU_SCRIPT.'/'.$id;
+ }elseif($conf['userewrite']){
+ $script = DOKU_BASE.$id;
+ }else{
+ $script = DOKU_BASE.DOKU_SCRIPT;
+ $params['id'] = $id;
+ }
+
+ $ret .= '<form class="button" method="'.$method.'" action="'.$script.'"><div class="no">';
+
+ if(is_array($params)){
+ reset($params);
+ while (list($key, $val) = each($params)) {
+ $ret .= '<input type="hidden" name="'.$key.'" ';
+ $ret .= 'value="'.htmlspecialchars($val).'" />';
+ }
+ }
+
+ if ($tooltip!='') {
+ $tip = htmlspecialchars($tooltip);
+ }else{
+ $tip = htmlspecialchars($label);
+ }
+
+ $ret .= '<input type="submit" value="'.htmlspecialchars($label).'" class="button" ';
+ if($akey){
+ $tip .= ' [ALT+'.strtoupper($akey).']';
+ $ret .= 'accesskey="'.$akey.'" ';
+ }
+ $ret .= 'title="'.$tip.'" ';
+ $ret .= '/>';
+ $ret .= '</div></form>';
+
+ return $ret;
+}
+
+/**
+ * show a wiki page
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_show($txt=''){
+ global $ID;
+ global $REV;
+ global $HIGH;
+ //disable section editing for old revisions or in preview
+ if($txt || $REV){
+ $secedit = false;
+ }else{
+ $secedit = true;
+ }
+
+ if ($txt){
+ //PreviewHeader
+ print '<br id="scroll__here" />';
+ print p_locale_xhtml('preview');
+ print '<div class="preview">';
+ print html_secedit(p_render('xhtml',p_get_instructions($txt),$info),$secedit);
+ print '<div class="clearer"></div>';
+ print '</div>';
+
+ }else{
+ if ($REV) print p_locale_xhtml('showrev');
+ $html = p_wiki_xhtml($ID,$REV,true);
+ $html = html_secedit($html,$secedit);
+ print html_hilight($html,$HIGH);
+ }
+}
+
+/**
+ * ask the user about how to handle an exisiting draft
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_draft(){
+ global $INFO;
+ global $ID;
+ global $lang;
+ global $conf;
+ $draft = unserialize(io_readFile($INFO['draft'],false));
+ $text = cleanText(con($draft['prefix'],$draft['text'],$draft['suffix'],true));
+
+ echo p_locale_xhtml('draft');
+ ?>
+ <form id="dw__editform" method="post" action="<?php echo script()?>"
+ accept-charset="<?php echo $lang['encoding']?>"><div class="no">
+ <input type="hidden" name="id" value="<?php echo $ID?>" />
+ <input type="hidden" name="date" value="<?php echo $draft['date']?>" /></div>
+ <textarea name="wikitext" id="wiki__text" readonly="readonly" cols="80" rows="10" class="edit"><?php echo "\n".formText($text)?></textarea>
+
+ <div id="draft__status"><?php echo $lang['draftdate'].' '.date($conf['dformat'],filemtime($INFO['draft']))?></div>
+
+ <input class="button" type="submit" name="do[recover]" value="<?php echo $lang['btn_recover']?>" tabindex="1" />
+ <input class="button" type="submit" name="do[draftdel]" value="<?php echo $lang['btn_draftdel']?>" tabindex="2" />
+ <input class="button" type="submit" name="do[show]" value="<?php echo $lang['btn_cancel']?>" tabindex="3" />
+ </form>
+ <?php
+}
+
+
+/**
+ * Run a search and display the result
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_search(){
+ require_once(DOKU_INC.'inc/search.php');
+ require_once(DOKU_INC.'inc/fulltext.php');
+ global $conf;
+ global $QUERY;
+ global $ID;
+ global $lang;
+
+ print p_locale_xhtml('searchpage');
+ flush();
+
+ //check if search is restricted to namespace
+ if(preg_match('/([^@]*)@([^@]*)/',$QUERY,$match)) {
+ $id = cleanID($match[1]);
+ if(empty($id)) {
+ print '<div class="nothing">'.$lang['nothingfound'].'</div>';
+ flush();
+ return;
+ }
+ } else {
+ $id = cleanID($QUERY);
+ }
+
+ //show progressbar
+ print '<div class="centeralign" id="dw__loading">';
+ print '<script type="text/javascript" charset="utf-8">';
+ print 'showLoadBar();';
+ print '</script>';
+ print "<br /></div>\n";
+ flush();
+
+ //do quick pagesearch
+ $data = array();
+
+ $data = ft_pageLookup($id);
+ if(count($data)){
+ sort($data);
+ print '<div class="search_quickresult">';
+ print '<h3>'.$lang['quickhits'].':</h3>';
+ print '<ul class="search_quickhits">';
+ foreach($data as $id){
+ print '<li> ';
+ print html_wikilink(':'.$id,$conf['useheading']?NULL:$id);
+ print '</li> ';
+ }
+ print '</ul> ';
+ //clear float (see http://www.complexspiral.com/publications/containing-floats/)
+ print '<div class="clearer">&nbsp;</div>';
+ print '</div>';
+ }
+ flush();
+
+ //do fulltext search
+ $data = ft_pageSearch($QUERY,$poswords);
+ if(count($data)){
+ $num = 1;
+ foreach($data as $id => $cnt){
+ print '<div class="search_result">';
+ print html_wikilink(':'.$id,$conf['useheading']?NULL:$id,$poswords);
+ print ': <span class="search_cnt">'.$cnt.' '.$lang['hits'].'</span><br />';
+ if($num < 15){ // create snippets for the first number of matches only #FIXME add to conf ?
+ print '<div class="search_snippet">'.ft_snippet($id,$poswords).'</div>';
+ }
+ print '</div>';
+ flush();
+ $num++;
+ }
+ }else{
+ print '<div class="nothing">'.$lang['nothingfound'].'</div>';
+ }
+
+ //hide progressbar
+ print '<script type="text/javascript" charset="utf-8">';
+ print 'hideLoadBar("dw__loading");';
+ print '</script>';
+ flush();
+}
+
+/**
+ * Display error on locked pages
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_locked(){
+ global $ID;
+ global $conf;
+ global $lang;
+ global $INFO;
+
+ $locktime = filemtime(wikiLockFN($ID));
+ $expire = @date($conf['dformat'], $locktime + $conf['locktime'] );
+ $min = round(($conf['locktime'] - (time() - $locktime) )/60);
+
+ print p_locale_xhtml('locked');
+ print '<ul>';
+ print '<li><div class="li"><strong>'.$lang['lockedby'].':</strong> '.$INFO['locked'].'</li>';
+ print '<li><div class="li"><strong>'.$lang['lockexpire'].':</strong> '.$expire.' ('.$min.' min)</div></li>';
+ print '</ul>';
+}
+
+/**
+ * list old revisions
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function html_revisions($first=0){
+ global $ID;
+ global $INFO;
+ global $conf;
+ global $lang;
+ /* we need to get one additionally log entry to be able to
+ * decide if this is the last page or is there another one.
+ * see html_recent()
+ */
+ $revisions = getRevisions($ID, $first, $conf['recent']+1);
+ if(count($revisions)==0 && $first!=0){
+ $first=0;
+ $revisions = getRevisions($ID, $first, $conf['recent']+1);;
+ }
+ $hasNext = false;
+ if (count($revisions)>$conf['recent']) {
+ $hasNext = true;
+ array_pop($revisions); // remove extra log entry
+ }
+
+ $date = @date($conf['dformat'],$INFO['lastmod']);
+
+ print p_locale_xhtml('revisions');
+ print '<ul>';
+ if($INFO['exists'] && $first==0){
+ print (isset($INFO['meta']) && isset($INFO['meta']['last_change']) && $INFO['meta']['last_change']['type']==='e') ? '<li class="minor">' : '<li>';
+ print '<div class="li">';
+
+ print $date;
+
+ print ' <img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" /> ';
+
+ print '<a class="wikilink1" href="'.wl($ID).'">'.$ID.'</a> ';
+
+ print ' &ndash; ';
+ print $INFO['sum'];
+ print ' <span class="user">';
+ print $INFO['editor'];
+ print '</span> ';
+
+ print '('.$lang['current'].')';
+ print '</div>';
+ print '</li>';
+ }
+
+ foreach($revisions as $rev){
+ $date = date($conf['dformat'],$rev);
+ $info = getRevisionInfo($ID,$rev,true);
+
+ print ($info['type']==='e') ? '<li class="minor">' : '<li>';
+ print '<div class="li">';
+ print $date;
+
+ if(@file_exists(wikiFN($ID,$rev))){
+ print ' <a href="'.wl($ID,"rev=$rev,do=diff").'">';
+ $p = array();
+ $p['src'] = DOKU_BASE.'lib/images/diff.png';
+ $p['width'] = 15;
+ $p['height'] = 11;
+ $p['title'] = $lang['diff'];
+ $p['alt'] = $lang['diff'];
+ $att = buildAttributes($p);
+ print "<img $att />";
+ print '</a> ';
+
+ print '<a class="wikilink1" href="'.wl($ID,"rev=$rev").'">'.$ID.'</a>';
+ }else{
+ print ' <img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" /> ';
+ print $ID;
+ }
+
+ print ' &ndash; ';
+ print htmlspecialchars($info['sum']);
+ print ' <span class="user">';
+ if($info['user']){
+ print $info['user'];
+ }else{
+ print $info['ip'];
+ }
+ print '</span>';
+
+ print '</div>';
+ print '</li>';
+ }
+ print '</ul>';
+
+ print '<div class="pagenav">';
+ $last = $first + $conf['recent'];
+ if ($first > 0) {
+ $first -= $conf['recent'];
+ if ($first < 0) $first = 0;
+ print '<div class="pagenav-prev">';
+ print html_btn('newer',$ID,"p",array('do' => 'revisions', 'first' => $first));
+ print '</div>';
+ }
+ if ($hasNext) {
+ print '<div class="pagenav-next">';
+ print html_btn('older',$ID,"n",array('do' => 'revisions', 'first' => $last));
+ print '</div>';
+ }
+ print '</div>';
+
+}
+
+/**
+ * display recent changes
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function html_recent($first=0){
+ global $conf;
+ global $lang;
+ global $ID;
+ /* we need to get one additionally log entry to be able to
+ * decide if this is the last page or is there another one.
+ * This is the cheapest solution to get this information.
+ */
+ $recents = getRecents($first,$conf['recent'] + 1,getNS($ID));
+ if(count($recents) == 0 && $first != 0){
+ $first=0;
+ $recents = getRecents($first,$conf['recent'] + 1,getNS($ID));
+ }
+ $hasNext = false;
+ if (count($recents)>$conf['recent']) {
+ $hasNext = true;
+ array_pop($recents); // remove extra log entry
+ }
+
+ print p_locale_xhtml('recent');
+ print '<ul>';
+
+ foreach($recents as $recent){
+ $date = date($conf['dformat'],$recent['date']);
+ print ($recent['type']==='e') ? '<li class="minor">' : '<li>';
+ print '<div class="li">';
+
+ print $date.' ';
+
+ print '<a href="'.wl($recent['id'],"do=diff").'">';
+ $p = array();
+ $p['src'] = DOKU_BASE.'lib/images/diff.png';
+ $p['width'] = 15;
+ $p['height'] = 11;
+ $p['title'] = $lang['diff'];
+ $p['alt'] = $lang['diff'];
+ $att = buildAttributes($p);
+ print "<img $att />";
+ print '</a> ';
+
+ print '<a href="'.wl($recent['id'],"do=revisions").'">';
+ $p = array();
+ $p['src'] = DOKU_BASE.'lib/images/history.png';
+ $p['width'] = 12;
+ $p['height'] = 14;
+ $p['title'] = $lang['btn_revs'];
+ $p['alt'] = $lang['btn_revs'];
+ $att = buildAttributes($p);
+ print "<img $att />";
+ print '</a> ';
+
+ print html_wikilink(':'.$recent['id'],$conf['useheading']?NULL:$recent['id']);
+ print ' &ndash; '.htmlspecialchars($recent['sum']);
+
+ print ' <span class="user">';
+ if($recent['user']){
+ print $recent['user'];
+ }else{
+ print $recent['ip'];
+ }
+ print '</span>';
+
+ print '</div>';
+ print '</li>';
+ }
+ print '</ul>';
+
+ print '<div class="pagenav">';
+ $last = $first + $conf['recent'];
+ if ($first > 0) {
+ $first -= $conf['recent'];
+ if ($first < 0) $first = 0;
+ print '<div class="pagenav-prev">';
+ print html_btn('newer','',"p",array('do' => 'recent', 'first' => $first));
+ print '</div>';
+ }
+ if ($hasNext) {
+ print '<div class="pagenav-next">';
+ print html_btn('older','',"n",array('do' => 'recent', 'first' => $last));
+ print '</div>';
+ }
+ print '</div>';
+}
+
+/**
+ * Display page index
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_index($ns){
+ require_once(DOKU_INC.'inc/search.php');
+ global $conf;
+ global $ID;
+ $dir = $conf['datadir'];
+ $ns = cleanID($ns);
+ #fixme use appropriate function
+ if(empty($ns)){
+ $ns = dirname(str_replace(':','/',$ID));
+ if($ns == '.') $ns ='';
+ }
+ $ns = utf8_encodeFN(str_replace(':','/',$ns));
+
+ print p_locale_xhtml('index');
+
+ $data = array();
+ search($data,$conf['datadir'],'search_index',array('ns' => $ns));
+ print html_buildlist($data,'idx','html_list_index','html_li_index');
+}
+
+/**
+ * Index item formatter
+ *
+ * User function for html_buildlist()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_list_index($item){
+ global $ID;
+ $ret = '';
+ $base = ':'.$item['id'];
+ $base = substr($base,strrpos($base,':')+1);
+ if($item['type']=='d'){
+ $ret .= '<a href="'.wl($ID,'idx='.$item['id']).'" class="idx_dir">';
+ $ret .= $base;
+ $ret .= '</a>';
+ }else{
+ $ret .= html_wikilink(':'.$item['id']);
+ }
+ return $ret;
+}
+
+/**
+ * Index List item
+ *
+ * This user function is used in html_build_lidt to build the
+ * <li> tags for namespaces when displaying the page index
+ * it gives different classes to opened or closed "folders"
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_li_index($item){
+ if($item['type'] == "f"){
+ return '<li class="level'.$item['level'].'">';
+ }elseif($item['open']){
+ return '<li class="open">';
+ }else{
+ return '<li class="closed">';
+ }
+}
+
+/**
+ * Default List item
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_li_default($item){
+ return '<li class="level'.$item['level'].'">';
+}
+
+/**
+ * Build an unordered list
+ *
+ * Build an unordered list from the given $data array
+ * Each item in the array has to have a 'level' property
+ * the item itself gets printed by the given $func user
+ * function. The second and optional function is used to
+ * print the <li> tag. Both user function need to accept
+ * a single item.
+ *
+ * Both user functions can be given as array to point to
+ * a member of an object.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_buildlist($data,$class,$func,$lifunc='html_li_default'){
+ $level = 0;
+ $opens = 0;
+ $ret = '';
+
+ foreach ($data as $item){
+
+ if( $item['level'] > $level ){
+ //open new list
+ for($i=0; $i<($item['level'] - $level); $i++){
+ if ($i) $ret .= "<li class=\"clear\">\n";
+ $ret .= "\n<ul class=\"$class\">\n";
+ }
+ }elseif( $item['level'] < $level ){
+ //close last item
+ $ret .= "</li>\n";
+ for ($i=0; $i<($level - $item['level']); $i++){
+ //close higher lists
+ $ret .= "</ul>\n</li>\n";
+ }
+ }else{
+ //close last item
+ $ret .= "</li>\n";
+ }
+
+ //remember current level
+ $level = $item['level'];
+
+ //print item
+ if(is_array($lifunc)){
+ $ret .= $lifunc[0]->$lifunc[1]($item); //user object method
+ }else{
+ $ret .= $lifunc($item); //user function
+ }
+ $ret .= '<div class="li">';
+ if(is_array($func)){
+ $ret .= $func[0]->$func[1]($item); //user object method
+ }else{
+ $ret .= $func($item); //user function
+ }
+ $ret .= '</div>';
+ }
+
+ //close remaining items and lists
+ for ($i=0; $i < $level; $i++){
+ $ret .= "</li></ul>\n";
+ }
+
+ return $ret;
+}
+
+/**
+ * display backlinks
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_backlinks(){
+ require_once(DOKU_INC.'inc/fulltext.php');
+ global $ID;
+ global $conf;
+
+ print p_locale_xhtml('backlinks');
+
+ $data = ft_backlinks($ID);
+
+ print '<ul class="idx">';
+ foreach($data as $blink){
+ print '<li><div class="li">';
+ print html_wikilink(':'.$blink,$conf['useheading']?NULL:$blink);
+ print '</div></li>';
+ }
+ print '</ul>';
+}
+
+/**
+ * show diff
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_diff($text='',$intro=true){
+ require_once(DOKU_INC.'inc/DifferenceEngine.php');
+ global $ID;
+ global $REV;
+ global $lang;
+ global $conf;
+
+ if($text){
+ $df = new Diff(explode("\n",htmlspecialchars(rawWiki($ID,''))),
+ explode("\n",htmlspecialchars(cleanText($text))));
+ $left = '<a class="wikilink1" href="'.wl($ID).'">'.
+ $ID.' '.date($conf['dformat'],@filemtime(wikiFN($ID))).'</a>'.
+ $lang['current'];
+ $right = $lang['yours'];
+ }else{
+ if($REV){
+ $r = $REV;
+ }else{
+ //use last revision if none given
+ $revs = getRevisions($ID, 0, 1);
+ $r = $revs[0];
+ }
+
+ if($r){
+ $df = new Diff(explode("\n",htmlspecialchars(rawWiki($ID,$r))),
+ explode("\n",htmlspecialchars(rawWiki($ID,''))));
+ $left = '<a class="wikilink1" href="'.wl($ID,"rev=$r").'">'.
+ $ID.' '.date($conf['dformat'],$r).'</a>';
+ }else{
+ $df = new Diff(array(''),
+ explode("\n",htmlspecialchars(rawWiki($ID,''))));
+ $left = '<a class="wikilink1" href="'.wl($ID).'">'.
+ $ID.'</a>';
+ }
+ $right = '<a class="wikilink1" href="'.wl($ID).'">'.
+ $ID.' '.date($conf['dformat'],@filemtime(wikiFN($ID))).'</a> '.
+ $lang['current'];
+ }
+ $tdf = new TableDiffFormatter();
+ if($intro) print p_locale_xhtml('diff');
+ ?>
+ <table class="diff">
+ <tr>
+ <th colspan="2">
+ <?php echo $left?>
+ </th>
+ <th colspan="2">
+ <?php echo $right?>
+ </th>
+ </tr>
+ <?php echo $tdf->format($df)?>
+ </table>
+ <?php
+}
+
+/**
+ * show warning on conflict detection
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_conflict($text,$summary){
+ global $ID;
+ global $lang;
+
+ print p_locale_xhtml('conflict');
+ ?>
+ <form id="dw__editform" method="post" action="<?php echo script()?>" accept-charset="<?php echo $lang['encoding']?>">
+ <div class="centeralign">
+ <input type="hidden" name="id" value="<?php echo $ID?>" />
+ <input type="hidden" name="wikitext" value="<?php echo formText($text)?>" />
+ <input type="hidden" name="summary" value="<?php echo formText($summary)?>" />
+
+ <input class="button" type="submit" name="do[save]" value="<?php echo $lang['btn_save']?>" accesskey="s" title="<?php echo $lang['btn_save']?> [ALT+S]" />
+ <input class="button" type="submit" name="do[cancel]" value="<?php echo $lang['btn_cancel']?>" />
+ </div>
+ </form>
+ <br /><br /><br /><br />
+ <?php
+}
+
+/**
+ * Prints the global message array
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_msgarea(){
+ global $MSG;
+
+ if(!isset($MSG)) return;
+
+ foreach($MSG as $msg){
+ print '<div class="'.$msg['lvl'].'">';
+ print $msg['msg'];
+ print '</div>';
+ }
+}
+
+/**
+ * Prints the registration form
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_register(){
+ global $lang;
+ global $conf;
+ global $ID;
+
+ print p_locale_xhtml('register');
+?>
+ <div class="centeralign">
+ <form id="dw__register" method="post" action="<?php echo wl($ID)?>" accept-charset="<?php echo $lang['encoding']?>">
+ <fieldset>
+ <input type="hidden" name="do" value="register" />
+ <input type="hidden" name="save" value="1" />
+
+ <legend><?php echo $lang['register']?></legend>
+ <label class="block">
+ <?php echo $lang['user']?>
+ <input type="text" name="login" class="edit" size="50" value="<?php echo formText($_POST['login'])?>" />
+ </label><br />
+
+ <?php
+ if (!$conf['autopasswd']) {
+ ?>
+ <label class="block">
+ <?php echo $lang['pass']?>
+ <input type="password" name="pass" class="edit" size="50" />
+ </label><br />
+ <label class="block">
+ <?php echo $lang['passchk']?>
+ <input type="password" name="passchk" class="edit" size="50" />
+ </label><br />
+ <?php
+ }
+ ?>
+
+ <label class="block">
+ <?php echo $lang['fullname']?>
+ <input type="text" name="fullname" class="edit" size="50" value="<?php echo formText($_POST['fullname'])?>" />
+ </label><br />
+ <label class="block">
+ <?php echo $lang['email']?>
+ <input type="text" name="email" class="edit" size="50" value="<?php echo formText($_POST['email'])?>" />
+ </label><br />
+ <input type="submit" class="button" value="<?php echo $lang['register']?>" />
+ </fieldset>
+ </form>
+ </div>
+<?php
+}
+
+/**
+ * Print the update profile form
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_updateprofile(){
+ global $lang;
+ global $conf;
+ global $ID;
+ global $INFO;
+ global $auth;
+
+ print p_locale_xhtml('updateprofile');
+
+ if (empty($_POST['fullname'])) $_POST['fullname'] = $INFO['userinfo']['name'];
+ if (empty($_POST['email'])) $_POST['email'] = $INFO['userinfo']['mail'];
+?>
+ <div class="centeralign">
+ <form id="dw__register" method="post" action="<?php echo wl($ID)?>" accept-charset="<?php echo $lang['encoding']?>">
+ <fieldset style="width: 80%;">
+ <input type="hidden" name="do" value="profile" />
+ <input type="hidden" name="save" value="1" />
+
+ <legend><?php echo $lang['profile']?></legend>
+ <label class="block">
+ <?php echo $lang['user']?>
+ <input type="text" name="fullname" disabled="disabled" class="edit" size="50" value="<?php echo formText($_SERVER['REMOTE_USER'])?>" />
+ </label><br />
+ <label class="block">
+ <?php echo $lang['fullname']?>
+ <input type="text" name="fullname" <?php if(!$auth->canDo('modName')) echo 'disabled="disabled"'?> class="edit" size="50" value="<?php echo formText($_POST['fullname'])?>" />
+ </label><br />
+ <label class="block">
+ <?php echo $lang['email']?>
+ <input type="text" name="email" <?php if(!$auth->canDo('modName')) echo 'disabled="disabled"'?> class="edit" size="50" value="<?php echo formText($_POST['email'])?>" />
+ </label><br /><br />
+
+ <?php if($auth->canDo('modPass')) { ?>
+ <label class="block">
+ <?php echo $lang['newpass']?>
+ <input type="password" name="newpass" class="edit" size="50" />
+ </label><br />
+ <label class="block">
+ <?php echo $lang['passchk']?>
+ <input type="password" name="passchk" class="edit" size="50" />
+ </label><br />
+ <?php } ?>
+
+ <?php if ($conf['profileconfirm']) { ?>
+ <br />
+ <label class="block">
+ <?php echo $lang['oldpass']?>
+ <input type="password" name="oldpass" class="edit" size="50" />
+ </label><br />
+ <?php } ?>
+
+ <input type="submit" class="button" value="<?php echo $lang['btn_save']?>" />
+ <input type="reset" class="button" value="<?php echo $lang['btn_reset']?>" />
+ </fieldset>
+ </form>
+ </div>
+<?php
+}
+
+/**
+ * This displays the edit form (lots of logic included)
+ *
+ * @fixme this is a huge lump of code and should be modularized
+ * @triggers HTML_PAGE_FROMTEMPLATE
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_edit($text=null,$include='edit'){ //FIXME: include needed?
+ global $ID;
+ global $REV;
+ global $DATE;
+ global $RANGE;
+ global $PRE;
+ global $SUF;
+ global $INFO;
+ global $SUM;
+ global $lang;
+ global $conf;
+
+ //set summary default
+ if(!$SUM){
+ if($REV){
+ $SUM = $lang['restored'];
+ }elseif(!$INFO['exists']){
+ $SUM = $lang['created'];
+ }
+ }
+
+ //no text? Load it!
+ if(!isset($text)){
+ $pr = false; //no preview mode
+ if($INFO['exists']){
+ if($RANGE){
+ list($PRE,$text,$SUF) = rawWikiSlices($RANGE,$ID,$REV);
+ }else{
+ $text = rawWiki($ID,$REV);
+ }
+ }else{
+ //try to load a pagetemplate
+ $data = array($ID);
+ $text = trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true);
+ }
+ }else{
+ $pr = true; //preview mode
+ }
+
+ $wr = $INFO['writable'];
+ if($wr){
+ if ($REV) print p_locale_xhtml('editrev');
+ print p_locale_xhtml($include);
+ $ro=false;
+ }else{
+ // check pseudo action 'source'
+ if(!actionOK('source')){
+ msg('Command disabled: source',-1);
+ return;
+ }
+ print p_locale_xhtml('read');
+ $ro='readonly="readonly"';
+ }
+ if(!$DATE) $DATE = $INFO['lastmod'];
+
+
+?>
+ <div style="width:99%;">
+
+ <div class="toolbar">
+ <div id="draft__status"><?php if(!empty($INFO['draft'])) echo $lang['draftdate'].' '.date($conf['dformat']);?></div>
+ <div id="tool__bar"><?php if(!$ro){?><a href="<?php echo DOKU_BASE?>lib/exe/mediamanager.php?ns=<?php echo $INFO['namespace']?>"
+ target="_blank"><?php echo $lang['mediaselect'] ?></a><?php }?></div>
+
+ <?php if($wr){?>
+ <script type="text/javascript" charset="utf-8">
+ <?php /* sets changed to true when previewed */?>
+ textChanged = <?php ($pr) ? print 'true' : print 'false' ?>;
+ </script>
+ <span id="spell__action"></span>
+ <div id="spell__suggest"></div>
+ <?php } ?>
+ </div>
+ <div id="spell__result"></div>
+
+
+ <form id="dw__editform" method="post" action="<?php echo script()?>" accept-charset="<?php echo $lang['encoding']?>"><div class="no">
+ <input type="hidden" name="id" value="<?php echo $ID?>" />
+ <input type="hidden" name="rev" value="<?php echo $REV?>" />
+ <input type="hidden" name="date" value="<?php echo $DATE?>" />
+ <input type="hidden" name="prefix" value="<?php echo formText($PRE)?>" />
+ <input type="hidden" name="suffix" value="<?php echo formText($SUF)?>" />
+ </div>
+
+ <textarea name="wikitext" id="wiki__text" <?php echo $ro?> cols="80" rows="10" class="edit" tabindex="1"><?php echo "\n".formText($text)?></textarea>
+
+ <div id="wiki__editbar">
+ <div id="size__ctl"></div>
+ <?php if($wr){?>
+ <div class="editButtons">
+ <input class="button" id="edbtn__save" type="submit" name="do[save]" value="<?php echo $lang['btn_save']?>" accesskey="s" title="<?php echo $lang['btn_save']?> [ALT+S]" tabindex="4" />
+ <input class="button" id="edbtn__preview" type="submit" name="do[preview]" value="<?php echo $lang['btn_preview']?>" accesskey="p" title="<?php echo $lang['btn_preview']?> [ALT+P]" tabindex="5" />
+ <input class="button" type="submit" name="do[draftdel]" value="<?php echo $lang['btn_cancel']?>" tabindex="6" />
+ </div>
+ <?php } ?>
+ <?php if($wr){ ?>
+ <div class="summary">
+ <label for="edit__summary" class="nowrap"><?php echo $lang['summary']?>:</label>
+ <input type="text" class="edit" name="summary" id="edit__summary" size="50" value="<?php echo formText($SUM)?>" tabindex="2" />
+ <?php html_minoredit()?>
+ </div>
+ <?php }?>
+ </div>
+ </form>
+ </div>
+<?php
+}
+
+/**
+ * Adds a checkbox for minor edits for logged in users
+ *
+ * @author Andrea Gohr <andi@splitbrain.org>
+ */
+function html_minoredit(){
+ global $conf;
+ global $lang;
+ // minor edits are for logged in users only
+ if(!$conf['useacl'] || !$_SERVER['REMOTE_USER']){
+ return;
+ }
+
+ $p = array();
+ $p['name'] = 'minor';
+ $p['type'] = 'checkbox';
+ $p['id'] = 'minoredit';
+ $p['tabindex'] = 3;
+ $p['value'] = '1';
+ if(!empty($_REQUEST['minor'])) $p['checked']='checked';
+ $att = buildAttributes($p);
+
+ print '<span class="nowrap">';
+ print "<input $att />";
+ print '<label for="minoredit">';
+ print $lang['minoredit'];
+ print '</label>';
+ print '</span>';
+}
+
+/**
+ * prints some debug info
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function html_debug(){
+ global $conf;
+ global $lang;
+ global $auth;
+ global $INFO;
+
+ //remove sensitive data
+ $cnf = $conf;
+ $cnf['auth']='***';
+ $cnf['notify']='***';
+ $cnf['ftp']='***';
+ $nfo = $INFO;
+ $nfo['userinfo'] = '***';
+ $ses = $_SESSION;
+ $ses[$conf['title']]['auth'] = '***';
+
+ print '<html><body>';
+
+ print '<p>When reporting bugs please send all the following ';
+ print 'output as a mail to andi@splitbrain.org ';
+ print 'The best way to do this is to save this page in your browser</p>';
+
+ print '<b>$INFO:</b><pre>';
+ print_r($nfo);
+ print '</pre>';
+
+ print '<b>$_SERVER:</b><pre>';
+ print_r($_SERVER);
+ print '</pre>';
+
+ print '<b>$conf:</b><pre>';
+ print_r($cnf);
+ print '</pre>';
+
+ print '<b>DOKU_BASE:</b><pre>';
+ print DOKU_BASE;
+ print '</pre>';
+
+ print '<b>abs DOKU_BASE:</b><pre>';
+ print DOKU_URL;
+ print '</pre>';
+
+ print '<b>rel DOKU_BASE:</b><pre>';
+ print dirname($_SERVER['PHP_SELF']).'/';
+ print '</pre>';
+
+ print '<b>PHP Version:</b><pre>';
+ print phpversion();
+ print '</pre>';
+
+ print '<b>locale:</b><pre>';
+ print setlocale(LC_ALL,0);
+ print '</pre>';
+
+ print '<b>encoding:</b><pre>';
+ print $lang['encoding'];
+ print '</pre>';
+
+ if($auth){
+ print '<b>Auth backend capabilities:</b><pre>';
+ print_r($auth->cando);
+ print '</pre>';
+ }
+
+ print '<b>$_SESSION:</b><pre>';
+ print_r($ses);
+ print '</pre>';
+
+ print '<b>Environment:</b><pre>';
+ print_r($_ENV);
+ print '</pre>';
+
+ print '<b>PHP settings:</b><pre>';
+ $inis = ini_get_all();
+ print_r($inis);
+ print '</pre>';
+
+ print '</body></html>';
+}
+
+function html_admin(){
+ global $ID;
+ global $lang;
+ global $conf;
+
+ print p_locale_xhtml('admin');
+
+ // build menu of admin functions from the plugins that handle them
+ $pluginlist = plugin_list('admin');
+ $menu = array();
+ foreach ($pluginlist as $p) {
+ if($obj =& plugin_load('admin',$p) === NULL) continue;
+ $menu[] = array('plugin' => $p,
+ 'prompt' => $obj->getMenuText($conf['lang']),
+ 'sort' => $obj->getMenuSort()
+ );
+ }
+
+ usort($menu, 'p_sort_modes');
+
+ // output the menu
+ ptln('<ul>');
+
+ foreach ($menu as $item) {
+ if (!$item['prompt']) continue;
+ ptln(' <li><div class="li"><a href="'.wl($ID, 'do=admin&amp;page='.$item['plugin']).'">'.$item['prompt'].'</a></div></li>');
+ }
+
+ ptln('</ul>');
+}
+
+/**
+ * Form to request a new password for an existing account
+ *
+ * @author Benoit Chesneau <benoit@bchesneau.info>
+ */
+function html_resendpwd() {
+ global $lang;
+ global $conf;
+ global $ID;
+
+ print p_locale_xhtml('resendpwd');
+?>
+ <div class="centeralign">
+ <form id="dw__resendpwd" action="<?php echo wl($ID)?>" accept-charset="<?php echo $lang['encoding']?>" method="post">
+ <fieldset>
+ <br />
+ <legend><?php echo $lang['resendpwd']?></legend>
+ <input type="hidden" name="do" value="resendpwd" />
+ <input type="hidden" name="save" value="1" />
+ <label class="block">
+ <span><?php echo $lang['user']?></span>
+ <input type="text" name="login" value="<?php echo formText($_POST['login'])?>" class="edit" /><br /><br />
+ </label><br />
+ <input type="submit" value="<?php echo $lang['btn_resendpwd']?>" class="button" />
+ </fieldset>
+ </form>
+ </div>
+<?php
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/infoutils.php b/plugins/dokuwiki/inc/infoutils.php
new file mode 100644
index 0000000..780a468
--- /dev/null
+++ b/plugins/dokuwiki/inc/infoutils.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Information and debugging functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+if(!defined('DOKU_MESSAGEURL')) define('DOKU_MESSAGEURL','http://update.dokuwiki.org/check/');
+require_once(DOKU_INC.'inc/HTTPClient.php');
+
+/**
+ * Check for new messages from upstream
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function checkUpdateMessages(){
+ global $conf;
+ global $INFO;
+ if(!$conf['updatecheck']) return;
+ if($conf['useacl'] && $INFO['perm'] < AUTH_ADMIN) return;
+
+ $cf = $conf['cachedir'].'/messages.txt';
+ $lm = @filemtime($cf);
+
+ // check if new messages needs to be fetched
+ if($lm < time()-(60*60*24) || $lm < @filemtime(DOKU_CONF.'msg')){
+ $num = @file(DOKU_CONF.'msg');
+ $num = is_array($num) ? (int) $num[0] : 0;
+ $http = new DokuHTTPClient();
+ $http->timeout = 8;
+ $data = $http->get(DOKU_MESSAGEURL.$num);
+ io_saveFile($cf,$data);
+ }else{
+ $data = io_readFile($cf);
+ }
+
+ // show messages through the usual message mechanism
+ $msgs = explode("\n%\n",$data);
+ foreach($msgs as $msg){
+ if($msg) msg($msg,2);
+ }
+}
+
+
+/**
+ * Return DokuWikis version
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getVersion(){
+ //import version string
+ if(@file_exists('VERSION')){
+ //official release
+ return 'Release '.trim(io_readfile(DOKU_INC.'/VERSION'));
+ }elseif(is_dir('_darcs')){
+ //darcs checkout - read last 2000 bytes of inventory
+ $sz = filesize('_darcs/inventory');
+ $seek = max(0,$sz-2000);
+ $fh = fopen('_darcs/inventory','rb');
+ fseek($fh,$seek);
+ $chunk = fread($fh,2000);
+ fclose($fh);
+ $inv = preg_grep('#\*\*\d{14}[\]$]#',explode("\n",$chunk));
+ $cur = array_pop($inv);
+ preg_match('#\*\*(\d{4})(\d{2})(\d{2})#',$cur,$matches);
+ return 'Darcs '.$matches[1].'-'.$matches[2].'-'.$matches[3];
+ }else{
+ return 'snapshot?';
+ }
+}
+
+/**
+ * Run a few sanity checks
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function check(){
+ global $conf;
+ global $INFO;
+
+ msg('DokuWiki version: '.getVersion(),1);
+
+ if(version_compare(phpversion(),'4.3.3','<')){
+ msg('Your PHP version is too old ('.phpversion().' vs. 4.3.3+ recommended)',-1);
+ }elseif(version_compare(phpversion(),'4.3.10','<')){
+ msg('Consider upgrading PHP to 4.3.10 or higher for security reasons (your version: '.phpversion().')',0);
+ }else{
+ msg('PHP version '.phpversion(),1);
+ }
+
+ if(is_writable($conf['changelog'])){
+ msg('Changelog is writable',1);
+ }else{
+ if (@file_exists($conf['changelog'])) {
+ msg('Changelog is not writable',-1);
+ }
+ }
+
+ if (isset($conf['changelog_old']) && @file_exists($conf['changelog_old'])) {
+ msg('Old changelog exists', 0);
+ }
+
+ if (@file_exists($conf['changelog'].'_failed')) {
+ msg('Importing old changelog failed', -1);
+ } else if (@file_exists($conf['changelog'].'_importing')) {
+ msg('Importing old changelog now.', 0);
+ } else if (@file_exists($conf['changelog'].'_import_ok')) {
+ msg('Old changelog imported', 1);
+ if (!plugin_isdisabled('importoldchangelog')) {
+ msg('Importoldchangelog plugin not disabled after import', -1);
+ }
+ }
+
+ if(is_writable($conf['datadir'])){
+ msg('Datadir is writable',1);
+ }else{
+ msg('Datadir is not writable',-1);
+ }
+
+ if(is_writable($conf['olddir'])){
+ msg('Attic is writable',1);
+ }else{
+ msg('Attic is not writable',-1);
+ }
+
+ if(is_writable($conf['mediadir'])){
+ msg('Mediadir is writable',1);
+ }else{
+ msg('Mediadir is not writable',-1);
+ }
+
+ if(is_writable($conf['cachedir'])){
+ msg('Cachedir is writable',1);
+ }else{
+ msg('Cachedir is not writable',-1);
+ }
+
+ if(is_writable($conf['lockdir'])){
+ msg('Lockdir is writable',1);
+ }else{
+ msg('Lockdir is not writable',-1);
+ }
+
+ if(is_writable(DOKU_CONF.'users.auth.php')){
+ msg('conf/users.auth.php is writable',1);
+ }else{
+ msg('conf/users.auth.php is not writable',0);
+ }
+
+ if(function_exists('mb_strpos')){
+ if(defined('UTF8_NOMBSTRING')){
+ msg('mb_string extension is available but will not be used',0);
+ }else{
+ msg('mb_string extension is available and will be used',1);
+ }
+ }else{
+ msg('mb_string extension not available - PHP only replacements will be used',0);
+ }
+
+ if($conf['allowdebug']){
+ msg('Debugging support is enabled. If you don\'t need it you should set $conf[\'allowdebug\'] = 0',-1);
+ }else{
+ msg('Debugging support is disabled',1);
+ }
+
+ msg('Your current permission for this page is '.$INFO['perm'],0);
+
+ if(is_writable($INFO['filepath'])){
+ msg('The current page is writable by the webserver',0);
+ }else{
+ msg('The current page is not writable by the webserver',0);
+ }
+
+ if($INFO['writable']){
+ msg('The current page is writable by you',0);
+ }else{
+ msg('The current page is not writable by you',0);
+ }
+}
+
+/**
+ * print a message
+ *
+ * If HTTP headers were not sent yet the message is added
+ * to the global message array else it's printed directly
+ * using html_msgarea()
+ *
+ *
+ * Levels can be:
+ *
+ * -1 error
+ * 0 info
+ * 1 success
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see html_msgarea
+ */
+function msg($message,$lvl=0,$line='',$file=''){
+ global $MSG;
+ $errors[-1] = 'error';
+ $errors[0] = 'info';
+ $errors[1] = 'success';
+ $errors[2] = 'notify';
+
+ if($line || $file) $message.=' ['.basename($file).':'.$line.']';
+
+ if(!headers_sent()){
+ if(!isset($MSG)) $MSG = array();
+ $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
+ }else{
+ $MSG = array();
+ $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
+ if(function_exists('html_msgarea')){
+ html_msgarea();
+ }else{
+ print "ERROR($lvl) $message";
+ }
+ }
+}
+
+/**
+ * print debug messages
+ *
+ * little function to print the content of a var
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function dbg($msg,$hidden=false){
+ (!$hidden) ? print '<pre class="dbg">' : print "<!--\n";
+ print_r($msg);
+ (!$hidden) ? print '</pre>' : print "\n-->";
+}
+
+/**
+ * Print info to a log file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function dbglog($msg){
+ global $conf;
+ $file = $conf['cachedir'].'/debug.log';
+ $fh = fopen($file,'a');
+ if($fh){
+ fwrite($fh,date('H:i:s ').$_SERVER['REMOTE_ADDR'].': '.$msg."\n");
+ fclose($fh);
+ }
+}
+
diff --git a/plugins/dokuwiki/inc/init.php b/plugins/dokuwiki/inc/init.php
new file mode 100644
index 0000000..272f759
--- /dev/null
+++ b/plugins/dokuwiki/inc/init.php
@@ -0,0 +1,367 @@
+<?php
+/**
+ * Initialize some defaults needed for DokuWiki
+ */
+
+ // start timing Dokuwiki execution
+ function delta_time($start=0) {
+ list($usec, $sec) = explode(" ", microtime());
+ return ((float)$usec+(float)$sec)-((float)$start);
+ }
+ define('DOKU_START_TIME', delta_time());
+
+ // define the include path
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+
+ // define config path (packagers may want to change this to /etc/dokuwiki/)
+ if(!defined('DOKU_CONF')) define('DOKU_CONF',DOKU_INC.'conf/');
+
+ // check for error reporting override or set error reporting to sane values
+ if (!defined('DOKU_E_LEVEL') && @file_exists(DOKU_CONF.'report_e_all')) {
+ define('DOKU_E_LEVEL', E_ALL);
+ }
+ if (!defined('DOKU_E_LEVEL')) { error_reporting(E_ALL ^ E_NOTICE); }
+ else { error_reporting(DOKU_E_LEVEL); }
+
+ // init memory caches
+ global $cache_revinfo; $cache_revinfo = array();
+ global $cache_wikifn; $cache_wikifn = array();
+ global $cache_cleanid; $cache_cleanid = array();
+ global $cache_authname; $cache_authname = array();
+
+ //prepare config array()
+ global $conf;
+ if (!defined('DOKU_UNITTEST')) {
+ $conf = array();
+
+ // load the config file(s)
+ require_once(DOKU_CONF.'dokuwiki.php');
+ if(@file_exists(DOKU_CONF.'local.php')){
+ require_once(DOKU_CONF.'local.php');
+ }
+ }
+
+ //prepare language array
+ global $lang;
+ $lang = array();
+
+ // define baseURL
+ if(!defined('DOKU_BASE')) define('DOKU_BASE',getBaseURL());
+ if(!defined('DOKU_URL')) define('DOKU_URL',getBaseURL(true));
+
+ // define cookie and session id
+ if (!defined('DOKU_COOKIE')) define('DOKU_COOKIE', 'DW'.md5(DOKU_URL));
+
+ // define Plugin dir
+ if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+
+ // define main script
+ if(!defined('DOKU_SCRIPT')) define('DOKU_SCRIPT','doku.php');
+
+ // define Template baseURL
+ if(!defined('DOKU_TPL')) define('DOKU_TPL',
+ DOKU_BASE.'lib/tpl/'.$conf['template'].'/');
+
+ // define real Template directory
+ if(!defined('DOKU_TPLINC')) define('DOKU_TPLINC',
+ DOKU_INC.'lib/tpl/'.$conf['template'].'/');
+
+ // make session rewrites XHTML compliant
+ @ini_set('arg_separator.output', '&amp;');
+
+ // enable gzip compression
+ if ($conf['gzip_output'] &&
+ !defined('DOKU_DISABLE_GZIP_OUTPUT') &&
+ function_exists('ob_gzhandler') &&
+ preg_match('/gzip|deflate/', $_SERVER['HTTP_ACCEPT_ENCODING'])) {
+ ob_start('ob_gzhandler');
+ }
+
+ // init session
+ if (!headers_sent() && !defined('NOSESSION')){
+ session_name("DokuWiki");
+ session_start();
+ }
+
+ // kill magic quotes
+ if (get_magic_quotes_gpc() && !defined('MAGIC_QUOTES_STRIPPED')) {
+ if (!empty($_GET)) remove_magic_quotes($_GET);
+ if (!empty($_POST)) remove_magic_quotes($_POST);
+ if (!empty($_COOKIE)) remove_magic_quotes($_COOKIE);
+ if (!empty($_REQUEST)) remove_magic_quotes($_REQUEST);
+# if (!empty($_SESSION)) remove_magic_quotes($_SESSION); #FIXME needed ?
+ @ini_set('magic_quotes_gpc', 0);
+ define('MAGIC_QUOTES_STRIPPED',1);
+ }
+ @set_magic_quotes_runtime(0);
+ @ini_set('magic_quotes_sybase',0);
+
+ // disable gzip if not available
+ if($conf['compression'] == 'bz' && !function_exists('bzopen')){
+ $conf['compression'] = 'gz';
+ }
+ if($conf['compression'] == 'gz' && !function_exists('gzopen')){
+ $conf['compression'] = 0;
+ }
+
+ // precalculate file creation modes
+ init_creationmodes();
+
+ // automatic upgrade to script versions of certain files
+ scriptify(DOKU_CONF.'users.auth');
+ scriptify(DOKU_CONF.'acl.auth');
+
+
+/**
+ * Checks paths from config file
+ */
+function init_paths(){
+ global $conf;
+
+ $paths = array('datadir' => 'pages',
+ 'olddir' => 'attic',
+ 'mediadir' => 'media',
+ 'metadir' => 'meta',
+ 'cachedir' => 'cache',
+ 'lockdir' => 'locks');
+
+ foreach($paths as $c => $p){
+ if(empty($conf[$c])) $conf[$c] = $conf['savedir'].'/'.$p;
+ $conf[$c] = init_path($conf[$c]);
+ if(empty($conf[$c])) nice_die("The $c does not exist, isn't accessable or writable.
+ You should check your config and permission settings.
+ Or maybe you want to <a href=\"install.php\">run the
+ installer</a>?");
+ }
+
+ // path to old changelog only needed for upgrading
+ $conf['changelog_old'] = init_path((isset($conf['changelog']))?($conf['changelog']):($conf['savedir'].'/changes.log'));
+ if ($conf['changelog_old']=='') { unset($conf['changelog_old']); }
+ // hardcoded changelog because it is now a cache that lives in meta
+ $conf['changelog'] = $conf['metadir'].'/_dokuwiki.changes';
+}
+
+/**
+ * Checks the existance of certain files and creates them if missing.
+ */
+function init_files(){
+ global $conf;
+
+ $files = array( $conf['cachedir'].'/word.idx',
+ $conf['cachedir'].'/page.idx',
+ $conf['cachedir'].'/index.idx');
+
+ foreach($files as $file){
+ if(!@file_exists($file)){
+ $fh = @fopen($file,'a');
+ if($fh){
+ fclose($fh);
+ if($conf['fperm']) chmod($file, $conf['fperm']);
+ }else{
+ nice_die("$file is not writable. Check your permissions settings!");
+ }
+ }
+ }
+}
+
+/**
+ * Returns absolute path
+ *
+ * This tries the given path first, then checks in DOKU_INC.
+ * Check for accessability on directories as well.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function init_path($path){
+ // check existance
+ $p = realpath($path);
+ if(!@file_exists($p)){
+ $p = realpath(DOKU_INC.$path);
+ if(!@file_exists($p)){
+ return '';
+ }
+ }
+
+ // check writability
+ if(!@is_writable($p)){
+ return '';
+ }
+
+ // check accessability (execute bit) for directories
+ if(@is_dir($p) && !@file_exists("$p/.")){
+ return '';
+ }
+
+ return $p;
+}
+
+/**
+ * Sets the internal config values fperm and dperm which, when set,
+ * will be used to change the permission of a newly created dir or
+ * file with chmod. Considers the influence of the system's umask
+ * setting the values only if needed.
+ */
+function init_creationmodes(){
+ global $conf;
+
+ // Legacy support for old umask/dmask scheme
+ unset($conf['dmask']);
+ unset($conf['fmask']);
+ unset($conf['umask']);
+ unset($conf['fperm']);
+ unset($conf['dperm']);
+
+ // get system umask, fallback to 0 if none available
+ $umask = @umask();
+ if(!$umask) $umask = 0000;
+
+ // check what is set automatically by the system on file creation
+ // and set the fperm param if it's not what we want
+ $auto_fmode = 0666 & ~$umask;
+ if($auto_fmode != $conf['fmode']) $conf['fperm'] = $conf['fmode'];
+
+ // check what is set automatically by the system on file creation
+ // and set the dperm param if it's not what we want
+ $auto_dmode = $conf['dmode'] & ~$umask;
+ if($auto_dmode != $conf['dmode']) $conf['dperm'] = $conf['dmode'];
+}
+
+/**
+ * remove magic quotes recursivly
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function remove_magic_quotes(&$array) {
+ foreach (array_keys($array) as $key) {
+ if (is_array($array[$key])) {
+ remove_magic_quotes($array[$key]);
+ }else {
+ $array[$key] = stripslashes($array[$key]);
+ }
+ }
+}
+
+/**
+ * Returns the full absolute URL to the directory where
+ * DokuWiki is installed in (includes a trailing slash)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getBaseURL($abs=false){
+ global $conf;
+ //if canonical url enabled always return absolute
+ if($conf['canonical']) $abs = true;
+
+ if($conf['basedir']){
+ $dir = $conf['basedir'].'/';
+ }elseif(substr($_SERVER['SCRIPT_NAME'],-4) == '.php'){
+ $dir = dirname($_SERVER['SCRIPT_NAME']).'/';
+ }elseif(substr($_SERVER['PHP_SELF'],-4) == '.php'){
+ $dir = dirname($_SERVER['PHP_SELF']).'/';
+ }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){
+ $dir = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','',
+ $_SERVER['SCRIPT_FILENAME']);
+ $dir = dirname('/'.$dir).'/';
+ }else{
+ $dir = './'; //probably wrong
+ }
+
+ $dir = str_replace('\\','/',$dir); #bugfix for weird WIN behaviour
+ $dir = preg_replace('#//+#','/',$dir);
+
+ //handle script in lib/exe dir
+ $dir = preg_replace('!lib/exe/$!','',$dir);
+
+ //handle script in lib/plugins dir
+ $dir = preg_replace('!lib/plugins/.*$!','',$dir);
+
+ //finish here for relative URLs
+ if(!$abs) return $dir;
+
+ //use config option if available
+ if($conf['baseurl']) return $conf['baseurl'].$dir;
+
+ //split hostheader into host and port
+ list($host,$port) = explode(':',$_SERVER['HTTP_HOST']);
+ if(!$port) $port = $_SERVER['SERVER_PORT'];
+ if(!$port) $port = 80;
+
+ // see if HTTPS is enabled - apache leaves this empty when not available,
+ // IIS sets it to 'off', 'false' and 'disabled' are just guessing
+ if (preg_match('/^(|off|false|disabled)$/i',$_SERVER['HTTPS'])){
+ $proto = 'http://';
+ if ($port == '80') {
+ $port='';
+ }
+ }else{
+ $proto = 'https://';
+ if ($port == '443') {
+ $port='';
+ }
+ }
+
+ if($port) $port = ':'.$port;
+
+ return $proto.$host.$port.$dir;
+}
+
+/**
+ * Append a PHP extension to a given file and adds an exit call
+ *
+ * This is used to migrate some old configfiles. An added PHP extension
+ * ensures the contents are not shown to webusers even if .htaccess files
+ * do not work
+ *
+ * @author Jan Decaluwe <jan@jandecaluwe.com>
+ */
+function scriptify($file) {
+ // checks
+ if (!is_readable($file)) {
+ return;
+ }
+ $fn = $file.'.php';
+ if (@file_exists($fn)) {
+ return;
+ }
+ $fh = fopen($fn, 'w');
+ if (!$fh) {
+ nice_die($fn.' is not writable. Check your permission settings!');
+ }
+ // write php exit hack first
+ fwrite($fh, "# $fn\n");
+ fwrite($fh, '# <?php exit()?>'."\n");
+ fwrite($fh, "# Don't modify the lines above\n");
+ fwrite($fh, "#\n");
+ // copy existing lines
+ $lines = file($file);
+ foreach ($lines as $line){
+ fwrite($fh, $line);
+ }
+ fclose($fh);
+ //try to rename the old file
+ io_rename($file,"$file.old");
+}
+
+/**
+ * print a nice message even if no styles are loaded yet.
+ */
+function nice_die($msg){
+ echo<<<EOT
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+ <html>
+ <head><title>DokuWiki Setup Error</title></head>
+ <body style="font-family: Arial, sans-serif">
+ <div style="width:60%; margin: auto; background-color: #fcc;
+ border: 1px solid #faa; padding: 0.5em 1em;">
+ <h1 style="font-size: 120%">DokuWiki Setup Error</h1>
+ <p>$msg</p>
+ </div>
+ </body>
+ </html>
+EOT;
+ exit;
+}
+
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/io.php b/plugins/dokuwiki/inc/io.php
new file mode 100644
index 0000000..d941ef0
--- /dev/null
+++ b/plugins/dokuwiki/inc/io.php
@@ -0,0 +1,567 @@
+<?php
+/**
+ * File IO functions
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+ require_once(DOKU_INC.'inc/common.php');
+ require_once(DOKU_INC.'inc/HTTPClient.php');
+ require_once(DOKU_INC.'inc/events.php');
+ require_once(DOKU_INC.'inc/utf8.php');
+
+/**
+ * Removes empty directories
+ *
+ * Sends IO_NAMESPACE_DELETED events for 'pages' and 'media' namespaces.
+ * Event data:
+ * $data[0] ns: The colon separated namespace path minus the trailing page name.
+ * $data[1] ns_type: 'pages' or 'media' namespace tree.
+ *
+ * @todo use safemode hack
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function io_sweepNS($id,$basedir='datadir'){
+ global $conf;
+ $types = array ('datadir'=>'pages', 'mediadir'=>'media');
+ $ns_type = (isset($types[$basedir])?$types[$basedir]:false);
+
+ //scan all namespaces
+ while(($id = getNS($id)) !== false){
+ $dir = $conf[$basedir].'/'.utf8_encodeFN(str_replace(':','/',$id));
+
+ //try to delete dir else return
+ if(@rmdir($dir)) {
+ if ($ns_type!==false) {
+ $data = array($id, $ns_type);
+ trigger_event('IO_NAMESPACE_DELETED', $data);
+ }
+ } else { return; }
+ }
+}
+
+/**
+ * Used to read in a DokuWiki page from file, and send IO_WIKIPAGE_READ events.
+ *
+ * Generates the action event which delegates to io_readFile().
+ * Action plugins are allowed to modify the page content in transit.
+ * The file path should not be changed.
+ *
+ * Event data:
+ * $data[0] The raw arguments for io_readFile as an array.
+ * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns)
+ * $data[2] page_name: The wiki page name.
+ * $data[3] rev: The page revision, false for current wiki pages.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function io_readWikiPage($file, $id, $rev=false) {
+ if (empty($rev)) { $rev = false; }
+ $data = array(array($file, false), getNS($id), noNS($id), $rev);
+ return trigger_event('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false);
+}
+
+/**
+ * Callback adapter for io_readFile().
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function _io_readWikiPage_action($data) {
+ if (is_array($data) && is_array($data[0]) && count($data[0])===2) {
+ return call_user_func_array('io_readFile', $data[0]);
+ } else {
+ return ''; //callback error
+ }
+}
+
+/**
+ * Returns content of $file as cleaned string.
+ *
+ * Uses gzip if extension is .gz
+ *
+ * If you want to use the returned value in unserialize
+ * be sure to set $clean to false!
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function io_readFile($file,$clean=true){
+ $ret = '';
+ if(@file_exists($file)){
+ if(substr($file,-3) == '.gz'){
+ $ret = join('',gzfile($file));
+ }else if(substr($file,-4) == '.bz2'){
+ $ret = bzfile($file);
+ }else{
+ $ret = join('',file($file));
+ }
+ }
+ if($clean){
+ return cleanText($ret);
+ }else{
+ return $ret;
+ }
+}
+/**
+* Returns the content of a .bz2 compressed file as string
+* @author marcel senf <marcel@rucksackreinigung.de>
+*/
+
+function bzfile($file){
+ $bz = bzopen($file,"r");
+ while (!feof($bz)){
+ //8192 seems to be the maximum buffersize?
+ $str = $str . bzread($bz,8192);
+ }
+ bzclose($bz);
+ return $str;
+}
+
+
+/**
+ * Used to write out a DokuWiki page to file, and send IO_WIKIPAGE_WRITE events.
+ *
+ * This generates an action event and delegates to io_saveFile().
+ * Action plugins are allowed to modify the page content in transit.
+ * The file path should not be changed.
+ * (The append parameter is set to false.)
+ *
+ * Event data:
+ * $data[0] The raw arguments for io_saveFile as an array.
+ * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns)
+ * $data[2] page_name: The wiki page name.
+ * $data[3] rev: The page revision, false for current wiki pages.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function io_writeWikiPage($file, $content, $id, $rev=false) {
+ if (empty($rev)) { $rev = false; }
+ if ($rev===false) { io_createNamespace($id); } // create namespaces as needed
+ $data = array(array($file, $content, false), getNS($id), noNS($id), $rev);
+ return trigger_event('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false);
+}
+
+/**
+ * Callback adapter for io_saveFile().
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function _io_writeWikiPage_action($data) {
+ if (is_array($data) && is_array($data[0]) && count($data[0])===3) {
+ return call_user_func_array('io_saveFile', $data[0]);
+ } else {
+ return false; //callback error
+ }
+}
+
+/**
+ * Saves $content to $file.
+ *
+ * If the third parameter is set to true the given content
+ * will be appended.
+ *
+ * Uses gzip if extension is .gz
+ * and bz2 if extension is .bz2
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @return bool true on success
+ */
+function io_saveFile($file,$content,$append=false){
+ global $conf;
+ $mode = ($append) ? 'ab' : 'wb';
+
+ $fileexists = @file_exists($file);
+ io_makeFileDir($file);
+ io_lock($file);
+ if(substr($file,-3) == '.gz'){
+ $fh = @gzopen($file,$mode.'9');
+ if(!$fh){
+ msg("Writing $file failed",-1);
+ io_unlock($file);
+ return false;
+ }
+ gzwrite($fh, $content);
+ gzclose($fh);
+ }else if(substr($file,-4) == '.bz2'){
+ $fh = @bzopen($file,$mode);
+ if(!$fh){
+ msg("Writing $file failed", -1);
+ io_unlock($file);
+ return false;
+ }
+ bzwrite($fh, $content);
+ bzclose($fh);
+ }else{
+ $fh = @fopen($file,$mode);
+ if(!$fh){
+ msg("Writing $file failed",-1);
+ io_unlock($file);
+ return false;
+ }
+ fwrite($fh, $content);
+ fclose($fh);
+ }
+
+ if(!$fileexists and !empty($conf['fperm'])) chmod($file, $conf['fperm']);
+ io_unlock($file);
+ return true;
+}
+
+/**
+ * Delete exact linematch for $badline from $file.
+ *
+ * Be sure to include the trailing newline in $badline
+ *
+ * Uses gzip if extension is .gz
+ *
+ * 2005-10-14 : added regex option -- Christopher Smith <chris@jalakai.co.uk>
+ *
+ * @author Steven Danz <steven-danz@kc.rr.com>
+ * @return bool true on success
+ */
+function io_deleteFromFile($file,$badline,$regex=false){
+ if (!@file_exists($file)) return true;
+
+ io_lock($file);
+
+ // load into array
+ if(substr($file,-3) == '.gz'){
+ $lines = gzfile($file);
+ }else{
+ $lines = file($file);
+ }
+
+ // remove all matching lines
+ if ($regex) {
+ $lines = preg_grep($badline,$lines,PREG_GREP_INVERT);
+ } else {
+ $pos = array_search($badline,$lines); //return null or false if not found
+ while(is_int($pos)){
+ unset($lines[$pos]);
+ $pos = array_search($badline,$lines);
+ }
+ }
+
+ if(count($lines)){
+ $content = join('',$lines);
+ if(substr($file,-3) == '.gz'){
+ $fh = @gzopen($file,'wb9');
+ if(!$fh){
+ msg("Removing content from $file failed",-1);
+ io_unlock($file);
+ return false;
+ }
+ gzwrite($fh, $content);
+ gzclose($fh);
+ }else{
+ $fh = @fopen($file,'wb');
+ if(!$fh){
+ msg("Removing content from $file failed",-1);
+ io_unlock($file);
+ return false;
+ }
+ fwrite($fh, $content);
+ fclose($fh);
+ }
+ }else{
+ @unlink($file);
+ }
+
+ io_unlock($file);
+ return true;
+}
+
+/**
+ * Tries to lock a file
+ *
+ * Locking is only done for io_savefile and uses directories
+ * inside $conf['lockdir']
+ *
+ * It waits maximal 3 seconds for the lock, after this time
+ * the lock is assumed to be stale and the function goes on
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function io_lock($file){
+ global $conf;
+ // no locking if safemode hack
+ if($conf['safemodehack']) return;
+
+ $lockDir = $conf['lockdir'].'/'.md5($file);
+ @ignore_user_abort(1);
+
+ $timeStart = time();
+ do {
+ //waited longer than 3 seconds? -> stale lock
+ if ((time() - $timeStart) > 3) break;
+ $locked = @mkdir($lockDir, $conf['dmode']);
+ if($locked){
+ if(!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']);
+ break;
+ }
+ usleep(50);
+ } while ($locked === false);
+}
+
+/**
+ * Unlocks a file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function io_unlock($file){
+ global $conf;
+ // no locking if safemode hack
+ if($conf['safemodehack']) return;
+
+ $lockDir = $conf['lockdir'].'/'.md5($file);
+ @rmdir($lockDir);
+ @ignore_user_abort(0);
+}
+
+/**
+ * Create missing namespace directories and send the IO_NAMESPACE_CREATED events
+ * in the order of directory creation. (Parent directories first.)
+ *
+ * Event data:
+ * $data[0] ns: The colon separated namespace path minus the trailing page name.
+ * $data[1] ns_type: 'pages' or 'media' namespace tree.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function io_createNamespace($id, $ns_type='pages') {
+ // verify ns_type
+ $types = array('pages'=>'wikiFN', 'media'=>'mediaFN');
+ if (!isset($types[$ns_type])) {
+ trigger_error('Bad $ns_type parameter for io_createNamespace().');
+ return;
+ }
+ // make event list
+ $missing = array();
+ $ns_stack = explode(':', $id);
+ $ns = $id;
+ $tmp = dirname( $file = call_user_func($types[$ns_type], $ns) );
+ while (!@is_dir($tmp) && !(@file_exists($tmp) && !is_dir($tmp))) {
+ array_pop($ns_stack);
+ $ns = implode(':', $ns_stack);
+ if (strlen($ns)==0) { break; }
+ $missing[] = $ns;
+ $tmp = dirname(call_user_func($types[$ns_type], $ns));
+ }
+ // make directories
+ io_makeFileDir($file);
+ // send the events
+ $missing = array_reverse($missing); // inside out
+ foreach ($missing as $ns) {
+ $data = array($ns, $ns_type);
+ trigger_event('IO_NAMESPACE_CREATED', $data);
+ }
+}
+
+/**
+ * Create the directory needed for the given file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function io_makeFileDir($file){
+ global $conf;
+
+ $dir = dirname($file);
+ if(!@is_dir($dir)){
+ io_mkdir_p($dir) || msg("Creating directory $dir failed",-1);
+ }
+}
+
+/**
+ * Creates a directory hierachy.
+ *
+ * @link http://www.php.net/manual/en/function.mkdir.php
+ * @author <saint@corenova.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function io_mkdir_p($target){
+ global $conf;
+ if (@is_dir($target)||empty($target)) return 1; // best case check first
+ if (@file_exists($target) && !is_dir($target)) return 0;
+ //recursion
+ if (io_mkdir_p(substr($target,0,strrpos($target,'/')))){
+ if($conf['safemodehack']){
+ $dir = preg_replace('/^'.preg_quote(realpath($conf['ftp']['root']),'/').'/','', $target);
+ return io_mkdir_ftp($dir);
+ }else{
+ $ret = @mkdir($target,$conf['dmode']); // crawl back up & create dir tree
+ if($ret && $conf['dperm']) chmod($target, $conf['dperm']);
+ return $ret;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Creates a directory using FTP
+ *
+ * This is used when the safemode workaround is enabled
+ *
+ * @author <andi@splitbrain.org>
+ */
+function io_mkdir_ftp($dir){
+ global $conf;
+
+ if(!function_exists('ftp_connect')){
+ msg("FTP support not found - safemode workaround not usable",-1);
+ return false;
+ }
+
+ $conn = @ftp_connect($conf['ftp']['host'],$conf['ftp']['port'],10);
+ if(!$conn){
+ msg("FTP connection failed",-1);
+ return false;
+ }
+
+ if(!@ftp_login($conn, $conf['ftp']['user'], $conf['ftp']['pass'])){
+ msg("FTP login failed",-1);
+ return false;
+ }
+
+ //create directory
+ $ok = @ftp_mkdir($conn, $dir);
+ //set permissions
+ @ftp_site($conn,sprintf("CHMOD %04o %s",$conf['dmode'],$dir));
+
+ @ftp_close($conn);
+ return $ok;
+}
+
+/**
+ * downloads a file from the net and saves it
+ *
+ * if $useAttachment is false,
+ * - $file is the full filename to save the file, incl. path
+ * - if successful will return true, false otherwise
+
+ * if $useAttachment is true,
+ * - $file is the directory where the file should be saved
+ * - if successful will return the name used for the saved file, false otherwise
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+function io_download($url,$file,$useAttachment=false,$defaultName='',$maxSize=2097152){
+ global $conf;
+ $http = new DokuHTTPClient();
+ $http->max_bodysize = $maxSize;
+ $http->timeout = 25; //max. 25 sec
+
+ $data = $http->get($url);
+ if(!$data) return false;
+
+ if ($useAttachment) {
+ $name = '';
+ if (isset($http->resp_headers['content-disposition'])) {
+ $content_disposition = $http->resp_headers['content-disposition'];
+ $match=array();
+ if (is_string($content_disposition) &&
+ preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) {
+
+ $name = basename($match[1]);
+ }
+
+ }
+
+ if (!$name) {
+ if (!$defaultName) return false;
+ $name = $defaultName;
+ }
+
+ $file = $file.$name;
+ }
+
+ $fileexists = @file_exists($file);
+ $fp = @fopen($file,"w");
+ if(!$fp) return false;
+ fwrite($fp,$data);
+ fclose($fp);
+ if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
+ if ($useAttachment) return $name;
+ return true;
+}
+
+/**
+ * Windows compatible rename
+ *
+ * rename() can not overwrite existing files on Windows
+ * this function will use copy/unlink instead
+ */
+function io_rename($from,$to){
+ global $conf;
+ if(!@rename($from,$to)){
+ if(@copy($from,$to)){
+ if($conf['fperm']) chmod($to, $conf['fperm']);
+ @unlink($from);
+ return true;
+ }
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Runs an external command and returns it's output as string
+ *
+ * @author Harry Brueckner <harry_b@eml.cc>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @deprecated
+ */
+function io_runcmd($cmd){
+ $fh = popen($cmd, "r");
+ if(!$fh) return false;
+ $ret = '';
+ while (!feof($fh)) {
+ $ret .= fread($fh, 8192);
+ }
+ pclose($fh);
+ return $ret;
+}
+
+/**
+ * Search a file for matching lines
+ *
+ * This is probably not faster than file()+preg_grep() but less
+ * memory intensive because not the whole file needs to be loaded
+ * at once.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $file The file to search
+ * @param string $pattern PCRE pattern
+ * @param int $max How many lines to return (0 for all)
+ * @param bool $baxkref When true returns array with backreferences instead of lines
+ * @return matching lines or backref, false on error
+ */
+function io_grep($file,$pattern,$max=0,$backref=false){
+ $fh = @fopen($file,'r');
+ if(!$fh) return false;
+ $matches = array();
+
+ $cnt = 0;
+ $line = '';
+ while (!feof($fh)) {
+ $line .= fgets($fh, 4096); // read full line
+ if(substr($line,-1) != "\n") continue;
+
+ // check if line matches
+ if(preg_match($pattern,$line,$match)){
+ if($backref){
+ $matches[] = $match;
+ }else{
+ $matches[] = $line;
+ }
+ $cnt++;
+ }
+ if($max && $max == $cnt) break;
+ $line = '';
+ }
+ fclose($fh);
+ return $matches;
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/pageutils.php b/plugins/dokuwiki/inc/pageutils.php
new file mode 100644
index 0000000..f58e751
--- /dev/null
+++ b/plugins/dokuwiki/inc/pageutils.php
@@ -0,0 +1,478 @@
+<?php
+/**
+ * Utilities for handling pagenames
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @todo Combine similar functions like {wiki,media,meta}FN()
+ */
+
+/**
+ * Fetch the an ID from request
+ *
+ * Uses either standard $_REQUEST variable or extracts it from
+ * the full request URI when userewrite is set to 2
+ *
+ * For $param='id' $conf['start'] is returned if no id was found.
+ * If the second parameter is true (default) the ID is cleaned.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getID($param='id',$clean=true){
+ global $conf;
+
+ $id = isset($_REQUEST[$param]) ? $_REQUEST[$param] : null;
+
+ //construct page id from request URI
+ if(empty($id) && $conf['userewrite'] == 2){
+ //get the script URL
+ if($conf['basedir']){
+ $relpath = '';
+ if($param != 'id') {
+ $relpath = 'lib/exe/';
+ }
+ $script = $conf['basedir'].$relpath.basename($_SERVER['SCRIPT_FILENAME']);
+ }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){
+ $script = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'],'/').'/','',
+ $_SERVER['SCRIPT_FILENAME']);
+ $script = '/'.$script;
+ }else{
+ $script = $_SERVER['SCRIPT_NAME'];
+ }
+
+ //clean script and request (fixes a windows problem)
+ $script = preg_replace('/\/\/+/','/',$script);
+ $request = preg_replace('/\/\/+/','/',$_SERVER['REQUEST_URI']);
+
+ //remove script URL and Querystring to gain the id
+ if(preg_match('/^'.preg_quote($script,'/').'(.*)/',$request, $match)){
+ $id = preg_replace ('/\?.*/','',$match[1]);
+ }
+ $id = urldecode($id);
+ //strip leading slashes
+ $id = preg_replace('!^/+!','',$id);
+ }
+ if($clean) $id = cleanID($id);
+ if(empty($id) && $param=='id') $id = $conf['start'];
+
+ return $id;
+}
+
+/**
+ * Remove unwanted chars from ID
+ *
+ * Cleans a given ID to only use allowed characters. Accented characters are
+ * converted to unaccented ones
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $raw_id The pageid to clean
+ * @param boolean $ascii Force ASCII
+ */
+function cleanID($raw_id,$ascii=false){
+ global $conf;
+ global $lang;
+ static $sepcharpat = null;
+
+ global $cache_cleanid;
+ $cache = & $cache_cleanid;
+
+ // check if it's already in the memory cache
+ if (isset($cache[$raw_id])) {
+ return $cache[$raw_id];
+ }
+
+ $sepchar = $conf['sepchar'];
+ if($sepcharpat == null) // build string only once to save clock cycles
+ $sepcharpat = '#\\'.$sepchar.'+#';
+
+ $id = trim($raw_id);
+ $id = utf8_strtolower($id);
+
+ //alternative namespace seperator
+ $id = strtr($id,';',':');
+ if($conf['useslash']){
+ $id = strtr($id,'/',':');
+ }else{
+ $id = strtr($id,'/',$sepchar);
+ }
+
+ if($conf['deaccent'] == 2 || $ascii) $id = utf8_romanize($id);
+ if($conf['deaccent'] || $ascii) $id = utf8_deaccent($id,-1);
+
+ //remove specials
+ $id = utf8_stripspecials($id,$sepchar,'\*');
+
+ if($ascii) $id = utf8_strip($id);
+
+ //clean up
+ $id = preg_replace($sepcharpat,$sepchar,$id);
+ $id = preg_replace('#:+#',':',$id);
+ $id = trim($id,':._-');
+ $id = preg_replace('#:[:\._\-]+#',':',$id);
+
+ $cache[$raw_id] = $id;
+ return($id);
+}
+
+/**
+ * Return namespacepart of a wiki ID
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function getNS($id){
+ $pos = strrpos($id,':');
+ if($pos!==false){
+ return substr($id,0,$pos);
+ }
+ return false;
+}
+
+/**
+ * Returns the ID without the namespace
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function noNS($id) {
+ $pos = strrpos($id, ':');
+ if ($pos!==false) {
+ return substr($id, $pos+1);
+ } else {
+ return $id;
+ }
+}
+
+/**
+ * returns the full path to the datafile specified by ID and
+ * optional revision
+ *
+ * The filename is URL encoded to protect Unicode chars
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function wikiFN($raw_id,$rev='',$clean=true){
+ global $conf;
+
+ global $cache_wikifn;
+ $cache = & $cache_wikifn;
+
+ if (isset($cache[$raw_id]) && isset($cache[$raw_id][$rev])) {
+ return $cache[$raw_id][$rev];
+ }
+
+ $id = $raw_id;
+
+ if ($clean) $id = cleanID($id);
+ $id = str_replace(':','/',$id);
+ if(empty($rev)){
+ $fn = $conf['datadir'].'/'.utf8_encodeFN($id).'.txt';
+ }else{
+ $fn = $conf['olddir'].'/'.utf8_encodeFN($id).'.'.$rev.'.txt';
+ if($conf['compression']){
+ //test for extensions here, we want to read both compressions
+ if (@file_exists($fn . '.gz')){
+ $fn .= '.gz';
+ }else if(@file_exists($fn . '.bz2')){
+ $fn .= '.bz2';
+ }else{
+ //file doesnt exist yet, so we take the configured extension
+ $fn .= '.' . $conf['compression'];
+ }
+ }
+ }
+
+ if (!isset($cache[$raw_id])) { $cache[$raw_id] = array(); }
+ $cache[$raw_id][$rev] = $fn;
+ return $fn;
+}
+
+/**
+ * Returns the full path to the file for locking the page while editing.
+ *
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function wikiLockFN($id) {
+ global $conf;
+ return $conf['lockdir'].'/'.md5(cleanID($id)).'.lock';
+}
+
+
+/**
+ * returns the full path to the meta file specified by ID and extension
+ *
+ * The filename is URL encoded to protect Unicode chars
+ *
+ * @author Steven Danz <steven-danz@kc.rr.com>
+ */
+function metaFN($id,$ext){
+ global $conf;
+ $id = cleanID($id);
+ $id = str_replace(':','/',$id);
+ $fn = $conf['metadir'].'/'.utf8_encodeFN($id).$ext;
+ return $fn;
+}
+
+/**
+ * returns an array of full paths to all metafiles of a given ID
+ *
+ * @author Esther Brunner <esther@kaffeehaus.ch>
+ */
+function metaFiles($id){
+ $name = noNS($id);
+ $dir = metaFN(getNS($id),'');
+ $files = array();
+
+ $dh = @opendir($dir);
+ if(!$dh) return $files;
+ while(($file = readdir($dh)) !== false){
+ if(strpos($file,$name.'.') === 0 && !is_dir($dir.$file))
+ $files[] = $dir.$file;
+ }
+ closedir($dh);
+
+ return $files;
+}
+
+/**
+ * returns the full path to the mediafile specified by ID
+ *
+ * The filename is URL encoded to protect Unicode chars
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function mediaFN($id){
+ global $conf;
+ $id = cleanID($id);
+ $id = str_replace(':','/',$id);
+ $fn = $conf['mediadir'].'/'.utf8_encodeFN($id);
+ return $fn;
+}
+
+/**
+ * Returns the full filepath to a localized textfile if local
+ * version isn't found the english one is returned
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function localeFN($id){
+ global $conf;
+ $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.txt';
+ if(!@file_exists($file)){
+ //fall back to english
+ $file = DOKU_INC.'inc/lang/en/'.$id.'.txt';
+ }
+ return $file;
+}
+
+/**
+ * Resolve relative paths in IDs
+ *
+ * Do not call directly use resolve_mediaid or resolve_pageid
+ * instead
+ *
+ * Partyly based on a cleanPath function found at
+ * http://www.php.net/manual/en/function.realpath.php#57016
+ *
+ * @author <bart at mediawave dot nl>
+ */
+function resolve_id($ns,$id,$clean=true){
+ // if the id starts with a dot we need to handle the
+ // relative stuff
+ if($id{0} == '.'){
+ // normalize initial dots without a colon
+ $id = preg_replace('/^(\.+)(?=[^:\.])/','\1:',$id);
+ // prepend the current namespace
+ $id = $ns.':'.$id;
+
+ // cleanup relatives
+ $result = array();
+ $pathA = explode(':', $id);
+ if (!$pathA[0]) $result[] = '';
+ foreach ($pathA AS $key => $dir) {
+ if ($dir == '..') {
+ if (end($result) == '..') {
+ $result[] = '..';
+ } elseif (!array_pop($result)) {
+ $result[] = '..';
+ }
+ } elseif ($dir && $dir != '.') {
+ $result[] = $dir;
+ }
+ }
+ if (!end($pathA)) $result[] = '';
+ $id = implode(':', $result);
+ }elseif($ns !== false && strpos($id,':') === false){
+ //if link contains no namespace. add current namespace (if any)
+ $id = $ns.':'.$id;
+ }
+
+ if($clean) $id = cleanID($id);
+ return $id;
+}
+
+/**
+ * Returns a full media id
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function resolve_mediaid($ns,&$page,&$exists){
+ $page = resolve_id($ns,$page);
+ $file = mediaFN($page);
+ $exists = @file_exists($file);
+}
+
+/**
+ * Returns a full page id
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function resolve_pageid($ns,&$page,&$exists){
+ global $conf;
+ $exists = false;
+
+ //keep hashlink if exists then clean both parts
+ if (strpos($page,'#')) {
+ list($page,$hash) = explode('#',$page,2);
+ } else {
+ $hash = '';
+ }
+ $hash = cleanID($hash);
+ $page = resolve_id($ns,$page,false); // resolve but don't clean, yet
+
+ // get filename (calls clean itself)
+ $file = wikiFN($page);
+
+ // if ends with colon we have a namespace link
+ if(substr($page,-1) == ':'){
+ if(@file_exists(wikiFN($page.$conf['start']))){
+ // start page inside namespace
+ $page = $page.$conf['start'];
+ $exists = true;
+ }elseif(@file_exists(wikiFN($page.noNS(cleanID($page))))){
+ // page named like the NS inside the NS
+ $page = $page.noNS(cleanID($page));
+ $exists = true;
+ }elseif(@file_exists(wikiFN($page))){
+ // page like namespace exists
+ $page = $page;
+ $exists = true;
+ }else{
+ // fall back to default
+ $page = $page.$conf['start'];
+ }
+ }else{
+ //check alternative plural/nonplural form
+ if(!@file_exists($file)){
+ if( $conf['autoplural'] ){
+ if(substr($page,-1) == 's'){
+ $try = substr($page,0,-1);
+ }else{
+ $try = $page.'s';
+ }
+ if(@file_exists(wikiFN($try))){
+ $page = $try;
+ $exists = true;
+ }
+ }
+ }else{
+ $exists = true;
+ }
+ }
+
+ // now make sure we have a clean page
+ $page = cleanID($page);
+
+ //add hash if any
+ if(!empty($hash)) $page .= '#'.$hash;
+}
+
+/**
+ * Returns the name of a cachefile from given data
+ *
+ * The needed directory is created by this function!
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param string $data This data is used to create a unique md5 name
+ * @param string $ext This is appended to the filename if given
+ * @return string The filename of the cachefile
+ */
+function getCacheName($data,$ext=''){
+ global $conf;
+ $md5 = md5($data);
+ $file = $conf['cachedir'].'/'.$md5{0}.'/'.$md5.$ext;
+ io_makeFileDir($file);
+ return $file;
+}
+
+/**
+ * Checks a pageid against $conf['hidepages']
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+function isHiddenPage($id){
+ global $conf;
+ if(empty($conf['hidepages'])) return false;
+
+ if(preg_match('/'.$conf['hidepages'].'/ui',':'.$id)){
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Reverse of isHiddenPage
+ *
+ * @author Andreas Gohr <gohr@cosmocode.de>
+ */
+function isVisiblePage($id){
+ return !isHiddenPage($id);
+}
+
+/**
+ * Checks and sets HTTP headers for conditional HTTP requests
+ *
+ * @author Simon Willison <swillison@gmail.com>
+ * @link http://simon.incutio.com/archive/2003/04/23/conditionalGet
+ * @param timestamp $timestamp lastmodified time of the cache file
+ * @returns void or void with previously header() commands executed
+ */
+function http_conditionalRequest($timestamp){
+ // A PHP implementation of conditional get, see
+ // http://fishbowl.pastiche.org/archives/001132.html
+ $last_modified = substr(date('r', $timestamp), 0, -5).'GMT';
+ $etag = '"'.md5($last_modified).'"';
+ // Send the headers
+ header("Last-Modified: $last_modified");
+ header("ETag: $etag");
+ // See if the client has provided the required headers
+ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){
+ $if_modified_since = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+ }else{
+ $if_modified_since = false;
+ }
+
+ if (isset($_SERVER['HTTP_IF_NONE_MATCH'])){
+ $if_none_match = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
+ }else{
+ $if_none_match = false;
+ }
+
+ if (!$if_modified_since && !$if_none_match){
+ return;
+ }
+
+ // At least one of the headers is there - check them
+ if ($if_none_match && $if_none_match != $etag) {
+ return; // etag is there but doesn't match
+ }
+
+ if ($if_modified_since && $if_modified_since != $last_modified) {
+ return; // if-modified-since is there but doesn't match
+ }
+
+ // Nothing has changed since their last request - serve a 304 and exit
+ header('HTTP/1.0 304 Not Modified');
+ exit;
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parser/handler.php b/plugins/dokuwiki/inc/parser/handler.php
new file mode 100644
index 0000000..bd307d6
--- /dev/null
+++ b/plugins/dokuwiki/inc/parser/handler.php
@@ -0,0 +1,1653 @@
+<?php
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+
+class Doku_Handler {
+
+ var $Renderer = NULL;
+
+ var $CallWriter = NULL;
+
+ var $calls = array();
+
+ var $status = array(
+ 'section' => FALSE,
+ 'section_edit_start' => -1,
+ 'section_edit_level' => 1,
+ 'section_edit_title' => ''
+ );
+
+ var $rewriteBlocks = TRUE;
+
+ function __construct() {
+ $this->CallWriter = new Doku_Handler_CallWriter($this);
+ }
+
+ function _addCall($handler, $args, $pos) {
+ $call = array($handler,$args, $pos);
+ $this->CallWriter->writeCall($call);
+ }
+
+ function _finalize(){
+
+ $this->CallWriter->finalise();
+
+ if ( $this->status['section'] ) {
+ $last_call = end($this->calls);
+ array_push($this->calls,array('section_close',array(), $last_call[2]));
+ if ($this->status['section_edit_start']>1) {
+ // ignore last edit section if there is only one header
+ array_push($this->calls,array('section_edit',array($this->status['section_edit_start'], 0, $this->status['section_edit_level'], $this->status['section_edit_title']), $last_call[2]));
+ }
+ }
+
+ if ( $this->rewriteBlocks ) {
+ $B = new Doku_Handler_Block();
+ $this->calls = $B->process($this->calls);
+ }
+
+ trigger_event('PARSER_HANDLER_DONE',$this);
+
+ array_unshift($this->calls,array('document_start',array(),0));
+ $last_call = end($this->calls);
+ array_push($this->calls,array('document_end',array(),$last_call[2]));
+ }
+
+ function fetch() {
+ $call = each($this->calls);
+ if ( $call ) {
+ return $call['value'];
+ }
+ return FALSE;
+ }
+
+
+ /**
+ * Special plugin handler
+ *
+ * This handler is called for all modes starting with 'plugin_'.
+ * An additional parameter with the plugin name is passed
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function plugin($match, $state, $pos, $pluginname){
+ $data = array($match);
+ $plugin =& plugin_load('syntax',$pluginname);
+ if($plugin != null){
+ $data = $plugin->handle($match, $state, $pos, $this);
+ }
+ $this->_addCall('plugin',array($pluginname,$data,$state),$pos);
+ return TRUE;
+ }
+
+ function base($match, $state, $pos) {
+ switch ( $state ) {
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('cdata',array($match), $pos);
+ return TRUE;
+ break;
+
+ }
+ }
+
+ function header($match, $state, $pos) {
+ global $conf;
+
+ // get level and title
+ $title = trim($match);
+ $level = 7 - strspn($title,'=');
+ if($level < 1) $level = 1;
+ $title = trim($title,'=');
+ $title = trim($title);
+
+ if ($this->status['section']) $this->_addCall('section_close',array(),$pos);
+
+ if ($level<=$conf['maxseclevel']) {
+ $this->_addCall('section_edit',array($this->status['section_edit_start'], $pos-1, $this->status['section_edit_level'], $this->status['section_edit_title']), $pos);
+ $this->status['section_edit_start'] = $pos;
+ $this->status['section_edit_level'] = $level;
+ $this->status['section_edit_title'] = $title;
+ }
+
+ $this->_addCall('header',array($title,$level,$pos), $pos);
+
+ $this->_addCall('section_open',array($level),$pos);
+ $this->status['section'] = TRUE;
+ return TRUE;
+ }
+
+ function notoc($match, $state, $pos) {
+ $this->_addCall('notoc',array(),$pos);
+ return TRUE;
+ }
+
+ function nocache($match, $state, $pos) {
+ $this->_addCall('nocache',array(),$pos);
+ return TRUE;
+ }
+
+ function linebreak($match, $state, $pos) {
+ $this->_addCall('linebreak',array(),$pos);
+ return TRUE;
+ }
+
+ function eol($match, $state, $pos) {
+ $this->_addCall('eol',array(),$pos);
+ return TRUE;
+ }
+
+ function hr($match, $state, $pos) {
+ $this->_addCall('hr',array(),$pos);
+ return TRUE;
+ }
+
+ function _nestingTag($match, $state, $pos, $name) {
+ switch ( $state ) {
+ case DOKU_LEXER_ENTER:
+ $this->_addCall($name.'_open', array(), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $this->_addCall($name.'_close', array(), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('cdata',array($match), $pos);
+ break;
+ }
+ }
+
+ function strong($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'strong');
+ return TRUE;
+ }
+
+ function emphasis($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'emphasis');
+ return TRUE;
+ }
+
+ function underline($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'underline');
+ return TRUE;
+ }
+
+ function monospace($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'monospace');
+ return TRUE;
+ }
+
+ function subscript($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'subscript');
+ return TRUE;
+ }
+
+ function superscript($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'superscript');
+ return TRUE;
+ }
+
+ function deleted($match, $state, $pos) {
+ $this->_nestingTag($match, $state, $pos, 'deleted');
+ return TRUE;
+ }
+
+
+ function footnote($match, $state, $pos) {
+// $this->_nestingTag($match, $state, $pos, 'footnote');
+ if (!isset($this->_footnote)) $this->_footnote = false;
+
+ switch ( $state ) {
+ case DOKU_LEXER_ENTER:
+ // footnotes can not be nested - however due to limitations in lexer it can't be prevented
+ // we will still enter a new footnote mode, we just do nothing
+ if ($this->_footnote) {
+ $this->_addCall('cdata',array($match), $pos);
+ break;
+ }
+
+ $this->_footnote = true;
+
+ $ReWriter = new Doku_Handler_Nest($this->CallWriter,'footnote_close');
+ $this->CallWriter = & $ReWriter;
+ $this->_addCall('footnote_open', array(), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ // check whether we have already exitted the footnote mode, can happen if the modes were nested
+ if (!$this->_footnote) {
+ $this->_addCall('cdata',array($match), $pos);
+ break;
+ }
+
+ $this->_footnote = false;
+
+ $this->_addCall('footnote_close', array(), $pos);
+ $this->CallWriter->process();
+ $ReWriter = & $this->CallWriter;
+ $this->CallWriter = & $ReWriter->CallWriter;
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('cdata', array($match), $pos);
+ break;
+ }
+ return TRUE;
+ }
+
+ function listblock($match, $state, $pos) {
+ switch ( $state ) {
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Doku_Handler_List($this->CallWriter);
+ $this->CallWriter = & $ReWriter;
+ $this->_addCall('list_open', array($match), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $this->_addCall('list_close', array(), $pos);
+ $this->CallWriter->process();
+ $ReWriter = & $this->CallWriter;
+ $this->CallWriter = & $ReWriter->CallWriter;
+ break;
+ case DOKU_LEXER_MATCHED:
+ $this->_addCall('list_item', array($match), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('cdata', array($match), $pos);
+ break;
+ }
+ return TRUE;
+ }
+
+ function unformatted($match, $state, $pos) {
+ if ( $state == DOKU_LEXER_UNMATCHED ) {
+ $this->_addCall('unformatted',array($match), $pos);
+ }
+ return TRUE;
+ }
+
+ function php($match, $state, $pos) {
+ global $conf;
+ if ( $state == DOKU_LEXER_UNMATCHED ) {
+ if ($conf['phpok']) {
+ $this->_addCall('php',array($match), $pos);
+ } else {
+ $this->_addCall('file',array($match), $pos);
+ }
+ }
+ return TRUE;
+ }
+
+ function html($match, $state, $pos) {
+ global $conf;
+ if ( $state == DOKU_LEXER_UNMATCHED ) {
+ if($conf['htmlok']){
+ $this->_addCall('html',array($match), $pos);
+ } else {
+ $this->_addCall('file',array($match), $pos);
+ }
+ }
+ return TRUE;
+ }
+
+ function preformatted($match, $state, $pos) {
+ switch ( $state ) {
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Doku_Handler_Preformatted($this->CallWriter);
+ $this->CallWriter = & $ReWriter;
+ $this->_addCall('preformatted_start',array(), $pos);
+ break;
+ case DOKU_LEXER_EXIT:
+ $this->_addCall('preformatted_end',array(), $pos);
+ $this->CallWriter->process();
+ $ReWriter = & $this->CallWriter;
+ $this->CallWriter = & $ReWriter->CallWriter;
+ break;
+ case DOKU_LEXER_MATCHED:
+ $this->_addCall('preformatted_newline',array(), $pos);
+ break;
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('preformatted_content',array($match), $pos);
+ break;
+ }
+
+ return TRUE;
+ }
+
+ function file($match, $state, $pos) {
+ if ( $state == DOKU_LEXER_UNMATCHED ) {
+ $this->_addCall('file',array($match), $pos);
+ }
+ return TRUE;
+ }
+
+ function quote($match, $state, $pos) {
+
+ switch ( $state ) {
+
+ case DOKU_LEXER_ENTER:
+ $ReWriter = new Doku_Handler_Quote($this->CallWriter);
+ $this->CallWriter = & $ReWriter;
+ $this->_addCall('quote_start',array($match), $pos);
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $this->_addCall('quote_end',array(), $pos);
+ $this->CallWriter->process();
+ $ReWriter = & $this->CallWriter;
+ $this->CallWriter = & $ReWriter->CallWriter;
+ break;
+
+ case DOKU_LEXER_MATCHED:
+ $this->_addCall('quote_newline',array($match), $pos);
+ break;
+
+ case DOKU_LEXER_UNMATCHED:
+ $this->_addCall('cdata',array($match), $pos);
+ break;
+
+ }
+
+ return TRUE;
+ }
+
+ function code($match, $state, $pos) {
+ switch ( $state ) {
+ case DOKU_LEXER_UNMATCHED:
+ $matches = preg_split('/>/u',$match,2);
+ $matches[0] = trim($matches[0]);
+ if ( trim($matches[0]) == '' ) {
+ $matches[0] = NULL;
+ }
+ # $matches[0] contains name of programming language
+ # if available, We shortcut html here.
+ if($matches[0] == 'html') $matches[0] = 'html4strict';
+ $this->_addCall(
+ 'code',
+ array($matches[1],$matches[0]),
+ $pos
+ );
+ break;
+ }
+ return TRUE;
+ }
+
+ function acronym($match, $state, $pos) {
+ $this->_addCall('acronym',array($match), $pos);
+ return TRUE;
+ }
+
+ function smiley($match, $state, $pos) {
+ $this->_addCall('smiley',array($match), $pos);
+ return TRUE;
+ }
+
+ function wordblock($match, $state, $pos) {
+ $this->_addCall('wordblock',array($match), $pos);
+ return TRUE;
+ }
+
+ function entity($match, $state, $pos) {
+ $this->_addCall('entity',array($match), $pos);
+ return TRUE;
+ }
+
+ function multiplyentity($match, $state, $pos) {
+ preg_match_all('/\d+/',$match,$matches);
+ $this->_addCall('multiplyentity',array($matches[0][0],$matches[0][1]), $pos);
+ return TRUE;
+ }
+
+ function singlequoteopening($match, $state, $pos) {
+ $this->_addCall('singlequoteopening',array(), $pos);
+ return TRUE;
+ }
+
+ function singlequoteclosing($match, $state, $pos) {
+ $this->_addCall('singlequoteclosing',array(), $pos);
+ return TRUE;
+ }
+
+ function doublequoteopening($match, $state, $pos) {
+ $this->_addCall('doublequoteopening',array(), $pos);
+ return TRUE;
+ }
+
+ function doublequoteclosing($match, $state, $pos) {
+ $this->_addCall('doublequoteclosing',array(), $pos);
+ return TRUE;
+ }
+
+ function camelcaselink($match, $state, $pos) {
+ $this->_addCall('camelcaselink',array($match), $pos);
+ return TRUE;
+ }
+
+ /*
+ */
+ function internallink($match, $state, $pos) {
+ // Strip the opening and closing markup
+ $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
+
+ // Split title from URL
+ $link = preg_split('/\|/u',$link,2);
+ if ( !isset($link[1]) ) {
+ $link[1] = NULL;
+ } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
+ // If the title is an image, convert it to an array containing the image details
+ $link[1] = Doku_Handler_Parse_Media($link[1]);
+ }
+ $link[0] = trim($link[0]);
+
+ //decide which kind of link it is
+
+ if ( preg_match('/^[a-zA-Z\.]+>{1}.*$/u',$link[0]) ) {
+ // Interwiki
+ $interwiki = preg_split('/>/u',$link[0]);
+ $this->_addCall(
+ 'interwikilink',
+ array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
+ $pos
+ );
+ }elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) {
+ // Windows Share
+ $this->_addCall(
+ 'windowssharelink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
+ // external link (accepts all protocols)
+ $this->_addCall(
+ 'externallink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ }elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link[0]) ) {
+ // E-Mail
+ $this->_addCall(
+ 'emaillink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ }elseif ( preg_match('!^#.+!',$link[0]) ){
+ // local link
+ $this->_addCall(
+ 'locallink',
+ array(substr($link[0],1),$link[1]),
+ $pos
+ );
+ }else{
+ // internal link
+ $this->_addCall(
+ 'internallink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ }
+
+ return TRUE;
+ }
+
+ function filelink($match, $state, $pos) {
+ $this->_addCall('filelink',array($match, NULL), $pos);
+ return TRUE;
+ }
+
+ function windowssharelink($match, $state, $pos) {
+ $this->_addCall('windowssharelink',array($match, NULL), $pos);
+ return TRUE;
+ }
+
+ function media($match, $state, $pos) {
+ $p = Doku_Handler_Parse_Media($match);
+
+ $this->_addCall(
+ $p['type'],
+ array($p['src'], $p['title'], $p['align'], $p['width'],
+ $p['height'], $p['cache'], $p['linking']),
+ $pos
+ );
+ return TRUE;
+ }
+
+ function rss($match, $state, $pos) {
+ $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
+
+ // get params
+ list($link,$params) = explode(' ',$link,2);
+
+ $p = array();
+ if(preg_match('/\b(\d+)\b/',$params,$match)){
+ $p['max'] = $match[1];
+ }else{
+ $p['max'] = 8;
+ }
+ $p['reverse'] = (preg_match('/rev/',$params));
+ $p['author'] = (preg_match('/\b(by|author)/',$params));
+ $p['date'] = (preg_match('/\b(date)/',$params));
+ $p['details'] = (preg_match('/\b(desc|detail)/',$params));
+
+ if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) {
+ $period = array('d' => 86400, 'h' => 3600, 'm' => 60);
+ $p['refresh'] = max(600,$match[1]*$period[$match[2]]); // n * period in seconds, minimum 10 minutes
+ } else {
+ $p['refresh'] = 14400; // default to 4 hours
+ }
+
+ $this->_addCall('rss',array($link,$p),$pos);
+ return TRUE;
+ }
+
+ function externallink($match, $state, $pos) {
+ // Prevent use of multibyte strings in URLs
+ // See: http://www.boingboing.net/2005/02/06/shmoo_group_exploit_.html
+ // Not worried about other charsets so long as page is output as UTF-8
+ /*if ( strlen($match) != utf8_strlen($match) ) {
+ $this->_addCall('cdata',array($match), $pos);
+ } else {*/
+
+ $this->_addCall('externallink',array($match, NULL), $pos);
+ //}
+ return TRUE;
+ }
+
+ function emaillink($match, $state, $pos) {
+ $email = preg_replace(array('/^</','/>$/'),'',$match);
+ $this->_addCall('emaillink',array($email, NULL), $pos);
+ return TRUE;
+ }
+
+ function table($match, $state, $pos) {
+ switch ( $state ) {
+
+ case DOKU_LEXER_ENTER:
+
+ $ReWriter = new Doku_Handler_Table($this->CallWriter);
+ $this->CallWriter = & $ReWriter;
+
+ $this->_addCall('table_start', array(), $pos);
+ //$this->_addCall('table_row', array(), $pos);
+ if ( trim($match) == '^' ) {
+ $this->_addCall('tableheader', array(), $pos);
+ } else {
+ $this->_addCall('tablecell', array(), $pos);
+ }
+ break;
+
+ case DOKU_LEXER_EXIT:
+ $this->_addCall('table_end', array(), $pos);
+ $this->CallWriter->process();
+ $ReWriter = & $this->CallWriter;
+ $this->CallWriter = & $ReWriter->CallWriter;
+ break;
+
+ case DOKU_LEXER_UNMATCHED:
+ if ( trim($match) != '' ) {
+ $this->_addCall('cdata',array($match), $pos);
+ }
+ break;
+
+ case DOKU_LEXER_MATCHED:
+ if ( $match == ' ' ){
+ $this->_addCall('cdata', array($match), $pos);
+ } else if ( preg_match('/\t+/',$match) ) {
+ $this->_addCall('table_align', array($match), $pos);
+ } else if ( preg_match('/ {2,}/',$match) ) {
+ $this->_addCall('table_align', array($match), $pos);
+ } else if ( $match == "\n|" ) {
+ $this->_addCall('table_row', array(), $pos);
+ $this->_addCall('tablecell', array(), $pos);
+ } else if ( $match == "\n^" ) {
+ $this->_addCall('table_row', array(), $pos);
+ $this->_addCall('tableheader', array(), $pos);
+ } else if ( $match == '|' ) {
+ $this->_addCall('tablecell', array(), $pos);
+ } else if ( $match == '^' ) {
+ $this->_addCall('tableheader', array(), $pos);
+ }
+ break;
+ }
+ return TRUE;
+ }
+}
+
+//------------------------------------------------------------------------
+function Doku_Handler_Parse_Media($match) {
+
+ // Strip the opening and closing markup
+ $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
+
+ // Split title from URL
+ $link = preg_split('/\|/u',$link,2);
+
+
+ // Check alignment
+ $ralign = (bool)preg_match('/^ /',$link[0]);
+ $lalign = (bool)preg_match('/ $/',$link[0]);
+
+ // Logic = what's that ;)...
+ if ( $lalign & $ralign ) {
+ $align = 'center';
+ } else if ( $ralign ) {
+ $align = 'right';
+ } else if ( $lalign ) {
+ $align = 'left';
+ } else {
+ $align = NULL;
+ }
+
+ // The title...
+ if ( !isset($link[1]) ) {
+ $link[1] = NULL;
+ }
+
+ //remove aligning spaces
+ $link[0] = trim($link[0]);
+
+ //split into src and parameters (using the very last questionmark)
+ $pos = strrpos($link[0], '?');
+ if($pos !== false){
+ $src = substr($link[0],0,$pos);
+ $param = substr($link[0],$pos+1);
+ }else{
+ $src = $link[0];
+ $param = '';
+ }
+
+ //parse width and height
+ if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
+ ($size[1]) ? $w = $size[1] : $w = NULL;
+ ($size[3]) ? $h = $size[3] : $h = NULL;
+ } else {
+ $w = NULL;
+ $h = NULL;
+ }
+
+ //get linking command
+ if(preg_match('/nolink/i',$param)){
+ $linking = 'nolink';
+ }else if(preg_match('/direct/i',$param)){
+ $linking = 'direct';
+ }else{
+ $linking = 'details';
+ }
+
+ //get caching command
+ if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
+ $cache = $cachemode[1];
+ }else{
+ $cache = 'cache';
+ }
+
+ // Check whether this is a local or remote image
+ if ( preg_match('#^(https?|ftp)#i',$src) ) {
+ $call = 'externalmedia';
+ } else {
+ $call = 'internalmedia';
+ }
+
+ $params = array(
+ 'type'=>$call,
+ 'src'=>$src,
+ 'title'=>$link[1],
+ 'align'=>$align,
+ 'width'=>$w,
+ 'height'=>$h,
+ 'cache'=>$cache,
+ 'linking'=>$linking,
+ );
+
+ return $params;
+}
+
+//------------------------------------------------------------------------
+class Doku_Handler_CallWriter {
+
+ var $Handler;
+
+ function __construct(& $Handler) {
+ $this->Handler = & $Handler;
+ }
+
+ function writeCall($call) {
+ $this->Handler->calls[] = $call;
+ }
+
+ function writeCalls($calls) {
+ $this->Handler->calls = array_merge($this->Handler->calls, $calls);
+ }
+
+ // function is required, but since this call writer is first/highest in
+ // the chain it is not required to do anything
+ function finalise() {
+ }
+}
+
+//------------------------------------------------------------------------
+/**
+ * Generic call writer class to handle nesting of rendering instructions
+ * within a render instruction. Also see nest() method of renderer base class
+ *
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+class Doku_Handler_Nest {
+
+ var $CallWriter;
+ var $calls = array();
+
+ var $closingInstruction;
+
+ /**
+ * constructor
+ *
+ * @param object $CallWriter the renderers current call writer
+ * @param string $close closing instruction name, this is required to properly terminate the
+ * syntax mode if the document ends without a closing pattern
+ */
+ function __construct(& $CallWriter, $close="nest_close") {
+ $this->CallWriter = & $CallWriter;
+
+ $this->closingInstruction = $close;
+ }
+
+ function writeCall($call) {
+ $this->calls[] = $call;
+ }
+
+ function writeCalls($calls) {
+ $this->calls = array_merge($this->calls, $calls);
+ }
+
+ function finalise() {
+ $last_call = end($this->calls);
+ $this->writeCall(array($this->closingInstruction,array(), $last_call[2]));
+
+ $this->process();
+ $this->CallWriter->finalise();
+ }
+
+ function process() {
+ $first_call = reset($this->calls);
+ $this->CallWriter->writeCall(array("nest", array($this->calls), $first_call[2]));
+ }
+}
+
+class Doku_Handler_List {
+
+ var $CallWriter;
+
+ var $calls = array();
+ var $listCalls = array();
+ var $listStack = array();
+
+ function __construct(& $CallWriter) {
+ $this->CallWriter = & $CallWriter;
+ }
+
+ function writeCall($call) {
+ $this->calls[] = $call;
+ }
+
+ // Probably not needed but just in case...
+ function writeCalls($calls) {
+ $this->calls = array_merge($this->calls, $calls);
+# $this->CallWriter->writeCalls($this->calls);
+ }
+
+ function finalise() {
+ $last_call = end($this->calls);
+ $this->writeCall(array('list_close',array(), $last_call[2]));
+
+ $this->process();
+ $this->CallWriter->finalise();
+ }
+
+ //------------------------------------------------------------------------
+ function process() {
+
+ foreach ( $this->calls as $call ) {
+ switch ($call[0]) {
+ case 'list_item':
+ $this->listOpen($call);
+ break;
+ case 'list_open':
+ $this->listStart($call);
+ break;
+ case 'list_close':
+ $this->listEnd($call);
+ break;
+ default:
+ $this->listContent($call);
+ break;
+ }
+ }
+
+ $this->CallWriter->writeCalls($this->listCalls);
+ }
+
+ //------------------------------------------------------------------------
+ function listStart($call) {
+ $depth = $this->interpretSyntax($call[1][0], $listType);
+
+ $this->initialDepth = $depth;
+ $this->listStack[] = array($listType, $depth);
+
+ $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]);
+ $this->listCalls[] = array('listitem_open',array(1),$call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+ }
+
+ //------------------------------------------------------------------------
+ function listEnd($call) {
+ $closeContent = TRUE;
+
+ while ( $list = array_pop($this->listStack) ) {
+ if ( $closeContent ) {
+ $this->listCalls[] = array('listcontent_close',array(),$call[2]);
+ $closeContent = FALSE;
+ }
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+ $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]);
+ }
+ }
+
+ //------------------------------------------------------------------------
+ function listOpen($call) {
+ $depth = $this->interpretSyntax($call[1][0], $listType);
+ $end = end($this->listStack);
+
+ // Not allowed to be shallower than initialDepth
+ if ( $depth < $this->initialDepth ) {
+ $depth = $this->initialDepth;
+ }
+
+ //------------------------------------------------------------------------
+ if ( $depth == $end[1] ) {
+
+ // Just another item in the list...
+ if ( $listType == $end[0] ) {
+ $this->listCalls[] = array('listcontent_close',array(),$call[2]);
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+ $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+
+ // Switched list type...
+ } else {
+
+ $this->listCalls[] = array('listcontent_close',array(),$call[2]);
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+ $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
+ $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
+ $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+
+ array_pop($this->listStack);
+ $this->listStack[] = array($listType, $depth);
+ }
+
+ //------------------------------------------------------------------------
+ // Getting deeper...
+ } else if ( $depth > $end[1] ) {
+
+ $this->listCalls[] = array('listcontent_close',array(),$call[2]);
+ $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
+ $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+
+ $this->listStack[] = array($listType, $depth);
+
+ //------------------------------------------------------------------------
+ // Getting shallower ( $depth < $end[1] )
+ } else {
+ $this->listCalls[] = array('listcontent_close',array(),$call[2]);
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+ $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
+
+ // Throw away the end - done
+ array_pop($this->listStack);
+
+ while (1) {
+ $end = end($this->listStack);
+
+ if ( $end[1] <= $depth ) {
+
+ // Normalize depths
+ $depth = $end[1];
+
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+
+ if ( $end[0] == $listType ) {
+ $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+
+ } else {
+ // Switching list type...
+ $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
+ $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
+ $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
+ $this->listCalls[] = array('listcontent_open',array(),$call[2]);
+
+ array_pop($this->listStack);
+ $this->listStack[] = array($listType, $depth);
+ }
+
+ break;
+
+ // Haven't dropped down far enough yet.... ( $end[1] > $depth )
+ } else {
+
+ $this->listCalls[] = array('listitem_close',array(),$call[2]);
+ $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
+
+ array_pop($this->listStack);
+
+ }
+
+ }
+
+ }
+ }
+
+ //------------------------------------------------------------------------
+ function listContent($call) {
+ $this->listCalls[] = $call;
+ }
+
+ //------------------------------------------------------------------------
+ function interpretSyntax($match, & $type) {
+ if ( substr($match,-1) == '*' ) {
+ $type = 'u';
+ } else {
+ $type = 'o';
+ }
+ return count(explode(' ',str_replace("\t",' ',$match)));
+ }
+}
+
+//------------------------------------------------------------------------
+class Doku_Handler_Preformatted {
+
+ var $CallWriter;
+
+ var $calls = array();
+ var $pos;
+ var $text ='';
+
+
+
+ function __construct(& $CallWriter) {
+ $this->CallWriter = & $CallWriter;
+ }
+
+ function writeCall($call) {
+ $this->calls[] = $call;
+ }
+
+ // Probably not needed but just in case...
+ function writeCalls($calls) {
+ $this->calls = array_merge($this->calls, $calls);
+# $this->CallWriter->writeCalls($this->calls);
+ }
+
+ function finalise() {
+ $last_call = end($this->calls);
+ $this->writeCall(array('preformatted_end',array(), $last_call[2]));
+
+ $this->process();
+ $this->CallWriter->finalise();
+ }
+
+ function process() {
+ foreach ( $this->calls as $call ) {
+ switch ($call[0]) {
+ case 'preformatted_start':
+ $this->pos = $call[2];
+ break;
+ case 'preformatted_newline':
+ $this->text .= "\n";
+ break;
+ case 'preformatted_content':
+ $this->text .= $call[1][0];
+ break;
+ case 'preformatted_end':
+ $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos));
+ break;
+ }
+ }
+ }
+
+}
+
+//------------------------------------------------------------------------
+class Doku_Handler_Quote {
+
+ var $CallWriter;
+
+ var $calls = array();
+
+ var $quoteCalls = array();
+
+ function __construct(& $CallWriter) {
+ $this->CallWriter = & $CallWriter;
+ }
+
+ function writeCall($call) {
+ $this->calls[] = $call;
+ }
+
+ // Probably not needed but just in case...
+ function writeCalls($calls) {
+ $this->calls = array_merge($this->calls, $calls);
+# $this->CallWriter->writeCalls($this->calls);
+ }
+
+ function finalise() {
+ $last_call = end($this->calls);
+ $this->writeCall(array('quote_end',array(), $last_call[2]));
+
+ $this->process();
+ $this->CallWriter->finalise();
+ }
+
+ function process() {
+
+ $quoteDepth = 1;
+
+ foreach ( $this->calls as $call ) {
+ switch ($call[0]) {
+
+ case 'quote_start':
+
+ $this->quoteCalls[] = array('quote_open',array(),$call[2]);
+
+ case 'quote_newline':
+
+ $quoteLength = $this->getDepth($call[1][0]);
+
+ if ( $quoteLength > $quoteDepth ) {
+ $quoteDiff = $quoteLength - $quoteDepth;
+ for ( $i = 1; $i <= $quoteDiff; $i++ ) {
+ $this->quoteCalls[] = array('quote_open',array(),$call[2]);
+ }
+ } else if ( $quoteLength < $quoteDepth ) {
+ $quoteDiff = $quoteDepth - $quoteLength;
+ for ( $i = 1; $i <= $quoteDiff; $i++ ) {
+ $this->quoteCalls[] = array('quote_close',array(),$call[2]);
+ }
+ } else {
+ if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]);
+ }
+
+ $quoteDepth = $quoteLength;
+
+ break;
+
+ case 'quote_end':
+
+ if ( $quoteDepth > 1 ) {
+ $quoteDiff = $quoteDepth - 1;
+ for ( $i = 1; $i <= $quoteDiff; $i++ ) {
+ $this->quoteCalls[] = array('quote_close',array(),$call[2]);
+ }
+ }
+
+ $this->quoteCalls[] = array('quote_close',array(),$call[2]);
+
+ $this->CallWriter->writeCalls($this->quoteCalls);
+ break;
+
+ default:
+ $this->quoteCalls[] = $call;
+ break;
+ }
+ }
+ }
+
+ function getDepth($marker) {
+ preg_match('/>{1,}/', $marker, $matches);
+ $quoteLength = strlen($matches[0]);
+ return $quoteLength;
+ }
+}
+
+//------------------------------------------------------------------------
+class Doku_Handler_Table {
+
+ var $CallWriter;
+
+ var $calls = array();
+ var $tableCalls = array();
+ var $maxCols = 0;
+ var $maxRows = 1;
+ var $currentCols = 0;
+ var $firstCell = FALSE;
+ var $lastCellType = 'tablecell';
+
+ function __construct(& $CallWriter) {
+ $this->CallWriter = & $CallWriter;
+ }
+
+ function writeCall($call) {
+ $this->calls[] = $call;
+ }
+
+ // Probably not needed but just in case...
+ function writeCalls($calls) {
+ $this->calls = array_merge($this->calls, $calls);
+# $this->CallWriter->writeCalls($this->calls);
+ }
+
+ function finalise() {
+ $last_call = end($this->calls);
+ $this->writeCall(array('table_end',array(), $last_call[2]));
+
+ $this->process();
+ $this->CallWriter->finalise();
+ }
+
+ //------------------------------------------------------------------------
+ function process() {
+ foreach ( $this->calls as $call ) {
+ switch ( $call[0] ) {
+ case 'table_start':
+ $this->tableStart($call);
+ break;
+ case 'table_row':
+ $this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
+ $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
+ break;
+ case 'tableheader':
+ case 'tablecell':
+ $this->tableCell($call);
+ break;
+ case 'table_end':
+ $this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
+ $this->tableEnd($call);
+ break;
+ default:
+ $this->tableDefault($call);
+ break;
+ }
+ }
+ $this->CallWriter->writeCalls($this->tableCalls);
+ }
+
+ function tableStart($call) {
+ $this->tableCalls[] = array('table_open',array(),$call[2]);
+ $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
+ $this->firstCell = TRUE;
+ }
+
+ function tableEnd($call) {
+ $this->tableCalls[] = array('table_close',array(),$call[2]);
+ $this->finalizeTable();
+ }
+
+ function tableRowOpen($call) {
+ $this->tableCalls[] = $call;
+ $this->currentCols = 0;
+ $this->firstCell = TRUE;
+ $this->lastCellType = 'tablecell';
+ $this->maxRows++;
+ }
+
+ function tableRowClose($call) {
+ // Strip off final cell opening and anything after it
+ while ( $discard = array_pop($this->tableCalls ) ) {
+
+ if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
+
+ // Its a spanning element - put it back and close it
+ if ( $discard[1][0] > 1 ) {
+
+ $this->tableCalls[] = $discard;
+ if ( strstr($discard[0],'cell') ) {
+ $name = 'tablecell';
+ } else {
+ $name = 'tableheader';
+ }
+ $this->tableCalls[] = array($name.'_close',array(),$call[2]);
+ }
+
+ break;
+ }
+ }
+ $this->tableCalls[] = $call;
+
+ if ( $this->currentCols > $this->maxCols ) {
+ $this->maxCols = $this->currentCols;
+ }
+ }
+
+ function tableCell($call) {
+ if ( !$this->firstCell ) {
+
+ // Increase the span
+ $lastCall = end($this->tableCalls);
+
+ // A cell call which follows an open cell means an empty cell so span
+ if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) {
+ $this->tableCalls[] = array('colspan',array(),$call[2]);
+
+ }
+
+ $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
+ $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]);
+ $this->lastCellType = $call[0];
+
+ } else {
+
+ $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]);
+ $this->lastCellType = $call[0];
+ $this->firstCell = FALSE;
+
+ }
+
+ $this->currentCols++;
+ }
+
+ function tableDefault($call) {
+ $this->tableCalls[] = $call;
+ }
+
+ function finalizeTable() {
+
+ // Add the max cols and rows to the table opening
+ if ( $this->tableCalls[0][0] == 'table_open' ) {
+ // Adjust to num cols not num col delimeters
+ $this->tableCalls[0][1][] = $this->maxCols - 1;
+ $this->tableCalls[0][1][] = $this->maxRows;
+ } else {
+ trigger_error('First element in table call list is not table_open');
+ }
+
+ $lastRow = 0;
+ $lastCell = 0;
+ $toDelete = array();
+
+ // Look for the colspan elements and increment the colspan on the
+ // previous non-empty opening cell. Once done, delete all the cells
+ // that contain colspans
+ foreach ( $this->tableCalls as $key => $call ) {
+
+ if ( $call[0] == 'tablerow_open' ) {
+
+ $lastRow = $key;
+
+ } else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) {
+
+ $lastCell = $key;
+
+ } else if ( $call[0] == 'table_align' ) {
+
+ // If the previous element was a cell open, align right
+ if ( $this->tableCalls[$key-1][0] == 'tablecell_open' || $this->tableCalls[$key-1][0] == 'tableheader_open' ) {
+ $this->tableCalls[$key-1][1][1] = 'right';
+
+ // If the next element if the close of an element, align either center or left
+ } else if ( $this->tableCalls[$key+1][0] == 'tablecell_close' || $this->tableCalls[$key+1][0] == 'tableheader_close' ) {
+ if ( $this->tableCalls[$lastCell][1][1] == 'right' ) {
+ $this->tableCalls[$lastCell][1][1] = 'center';
+ } else {
+ $this->tableCalls[$lastCell][1][1] = 'left';
+ }
+
+ }
+
+ // Now convert the whitespace back to cdata
+ $this->tableCalls[$key][0] = 'cdata';
+
+ } else if ( $call[0] == 'colspan' ) {
+
+ $this->tableCalls[$key-1][1][0] = FALSE;
+
+ for($i = $key-2; $i > $lastRow; $i--) {
+
+ if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) {
+
+ if ( FALSE !== $this->tableCalls[$i][1][0] ) {
+ $this->tableCalls[$i][1][0]++;
+ break;
+ }
+
+
+ }
+ }
+
+ $toDelete[] = $key-1;
+ $toDelete[] = $key;
+ $toDelete[] = $key+1;
+ }
+ }
+
+
+ // condense cdata
+ $cnt = count($this->tableCalls);
+ for( $key = 0; $key < $cnt; $key++){
+ if($this->tableCalls[$key][0] == 'cdata'){
+ $ckey = $key;
+ $key++;
+ while($this->tableCalls[$key][0] == 'cdata'){
+ $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
+ $toDelete[] = $key;
+ $key++;
+ }
+ continue;
+ }
+ }
+
+ foreach ( $toDelete as $delete ) {
+ unset($this->tableCalls[$delete]);
+ }
+ $this->tableCalls = array_values($this->tableCalls);
+ }
+}
+
+//------------------------------------------------------------------------
+class Doku_Handler_Section {
+
+ function process($calls) {
+
+ $sectionCalls = array();
+ $inSection = FALSE;
+
+ foreach ( $calls as $call ) {
+
+ if ( $call[0] == 'header' ) {
+
+ if ( $inSection ) {
+ $sectionCalls[] = array('section_close',array(), $call[2]);
+ }
+
+ $sectionCalls[] = $call;
+ $sectionCalls[] = array('section_open',array($call[1][1]), $call[2]);
+ $inSection = TRUE;
+
+ } else {
+
+ if ($call[0] == 'section_open' ) {
+ $inSection = TRUE;
+ } else if ($call[0] == 'section_open' ) {
+ $inSection = FALSE;
+ }
+ $sectionCalls[] = $call;
+ }
+ }
+
+ if ( $inSection ) {
+ $sectionCalls[] = array('section_close',array(), $call[2]);
+ }
+
+ return $sectionCalls;
+ }
+
+}
+
+/**
+ * Handler for paragraphs
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+class Doku_Handler_Block {
+
+ var $calls = array();
+
+ var $blockStack = array();
+
+ var $inParagraph = FALSE;
+ var $atStart = TRUE;
+ var $skipEolKey = -1;
+
+ // Blocks these should not be inside paragraphs
+ var $blockOpen = array(
+ 'header',
+ 'listu_open','listo_open','listitem_open','listcontent_open',
+ 'table_open','tablerow_open','tablecell_open','tableheader_open',
+ 'quote_open',
+ 'section_open', // Needed to prevent p_open between header and section_open
+ 'code','file','hr','preformatted','rss',
+ );
+
+ var $blockClose = array(
+ 'header',
+ 'listu_close','listo_close','listitem_close','listcontent_close',
+ 'table_close','tablerow_close','tablecell_close','tableheader_close',
+ 'quote_close',
+ 'section_close', // Needed to prevent p_close after section_close
+ 'code','file','hr','preformatted','rss',
+ );
+
+ // Stacks can contain paragraphs
+ var $stackOpen = array(
+ 'footnote_open','section_open',
+ );
+
+ var $stackClose = array(
+ 'footnote_close','section_close',
+ );
+
+
+ /**
+ * Constructor. Adds loaded syntax plugins to the block and stack
+ * arrays
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function __construct(){
+ global $DOKU_PLUGINS;
+ //check if syntax plugins were loaded
+ if(empty($DOKU_PLUGINS['syntax'])) return;
+ foreach($DOKU_PLUGINS['syntax'] as $n => $p){
+ $ptype = $p->getPType();
+ if($ptype == 'block'){
+ $this->blockOpen[] = 'plugin_'.$n;
+ $this->blockClose[] = 'plugin_'.$n;
+ }elseif($ptype == 'stack'){
+ $this->stackOpen[] = 'plugin_'.$n;
+ $this->stackClose[] = 'plugin_'.$n;
+ }
+ }
+ }
+
+ /**
+ * Close a paragraph if needed
+ *
+ * This function makes sure there are no empty paragraphs on the stack
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function closeParagraph($pos){
+ // look back if there was any content - we don't want empty paragraphs
+ $content = '';
+ for($i=count($this->calls)-1; $i>=0; $i--){
+ if($this->calls[$i][0] == 'p_open'){
+ break;
+ }elseif($this->calls[$i][0] == 'cdata'){
+ $content .= $this->calls[$i][1][0];
+ }else{
+ $content = 'found markup';
+ break;
+ }
+ }
+
+ if(trim($content)==''){
+ //remove the whole paragraph
+ array_splice($this->calls,$i);
+ }else{
+ if ($this->calls[count($this->calls)-1][0] == 'section_edit') {
+ $tmp = array_pop($this->calls);
+ $this->calls[] = array('p_close',array(), $pos);
+ $this->calls[] = $tmp;
+ } else {
+ $this->calls[] = array('p_close',array(), $pos);
+ }
+ }
+
+ $this->inParagraph = FALSE;
+ }
+
+ /**
+ * Processes the whole instruction stack to open and close paragraphs
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @todo This thing is really messy and should be rewritten
+ */
+ function process($calls) {
+ foreach ( $calls as $key => $call ) {
+ $cname = $call[0];
+ if($cname == 'plugin') {
+ $cname='plugin_'.$call[1][0];
+
+ $plugin = true;
+ $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL));
+ $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL));
+ } else {
+ $plugin = false;
+ }
+
+ // Process blocks which are stack like... (contain linefeeds)
+ if ( in_array($cname,$this->stackOpen ) && (!$plugin || $plugin_open) ) {
+
+ $this->calls[] = $call;
+
+ // Hack - footnotes shouldn't immediately contain a p_open
+ if ( $cname != 'footnote_open' ) {
+ $this->addToStack();
+ } else {
+ $this->addToStack(FALSE);
+ }
+ continue;
+ }
+
+ if ( in_array($cname,$this->stackClose ) && (!$plugin || $plugin_close)) {
+
+ if ( $this->inParagraph ) {
+ $this->closeParagraph($call[2]);
+ }
+ $this->calls[] = $call;
+ $this->removeFromStack();
+ continue;
+ }
+
+ if ( !$this->atStart ) {
+
+ if ( $cname == 'eol' ) {
+
+ // Check this isn't an eol instruction to skip...
+ if ( $this->skipEolKey != $key ) {
+ // Look to see if the next instruction is an EOL
+ if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) {
+
+ if ( $this->inParagraph ) {
+ //$this->calls[] = array('p_close',array(), $call[2]);
+ $this->closeParagraph($call[2]);
+ }
+
+ $this->calls[] = array('p_open',array(), $call[2]);
+ $this->inParagraph = TRUE;
+
+
+ // Mark the next instruction for skipping
+ $this->skipEolKey = $key+1;
+
+ }else{
+ //if this is just a single eol make a space from it
+ $this->calls[] = array('cdata',array(" "), $call[2]);
+ }
+ }
+
+
+ } else {
+
+ $storeCall = TRUE;
+ if ( $this->inParagraph && (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open))) {
+ $this->closeParagraph($call[2]);
+ $this->calls[] = $call;
+ $storeCall = FALSE;
+ }
+
+ if ( in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) {
+ if ( $this->inParagraph ) {
+ $this->closeParagraph($call[2]);
+ }
+ if ( $storeCall ) {
+ $this->calls[] = $call;
+ $storeCall = FALSE;
+ }
+
+ // This really sucks and suggests this whole class sucks but...
+ if ( isset($calls[$key+1])) {
+ $cname_plusone = $calls[$key+1][0];
+ if ($cname_plusone == 'plugin') {
+ $cname_plusone = 'plugin'.$calls[$key+1][1][0];
+
+ // plugin test, true if plugin has a state which precludes it requiring blockOpen or blockClose
+ $plugin_plusone = true;
+ $plugin_test = ($call[$key+1][1][2] == DOKU_LEXER_MATCHED) || ($call[$key+1][1][2] == DOKU_LEXER_MATCHED);
+ } else {
+ $plugin_plusone = false;
+ }
+ if ((!in_array($cname_plusone, $this->blockOpen) && !in_array($cname_plusone, $this->blockClose)) ||
+ ($plugin_plusone && $plugin_test)
+ ) {
+
+ $this->calls[] = array('p_open',array(), $call[2]);
+ $this->inParagraph = TRUE;
+ }
+ }
+ }
+
+ if ( $storeCall ) {
+ $this->calls[] = $call;
+ }
+
+ }
+
+
+ } else {
+
+ // Unless there's already a block at the start, start a paragraph
+ if ( !in_array($cname,$this->blockOpen) ) {
+ $this->calls[] = array('p_open',array(), $call[2]);
+ if ( $call[0] != 'eol' ) {
+ $this->calls[] = $call;
+ }
+ $this->atStart = FALSE;
+ $this->inParagraph = TRUE;
+ } else {
+ $this->calls[] = $call;
+ $this->atStart = FALSE;
+ }
+
+ }
+
+ }
+
+ if ( $this->inParagraph ) {
+ if ( $cname == 'p_open' ) {
+ // Ditch the last call
+ array_pop($this->calls);
+ } else if ( !in_array($cname, $this->blockClose) ) {
+ //$this->calls[] = array('p_close',array(), $call[2]);
+ $this->closeParagraph($call[2]);
+ } else {
+ $last_call = array_pop($this->calls);
+ //$this->calls[] = array('p_close',array(), $call[2]);
+ $this->closeParagraph($call[2]);
+ $this->calls[] = $last_call;
+ }
+ }
+
+ return $this->calls;
+ }
+
+ function addToStack($newStart = TRUE) {
+ $this->blockStack[] = array($this->atStart, $this->inParagraph);
+ $this->atStart = $newStart;
+ $this->inParagraph = FALSE;
+ }
+
+ function removeFromStack() {
+ $state = array_pop($this->blockStack);
+ $this->atStart = $state[0];
+ $this->inParagraph = $state[1];
+ }
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parser/lexer.php b/plugins/dokuwiki/inc/parser/lexer.php
new file mode 100644
index 0000000..249048f
--- /dev/null
+++ b/plugins/dokuwiki/inc/parser/lexer.php
@@ -0,0 +1,607 @@
+<?php
+/**
+* Author Markus Baker: http://www.lastcraft.com
+* Version adapted from Simple Test: http://sourceforge.net/projects/simpletest/
+* For an intro to the Lexer see:
+* http://www.phppatterns.com/index.php/article/articleview/106/1/2/
+* @author Marcus Baker
+* @package Doku
+* @subpackage Lexer
+* @version $Id$
+*/
+
+/**
+* Init path constant
+*/
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+
+/**#@+
+ * lexer mode constant
+ */
+define("DOKU_LEXER_ENTER", 1);
+define("DOKU_LEXER_MATCHED", 2);
+define("DOKU_LEXER_UNMATCHED", 3);
+define("DOKU_LEXER_EXIT", 4);
+define("DOKU_LEXER_SPECIAL", 5);
+/**#@-*/
+
+/**
+ * Compounded regular expression. Any of
+ * the contained patterns could match and
+ * when one does it's label is returned.
+ * @package Doku
+ * @subpackage Lexer
+ */
+class Doku_LexerParallelRegex {
+ var $_patterns;
+ var $_labels;
+ var $_regex;
+ var $_case;
+
+ /**
+ * Constructor. Starts with no patterns.
+ * @param boolean $case True for case sensitive, false
+ * for insensitive.
+ * @access public
+ */
+ function __construct($case) {
+ $this->_case = $case;
+ $this->_patterns = array();
+ $this->_labels = array();
+ $this->_regex = null;
+ }
+
+ /**
+ * Adds a pattern with an optional label.
+ * @param mixed $pattern Perl style regex. Must be UTF-8
+ * encoded. If its a string, the (, )
+ * lose their meaning unless they
+ * form part of a lookahead or
+ * lookbehind assertation.
+ * @param string $label Label of regex to be returned
+ * on a match. Label must be ASCII
+ * @access public
+ */
+ function addPattern($pattern, $label = true) {
+ $duplicate = false;
+ // Nux: check if not duplicate
+ // Note! This prevents regexp overflow when viewing Flyspray issues with many comments
+ if (in_array($pattern, $this->_patterns)) {
+ $index = array_search($pattern, $this->_patterns, true);
+ // label also have to be the same
+ if (isset($this->_labels[$index]) && $this->_labels[$index] === $label) {
+ $duplicate = true;
+ }
+ }
+ // only add new pattern if not duplicate...
+ if (!$duplicate) {
+ $count = count($this->_patterns);
+ $this->_patterns[$count] = $pattern;
+ $this->_labels[$count] = $label;
+ }
+ $this->_regex = null;
+ }
+
+ /**
+ * Attempts to match all patterns at once against
+ * a string.
+ * @param string $subject String to match against.
+ * @param string $match First matched portion of
+ * subject.
+ * @return boolean True on success.
+ * @access public
+ */
+ function match($subject, &$match) {
+ if (count($this->_patterns) == 0) {
+ return false;
+ }
+ if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
+ $match = "";
+ return false;
+ }
+
+ $match = $matches[0];
+ $size = count($matches);
+ for ($i = 1; $i < $size; $i++) {
+ if ($matches[$i] && isset($this->_labels[$i - 1])) {
+ return $this->_labels[$i - 1];
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Attempts to split the string against all patterns at once
+ *
+ * @param string $subject String to match against.
+ * @param array $split The split result: array containing, pre-match, match & post-match strings
+ * @return boolean True on success.
+ * @access public
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+ function explode($subject, &$split) {
+ if (count($this->_patterns) == 0) {
+ return false;
+ }
+
+ if (! preg_match($this->_getCompoundedRegex(), $subject, $matches)) {
+ $split = array($subject, "", "");
+ return false;
+ }
+
+ $idx = count($matches)-2;
+
+ list($pre, $post) = preg_split($this->_patterns[$idx].$this->_getPerlMatchingFlags(), $subject, 2);
+
+ $split = array($pre, $matches[0], $post);
+ return isset($this->_labels[$idx]) ? $this->_labels[$idx] : true;
+ }
+
+ /**
+ * Compounds the patterns into a single
+ * regular expression separated with the
+ * "or" operator. Caches the regex.
+ * Will automatically escape (, ) and / tokens.
+ * @param array $patterns List of patterns in order.
+ * @access private
+ */
+ function _getCompoundedRegex() {
+ if ($this->_regex == null) {
+ $cnt = count($this->_patterns);
+ for ($i = 0; $i < $cnt; $i++) {
+
+ // Replace lookaheads / lookbehinds with marker
+ $m = "\1\1";
+ $pattern = preg_replace(
+ array (
+ '/\(\?(i|m|s|x|U)\)/U',
+ '/\(\?(\-[i|m|s|x|U])\)/U',
+ '/\(\?\=(.*)\)/sU',
+ '/\(\?\!(.*)\)/sU',
+ '/\(\?\<\=(.*)\)/sU',
+ '/\(\?\<\!(.*)\)/sU',
+ '/\(\?\:(.*)\)/sU',
+ ),
+ array (
+ $m.'SO:\\1'.$m,
+ $m.'SOR:\\1'.$m,
+ $m.'LA:IS:\\1'.$m,
+ $m.'LA:NOT:\\1'.$m,
+ $m.'LB:IS:\\1'.$m,
+ $m.'LB:NOT:\\1'.$m,
+ $m.'GRP:\\1'.$m,
+ ),
+ $this->_patterns[$i]
+ );
+ // Quote the rest
+ $pattern = str_replace(
+ array('/', '(', ')'),
+ array('\/', '\(', '\)'),
+ $pattern
+ );
+
+ // Restore lookaheads / lookbehinds
+ $pattern = preg_replace(
+ array (
+ '/'.$m.'SO:(.{1})'.$m.'/',
+ '/'.$m.'SOR:(.{2})'.$m.'/',
+ '/'.$m.'LA:IS:(.*)'.$m.'/sU',
+ '/'.$m.'LA:NOT:(.*)'.$m.'/sU',
+ '/'.$m.'LB:IS:(.*)'.$m.'/sU',
+ '/'.$m.'LB:NOT:(.*)'.$m.'/sU',
+ '/'.$m.'GRP:(.*)'.$m.'/sU',
+ ),
+ array (
+ '(?\\1)',
+ '(?\\1)',
+ '(?=\\1)',
+ '(?!\\1)',
+ '(?<=\\1)',
+ '(?<!\\1)',
+ '(?:\\1)',
+ ),
+ $pattern
+ );
+
+ $this->_patterns[$i] = '('.$pattern.')';
+ }
+ $this->_regex = "/" . implode("|", $this->_patterns) . "/" . $this->_getPerlMatchingFlags();
+ }
+ return $this->_regex;
+ }
+
+ /**
+ * Accessor for perl regex mode flags to use.
+ * @return string Perl regex flags.
+ * @access private
+ */
+ function _getPerlMatchingFlags() {
+ return ($this->_case ? "msS" : "msSi");
+ }
+}
+
+/**
+ * States for a stack machine.
+ * @package Lexer
+ * @subpackage Lexer
+ */
+class Doku_LexerStateStack {
+ var $_stack;
+
+ /**
+ * Constructor. Starts in named state.
+ * @param string $start Starting state name.
+ * @access public
+ */
+ function __construct($start) {
+ $this->_stack = array($start);
+ }
+
+ /**
+ * Accessor for current state.
+ * @return string State.
+ * @access public
+ */
+ function getCurrent() {
+ return $this->_stack[count($this->_stack) - 1];
+ }
+
+ /**
+ * Adds a state to the stack and sets it
+ * to be the current state.
+ * @param string $state New state.
+ * @access public
+ */
+ function enter($state) {
+ array_push($this->_stack, $state);
+ }
+
+ /**
+ * Leaves the current state and reverts
+ * to the previous one.
+ * @return boolean False if we drop off
+ * the bottom of the list.
+ * @access public
+ */
+ function leave() {
+ if (count($this->_stack) == 1) {
+ return false;
+ }
+ array_pop($this->_stack);
+ return true;
+ }
+}
+
+/**
+ * Accepts text and breaks it into tokens.
+ * Some optimisation to make the sure the
+ * content is only scanned by the PHP regex
+ * parser once. Lexer modes must not start
+ * with leading underscores.
+ * @package Doku
+ * @subpackage Lexer
+ */
+class Doku_Lexer {
+ var $_regexes;
+ var $_parser;
+ var $_mode;
+ var $_mode_handlers;
+ var $_case;
+
+ /**
+ * Sets up the lexer in case insensitive matching
+ * by default.
+ * @param Doku_Parser $parser Handling strategy by
+ * reference.
+ * @param string $start Starting handler.
+ * @param boolean $case True for case sensitive.
+ * @access public
+ */
+ function __construct(&$parser, $start = "accept", $case = false) {
+ $this->_case = $case;
+ $this->_regexes = array();
+ $this->_parser = &$parser;
+ $this->_mode = new Doku_LexerStateStack($start);
+ $this->_mode_handlers = array();
+ }
+
+ /**
+ * Adds a token search pattern for a particular
+ * parsing mode. The pattern does not change the
+ * current mode.
+ * @param string $pattern Perl style regex, but ( and )
+ * lose the usual meaning.
+ * @param string $mode Should only apply this
+ * pattern when dealing with
+ * this type of input.
+ * @access public
+ */
+ function addPattern($pattern, $mode = "accept") {
+ if (! isset($this->_regexes[$mode])) {
+ $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case);
+ }
+ $this->_regexes[$mode]->addPattern($pattern);
+ }
+
+ /**
+ * Adds a pattern that will enter a new parsing
+ * mode. Useful for entering parenthesis, strings,
+ * tags, etc.
+ * @param string $pattern Perl style regex, but ( and )
+ * lose the usual meaning.
+ * @param string $mode Should only apply this
+ * pattern when dealing with
+ * this type of input.
+ * @param string $new_mode Change parsing to this new
+ * nested mode.
+ * @access public
+ */
+ function addEntryPattern($pattern, $mode, $new_mode) {
+ if (! isset($this->_regexes[$mode])) {
+ $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case);
+ }
+ $this->_regexes[$mode]->addPattern($pattern, $new_mode);
+ }
+
+ /**
+ * Adds a pattern that will exit the current mode
+ * and re-enter the previous one.
+ * @param string $pattern Perl style regex, but ( and )
+ * lose the usual meaning.
+ * @param string $mode Mode to leave.
+ * @access public
+ */
+ function addExitPattern($pattern, $mode) {
+ if (! isset($this->_regexes[$mode])) {
+ $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case);
+ }
+ $this->_regexes[$mode]->addPattern($pattern, "__exit");
+ }
+
+ /**
+ * Adds a pattern that has a special mode. Acts as an entry
+ * and exit pattern in one go, effectively calling a special
+ * parser handler for this token only.
+ * @param string $pattern Perl style regex, but ( and )
+ * lose the usual meaning.
+ * @param string $mode Should only apply this
+ * pattern when dealing with
+ * this type of input.
+ * @param string $special Use this mode for this one token.
+ * @access public
+ */
+ function addSpecialPattern($pattern, $mode, $special) {
+ if (! isset($this->_regexes[$mode])) {
+ $this->_regexes[$mode] = new Doku_LexerParallelRegex($this->_case);
+ }
+ $this->_regexes[$mode]->addPattern($pattern, "_$special");
+ }
+
+ /**
+ * Adds a mapping from a mode to another handler.
+ * @param string $mode Mode to be remapped.
+ * @param string $handler New target handler.
+ * @access public
+ */
+ function mapHandler($mode, $handler) {
+ $this->_mode_handlers[$mode] = $handler;
+ }
+
+ /**
+ * Splits the page text into tokens. Will fail
+ * if the handlers report an error or if no
+ * content is consumed. If successful then each
+ * unparsed and parsed token invokes a call to the
+ * held listener.
+ * @param string $raw Raw HTML text.
+ * @return boolean True on success, else false.
+ * @access public
+ */
+ function parse($raw) {
+ if (! isset($this->_parser)) {
+ return false;
+ }
+ $initialLength = strlen($raw);
+ $length = $initialLength;
+ $pos = 0;
+ while (is_array($parsed = $this->_reduce($raw))) {
+ list($unmatched, $matched, $mode) = $parsed;
+ $currentLength = strlen($raw);
+ $matchPos = $initialLength - $currentLength - strlen($matched);
+ if (! $this->_dispatchTokens($unmatched, $matched, $mode, $pos, $matchPos)) {
+ return false;
+ }
+ if ($currentLength == $length) {
+ return false;
+ }
+ $length = $currentLength;
+ $pos = $initialLength - $currentLength;
+ }
+ if (!$parsed) {
+ return false;
+ }
+ return $this->_invokeParser($raw, DOKU_LEXER_UNMATCHED, $pos);
+ }
+
+ /**
+ * Sends the matched token and any leading unmatched
+ * text to the parser changing the lexer to a new
+ * mode if one is listed.
+ * @param string $unmatched Unmatched leading portion.
+ * @param string $matched Actual token match.
+ * @param string $mode Mode after match. A boolean
+ * false mode causes no change.
+ * @param int $pos Current byte index location in raw doc
+ * thats being parsed
+ * @return boolean False if there was any error
+ * from the parser.
+ * @access private
+ */
+ function _dispatchTokens($unmatched, $matched, $mode = false, $initialPos, $matchPos) {
+ if (! $this->_invokeParser($unmatched, DOKU_LEXER_UNMATCHED, $initialPos) ){
+ return false;
+ }
+ if ($this->_isModeEnd($mode)) {
+ if (! $this->_invokeParser($matched, DOKU_LEXER_EXIT, $matchPos)) {
+ return false;
+ }
+ return $this->_mode->leave();
+ }
+ if ($this->_isSpecialMode($mode)) {
+ $this->_mode->enter($this->_decodeSpecial($mode));
+ if (! $this->_invokeParser($matched, DOKU_LEXER_SPECIAL, $matchPos)) {
+ return false;
+ }
+ return $this->_mode->leave();
+ }
+ if (is_string($mode)) {
+ $this->_mode->enter($mode);
+ return $this->_invokeParser($matched, DOKU_LEXER_ENTER, $matchPos);
+ }
+ return $this->_invokeParser($matched, DOKU_LEXER_MATCHED, $matchPos);
+ }
+
+ /**
+ * Tests to see if the new mode is actually to leave
+ * the current mode and pop an item from the matching
+ * mode stack.
+ * @param string $mode Mode to test.
+ * @return boolean True if this is the exit mode.
+ * @access private
+ */
+ function _isModeEnd($mode) {
+ return ($mode === "__exit");
+ }
+
+ /**
+ * Test to see if the mode is one where this mode
+ * is entered for this token only and automatically
+ * leaves immediately afterwoods.
+ * @param string $mode Mode to test.
+ * @return boolean True if this is the exit mode.
+ * @access private
+ */
+ function _isSpecialMode($mode) {
+ return (strncmp($mode, "_", 1) == 0);
+ }
+
+ /**
+ * Strips the magic underscore marking single token
+ * modes.
+ * @param string $mode Mode to decode.
+ * @return string Underlying mode name.
+ * @access private
+ */
+ function _decodeSpecial($mode) {
+ return substr($mode, 1);
+ }
+
+ /**
+ * Calls the parser method named after the current
+ * mode. Empty content will be ignored. The lexer
+ * has a parser handler for each mode in the lexer.
+ * @param string $content Text parsed.
+ * @param boolean $is_match Token is recognised rather
+ * than unparsed data.
+ * @param int $pos Current byte index location in raw doc
+ * thats being parsed
+ * @access private
+ */
+ function _invokeParser($content, $is_match, $pos) {
+ if (($content === "") || ($content === false)) {
+ return true;
+ }
+ $handler = $this->_mode->getCurrent();
+ if (isset($this->_mode_handlers[$handler])) {
+ $handler = $this->_mode_handlers[$handler];
+ }
+
+ // modes starting with plugin_ are all handled by the same
+ // handler but with an additional parameter
+ if(substr($handler,0,7)=='plugin_'){
+ list($handler,$plugin) = explode('_',$handler,2);
+ return $this->_parser->$handler($content, $is_match, $pos, $plugin);
+ }
+
+ return $this->_parser->$handler($content, $is_match, $pos);
+ }
+
+ /**
+ * Tries to match a chunk of text and if successful
+ * removes the recognised chunk and any leading
+ * unparsed data. Empty strings will not be matched.
+ * @param string $raw The subject to parse. This is the
+ * content that will be eaten.
+ * @return array Three item list of unparsed
+ * content followed by the
+ * recognised token and finally the
+ * action the parser is to take.
+ * True if no match, false if there
+ * is a parsing error.
+ * @access private
+ */
+ function _reduce(&$raw) {
+ if (! isset($this->_regexes[$this->_mode->getCurrent()])) {
+ return false;
+ }
+ if ($raw === "") {
+ return true;
+ }
+ if ($action = $this->_regexes[$this->_mode->getCurrent()]->explode($raw, $split)) {
+ list($unparsed, $match, $raw) = $split;
+ return array($unparsed, $match, $action);
+ }
+ return true;
+ }
+}
+
+/**
+* Escapes regex characters other than (, ) and /
+* @TODO
+*/
+function Doku_Lexer_Escape($str) {
+ //$str = addslashes($str);
+ $chars = array(
+ '/\\\\/',
+ '/\./',
+ '/\+/',
+ '/\*/',
+ '/\?/',
+ '/\[/',
+ '/\^/',
+ '/\]/',
+ '/\$/',
+ '/\{/',
+ '/\}/',
+ '/\=/',
+ '/\!/',
+ '/\</',
+ '/\>/',
+ '/\|/',
+ '/\:/'
+ );
+
+ $escaped = array(
+ '\\\\\\\\',
+ '\.',
+ '\+',
+ '\*',
+ '\?',
+ '\[',
+ '\^',
+ '\]',
+ '\$',
+ '\{',
+ '\}',
+ '\=',
+ '\!',
+ '\<',
+ '\>',
+ '\|',
+ '\:'
+ );
+ return preg_replace($chars, $escaped, $str);
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parser/parser.php b/plugins/dokuwiki/inc/parser/parser.php
new file mode 100644
index 0000000..b77af88
--- /dev/null
+++ b/plugins/dokuwiki/inc/parser/parser.php
@@ -0,0 +1,932 @@
+<?php
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+
+require_once DOKU_INC . 'inc/parser/lexer.php';
+require_once DOKU_INC . 'inc/parser/handler.php';
+
+
+/**
+ * Define various types of modes used by the parser - they are used to
+ * populate the list of modes another mode accepts
+ */
+global $PARSER_MODES;
+$PARSER_MODES = array(
+ // containers are complex modes that can contain many other modes
+ // hr breaks the principle but they shouldn't be used in tables / lists
+ // so they are put here
+ 'container' => array('listblock','table','quote','hr'),
+
+ // some mode are allowed inside the base mode only
+ 'baseonly' => array('header'),
+
+ // modes for styling text -- footnote behaves similar to styling
+ 'formatting' => array('strong', 'emphasis', 'underline', 'monospace',
+ 'subscript', 'superscript', 'deleted', 'footnote'),
+
+ // modes where the token is simply replaced - they can not contain any
+ // other modes
+ 'substition' => array('acronym','smiley','wordblock','entity',
+ 'camelcaselink', 'internallink','media',
+ 'externallink','linebreak','emaillink',
+ 'windowssharelink','filelink','notoc',
+ 'nocache','multiplyentity','quotes','rss'),
+
+ // modes which have a start and end token but inside which
+ // no other modes should be applied
+ 'protected' => array('preformatted','code','file','php','html'),
+
+ // inside this mode no wiki markup should be applied but lineendings
+ // and whitespace isn't preserved
+ 'disabled' => array('unformatted'),
+
+ // used to mark paragraph boundaries
+ 'paragraphs' => array('eol')
+);
+
+//-------------------------------------------------------------------
+
+/**
+* Sets up the Lexer with modes and points it to the Handler
+* For an intro to the Lexer see: wiki:parser
+*/
+class Doku_Parser {
+
+ var $Handler;
+
+ var $Lexer;
+
+ var $modes = array();
+
+ var $connected = FALSE;
+
+ function addBaseMode(& $BaseMode) {
+ $this->modes['base'] = & $BaseMode;
+ if ( !$this->Lexer ) {
+ $this->Lexer = new Doku_Lexer($this->Handler,'base', TRUE);
+ }
+ $this->modes['base']->Lexer = & $this->Lexer;
+ }
+
+ /**
+ * PHP preserves order of associative elements
+ * Mode sequence is important
+ */
+ function addMode($name, & $Mode) {
+ if ( !isset($this->modes['base']) ) {
+ $this->addBaseMode(new Doku_Parser_Mode_base());
+ }
+ $Mode->Lexer = & $this->Lexer;
+ $this->modes[$name] = & $Mode;
+ }
+
+ function connectModes() {
+
+ if ( $this->connected ) {
+ return;
+ }
+
+ foreach ( array_keys($this->modes) as $mode ) {
+
+ // Base isn't connected to anything
+ if ( $mode == 'base' ) {
+ continue;
+ }
+
+ $this->modes[$mode]->preConnect();
+
+ foreach ( array_keys($this->modes) as $cm ) {
+
+ if ( $this->modes[$cm]->accepts($mode) ) {
+ $this->modes[$mode]->connectTo($cm);
+ }
+
+ }
+
+ $this->modes[$mode]->postConnect();
+ }
+
+ $this->connected = TRUE;
+ }
+
+ function parse($doc) {
+ if ( $this->Lexer ) {
+ $this->connectModes();
+ // Normalize CRs and pad doc
+ $doc = "\n".str_replace("\r\n","\n",$doc)."\n";
+ $this->Lexer->parse($doc);
+ $this->Handler->_finalize();
+ return $this->Handler->calls;
+ } else {
+ return FALSE;
+ }
+ }
+
+}
+
+//-------------------------------------------------------------------
+/**
+ * This class and all the subclasses below are
+ * used to reduce the effort required to register
+ * modes with the Lexer. For performance these
+ * could all be eliminated later perhaps, or
+ * the Parser could be serialized to a file once
+ * all modes are registered
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+*/
+class Doku_Parser_Mode {
+
+ var $Lexer;
+
+ var $allowedModes = array();
+
+ // returns a number used to determine in which order modes are added
+ function getSort() {
+ trigger_error('getSort() not implemented in '.get_class($this), E_USER_WARNING);
+ }
+
+ // Called before any calls to connectTo
+ function preConnect() {}
+
+ // Connects the mode
+ function connectTo($mode) {}
+
+ // Called after all calls to connectTo
+ function postConnect() {}
+
+ function accepts($mode) {
+ return in_array($mode, $this->allowedModes );
+ }
+
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_base extends Doku_Parser_Mode {
+
+ function __construct() {
+ global $PARSER_MODES;
+
+ $this->allowedModes = array_merge (
+ $PARSER_MODES['container'],
+ $PARSER_MODES['baseonly'],
+ $PARSER_MODES['paragraphs'],
+ $PARSER_MODES['formatting'],
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['protected'],
+ $PARSER_MODES['disabled']
+ );
+ }
+
+ function getSort() {
+ return 0;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_footnote extends Doku_Parser_Mode {
+
+ function __construct() {
+ global $PARSER_MODES;
+
+ $this->allowedModes = array_merge (
+ $PARSER_MODES['container'],
+ $PARSER_MODES['formatting'],
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['protected'],
+ $PARSER_MODES['disabled']
+ );
+
+ unset($this->allowedModes[array_search('footnote', $this->allowedModes)]);
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern(
+ '\x28\x28(?=.*\x29\x29)',$mode,'footnote'
+ );
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern(
+ '\x29\x29','footnote'
+ );
+ }
+
+ function getSort() {
+ return 150;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_header extends Doku_Parser_Mode {
+
+ function preConnect() {
+ //we're not picky about the closing ones, two are enough
+ $this->Lexer->addSpecialPattern(
+ '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)',
+ 'base',
+ 'header'
+ );
+ }
+
+ function getSort() {
+ return 50;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_notoc extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('~~NOTOC~~',$mode,'notoc');
+ }
+
+ function getSort() {
+ return 30;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_nocache extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('~~NOCACHE~~',$mode,'nocache');
+ }
+
+ function getSort() {
+ return 40;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_linebreak extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('\x5C{2}(?=\s)',$mode,'linebreak');
+ }
+
+ function getSort() {
+ return 140;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_eol extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $badModes = array('listblock','table');
+ if ( in_array($mode, $badModes) ) {
+ return;
+ }
+ $this->Lexer->addSpecialPattern('\n',$mode,'eol');
+ }
+
+ function getSort() {
+ return 370;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_hr extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)',$mode,'hr');
+ }
+
+ function getSort() {
+ return 160;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_formatting extends Doku_Parser_Mode {
+ var $type;
+
+ var $formatting = array (
+ 'strong' => array (
+ 'entry'=>'\*\*(?=.*\*\*)',
+ 'exit'=>'\*\*',
+ 'sort'=>70
+ ),
+
+ 'emphasis'=> array (
+ 'entry'=>'//(?=[^\x00]*[^:]//)', //hack for bug #384
+ 'exit'=>'//',
+ 'sort'=>80
+ ),
+
+ 'underline'=> array (
+ 'entry'=>'__(?=.*__)',
+ 'exit'=>'__',
+ 'sort'=>90
+ ),
+
+ 'monospace'=> array (
+ 'entry'=>'\x27\x27(?=.*\x27\x27)',
+ 'exit'=>'\x27\x27',
+ 'sort'=>100
+ ),
+
+ 'subscript'=> array (
+ 'entry'=>'<sub>(?=.*</sub>)',
+ 'exit'=>'</sub>',
+ 'sort'=>110
+ ),
+
+ 'superscript'=> array (
+ 'entry'=>'<sup>(?=.*</sup>)',
+ 'exit'=>'</sup>',
+ 'sort'=>120
+ ),
+
+ 'deleted'=> array (
+ 'entry'=>'<del>(?=.*</del>)',
+ 'exit'=>'</del>',
+ 'sort'=>130
+ ),
+ );
+
+ function __construct($type) {
+ global $PARSER_MODES;
+
+ if ( !array_key_exists($type, $this->formatting) ) {
+ trigger_error('Invalid formatting type '.$type, E_USER_WARNING);
+ }
+
+ $this->type = $type;
+
+ // formatting may contain other formatting but not it self
+ $modes = $PARSER_MODES['formatting'];
+ $key = array_search($type, $modes);
+ if ( is_int($key) ) {
+ unset($modes[$key]);
+ }
+
+ $this->allowedModes = array_merge (
+ $modes,
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['disabled']
+ );
+ }
+
+ function connectTo($mode) {
+
+ // Can't nest formatting in itself
+ if ( $mode == $this->type ) {
+ return;
+ }
+
+ $this->Lexer->addEntryPattern(
+ $this->formatting[$this->type]['entry'],
+ $mode,
+ $this->type
+ );
+ }
+
+ function postConnect() {
+
+ $this->Lexer->addExitPattern(
+ $this->formatting[$this->type]['exit'],
+ $this->type
+ );
+
+ }
+
+ function getSort() {
+ return $this->formatting[$this->type]['sort'];
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_listblock extends Doku_Parser_Mode {
+
+ function __construct() {
+ global $PARSER_MODES;
+
+ $this->allowedModes = array_merge (
+ $PARSER_MODES['formatting'],
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['disabled'],
+ $PARSER_MODES['protected'] #XXX new
+ );
+
+ // $this->allowedModes[] = 'footnote';
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('\n {2,}[\-\*]',$mode,'listblock');
+ $this->Lexer->addEntryPattern('\n\t{1,}[\-\*]',$mode,'listblock');
+
+ $this->Lexer->addPattern('\n {2,}[\-\*]','listblock');
+ $this->Lexer->addPattern('\n\t{1,}[\-\*]','listblock');
+
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('\n','listblock');
+ }
+
+ function getSort() {
+ return 10;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_table extends Doku_Parser_Mode {
+
+ function __construct() {
+ global $PARSER_MODES;
+
+ $this->allowedModes = array_merge (
+ $PARSER_MODES['formatting'],
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['disabled'],
+ $PARSER_MODES['protected']
+ );
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('\n\^',$mode,'table');
+ $this->Lexer->addEntryPattern('\n\|',$mode,'table');
+ }
+
+ function postConnect() {
+ $this->Lexer->addPattern('\n\^','table');
+ $this->Lexer->addPattern('\n\|','table');
+ #$this->Lexer->addPattern(' {2,}','table');
+ $this->Lexer->addPattern('[\t ]+','table');
+ $this->Lexer->addPattern('\^','table');
+ $this->Lexer->addPattern('\|','table');
+ $this->Lexer->addExitPattern('\n','table');
+ }
+
+ function getSort() {
+ return 60;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_unformatted extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)',$mode,'unformatted');
+ $this->Lexer->addEntryPattern('%%(?=.*%%)',$mode,'unformattedalt');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('</nowiki>','unformatted');
+ $this->Lexer->addExitPattern('%%','unformattedalt');
+ $this->Lexer->mapHandler('unformattedalt','unformatted');
+ }
+
+ function getSort() {
+ return 170;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_php extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('<php>(?=.*</php>)',$mode,'php');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('</php>','php');
+ }
+
+ function getSort() {
+ return 180;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_html extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('<html>(?=.*</html>)',$mode,'html');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('</html>','html');
+ }
+
+ function getSort() {
+ return 190;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_preformatted extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ // Has hard coded awareness of lists...
+ $this->Lexer->addEntryPattern('\n (?![\*\-])',$mode,'preformatted');
+ $this->Lexer->addEntryPattern('\n\t(?![\*\-])',$mode,'preformatted');
+
+ // How to effect a sub pattern with the Lexer!
+ $this->Lexer->addPattern('\n ','preformatted');
+ $this->Lexer->addPattern('\n\t','preformatted');
+
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('\n','preformatted');
+ }
+
+ function getSort() {
+ return 20;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_code extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('<code(?=.*</code>)',$mode,'code');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('</code>','code');
+ }
+
+ function getSort() {
+ return 200;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_file extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('<file>(?=.*</file>)',$mode,'file');
+ }
+
+ function postConnect() {
+ $this->Lexer->addExitPattern('</file>','file');
+ }
+
+ function getSort() {
+ return 210;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_quote extends Doku_Parser_Mode {
+
+ function __construct() {
+ global $PARSER_MODES;
+
+ $this->allowedModes = array_merge (
+ $PARSER_MODES['formatting'],
+ $PARSER_MODES['substition'],
+ $PARSER_MODES['disabled'],
+ $PARSER_MODES['protected'] #XXX new
+ );
+ #$this->allowedModes[] = 'footnote';
+ #$this->allowedModes[] = 'preformatted';
+ #$this->allowedModes[] = 'unformatted';
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addEntryPattern('\n>{1,}',$mode,'quote');
+ }
+
+ function postConnect() {
+ $this->Lexer->addPattern('\n>{1,}','quote');
+ $this->Lexer->addExitPattern('\n','quote');
+ }
+
+ function getSort() {
+ return 220;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_acronym extends Doku_Parser_Mode {
+ // A list
+ var $acronyms = array();
+ var $pattern = '';
+
+ function __construct($acronyms) {
+ $this->acronyms = $acronyms;
+ }
+
+ function preConnect() {
+ if(!count($this->acronyms)) return;
+
+ $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]';
+ $acronyms = array_map('Doku_Lexer_Escape',$this->acronyms);
+ $this->pattern = '(?<=^|'.$bound.')(?:'.join('|',$acronyms).')(?='.$bound.')';
+ }
+
+ function connectTo($mode) {
+ if(!count($this->acronyms)) return;
+
+ if ( strlen($this->pattern) > 0 ) {
+ $this->Lexer->addSpecialPattern($this->pattern,$mode,'acronym');
+ }
+ }
+
+ function getSort() {
+ return 240;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_smiley extends Doku_Parser_Mode {
+ // A list
+ var $smileys = array();
+ var $pattern = '';
+
+ function __construct($smileys) {
+ $this->smileys = $smileys;
+ }
+
+ function preConnect() {
+ if(!count($this->smileys)) return;
+
+ $sep = '';
+ // Nux: fix for potential pattern overflow...
+ $this->pattern = '';
+ foreach ( $this->smileys as $smiley ) {
+ $this->pattern .= $sep.Doku_Lexer_Escape($smiley);
+ $sep = '|';
+ }
+ }
+
+ function connectTo($mode) {
+ if(!count($this->smileys)) return;
+
+ if ( strlen($this->pattern) > 0 ) {
+ $this->Lexer->addSpecialPattern($this->pattern,$mode,'smiley');
+ }
+ }
+
+ function getSort() {
+ return 230;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_wordblock extends Doku_Parser_Mode {
+ // A list
+ var $badwords = array();
+ var $pattern = '';
+
+ function __construct($badwords) {
+ $this->badwords = $badwords;
+ }
+
+ function preConnect() {
+
+ if ( count($this->badwords) == 0 ) {
+ return;
+ }
+
+ $sep = '';
+ // Nux: fix for potential pattern overflow...
+ $this->pattern = '';
+ foreach ( $this->badwords as $badword ) {
+ $this->pattern .= $sep.'(?<=\b)(?i)'.Doku_Lexer_Escape($badword).'(?-i)(?=\b)';
+ $sep = '|';
+ }
+
+ }
+
+ function connectTo($mode) {
+ if ( strlen($this->pattern) > 0 ) {
+ $this->Lexer->addSpecialPattern($this->pattern,$mode,'wordblock');
+ }
+ }
+
+ function getSort() {
+ return 250;
+ }
+}
+
+//-------------------------------------------------------------------
+/**
+* @TODO Quotes and 640x480 are not supported - just straight replacements here
+*/
+class Doku_Parser_Mode_entity extends Doku_Parser_Mode {
+ // A list
+ var $entities = array();
+ var $pattern = '';
+
+ function __construct($entities) {
+ $this->entities = $entities;
+ }
+
+ function preConnect() {
+ if(!count($this->entities)) return;
+
+ $sep = '';
+ // Nux: fix for potential pattern overflow...
+ $this->pattern = '';
+ foreach ( $this->entities as $entity ) {
+ $this->pattern .= $sep.Doku_Lexer_Escape($entity);
+ $sep = '|';
+ }
+ }
+
+ function connectTo($mode) {
+ if(!count($this->entities)) return;
+
+ if ( strlen($this->pattern) > 0 ) {
+ $this->Lexer->addSpecialPattern($this->pattern,$mode,'entity');
+ }
+ }
+
+ function getSort() {
+ return 260;
+ }
+}
+
+//-------------------------------------------------------------------
+// Implements the 640x480 replacement
+class Doku_Parser_Mode_multiplyentity extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+
+ $this->Lexer->addSpecialPattern(
+ '(?<=\b)\d+[xX]\d+(?=\b)',$mode,'multiplyentity'
+ );
+
+ }
+
+ function getSort() {
+ return 270;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_quotes extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+
+ $this->Lexer->addSpecialPattern(
+ '(?<=^|\s)\'(?=\S)',$mode,'singlequoteopening'
+ );
+ $this->Lexer->addSpecialPattern(
+ '(?<=^|\S)\'',$mode,'singlequoteclosing'
+ );
+ $this->Lexer->addSpecialPattern(
+ '(?<=^|\s)"(?=\S)',$mode,'doublequoteopening'
+ );
+ $this->Lexer->addSpecialPattern(
+ '(?<=^|\S)"',$mode,'doublequoteclosing'
+ );
+
+ }
+
+ function getSort() {
+ return 280;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_camelcaselink extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b',$mode,'camelcaselink'
+ );
+ }
+
+ function getSort() {
+ return 290;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_internallink extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ // Word boundaries?
+ $this->Lexer->addSpecialPattern("\[\[.+?\]\]",$mode,'internallink');
+ }
+
+ function getSort() {
+ return 300;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_media extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ // Word boundaries?
+ $this->Lexer->addSpecialPattern("\{\{[^\}]+\}\}",$mode,'media');
+ }
+
+ function getSort() {
+ return 320;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_rss extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}",$mode,'rss');
+ }
+
+ function getSort() {
+ return 310;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_externallink extends Doku_Parser_Mode {
+ var $schemes = array('http','https','telnet','gopher','wais','ftp','ed2k','irc','ldap');
+ var $patterns = array();
+
+ function preConnect() {
+
+ $ltrs = '\w';
+ $gunk = '/\#~:.?+=&%@!\-';
+ $punc = '.:?\-;,';
+ $host = $ltrs.$punc;
+ $any = $ltrs.$gunk.$punc;
+
+ foreach ( $this->schemes as $scheme ) {
+ $this->patterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
+ }
+
+ $this->patterns[] = '\b(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
+ $this->patterns[] = '\b(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
+
+ }
+
+ function connectTo($mode) {
+ foreach ( $this->patterns as $pattern ) {
+ $this->Lexer->addSpecialPattern($pattern,$mode,'externallink');
+ }
+ }
+
+ function getSort() {
+ return 330;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_filelink extends Doku_Parser_Mode {
+
+ var $pattern;
+
+ function preConnect() {
+
+ $ltrs = '\w';
+ $gunk = '/\#~:.?+=&%@!\-';
+ $punc = '.:?\-;,';
+ $host = $ltrs.$punc;
+ $any = $ltrs.$gunk.$punc;
+
+ $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['.
+ $punc.']*[^'.$any.']';
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ $this->pattern,$mode,'filelink');
+ }
+
+ function getSort() {
+ return 360;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_windowssharelink extends Doku_Parser_Mode {
+
+ var $pattern;
+
+ function preConnect() {
+ $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w$]+)+";
+ }
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern(
+ $this->pattern,$mode,'windowssharelink');
+ }
+
+ function getSort() {
+ return 350;
+ }
+}
+
+//-------------------------------------------------------------------
+class Doku_Parser_Mode_emaillink extends Doku_Parser_Mode {
+
+ function connectTo($mode) {
+ $this->Lexer->addSpecialPattern("<[\w0-9\-_.]+?@[\w\-]+\.[\w\-\.]+\.*[\w]+>",$mode,'emaillink');
+ }
+
+ function getSort() {
+ return 340;
+ }
+}
+
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parser/renderer.php b/plugins/dokuwiki/inc/parser/renderer.php
new file mode 100644
index 0000000..f9dcaab
--- /dev/null
+++ b/plugins/dokuwiki/inc/parser/renderer.php
@@ -0,0 +1,212 @@
+<?php
+/**
+ * Renderer output base class
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+
+require_once DOKU_INC . 'inc/parser/renderer.php';
+require_once DOKU_INC . 'inc/pluginutils.php';
+
+class Doku_Renderer {
+ var $info = array(
+ 'cache' => TRUE, // may the rendered result cached?
+ 'toc' => TRUE, // render the TOC?
+ );
+
+
+ function nocache() {
+ $this->info['cache'] = FALSE;
+ }
+
+ function notoc() {
+ $this->info['toc'] = FALSE;
+ }
+
+ //handle plugin rendering
+ function plugin($name,$data){
+ $plugin =& plugin_load('syntax',$name);
+ if($plugin != null){
+ // determine mode from renderer class name - format = "Doku_Renderer_<mode>"
+ $mode = substr(get_class($this), 14);
+ $plugin->render($mode,$this,$data);
+ }
+ }
+
+ /**
+ * handle nested render instructions
+ * this method (and nest_close method) should not be overloaded in actual renderer output classes
+ */
+ function nest($instructions) {
+
+ foreach ( $instructions as $instruction ) {
+ // execute the callback against ourself
+ call_user_func_array(array(&$this, $instruction[0]),$instruction[1]);
+ }
+ }
+
+ // dummy closing instruction issued by Doku_Handler_Nest, normally the syntax mode should
+ // override this instruction when instantiating Doku_Handler_Nest - however plugins will not
+ // be able to - as their instructions require data.
+ function nest_close() {}
+
+ function document_start() {}
+
+ function document_end() {}
+
+ function render_TOC() { return ''; }
+
+ function header($text, $level, $pos) {}
+
+ function section_edit($start, $end, $level, $name) {}
+
+ function section_open($level) {}
+
+ function section_close() {}
+
+ function cdata($text) {}
+
+ function p_open() {}
+
+ function p_close() {}
+
+ function linebreak() {}
+
+ function hr() {}
+
+ function strong_open() {}
+
+ function strong_close() {}
+
+ function emphasis_open() {}
+
+ function emphasis_close() {}
+
+ function underline_open() {}
+
+ function underline_close() {}
+
+ function monospace_open() {}
+
+ function monospace_close() {}
+
+ function subscript_open() {}
+
+ function subscript_close() {}
+
+ function superscript_open() {}
+
+ function superscript_close() {}
+
+ function deleted_open() {}
+
+ function deleted_close() {}
+
+ function footnote_open() {}
+
+ function footnote_close() {}
+
+ function listu_open() {}
+
+ function listu_close() {}
+
+ function listo_open() {}
+
+ function listo_close() {}
+
+ function listitem_open($level) {}
+
+ function listitem_close() {}
+
+ function listcontent_open() {}
+
+ function listcontent_close() {}
+
+ function unformatted($text) {}
+
+ function php($text) {}
+
+ function html($text) {}
+
+ function preformatted($text) {}
+
+ function file($text) {}
+
+ function quote_open() {}
+
+ function quote_close() {}
+
+ function code($text, $lang = NULL) {}
+
+ function acronym($acronym) {}
+
+ function smiley($smiley) {}
+
+ function wordblock($word) {}
+
+ function entity($entity) {}
+
+ // 640x480 ($x=640, $y=480)
+ function multiplyentity($x, $y) {}
+
+ function singlequoteopening() {}
+
+ function singlequoteclosing() {}
+
+ function doublequoteopening() {}
+
+ function doublequoteclosing() {}
+
+ // $link like 'SomePage'
+ function camelcaselink($link) {}
+
+ // $link like 'wiki:syntax', $title could be an array (media)
+ function internallink($link, $title = NULL) {}
+
+ // $link is full URL with scheme, $title could be an array (media)
+ function externallink($link, $title = NULL) {}
+
+ // $link is the original link - probably not much use
+ // $wikiName is an indentifier for the wiki
+ // $wikiUri is the URL fragment to append to some known URL
+ function interwikilink($link, $title = NULL, $wikiName, $wikiUri) {}
+
+ // Link to file on users OS, $title could be an array (media)
+ function filelink($link, $title = NULL) {}
+
+ // Link to a Windows share, , $title could be an array (media)
+ function windowssharelink($link, $title = NULL) {}
+
+// function email($address, $title = NULL) {}
+ function emaillink($address, $name = NULL) {}
+
+ function internalmedialink (
+ $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL
+ ) {}
+
+ function externalmedialink(
+ $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL
+ ) {}
+
+ function table_open($maxcols = NULL, $numrows = NULL){}
+
+ function table_close(){}
+
+ function tablerow_open(){}
+
+ function tablerow_close(){}
+
+ function tableheader_open($colspan = 1, $align = NULL){}
+
+ function tableheader_close(){}
+
+ function tablecell_open($colspan = 1, $align = NULL){}
+
+ function tablecell_close(){}
+
+}
+
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parser/xhtml.php b/plugins/dokuwiki/inc/parser/xhtml.php
new file mode 100644
index 0000000..a220819
--- /dev/null
+++ b/plugins/dokuwiki/inc/parser/xhtml.php
@@ -0,0 +1,1097 @@
+<?php
+/**
+ * Renderer for XHTML output
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+
+if ( !defined('DOKU_LF') ) {
+ // Some whitespace to help View > Source
+ define ('DOKU_LF',"\n");
+}
+
+if ( !defined('DOKU_TAB') ) {
+ // Some whitespace to help View > Source
+ define ('DOKU_TAB',"\t");
+}
+
+require_once DOKU_INC . 'inc/parser/renderer.php';
+require_once DOKU_INC . 'inc/html.php';
+
+/**
+ * The Renderer
+ */
+class Doku_Renderer_xhtml extends Doku_Renderer {
+
+ // @access public
+ var $doc = ''; // will contain the whole document
+ var $toc = array(); // will contain the Table of Contents
+
+
+ var $headers = array();
+
+ var $footnotes = array();
+
+ var $acronyms = array();
+ var $smileys = array();
+ var $badwords = array();
+ var $entities = array();
+ var $interwiki = array();
+
+ var $lastsec = 0;
+
+ var $store = '';
+
+ function document_start() {
+ //reset some internals
+ $this->toc = array();
+ $this->headers = array();
+ }
+
+ function document_end() {
+ if ( count ($this->footnotes) > 0 ) {
+ $this->doc .= '<div class="footnotes">'.DOKU_LF;
+
+ $id = 0;
+ foreach ( $this->footnotes as $footnote ) {
+ $id++; // the number of the current footnote
+
+ // check its not a placeholder that indicates actual footnote text is elsewhere
+ if (substr($footnote, 0, 5) != "@@FNT") {
+
+ // open the footnote and set the anchor and backlink
+ $this->doc .= '<div class="fn">';
+ $this->doc .= '<a href="#fnt__'.$id.'" id="fn__'.$id.'" name="fn__'.$id.'" class="fn_bot">';
+ $this->doc .= $id.')</a> '.DOKU_LF;
+
+ // get any other footnotes that use the same markup
+ $alt = array_keys($this->footnotes, "@@FNT$id");
+
+ if (count($alt)) {
+ foreach ($alt as $ref) {
+ // set anchor and backlink for the other footnotes
+ $this->doc .= ', <a href="#fnt__'.($ref+1).'" id="fn__'.($ref+1).'" name="fn__'.($ref+1).'" class="fn_bot">';
+ $this->doc .= ($ref+1).')</a> '.DOKU_LF;
+ }
+ }
+
+ // add footnote markup and close this footnote
+ $this->doc .= $footnote;
+ $this->doc .= '</div>' . DOKU_LF;
+ }
+ }
+ $this->doc .= '</div>'.DOKU_LF;
+ }
+
+ // prepend the TOC
+ if($this->info['toc']){
+ $this->doc = $this->render_TOC().$this->doc;
+ }
+
+ // make sure there are no empty paragraphs
+ $this->doc = preg_replace('#<p>\s*</p>#','',$this->doc);
+ }
+
+ /**
+ * Return the TOC rendered to XHTML
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function render_TOC(){
+ if(count($this->toc) < 3) return '';
+ global $lang;
+ $out = '<div class="toc">'.DOKU_LF;
+ $out .= '<div class="tocheader toctoggle" id="toc__header">';
+ $out .= $lang['toc'];
+ $out .= '</div>'.DOKU_LF;
+ $out .= '<div id="toc__inside">'.DOKU_LF;
+ $out .= html_buildlist($this->toc,'toc',array($this,'_tocitem'));
+ $out .= '</div>'.DOKU_LF.'</div>'.DOKU_LF;
+ return $out;
+ }
+
+ /**
+ * Callback for html_buildlist
+ */
+ function _tocitem($item){
+ return '<span class="li"><a href="#'.$item['hid'].'" class="toc">'.
+ $this->_xmlEntities($item['title']).'</a></span>';
+ }
+
+ function header($text, $level, $pos) {
+ global $conf;
+
+ // create a unique header id
+ $hid = $this->_headerToLink($text,'true');
+
+ //handle TOC
+ if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){
+ // the TOC is one of our standard ul list arrays ;-)
+ $this->toc[] = array( 'hid' => $hid,
+ 'title' => $text,
+ 'type' => 'ul',
+ 'level' => $level-$conf['toptoclevel']+1);
+ }
+
+ // write the header
+ $this->doc .= DOKU_LF.'<h'.$level.' id="'.$hid.'">'.$this->_xmlEntities($text)."</h$level>".DOKU_LF;
+ }
+
+ /**
+ * Section edit marker is replaced by an edit button when
+ * the page is editable. Replacement done in 'inc/html.php#html_secedit'
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+ function section_edit($start, $end, $level, $name) {
+ global $conf;
+
+ if ($start!=-1 && $level<=$conf['maxseclevel']) {
+ $name = str_replace('"', '', $name);
+ $this->doc .= '<!-- SECTION "'.$name.'" ['.$start.'-'.(($end===0)?'':$end).'] -->';
+ }
+ }
+
+ function section_open($level) {
+ $this->doc .= "<div class=\"level$level\">".DOKU_LF;
+ }
+
+ function section_close() {
+ $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
+ }
+
+ function cdata($text) {
+ $this->doc .= $this->_xmlEntities($text);
+ }
+
+ function p_open() {
+ $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
+ }
+
+ function p_close() {
+ $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
+ }
+
+ function linebreak() {
+ $this->doc .= '<br/>'.DOKU_LF;
+ }
+
+ function hr() {
+ $this->doc .= '<hr />'.DOKU_LF;
+ }
+
+ function strong_open() {
+ $this->doc .= '<strong>';
+ }
+
+ function strong_close() {
+ $this->doc .= '</strong>';
+ }
+
+ function emphasis_open() {
+ $this->doc .= '<em>';
+ }
+
+ function emphasis_close() {
+ $this->doc .= '</em>';
+ }
+
+ function underline_open() {
+ $this->doc .= '<em class="u">';
+ }
+
+ function underline_close() {
+ $this->doc .= '</em>';
+ }
+
+ function monospace_open() {
+ $this->doc .= '<code>';
+ }
+
+ function monospace_close() {
+ $this->doc .= '</code>';
+ }
+
+ function subscript_open() {
+ $this->doc .= '<sub>';
+ }
+
+ function subscript_close() {
+ $this->doc .= '</sub>';
+ }
+
+ function superscript_open() {
+ $this->doc .= '<sup>';
+ }
+
+ function superscript_close() {
+ $this->doc .= '</sup>';
+ }
+
+ function deleted_open() {
+ $this->doc .= '<del>';
+ }
+
+ function deleted_close() {
+ $this->doc .= '</del>';
+ }
+
+ /**
+ * Callback for footnote start syntax
+ *
+ * All following content will go to the footnote instead of
+ * the document. To achieve this the previous rendered content
+ * is moved to $store and $doc is cleared
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function footnote_open() {
+
+ // move current content to store and record footnote
+ $this->store = $this->doc;
+ $this->doc = '';
+ }
+
+ /**
+ * Callback for footnote end syntax
+ *
+ * All rendered content is moved to the $footnotes array and the old
+ * content is restored from $store again
+ *
+ * @author Andreas Gohr
+ */
+ function footnote_close() {
+
+ // recover footnote into the stack and restore old content
+ $footnote = $this->doc;
+ $this->doc = $this->store;
+ $this->store = '';
+
+ // check to see if this footnote has been seen before
+ $i = array_search($footnote, $this->footnotes);
+
+ if ($i === false) {
+ // its a new footnote, add it to the $footnotes array
+ $id = count($this->footnotes)+1;
+ $this->footnotes[count($this->footnotes)] = $footnote;
+ } else {
+ // seen this one before, translate the index to an id and save a placeholder
+ $i++;
+ $id = count($this->footnotes)+1;
+ $this->footnotes[count($this->footnotes)] = "@@FNT".($i);
+ }
+
+ // output the footnote reference and link, incl. onmouseover for insitu footnote popup
+ $this->doc .= '<a href="#fn__'.$id.'" name="fnt__'.$id.'" id="fnt__'.$id.'" class="fn_top" onmouseover="fnt(\''.$id.'\', this, event);">'.$id.')</a>';
+ }
+
+ function listu_open() {
+ $this->doc .= '<ul>'.DOKU_LF;
+ }
+
+ function listu_close() {
+ $this->doc .= '</ul>'.DOKU_LF;
+ }
+
+ function listo_open() {
+ $this->doc .= '<ol>'.DOKU_LF;
+ }
+
+ function listo_close() {
+ $this->doc .= '</ol>'.DOKU_LF;
+ }
+
+ function listitem_open($level) {
+ $this->doc .= '<li class="level'.$level.'">';
+ }
+
+ function listitem_close() {
+ $this->doc .= '</li>'.DOKU_LF;
+ }
+
+ function listcontent_open() {
+ $this->doc .= '<div class="li">';
+ }
+
+ function listcontent_close() {
+ $this->doc .= '</div>'.DOKU_LF;
+ }
+
+ function unformatted($text) {
+ $this->doc .= $this->_xmlEntities($text);
+ }
+
+ /**
+ * Execute PHP code if allowed
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function php($text) {
+ global $conf;
+ if($conf['phpok']){
+ ob_start();
+ eval($text);
+ $this->doc .= ob_get_contents();
+ ob_end_clean();
+ }else{
+ $this->file($text);
+ }
+ }
+
+ /**
+ * Insert HTML if allowed
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function html($text) {
+ global $conf;
+ if($conf['htmlok']){
+ $this->doc .= $text;
+ }else{
+ $this->file($text);
+ }
+ }
+
+ function preformatted($text) {
+ $this->doc .= '<pre class="code">' . $this->_xmlEntities($text) . '</pre>'. DOKU_LF;
+ }
+
+ function file($text) {
+ $this->doc .= '<pre class="file">' . $this->_xmlEntities($text). '</pre>'. DOKU_LF;
+ }
+
+ function quote_open() {
+ $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
+ }
+
+ function quote_close() {
+ $this->doc .= '</div></blockquote>'.DOKU_LF;
+ }
+
+ /**
+ * Callback for code text
+ *
+ * Uses GeSHi to highlight language syntax
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function code($text, $language = NULL) {
+ global $conf;
+
+ if ( is_null($language) ) {
+ $this->preformatted($text);
+ } else {
+ //strip leading and trailing blank line
+ $text = preg_replace('/^\s*?\n/','',$text);
+ $text = preg_replace('/\s*?\n$/','',$text);
+ $this->doc .= p_xhtml_cached_geshi($text, $language);
+ }
+ }
+
+ function geshi_cached($text = '') {
+ $this->doc .= $text;
+ }
+
+ function acronym($acronym) {
+
+ if ( array_key_exists($acronym, $this->acronyms) ) {
+
+ $title = $this->_xmlEntities($this->acronyms[$acronym]);
+
+ $this->doc .= '<acronym title="'.$title
+ .'">'.$this->_xmlEntities($acronym).'</acronym>';
+
+ } else {
+ $this->doc .= $this->_xmlEntities($acronym);
+ }
+ }
+
+ function smiley($smiley) {
+ if ( array_key_exists($smiley, $this->smileys) ) {
+ $title = $this->_xmlEntities($this->smileys[$smiley]);
+ $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley].
+ '" class="middle" alt="'.
+ $this->_xmlEntities($smiley).'" />';
+ } else {
+ $this->doc .= $this->_xmlEntities($smiley);
+ }
+ }
+
+ /*
+ * not used
+ function wordblock($word) {
+ if ( array_key_exists($word, $this->badwords) ) {
+ $this->doc .= '** BLEEP **';
+ } else {
+ $this->doc .= $this->_xmlEntities($word);
+ }
+ }
+ */
+
+ function entity($entity) {
+ if ( array_key_exists($entity, $this->entities) ) {
+ $this->doc .= $this->entities[$entity];
+ } else {
+ $this->doc .= $this->_xmlEntities($entity);
+ }
+ }
+
+ function multiplyentity($x, $y) {
+ $this->doc .= "$x&times;$y";
+ }
+
+ function singlequoteopening() {
+ $this->doc .= "&lsquo;";
+ }
+
+ function singlequoteclosing() {
+ $this->doc .= "&rsquo;";
+ }
+
+ function doublequoteopening() {
+ $this->doc .= "&ldquo;";
+ }
+
+ function doublequoteclosing() {
+ $this->doc .= "&rdquo;";
+ }
+
+ /**
+ */
+ function camelcaselink($link) {
+ $this->internallink($link,$link);
+ }
+
+
+ function locallink($hash, $name = NULL){
+ global $ID;
+ $name = $this->_getLinkTitle($name, $hash, $isImage);
+ $hash = $this->_headerToLink($hash);
+ $title = $ID.' &crarr;';
+ $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
+ $this->doc .= $name;
+ $this->doc .= '</a>';
+ }
+
+ /**
+ * Render an internal Wiki Link
+ *
+ * $search and $returnonly are not for the renderer but are used
+ * elsewhere - no need to implement them in other renderers
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function internallink($id, $name = NULL, $search=NULL,$returnonly=false) {
+ global $conf;
+ global $ID;
+ // default name is based on $id as given
+ $default = $this->_simpleTitle($id);
+ // now first resolve and clean up the $id
+ resolve_pageid(getNS($ID),$id,$exists);
+ $name = $this->_getLinkTitle($name, $default, $isImage, $id);
+ if ( !$isImage ) {
+ if ( $exists ) {
+ $class='wikilink1';
+ } else {
+ $class='wikilink2';
+ }
+ } else {
+ $class='media';
+ }
+
+ //keep hash anchor
+ list($id,$hash) = explode('#',$id,2);
+
+ //prepare for formating
+ $link['target'] = $conf['target']['wiki'];
+ $link['style'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ // highlight link to current page
+ if ($id == $ID) {
+ $link['pre'] = '<span class="curid">';
+ $link['suf'] = '</span>';
+ }
+ $link['more'] = '';
+ $link['class'] = $class;
+ $link['url'] = wl($id);
+ $link['name'] = $name;
+ $link['title'] = $id;
+ //add search string
+ if($search){
+ ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&amp;s=';
+ $link['url'] .= rawurlencode($search);
+ }
+
+ //keep hash
+ if($hash) $link['url'].='#'.$hash;
+
+ //output formatted
+ if($returnonly){
+ return $this->_formatLink($link);
+ }else{
+ $this->doc .= $this->_formatLink($link);
+ }
+ }
+
+ function externallink($url, $name = NULL) {
+ global $conf;
+
+ $name = $this->_getLinkTitle($name, $url, $isImage);
+
+ // add protocol on simple short URLs
+ if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')) $url = 'ftp://'.$url;
+ if(substr($url,0,3) == 'www') $url = 'http://'.$url;
+
+ if ( !$isImage ) {
+ $class='urlextern';
+ } else {
+ $class='media';
+ }
+
+ //prepare for formating
+ $link['target'] = $conf['target']['extern'];
+ $link['style'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['more'] = '';
+ $link['class'] = $class;
+ $link['url'] = $url;
+
+ $link['name'] = $name;
+ $link['title'] = $this->_xmlEntities($url);
+ if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
+
+ //output formatted
+ $this->doc .= $this->_formatLink($link);
+ }
+
+ /**
+ */
+ function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
+ global $conf;
+
+ $link = array();
+ $link['target'] = $conf['target']['interwiki'];
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['more'] = '';
+ $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage);
+
+ //get interwiki URL
+ if ( isset($this->interwiki[$wikiName]) ) {
+ $url = $this->interwiki[$wikiName];
+ } else {
+ // Default to Google I'm feeling lucky
+ $url = 'http://www.google.com/search?q={URL}&amp;btnI=lucky';
+ $wikiName = 'go';
+ }
+
+ if ( !$isImage ) {
+ $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName);
+ $link['class'] = "interwiki iw_$class";
+ } else {
+ $link['class'] = 'media';
+ }
+
+ //do we stay at the same server? Use local target
+ if( strpos($url,DOKU_URL) === 0 ){
+ $link['target'] = $conf['target']['wiki'];
+ }
+
+ //split into hash and url part
+ list($wikiUri,$hash) = explode('#',$wikiUri,2);
+
+ //replace placeholder
+ if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#',$url)){
+ //use placeholders
+ $url = str_replace('{URL}',rawurlencode($wikiUri),$url);
+ $url = str_replace('{NAME}',$wikiUri,$url);
+ $parsed = parse_url($wikiUri);
+ if(!$parsed['port']) $parsed['port'] = 80;
+ $url = str_replace('{SCHEME}',$parsed['scheme'],$url);
+ $url = str_replace('{HOST}',$parsed['host'],$url);
+ $url = str_replace('{PORT}',$parsed['port'],$url);
+ $url = str_replace('{PATH}',$parsed['path'],$url);
+ $url = str_replace('{QUERY}',$parsed['query'],$url);
+ $link['url'] = $url;
+ }else{
+ //default
+ $link['url'] = $url.rawurlencode($wikiUri);
+ }
+ if($hash) $link['url'] .= '#'.rawurlencode($hash);
+
+ $link['title'] = htmlspecialchars($link['url']);
+
+ //output formatted
+ $this->doc .= $this->_formatLink($link);
+ }
+
+ /**
+ */
+ function windowssharelink($url, $name = NULL) {
+ global $conf;
+ global $lang;
+ //simple setup
+ $link['target'] = $conf['target']['windows'];
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['style'] = '';
+ //Display error on browsers other than IE
+ $link['more'] = 'onclick="if(document.all == null){alert(\''.
+ $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).
+ '\');}" onkeypress="if(document.all == null){alert(\''.
+ $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"';
+
+ $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
+ if ( !$isImage ) {
+ $link['class'] = 'windows';
+ } else {
+ $link['class'] = 'media';
+ }
+
+
+ $link['title'] = $this->_xmlEntities($url);
+ $url = str_replace('\\','/',$url);
+ $url = 'file:///'.$url;
+ $link['url'] = $url;
+
+ //output formatted
+ $this->doc .= $this->_formatLink($link);
+ }
+
+ function emaillink($address, $name = NULL) {
+ global $conf;
+ //simple setup
+ $link = array();
+ $link['target'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['style'] = '';
+ $link['more'] = '';
+
+ $name = $this->_getLinkTitle($name, $address, $isImage);
+ if ( !$isImage ) {
+ $link['class']='mail JSnocheck';
+ } else {
+ $link['class']='media JSnocheck';
+ }
+
+ $address = $this->_xmlEntities($address);
+ $address = obfuscate($address);
+ $title = $address;
+
+ if(empty($name)){
+ $name = $address;
+ }
+#elseif($isImage{
+# $name = $this->_xmlEntities($name);
+# }
+
+ if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
+
+ $link['url'] = 'mailto:'.$address;
+ $link['name'] = $name;
+ $link['title'] = $title;
+
+ //output formatted
+ $this->doc .= $this->_formatLink($link);
+ }
+
+ function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
+ $height=NULL, $cache=NULL, $linking=NULL) {
+ global $conf;
+ global $ID;
+ resolve_mediaid(getNS($ID),$src, $exists);
+
+ $link = array();
+ $link['class'] = 'media';
+ $link['style'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['more'] = '';
+ $link['target'] = $conf['target']['media'];
+ $noLink = false;
+
+ $link['title'] = $this->_xmlEntities($src);
+ list($ext,$mime) = mimetype($src);
+ if(substr($mime,0,5) == 'image'){
+ $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct'));
+ }elseif($mime == 'application/x-shockwave-flash'){
+ // don't link flash movies
+ $noLink = TRUE;
+ }else{
+ // add file icons
+ $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext);
+ $link['class'] .= ' mediafile mf_'.$class;
+ $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true);
+ }
+ $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache);
+
+ //output formatted
+ if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
+ else $this->doc .= $this->_formatLink($link);
+ }
+
+ /**
+ * @todo don't add link for flash
+ */
+ function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
+ $height=NULL, $cache=NULL, $linking=NULL) {
+ global $conf;
+
+ $link = array();
+ $link['class'] = 'media';
+ $link['style'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['more'] = '';
+ $link['target'] = $conf['target']['media'];
+
+ $link['title'] = $this->_xmlEntities($src);
+ $link['url'] = ml($src,array('cache'=>$cache));
+ $link['name'] = $this->_media ($src, $title, $align, $width, $height, $cache);
+ $noLink = false;
+
+ list($ext,$mime) = mimetype($src);
+ if(substr($mime,0,5) == 'image'){
+ // link only jpeg images
+ // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = TRUE;
+ }elseif($mime == 'application/x-shockwave-flash'){
+ // don't link flash movies
+ $noLink = TRUE;
+ }else{
+ // add file icons
+ $link['class'] .= ' mediafile mf_'.$ext;
+ }
+
+ //output formatted
+ if ($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
+ else $this->doc .= $this->_formatLink($link);
+ }
+
+ /**
+ * Renders an RSS feed
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function rss ($url,$params){
+ global $lang;
+ global $conf;
+
+ require_once(DOKU_INC.'inc/FeedParser.php');
+ $feed = new FeedParser();
+ $feed->feed_url($url);
+
+ //disable warning while fetching
+ if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); }
+ $rc = $feed->init();
+ if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); }
+
+ //decide on start and end
+ if($params['reverse']){
+ $mod = -1;
+ $start = $feed->get_item_quantity()-1;
+ $end = $start - ($params['max']);
+ $end = ($end < -1) ? -1 : $end;
+ }else{
+ $mod = 1;
+ $start = 0;
+ $end = $feed->get_item_quantity();
+ $end = ($end > $params['max']) ? $params['max'] : $end;;
+ }
+
+ $this->doc .= '<ul class="rss">';
+ if($rc){
+ for ($x = $start; $x != $end; $x += $mod) {
+ $item = $feed->get_item($x);
+ $this->doc .= '<li><div class="li">';
+ $this->externallink($item->get_permalink(),
+ $item->get_title());
+ if($params['author']){
+ $author = $item->get_author(0);
+ if($author){
+ $name = $author->get_name();
+ if(!$name) $name = $author->get_email();
+ if($name) $this->doc .= ' '.$lang['by'].' '.$name;
+ }
+ }
+ if($params['date']){
+ $this->doc .= ' ('.$item->get_date($conf['dformat']).')';
+ }
+ if($params['details']){
+ $this->doc .= '<div class="detail">';
+ if($htmlok){
+ $this->doc .= $item->get_description();
+ }else{
+ $this->doc .= strip_tags($item->get_description());
+ }
+ $this->doc .= '</div>';
+ }
+
+ $this->doc .= '</div></li>';
+ }
+ }else{
+ $this->doc .= '<li><div class="li">';
+ $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
+ $this->externallink($url);
+ $this->doc .= '</div></li>';
+ }
+ $this->doc .= '</ul>';
+ }
+
+ // $numrows not yet implemented
+ function table_open($maxcols = NULL, $numrows = NULL){
+ $this->doc .= '<table class="inline">'.DOKU_LF;
+ }
+
+ function table_close(){
+ $this->doc .= '</table>'.DOKU_LF;
+ }
+
+ function tablerow_open(){
+ $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB;
+ }
+
+ function tablerow_close(){
+ $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
+ }
+
+ function tableheader_open($colspan = 1, $align = NULL){
+ $this->doc .= '<th';
+ if ( !is_null($align) ) {
+ $this->doc .= ' class="'.$align.'align"';
+ }
+ if ( $colspan > 1 ) {
+ $this->doc .= ' colspan="'.$colspan.'"';
+ }
+ $this->doc .= '>';
+ }
+
+ function tableheader_close(){
+ $this->doc .= '</th>';
+ }
+
+ function tablecell_open($colspan = 1, $align = NULL){
+ $this->doc .= '<td';
+ if ( !is_null($align) ) {
+ $this->doc .= ' class="'.$align.'align"';
+ }
+ if ( $colspan > 1 ) {
+ $this->doc .= ' colspan="'.$colspan.'"';
+ }
+ $this->doc .= '>';
+ }
+
+ function tablecell_close(){
+ $this->doc .= '</td>';
+ }
+
+ //----------------------------------------------------------
+ // Utils
+
+ /**
+ * Build a link
+ *
+ * Assembles all parts defined in $link returns HTML for the link
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _formatLink($link){
+ //make sure the url is XHTML compliant (skip mailto)
+ if(substr($link['url'],0,7) != 'mailto:'){
+ $link['url'] = str_replace('&','&amp;',$link['url']);
+ $link['url'] = str_replace('&amp;amp;','&amp;',$link['url']);
+ }
+ //remove double encodings in titles
+ $link['title'] = str_replace('&amp;amp;','&amp;',$link['title']);
+
+ // be sure there are no bad chars in url or title
+ // (we can't do this for name because it can contain an img tag)
+ $link['url'] = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22'));
+ $link['title'] = strtr($link['title'],array('>'=>'&gt;','<'=>'&lt;','"'=>'&quot;'));
+
+ $ret = '';
+ $ret .= $link['pre'];
+ $ret .= '<a href="'.$link['url'].'"';
+ if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"';
+ if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"';
+ if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"';
+ if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"';
+ if(!empty($link['more'])) $ret .= ' '.$link['more'];
+ $ret .= '>';
+ $ret .= $link['name'];
+ $ret .= '</a>';
+ $ret .= $link['suf'];
+ return $ret;
+ }
+
+ /**
+ * Removes any Namespace from the given name but keeps
+ * casing and special chars
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _simpleTitle($name){
+ global $conf;
+
+ //if there is a hash we use the ancor name only
+ list($name,$hash) = explode('#',$name,2);
+ if($hash) return $hash;
+
+ //trim colons of a namespace link
+ $name = rtrim($name,':');
+
+ if($conf['useslash']){
+ $nssep = '[:;/]';
+ }else{
+ $nssep = '[:;]';
+ }
+ $name = preg_replace('!.*'.$nssep.'!','',$name);
+
+ if(!$name) return $this->_simpleTitle($conf['start']);
+ return $name;
+ }
+
+ /**
+ * Renders internal and external media
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _media ($src, $title=NULL, $align=NULL, $width=NULL,
+ $height=NULL, $cache=NULL) {
+
+ $ret = '';
+
+ list($ext,$mime) = mimetype($src);
+ if(substr($mime,0,5) == 'image'){
+ //add image tag
+ $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"';
+ $ret .= ' class="media'.$align.'"';
+
+ if (!is_null($title)) {
+ $ret .= ' title="'.$this->_xmlEntities($title).'"';
+ $ret .= ' alt="'.$this->_xmlEntities($title).'"';
+ }elseif($ext == 'jpg' || $ext == 'jpeg'){
+ //try to use the caption from IPTC/EXIF
+ require_once(DOKU_INC.'inc/JpegMeta.php');
+ $jpeg = new JpegMeta(mediaFN($src));
+ if($jpeg !== false) $cap = $jpeg->getTitle();
+ if($cap){
+ $ret .= ' title="'.$this->_xmlEntities($cap).'"';
+ $ret .= ' alt="'.$this->_xmlEntities($cap).'"';
+ }
+ }else{
+ $ret .= ' alt=""';
+ }
+
+ if ( !is_null($width) )
+ $ret .= ' width="'.$this->_xmlEntities($width).'"';
+
+ if ( !is_null($height) )
+ $ret .= ' height="'.$this->_xmlEntities($height).'"';
+
+ $ret .= ' />';
+
+ }elseif($mime == 'application/x-shockwave-flash'){
+ $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'.
+ ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"';
+ if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
+ if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
+ $ret .= '>'.DOKU_LF;
+ $ret .= '<param name="movie" value="'.ml($src).'" />'.DOKU_LF;
+ $ret .= '<param name="quality" value="high" />'.DOKU_LF;
+ $ret .= '<embed src="'.ml($src).'"'.
+ ' quality="high"';
+ if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
+ if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
+ $ret .= ' type="application/x-shockwave-flash"'.
+ ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF;
+ $ret .= '</object>'.DOKU_LF;
+
+ }elseif(!is_null($title)){
+ // well at least we have a title to display
+ $ret .= $this->_xmlEntities($title);
+ }else{
+ // just show the sourcename
+ $ret .= $this->_xmlEntities(noNS($src));
+ }
+
+ return $ret;
+ }
+
+ function _xmlEntities($string) {
+ return htmlspecialchars($string);
+ }
+
+ /**
+ * Creates a linkid from a headline
+ *
+ * @param string $title The headline title
+ * @param boolean $create Create a new unique ID?
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _headerToLink($title,$create=false) {
+ $title = str_replace(':','',cleanID($title));
+ $title = ltrim($title,'0123456789._-');
+ if(empty($title)) $title='section';
+
+ if($create){
+ // make sure tiles are unique
+ $num = '';
+ while(in_array($title.$num,$this->headers)){
+ ($num) ? $num++ : $num = 1;
+ }
+ $title = $title.$num;
+ $this->headers[] = $title;
+ }
+
+ return $title;
+ }
+
+ /**
+ * Construct a title and handle images in titles
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+ function _getLinkTitle($title, $default, & $isImage, $id=NULL) {
+ global $conf;
+
+ $isImage = FALSE;
+ if ( is_null($title) ) {
+ if ($conf['useheading'] && $id) {
+ $heading = p_get_first_heading($id);
+ if ($heading) {
+ return $this->_xmlEntities($heading);
+ }
+ }
+ return $this->_xmlEntities($default);
+ } else if ( is_string($title) ) {
+ return $this->_xmlEntities($title);
+ } else if ( is_array($title) ) {
+ $isImage = TRUE;
+ return $this->_imageTitle($title);
+ }
+ }
+
+ /**
+ * Returns an HTML code for images used in link titles
+ *
+ * @todo Resolve namespace on internal images
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ function _imageTitle($img) {
+ return $this->_media($img['src'],
+ $img['title'],
+ $img['align'],
+ $img['width'],
+ $img['height'],
+ $img['cache']);
+ }
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/parserutils.php b/plugins/dokuwiki/inc/parserutils.php
new file mode 100644
index 0000000..41fa1c3
--- /dev/null
+++ b/plugins/dokuwiki/inc/parserutils.php
@@ -0,0 +1,524 @@
+<?php
+/**
+ * Utilities for collecting data from config files
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/');
+
+ require_once(DOKU_INC.'inc/confutils.php');
+ require_once(DOKU_INC.'inc/pageutils.php');
+ require_once(DOKU_INC.'inc/pluginutils.php');
+ require_once(DOKU_INC.'inc/cache.php');
+
+/**
+ * Returns the parsed Wikitext in XHTML for the given id and revision.
+ *
+ * If $excuse is true an explanation is returned if the file
+ * wasn't found
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_wiki_xhtml($id, $rev='', $excuse=true){
+ $file = wikiFN($id,$rev);
+ $ret = '';
+
+ //ensure $id is in global $ID (needed for parsing)
+ global $ID;
+ $keep = $ID;
+ $ID = $id;
+
+ if($rev){
+ if(@file_exists($file)){
+ $ret = p_render('xhtml',p_get_instructions(io_readfile($file)),$info); //no caching on old revisions
+ }elseif($excuse){
+ $ret = p_locale_xhtml('norev');
+ }
+ }else{
+ if(@file_exists($file)){
+ $ret = p_cached_output($file,'xhtml',$id);
+ }elseif($excuse){
+ $ret = p_locale_xhtml('newpage');
+ }
+ }
+
+ //restore ID (just in case)
+ $ID = $keep;
+
+ return $ret;
+}
+
+/**
+ * Returns starting summary for a page (e.g. the first few
+ * paragraphs), marked up in XHTML.
+ *
+ * If $excuse is true an explanation is returned if the file
+ * wasn't found
+ *
+ * @param string wiki page id
+ * @param reference populated with page title from heading or page id
+ * @deprecated
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function p_wiki_xhtml_summary($id, &$title, $rev='', $excuse=true){
+ $file = wikiFN($id,$rev);
+ $ret = '';
+
+ //ensure $id is in global $ID (needed for parsing)
+ global $ID;
+ $keep = $ID;
+ $ID = $id;
+
+ if($rev){
+ if(@file_exists($file)){
+ //no caching on old revisions
+ $ins = p_get_instructions(io_readfile($file));
+ }elseif($excuse){
+ $ret = p_locale_xhtml('norev');
+ //restore ID (just in case)
+ $ID = $keep;
+ return $ret;
+ }
+
+ }else{
+
+ if(@file_exists($file)){
+ // The XHTML for a summary is not cached so use the instruction cache
+ $ins = p_cached_instructions($file);
+ }elseif($excuse){
+ $ret = p_locale_xhtml('newpage');
+ //restore ID (just in case)
+ $ID = $keep;
+ return $ret;
+ }
+ }
+
+ $ret = p_render('xhtmlsummary',$ins,$info);
+
+ if ( $info['sum_pagetitle'] ) {
+ $title = $info['sum_pagetitle'];
+ } else {
+ $title = $id;
+ }
+
+ $ID = $keep;
+ return $ret;
+}
+
+/**
+ * Returns the specified local text in parsed format
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_locale_xhtml($id){
+ //fetch parsed locale
+ $html = p_cached_output(localeFN($id));
+ return $html;
+}
+
+/**
+ * *** DEPRECATED ***
+ *
+ * use p_cached_output()
+ *
+ * Returns the given file parsed to XHTML
+ *
+ * Uses and creates a cachefile
+ *
+ * @deprecated
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @todo rewrite to use mode instead of hardcoded XHTML
+ */
+function p_cached_xhtml($file){
+ return p_cached_output($file);
+}
+
+/**
+ * Returns the given file parsed into the requested output format
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ */
+function p_cached_output($file, $format='xhtml', $id='') {
+ global $conf;
+
+ $cache = new cache_renderer($id, $file, $format);
+ if ($cache->useCache()) {
+ $parsed = $cache->retrieveCache();
+ if($conf['allowdebug']) $parsed .= "\n<!-- cachefile {$cache->cache} used -->\n";
+ } else {
+ $parsed = p_render($format, p_cached_instructions($file,false,$id), $info);
+
+ if ($info['cache']) {
+ $cache->storeCache($parsed); //save cachefile
+ if($conf['allowdebug']) $parsed .= "\n<!-- no cachefile used, but created -->\n";
+ }else{
+ $cache->removeCache(); //try to delete cachefile
+ if($conf['allowdebug']) $parsed .= "\n<!-- no cachefile used, caching forbidden -->\n";
+ }
+ }
+
+ return $parsed;
+}
+
+/**
+ * Returns the render instructions for a file
+ *
+ * Uses and creates a serialized cache file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_cached_instructions($file,$cacheonly=false,$id='') {
+ global $conf;
+
+ $cache = new cache_instructions($id, $file);
+
+ if ($cacheonly || $cache->useCache()) {
+ return $cache->retrieveCache();
+ } else if (@file_exists($file)) {
+ // no cache - do some work
+ $ins = p_get_instructions(io_readfile($file));
+ $cache->storeCache($ins);
+ return $ins;
+ }
+
+ return NULL;
+}
+
+/**
+ * turns a page into a list of instructions
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_get_instructions($text){
+
+ $modes = p_get_parsermodes();
+
+ // Create the parser
+ $Parser = new Doku_Parser();
+
+ // Add the Handler
+ $Parser->Handler = new Doku_Handler();
+
+ //add modes to parser
+ foreach($modes as $mode){
+ $Parser->addMode($mode['mode'],$mode['obj']);
+ }
+
+ // Do the parsing
+ trigger_event('PARSER_WIKITEXT_PREPROCESS', $text);
+ $p = $Parser->parse($text);
+// dbg($p);
+ return $p;
+}
+
+/**
+ * returns the metadata of a page
+ *
+ * @author Esther Brunner <esther@kaffeehaus.ch>
+ */
+function p_get_metadata($id, $key=false, $render=false){
+ global $INFO;
+
+ if ($id == $INFO['id'] && !empty($INFO['meta'])) {
+ $meta = $INFO['meta'];
+ } else {
+ $file = metaFN($id, '.meta');
+
+ if (@file_exists($file)) $meta = unserialize(io_readFile($file, false));
+ else $meta = array();
+
+ // metadata has never been rendered before - do it!
+ if ($render && !$meta['description']['abstract']){
+ $meta = p_render_metadata($id, $meta);
+ io_saveFile($file, serialize($meta));
+ }
+ }
+
+ // filter by $key
+ if ($key){
+ list($key, $subkey) = explode(' ', $key, 2);
+ if (trim($subkey)) return $meta[$key][$subkey];
+ else return $meta[$key];
+ }
+
+ return $meta;
+}
+
+/**
+ * sets metadata elements of a page
+ *
+ * @author Esther Brunner <esther@kaffeehaus.ch>
+ */
+function p_set_metadata($id, $data, $render=false){
+ if (!is_array($data)) return false;
+
+ $orig = p_get_metadata($id);
+
+ // render metadata first?
+ if ($render) $meta = p_render_metadata($id, $orig);
+ else $meta = $orig;
+
+ // now add the passed metadata
+ $protected = array('description', 'date', 'contributor');
+ foreach ($data as $key => $value){
+
+ // be careful with sub-arrays of $meta['relation']
+ if ($key == 'relation'){
+ foreach ($value as $subkey => $subvalue){
+ $meta[$key][$subkey] = array_merge($meta[$key][$subkey], $subvalue);
+ }
+
+ // be careful with some senisitive arrays of $meta
+ } elseif (in_array($key, $protected)){
+ if (is_array($value)){
+ #FIXME not sure if this is the intended thing:
+ if(!is_array($meta[$key])) $meta[$key] = array($meta[$key]);
+ $meta[$key] = array_merge($meta[$key], $value);
+ }
+
+ // no special treatment for the rest
+ } else {
+ $meta[$key] = $value;
+ }
+ }
+
+ // save only if metadata changed
+ if ($meta == $orig) return true;
+
+ // check if current page metadata has been altered - if so sync the changes
+ global $INFO;
+ if ($id == $INFO['id'] && isset($INFO['meta'])) {
+ $INFO['meta'] = $meta;
+ }
+
+ return io_saveFile(metaFN($id, '.meta'), serialize($meta));
+}
+
+/**
+ * renders the metadata of a page
+ *
+ * @author Esther Brunner <esther@kaffeehaus.ch>
+ */
+function p_render_metadata($id, $orig){
+ require_once DOKU_INC."inc/parser/metadata.php";
+
+ // get instructions
+ $instructions = p_cached_instructions(wikiFN($id),false,$id);
+
+ // set up the renderer
+ $renderer = new Doku_Renderer_metadata();
+ $renderer->meta = $orig;
+
+ // loop through the instructions
+ foreach ($instructions as $instruction){
+ // execute the callback against the renderer
+ call_user_func_array(array(&$renderer, $instruction[0]), $instruction[1]);
+ }
+
+ return $renderer->meta;
+}
+
+/**
+ * returns all available parser syntax modes in correct order
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_get_parsermodes(){
+ global $conf;
+
+ //reuse old data
+ static $modes = null;
+ if($modes != null){
+ return $modes;
+ }
+
+ //import parser classes and mode definitions
+ require_once DOKU_INC . 'inc/parser/parser.php';
+
+ // we now collect all syntax modes and their objects, then they will
+ // be sorted and added to the parser in correct order
+ $modes = array();
+
+ // add syntax plugins
+ $pluginlist = plugin_list('syntax');
+ if(count($pluginlist)){
+ global $PARSER_MODES;
+ $obj = null;
+ foreach($pluginlist as $p){
+ if(!$obj =& plugin_load('syntax',$p)) continue; //attempt to load plugin into $obj
+ $PARSER_MODES[$obj->getType()][] = "plugin_$p"; //register mode type
+ //add to modes
+ $modes[] = array(
+ 'sort' => $obj->getSort(),
+ 'mode' => "plugin_$p",
+ 'obj' => $obj,
+ );
+ unset($obj); //remove the reference
+ }
+ }
+
+ // add default modes
+ $std_modes = array('listblock','preformatted','notoc','nocache',
+ 'header','table','linebreak','footnote','hr',
+ 'unformatted','php','html','code','file','quote',
+ 'internallink','rss','media','externallink',
+ 'emaillink','windowssharelink','eol');
+ if($conf['typography']){
+ $std_modes[] = 'quotes';
+ $std_modes[] = 'multiplyentity';
+ }
+ foreach($std_modes as $m){
+ $class = "Doku_Parser_Mode_$m";
+ $obj = new $class();
+ $modes[] = array(
+ 'sort' => $obj->getSort(),
+ 'mode' => $m,
+ 'obj' => $obj
+ );
+ }
+
+ // add formatting modes
+ $fmt_modes = array('strong','emphasis','underline','monospace',
+ 'subscript','superscript','deleted');
+ foreach($fmt_modes as $m){
+ $obj = new Doku_Parser_Mode_formatting($m);
+ $modes[] = array(
+ 'sort' => $obj->getSort(),
+ 'mode' => $m,
+ 'obj' => $obj
+ );
+ }
+
+ // add modes which need files
+ $obj = new Doku_Parser_Mode_smiley(array_keys(getSmileys()));
+ $modes[] = array('sort' => $obj->getSort(), 'mode' => 'smiley','obj' => $obj );
+ $obj = new Doku_Parser_Mode_acronym(array_keys(getAcronyms()));
+ $modes[] = array('sort' => $obj->getSort(), 'mode' => 'acronym','obj' => $obj );
+ $obj = new Doku_Parser_Mode_entity(array_keys(getEntities()));
+ $modes[] = array('sort' => $obj->getSort(), 'mode' => 'entity','obj' => $obj );
+
+
+ // add optional camelcase mode
+ if($conf['camelcase']){
+ $obj = new Doku_Parser_Mode_camelcaselink();
+ $modes[] = array('sort' => $obj->getSort(), 'mode' => 'camelcaselink','obj' => $obj );
+ }
+
+ //sort modes
+ usort($modes,'p_sort_modes');
+
+ return $modes;
+}
+
+/**
+ * Callback function for usort
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_sort_modes($a, $b){
+ if($a['sort'] == $b['sort']) return 0;
+ return ($a['sort'] < $b['sort']) ? -1 : 1;
+}
+
+/**
+ * Renders a list of instruction to the specified output mode
+ *
+ * In the $info array are informations from the renderer returned
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_render($mode,$instructions,& $info){
+ if(is_null($instructions)) return '';
+
+ if ($mode=='wiki') { msg("Renderer for $mode not valid",-1); return null; } //FIXME!! remove this line when inc/parser/wiki.php works.
+
+ // Create the renderer
+ if(!@file_exists(DOKU_INC."inc/parser/$mode.php")){
+ msg("No renderer for $mode found",-1);
+ return null;
+ }
+
+ require_once DOKU_INC."inc/parser/$mode.php";
+ $rclass = "Doku_Renderer_$mode";
+ if ( !class_exists($rclass) ) {
+ trigger_error("Unable to resolve render class $rclass",E_USER_WARNING);
+ msg("Renderer for $mode not valid",-1);
+ return null;
+ }
+ $Renderer = new $rclass(); #FIXME any way to check for class existance?
+
+ $Renderer->smileys = getSmileys();
+ $Renderer->entities = getEntities();
+ $Renderer->acronyms = getAcronyms();
+ $Renderer->interwiki = getInterwiki();
+ #$Renderer->badwords = getBadWords();
+
+ // Loop through the instructions
+ foreach ( $instructions as $instruction ) {
+ // Execute the callback against the Renderer
+ call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]);
+ }
+
+ //set info array
+ $info = $Renderer->info;
+
+ // Post process and return the output
+ $data = array($mode,& $Renderer->doc);
+ trigger_event('RENDERER_CONTENT_POSTPROCESS',$data);
+ return $Renderer->doc;
+}
+
+/**
+ * Gets the first heading from a file
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function p_get_first_heading($id){
+ global $conf;
+ return $conf['useheading'] ? p_get_metadata($id,'title') : null;
+}
+
+/**
+ * Wrapper for GeSHi Code Highlighter, provides caching of its output
+ *
+ * @author Christopher Smith <chris@jalakai.co.uk>
+ */
+function p_xhtml_cached_geshi($code, $language) {
+ $cache = getCacheName($language.$code,".code");
+
+ if (@file_exists($cache) && !$_REQUEST['purge'] &&
+ (filemtime($cache) > filemtime(DOKU_INC . 'inc/geshi.php'))) {
+
+ $highlighted_code = io_readFile($cache, false);
+ @touch($cache);
+
+ } else {
+
+ require_once(DOKU_INC . 'inc/geshi.php');
+
+ $geshi = new GeSHi($code, strtolower($language), DOKU_INC . 'inc/geshi');
+ $geshi->set_encoding('utf-8');
+ $geshi->enable_classes();
+ $geshi->set_header_type(GESHI_HEADER_PRE);
+
+ #$geshi->set_overall_class("code $language");
+ # geshi 1.0.8.* now adds the language first
+ $geshi->set_overall_class("code");
+
+ $geshi->set_link_target($conf['target']['extern']);
+
+ $highlighted_code = $geshi->parse_code();
+
+ io_saveFile($cache,$highlighted_code);
+ }
+
+ return $highlighted_code;
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
diff --git a/plugins/dokuwiki/inc/pluginutils.php b/plugins/dokuwiki/inc/pluginutils.php
new file mode 100644
index 0000000..183e222
--- /dev/null
+++ b/plugins/dokuwiki/inc/pluginutils.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Utilities for handling plugins
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+// plugin related constants
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+$plugin_types = array('admin','syntax','action');
+
+/**
+ * Returns a list of available plugins of given type
+ *
+ * @param $type string, plugin_type name;
+ * the type of plugin to return,
+ * use empty string for all types
+ * @param $all bool;
+ * false to only return enabled plugins,
+ * true to return both enabled and disabled plugins
+ *
+ * @return array of plugin names
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function plugin_list($type='',$all=false){
+ $plugins = array();
+ if ($dh = opendir(DOKU_PLUGIN)) {
+ while (false !== ($plugin = readdir($dh))) {
+ if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp') continue;
+ if (is_file(DOKU_PLUGIN.$plugin)) continue;
+
+ // if required, skip disabled plugins
+ if (!$all && plugin_isdisabled($plugin)) continue;
+
+ if ($type=='' || @file_exists(DOKU_PLUGIN."$plugin/$type.php")){
+ $plugins[] = $plugin;
+ } else {
+ if ($dp = @opendir(DOKU_PLUGIN."$plugin/$type/")) {
+ while (false !== ($component = readdir($dp))) {
+ if ($component == '.' || $component == '..' || strtolower(substr($component, -4)) != ".php") continue;
+ if (is_file(DOKU_PLUGIN."$plugin/$type/$component")) {
+ $plugins[] = $plugin.'_'.substr($component, 0, -4);
+ }
+ }
+ closedir($dp);
+ }
+ }
+ }
+ closedir($dh);
+ }
+ return $plugins;
+}
+
+/**
+ * Loads the given plugin and creates an object of it
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ *
+ * @param $type string type of plugin to load
+ * @param $name string name of the plugin to load
+ * @return objectreference the plugin object or null on failure
+ */
+function &plugin_load($type,$name){
+ //we keep all loaded plugins available in global scope for reuse
+ global $DOKU_PLUGINS;
+
+
+ //plugin already loaded?
+ if(!empty($DOKU_PLUGINS[$type][$name])){
+ return $DOKU_PLUGINS[$type][$name];
+ }
+
+ //try to load the wanted plugin file
+ if (@file_exists(DOKU_PLUGIN."$name/$type.php")){
+ include_once(DOKU_PLUGIN."$name/$type.php");
+ }else{
+ list($plugin, $component) = preg_split("/_/",$name, 2);
+ if (!$component || !include_once(DOKU_PLUGIN."$plugin/$type/$component.php")) {
+ return null;
+ }
+ }
+
+ //construct class and instantiate
+ $class = $type.'_plugin_'.$name;
+ if (!class_exists($class)) return null;
+
+ $DOKU_PLUGINS[$type][$name] = new $class;
+ return $DOKU_PLUGINS[$type][$name];
+}
+
+function plugin_isdisabled($name) { return @file_exists(DOKU_PLUGIN.$name.'/disabled'); }
+function plugin_enable($name) { return @unlink(DOKU_PLUGIN.$name.'/disabled'); }
+function plugin_disable($name) { return @touch(DOKU_PLUGIN.$name.'/disabled'); }
diff --git a/plugins/dokuwiki/inc/utf8.php b/plugins/dokuwiki/inc/utf8.php
new file mode 100644
index 0000000..bdcf365
--- /dev/null
+++ b/plugins/dokuwiki/inc/utf8.php
@@ -0,0 +1,1276 @@
+<?php
+/**
+ * UTF8 helper functions
+ *
+ * @license LGPL (http://www.gnu.org/copyleft/lesser.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+/**
+ * check for mb_string support
+ */
+if(!defined('UTF8_MBSTRING')){
+ if(function_exists('mb_substr') && !defined('UTF8_NOMBSTRING')){
+ define('UTF8_MBSTRING',1);
+ }else{
+ define('UTF8_MBSTRING',0);
+ }
+}
+
+if(UTF8_MBSTRING){ mb_internal_encoding('UTF-8'); }
+
+
+/**
+ * URL-Encode a filename to allow unicodecharacters
+ *
+ * Slashes are not encoded
+ *
+ * When the second parameter is true the string will
+ * be encoded only if non ASCII characters are detected -
+ * This makes it safe to run it multiple times on the
+ * same string (default is true)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see urlencode
+ */
+if (!function_exists('utf8_encodeFN')){
+function utf8_encodeFN($file,$safe=true){
+ if($safe && preg_match('#^[a-zA-Z0-9/_\-.%]+$#',$file)){
+ return $file;
+ }
+ $file = urlencode($file);
+ $file = str_replace('%2F','/',$file);
+ return $file;
+}
+}
+
+/**
+ * URL-Decode a filename
+ *
+ * This is just a wrapper around urldecode
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see urldecode
+ */
+if (!function_exists('utf8_decodeFN')){
+function utf8_decodeFN($file){
+ $file = urldecode($file);
+ return $file;
+}
+}
+
+/**
+ * Checks if a string contains 7bit ASCII only
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if (!function_exists('utf8_isASCII')){
+function utf8_isASCII($str){
+ for($i=0; $i<strlen($str); $i++){
+ if(ord($str{$i}) >127) return false;
+ }
+ return true;
+}
+}
+
+/**
+ * Strips all highbyte chars
+ *
+ * Returns a pure ASCII7 string
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if (!function_exists('utf8_strip')){
+function utf8_strip($str){
+ $ascii = '';
+ for($i=0; $i<strlen($str); $i++){
+ if(ord($str{$i}) <128){
+ $ascii .= $str{$i};
+ }
+ }
+ return $ascii;
+}
+}
+
+/**
+ * Tries to detect if a string is in Unicode encoding
+ *
+ * @author <bmorel@ssi.fr>
+ * @link http://www.php.net/manual/en/function.utf8-encode.php
+ */
+if (!function_exists('utf8_check')){
+function utf8_check($Str) {
+ for ($i=0; $i<strlen($Str); $i++) {
+ $b = ord($Str[$i]);
+ if ($b < 0x80) continue; # 0bbbbbbb
+ elseif (($b & 0xE0) == 0xC0) $n=1; # 110bbbbb
+ elseif (($b & 0xF0) == 0xE0) $n=2; # 1110bbbb
+ elseif (($b & 0xF8) == 0xF0) $n=3; # 11110bbb
+ elseif (($b & 0xFC) == 0xF8) $n=4; # 111110bb
+ elseif (($b & 0xFE) == 0xFC) $n=5; # 1111110b
+ else return false; # Does not match any model
+ for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
+ if ((++$i == strlen($Str)) || ((ord($Str[$i]) & 0xC0) != 0x80))
+ return false;
+ }
+ }
+ return true;
+}
+}
+
+/**
+ * Unicode aware replacement for strlen()
+ *
+ * utf8_decode() converts characters that are not in ISO-8859-1
+ * to '?', which, for the purpose of counting, is alright - It's
+ * even faster than mb_strlen.
+ *
+ * @author <chernyshevsky at hotmail dot com>
+ * @see strlen()
+ * @see utf8_decode()
+ */
+if(!function_exists('utf8_strlen')){
+function utf8_strlen($string){
+ return strlen(utf8_decode($string));
+}
+}
+
+/**
+ * UTF-8 aware alternative to substr
+ *
+ * Return part of a string given character offset (and optionally length)
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @author Chris Smith <chris@jalakai.co.uk>
+ * @param string
+ * @param integer number of UTF-8 characters offset (from left)
+ * @param integer (optional) length in UTF-8 characters from offset
+ * @return mixed string or FALSE if failure
+ */
+if(!function_exists('utf8_substr')){
+function utf8_substr($str, $offset, $length = null) {
+ if(UTF8_MBSTRING){
+ if( $length === null ){
+ return mb_substr($str, $offset);
+ }else{
+ return mb_substr($str, $offset, $length);
+ }
+ }
+
+ /*
+ * Notes:
+ *
+ * no mb string support, so we'll use pcre regex's with 'u' flag
+ * pcre only supports repetitions of less than 65536, in order to accept up to MAXINT values for
+ * offset and length, we'll repeat a group of 65535 characters when needed (ok, up to MAXINT-65536)
+ *
+ * substr documentation states false can be returned in some cases (e.g. offset > string length)
+ * mb_substr never returns false, it will return an empty string instead.
+ *
+ * calculating the number of characters in the string is a relatively expensive operation, so
+ * we only carry it out when necessary. It isn't necessary for +ve offsets and no specified length
+ */
+
+ // cast parameters to appropriate types to avoid multiple notices/warnings
+ $str = (string)$str; // generates E_NOTICE for PHP4 objects, but not PHP5 objects
+ $offset = (int)$offset;
+ if (!is_null($length)) $length = (int)$length;
+
+ // handle trivial cases
+ if ($length === 0) return '';
+ if ($offset < 0 && $length < 0 && $length < $offset) return '';
+
+ $offset_pattern = '';
+ $length_pattern = '';
+
+ // normalise -ve offsets (we could use a tail anchored pattern, but they are horribly slow!)
+ if ($offset < 0) {
+ $strlen = strlen(utf8_decode($str)); // see notes
+ $offset = $strlen + $offset;
+ if ($offset < 0) $offset = 0;
+ }
+
+ // establish a pattern for offset, a non-captured group equal in length to offset
+ if ($offset > 0) {
+ $Ox = (int)($offset/65535);
+ $Oy = $offset%65535;
+
+ if ($Ox) $offset_pattern = '(?:.{65535}){'.$Ox.'}';
+ $offset_pattern = '^(?:'.$offset_pattern.'.{'.$Oy.'})';
+ } else {
+ $offset_pattern = '^'; // offset == 0; just anchor the pattern
+ }
+
+ // establish a pattern for length
+ if (is_null($length)) {
+ $length_pattern = '(.*)$'; // the rest of the string
+ } else {
+
+ if (!isset($strlen)) $strlen = strlen(utf8_decode($str)); // see notes
+ if ($offset > $strlen) return ''; // another trivial case
+
+ if ($length > 0) {
+
+ $length = min($strlen-$offset, $length); // reduce any length that would go passed the end of the string
+
+ $Lx = (int)($length/65535);
+ $Ly = $length%65535;
+
+ // +ve length requires ... a captured group of length characters
+ if ($Lx) $length_pattern = '(?:.{65535}){'.$Lx.'}';
+ $length_pattern = '('.$length_pattern.'.{'.$Ly.'})';
+
+ } else if ($length < 0) {
+
+ if ($length < ($offset - $strlen)) return '';
+
+ $Lx = (int)((-$length)/65535);
+ $Ly = (-$length)%65535;
+
+ // -ve length requires ... capture everything except a group of -length characters
+ // anchored at the tail-end of the string
+ if ($Lx) $length_pattern = '(?:.{65535}){'.$Lx.'}';
+ $length_pattern = '(.*)(?:'.$length_pattern.'.{'.$Ly.'})$';
+ }
+ }
+
+ if (!preg_match('#'.$offset_pattern.$length_pattern.'#us',$str,$match)) return '';
+ return $match[1];
+}
+}
+
+/**
+ * Unicode aware replacement for substr_replace()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see substr_replace()
+ */
+if (!function_exists('utf8_substr_replace')){
+function utf8_substr_replace($string, $replacement, $start , $length=0 ){
+ $ret = '';
+ if($start>0) $ret .= utf8_substr($string, 0, $start);
+ $ret .= $replacement;
+ $ret .= utf8_substr($string, $start+$length);
+ return $ret;
+}
+}
+
+/**
+ * Unicode aware replacement for explode
+ *
+ * @TODO support third limit arg
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @see explode();
+ */
+if (!function_exists('utf8_explode')){
+function utf8_explode($sep, $str) {
+ if ( $sep == '' ) {
+ trigger_error('Empty delimiter',E_USER_WARNING);
+ return FALSE;
+ }
+
+ return preg_split('!'.preg_quote($sep,'!').'!u',$str);
+}
+}
+
+/**
+ * Unicode aware replacement for strrepalce()
+ *
+ * @todo support PHP5 count (fourth arg)
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @see strreplace();
+ */
+if (!function_exists('utf8_str_replace')){
+function utf8_str_replace($s,$r,$str){
+ if(!is_array($s)){
+ $s = '!'.preg_quote($s,'!').'!u';
+ }else{
+ foreach ($s as $k => $v) {
+ $s[$k] = '!'.preg_quote($v).'!u';
+ }
+ }
+ return preg_replace($s,$r,$str);
+}
+}
+
+/**
+ * Unicode aware replacement for ltrim()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see ltrim()
+ * @return string
+ */
+if (!function_exists('utf8_ltrim')){
+function utf8_ltrim($str,$charlist=''){
+ if($charlist == '') return ltrim($str);
+
+ //quote charlist for use in a characterclass
+ $charlist = preg_replace('!([\\\\\\-\\]\\[/])!','\\\${1}',$charlist);
+
+ return preg_replace('/^['.$charlist.']+/u','',$str);
+}
+}
+
+/**
+ * Unicode aware replacement for rtrim()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see rtrim()
+ * @return string
+ */
+if (!function_exists('utf8_rtrim')){
+function utf8_rtrim($str,$charlist=''){
+ if($charlist == '') return rtrim($str);
+
+ //quote charlist for use in a characterclass
+ $charlist = preg_replace('!([\\\\\\-\\]\\[/])!','\\\${1}',$charlist);
+
+ return preg_replace('/['.$charlist.']+$/u','',$str);
+}
+}
+
+/**
+ * Unicode aware replacement for trim()
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see trim()
+ * @return string
+ */
+if (!function_exists('utf8_trim')){
+function utf8_trim($str,$charlist='') {
+ if($charlist == '') return trim($str);
+
+ return utf8_ltrim(utf8_rtrim($str));
+}
+}
+
+
+/**
+ * This is a unicode aware replacement for strtolower()
+ *
+ * Uses mb_string extension if available
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see strtolower()
+ * @see utf8_strtoupper()
+ */
+if(!function_exists('utf8_strtolower')){
+function utf8_strtolower($string){
+ if(UTF8_MBSTRING) return mb_strtolower($string,'utf-8');
+
+ global $UTF8_UPPER_TO_LOWER;
+ $uni = utf8_to_unicode($string);
+ $cnt = count($uni);
+ for ($i=0; $i < $cnt; $i++){
+ if($UTF8_UPPER_TO_LOWER[$uni[$i]]){
+ $uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
+ }
+ }
+ return unicode_to_utf8($uni);
+}
+}
+
+/**
+ * This is a unicode aware replacement for strtoupper()
+ *
+ * Uses mb_string extension if available
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see strtoupper()
+ * @see utf8_strtoupper()
+ */
+if (!function_exists('utf8_strtoupper')){
+function utf8_strtoupper($string){
+ if(UTF8_MBSTRING) return mb_strtoupper($string,'utf-8');
+
+ global $UTF8_LOWER_TO_UPPER;
+ $uni = utf8_to_unicode($string);
+ $cnt = count($uni);
+ for ($i=0; $i < $cnt; $i++){
+ if($UTF8_LOWER_TO_UPPER[$uni[$i]]){
+ $uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
+ }
+ }
+ return unicode_to_utf8($uni);
+}
+}
+
+/**
+ * Replace accented UTF-8 characters by unaccented ASCII-7 equivalents
+ *
+ * Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1)
+ * letters. Default is to deaccent both cases ($case = 0)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if (!function_exists('utf8_deaccent')){
+function utf8_deaccent($string,$case=0){
+ if($case <= 0){
+ global $UTF8_LOWER_ACCENTS;
+ $string = str_replace(array_keys($UTF8_LOWER_ACCENTS),array_values($UTF8_LOWER_ACCENTS),$string);
+ }
+ if($case >= 0){
+ global $UTF8_UPPER_ACCENTS;
+ $string = str_replace(array_keys($UTF8_UPPER_ACCENTS),array_values($UTF8_UPPER_ACCENTS),$string);
+ }
+ return $string;
+}
+}
+
+/**
+ * Romanize a non-latin string
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+if (!function_exists('utf8_romanize')){
+function utf8_romanize($string){
+ if(utf8_isASCII($string)) return $string; //nothing to do
+
+ global $UTF8_ROMANIZATION;
+ return strtr($string,$UTF8_ROMANIZATION);
+}
+}
+
+/**
+ * Removes special characters (nonalphanumeric) from a UTF-8 string
+ *
+ * This function adds the controlchars 0x00 to 0x19 to the array of
+ * stripped chars (they are not included in $UTF8_SPECIAL_CHARS)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $string The UTF8 string to strip of special chars
+ * @param string $repl Replace special with this string
+ * @param string $additional Additional chars to strip (used in regexp char class)
+ */
+if (!function_exists('utf8_stripspecials')){
+function utf8_stripspecials($string,$repl='',$additional=''){
+ global $UTF8_SPECIAL_CHARS;
+ global $UTF8_SPECIAL_CHARS2;
+
+ static $specials = null;
+ if(is_null($specials)){
+# $specials = preg_quote(unicode_to_utf8($UTF8_SPECIAL_CHARS), '/');
+ $specials = preg_quote($UTF8_SPECIAL_CHARS2, '/');
+ }
+
+ return preg_replace('/['.$additional.'\x00-\x19'.$specials.']/u',$repl,$string);
+}
+}
+
+/**
+ * This is an Unicode aware replacement for strpos
+ *
+ * Uses mb_string extension if available
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @see strpos()
+ */
+if (!function_exists('utf8_strpos')){
+function utf8_strpos($haystack, $needle,$offset=0) {
+ if(UTF8_MBSTRING) return mb_strpos($haystack,$needle,$offset,'utf-8');
+
+ if(!$offset){
+ $ar = utf8_explode($needle, $haystack);
+ if ( count($ar) > 1 ) {
+ return utf8_strlen($ar[0]);
+ }
+ return false;
+ }else{
+ if ( !is_int($offset) ) {
+ trigger_error('Offset must be an integer',E_USER_WARNING);
+ return false;
+ }
+
+ $haystack = utf8_substr($haystack, $offset);
+
+ if ( false !== ($pos = utf8_strpos($haystack,$needle))){
+ return $pos + $offset;
+ }
+ return false;
+ }
+}
+}
+
+/**
+ * Encodes UTF-8 characters to HTML entities
+ *
+ * @author <vpribish at shopping dot com>
+ * @link http://www.php.net/manual/en/function.utf8-decode.php
+ */
+if (!function_exists('utf8_tohtml')){
+function utf8_tohtml ($str) {
+ $ret = '';
+ $max = strlen($str);
+ $last = 0; // keeps the index of the last regular character
+ for ($i=0; $i<$max; $i++) {
+ $c = $str{$i};
+ $c1 = ord($c);
+ if ($c1>>5 == 6) { // 110x xxxx, 110 prefix for 2 bytes unicode
+ $ret .= substr($str, $last, $i-$last); // append all the regular characters we've passed
+ $c1 &= 31; // remove the 3 bit two bytes prefix
+ $c2 = ord($str{++$i}); // the next byte
+ $c2 &= 63; // remove the 2 bit trailing byte prefix
+ $c2 |= (($c1 & 3) << 6); // last 2 bits of c1 become first 2 of c2
+ $c1 >>= 2; // c1 shifts 2 to the right
+ $ret .= '&#' . ($c1 * 100 + $c2) . ';'; // this is the fastest string concatenation
+ $last = $i+1;
+ }
+ }
+ return $ret . substr($str, $last, $i); // append the last batch of regular characters
+}
+}
+
+/**
+ * Takes an UTF-8 string and returns an array of ints representing the
+ * Unicode characters. Astral planes are supported ie. the ints in the
+ * output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
+ * are not allowed.
+ *
+ * If $strict is set to true the function returns false if the input
+ * string isn't a valid UTF-8 octet sequence and raises a PHP error at
+ * level E_USER_WARNING
+ *
+ * Note: this function has been modified slightly in this library to
+ * trigger errors on encountering bad bytes
+ *
+ * @author <hsivonen@iki.fi>
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @param string UTF-8 encoded string
+ * @param boolean Check for invalid sequences?
+ * @return mixed array of unicode code points or FALSE if UTF-8 invalid
+ * @see unicode_to_utf8
+ * @link http://hsivonen.iki.fi/php-utf8/
+ * @link http://sourceforge.net/projects/phputf8/
+ */
+if (!function_exists('utf8_to_unicode')){
+function utf8_to_unicode($str,$strict=false) {
+ $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
+
+ $out = array();
+
+ $len = strlen($str);
+
+ for($i = 0; $i < $len; $i++) {
+
+ $in = ord($str{$i});
+
+ if ( $mState == 0) {
+
+ // 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.
+ $out[] = $in;
+ $mBytes = 1;
+
+ } else if (0xC0 == (0xE0 & ($in))) {
+ // First octet of 2 octet sequence
+ $mUcs4 = ($in);
+ $mUcs4 = ($mUcs4 & 0x1F) << 6;
+ $mState = 1;
+ $mBytes = 2;
+
+ } else if (0xE0 == (0xF0 & ($in))) {
+ // First octet of 3 octet sequence
+ $mUcs4 = ($in);
+ $mUcs4 = ($mUcs4 & 0x0F) << 12;
+ $mState = 2;
+ $mBytes = 3;
+
+ } else if (0xF0 == (0xF8 & ($in))) {
+ // First octet of 4 octet sequence
+ $mUcs4 = ($in);
+ $mUcs4 = ($mUcs4 & 0x07) << 18;
+ $mState = 3;
+ $mBytes = 4;
+
+ } else if (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;
+
+ } else if (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;
+
+ } elseif($strict) {
+ /* Current octet is neither in the US-ASCII range nor a legal first
+ * octet of a multi-octet sequence.
+ */
+ trigger_error(
+ 'utf8_to_unicode: Illegal sequence identifier '.
+ 'in UTF-8 at byte '.$i,
+ E_USER_WARNING
+ );
+ return FALSE;
+
+ }
+
+ } 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;
+
+ /**
+ * End of the multi-octet sequence. mUcs4 now contains the final
+ * Unicode codepoint to be output
+ */
+ if (0 == --$mState) {
+
+ /*
+ * 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 are illegal
+ (($mUcs4 & 0xFFFFF800) == 0xD800) ||
+ // Codepoints outside the Unicode range are illegal
+ ($mUcs4 > 0x10FFFF)) {
+
+ if($strict){
+ trigger_error(
+ 'utf8_to_unicode: Illegal sequence or codepoint '.
+ 'in UTF-8 at byte '.$i,
+ E_USER_WARNING
+ );
+
+ return FALSE;
+ }
+
+ }
+
+ if (0xFEFF != $mUcs4) {
+ // BOM is legal but we don't want to output it
+ $out[] = $mUcs4;
+ }
+
+ //initialize UTF8 cache
+ $mState = 0;
+ $mUcs4 = 0;
+ $mBytes = 1;
+ }
+
+ } elseif($strict) {
+ /**
+ *((0xC0 & (*in) != 0x80) && (mState != 0))
+ * Incomplete multi-octet sequence.
+ */
+ trigger_error(
+ 'utf8_to_unicode: Incomplete multi-octet '.
+ ' sequence in UTF-8 at byte '.$i,
+ E_USER_WARNING
+ );
+
+ return FALSE;
+ }
+ }
+ }
+ return $out;
+}
+}
+
+/**
+ * Takes an array of ints representing the Unicode characters and returns
+ * a UTF-8 string. Astral planes are supported ie. the ints in the
+ * input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
+ * are not allowed.
+ *
+ * If $strict is set to true the function returns false if the input
+ * array contains ints that represent surrogates or are outside the
+ * Unicode range and raises a PHP error at level E_USER_WARNING
+ *
+ * Note: this function has been modified slightly in this library to use
+ * output buffering to concatenate the UTF-8 string (faster) as well as
+ * reference the array by it's keys
+ *
+ * @param array of unicode code points representing a string
+ * @param boolean Check for invalid sequences?
+ * @return mixed UTF-8 string or FALSE if array contains invalid code points
+ * @author <hsivonen@iki.fi>
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @see utf8_to_unicode
+ * @link http://hsivonen.iki.fi/php-utf8/
+ * @link http://sourceforge.net/projects/phputf8/
+ */
+if (!function_exists('unicode_to_utf8')){
+function unicode_to_utf8($arr,$strict=false) {
+ if (!is_array($arr)) return '';
+ ob_start();
+
+ foreach (array_keys($arr) as $k) {
+
+ # ASCII range (including control chars)
+ if ( ($arr[$k] >= 0) && ($arr[$k] <= 0x007f) ) {
+
+ echo chr($arr[$k]);
+
+ # 2 byte sequence
+ } else if ($arr[$k] <= 0x07ff) {
+
+ echo chr(0xc0 | ($arr[$k] >> 6));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+
+ # Byte order mark (skip)
+ } else if($arr[$k] == 0xFEFF) {
+
+ // nop -- zap the BOM
+
+ # Test for illegal surrogates
+ } else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) {
+
+ // found a surrogate
+ if($strict){
+ trigger_error(
+ 'unicode_to_utf8: Illegal surrogate '.
+ 'at index: '.$k.', value: '.$arr[$k],
+ E_USER_WARNING
+ );
+ return FALSE;
+ }
+
+ # 3 byte sequence
+ } else if ($arr[$k] <= 0xffff) {
+
+ echo chr(0xe0 | ($arr[$k] >> 12));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+
+ # 4 byte sequence
+ } else if ($arr[$k] <= 0x10ffff) {
+
+ echo chr(0xf0 | ($arr[$k] >> 18));
+ echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+ echo chr(0x80 | ($arr[$k] & 0x3f));
+
+ } elseif($strict) {
+
+ trigger_error(
+ 'unicode_to_utf8: Codepoint out of Unicode range '.
+ 'at index: '.$k.', value: '.$arr[$k],
+ E_USER_WARNING
+ );
+
+ // out of range
+ return FALSE;
+ }
+ }
+
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+}
+}
+
+/**
+ * UTF-8 to UTF-16BE conversion.
+ *
+ * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits
+ */
+if (!function_exists('utf8_to_utf16be')){
+function utf8_to_utf16be(&$str, $bom = false) {
+ $out = $bom ? "\xFE\xFF" : '';
+ if(UTF8_MBSTRING) return $out.mb_convert_encoding($str,'UTF-16BE','UTF-8');
+
+ $uni = utf8_to_unicode($str);
+ foreach($uni as $cp){
+ $out .= pack('n',$cp);
+ }
+ return $out;
+}
+}
+
+/**
+ * UTF-8 to UTF-16BE conversion.
+ *
+ * Maybe really UCS-2 without mb_string due to utf8_to_unicode limits
+ */
+if (!function_exists('utf16be_to_utf8')){
+function utf16be_to_utf8(&$str) {
+ $uni = unpack('n*',$str);
+ return unicode_to_utf8($uni);
+}
+}
+
+/**
+ * Replace bad bytes with an alternative character
+ *
+ * ASCII character is recommended for replacement char
+ *
+ * PCRE Pattern to locate bad bytes in a UTF-8 string
+ * Comes from W3 FAQ: Multilingual Forms
+ * Note: modified to include full ASCII range including control chars
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ * @see http://www.w3.org/International/questions/qa-forms-utf-8
+ * @param string to search
+ * @param string to replace bad bytes with (defaults to '?') - use ASCII
+ * @return string
+ */
+if (!function_exists('utf8_bad_replace')){
+function utf8_bad_replace($str, $replace = '') {
+ $UTF8_BAD =
+ '([\x00-\x7F]'. # ASCII (including control chars)
+ '|[\xC2-\xDF][\x80-\xBF]'. # non-overlong 2-byte
+ '|\xE0[\xA0-\xBF][\x80-\xBF]'. # excluding overlongs
+ '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'. # straight 3-byte
+ '|\xED[\x80-\x9F][\x80-\xBF]'. # excluding surrogates
+ '|\xF0[\x90-\xBF][\x80-\xBF]{2}'. # planes 1-3
+ '|[\xF1-\xF3][\x80-\xBF]{3}'. # planes 4-15
+ '|\xF4[\x80-\x8F][\x80-\xBF]{2}'. # plane 16
+ '|(.{1}))'; # invalid byte
+ ob_start();
+ while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) {
+ if ( !isset($matches[2])) {
+ echo $matches[0];
+ } else {
+ echo $replace;
+ }
+ $str = substr($str,strlen($matches[0]));
+ }
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+}
+}
+
+/**
+ * adjust a byte index into a utf8 string to a utf8 character boundary
+ *
+ * @param $str string utf8 character string
+ * @param $i int byte index into $str
+ * @param $next bool direction to search for boundary,
+ * false = up (current character)
+ * true = down (next character)
+ *
+ * @return int byte index into $str now pointing to a utf8 character boundary
+ *
+ * @author chris smith <chris@jalakai.co.uk>
+ */
+if (!function_exists('utf8_correctIdx')){
+function utf8_correctIdx(&$str,$i,$next=false) {
+
+ if ($i <= 0) return 0;
+
+ $limit = strlen($str);
+ if ($i>=$limit) return $limit;
+
+ if ($next) {
+ while (($i<$limit) && ((ord($str[$i]) & 0xC0) == 0x80)) $i++;
+ } else {
+ while ($i && ((ord($str[$i]) & 0xC0) == 0x80)) $i--;
+ }
+
+ return $i;
+}
+}
+
+// only needed if no mb_string available
+if(!UTF8_MBSTRING){
+
+ /**
+ * UTF-8 Case lookup table
+ *
+ * This lookuptable defines the upper case letters to their correspponding
+ * lower case letter in UTF-8
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ global $UTF8_LOWER_TO_UPPER;
+ $UTF8_LOWER_TO_UPPER = array(
+ 0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
+ 0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
+ 0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
+ 0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
+ 0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
+ 0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
+ 0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
+ 0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
+ 0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
+ 0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
+ 0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
+ 0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
+ 0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
+ 0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
+ 0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
+ 0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
+ 0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
+ 0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
+ 0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
+ 0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
+ 0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
+ 0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
+ 0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
+ 0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
+ 0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
+ 0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
+ 0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
+ 0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
+ 0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
+ 0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
+ 0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
+ 0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
+ 0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
+ 0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
+ 0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
+ 0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
+ 0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
+ 0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
+ 0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
+ 0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
+ 0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
+ 0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
+ 0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
+ );
+
+ /**
+ * UTF-8 Case lookup table
+ *
+ * This lookuptable defines the lower case letters to their correspponding
+ * upper case letter in UTF-8 (it does so by flipping $UTF8_LOWER_TO_UPPER)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+ global $UTF8_UPPER_TO_LOWER;
+ $UTF8_UPPER_TO_LOWER = @array_flip($UTF8_LOWER_TO_UPPER);
+
+} // end of case lookup tables
+
+
+/**
+ * UTF-8 lookup table for lower case accented letters
+ *
+ * This lookuptable defines replacements for accented characters from the ASCII-7
+ * range. This are lower case letters only.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see utf8_deaccent()
+ */
+global $UTF8_LOWER_ACCENTS;
+$UTF8_LOWER_ACCENTS = array(
+ 'à' => 'a', 'ô' => 'o', 'Ä' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'Å¡' => 's', 'Æ¡' => 'o',
+ 'ß' => 'ss', 'ă' => 'a', 'Å™' => 'r', 'È›' => 't', 'ň' => 'n', 'Ä' => 'a', 'Ä·' => 'k',
+ 'Å' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'á¹—' => 'p', 'ó' => 'o',
+ 'ú' => 'u', 'Ä›' => 'e', 'é' => 'e', 'ç' => 'c', 'áº' => 'w', 'Ä‹' => 'c', 'õ' => 'o',
+ 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+ 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+ 'Å«' => 'u', 'Ä' => 'c', 'ö' => 'oe', 'è' => 'e', 'Å·' => 'y', 'Ä…' => 'a', 'Å‚' => 'l',
+ 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+ 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+ 'ŗ' => 'r', 'ä' => 'ae', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'ue', 'ò' => 'o',
+ 'Ä“' => 'e', 'ñ' => 'n', 'Å„' => 'n', 'Ä¥' => 'h', 'Ä' => 'g', 'Ä‘' => 'd', 'ĵ' => 'j',
+ 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+ 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+ 'á¹' => 'm', 'Å' => 'o', 'Ä©' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+ 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e',
+);
+
+/**
+ * UTF-8 lookup table for upper case accented letters
+ *
+ * This lookuptable defines replacements for accented characters from the ASCII-7
+ * range. This are upper case letters only.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see utf8_deaccent()
+ */
+global $UTF8_UPPER_ACCENTS;
+$UTF8_UPPER_ACCENTS = array(
+ 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+ 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K',
+ 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+ 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+ 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+ 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+ 'Ū' => 'U', 'ÄŒ' => 'C', 'Ö' => 'Oe', 'È' => 'E', 'Ŷ' => 'Y', 'Ä„' => 'A', 'Å' => 'L',
+ 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+ 'Ẃ' => 'W', 'Ḃ' => 'B', 'Ã…' => 'A', 'ÃŒ' => 'I', 'Ã' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+ 'Å–' => 'R', 'Ä' => 'Ae', 'Ã' => 'I', 'Å”' => 'R', 'Ê' => 'E', 'Ãœ' => 'Ue', 'Ã’' => 'O',
+ 'Ä’' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Äœ' => 'G', 'Ä' => 'D', 'Ä´' => 'J',
+ 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Å¢' => 'T', 'Ã' => 'Y', 'Å' => 'O',
+ 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+ 'á¹€' => 'M', 'ÅŒ' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Ä®' => 'I', 'Ź' => 'Z', 'Ã' => 'A',
+ 'Û' => 'U', 'Þ' => 'Th', 'Ã' => 'Dh', 'Æ' => 'Ae', 'Ä”' => 'E',
+);
+
+/**
+ * UTF-8 array of common special characters
+ *
+ * This array should contain all special characters (not a letter or digit)
+ * defined in the various local charsets - it's not a complete list of non-alphanum
+ * characters in UTF-8. It's not perfect but should match most cases of special
+ * chars.
+ *
+ * The controlchars 0x00 to 0x19 are _not_ included in this array. The space 0x20 is!
+ * These chars are _not_ in the array either: _ (0x5f), : 0x3a, . 0x2e, - 0x2d, * 0x2a
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @see utf8_stripspecials()
+ */
+global $UTF8_SPECIAL_CHARS;
+$UTF8_SPECIAL_CHARS = array(
+ 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023,
+ 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002b, 0x002c,
+ 0x002f, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x005b,
+ 0x005c, 0x005d, 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e,
+ 0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088,
+ 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092,
+ 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c,
+ 0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
+ 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0,
+ 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba,
+ 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00d7, 0x00f7, 0x02c7, 0x02d8, 0x02d9,
+ 0x02da, 0x02db, 0x02dc, 0x02dd, 0x0300, 0x0301, 0x0303, 0x0309, 0x0323, 0x0384,
+ 0x0385, 0x0387, 0x03b2, 0x03c6, 0x03d1, 0x03d2, 0x03d5, 0x03d6, 0x05b0, 0x05b1,
+ 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05bb, 0x05bc,
+ 0x05bd, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f3, 0x05f4, 0x060c,
+ 0x061b, 0x061f, 0x0640, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651,
+ 0x0652, 0x066a, 0x0e3f, 0x200c, 0x200d, 0x200e, 0x200f, 0x2013, 0x2014, 0x2015,
+ 0x2017, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2022,
+ 0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203a, 0x2044, 0x20a7, 0x20aa, 0x20ab,
+ 0x20ac, 0x2116, 0x2118, 0x2122, 0x2126, 0x2135, 0x2190, 0x2191, 0x2192, 0x2193,
+ 0x2194, 0x2195, 0x21b5, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x2200, 0x2202,
+ 0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220b, 0x220f, 0x2211, 0x2212,
+ 0x2215, 0x2217, 0x2219, 0x221a, 0x221d, 0x221e, 0x2220, 0x2227, 0x2228, 0x2229,
+ 0x222a, 0x222b, 0x2234, 0x223c, 0x2245, 0x2248, 0x2260, 0x2261, 0x2264, 0x2265,
+ 0x2282, 0x2283, 0x2284, 0x2286, 0x2287, 0x2295, 0x2297, 0x22a5, 0x22c5, 0x2310,
+ 0x2320, 0x2321, 0x2329, 0x232a, 0x2469, 0x2500, 0x2502, 0x250c, 0x2510, 0x2514,
+ 0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2550, 0x2551, 0x2552, 0x2553,
+ 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d,
+ 0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567,
+ 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590,
+ 0x2591, 0x2592, 0x2593, 0x25a0, 0x25b2, 0x25bc, 0x25c6, 0x25ca, 0x25cf, 0x25d7,
+ 0x2605, 0x260e, 0x261b, 0x261e, 0x2660, 0x2663, 0x2665, 0x2666, 0x2701, 0x2702,
+ 0x2703, 0x2704, 0x2706, 0x2707, 0x2708, 0x2709, 0x270c, 0x270d, 0x270e, 0x270f,
+ 0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719,
+ 0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, 0x2720, 0x2721, 0x2722, 0x2723,
+ 0x2724, 0x2725, 0x2726, 0x2727, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e,
+ 0x272f, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, 0x2738,
+ 0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, 0x2740, 0x2741, 0x2742,
+ 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748, 0x2749, 0x274a, 0x274b, 0x274d,
+ 0x274f, 0x2750, 0x2751, 0x2752, 0x2756, 0x2758, 0x2759, 0x275a, 0x275b, 0x275c,
+ 0x275d, 0x275e, 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, 0x277f,
+ 0x2789, 0x2793, 0x2794, 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e,
+ 0x279f, 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, 0x27a8,
+ 0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, 0x27b1, 0x27b2, 0x27b3,
+ 0x27b4, 0x27b5, 0x27b6, 0x27b7, 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd,
+ 0x27be, 0xf6d9, 0xf6da, 0xf6db, 0xf8d7, 0xf8d8, 0xf8d9, 0xf8da, 0xf8db, 0xf8dc,
+ 0xf8dd, 0xf8de, 0xf8df, 0xf8e0, 0xf8e1, 0xf8e2, 0xf8e3, 0xf8e4, 0xf8e5, 0xf8e6,
+ 0xf8e7, 0xf8e8, 0xf8e9, 0xf8ea, 0xf8eb, 0xf8ec, 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0,
+ 0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, 0xf8f5, 0xf8f6, 0xf8f7, 0xf8f8, 0xf8f9, 0xf8fa,
+ 0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0xfe7c, 0xfe7d,
+);
+
+// utf8 version of above data
+global $UTF8_SPECIAL_CHARS2;
+$UTF8_SPECIAL_CHARS2 =
+ ' !"#$%&\'()+,/;<=>?@[\]^`{|}~€Â‚ƒ„…†‡ˆ‰Š‹ŒÂŽ‘’“”•�'.
+ '�—˜™š›œÂžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½�'.
+ '�¿×÷ˇ˘˙˚˛˜ËÌ€Ị̀̃̉΄΅·βφϑϒϕϖְֱֲֳִֵֶַָֹֻּֽ־ֿ�'.
+ '�×ׂ׃׳״،؛؟ـًٌÙÙŽÙÙّْ٪฿‌â€â€Žâ€â€“—―‗‘’‚“â€ï¿½'.
+ '��†‡•…‰′″‹›â„₧₪₫€№℘™Ωℵâ†â†‘→↓↔↕↵'.
+ 'â‡â‡‘⇒⇓⇔∀∂∃∅∆∇∈∉∋âˆâˆ‘−∕∗∙√âˆâˆžâˆ âˆ§âˆ¨ï¿½'.
+ '�∪∫∴∼≅≈≠≡≤≥⊂⊃⊄⊆⊇⊕⊗⊥⋅âŒâŒ âŒ¡âŒ©âŒªâ‘©â”€ï¿½'.
+ '��┌â”└┘├┤┬┴┼â•â•‘╒╓╔╕╖╗╘╙╚╛╜â•â•žâ•Ÿâ• '.
+ '╡╢╣╤╥╦╧╨╩╪╫╬▀▄█▌â–░▒▓■▲▼◆◊â—�'.
+ '�★☎☛☞♠♣♥♦âœâœ‚✃✄✆✇✈✉✌âœâœŽâœâœâœ‘✒✓✔✕�'.
+ '��✗✘✙✚✛✜âœâœžâœŸâœ âœ¡âœ¢âœ£âœ¤âœ¥âœ¦âœ§âœ©âœªâœ«âœ¬âœ­âœ®âœ¯âœ°âœ±'.
+ '✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹ï¿½'.
+ '�âââ‘â’â–â˜â™âšâ›âœââžâ¡â¢â£â¤â¥â¦â§â¿âž‰âž“➔➘➙➚�'.
+ '��➜âžâžžâžŸâž âž¡âž¢âž£âž¤âž¥âž¦âž§âž¨âž©âžªâž«âž¬âž­âž®âž¯âž±âž²âž³âž´âžµâž¶'.
+ '➷➸➹➺➻➼➽➾ï£ï£žï£Ÿï£ ï£¡ï£¢ï££ï£¤ï£¥ï¿½'.
+ '�ﹼﹽ';
+
+/**
+ * Romanization lookup table
+ *
+ * This lookup tables provides a way to transform strings written in a language
+ * different from the ones based upon latin letters into plain ASCII.
+ *
+ * Please note: this is not a scientific transliteration table. It only works
+ * oneway from nonlatin to ASCII and it works by simple character replacement
+ * only. Specialities of each language are not supported.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Vitaly Blokhin <vitinfo@vitn.com>
+ * @link http://www.uconv.com/translit.htm
+ * @author Bisqwit <bisqwit@iki.fi>
+ * @link http://kanjidict.stc.cx/hiragana.php?src=2
+ * @link http://www.translatum.gr/converter/greek-transliteration.htm
+ * @link http://en.wikipedia.org/wiki/Royal_Thai_General_System_of_Transcription
+ * @link http://www.btranslations.com/resources/romanization/korean.asp
+ */
+global $UTF8_ROMANIZATION;
+$UTF8_ROMANIZATION = array(
+ //russian cyrillic
+ 'а'=>'a','Ð'=>'A','б'=>'b','Б'=>'B','в'=>'v','Ð’'=>'V','г'=>'g','Г'=>'G',
+ 'д'=>'d','Д'=>'D','е'=>'e','Е'=>'E','Ñ‘'=>'jo','Ð'=>'Jo','ж'=>'zh','Ж'=>'Zh',
+ 'з'=>'z','З'=>'Z','и'=>'i','И'=>'I','й'=>'j','Й'=>'J','к'=>'k','К'=>'K',
+ 'л'=>'l','Л'=>'L','м'=>'m','Ðœ'=>'M','н'=>'n','Ð'=>'N','о'=>'o','О'=>'O',
+ 'п'=>'p','П'=>'P','Ñ€'=>'r','Р'=>'R','Ñ'=>'s','С'=>'S','Ñ‚'=>'t','Т'=>'T',
+ 'у'=>'u','У'=>'U','ф'=>'f','Ф'=>'F','х'=>'x','Х'=>'X','ц'=>'c','Ц'=>'C',
+ 'ч'=>'ch','Ч'=>'Ch','ш'=>'sh','Ш'=>'Sh','щ'=>'sch','Щ'=>'Sch','ъ'=>'',
+ 'Ъ'=>'','Ñ‹'=>'y','Ы'=>'Y','ÑŒ'=>'','Ь'=>'','Ñ'=>'eh','Э'=>'Eh','ÑŽ'=>'ju',
+ 'Ю'=>'Ju','Ñ'=>'ja','Я'=>'Ja',
+ // Ukrainian cyrillic
+ 'Ò'=>'Gh','Ò‘'=>'gh','Є'=>'Je','Ñ”'=>'je','І'=>'I','Ñ–'=>'i','Ї'=>'Ji','Ñ—'=>'ji',
+ // Georgian
+ 'áƒ'=>'a','ბ'=>'b','გ'=>'g','დ'=>'d','ე'=>'e','ვ'=>'v','ზ'=>'z','თ'=>'th',
+ 'ი'=>'i','კ'=>'p','ლ'=>'l','მ'=>'m','ნ'=>'n','áƒ'=>'o','პ'=>'p','ჟ'=>'zh',
+ 'რ'=>'r','ს'=>'s','ტ'=>'t','უ'=>'u','ფ'=>'ph','ქ'=>'kh','ღ'=>'gh','ყ'=>'q',
+ 'შ'=>'sh','ჩ'=>'ch','ც'=>'c','ძ'=>'dh','წ'=>'w','ჭ'=>'j','ხ'=>'x','ჯ'=>'jh',
+ 'ჰ'=>'xh',
+ //Sanskrit
+ 'अ'=>'a','आ'=>'ah','इ'=>'i','ई'=>'ih','उ'=>'u','ऊ'=>'uh','ऋ'=>'ry',
+ 'ॠ'=>'ryh','ऌ'=>'ly','ॡ'=>'lyh','à¤'=>'e','à¤'=>'ay','ओ'=>'o','औ'=>'aw',
+ 'अं'=>'amh','अः'=>'aq','क'=>'k','ख'=>'kh','ग'=>'g','घ'=>'gh','ङ'=>'nh',
+ 'च'=>'c','छ'=>'ch','ज'=>'j','à¤'=>'jh','ञ'=>'ny','ट'=>'tq','ठ'=>'tqh',
+ 'ड'=>'dq','ढ'=>'dqh','ण'=>'nq','त'=>'t','थ'=>'th','द'=>'d','ध'=>'dh',
+ 'न'=>'n','प'=>'p','फ'=>'ph','ब'=>'b','भ'=>'bh','म'=>'m','य'=>'z','र'=>'r',
+ 'ल'=>'l','व'=>'v','श'=>'sh','ष'=>'sqh','स'=>'s','ह'=>'x',
+ //Hebrew
+ '×'=>'a', 'ב'=>'b','×’'=>'g','ד'=>'d','×”'=>'h','ו'=>'v','×–'=>'z','×—'=>'kh','ט'=>'th',
+ '×™'=>'y','ך'=>'h','×›'=>'k','ל'=>'l','×'=>'m','מ'=>'m','ן'=>'n','× '=>'n',
+ 'ס'=>'s','ע'=>'ah','ף'=>'f','פ'=>'p','ץ'=>'c','צ'=>'c','ק'=>'q','ר'=>'r',
+ 'ש'=>'sh','ת'=>'t',
+ //Arabic
+ 'ا'=>'a','ب'=>'b','ت'=>'t','ث'=>'th','ج'=>'g','ح'=>'xh','خ'=>'x','د'=>'d',
+ 'ذ'=>'dh','ر'=>'r','ز'=>'z','س'=>'s','ش'=>'sh','ص'=>'s\'','ض'=>'d\'',
+ 'Ø·'=>'t\'','ظ'=>'z\'','ع'=>'y','غ'=>'gh','Ù'=>'f','Ù‚'=>'q','Ùƒ'=>'k',
+ 'Ù„'=>'l','Ù…'=>'m','Ù†'=>'n','Ù‡'=>'x\'','Ùˆ'=>'u','ÙŠ'=>'i',
+
+ // Japanese hiragana
+ 'ã‚'=>'a','ãˆ'=>'e','ã„'=>'i','ãŠ'=>'o','ã†'=>'u','ã°'=>'ba','ã¹'=>'be',
+ 'ã³'=>'bi','ã¼'=>'bo','ã¶'=>'bu','ã—'=>'ci','ã '=>'da','ã§'=>'de','ã¢'=>'di',
+ 'ã©'=>'do','ã¥'=>'du','ãµã'=>'fa','ãµã‡'=>'fe','ãµãƒ'=>'fi','ãµã‰'=>'fo',
+ 'ãµ'=>'fu','ãŒ'=>'ga','ã’'=>'ge','ãŽ'=>'gi','ã”'=>'go','ã'=>'gu','ã¯'=>'ha',
+ 'ã¸'=>'he','ã²'=>'hi','ã»'=>'ho','ãµ'=>'hu','ã˜ã‚ƒ'=>'ja','ã˜ã‡'=>'je',
+ 'ã˜'=>'ji','ã˜ã‚‡'=>'jo','ã˜ã‚…'=>'ju','ã‹'=>'ka','ã‘'=>'ke','ã'=>'ki',
+ 'ã“'=>'ko','ã'=>'ku','ら'=>'la','ã‚Œ'=>'le','ã‚Š'=>'li','ã‚'=>'lo','ã‚‹'=>'lu',
+ 'ã¾'=>'ma','ã‚'=>'me','ã¿'=>'mi','ã‚‚'=>'mo','ã‚€'=>'mu','ãª'=>'na','ã­'=>'ne',
+ 'ã«'=>'ni','ã®'=>'no','ã¬'=>'nu','ã±'=>'pa','ãº'=>'pe','ã´'=>'pi','ã½'=>'po',
+ 'ã·'=>'pu','ら'=>'ra','ã‚Œ'=>'re','ã‚Š'=>'ri','ã‚'=>'ro','ã‚‹'=>'ru','ã•'=>'sa',
+ 'ã›'=>'se','ã—'=>'si','ã'=>'so','ã™'=>'su','ãŸ'=>'ta','ã¦'=>'te','ã¡'=>'ti',
+ 'ã¨'=>'to','ã¤'=>'tu','ヴã'=>'va','ヴã‡'=>'ve','ヴãƒ'=>'vi','ヴã‰'=>'vo',
+ 'ヴ'=>'vu','ã‚'=>'wa','ã†ã‡'=>'we','ã†ãƒ'=>'wi','ã‚’'=>'wo','ã‚„'=>'ya','ã„ã‡'=>'ye',
+ 'ã„'=>'yi','よ'=>'yo','ゆ'=>'yu','ã–'=>'za','ãœ'=>'ze','ã˜'=>'zi','ãž'=>'zo',
+ 'ãš'=>'zu','ã³ã‚ƒ'=>'bya','ã³ã‡'=>'bye','ã³ãƒ'=>'byi','ã³ã‚‡'=>'byo','ã³ã‚…'=>'byu',
+ 'ã¡ã‚ƒ'=>'cha','ã¡ã‡'=>'che','ã¡'=>'chi','ã¡ã‚‡'=>'cho','ã¡ã‚…'=>'chu','ã¡ã‚ƒ'=>'cya',
+ 'ã¡ã‡'=>'cye','ã¡ãƒ'=>'cyi','ã¡ã‚‡'=>'cyo','ã¡ã‚…'=>'cyu','ã§ã‚ƒ'=>'dha','ã§ã‡'=>'dhe',
+ 'ã§ãƒ'=>'dhi','ã§ã‚‡'=>'dho','ã§ã‚…'=>'dhu','ã©ã'=>'dwa','ã©ã‡'=>'dwe','ã©ãƒ'=>'dwi',
+ 'ã©ã‰'=>'dwo','ã©ã…'=>'dwu','ã¢ã‚ƒ'=>'dya','ã¢ã‡'=>'dye','ã¢ãƒ'=>'dyi','ã¢ã‚‡'=>'dyo',
+ 'ã¢ã‚…'=>'dyu','ã¢'=>'dzi','ãµã'=>'fwa','ãµã‡'=>'fwe','ãµãƒ'=>'fwi','ãµã‰'=>'fwo',
+ 'ãµã…'=>'fwu','ãµã‚ƒ'=>'fya','ãµã‡'=>'fye','ãµãƒ'=>'fyi','ãµã‚‡'=>'fyo','ãµã‚…'=>'fyu',
+ 'ãŽã‚ƒ'=>'gya','ãŽã‡'=>'gye','ãŽãƒ'=>'gyi','ãŽã‚‡'=>'gyo','ãŽã‚…'=>'gyu','ã²ã‚ƒ'=>'hya',
+ 'ã²ã‡'=>'hye','ã²ãƒ'=>'hyi','ã²ã‚‡'=>'hyo','ã²ã‚…'=>'hyu','ã˜ã‚ƒ'=>'jya','ã˜ã‡'=>'jye',
+ 'ã˜ãƒ'=>'jyi','ã˜ã‚‡'=>'jyo','ã˜ã‚…'=>'jyu','ãゃ'=>'kya','ãã‡'=>'kye','ããƒ'=>'kyi',
+ 'ãょ'=>'kyo','ãã‚…'=>'kyu','りゃ'=>'lya','ã‚Šã‡'=>'lye','ã‚Šãƒ'=>'lyi','りょ'=>'lyo',
+ 'ã‚Šã‚…'=>'lyu','ã¿ã‚ƒ'=>'mya','ã¿ã‡'=>'mye','ã¿ãƒ'=>'myi','ã¿ã‚‡'=>'myo','ã¿ã‚…'=>'myu',
+ 'ã‚“'=>'n','ã«ã‚ƒ'=>'nya','ã«ã‡'=>'nye','ã«ãƒ'=>'nyi','ã«ã‚‡'=>'nyo','ã«ã‚…'=>'nyu',
+ 'ã´ã‚ƒ'=>'pya','ã´ã‡'=>'pye','ã´ãƒ'=>'pyi','ã´ã‚‡'=>'pyo','ã´ã‚…'=>'pyu','りゃ'=>'rya',
+ 'ã‚Šã‡'=>'rye','ã‚Šãƒ'=>'ryi','りょ'=>'ryo','ã‚Šã‚…'=>'ryu','ã—ゃ'=>'sha','ã—ã‡'=>'she',
+ 'ã—'=>'shi','ã—ょ'=>'sho','ã—ã‚…'=>'shu','ã™ã'=>'swa','ã™ã‡'=>'swe','ã™ãƒ'=>'swi',
+ 'ã™ã‰'=>'swo','ã™ã…'=>'swu','ã—ゃ'=>'sya','ã—ã‡'=>'sye','ã—ãƒ'=>'syi','ã—ょ'=>'syo',
+ 'ã—ã‚…'=>'syu','ã¦ã‚ƒ'=>'tha','ã¦ã‡'=>'the','ã¦ãƒ'=>'thi','ã¦ã‚‡'=>'tho','ã¦ã‚…'=>'thu',
+ 'ã¤ã‚ƒ'=>'tsa','ã¤ã‡'=>'tse','ã¤ãƒ'=>'tsi','ã¤ã‚‡'=>'tso','ã¤'=>'tsu','ã¨ã'=>'twa',
+ 'ã¨ã‡'=>'twe','ã¨ãƒ'=>'twi','ã¨ã‰'=>'two','ã¨ã…'=>'twu','ã¡ã‚ƒ'=>'tya','ã¡ã‡'=>'tye',
+ 'ã¡ãƒ'=>'tyi','ã¡ã‚‡'=>'tyo','ã¡ã‚…'=>'tyu','ヴゃ'=>'vya','ヴã‡'=>'vye','ヴãƒ'=>'vyi',
+ 'ヴょ'=>'vyo','ヴゅ'=>'vyu','ã†ã'=>'wha','ã†ã‡'=>'whe','ã†ãƒ'=>'whi','ã†ã‰'=>'who',
+ 'ã†ã…'=>'whu','ã‚‘'=>'wye','ã‚'=>'wyi','ã˜ã‚ƒ'=>'zha','ã˜ã‡'=>'zhe','ã˜ãƒ'=>'zhi',
+ 'ã˜ã‚‡'=>'zho','ã˜ã‚…'=>'zhu','ã˜ã‚ƒ'=>'zya','ã˜ã‡'=>'zye','ã˜ãƒ'=>'zyi','ã˜ã‚‡'=>'zyo',
+ 'ã˜ã‚…'=>'zyu',
+ // Japanese katakana
+ 'ã‚¢'=>'a','エ'=>'e','イ'=>'i','オ'=>'o','ウ'=>'u','ãƒ'=>'ba','ベ'=>'be','ビ'=>'bi',
+ 'ボ'=>'bo','ブ'=>'bu','シ'=>'ci','ダ'=>'da','デ'=>'de','ヂ'=>'di','ド'=>'do',
+ 'ヅ'=>'du','ファ'=>'fa','フェ'=>'fe','フィ'=>'fi','フォ'=>'fo','フ'=>'fu','ガ'=>'ga',
+ 'ゲ'=>'ge','ã‚®'=>'gi','ã‚´'=>'go','ã‚°'=>'gu','ãƒ'=>'ha','ヘ'=>'he','ヒ'=>'hi','ホ'=>'ho',
+ 'フ'=>'hu','ジャ'=>'ja','ジェ'=>'je','ジ'=>'ji','ジョ'=>'jo','ジュ'=>'ju','カ'=>'ka',
+ 'ケ'=>'ke','キ'=>'ki','コ'=>'ko','ク'=>'ku','ラ'=>'la','レ'=>'le','リ'=>'li','ロ'=>'lo',
+ 'ル'=>'lu','マ'=>'ma','メ'=>'me','ミ'=>'mi','モ'=>'mo','ム'=>'mu','ナ'=>'na','ãƒ'=>'ne',
+ 'ニ'=>'ni','ノ'=>'no','ヌ'=>'nu','パ'=>'pa','ペ'=>'pe','ピ'=>'pi','ãƒ'=>'po','プ'=>'pu',
+ 'ラ'=>'ra','レ'=>'re','リ'=>'ri','ロ'=>'ro','ル'=>'ru','サ'=>'sa','セ'=>'se','シ'=>'si',
+ 'ソ'=>'so','ス'=>'su','ã‚¿'=>'ta','テ'=>'te','ãƒ'=>'ti','ト'=>'to','ツ'=>'tu','ヴァ'=>'va',
+ 'ヴェ'=>'ve','ヴィ'=>'vi','ヴォ'=>'vo','ヴ'=>'vu','ワ'=>'wa','ウェ'=>'we','ウィ'=>'wi',
+ 'ヲ'=>'wo','ヤ'=>'ya','イェ'=>'ye','イ'=>'yi','ヨ'=>'yo','ユ'=>'yu','ザ'=>'za','ゼ'=>'ze',
+ 'ジ'=>'zi','ゾ'=>'zo','ズ'=>'zu','ビャ'=>'bya','ビェ'=>'bye','ビィ'=>'byi','ビョ'=>'byo',
+ 'ビュ'=>'byu','ãƒãƒ£'=>'cha','ãƒã‚§'=>'che','ãƒ'=>'chi','ãƒãƒ§'=>'cho','ãƒãƒ¥'=>'chu',
+ 'ãƒãƒ£'=>'cya','ãƒã‚§'=>'cye','ãƒã‚£'=>'cyi','ãƒãƒ§'=>'cyo','ãƒãƒ¥'=>'cyu','デャ'=>'dha',
+ 'デェ'=>'dhe','ディ'=>'dhi','デョ'=>'dho','デュ'=>'dhu','ドァ'=>'dwa','ドェ'=>'dwe',
+ 'ドィ'=>'dwi','ドォ'=>'dwo','ドゥ'=>'dwu','ヂャ'=>'dya','ヂェ'=>'dye','ヂィ'=>'dyi',
+ 'ヂョ'=>'dyo','ヂュ'=>'dyu','ヂ'=>'dzi','ファ'=>'fwa','フェ'=>'fwe','フィ'=>'fwi',
+ 'フォ'=>'fwo','フゥ'=>'fwu','フャ'=>'fya','フェ'=>'fye','フィ'=>'fyi','フョ'=>'fyo',
+ 'フュ'=>'fyu','ギャ'=>'gya','ギェ'=>'gye','ギィ'=>'gyi','ギョ'=>'gyo','ギュ'=>'gyu',
+ 'ヒャ'=>'hya','ヒェ'=>'hye','ヒィ'=>'hyi','ヒョ'=>'hyo','ヒュ'=>'hyu','ジャ'=>'jya',
+ 'ジェ'=>'jye','ジィ'=>'jyi','ジョ'=>'jyo','ジュ'=>'jyu','キャ'=>'kya','キェ'=>'kye',
+ 'キィ'=>'kyi','キョ'=>'kyo','キュ'=>'kyu','リャ'=>'lya','リェ'=>'lye','リィ'=>'lyi',
+ 'リョ'=>'lyo','リュ'=>'lyu','ミャ'=>'mya','ミェ'=>'mye','ミィ'=>'myi','ミョ'=>'myo',
+ 'ミュ'=>'myu','ン'=>'n','ニャ'=>'nya','ニェ'=>'nye','ニィ'=>'nyi','ニョ'=>'nyo',
+ 'ニュ'=>'nyu','ピャ'=>'pya','ピェ'=>'pye','ピィ'=>'pyi','ピョ'=>'pyo','ピュ'=>'pyu',
+ 'リャ'=>'rya','リェ'=>'rye','リィ'=>'ryi','リョ'=>'ryo','リュ'=>'ryu','シャ'=>'sha',
+ 'シェ'=>'she','シ'=>'shi','ショ'=>'sho','シュ'=>'shu','スァ'=>'swa','スェ'=>'swe',
+ 'スィ'=>'swi','スォ'=>'swo','スゥ'=>'swu','シャ'=>'sya','シェ'=>'sye','シィ'=>'syi',
+ 'ショ'=>'syo','シュ'=>'syu','テャ'=>'tha','テェ'=>'the','ティ'=>'thi','テョ'=>'tho',
+ 'テュ'=>'thu','ツャ'=>'tsa','ツェ'=>'tse','ツィ'=>'tsi','ツョ'=>'tso','ツ'=>'tsu',
+ 'トァ'=>'twa','トェ'=>'twe','トィ'=>'twi','トォ'=>'two','トゥ'=>'twu','ãƒãƒ£'=>'tya',
+ 'ãƒã‚§'=>'tye','ãƒã‚£'=>'tyi','ãƒãƒ§'=>'tyo','ãƒãƒ¥'=>'tyu','ヴャ'=>'vya','ヴェ'=>'vye',
+ 'ヴィ'=>'vyi','ヴョ'=>'vyo','ヴュ'=>'vyu','ウァ'=>'wha','ウェ'=>'whe','ウィ'=>'whi',
+ 'ウォ'=>'who','ウゥ'=>'whu','ヱ'=>'wye','ヰ'=>'wyi','ジャ'=>'zha','ジェ'=>'zhe',
+ 'ジィ'=>'zhi','ジョ'=>'zho','ジュ'=>'zhu','ジャ'=>'zya','ジェ'=>'zye','ジィ'=>'zyi',
+ 'ジョ'=>'zyo','ジュ'=>'zyu',
+
+ // "Greeklish"
+ 'Γ'=>'G','Δ'=>'E','Θ'=>'Th','Λ'=>'L','Ξ'=>'X','Π'=>'P','Σ'=>'S','Φ'=>'F','Ψ'=>'Ps',
+ 'γ'=>'g','δ'=>'e','θ'=>'th','λ'=>'l','ξ'=>'x','π'=>'p','σ'=>'s','φ'=>'f','ψ'=>'ps',
+
+ // Thai
+ 'à¸'=>'k','ข'=>'kh','ฃ'=>'kh','ค'=>'kh','ฅ'=>'kh','ฆ'=>'kh','ง'=>'ng','จ'=>'ch',
+ 'ฉ'=>'ch','ช'=>'ch','ซ'=>'s','ฌ'=>'ch','à¸'=>'y','ฎ'=>'d','à¸'=>'t','à¸'=>'th',
+ 'ฑ'=>'d','ฒ'=>'th','ณ'=>'n','ด'=>'d','ต'=>'t','ถ'=>'th','ท'=>'th','ธ'=>'th',
+ 'น'=>'n','บ'=>'b','ป'=>'p','ผ'=>'ph','à¸'=>'f','พ'=>'ph','ฟ'=>'f','ภ'=>'ph',
+ 'ม'=>'m','ย'=>'y','ร'=>'r','ฤ'=>'rue','ฤๅ'=>'rue','ล'=>'l','ฦ'=>'lue',
+ 'ฦๅ'=>'lue','ว'=>'w','ศ'=>'s','ษ'=>'s','ส'=>'s','ห'=>'h','ฬ'=>'l','ฮ'=>'h',
+ 'ะ'=>'a','–ั'=>'a','รร'=>'a','า'=>'a','รร'=>'an','ำ'=>'am','–ิ'=>'i','–ี'=>'i',
+ '–ึ'=>'ue','–ื'=>'ue','–ุ'=>'u','–ู'=>'u','เะ'=>'e','เ–็'=>'e','เ'=>'e','à¹à¸°'=>'ae',
+ 'à¹'=>'ae','โะ'=>'o','โ'=>'o','เาะ'=>'o','อ'=>'o','เอะ'=>'oe','เ–ิ'=>'oe',
+ 'เอ'=>'oe','เ–ียะ'=>'ia','เ–ีย'=>'ia','เ–ือะ'=>'uea','เ–ือ'=>'uea','–ัวะ'=>'ua',
+ '–ัว'=>'ua','ว'=>'ua','ใ'=>'ai','ไ'=>'ai','–ัย'=>'ai','ไย'=>'ai','าย'=>'ai',
+ 'เา'=>'ao','าว'=>'ao','–ุย'=>'ui','โย'=>'oi','อย'=>'oi','เย'=>'oei','เ–ือย'=>'ueai',
+ 'วย'=>'uai','–ิว'=>'io','เ–็ว'=>'eo','เว'=>'eo','à¹â€“็ว'=>'aeo','à¹à¸§'=>'aeo',
+ 'เ–ียว'=>'iao',
+
+ // Korean
+ 'ㄱ'=>'k','ㅋ'=>'kh','ㄲ'=>'kk','ㄷ'=>'t','ㅌ'=>'th','ㄸ'=>'tt','ㅂ'=>'p',
+ 'ã…'=>'ph','ã…ƒ'=>'pp','ã…ˆ'=>'c','ã…Š'=>'ch','ã…‰'=>'cc','ã……'=>'s','ã…†'=>'ss',
+ 'ã…Ž'=>'h','ã…‡'=>'ng','ã„´'=>'n','ㄹ'=>'l','ã…'=>'m', 'ã…'=>'a','ã…“'=>'e','ã…—'=>'o',
+ 'ã…œ'=>'wu','ã…¡'=>'u','ã…£'=>'i','ã…'=>'ay','ã…”'=>'ey','ã…š'=>'oy','ã…˜'=>'wa','ã…'=>'we',
+ 'ã…Ÿ'=>'wi','ã…™'=>'way','ã…ž'=>'wey','ã…¢'=>'uy','ã…‘'=>'ya','ã…•'=>'ye','ã…›'=>'oy',
+ 'ã… '=>'yu','ã…’'=>'yay','ã…–'=>'yey',
+);
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
+
diff --git a/plugins/dokuwiki/lib/exe/fetch.php b/plugins/dokuwiki/lib/exe/fetch.php
new file mode 100644
index 0000000..59e59ca
--- /dev/null
+++ b/plugins/dokuwiki/lib/exe/fetch.php
@@ -0,0 +1,433 @@
+<?php
+/**
+ * DokuWiki media passthrough file
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+# security hotfix
+die();
+
+ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+ define('DOKU_DISABLE_GZIP_OUTPUT', 1);
+ require_once(DOKU_INC.'inc/init.php');
+ require_once(DOKU_INC.'inc/common.php');
+ require_once(DOKU_INC.'inc/pageutils.php');
+ require_once(DOKU_INC.'inc/confutils.php');
+ //close sesseion
+ session_write_close();
+ if(!defined('CHUNK_SIZE')) define('CHUNK_SIZE',16*1024);
+
+ $mimetypes = getMimeTypes();
+
+ //get input
+ $MEDIA = stripctl(getID('media',false)); // no cleaning except control chars - maybe external
+ $CACHE = calc_cache($_REQUEST['cache']);
+ $WIDTH = (int) $_REQUEST['w'];
+ $HEIGHT = (int) $_REQUEST['h'];
+ list($EXT,$MIME) = mimetype($MEDIA);
+ if($EXT === false){
+ $EXT = 'unknown';
+ $MIME = 'application/octet-stream';
+ }
+
+ //media to local file
+ if(preg_match('#^(https?)://#i',$MEDIA)){
+ //handle external images
+ if(strncmp($MIME,'image/',6) == 0) $FILE = get_from_URL($MEDIA,$EXT,$CACHE);
+ if(!$FILE){
+ //download failed - redirect to original URL
+ header('Location: '.$MEDIA);
+ exit;
+ }
+ }else{
+ $MEDIA = cleanID($MEDIA);
+ if(empty($MEDIA)){
+ header("HTTP/1.0 400 Bad Request");
+ print 'Bad request';
+ exit;
+ }
+
+ $FILE = mediaFN($MEDIA);
+ }
+
+ //check file existance
+ if(!@file_exists($FILE)){
+ header("HTTP/1.0 404 Not Found");
+ //FIXME add some default broken image
+ print 'Not Found';
+ exit;
+ }
+
+ //handle image resizing
+ if((substr($MIME,0,5) == 'image') && $WIDTH){
+ $FILE = get_resized($FILE,$EXT,$WIDTH,$HEIGHT);
+ }
+
+ // finally send the file to the client
+ sendFile($FILE,$MIME,$CACHE);
+
+/* ------------------------------------------------------------------------ */
+
+/**
+ * Set headers and send the file to the client
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Ben Coburn <btcoburn@silicodon.net>
+ */
+function sendFile($file,$mime,$cache){
+ global $conf;
+ $fmtime = filemtime($file);
+ // send headers
+ header("Content-Type: $mime");
+ // smart http caching headers
+ if ($cache==-1) {
+ // cache
+ // cachetime or one hour
+ header('Expires: '.gmdate("D, d M Y H:i:s", time()+max($conf['cachetime'], 3600)).' GMT');
+ header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600));
+ header('Pragma: public');
+ } else if ($cache>0) {
+ // recache
+ // remaining cachetime + 10 seconds so the newly recached media is used
+ header('Expires: '.gmdate("D, d M Y H:i:s", $fmtime+$conf['cachetime']+10).' GMT');
+ header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime-time()+$conf['cachetime']+10, 0));
+ header('Pragma: public');
+ } else if ($cache==0) {
+ // nocache
+ header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0');
+ header('Pragma: public');
+ }
+ header('Accept-Ranges: bytes');
+ //send important headers first, script stops here if '304 Not Modified' response
+ http_conditionalRequest($fmtime);
+ list($start,$len) = http_rangeRequest(filesize($file));
+
+ //application mime type is downloadable
+ if(substr($mime,0,11) == 'application'){
+ header('Content-Disposition: attachment; filename="'.basename($file).'";');
+ }
+
+ // send file contents
+ $fp = @fopen($file,"rb");
+ if($fp){
+ fseek($fp,$start); //seek to start of range
+
+ $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
+ while (!feof($fp) && $chunk > 0) {
+ @set_time_limit(); // large files can take a lot of time
+ print fread($fp, $chunk);
+ flush();
+ $len -= $chunk;
+ $chunk = ($len > CHUNK_SIZE) ? CHUNK_SIZE : $len;
+ }
+ fclose($fp);
+ }else{
+ header("HTTP/1.0 500 Internal Server Error");
+ print "Could not read $file - bad permissions?";
+ }
+}
+
+/**
+ * Checks and sets headers to handle range requets
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @returns array The start byte and the amount of bytes to send
+ */
+function http_rangeRequest($size){
+ if(!isset($_SERVER['HTTP_RANGE'])){
+ // no range requested - send the whole file
+ header("Content-Length: $size");
+ return array(0,$size);
+ }
+
+ $t = explode('=', $_SERVER['HTTP_RANGE']);
+ if (!$t[0]=='bytes') {
+ // we only understand byte ranges - send the whole file
+ header("Content-Length: $size");
+ return array(0,$size);
+ }
+
+ $r = explode('-', $t[1]);
+ $start = (int)$r[0];
+ $end = (int)$r[1];
+ if (!$end) $end = $size - 1;
+ if ($start > $end || $start > $size || $end > $size){
+ header('HTTP/1.1 416 Requested Range Not Satisfiable');
+ print 'Bad Range Request!';
+ exit;
+ }
+
+ $tot = $end - $start + 1;
+ header('HTTP/1.1 206 Partial Content');
+ header("Content-Range: bytes {$start}-{$end}/{$size}");
+ header("Content-Length: $tot");
+
+ return array($start,$tot);
+}
+
+/**
+ * Resizes the given image to the given size
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function get_resized($file, $ext, $w, $h=0){
+ global $conf;
+
+ $info = getimagesize($file);
+ if(!$h) $h = round(($w * $info[1]) / $info[0]);
+
+ // we wont scale up to infinity
+ if($w > 2000 || $h > 2000) return $file;
+
+ //cache
+ $local = getCacheName($file,'.media.'.$w.'x'.$h.'.'.$ext);
+ $mtime = @filemtime($local); // 0 if not exists
+
+ if( $mtime > filemtime($file) ||
+ resize_imageIM($ext,$file,$info[0],$info[1],$local,$w,$h) ||
+ resize_imageGD($ext,$file,$info[0],$info[1],$local,$w,$h) ){
+ return $local;
+ }
+ //still here? resizing failed
+ return $file;
+}
+
+/**
+ * Returns the wanted cachetime in seconds
+ *
+ * Resolves named constants
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function calc_cache($cache){
+ global $conf;
+
+ if(strtolower($cache) == 'nocache') return 0; //never cache
+ if(strtolower($cache) == 'recache') return $conf['cachetime']; //use standard cache
+ return -1; //cache endless
+}
+
+/**
+ * Download a remote file and return local filename
+ *
+ * returns false if download fails. Uses cached file if available and
+ * wanted
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
+ */
+function get_from_URL($url,$ext,$cache){
+ global $conf;
+
+ // if no cache or fetchsize just redirect
+ if ($cache==0) return false;
+ if (!$conf['fetchsize']) return false;
+
+ $local = getCacheName(strtolower($url),".media.$ext");
+ $mtime = @filemtime($local); // 0 if not exists
+
+ //decide if download needed:
+ if( ($mtime == 0) || // cache does not exist
+ ($cache != -1 && $mtime < time()-$cache) // 'recache' and cache has expired
+ ){
+ if(image_download($url,$local)){
+ return $local;
+ }else{
+ return false;
+ }
+ }
+
+ //if cache exists use it else
+ if($mtime) return $local;
+
+ //else return false
+ return false;
+}
+
+/**
+ * Download image files
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function image_download($url,$file){
+ global $conf;
+ $http = new DokuHTTPClient();
+ $http->max_bodysize = $conf['fetchsize'];
+ $http->timeout = 25; //max. 25 sec
+ $http->header_regexp = '!\r\nContent-Type: image/(jpe?g|gif|png)!i';
+
+ $data = $http->get($url);
+ if(!$data) return false;
+
+ $fileexists = @file_exists($file);
+ $fp = @fopen($file,"w");
+ if(!$fp) return false;
+ fwrite($fp,$data);
+ fclose($fp);
+ if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
+
+ // check if it is really an image
+ $info = @getimagesize($file);
+ if(!$info){
+ @unlink($file);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * resize images using external ImageMagick convert program
+ *
+ * @author Pavel Vitis <Pavel.Vitis@seznam.cz>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function resize_imageIM($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
+ global $conf;
+
+ // check if convert is configured
+ if(!$conf['im_convert']) return false;
+
+ // prepare command
+ $cmd = $conf['im_convert'];
+ $cmd .= ' -resize '.$to_w.'x'.$to_h.'!';
+ if ($ext == 'jpg' || $ext == 'jpeg') {
+ $cmd .= ' -quality '.$conf['jpg_quality'];
+ }
+ $cmd .= " $from $to";
+
+ @exec($cmd,$out,$retval);
+ if ($retval == 0) return true;
+ return false;
+}
+
+/**
+ * resize images using PHP's libGD support
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function resize_imageGD($ext,$from,$from_w,$from_h,$to,$to_w,$to_h){
+ global $conf;
+
+ if($conf['gdlib'] < 1) return false; //no GDlib available or wanted
+
+ // check available memory
+ if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
+ return false;
+ }
+
+ // create an image of the given filetype
+ if ($ext == 'jpg' || $ext == 'jpeg'){
+ if(!function_exists("imagecreatefromjpeg")) return false;
+ $image = @imagecreatefromjpeg($from);
+ }elseif($ext == 'png') {
+ if(!function_exists("imagecreatefrompng")) return false;
+ $image = @imagecreatefrompng($from);
+
+ }elseif($ext == 'gif') {
+ if(!function_exists("imagecreatefromgif")) return false;
+ $image = @imagecreatefromgif($from);
+ }
+ if(!$image) return false;
+
+ if(($conf['gdlib']>1) && function_exists("imagecreatetruecolor")){
+ $newimg = @imagecreatetruecolor ($to_w, $to_h);
+ }
+ if(!$newimg) $newimg = @imagecreate($to_w, $to_h);
+ if(!$newimg){
+ imagedestroy($image);
+ return false;
+ }
+
+ //keep png alpha channel if possible
+ if($ext == 'png' && $conf['gdlib']>1 && function_exists('imagesavealpha')){
+ imagealphablending($newimg, false);
+ imagesavealpha($newimg,true);
+ }
+
+ //try resampling first
+ if(function_exists("imagecopyresampled")){
+ if(!@imagecopyresampled($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h)) {
+ imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
+ }
+ }else{
+ imagecopyresized($newimg, $image, 0, 0, 0, 0, $to_w, $to_h, $from_w, $from_h);
+ }
+
+ $okay = false;
+ if ($ext == 'jpg' || $ext == 'jpeg'){
+ if(!function_exists('imagejpeg')){
+ $okay = false;
+ }else{
+ $okay = imagejpeg($newimg, $to, $conf['jpg_quality']);
+ }
+ }elseif($ext == 'png') {
+ if(!function_exists('imagepng')){
+ $okay = false;
+ }else{
+ $okay = imagepng($newimg, $to);
+ }
+ }elseif($ext == 'gif') {
+ if(!function_exists('imagegif')){
+ $okay = false;
+ }else{
+ $okay = imagegif($newimg, $to);
+ }
+ }
+
+ // destroy GD image ressources
+ if($image) imagedestroy($image);
+ if($newimg) imagedestroy($newimg);
+
+ return $okay;
+}
+
+/**
+ * Checks if the given amount of memory is available
+ *
+ * If the memory_get_usage() function is not available the
+ * function just assumes $used bytes of already allocated memory
+ *
+ * @param int $mem Size of memory you want to allocate in bytes
+ * @param int $used already allocated memory (see above)
+ * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function is_mem_available($mem,$bytes=1048576){
+ $limit = trim(ini_get('memory_limit'));
+ if(empty($limit)) return true; // no limit set!
+
+ // parse limit to bytes
+ $unit = strtolower(substr($limit,-1));
+ switch($unit){
+ case 'g':
+ $limit = substr($limit,0,-1);
+ $limit *= 1024*1024*1024;
+ break;
+ case 'm':
+ $limit = substr($limit,0,-1);
+ $limit *= 1024*1024;
+ break;
+ case 'k':
+ $limit = substr($limit,0,-1);
+ $limit *= 1024;
+ break;
+ }
+
+ // get used memory if possible
+ if(function_exists('memory_get_usage')){
+ $used = memory_get_usage();
+ }
+
+
+ if($used+$mem > $limit){
+ return false;
+ }
+
+ return true;
+}
+
+//Setup VIM: ex: et ts=2 enc=utf-8 :
+?>
diff --git a/plugins/dokuwiki/lib/images/fileicons/bz2.png b/plugins/dokuwiki/lib/images/fileicons/bz2.png
new file mode 100644
index 0000000..d48cae0
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/bz2.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/conf.png b/plugins/dokuwiki/lib/images/fileicons/conf.png
new file mode 100644
index 0000000..ddffe6f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/conf.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/deb.png b/plugins/dokuwiki/lib/images/fileicons/deb.png
new file mode 100644
index 0000000..9229d87
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/deb.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/doc.png b/plugins/dokuwiki/lib/images/fileicons/doc.png
new file mode 100644
index 0000000..932567f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/doc.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/file.gif b/plugins/dokuwiki/lib/images/fileicons/file.gif
new file mode 100644
index 0000000..815ccb1
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/file.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/file.png b/plugins/dokuwiki/lib/images/fileicons/file.png
new file mode 100644
index 0000000..817014f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/file.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/gif.png b/plugins/dokuwiki/lib/images/fileicons/gif.png
new file mode 100644
index 0000000..b4c07a9
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/gif.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/gz.png b/plugins/dokuwiki/lib/images/fileicons/gz.png
new file mode 100644
index 0000000..2426bd1
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/gz.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/htm.png b/plugins/dokuwiki/lib/images/fileicons/htm.png
new file mode 100644
index 0000000..1a68121
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/htm.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/html.png b/plugins/dokuwiki/lib/images/fileicons/html.png
new file mode 100644
index 0000000..672cbce
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/html.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/index.php b/plugins/dokuwiki/lib/images/fileicons/index.php
new file mode 100644
index 0000000..805b36d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/index.php
@@ -0,0 +1,49 @@
+<!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" xml:lang="en"
+ lang="en" dir="ltr">
+ <title>filetype icons</title>
+
+ <style type="text/css">
+ body {
+ background-color: #ccc;
+ font-family: Arial;
+ }
+
+ .box {
+ width: 200px;
+ float:left;
+ padding: 0.5em;
+ margin: 0;
+ }
+
+ .white {
+ background-color: #fff;
+ }
+
+ .black {
+ background-color: #000;
+ }
+ </style>
+
+</head>
+<body>
+
+<div class="white box">
+<?php
+foreach (glob('*.png') as $img) {
+ echo '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+}
+?>
+</div>
+
+<div class="black box">
+<?php
+foreach (glob('*.png') as $img) {
+ echo '<img src="'.$img.'" alt="'.$img.'" title="'.$img.'" /> ';
+}
+?>
+</div>
+
+</body>
+</html>
diff --git a/plugins/dokuwiki/lib/images/fileicons/jpeg.png b/plugins/dokuwiki/lib/images/fileicons/jpeg.png
new file mode 100644
index 0000000..aa4cc23
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/jpeg.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/jpg.png b/plugins/dokuwiki/lib/images/fileicons/jpg.png
new file mode 100644
index 0000000..1fb6cc1
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/jpg.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odc.png b/plugins/dokuwiki/lib/images/fileicons/odc.png
new file mode 100644
index 0000000..47f65c8
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odc.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odf.png b/plugins/dokuwiki/lib/images/fileicons/odf.png
new file mode 100644
index 0000000..a2fbc51
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odf.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odg.png b/plugins/dokuwiki/lib/images/fileicons/odg.png
new file mode 100644
index 0000000..434f182
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odg.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odi.png b/plugins/dokuwiki/lib/images/fileicons/odi.png
new file mode 100644
index 0000000..74f6303
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odi.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odp.png b/plugins/dokuwiki/lib/images/fileicons/odp.png
new file mode 100644
index 0000000..a5c77f8
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odp.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/ods.png b/plugins/dokuwiki/lib/images/fileicons/ods.png
new file mode 100644
index 0000000..2ab1273
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/ods.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/odt.png b/plugins/dokuwiki/lib/images/fileicons/odt.png
new file mode 100644
index 0000000..b0c21fc
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/odt.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/pdf.png b/plugins/dokuwiki/lib/images/fileicons/pdf.png
new file mode 100644
index 0000000..638066d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/pdf.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/png.png b/plugins/dokuwiki/lib/images/fileicons/png.png
new file mode 100644
index 0000000..f0b5b00
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/png.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/ppt.png b/plugins/dokuwiki/lib/images/fileicons/ppt.png
new file mode 100644
index 0000000..adaefc6
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/ppt.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/ps.png b/plugins/dokuwiki/lib/images/fileicons/ps.png
new file mode 100644
index 0000000..c51c763
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/ps.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/rpm.png b/plugins/dokuwiki/lib/images/fileicons/rpm.png
new file mode 100644
index 0000000..22212ea
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/rpm.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/rtf.png b/plugins/dokuwiki/lib/images/fileicons/rtf.png
new file mode 100644
index 0000000..d8bada5
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/rtf.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/swf.png b/plugins/dokuwiki/lib/images/fileicons/swf.png
new file mode 100644
index 0000000..0729ed0
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/swf.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/sxc.png b/plugins/dokuwiki/lib/images/fileicons/sxc.png
new file mode 100644
index 0000000..419c183
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/sxc.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/sxd.png b/plugins/dokuwiki/lib/images/fileicons/sxd.png
new file mode 100644
index 0000000..5801bb2
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/sxd.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/sxi.png b/plugins/dokuwiki/lib/images/fileicons/sxi.png
new file mode 100644
index 0000000..2a94290
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/sxi.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/sxw.png b/plugins/dokuwiki/lib/images/fileicons/sxw.png
new file mode 100644
index 0000000..6da97be
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/sxw.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/tar.png b/plugins/dokuwiki/lib/images/fileicons/tar.png
new file mode 100644
index 0000000..5a2f717
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/tar.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/tgz.png b/plugins/dokuwiki/lib/images/fileicons/tgz.png
new file mode 100644
index 0000000..141acf5
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/tgz.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/txt.png b/plugins/dokuwiki/lib/images/fileicons/txt.png
new file mode 100644
index 0000000..da20009
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/txt.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/xls.png b/plugins/dokuwiki/lib/images/fileicons/xls.png
new file mode 100644
index 0000000..e8cd58d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/xls.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/xml.png b/plugins/dokuwiki/lib/images/fileicons/xml.png
new file mode 100644
index 0000000..eb46323
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/xml.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/fileicons/zip.png b/plugins/dokuwiki/lib/images/fileicons/zip.png
new file mode 100644
index 0000000..999ffbe
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/fileicons/zip.png
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/amazon.de.gif b/plugins/dokuwiki/lib/images/interwiki/amazon.de.gif
new file mode 100644
index 0000000..f52c1c5
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/amazon.de.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/amazon.gif b/plugins/dokuwiki/lib/images/interwiki/amazon.gif
new file mode 100644
index 0000000..f52c1c5
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/amazon.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/amazon.uk.gif b/plugins/dokuwiki/lib/images/interwiki/amazon.uk.gif
new file mode 100644
index 0000000..f52c1c5
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/amazon.uk.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/bug.gif b/plugins/dokuwiki/lib/images/interwiki/bug.gif
new file mode 100644
index 0000000..3432b8d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/bug.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/coral.gif b/plugins/dokuwiki/lib/images/interwiki/coral.gif
new file mode 100644
index 0000000..0f9f675
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/coral.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/doku.gif b/plugins/dokuwiki/lib/images/interwiki/doku.gif
new file mode 100644
index 0000000..7dc4248
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/doku.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/google.gif b/plugins/dokuwiki/lib/images/interwiki/google.gif
new file mode 100644
index 0000000..fb39f61
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/google.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/meatball.gif b/plugins/dokuwiki/lib/images/interwiki/meatball.gif
new file mode 100644
index 0000000..7ac5454
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/meatball.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/phpfn.gif b/plugins/dokuwiki/lib/images/interwiki/phpfn.gif
new file mode 100644
index 0000000..638d4c4
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/phpfn.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/sb.gif b/plugins/dokuwiki/lib/images/interwiki/sb.gif
new file mode 100644
index 0000000..e272a29
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/sb.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/wiki.gif b/plugins/dokuwiki/lib/images/interwiki/wiki.gif
new file mode 100644
index 0000000..e8dc5d2
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/wiki.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/wp.gif b/plugins/dokuwiki/lib/images/interwiki/wp.gif
new file mode 100644
index 0000000..ca853b8
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/wp.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/wpde.gif b/plugins/dokuwiki/lib/images/interwiki/wpde.gif
new file mode 100644
index 0000000..ca853b8
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/wpde.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/interwiki/wpmeta.gif b/plugins/dokuwiki/lib/images/interwiki/wpmeta.gif
new file mode 100644
index 0000000..ca853b8
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/interwiki/wpmeta.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/delete.gif b/plugins/dokuwiki/lib/images/smileys/delete.gif
new file mode 100644
index 0000000..d668348
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/delete.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/fixme.gif b/plugins/dokuwiki/lib/images/smileys/fixme.gif
new file mode 100644
index 0000000..b66ea99
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/fixme.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_arrow.gif b/plugins/dokuwiki/lib/images/smileys/icon_arrow.gif
new file mode 100644
index 0000000..2880055
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_arrow.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_biggrin.gif b/plugins/dokuwiki/lib/images/smileys/icon_biggrin.gif
new file mode 100644
index 0000000..d352772
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_biggrin.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_confused.gif b/plugins/dokuwiki/lib/images/smileys/icon_confused.gif
new file mode 100644
index 0000000..0c49e06
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_confused.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_cool.gif b/plugins/dokuwiki/lib/images/smileys/icon_cool.gif
new file mode 100644
index 0000000..cead030
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_cool.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_cry.gif b/plugins/dokuwiki/lib/images/smileys/icon_cry.gif
new file mode 100644
index 0000000..7d54b1f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_cry.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_doubt.gif b/plugins/dokuwiki/lib/images/smileys/icon_doubt.gif
new file mode 100644
index 0000000..fd7903b
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_doubt.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_doubt2.gif b/plugins/dokuwiki/lib/images/smileys/icon_doubt2.gif
new file mode 100644
index 0000000..eb4b70b
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_doubt2.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_eek.gif b/plugins/dokuwiki/lib/images/smileys/icon_eek.gif
new file mode 100644
index 0000000..5d39781
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_eek.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_evil.gif b/plugins/dokuwiki/lib/images/smileys/icon_evil.gif
new file mode 100644
index 0000000..ab1aa8e
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_evil.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_exclaim.gif b/plugins/dokuwiki/lib/images/smileys/icon_exclaim.gif
new file mode 100644
index 0000000..6e50e2e
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_exclaim.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_frown.gif b/plugins/dokuwiki/lib/images/smileys/icon_frown.gif
new file mode 100644
index 0000000..d2ac78c
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_frown.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_fun.gif b/plugins/dokuwiki/lib/images/smileys/icon_fun.gif
new file mode 100644
index 0000000..a8bb8a3
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_fun.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_idea.gif b/plugins/dokuwiki/lib/images/smileys/icon_idea.gif
new file mode 100644
index 0000000..a40ae0d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_idea.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_kaddi.gif b/plugins/dokuwiki/lib/images/smileys/icon_kaddi.gif
new file mode 100644
index 0000000..1410f7f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_kaddi.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_lol.gif b/plugins/dokuwiki/lib/images/smileys/icon_lol.gif
new file mode 100644
index 0000000..374ba15
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_lol.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_mrgreen.gif b/plugins/dokuwiki/lib/images/smileys/icon_mrgreen.gif
new file mode 100644
index 0000000..b54cd0f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_mrgreen.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_neutral.gif b/plugins/dokuwiki/lib/images/smileys/icon_neutral.gif
new file mode 100644
index 0000000..4f31156
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_neutral.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_question.gif b/plugins/dokuwiki/lib/images/smileys/icon_question.gif
new file mode 100644
index 0000000..9d07226
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_question.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_razz.gif b/plugins/dokuwiki/lib/images/smileys/icon_razz.gif
new file mode 100644
index 0000000..29da2a2
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_razz.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_redface.gif b/plugins/dokuwiki/lib/images/smileys/icon_redface.gif
new file mode 100644
index 0000000..ad76283
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_redface.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_rolleyes.gif b/plugins/dokuwiki/lib/images/smileys/icon_rolleyes.gif
new file mode 100644
index 0000000..d7f5f2f
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_rolleyes.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_sad.gif b/plugins/dokuwiki/lib/images/smileys/icon_sad.gif
new file mode 100644
index 0000000..d2ac78c
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_sad.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_silenced.gif b/plugins/dokuwiki/lib/images/smileys/icon_silenced.gif
new file mode 100644
index 0000000..448399b
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_silenced.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_smile.gif b/plugins/dokuwiki/lib/images/smileys/icon_smile.gif
new file mode 100644
index 0000000..7b1f6d3
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_smile.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_smile2.gif b/plugins/dokuwiki/lib/images/smileys/icon_smile2.gif
new file mode 100644
index 0000000..769639d
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_smile2.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_surprised.gif b/plugins/dokuwiki/lib/images/smileys/icon_surprised.gif
new file mode 100644
index 0000000..cb21424
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_surprised.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_twisted.gif b/plugins/dokuwiki/lib/images/smileys/icon_twisted.gif
new file mode 100644
index 0000000..502fe24
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_twisted.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/images/smileys/icon_wink.gif b/plugins/dokuwiki/lib/images/smileys/icon_wink.gif
new file mode 100644
index 0000000..d148288
--- /dev/null
+++ b/plugins/dokuwiki/lib/images/smileys/icon_wink.gif
Binary files differ
diff --git a/plugins/dokuwiki/lib/plugins/changelinks/syntax.php b/plugins/dokuwiki/lib/plugins/changelinks/syntax.php
new file mode 100644
index 0000000..263e066
--- /dev/null
+++ b/plugins/dokuwiki/lib/plugins/changelinks/syntax.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Change-Interwikilinks Plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Florian Schmitz floele at gmail dot com
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class syntax_plugin_changelinks extends DokuWiki_Syntax_Plugin {
+
+ /**
+ * return some info
+ */
+ function getInfo(){
+ return array(
+ 'author' => 'Florian Schmitz',
+ 'email' => 'floele@gmail.com',
+ 'date' => '2005-12-18',
+ 'name' => 'Change-Interwikilinks Plugin',
+ 'desc' => 'Changes the functionality of interwikilinks',
+ 'url' => 'http://flyspray.org/',
+ );
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 299;
+ }
+
+ /**
+ * Connect pattern to lexer
+ */
+
+ function connectTo($mode) {
+ // Word boundaries?
+ $this->Lexer->addSpecialPattern("\[\[.+?\]\]",$mode,'plugin_changelinks');
+ }
+
+ /**
+ * Handle the match
+ */
+ function handle($match, $state, $pos, &$handler){
+ // Strip the opening and closing markup
+ $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
+
+ // Split title from URL
+ $link = preg_split('/\|/u',$link,2);
+ if ( !isset($link[1]) ) {
+ $link[1] = NULL;
+ } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
+ // If the title is an image, convert it to an array containing the image details
+ $link[1] = Doku_Handler_Parse_Media($link[1]);
+ }
+ $link[0] = trim($link[0]);
+
+ //decide which kind of link it is
+
+ if ( preg_match('/^[a-zA-Z]+>{1}.*$/u',$link[0]) ) {
+ // Interwiki
+ $interwiki = preg_split('/>/u',$link[0]);
+ $handler->_addCall(
+ 'interwikilink',
+ array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
+ $pos
+ );
+ } elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) {
+ // Windows Share
+ $handler->_addCall(
+ 'windowssharelink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ } elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
+ // external link (accepts all protocols)
+ $handler->_addCall(
+ 'externallink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ } elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link[0]) ) {
+ // E-Mail
+ $handler->_addCall(
+ 'emaillink',
+ array($link[0],$link[1]),
+ $pos
+ );
+ } elseif ( preg_match('!^#.+!',$link[0]) ){
+ // local link
+ $handler->_addCall(
+ 'locallink',
+ array(substr($link[0],1),$link[1]),
+ $pos
+ );
+ } else {
+ return array($link[0],$link[1]);
+ }
+ }
+
+ /**
+ * Create output
+ */
+ function render($mode, &$renderer, $data) {
+ if($mode == 'xhtml') {
+ global $conf;
+ $id = $data[0];
+ $name = $data[1];
+
+ //prepare for formating
+ $link['target'] = $conf['target']['wiki'];
+ $link['style'] = '';
+ $link['pre'] = '';
+ $link['suf'] = '';
+ $link['more'] = '';
+ $link['class'] = 'internallink';
+ $link['url'] = DOKU_INTERNAL_LINK . $id;
+
+ if(is_array($name)){
+ $link['name'] = (isset($name['title'])) ? hsc($name['title']) : hsc($id);
+ $link['title'] = $id;
+ } else{
+ $link['name'] = ($name) ? hsc($name) : hsc($id);
+ $link['title'] = ($name) ? $name : $id;
+ }
+
+ //add search string
+ if($search){
+ ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&amp;s=';
+ $link['url'] .= urlencode($search);
+ }
+
+ //output formatted
+ $renderer->doc .= $renderer->_formatLink($link);
+ }
+ return true;
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
+?>
diff --git a/plugins/dokuwiki/lib/plugins/fslink/syntax.php b/plugins/dokuwiki/lib/plugins/fslink/syntax.php
new file mode 100644
index 0000000..57c91c3
--- /dev/null
+++ b/plugins/dokuwiki/lib/plugins/fslink/syntax.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * FS#X and bug X plugin
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Florian Schmitz floele at gmail dot com
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class syntax_plugin_fslink extends DokuWiki_Syntax_Plugin {
+
+ /**
+ * return some info
+ */
+ function getInfo(){
+ return array(
+ 'author' => 'Florian Schmitz',
+ 'email' => 'floele@gmail.com',
+ 'date' => '2005-12-17',
+ 'name' => 'FS-link Plugin',
+ 'desc' => 'Enables Flyspray\'s bug links',
+ 'url' => 'http://flyspray.org/',
+ );
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 301;
+ }
+
+ /**
+ * Connect pattern to lexer
+ */
+
+ function connectTo($mode) {
+ // Word boundaries?
+ $this->Lexer->addSpecialPattern('FS#\d+',$mode,'plugin_fslink');
+ $this->Lexer->addSpecialPattern('bug \d+',$mode,'plugin_fslink');
+ }
+
+ /**
+ * Handle the match
+ */
+ function handle($match, $state, $pos, &$handler){
+ return array($match, $state);
+ }
+
+ /**
+ * Create output
+ */
+ function render($mode, &$renderer, $data) {
+ if($mode == 'xhtml'){
+ $fsid = explode('#', $data[0]);
+ if(count($fsid) < 2) {
+ $fsid = explode(' ', $data[0]);
+ }
+ $renderer->doc .= tpl_tasklink($fsid[1], $data[0]);
+ }
+ return true;
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/lib/plugins/newline/syntax.php b/plugins/dokuwiki/lib/plugins/newline/syntax.php
new file mode 100644
index 0000000..6a5eed8
--- /dev/null
+++ b/plugins/dokuwiki/lib/plugins/newline/syntax.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Adds simple <br />s
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Florian Schmitz floele at gmail dot com
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'syntax.php');
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class syntax_plugin_newline extends DokuWiki_Syntax_Plugin {
+
+ /**
+ * return some info
+ */
+ function getInfo(){
+ return array(
+ 'author' => 'Florian Schmitz',
+ 'email' => 'floele@gmail.com',
+ 'date' => '2005-12-17',
+ 'name' => '<br /> Plugin',
+ 'desc' => 'Enables simple newlines',
+ 'url' => 'http://flyspray.org/',
+ );
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ function getType(){
+ return 'substition';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ function getSort(){
+ return 201;
+ }
+
+ /**
+ * Connect pattern to lexer
+ */
+
+ function connectTo($mode) {
+ // Word boundaries?
+ $this->Lexer->addSpecialPattern("(?<!^|\n)\n(?!\n)",$mode,'plugin_newline');
+ }
+
+ /**
+ * Handle the match
+ */
+ function handle($match, $state, $pos, &$handler){
+ return array($match, $state);
+ }
+
+ /**
+ * Create output
+ */
+ function render($mode, &$renderer, $data) {
+ if($mode == 'xhtml'){
+ if ($data[0]) $renderer->doc .= '<br />';
+ return true;
+ }
+ return false;
+ }
+
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
+?> \ No newline at end of file
diff --git a/plugins/dokuwiki/lib/plugins/syntax.php b/plugins/dokuwiki/lib/plugins/syntax.php
new file mode 100644
index 0000000..ccf6577
--- /dev/null
+++ b/plugins/dokuwiki/lib/plugins/syntax.php
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Syntax Plugin Prototype
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_INC.'inc/parser/parser.php');
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class DokuWiki_Syntax_Plugin extends Doku_Parser_Mode {
+
+ var $allowedModesSetup = false;
+ var $localised = false; // set to true by setupLocale() after loading language dependent strings
+ var $lang = array(); // array to hold language dependent strings, best accessed via ->getLang()
+ var $configloaded = false; // set to true by loadConfig() after loading plugin configuration variables
+ var $conf = array(); // array to hold plugin settings, best accessed via ->getConf()
+
+ /**
+ * General Info
+ *
+ * Needs to return a associative array with the following values:
+ *
+ * author - Author of the plugin
+ * email - Email address to contact the author
+ * date - Last modified date of the plugin in YYYY-MM-DD format
+ * name - Name of the plugin
+ * desc - Short description of the plugin (Text only)
+ * url - Website with more information on the plugin (eg. syntax description)
+ */
+ function getInfo(){
+ trigger_error('getType() not implemented in '.get_class($this), E_USER_WARNING);
+ }
+
+ /**
+ * Syntax Type
+ *
+ * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
+ */
+ function getType(){
+ trigger_error('getType() not implemented in '.get_class($this), E_USER_WARNING);
+ }
+
+ /**
+ * Allowed Mode Types
+ *
+ * Defines the mode types for other dokuwiki markup that maybe nested within the
+ * plugin's own markup. Needs to return an array of one or more of the mode types
+ * defined in $PARSER_MODES in parser.php
+ */
+ function getAllowedTypes() {
+ return array();
+ }
+
+ /**
+ * Paragraph Type
+ *
+ * Defines how this syntax is handled regarding paragraphs. This is important
+ * for correct XHTML nesting. Should return one of the following:
+ *
+ * 'normal' - The plugin can be used inside paragraphs
+ * 'block' - Open paragraphs need to be closed before plugin output
+ * 'stack' - Special case. Plugin wraps other paragraphs.
+ *
+ * @see Doku_Handler_Block
+ */
+ function getPType(){
+ return 'normal';
+ }
+
+ /**
+ * Handler to prepare matched data for the rendering process
+ *
+ * This function can only pass data to render() via its return value - render()
+ * may be not be run during the object's current life.
+ *
+ * Usually you should only need the $match param.
+ *
+ * @param $match string The text matched by the patterns
+ * @param $state int The lexer state for the match
+ * @param $pos int The character position of the matched text
+ * @param $handler ref Reference to the Doku_Handler object
+ * @return array Return an array with all data you want to use in render
+ */
+ function handle($match, $state, $pos, &$handler){
+ trigger_error('handle() not implemented in '.get_class($this), E_USER_WARNING);
+ }
+
+ /**
+ * Handles the actual output creation.
+ *
+ * The function must not assume any other of the classes methods have been run
+ * during the object's current life. The only reliable data it receives are its
+ * parameters.
+ *
+ * The function should always check for the given output format and return false
+ * when a format isn't supported.
+ *
+ * $renderer contains a reference to the renderer object which is
+ * currently handling the rendering. You need to use it for writing
+ * the output. How this is done depends on the renderer used (specified
+ * by $format
+ *
+ * The contents of the $data array depends on what the handler() function above
+ * created
+ *
+ * @param $format string output format to being Rendered
+ * @param $renderer ref reference to the current renderer object
+ * @param $data array data created by handler()
+ * @return boolean rendered correctly?
+ */
+ function render($format, &$renderer, $data) {
+ trigger_error('render() not implemented in '.get_class($this), E_USER_WARNING);
+
+ }
+
+ /**
+ * There should be no need to override these functions
+ */
+ function accepts($mode) {
+
+ if (!$this->allowedModesSetup) {
+ global $PARSER_MODES;
+
+ $allowedModeTypes = $this->getAllowedTypes();
+ foreach($allowedModeTypes as $mt) {
+ $this->allowedModes = array_merge($this->allowedModes, $PARSER_MODES[$mt]);
+ }
+
+ $idx = array_search(substr(get_class($this), 7), $this->allowedModes);
+ if ($idx !== false) {
+ unset($this->allowedModes[$idx]);
+ }
+ $this->allowedModesSetup = true;
+ }
+
+ return parent::accepts($mode);
+ }
+
+ // plugin introspection methods
+ // extract from class name, format = <plugin type>_plugin_<name>[_<component name>]
+ function getPluginType() { list($t) = explode('_', get_class($this), 2); return $t; }
+ function getPluginName() { list($t, $p, $n) = explode('_', get_class($this), 4); return $n; }
+ function getPluginComponent() { list($t, $p, $n, $c) = explode('_', get_class($this), 4); return (isset($c)?$c:''); }
+
+ // localisation methods
+ /**
+ * getLang($id)
+ *
+ * use this function to access plugin language strings
+ * to try to minimise unnecessary loading of the strings when the plugin doesn't require them
+ * e.g. when info plugin is querying plugins for information about themselves.
+ *
+ * @param $id id of the string to be retrieved
+ * @return string string in appropriate language or english if not available
+ */
+ function getLang($id) {
+ if (!$this->localised) $this->setupLocale();
+
+ return (isset($this->lang[$id]) ? $this->lang[$id] : '');
+ }
+
+ /**
+ * locale_xhtml($id)
+ *
+ * retrieve a language dependent wiki page and pass to xhtml renderer for display
+ * plugin equivalent of p_locale_xhtml()
+ *
+ * @param $id id of language dependent wiki page
+ * @return string parsed contents of the wiki page in xhtml format
+ */
+ function locale_xhtml($id) {
+ return p_cached_output($this->localFN($id));
+ }
+
+ /**
+ * localFN($id)
+ * prepends appropriate path for a language dependent filename
+ * plugin equivalent of localFN()
+ */
+ function localFN($id) {
+ global $conf;
+ $plugin = $this->getPluginName();
+ $file = DOKU_PLUGIN.$plugin.'/lang/'.$conf['lang'].'/'.$id.'.txt';
+ if(!@file_exists($file)){
+ //fall back to english
+ $file = DOKU_PLUGIN.$plugin.'/lang/en/'.$id.'.txt';
+ }
+ return $file;
+ }
+
+ /**
+ * setupLocale()
+ * reads all the plugins language dependent strings into $this->lang
+ * this function is automatically called by getLang()
+ */
+ function setupLocale() {
+ if ($this->localised) return;
+
+ global $conf; // definitely don't invoke "global $lang"
+ $path = DOKU_PLUGIN.$this->getPluginName().'/lang/';
+
+ // don't include once, in case several plugin components require the same language file
+ @include($path.'en/lang.php');
+ if ($conf['lang'] != 'en') @include($path.$conf['lang'].'/lang.php');
+
+ $this->lang = $lang;
+ $this->localised = true;
+ }
+
+ // configuration methods
+ /**
+ * getConf($setting)
+ *
+ * use this function to access plugin configuration variables
+ */
+ function getConf($setting){
+
+ if (!$this->configloaded){ $this->loadConfig(); }
+
+ return $this->conf[$setting];
+ }
+
+ /**
+ * loadConfig()
+ * merges the plugin's default settings with any local settings
+ * this function is automatically called through getConf()
+ */
+ function loadConfig(){
+ global $conf;
+
+ $defaults = $this->readDefaultSettings();
+ $plugin = $this->getPluginName();
+
+ foreach ($defaults as $key => $value) {
+ if (isset($conf['plugin'][$plugin][$key])) continue;
+ $conf['plugin'][$plugin][$key] = $value;
+ }
+
+ $this->configloaded = true;
+ $this->conf =& $conf['plugin'][$plugin];
+ }
+
+ /**
+ * read the plugin's default configuration settings from conf/default.php
+ * this function is automatically called through getConf()
+ *
+ * @return array setting => value
+ */
+ function readDefaultSettings() {
+
+ $path = DOKU_PLUGIN.$this->getPluginName().'/conf/';
+ $conf = array();
+
+ if (@file_exists($path.'default.php')) {
+ include($path.'default.php');
+ }
+
+ return $conf;
+ }
+
+}
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 0000000..1893631
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,2 @@
+User-agent: Fasterfox
+Disallow: /
diff --git a/schedule.php b/schedule.php
new file mode 100644
index 0000000..14fba26
--- /dev/null
+++ b/schedule.php
@@ -0,0 +1,91 @@
+<?php
+
+/* * ******************************************************\
+ | Scheduled Jobs |
+ | |
+ | This script checks for pending scheduled notifications |
+ | and sends them at the right time. |
+ | |
+ | Setup a cronjob that calls this file on a regular basis|
+ | (crontab -l to view, crontab -e to edit on linux)
+ | Example for a cronjob entry: |
+ | |
+ | 7 * * * * php flyspray/schedule.php |
+ | |
+ | Similiar setup possible with Windows Task Scheduler(MS)|
+ \****************************************************** */
+
+define('IN_FS', true);
+
+if (PHP_SAPI !== 'cli') {
+ die("Scheduler must be called from the CLI SAPI only, for instance by a cronjob.");
+}
+
+require_once 'header.php';
+include_once BASEDIR . '/includes/class.notify.php';
+
+function send_reminders() {
+ global $db, $fs, $proj;
+
+ $notify = new Notifications;
+ $user = new User(0);
+ $now = time();
+
+ $get_reminders = $db->query("SELECT r.*, t.*, u.*
+ FROM {reminders} r
+ INNER JOIN {users} u ON u.user_id = r.to_user_id
+ INNER JOIN {tasks} t ON r.task_id = t.task_id
+ INNER JOIN {projects} p ON t.project_id = p.project_id
+ WHERE t.is_closed = '0'
+ AND r.start_time < ?
+ AND r.last_sent + r.how_often < ?
+ ORDER BY r.reminder_id", array($now, $now)
+ );
+
+ while ($row = $db->fetchRow($get_reminders)) {
+ // So that the sender in emails will is the right project, not 'Default project'
+ // and also to get the projects default language, if needed.
+ $proj = new Project($row['project_id']);
+ $jabber_users = array();
+ $email_users = array();
+
+ if (( $fs->prefs['user_notify'] == 1 || $fs->prefs['user_notify'] == 2 ) && ($row['notify_type'] == 1 || $row['notify_type'] == 3 )) {
+ $email_users[] = $row['email_address'];
+ }
+
+ if (( $fs->prefs['user_notify'] == 1 || $fs->prefs['user_notify'] == 3 ) && ($row['notify_type'] == 2 || $row['notify_type'] == 3 )) {
+ $jabber_users[] = $row['jabber_id'];
+ }
+
+ if (!empty($row['lang_code'])) {
+ $lang = $row['lang_code'];
+ } else if (!empty($proj->prefs['lang_code'])) {
+ $lang = $proj->prefs['lang_code'];
+ } else {
+ $lang = $fs->prefs['lang_code'];
+ }
+
+ $subject = tL('notifyfromfs', $lang);
+ $message = $row['reminder_message'];
+
+ // Pass the recipients and message onto the notification function
+ $notify->sendEmail($email_users, $subject, $message);
+ $notify->storeJabber($jabber_users, $subject, $message);
+
+ // Update the database with the time sent
+ $update_db = $db->query("UPDATE {reminders}
+ SET last_sent = ?
+ WHERE reminder_id = ?", array(time(), $row['reminder_id']));
+ }
+
+ // send those stored notifications
+ $notify->sendJabber();
+ unset($notify, $user);
+}
+
+if (isset($conf['general']['reminder_daemon']) && ($conf['general']['reminder_daemon'] == 1)) {
+ send_reminders();
+} else {
+ die("Scheduled Reminding is disabled.");
+}
+?>
diff --git a/scripts/activity.php b/scripts/activity.php
new file mode 100644
index 0000000..7968c19
--- /dev/null
+++ b/scripts/activity.php
@@ -0,0 +1,64 @@
+<?php
+/*****************************\
+| Activity Graph Maker |
+| Renders a graph for topview |
+\*****************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$data='';
+
+# Project Graph
+if ((Get::has('project_id') && Get::val('graph', 'project') == 'project')) {
+ if ($user->can_view_project(Get::num('project_id'))) {
+ $today = date('Y-m-d');
+ $thirtyone_days = date('U' , strtotime("-31 day", strtotime($today)));
+ $sixtyone_days = date('U' , strtotime("-61 day", strtotime($today)));
+
+ //look 30 + days and if found scale
+ $projectCheck = Project::getActivityProjectCount($sixtyone_days, $thirtyone_days, Get::num('project_id'));
+
+ if($projectCheck > 0) {
+ $data = Project::getDayActivityByProject($sixtyone_days, date('U', strtotime(date('Y-m-d'))), Get::num('project_id'));
+ } else {
+ $data = Project::getDayActivityByProject($thirtyone_days, date('U', strtotime(date('Y-m-d'))), Get::num('project_id'));
+ }
+
+ $data = implode(',', $data);
+ } else {
+ # and make the zero-line 'invisible'
+ $_GET['line']='fff';
+ }
+# User Graph
+} else if(Get::has('user_id') && Get::has('project_id') && Get::val('graph') == 'user') {
+ if ($user->can_view_project(Get::num('project_id'))) {
+ $today = date('Y-m-d');
+ $thirtyone_days = date('U' , strtotime("-31 day", strtotime($today)));
+ $sixtyone_days = date('U' , strtotime("-61 day", strtotime($today)));
+
+ //look 30 + days and if found scale
+ $projectCheck = Project::getActivityProjectCount($sixtyone_days, $thirtyone_days, Get::num('project_id'));
+
+ if($projectCheck > 0) {
+ $data = User::getDayActivityByUser($sixtyone_days, date('U', strtotime(date('Y-m-d'))), Get::num('project_id'), Get::num('user_id'));
+ } else {
+ $data = User::getDayActivityByUser($thirtyone_days, date('U', strtotime(date('Y-m-d'))), Get::num('project_id'), Get::num('user_id'));
+ }
+
+ $data = implode(',', $data);
+ } else {
+ # and make the zero-line 'invisible'
+ $_GET['line']='fff';
+ }
+} else {
+ # make the zero-line 'invisible'
+ $_GET['line']='fff';
+}
+
+// Not pretty but gets the job done.
+$_SERVER['QUERY_STRING'] = 'size=160x25&data='. $data;
+$_GET['size'] = '160x25';
+$_GET['data'] = $data;
+require dirname(__DIR__) . '/vendor/jamiebicknell/Sparkline/sparkline.php';
diff --git a/scripts/admin.php b/scripts/admin.php
new file mode 100644
index 0000000..08c0caa
--- /dev/null
+++ b/scripts/admin.php
@@ -0,0 +1,185 @@
+<?php
+
+ /***********************************************\
+ | Administrator's Toolbox |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~ |
+ | This script allows members of a global Admin |
+ | group to modify the global preferences, user |
+ | profiles, global lists, global groups, pretty |
+ | much everything global. |
+ \***********************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$user->perms('is_admin')) {
+ Flyspray::show_error(4);
+}
+
+$proj = new Project(0);
+#I $proj->setCookie();
+
+$page->pushTpl('admin.menu.tpl');
+
+switch ($area = Req::val('area', 'prefs')) {
+ case 'users':
+ $id = Flyspray::usernameToId(Req::val('user_name'));
+ if (!$id) {
+ $id = is_numeric(Req::val('user_id')) ? Req::val('user_id') : 0;
+ }
+ $theuser = new User($id, $proj);
+ if ($theuser->isAnon()) {
+ Flyspray::show_error(5, true, null, $_SESSION['prev_page']);
+ }
+ $page->assign('theuser', $theuser);
+ case 'cat':
+ case 'editgroup':
+ // yeah, utterly stupid, is changed in 1.0 already
+ if (Req::val('area') == 'editgroup') {
+ $group_details = Flyspray::getGroupDetails(Req::num('id'));
+ if (!$group_details || $group_details['project_id'] != $proj->id) {
+ Flyspray::show_error(L('groupnotexist'));
+ Flyspray::redirect(createURL('pm', 'groups', $proj->id));
+ }
+ $page->uses('group_details');
+ }
+ case 'groups':
+ case 'newuser':
+ case 'newuserbulk':
+ case 'editallusers':
+ $page->assign('groups', Flyspray::listGroups());
+ case 'userrequest':
+ $sql = $db->query("SELECT *
+ FROM {admin_requests}
+ WHERE request_type = 3 AND project_id = 0 AND resolved_by = 0
+ ORDER BY time_submitted ASC");
+
+ $page->assign('pendings', $db->fetchAllArray($sql));
+ case 'newproject':
+ case 'os':
+ case 'prefs':
+ case 'resolution':
+ case 'tasktype':
+ case 'tag':
+ case 'status':
+ case 'version':
+ case 'newgroup':
+ $page->setTitle($fs->prefs['page_title'] . L('admintoolboxlong'));
+ $page->pushTpl('admin.'.$area.'.tpl');
+ break;
+
+ case 'translations':
+ require_once(BASEDIR.'/scripts/langdiff.php');
+ break;
+
+ case 'checks':
+ $hashtypes=$db->query('
+ SELECT COUNT(*) c, LENGTH(user_pass) l,
+ CASE WHEN SUBSTRING(user_pass FROM 1 FOR 1)=\'$\' THEN 1 ELSE 0 END AS s,
+ SUM(CASE WHEN (SUBSTRING(user_pass FROM 1 FOR 2)=\'$2\' AND SUBSTRING(user_pass FROM 3 FOR 1)=\'$\' ) THEN 1 ELSE 0 END) cr,
+ SUM(CASE WHEN (SUBSTRING(user_pass FROM 1 FOR 2)=\'$2\' AND SUBSTRING(user_pass FROM 3 FOR 1) IN( \'a\', \'x\', \'y\' ) ) THEN 1 ELSE 0 END) bcr,
+ SUM(CASE WHEN SUBSTRING(user_pass FROM 1 FOR 3)=\'$1$\' THEN 1 ELSE 0 END) md5crypt,
+ SUM(CASE WHEN SUBSTRING(user_pass FROM 1 FOR 8)=\'$argon2i\' THEN 1 ELSE 0 END) argon2i
+ FROM {users}
+ GROUP BY LENGTH(user_pass), CASE WHEN SUBSTRING(user_pass FROM 1 FOR 1)=\'$\' THEN 1 ELSE 0 END
+ ORDER BY l ASC, s ASC');
+ $hashlengths='<table><thead><tr><th>strlen</th><th>count</th><th>salted?</th><th>options</th><th>hash algo</th></tr></thead><tbody>';
+ $warnhash=0;
+ $warnhash2=0;
+ while ($r = $db->fetchRow($hashtypes)){
+ $alert='';
+ if( $r['l']==32 && $r['s']==0){ $maybe='md5'; $warnhash+=$r['c']; $alert=' style="background-color:#f99"';}
+ elseif($r['l']==13 && $r['s']==0){ $maybe='CRYPT_STD_DES'; $r['s']=2; $warnhash2+=$r['c']; $alert=' style="background-color:#fc9"';}
+ elseif($r['l']==40 && $r['s']==0){ $maybe='sha1'; $warnhash+=$r['c']; $alert=' style="background-color:#f99"';}
+ elseif($r['l']==128 && $r['s']==0){ $maybe='sha512'; $warnhash+=$r['c']; $alert=' style="background-color:#f99"';}
+ elseif($r['l']==34 && $r['s']==1){ $maybe='CRYPT_MD5';$warnhash2+=$r['c'];$alert=' style="background-color:#fc9"';}
+ elseif($r['l']==60){$maybe='CRYPT_BLOWFISH';}
+ elseif($r['s']==1){
+ $maybe='other pw hashes';
+ if($r['argon2i']>0){$maybe.=': '.$r['argon2i'].' argon2i'; }
+ }else{$maybe='not detected';}
+ $hashlengths.='<tr'.$alert.'><td>'.$r['l'].'</td><td> '.$r['c'].'</td><td>'.$r['s'].'</td><td>'.$r['bcr'].' '.$r['cr'].' '.$r['md5crypt'].' '.$r['argon2i'].'</td><td>'.$maybe.'</td></tr>';
+ }
+ $hashlengths.='</tbody></table>';
+ if($warnhash>0){
+ $hashlengths.='<div class="error">'.$warnhash." users with unsalted password hashes.</div>";
+ }
+ if($warnhash2>0){
+ $hashlengths.='<div class="error">'.$warnhash2." users with salted password hashes, but considered bad algorithms for password hashing.</div>";
+ }
+ $page->assign('passwdcrypt', $conf['general']['passwdcrypt']);
+ $page->assign('hashlengths', $hashlengths);
+
+ # info of old temporary unfinished user registration entries, for insights into register bot pattern or for cleanup old entries to free unused usernames as available again.
+ $statregistrations=$db->query('SELECT COUNT(*) FROM {registrations}');
+ $regcount=$db->fetchOne($statregistrations);
+ $page->assign('regcount', $regcount);
+
+ # show oldest unfinished user registrations
+ $registrations=$db->query('SELECT reg_time, user_name, email_address FROM {registrations}
+ ORDER BY reg_time ASC
+ LIMIT 50');
+ $page->assign('registrations', $db->fetchAllArray($registrations));
+
+ $sinfo=$db->dblink->serverInfo();
+ if( ($db->dbtype=='mysqli' || $db->dbtype=='mysql') && isset($sinfo['version'])){
+ $fsdb=$db->query("SELECT default_character_set_name, default_collation_name
+ FROM INFORMATION_SCHEMA.SCHEMATA
+ WHERE SCHEMA_NAME=?", array($db->dblink->database)
+ );
+ $page->assign('fsdb', $db->fetchRow($fsdb));
+
+ # TODO Test if Flyspray tables really have default charset utf8mb4 and default collation utf8mb4_unicode_ci.
+ # TODO Test if the TEXT/CHAR/VARCHAR fields that should have utf8mb_unicode_ci really have it.
+ # TODO Test if the TEXT/CHAR/VARCHAR fields that should have other collations really have that other collation.
+ # utf8mb4_unicode_ci may be not optimal for every TEXT/CHAR/VARCHAR field of Flyspray.
+ # Must be defined explicit for fields that differs from the default in the xmlschemas in the setup/upgrade/* files.
+ # At the moment (in 2019) the current ADODB 5.20.14 release does not handle that stuff yet.
+
+ if(version_compare($sinfo['version'], '5.5.3')>=0 ){
+ $page->assign('utf8mb4upgradable', "Your MySQL supports full utf-8 since 5.5.3. You are using ".$sinfo['version']." and Flyspray tables could be upgraded.");
+ } else{
+ $page->assign('oldmysqlversion', "Your MySQL version ".$sinfo['version']." does not support full utf-8, only up to 3 Byte chars. No emojis for instance. Consider upgrading your MySQL server version.");
+ }
+
+ $fstables=$db->query("SELECT table_name, table_collation, engine as table_type, create_options, table_comment
+ FROM INFORMATION_SCHEMA.tables
+ WHERE table_schema=? AND table_name LIKE '".$db->dbprefix."%'
+ ORDER BY table_name ASC", array($db->dblink->database)
+ );
+ $page->assign('fstables', $db->fetchAllArray($fstables));
+
+ $fsfields=$db->query("
+ SELECT table_name, column_name, column_default, data_type, character_set_name, collation_name, column_type, column_comment
+ FROM INFORMATION_SCHEMA.columns
+ WHERE table_schema=? AND table_name LIKE '".$db->dbprefix."%'
+ ORDER BY table_name ASC, ordinal_position ASC", array($db->dblink->database)
+ );
+ $page->assign('fsfields', $db->fetchAllArray($fsfields));
+
+ } elseif($db->dbtype=='pgsql'){
+ $fstables=$db->query("SELECT table_name, '' AS table_collation, table_type, '' AS create_options, '-' AS table_comment
+ FROM INFORMATION_SCHEMA.tables
+ WHERE table_catalog=? AND table_name LIKE '".$db->dbprefix."%'
+ ORDER BY table_name ASC", array($db->dblink->database)
+ );
+ $page->assign('fstables', $db->fetchAllArray($fstables));
+
+ $fsfields=$db->query("
+ SELECT table_name, column_name, column_default, data_type as column_type, character_set_name, collation_name, '-' AS column_comment
+ FROM INFORMATION_SCHEMA.columns
+ WHERE table_catalog=? AND table_name LIKE '".$db->dbprefix."%'
+ ORDER BY table_name ASC, ordinal_position ASC", array($db->dblink->database)
+ );
+ $page->assign('fsfields', $db->fetchAllArray($fsfields));
+ }
+ $page->assign('adodbversion', $db->dblink->version());
+ $page->assign('htmlpurifierversion', HTMLPurifier::VERSION);
+ $page->pushTpl('admin.'.$area.'.tpl');
+ break;
+ default:
+ Flyspray::show_error(6);
+}
+
+?>
diff --git a/scripts/authenticate.php b/scripts/authenticate.php
new file mode 100644
index 0000000..dbcd882
--- /dev/null
+++ b/scripts/authenticate.php
@@ -0,0 +1,104 @@
+<?php
+
+ /********************************************************\
+ | User authentication (no output) |
+ | ~~~~~~~~~~~~~~~~~~~ |
+ \********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (Req::val('logout')) {
+ $user->logout();
+ Flyspray::redirect($baseurl);
+}
+
+if (Req::val('user_name') != '' && Req::val('password') != '') {
+ // Otherwise, they requested login. See if they provided the correct credentials...
+ // FIXME: Do not do clean_username. Should not autostrip stuff
+ // $username = Backend::clean_username(Req::val('user_name'));
+ $username = Req::val('user_name');
+ $password = Req::val('password');
+
+ // Run the username and password through the login checker
+ if (($user_id = Flyspray::checkLogin($username, $password)) < 1) {
+ $_SESSION['failed_login'] = Req::val('user_name');
+ if($user_id === -2) {
+ Flyspray::show_error(L('usernotexist'));
+ }elseif ($user_id === -1) {
+ Flyspray::show_error(23);
+ } else /* $user_id == 0 */ {
+ // just some extra check here so that never ever an account can get locked when it's already disabled
+ // ... that would make it easy to get enabled
+ $db->query('UPDATE {users} SET login_attempts = login_attempts+1 WHERE account_enabled = 1 AND user_name = ?',
+ array($username));
+ // Lock account if failed too often for a limited amount of time
+ $db->query('UPDATE {users} SET lock_until = ?, account_enabled = 0 WHERE login_attempts > ? AND user_name = ?',
+ array(time() + 60 * $fs->prefs['lock_for'], LOGIN_ATTEMPTS, $username));
+
+ if ($db->affectedRows()) {
+ Flyspray::show_error(sprintf(L('error71'), $fs->prefs['lock_for']));
+ Flyspray::redirect($baseurl);
+ } else {
+ Flyspray::show_error(7);
+ }
+ }
+ } else {
+ // Determine if the user should be remembered on this machine
+ if (Req::has('remember_login')) {
+ $cookie_time = time() + (60 * 60 * 24 * 30); // Set cookies for 30 days
+ } else {
+ $cookie_time = 0; // Set cookies to expire when session ends (browser closes)
+ }
+
+ $user = new User($user_id);
+
+ # check if user still has an outdated password hash and upgrade it
+ if( $conf['general']['passwdcrypt']!='md5'
+ && $conf['general']['passwdcrypt']!='sha1'
+ && $conf['general']['passwdcrypt']!='sha512'
+ ){
+ if( substr($user->infos['user_pass'],0,1)!='$'
+ && ( strlen($user->infos['user_pass'])==32
+ || strlen($user->infos['user_pass'])==40
+ || strlen($user->infos['user_pass'])==128
+ )
+ ){
+ # upgrade from unsalted md5 or unsalted sha1 or unsalted sha512 to better
+ if($conf['general']['passwdcrypt']=='argon2i'){
+ $newhash=password_hash($password, PASSWORD_ARGON2I);
+ }else{
+ $cryptoptions=array('cost'=>12);
+ $newhash=password_hash($password, PASSWORD_BCRYPT, $cryptoptions);
+ }
+ # save the new hash
+ $db->query("UPDATE {users} SET user_pass=? WHERE user_id=?", array($newhash, $user_id));
+ # reload the user with updated data
+ $user= new User($user_id);
+ }
+ }
+
+ // Set a couple of cookies
+ $passweirded = crypt($user->infos['user_pass'], $conf['general']['cookiesalt']);
+ Flyspray::setCookie('flyspray_userid', $user->id, $cookie_time,null,null,null,true);
+ Flyspray::setCookie('flyspray_passhash', $passweirded, $cookie_time,null,null,null,true);
+ // If the user had previously requested a password change, remove the magic url
+ $remove_magic = $db->query("UPDATE {users} SET magic_url = '' WHERE user_id = ?",
+ array($user->id));
+ // Save for displaying
+ if ($user->infos['login_attempts'] > 0) {
+ $_SESSION['login_attempts'] = $user->infos['login_attempts'];
+ }
+ $db->query('UPDATE {users} SET login_attempts = 0, last_login = ? WHERE user_id = ?', array(time(), $user->id));
+
+ $_SESSION['SUCCESS'] = L('loginsuccessful');
+ }
+}
+else {
+ // If the user didn't provide both a username and a password, show this error:
+ Flyspray::show_error(8);
+}
+
+Flyspray::redirect(Req::val('return_to'));
+?>
diff --git a/scripts/depends.php b/scripts/depends.php
new file mode 100644
index 0000000..0621a4a
--- /dev/null
+++ b/scripts/depends.php
@@ -0,0 +1,196 @@
+<?php
+
+ /********************************************************\
+ | Task Dependancy Graph |
+ | ~~~~~~~~~~~~~~~~~~~~~ |
+ \********************************************************/
+
+/**
+ * XXX: This stuff looks incredible ugly, rewrite me for 1.0
+ */
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if ( !($task_details = Flyspray::getTaskDetails(Req::num('task_id')))
+ || !$user->can_view_task($task_details))
+{
+ Flyspray::show_error(9);
+}
+
+$id = Req::num('task_id');
+$page->assign('task_id', $id);
+
+$prunemode = Req::num('prune', 0);
+$selfurl = createURL('depends', $id);
+$pmodes = array(L('none'), L('pruneclosedlinks'), L('pruneclosedtasks'));
+
+foreach ($pmodes as $mode => $desc) {
+ if ($mode == $prunemode) {
+ $strlist[] = $desc;
+ } else {
+ $strlist[] = "<a href='". htmlspecialchars($selfurl, ENT_QUOTES, 'utf-8') .
+ ($mode !=0 ? "&amp;prune=$mode" : "") . "'>$desc</a>\n";
+ }
+}
+
+$page->uses('strlist');
+
+$starttime = microtime();
+
+$sql= 'SELECT t1.task_id AS id1, t1.item_summary AS sum1,
+ t1.percent_complete AS pct1, t1.is_closed AS clsd1,
+ lst1.status_name AS stat1, t1.task_severity AS sev1,
+ t1.task_priority AS pri1,
+ t1.closure_comment AS com1, u1c.real_name AS clsdby1,
+ r1.resolution_name as res1,
+ t2.task_id AS id2, t2.item_summary AS sum2,
+ t2.percent_complete AS pct2, t2.is_closed AS clsd2,
+ lst2.status_name AS stat2, t2.task_severity AS sev2,
+ t2.task_priority AS pri2,
+ t2.closure_comment AS com2, u2c.real_name AS clsdby2,
+ r2.resolution_name as res2
+ FROM {dependencies} AS d
+ JOIN {tasks} AS t1 ON d.task_id=t1.task_id
+ LEFT JOIN {users} AS u1c ON t1.closed_by=u1c.user_id
+ LEFT JOIN {list_status} AS lst1 ON t1.item_status = lst1.status_id
+ LEFT JOIN {list_resolution} AS r1 ON t1.resolution_reason=r1.resolution_id
+ JOIN {tasks} AS t2 ON d.dep_task_id=t2.task_id
+ LEFT JOIN {list_status} AS lst2 ON t2.item_status = lst2.status_id
+ LEFT JOIN {users} AS u2c ON t2.closed_by=u2c.user_id
+ LEFT JOIN {list_resolution} AS r2 ON t2.resolution_reason=r2.resolution_id
+ WHERE t1.project_id= ?
+ ORDER BY d.task_id, d.dep_task_id';
+
+$get_edges = $db->query($sql, array($proj->id));
+
+$edge_list = array();
+$rvrs_list = array();
+$node_list = array();
+while ($row = $db->fetchRow($get_edges)) {
+ extract($row, EXTR_REFS);
+ $edge_list[$id1][] = $id2;
+ $rvrs_list[$id2][] = $id1;
+ if (!isset($node_list[$id1])) {
+ $node_list[$id1] =
+ array('id'=>$id1, 'sum'=>$sum1, 'pct'=>$pct1, 'clsd'=>$clsd1,
+ 'status_name'=>$stat1, 'sev'=>$sev1, 'pri'=>$pri1,
+ 'com'=>$com1, 'clsdby'=>$clsdby1, 'res'=>$res1);
+ }
+ if (!isset($node_list[$id2])) {
+ $node_list[$id2] =
+ array('id'=>$id2, 'sum'=>$sum2, 'pct'=>$pct2, 'clsd'=>$clsd2,
+ 'status_name'=>$stat2, 'sev'=>$sev2, 'pri'=>$pri2,
+ 'com'=>$com2, 'clsdby'=>$clsdby2, 'res'=>$res2);
+ }
+}
+
+// Now we have our lists of nodes and edges, along with a helper
+// list of reverse edges. Time to do the graph coloring, so we know
+// which ones are in our particular connected graph. We'll set up a
+// list and fill it up as we visit nodes that are connected to our
+// main task.
+
+$connected = array();
+$levelsdown = 0;
+$levelsup = 0;
+function connectsTo($id, $down, $up) {
+ global $connected, $edge_list, $rvrs_list, $levelsdown, $levelsup;
+ global $prunemode, $node_list;
+ if (!isset($connected[$id])) { $connected[$id]=1; }
+ if ($down > $levelsdown) { $levelsdown = $down; }
+ if ($up > $levelsup ) { $levelsup = $up ; }
+
+/*
+echo '<pre><code>';
+echo "$id ($down d, $up u) => $levelsdown d $levelsup u<br>\n";
+echo 'nodes:';print_r($node_list);
+echo 'edges:';print_r($edge_list);
+echo 'rvrs:';print_r($rvrs_list);
+echo 'levelsdown:';print_r($levelsdown);
+echo "\n".'levelsup';print_r($levelsup);
+echo '<code></pre>';
+*/
+ if (empty($node_list)){ return; }
+ if (!isset($node_list[$id])){ return; }
+ $selfclosed = $node_list[$id]['clsd'];
+ if (isset($edge_list[$id])) {
+ foreach ($edge_list[$id] as $neighbor) {
+ $neighborclosed = $node_list[$neighbor]['clsd'];
+ if (!isset($connected[$neighbor]) &&
+ !($prunemode==1 && $selfclosed && $neighborclosed) &&
+ !($prunemode==2 && $neighborclosed)) {
+ connectsTo($neighbor, $down, $up+1);
+ }
+ }
+ }
+ if (isset($rvrs_list[$id])) {
+ foreach ($rvrs_list[$id] as $neighbor) {
+ $neighborclosed = $node_list[$neighbor]['clsd'];
+ if (!isset($connected[$neighbor]) &&
+ !($prunemode==1 && $selfclosed && $neighborclosed) &&
+ !($prunemode==2 && $neighborclosed)) {
+ connectsTo($neighbor, $down+1, $up);
+ }
+ }
+ }
+}
+
+connectsTo($id, 0, 0);
+$connected_nodes = array_keys($connected);
+sort($connected_nodes);
+
+// Now lets get rid of the extra junk in our arrays.
+// In prunemode 0, we know we're only going to have to get rid of
+// whole lists, and not elements in the lists, because if they were
+// in the list, they'd be connected, so we wouldn't be removing them.
+// In prunemode 1 or 2, we may have to remove stuff from the list, because
+// you can have an edge to a node that didn't end up connected.
+foreach (array("edge_list", "rvrs_list", "node_list") as $l) {
+ foreach (${$l} as $n => $list) {
+ if (!isset($connected[$n])) {
+ unset(${$l}[$n]);
+ }
+ if ($prunemode!=0 && $l!="node_list" && isset(${$l}[$n])) {
+ // Only keep entries that appear in the $connected_nodes list
+ ${$l}[$n] = array_intersect(${$l}[$n], $connected_nodes);
+ }
+ }
+}
+
+// Now we've got everything we need... prepare JSON data
+$resultData = array();
+foreach ($node_list as $task_id => $taskInfo) {
+ $adjacencies = array();
+ if (isset($edge_list[$task_id])) {
+ foreach ($edge_list[$task_id] as $dst) {
+ array_push($adjacencies, array('nodeTo' => $dst, 'nodeFrom' => $task_id));
+ }
+ }
+
+ if ($task_id == $id) {
+ $color = '#5F9729';
+ } else if ($taskInfo['clsd']) {
+ $color = '#808080';
+ } else {
+ $color = '#83548B';
+ }
+
+ $newTask = array('id' => $task_id,
+ 'name' => tpl_tasklink($task_id),
+ 'data' => array('$color' => $color,
+ '$type' => 'circle',
+ '$dim' => 15),
+ 'adjacencies' => $adjacencies);
+
+ array_push($resultData, $newTask);
+}
+
+$jasonData = json_encode($resultData);
+$page->assign('jasonData', $jasonData);
+$page->assign('task_id', $id);
+
+$page->setTitle(sprintf('FS#%d : %s', $id, L('dependencygraph')));
+$page->pushTpl('depends.tpl');
+?>
diff --git a/scripts/details.php b/scripts/details.php
new file mode 100644
index 0000000..421a68a
--- /dev/null
+++ b/scripts/details.php
@@ -0,0 +1,768 @@
+<?php
+
+ /*************************************************************\
+ | Details a task (and edit it) |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+ | This script displays task details when in view mode, |
+ | and allows the user to edit task details when in edit mode. |
+ | It also shows comments, attachments, notifications etc. |
+ \*************************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$task_id = Req::num('task_id');
+
+if ( !($task_details = Flyspray::GetTaskDetails($task_id)) ) {
+ Flyspray::show_error(10);
+}
+if (!$user->can_view_task($task_details)) {
+ Flyspray::show_error( $user->isAnon() ? 102 : 101, false);
+} else{
+
+ require_once(BASEDIR . '/includes/events.inc.php');
+
+ if($proj->prefs['use_effort_tracking']){
+ require_once(BASEDIR . '/includes/class.effort.php');
+ $effort = new effort($task_id,$user->id);
+ $effort->populateDetails();
+ $page->assign('effort',$effort);
+ }
+
+ $page->uses('task_details');
+
+ // Send user variables to the template
+ $page->assign('assigned_users', $task_details['assigned_to']);
+ $page->assign('old_assigned', implode(' ', $task_details['assigned_to']));
+ $page->assign('tags', $task_details['tags']);
+
+ $page->setTitle(sprintf('FS#%d : %s', $task_details['task_id'], $task_details['item_summary']));
+
+
+ if ((Get::val('edit') || (Post::has('item_summary') && !isset($_SESSION['SUCCESS']))) && $user->can_edit_task($task_details)) {
+
+ if(isset($move) && $move==1){
+ if( !$user->perms('modify_all_tasks', $toproject->id)){
+ Flyspray::show_error('invalidtargetproject');
+ }
+ }
+
+ $result = $db->query('
+ SELECT g.project_id, u.user_id, u.user_name, u.real_name, g.group_id, g.group_name
+ FROM {users} u
+ JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ JOIN {groups} g ON g.group_id = uig.group_id
+ WHERE (g.show_as_assignees = 1 OR g.is_admin = 1)
+ AND (g.project_id = 0 OR g.project_id = ?)
+ AND u.account_enabled = 1
+ ORDER BY g.project_id ASC, g.group_name ASC, u.user_name ASC',
+ ($proj->id ? $proj->id : -1)
+ ); // FIXME: -1 is a hack. when $proj->id is 0 the query fails
+
+ $userlist = array();
+ $userids = array();
+ while ($row = $db->fetchRow($result)) {
+ if( !in_array($row['user_id'], $userids) ){
+ $userlist[$row['group_id']][] = array(
+ 0 => $row['user_id'],
+ 1 => sprintf('%s (%s)', $row['user_name'], $row['real_name']),
+ 2 => $row['project_id'],
+ 3 => $row['group_name']
+ );
+ $userids[]=$row['user_id'];
+ } else{
+ # user is probably in a global group with assignee permission listed, so no need to show second time in a project group.
+ }
+ }
+
+ if (is_array(Post::val('rassigned_to'))) {
+ $page->assign('assignees', Post::val('rassigned_to'));
+ } else {
+ $assignees = $db->query('SELECT user_id FROM {assigned} WHERE task_id = ?', $task_details['task_id']);
+ $page->assign('assignees', $db->fetchCol($assignees));
+ }
+ $page->assign('userlist', $userlist);
+
+ # Build the select arrays, for 'move task' or normal taskedit
+ # Then in the template just use tpl_select($xxxselect);
+
+ # keep last selections
+ $catselected=Req::val('product_category', $task_details['product_category']);
+ $osselected=Req::val('operating_system', $task_details['operating_system']);
+ $ttselected=Req::val('task_type', $task_details['task_type']);
+ $stselected=Req::val('item_status', $task_details['item_status']);
+ $repverselected=Req::val('reportedver', $task_details['product_version']);
+ $dueverselected=Req::val('closedby_version', $task_details['closedby_version']);
+ if(isset($move) && $move==1){
+ # get global categories
+ $gcats=$proj->listCategories(0);
+ if( count($gcats)>0){
+ foreach($gcats as $cat){
+ $gcatopts[]=array('value'=>$cat['category_id'], 'label'=>$cat['category_name']);
+ if($catselected==$cat['category_id']){
+ $gcatopts[count($gcatopts)-1]['selected']=1;
+ }
+ }
+ #$catsel['options'][]=array('optgroup'=>1, 'label'=>L('categoriesglobal'), 'options'=>$gcatopts);
+ $catsel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$gcatopts);
+ }
+ # get project categories
+ $pcats=$proj->listCategories($proj->id);
+ if( count($pcats)>0){
+ foreach($pcats as $cat){
+ $pcatopts[]=array('value'=>$cat['category_id'], 'label'=>$cat['category_name']);
+ if($catselected==$cat['category_id']){
+ $pcatopts[count($pcatopts)-1]['selected']=1;
+ }
+ }
+ #$catsel['options'][]=array('optgroup'=>1, 'label'=>L('categoriesproject').' '.$proj->prefs['project_title'], 'options'=>$pcatopts);
+ $catsel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$pcatopts);
+ }
+ # get target categories
+ $tcats=$toproject->listCategories($toproject->id);
+ if( count($tcats)>0){
+ foreach($tcats as $cat){
+ $tcatopts[]=array('value'=>$cat['category_id'], 'label'=>$cat['category_name']);
+ if($catselected==$cat['category_id']){
+ $tcatopts[count($tcatopts)-1]['selected']=1;
+ }
+ }
+ #$catsel['options'][]=array('optgroup'=>1, 'label'=>L('categoriestarget').' '.$toproject->prefs['project_title'], 'options'=>$tcatopts);
+ $catsel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$tcatopts);
+ }
+
+
+ # get global task statuses
+ $resgst=$db->query("SELECT status_id, status_name, list_position, show_in_list FROM {list_status} WHERE project_id=0 ORDER BY list_position");
+ $gsts=$db->fetchAllArray($resgst);
+ if(count($gsts)>0){
+ foreach($gsts as $gst){
+ $gstopts[]=array('value'=>$gst['status_id'], 'label'=>$gst['status_name']);
+ if($stselected==$gst['status_id']){
+ $gstopts[count($gstopts)-1]['selected']=1;
+ }
+ if($gst['show_in_list']==0){
+ $gstopts[count($gstopts)-1]['disabled']=1;
+ }
+ }
+ $statussel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$gstopts);
+ }
+ # get current project task statuses
+ $rescst=$db->query("SELECT status_id, status_name, list_position, show_in_list FROM {list_status} WHERE project_id=? ORDER BY list_position", array($proj->id));
+ $csts=$db->fetchAllArray($rescst);
+ if(count($csts)>0){
+ foreach($csts as $cst){
+ $cstopts[]=array('value'=>$cst['status_id'], 'label'=>$cst['status_name']);
+ if($stselected==$cst['status_id']){
+ $cstopts[count($cstopts)-1]['selected']=1;
+ }
+ if($cst['show_in_list']==0){
+ $cstopts[count($cstopts)-1]['disabled']=1;
+ }
+ }
+ $statussel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$cstopts);
+ }
+ # get target project task statuses
+ $restst=$db->query("SELECT status_id, status_name, list_position, show_in_list FROM {list_status} WHERE project_id=? ORDER BY list_position", array($toproject->id));
+ $tsts=$db->fetchAllArray($restst);
+ if(count($tsts)>0){
+ foreach($tsts as $tst){
+ $tstopts[]=array('value'=>$tst['status_id'], 'label'=>$tst['status_name']);
+ if($stselected==$tst['status_id']){
+ $tstopts[count($tstopts)-1]['selected']=1;
+ }
+ if($tst['show_in_list']==0){
+ $tstopts[count($tstopts)-1]['disabled']=1;
+ }
+ }
+ $statussel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$tstopts);
+ }
+
+
+ # get list global tasktypes
+ $resgtt=$db->query("SELECT tasktype_id, tasktype_name, list_position, show_in_list FROM {list_tasktype} WHERE project_id=0 ORDER BY list_position");
+ $gtts=$db->fetchAllArray($resgtt);
+ if(count($gtts)>0){
+ foreach($gtts as $gtt){
+ $gttopts[]=array('value'=>$gtt['tasktype_id'], 'label'=>$gtt['tasktype_name']);
+ if($ttselected==$gtt['tasktype_id']){
+ $gttopts[count($gttopts)-1]['selected']=1;
+ }
+ }
+ $tasktypesel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$gttopts);
+ }
+ # get current project tasktypes
+ $resctt=$db->query("SELECT tasktype_id, tasktype_name, list_position, show_in_list FROM {list_tasktype} WHERE project_id=? ORDER BY list_position", array($proj->id));
+ $ctts=$db->fetchAllArray($resctt);
+ if(count($ctts)>0){
+ foreach($ctts as $ctt){
+ $cttopts[]=array('value'=>$ctt['tasktype_id'], 'label'=>$ctt['tasktype_name']);
+ if($ttselected==$ctt['tasktype_id']){
+ $cttopts[count($cttopts)-1]['selected']=1;
+ }
+ }
+ $tasktypesel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$cttopts);
+ }
+ # get target project tasktypes
+ $resttt=$db->query("SELECT tasktype_id, tasktype_name, list_position, show_in_list FROM {list_tasktype} WHERE project_id=? ORDER BY list_position", array($toproject->id));
+ $ttts=$db->fetchAllArray($resttt);
+ if(count($ttts)>0){
+ foreach($ttts as $ttt){
+ $tttopts[]=array('value'=>$ttt['tasktype_id'], 'label'=>$ttt['tasktype_name']);
+ if($ttselected==$ttt['tasktype_id']){
+ $tttopts[count($tttopts)-1]['selected']=1;
+ }
+ }
+ $tasktypesel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$tttopts);
+ }
+
+
+ # allow unset (0) value (field os_id currently defined with NOT NULL by flyspray-install.xml, so must use 0 instead null)
+ $osfound=0;
+ $ossel['options'][]=array('value'=>0, 'label'=>L('undecided'));
+ # get global operating systems
+ $resgos=$db->query("SELECT os_id, os_name, list_position, show_in_list FROM {list_os} WHERE project_id=0 AND show_in_list=1 ORDER BY list_position");
+ $goses=$db->fetchAllArray($resgos);
+ if(count($goses)>0){
+ foreach($goses as $gos){
+ $gosopts[]=array('value'=>$gos['os_id'], 'label'=>$gos['os_name']);
+ if($osselected==$gos['os_id']){
+ $gosopts[count($gosopts)-1]['selected']=1;
+ $osfound=1;
+ }
+ }
+ $ossel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$gosopts);
+ }
+ # get current project operating systems
+ $rescos=$db->query("SELECT os_id, os_name, list_position, show_in_list FROM {list_os} WHERE project_id=? AND show_in_list=1 ORDER BY list_position", array($proj->id));
+ $coses=$db->fetchAllArray($rescos);
+ if(count($coses)>0){
+ foreach($coses as $cos){
+ $cosopts[]=array('value'=>$cos['os_id'], 'label'=>$cos['os_name']);
+ if($osselected==$cos['os_id']){
+ $cosopts[count($cosopts)-1]['selected']=1;
+ $osfound=1;
+ }
+ }
+ $ossel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$cosopts);
+ }
+ # get target project operating systems
+ $restos=$db->query("SELECT os_id, os_name, list_position, show_in_list FROM {list_os} WHERE project_id=? AND show_in_list=1 ORDER BY list_position", array($toproject->id));
+ $toses=$db->fetchAllArray($restos);
+ if(count($toses)>0){
+ foreach($toses as $tos){
+ $tosopts[]=array('value'=>$tos['os_id'], 'label'=>$tos['os_name']);
+ if($osselected==$tos['os_id']){
+ $tosopts[count($tosopts)-1]['selected']=1;
+ $osfound=1;
+ }
+ }
+ $ossel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$tosopts);
+ }
+ # keep existing operating_system entry choosable even if would not currently selectable by current settings
+ if($osfound==0 && $task_details['operating_system']>0){
+ # get operating_system of that existing old entry, even if show_in_list=0 or other project
+ $resexistos=$db->query("
+ SELECT os.os_id, os.os_name, os.list_position, os.show_in_list, os.project_id, p.project_id AS p_project_id FROM {list_os} os
+ LEFT JOIN {projects} p ON p.project_id=os.project_id
+ WHERE os.os_id=?", array($task_details['operating_system']));
+ $existos=$db->fetchRow($resexistos);
+ if($existos['project_id']==$proj->id){
+ $existosgrouplabel=$proj->prefs['project_title'].': existing reported version';
+ } elseif($existos['project_id']==$toproject->id){
+ $existosgrouplabel=$toproject->prefs['project_title'].': existing reported version';
+ } else{
+ # maybe version_id from other/hidden/forbidden/deleted project, so only show project_id as hint.
+ # if user has view permission of this other project, then showing project_title would be ok -> extra sql required
+ $existosgrouplabel='existing os of project '.($existos['p_project_id']->id);
+ }
+ $existosopts[]=array('value'=>$task_details['operating_system'], 'label'=>$existos['os_name']);
+ if($osselected==$task_details['operating_system']){
+ $existosopts[count($existosopts)-1]['selected']=1;
+ }
+
+ #$ossel['options'][]=array('optgroup'=>1, 'label'=>$existosgrouplabel, 'options'=>$existosopts);
+ # put existing at beginning
+ $ossel['options']=array_merge(array(array('optgroup'=>1, 'label'=>$existosgrouplabel, 'options'=>$existosopts)), $ossel['options']);
+ }
+
+
+
+ # get list global reported versions
+ # FIXME/TODO: Should we use 'show_in_list' list setting here to filter them out here? Or distinguish between editor/projectmanager/admin roles?
+ # FIXME/TODO: All Flyspray version up to 1.0-rc8 only versions with tense=2 were shown for edit.
+ # But what if someone edits an old tasks (maybe reopened an old closed), and that old task is connected with an old reported version (tense=1)
+ # Or that {list_version} entry has now show_in_list=0 set ?
+ # In both cases that version would not be selectable for editing the task, although it is the correct reported version.
+ $reportedversionfound=0;
+ $repversel['options'][]=array('value'=>0, 'label'=>L('undecided'));
+ $resgrepver=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=0
+ AND version_tense=2
+ AND show_in_list=1
+ ORDER BY list_position");
+ $grepvers=$db->fetchAllArray($resgrepver);
+ if(count($grepvers)>0){
+ foreach($grepvers as $grepver){
+ $grepveropts[]=array('value'=>$grepver['version_id'], 'label'=>$grepver['version_name']);
+ if($repverselected==$grepver['version_id']){
+ $grepveropts[count($grepveropts)-1]['selected']=1;
+ $reportedversionfound=1;
+ }
+ }
+ $repversel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$grepveropts);
+ }
+ # get current project reported versions
+ $rescrepver=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=?
+ AND version_tense=2
+ AND show_in_list=1
+ ORDER BY list_position", array($proj->id));
+ $crepvers=$db->fetchAllArray($rescrepver);
+ if(count($crepvers)>0){
+ foreach($crepvers as $crepver){
+ $crepveropts[]=array('value'=>$crepver['version_id'], 'label'=>$crepver['version_name']);
+ if($repverselected==$crepver['version_id']){
+ $crepveropts[count($crepveropts)-1]['selected']=1;
+ $reportedversionfound=1;
+ }
+ }
+ $repversel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$crepveropts);
+ }
+ # get target project reported versions
+ $restrepver=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=?
+ AND version_tense=2
+ AND show_in_list=1
+ ORDER BY list_position", array($toproject->id));
+ $trepvers=$db->fetchAllArray($restrepver);
+ if(count($trepvers)>0){
+ foreach($trepvers as $trepver){
+ $trepveropts[]=array('value'=>$trepver['version_id'], 'label'=>$trepver['version_name']);
+ if($repverselected==$trepver['version_id']){
+ $trepveropts[count($trepveropts)-1]['selected']=1;
+ $reportedversionfound=1;
+ }
+ }
+ $repversel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$trepveropts);
+ }
+ # keep existing reportedversion(product_version) choosable even if would not currently selectable by current settings
+ if($reportedversionfound==0 && $task_details['product_version']>0){
+ # get version_name of that existing old entry, even if tense is past or show_in_list=0 or other project
+ $resexistrepver=$db->query("
+ SELECT v.version_id, v.version_name, v.list_position, v.show_in_list, v.project_id, p.project_id AS p_project_id FROM {list_version} v
+ LEFT JOIN {projects} p ON p.project_id=v.project_id
+ WHERE v.version_id=?", array($task_details['product_version']));
+ $existrepver=$db->fetchRow($resexistrepver);
+ if($existrepver['project_id']==$proj->id){
+ $existgrouplabel=$proj->prefs['project_title'].': existing reported version';
+ } elseif($existrepver['project_id']==$toproject->id){
+ $existgrouplabel=$toproject->prefs['project_title'].': existing reported version';
+ } else{
+ # maybe version_id from other/hidden/forbidden/deleted project, so only show project_id as hint.
+ # if user has view permission of this other project, then showing project_title would be ok -> extra sql required
+ $existgrouplabel='existing reported version of project '.($existrepver['p_project_id']);
+ }
+ $existrepveropts[]=array('value'=>$task_details['product_version'], 'label'=>$existrepver['version_name']);
+ if($repverselected==$task_details['product_version']){
+ $existrepveropts[count($existrepveropts)-1]['selected']=1;
+ }
+
+ #$repversel['options'][]=array('optgroup'=>1, 'label'=>$existgrouplabel, 'options'=>$existrepveropts);
+ # put existing at beginning
+ $repversel['options']=array_merge(array(array('optgroup'=>1, 'label'=>$existgrouplabel, 'options'=>$existrepveropts)), $repversel['options']);
+ }
+
+
+ # get list global due versions
+ # FIXME/TODO: Should we use 'show_in_list' list setting here to filter them out here? Or distinguish between editor/projectmanager/admin roles?
+ $dueversel['options'][]=array('value'=>0, 'label'=>L('undecided'));
+ $resgduever=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=0
+ AND version_tense=3
+ AND show_in_list=1
+ ORDER BY list_position");
+ $gduevers=$db->fetchAllArray($resgduever);
+ if(count($gduevers)>0){
+ foreach($gduevers as $gduever){
+ $gdueveropts[]=array('value'=>$gduever['version_id'], 'label'=>$gduever['version_name']);
+ if($dueverselected==$gduever['version_id']){
+ $gdueveropts[count($gdueveropts)-1]['selected']=1;
+ }
+ }
+ $dueversel['options'][]=array('optgroup'=>1, 'label'=>L('globaloptions'), 'options'=>$gdueveropts);
+ }
+ # get current project due versions
+ $rescduever=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=?
+ AND version_tense=3
+ AND show_in_list=1
+ ORDER BY list_position", array($proj->id));
+ $cduevers=$db->fetchAllArray($rescduever);
+ if(count($cduevers)>0){
+ foreach($cduevers as $cduever){
+ $cdueveropts[]=array('value'=>$cduever['version_id'], 'label'=>$cduever['version_name']);
+ if($dueverselected==$cduever['version_id']){
+ $cdueveropts[count($cdueveropts)-1]['selected']=1;
+ }
+ }
+ $dueversel['options'][]=array('optgroup'=>1, 'label'=>L('currentproject').' '.$proj->prefs['project_title'], 'options'=>$cdueveropts);
+ }
+ # get target project due versions
+ $restduever=$db->query("SELECT version_id, version_name, list_position, show_in_list FROM {list_version}
+ WHERE project_id=?
+ AND version_tense=3
+ AND show_in_list=1
+ ORDER BY list_position", array($toproject->id));
+ $tduevers=$db->fetchAllArray($restduever);
+ if(count($tduevers)>0){
+ foreach($tduevers as $tduever){
+ $tdueveropts[]=array('value'=>$tduever['version_id'], 'label'=>$tduever['version_name']);
+ if($dueverselected==$tduever['version_id']){
+ $tdueveropts[count($tdueveropts)-1]['selected']=1;
+ }
+ }
+ $dueversel['options'][]=array('optgroup'=>1, 'label'=>L('targetproject').' '.$toproject->prefs['project_title'], 'options'=>$tdueveropts);
+ }
+
+
+ }else{
+ # just the normal merged global/project categories
+ $cats=$proj->listCategories();
+ if( count($cats)>0){
+ foreach($cats as $cat){
+ $catopts[]=array('value'=>$cat['category_id'], 'label'=>$cat['category_name']);
+ if($catselected==$cat['category_id']){
+ $catopts[count($catopts)-1]['selected']=1;
+ }
+ }
+ $catsel['options']=$catopts;
+ }
+
+ # just the normal merged global/project statuses
+ $sts=$proj->listTaskStatuses();
+ if( count($sts)>0){
+ foreach($sts as $st){
+ $stopts[]=array('value'=>$st['status_id'], 'label'=>$st['status_name']);
+ if($stselected==$st['status_id']){
+ $stopts[count($stopts)-1]['selected']=1;
+ }
+ }
+ $statussel['options']=$stopts;
+ }
+
+ # just the normal merged global/project tasktypes
+ $tts=$proj->listTaskTypes();
+ if( count($tts)>0){
+ foreach($tts as $tt){
+ $ttopts[]=array('value'=>$tt['tasktype_id'], 'label'=>$tt['tasktype_name']);
+ if($ttselected==$tt['tasktype_id']){
+ $ttopts[count($ttopts)-1]['selected']=1;
+ }
+ }
+ $tasktypesel['options']=$ttopts;
+ }
+
+ # just the normal merged global/project os
+ $osses=$proj->listOs();
+ # also allow unsetting operating system entry
+ $osopts[]=array('value'=>0, 'label'=>L('undecided'));
+ if( count($osses)>0){
+ foreach($osses as $os){
+ $osopts[]=array('value'=>$os['os_id'], 'label'=>$os['os_name']);
+ if($osselected==$os['os_id']){
+ $osopts[count($osopts)-1]['selected']=1;
+ }
+ }
+ $ossel['options']=$osopts;
+ }
+
+ # just the normal merged global/project reported version
+ $repversions=$proj->listVersions(false, 2, $task_details['product_version']);
+ # also allow unsetting dueversion system entry
+ $repveropts[]=array('value'=>0, 'label'=>L('undecided'));
+ if( count($repversions)>0){
+ foreach($repversions as $repver){
+ $repveropts[]=array('value'=>$repver['version_id'], 'label'=>$repver['version_name']);
+ if($repverselected==$repver['version_id']){
+ $repveropts[count($repveropts)-1]['selected']=1;
+ }
+ }
+ $repversel['options']=$repveropts;
+ }
+
+ # just the normal merged global/project dueversion
+ $dueversions=$proj->listVersions(false, 3); # future (tense=3) with 'shown_in_list' set
+ # also allow unsetting dueversion system entry
+ $dueveropts[]=array('value'=>0, 'label'=>L('undecided'));
+ if( count($dueversions)>0){
+ foreach($dueversions as $duever){
+ $dueveropts[]=array('value'=>$duever['version_id'], 'label'=>$duever['version_name']);
+ if($dueverselected==$duever['version_id']){
+ $dueveropts[count($dueveropts)-1]['selected']=1;
+ }
+ }
+ $dueversel['options']=$dueveropts;
+ }
+ }
+ $catsel['name']='product_category';
+ $catsel['attr']['id']='category';
+ $page->assign('catselect', $catsel);
+
+ $statussel['name']='item_status';
+ $statussel['attr']['id']='status';
+ $page->assign('statusselect', $statussel);
+
+ $tasktypesel['name']='task_type';
+ $tasktypesel['attr']['id']='tasktype';
+ $page->assign('tasktypeselect', $tasktypesel);
+
+ $ossel['name']='operating_system';
+ $ossel['attr']['id']='os';
+ $page->assign('osselect', $ossel);
+
+ $repversel['name']='reportedver';
+ $repversel['attr']['id']='reportedver';
+ $page->assign('reportedversionselect', $repversel);
+
+ $dueversel['name']='closedby_version';
+ $dueversel['attr']['id']='dueversion';
+ $page->assign('dueversionselect', $dueversel);
+
+ # user tries to move a task to a different project:
+ if(isset($move) && $move==1){
+ $page->assign('move', 1);
+ $page->assign('toproject', $toproject);
+ }
+ $page->pushTpl('details.edit.tpl');
+ } else {
+ $prev_id = $next_id = 0;
+
+ if (isset($_SESSION['tasklist']) && ($id_list = $_SESSION['tasklist'])
+ && ($i = array_search($task_id, $id_list)) !== false) {
+ $prev_id = isset($id_list[$i - 1]) ? $id_list[$i - 1] : '';
+ $next_id = isset($id_list[$i + 1]) ? $id_list[$i + 1] : '';
+ }
+
+ // Sub-Tasks
+ $subtasks = $db->query('SELECT t.*, p.project_title
+ FROM {tasks} t
+ LEFT JOIN {projects} p ON t.project_id = p.project_id
+ WHERE t.supertask_id = ?',
+ array($task_id));
+ $subtasks_cleaned = Flyspray::weedOutTasks($user, $db->fetchAllArray($subtasks));
+
+ for($i=0;$i<count($subtasks_cleaned);$i++){
+ $subtasks_cleaned[$i]['assigned_to']=array();
+ if ($assignees = Flyspray::getAssignees($subtasks_cleaned[$i]["task_id"], false)) {
+ for($j=0;$j<count($assignees);$j++){
+ $subtasks_cleaned[$i]['assigned_to'][$j] = tpl_userlink($assignees[$j]);
+ }
+ }
+ }
+
+ // Parent categories
+ $parent = $db->query('SELECT *
+ FROM {list_category}
+ WHERE lft < ? AND rgt > ? AND project_id = ? AND lft != 1
+ ORDER BY lft ASC',
+ array($task_details['lft'], $task_details['rgt'], $task_details['cproj']));
+ // Check for task dependencies that block closing this task
+ $check_deps = $db->query('SELECT t.*, s.status_name, r.resolution_name, d.depend_id, p.project_title
+ FROM {dependencies} d
+ LEFT JOIN {tasks} t on d.dep_task_id = t.task_id
+ LEFT JOIN {list_status} s ON t.item_status = s.status_id
+ LEFT JOIN {list_resolution} r ON t.resolution_reason = r.resolution_id
+ LEFT JOIN {projects} p ON t.project_id = p.project_id
+ WHERE d.task_id = ?', array($task_id));
+ $check_deps_cleaned = Flyspray::weedOutTasks($user, $db->fetchAllArray($check_deps));
+
+
+ for($i=0;$i<count($check_deps_cleaned);$i++){
+ $check_deps_cleaned[$i]['assigned_to']=array();
+ if ($assignees = Flyspray::getAssignees($check_deps_cleaned[$i]["task_id"], false)) {
+ for($j=0;$j<count($assignees);$j++){
+ $check_deps_cleaned[$i]['assigned_to'][$j] = tpl_userlink($assignees[$j]);
+ }
+ }
+ }
+
+ // Check for tasks that this task blocks
+ $check_blocks = $db->query('SELECT t.*, s.status_name, r.resolution_name, d.depend_id, p.project_title
+ FROM {dependencies} d
+ LEFT JOIN {tasks} t on d.task_id = t.task_id
+ LEFT JOIN {list_status} s ON t.item_status = s.status_id
+ LEFT JOIN {list_resolution} r ON t.resolution_reason = r.resolution_id
+ LEFT JOIN {projects} p ON t.project_id = p.project_id
+ WHERE d.dep_task_id = ?', array($task_id));
+ $check_blocks_cleaned = Flyspray::weedOutTasks($user, $db->fetchAllArray($check_blocks));
+
+
+ for($i=0;$i<count($check_blocks_cleaned);$i++){
+ $check_blocks_cleaned[$i]['assigned_to']=array();
+ if ($assignees = Flyspray::getAssignees($check_blocks_cleaned[$i]["task_id"], false)) {
+ for($j=0;$j<count($assignees);$j++){
+ $check_blocks_cleaned[$i]['assigned_to'][$j] = tpl_userlink($assignees[$j]);
+ }
+ }
+ }
+
+ // Check for pending PM requests
+ $get_pending = $db->query("SELECT *
+ FROM {admin_requests}
+ WHERE task_id = ? AND resolved_by = 0",
+ array($task_id));
+
+ // Get info on the dependencies again
+ $open_deps = $db->query('SELECT COUNT(*) - SUM(is_closed)
+ FROM {dependencies} d
+ LEFT JOIN {tasks} t on d.dep_task_id = t.task_id
+ WHERE d.task_id = ?', array($task_id));
+
+ $watching = $db->query('SELECT COUNT(*)
+ FROM {notifications}
+ WHERE task_id = ? AND user_id = ?',
+ array($task_id, $user->id));
+
+ // Check if task has been reopened some time
+ $reopened = $db->query('SELECT COUNT(*)
+ FROM {history}
+ WHERE task_id = ? AND event_type = 13',
+ array($task_id));
+
+ // Check for cached version
+ $cached = $db->query("SELECT content, last_updated
+ FROM {cache}
+ WHERE topic = ? AND type = 'task'",
+ array($task_details['task_id']));
+ $cached = $db->fetchRow($cached);
+
+ // List of votes
+ $get_votes = $db->query('SELECT u.user_id, u.user_name, u.real_name, v.date_time
+ FROM {votes} v
+ LEFT JOIN {users} u ON v.user_id = u.user_id
+ WHERE v.task_id = ?
+ ORDER BY v.date_time DESC',
+ array($task_id));
+
+ if ($task_details['last_edited_time'] > $cached['last_updated'] || !defined('FLYSPRAY_USE_CACHE')) {
+ $task_text = TextFormatter::render($task_details['detailed_desc'], 'task', $task_details['task_id']);
+ } else {
+ $task_text = TextFormatter::render($task_details['detailed_desc'], 'task', $task_details['task_id'], $cached['content']);
+ }
+
+ $page->assign('prev_id', $prev_id);
+ $page->assign('next_id', $next_id);
+ $page->assign('task_text', $task_text);
+ $page->assign('subtasks', $subtasks_cleaned);
+ $page->assign('deps', $check_deps_cleaned);
+ $page->assign('parent', $db->fetchAllArray($parent));
+ $page->assign('blocks', $check_blocks_cleaned);
+ $page->assign('votes', $db->fetchAllArray($get_votes));
+ $page->assign('penreqs', $db->fetchAllArray($get_pending));
+ $page->assign('d_open', $db->fetchOne($open_deps));
+ $page->assign('watched', $db->fetchOne($watching));
+ $page->assign('reopened', $db->fetchOne($reopened));
+ $page->pushTpl('details.view.tpl');
+
+ ///////////////
+ // tabbed area
+
+ // Comments + cache
+ $sql = $db->query('SELECT * FROM {comments} c
+ LEFT JOIN {cache} ca ON (c.comment_id = ca.topic AND ca.type = ?)
+ WHERE task_id = ?
+ ORDER BY date_added ASC',
+ array('comm', $task_id));
+ $page->assign('comments', $db->fetchAllArray($sql));
+
+ // Comment events
+ $sql = get_events($task_id, ' AND (event_type = 3 OR event_type = 14)');
+ $comment_changes = array();
+ while ($row = $db->fetchRow($sql)) {
+ $comment_changes[$row['event_date']][] = $row;
+ }
+ $page->assign('comment_changes', $comment_changes);
+
+ // Comment attachments
+ $attachments = array();
+ $sql = $db->query('SELECT *
+ FROM {attachments} a, {comments} c
+ WHERE c.task_id = ? AND a.comment_id = c.comment_id',
+ array($task_id));
+ while ($row = $db->fetchRow($sql)) {
+ $attachments[$row['comment_id']][] = $row;
+ }
+ $page->assign('comment_attachments', $attachments);
+
+ // Comment links
+ $links = array();
+ $sql = $db->query('SELECT *
+ FROM {links} l, {comments} c
+ WHERE c.task_id = ? AND l.comment_id = c.comment_id',
+ array($task_id));
+ while ($row = $db->fetchRow($sql)) {
+ $links[$row['comment_id']][] = $row;
+ }
+ $page->assign('comment_links', $links);
+
+ // Relations, notifications and reminders
+ $sql = $db->query('SELECT t.*, r.*, s.status_name, res.resolution_name
+ FROM {related} r
+ LEFT JOIN {tasks} t ON (r.related_task = t.task_id AND r.this_task = ? OR r.this_task = t.task_id AND r.related_task = ?)
+ LEFT JOIN {list_status} s ON t.item_status = s.status_id
+ LEFT JOIN {list_resolution} res ON t.resolution_reason = res.resolution_id
+ WHERE t.task_id is NOT NULL AND is_duplicate = 0 AND ( t.mark_private = 0 OR ? = 1 )
+ ORDER BY t.task_id ASC',
+ array($task_id, $task_id, $user->perms('manage_project')));
+ $related_cleaned = Flyspray::weedOutTasks($user, $db->fetchAllArray($sql));
+ $page->assign('related', $related_cleaned);
+
+ $sql = $db->query('SELECT t.*, r.*, s.status_name, res.resolution_name
+ FROM {related} r
+ LEFT JOIN {tasks} t ON r.this_task = t.task_id
+ LEFT JOIN {list_status} s ON t.item_status = s.status_id
+ LEFT JOIN {list_resolution} res ON t.resolution_reason = res.resolution_id
+ WHERE is_duplicate = 1 AND r.related_task = ?
+ ORDER BY t.task_id ASC',
+ array($task_id));
+ $duplicates_cleaned = Flyspray::weedOutTasks($user, $db->fetchAllArray($sql));
+ $page->assign('duplicates', $duplicates_cleaned);
+
+ $sql = $db->query('SELECT *
+ FROM {notifications} n
+ LEFT JOIN {users} u ON n.user_id = u.user_id
+ WHERE n.task_id = ?', array($task_id));
+ $page->assign('notifications', $db->fetchAllArray($sql));
+
+ $sql = $db->query('SELECT *
+ FROM {reminders} r
+ LEFT JOIN {users} u ON r.to_user_id = u.user_id
+ WHERE task_id = ?
+ ORDER BY reminder_id', array($task_id));
+ $page->assign('reminders', $db->fetchAllArray($sql));
+
+ $page->pushTpl('details.tabs.tpl');
+
+ if ($user->perms('view_comments') || $proj->prefs['others_view'] || ($user->isAnon() && $task_details['task_token'] && Get::val('task_token') == $task_details['task_token'])) {
+ $page->pushTpl('details.tabs.comment.tpl');
+ }
+
+ $page->pushTpl('details.tabs.related.tpl');
+
+ if ($user->perms('manage_project')) {
+ $page->pushTpl('details.tabs.notifs.tpl');
+ $page->pushTpl('details.tabs.remind.tpl');
+ }
+
+ if ($proj->prefs['use_effort_tracking']) {
+ $page->pushTpl('details.tabs.efforttracking.tpl');
+ }
+
+ $page->pushTpl('details.tabs.history.tpl');
+
+ } # endif can_edit_task
+
+} # endif can_view_task
+?>
diff --git a/scripts/editcomment.php b/scripts/editcomment.php
new file mode 100644
index 0000000..a74ee30
--- /dev/null
+++ b/scripts/editcomment.php
@@ -0,0 +1,28 @@
+<?php
+
+ /************************************\
+ | Edit comment |
+ | ~~~~~~~~~~~~ |
+ | This script allows users |
+ | to edit comments attached to tasks |
+ \************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$sql = $db->query("SELECT c.*, u.real_name
+ FROM {comments} c
+ INNER JOIN {users} u ON c.user_id = u.user_id
+ WHERE comment_id = ? AND task_id = ?",
+ array(Get::num('id', 0), Get::num('task_id', 0)));
+
+$page->assign('comment', $comment = $db->fetchRow($sql));
+
+if (!$user->can_edit_comment($comment)) {
+ Flyspray::show_error(11);
+}
+
+$page->pushTpl('editcomment.tpl');
+
+?>
diff --git a/scripts/index.php b/scripts/index.php
new file mode 100644
index 0000000..28e194e
--- /dev/null
+++ b/scripts/index.php
@@ -0,0 +1,534 @@
+<?php
+
+/*
+ This script sets up and shows the tasklist page.
+ It is for historical reason called index.php, because it was also the frontpage.
+ But now there can be a different pagetype set up as frontpage in Flyspray.
+*/
+
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+// Need to get function ConvertSeconds
+require_once(BASEDIR . '/includes/class.effort.php');
+
+if (!$user->can_select_project($proj->id)) {
+ $proj = new Project(0);
+}
+
+$perpage = '50';
+if (isset($user->infos['tasks_perpage']) && $user->infos['tasks_perpage'] > 0) {
+ $perpage = $user->infos['tasks_perpage'];
+}
+
+$pagenum = Get::num('pagenum', 1);
+if ($pagenum < 1) {
+ $pagenum = 1;
+}
+$offset = $perpage * ($pagenum - 1);
+
+// Get the visibility state of all columns
+$visible = explode(' ', trim($proj->id ? $proj->prefs['visible_columns'] : $fs->prefs['visible_columns']));
+if (!is_array($visible) || !count($visible) || !$visible[0]) {
+ $visible = array('id');
+}
+
+// Remove columns the user is not allowed to see
+if (in_array('estimated_effort', $visible) && !$user->perms('view_estimated_effort')) {
+ unset($visible[array_search('estimated_effort', $visible)]);
+}
+if (in_array('effort', $visible) && !$user->perms('view_current_effort_done')) {
+ unset($visible[array_search('effort', $visible)]);
+}
+
+# for csv export no paging limits
+if (Get::has('export_list')) {
+ $offset = -1;
+ $perpage = -1;
+}
+
+list($tasks, $id_list, $totalcount, $forbiddencount) = Backend::get_task_list($_GET, $visible, $offset, $perpage);
+
+if (Get::has('export_list')) {
+ export_task_list();
+}
+
+$page->uses('tasks', 'offset', 'perpage', 'pagenum', 'visible');
+
+// List of task IDs for next/previous links
+# Mmh the result is persistent in $_SESSION a bit for the length of each user session and can lead to a DOS quite fast on bigger installs?
+# Do we really need prev-next on task details view or can we find an alternative solution?
+# And using the $_SESSION for that is currently not working correct if someone uses 2 browser tabs for 2 different projects.
+$_SESSION['tasklist'] = $id_list;
+
+$page->assign('total', $totalcount);
+$page->assign('forbiddencount', $forbiddencount);
+
+// Send user variables to the template
+
+$result = $db->query('SELECT DISTINCT u.user_id, u.user_name, u.real_name, g.group_name, g.project_id
+ FROM {users} u
+ LEFT JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ LEFT JOIN {groups} g ON g.group_id = uig.group_id
+ WHERE (g.show_as_assignees = 1 OR g.is_admin = 1)
+ AND (g.project_id = 0 OR g.project_id = ?) AND u.account_enabled = 1
+ ORDER BY g.project_id ASC, g.group_name ASC, u.user_name ASC', ($proj->id || -1)); // FIXME: -1 is a hack. when $proj->id is 0 the query fails
+$userlist = array();
+while ($row = $db->fetchRow($result)) {
+ $userlist[$row['group_name']][] = array(0 => $row['user_id'],
+ 1 => sprintf('%s (%s)', $row['user_name'], $row['real_name']));
+}
+
+$page->assign('userlist', $userlist);
+
+/**
+ * tpl function that Displays a header cell for report list
+ */
+function tpl_list_heading($colname, $format = "<th%s>%s</th>")
+{
+ global $proj, $page;
+ $imgbase = '<img src="%s" alt="%s" />';
+ $class = $colname;
+ $html = eL($colname);
+/*
+ if ($colname == 'comments' || $colname == 'attachments') {
+ $html = sprintf($imgbase, $page->get_image(substr($colname, 0, -1)), $html);
+ }
+*/
+ if ($colname == 'attachments') {
+ $html='<i class="fa fa-paperclip fa-lg" title="'.$html.'"></i>';
+ }
+ if ($colname == 'comments') {
+ $html='<i class="fa fa-comments fa-lg" title="'.$html.'"></i>';
+ }
+ if ($colname == 'votes') {
+ $html='<i class="fa fa-star-o fa-lg" title="'.$html.'"></i>';
+ }
+
+ if (Get::val('order') == $colname) {
+ $class .= ' orderby';
+ $sort1 = Get::safe('sort', 'desc') == 'desc' ? 'asc' : 'desc';
+ $sort2 = Get::safe('sort2', 'desc');
+ $order2 = Get::safe('order2');
+ $html .= '&nbsp;&nbsp;'.sprintf($imgbase, $page->get_image(Get::val('sort')), Get::safe('sort'));
+ }
+ else {
+ $sort1 = 'desc';
+ if (in_array($colname,
+ array('project', 'tasktype', 'category', 'openedby', 'assignedto')))
+ {
+ $sort1 = 'asc';
+ }
+ $sort2 = Get::safe('sort', 'desc');
+ $order2 = Get::safe('order');
+ }
+
+
+ $new_order = array('order' => $colname, 'sort' => $sort1, 'order2' => $order2, 'sort2' => $sort2);
+ # unneeded params from $_GET for the sort links
+ $params=array_merge($_GET, $new_order);
+ unset($params['do']);
+ unset($params['project']);
+ unset($params['switch']);
+ $html = sprintf('<a title="%s" href="%s">%s</a>',
+ eL('sortthiscolumn'), Filters::noXSS(createURL('tasklist', $proj->id, null, $params )), $html);
+
+ return sprintf($format, ' class="'.$class.'"', $html);
+}
+
+
+/**
+ * tpl function that draws a cell
+ */
+function tpl_draw_cell($task, $colname, $format = "<td class='%s'>%s</td>") {
+ global $fs, $db, $proj, $page, $user;
+
+ $indexes = array (
+ 'id' => 'task_id',
+ 'project' => 'project_title',
+ 'tasktype' => 'task_type',
+ 'tasktypename'=> 'tasktype_name',
+ 'category' => 'category_name',
+ 'severity' => '',
+ 'priority' => '',
+ 'summary' => 'item_summary',
+ 'dateopened' => 'date_opened',
+ 'status' => 'status_name',
+ 'openedby' => 'opened_by',
+ 'openedbyname'=> 'opened_by_name',
+ 'assignedto' => 'assigned_to_name',
+ 'lastedit' => 'max_date',
+ 'editedby' => 'last_edited_by',
+ 'reportedin' => 'product_version_name',
+ 'dueversion' => 'closedby_version_name',
+ 'duedate' => 'due_date',
+ 'comments' => 'num_comments',
+ 'votes' => 'num_votes',
+ 'attachments'=> 'num_attachments',
+ 'dateclosed' => 'date_closed',
+ 'closedby' => 'closed_by',
+ 'commentedby'=> 'commented_by',
+ 'progress' => '',
+ 'os' => 'os_name',
+ 'private' => 'mark_private',
+ 'parent' => 'supertask_id',
+ 'estimatedeffort' => 'estimated_effort',
+ );
+
+ //must be an array , must contain elements and be alphanumeric (permitted "_")
+ if(!is_array($task) || empty($task) || preg_match('![^A-Za-z0-9_]!', $colname)) {
+ //run away..
+ return '';
+ }
+ $class= 'task_'.$colname;
+
+ switch ($colname) {
+ case 'id':
+ $value = tpl_tasklink($task, $task['task_id']);
+ break;
+ case 'summary':
+ $value = tpl_tasklink($task, utf8_substr($task['item_summary'], 0, 55));
+ if (utf8_strlen($task['item_summary']) > 55) {
+ $value .= '...';
+ }
+
+ if($task['tagids']!=''){
+ #$tags=explode(',', $task['tags']);
+ $tagids=explode(',', $task['tagids']);
+ #$tagclass=explode(',', $task['tagclass']);
+ $tgs='';
+ for($i=0;$i< count($tagids); $i++){
+ $tgs.=tpl_tag($tagids[$i]);
+ }
+ $value.=$tgs;
+ }
+ break;
+
+ case 'tasktype':
+ $value = htmlspecialchars($task['tasktype_name'], ENT_QUOTES, 'utf-8');
+ $class.=' typ'.$task['task_type'];
+ break;
+
+ case 'severity':
+ $value = $task['task_severity']==0 ? '' : $fs->severities[$task['task_severity']];
+ $class.=' sev'.$task['task_severity'];
+ break;
+
+ case 'priority':
+ $value = $task['task_priority']==0 ? '' : $fs->priorities[$task['task_priority']];
+ $class.=' pri'.$task['task_priority'];
+ break;
+
+ case 'attachments':
+ case 'comments':
+ case 'votes':
+ $value = $task[$indexes[$colname]]>0 ? $task[$indexes[$colname]]:'';
+ break;
+
+ case 'lastedit':
+ case 'duedate':
+ case 'dateopened':
+ case 'dateclosed':
+ $value = formatDate($task[$indexes[$colname]]);
+ break;
+
+ case 'status':
+ if ($task['is_closed']) {
+ $value = eL('closed');
+ } else {
+ $value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
+ }
+ $class.=' sta'.$task['item_status'];
+ break;
+
+ case 'progress':
+ $value = tpl_img($page->get_image('percent-' . $task['percent_complete'], false),
+ $task['percent_complete'] . '%');
+ break;
+
+ case 'assignedto':
+ # group_concat-ed for mysql/pgsql
+ #$value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
+ $value='';
+ $anames=explode(',',$task[$indexes[$colname]]);
+ $aids=explode(',',$task['assignedids']);
+ $aimages=explode(',',$task['assigned_image']);
+ for($a=0;$a < count($anames);$a++){
+ if($aids[$a]){
+ # deactivated: avatars looks too ugly in the tasklist, user's name needs to be visible on a first look here, without needed mouse hovering..
+ #if ($fs->prefs['enable_avatars']==1 && $aimages[$a]){
+ # $value.=tpl_userlinkavatar($aids[$a],30);
+ #} else{
+ $value.=tpl_userlink($aids[$a]);
+ #}
+ #$value.='<a href="'.$aids[$a].'">'.htmlspecialchars($anames[$a], ENT_QUOTES, 'utf-8').'</a>';
+ }
+ }
+
+ # fallback for DBs we haven't written sql string aggregation yet (currently with group_concat() mysql and array_agg() postgresql)
+ if( ('postgres' != $db->dblink->dataProvider) && ('mysql' != $db->dblink->dataProvider) && ($task['num_assigned'] > 1)) {
+ $value .= ', +' . ($task['num_assigned'] - 1);
+ }
+ break;
+
+ case 'private':
+ $value = $task[$indexes[$colname]] ? L('yes') : L('no');
+ break;
+
+ case 'commentedby':
+ case 'openedby':
+ case 'editedby':
+ case 'closedby':
+ $value = '';
+ # a bit expensive! tpl_userlinkavatar() an additional sql query for each new user in the output table
+ # at least tpl_userlink() uses a $cache array so query for repeated users
+ if ($task[$indexes[$colname]] > 0) {
+ # deactivated: avatars looks too ugly in the tasklist, user's name needs to be visible on a first look here, without needed mouse hovering..
+ #if ($fs->prefs['enable_avatars']==1){
+ # $value = tpl_userlinkavatar($task[$indexes[$colname]],30);
+ #} else{
+ $value = tpl_userlink($task[$indexes[$colname]]);
+ #}
+ }
+ break;
+
+ case 'parent':
+ $value = '';
+ if ($task['supertask_id'] > 0) {
+ $value = tpl_tasklink($task, $task['supertask_id']);
+ }
+ break;
+
+ case 'estimatedeffort':
+ $value = '';
+ if ($user->perms('view_estimated_effort')) {
+ if ($task['estimated_effort'] > 0){
+ $value = effort::secondsToString($task['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ }
+ }
+ break;
+
+ case 'effort':
+ $value = '';
+ if ($user->perms('view_current_effort_done')) {
+ if ($task['effort'] > 0){
+ $value = effort::secondsToString($task['effort'], $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
+ }
+ }
+ break;
+
+ default:
+ $value = '';
+ // $colname here is NOT column name in database but a name that can appear
+ // both in a projects visible fields and as a key in language translation
+ // file, which is also used to draw a localized heading. Column names in
+ // database customarily use _ t to separate words, translation file entries
+ // instead do not and can be also be quite different. If you do see an empty
+ // value when you expected something, check your usage, what visible fields
+ // in database actually constains, and maybe add a mapping from $colname to
+ // to the database column name to array $indexes at the beginning of this
+ // function. Note that inconsistencies between $colname, database column
+ // name, translation entry key and name in visible fields do occur sometimes
+ // during development phase.
+ if (array_key_exists($colname, $indexes)) {
+ $value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
+ }
+ break;
+ }
+ return sprintf($format, $class, $value);
+}
+
+$sort;
+$orderby;
+
+/**
+ *
+ * comparison function used by export_task_list
+ *
+ */
+function do_cmp($a, $b)
+{
+ global $sort,$orderby;
+
+ if ($a[ $orderby ] == $b[ $orderby ]) { return 0; }
+
+ if ($sort == 'asc')
+ return ($a[ $orderby ] < $b[ $orderby ]) ? -1 : 1;
+ else
+ return ($a[ $orderby ] > $b[ $orderby ]) ? -1 : 1;
+}
+
+/**
+* workaround fputcsv() bug https://bugs.php.net/bug.php?id=43225
+*/
+function my_fputcsv($handle, $fields)
+{
+ $out = array();
+
+ foreach ($fields as $field) {
+ if (empty($field)) {
+ $out[] = '';
+ }
+ elseif (preg_match('/^\d+(\.\d+)?$/', $field)) {
+ $out[] = $field;
+ }
+ else {
+ $out[] = '"' . preg_replace('/"/', '""', $field) . '"';
+ }
+ }
+
+ return fwrite($handle, implode(',', $out) . "\n");
+}
+
+
+/**
+ * Export the tasks as a .csv file
+ * Currently only a fixed list of task fields
+ */
+function export_task_list()
+{
+ global $tasks, $fs, $user, $sort, $orderby, $proj;
+
+ if (!is_array($tasks)){
+ return;
+ }
+
+ # TODO enforcing user permissions on allowed fields
+ # TODO Flyspray 1.1 or later: selected fields by user request, saved user settings, tasklist settings or project defined list which fields should appear in an export
+ # TODO Flyspray 1.1 or later: export in .ods open document spreadsheet, .xml ....
+ $indexes = array (
+ 'id' => 'task_id',
+ 'project' => 'project_title',
+ 'tasktype' => 'task_type',
+ 'category' => 'category_name',
+ 'severity' => 'task_severity',
+ 'priority' => 'task_priority',
+ 'summary' => 'item_summary',
+ 'dateopened' => 'date_opened',
+ 'status' => 'status_name',
+ 'openedby' => 'opened_by_name',
+ 'assignedto' => 'assigned_to_name',
+ 'lastedit' => 'max_date',
+ 'reportedin' => 'product_version',
+ 'dueversion' => 'closedby_version',
+ 'duedate' => 'due_date',
+ 'comments' => 'num_comments',
+ 'votes' => 'num_votes',
+ 'attachments'=> 'num_attachments',
+ 'dateclosed' => 'date_closed',
+ 'progress' => 'percent_complete',
+ 'os' => 'os_name',
+ 'private' => 'mark_private',
+ 'supertask' => 'supertask_id',
+ 'detailed_desc'=>'detailed_desc',
+ );
+
+
+ # we can put this info also in the filename ...
+ #$projectinfo = array('Project ', $tasks[0]['project_title'], date("H:i:s d-m-Y") );
+
+ // sort the tasks into the order selected by the user. Set
+ // global vars for use by sort comparison function
+
+ $sort = Get::safe('sort','desc') == 'desc' ? 'desc' : 'asc';
+ $field = Get::safe('order', 'id');
+
+ if ($field == '') $field = 'id';
+ $orderby = $indexes[ $field ];
+
+ usort($tasks, "do_cmp");
+
+ $outfile = str_replace(' ', '_', $proj->prefs['project_title']).'_'.date("Y-m-d").'.csv';
+
+ #header('Content-Type: application/csv');
+ header('Content-Type: text/csv');
+ header('Content-Disposition: attachment; filename='.$outfile);
+ header('Content-Transfer-Encoding: text');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ #header('Pragma: public');
+ #header('Content-Length: '.strlen($result)); # unknown at this time..
+ ob_clean();
+ flush();
+
+ $output = fopen('php://output', 'w');
+ $headings= array(
+ 'ID',
+ 'Category',
+ 'Task Type',
+ 'Severity',
+ 'Summary',
+ 'Status',
+ 'Progress',
+ 'date_opened',
+ 'date_closed',
+ 'due_date',
+ 'supertask_id'
+ );
+ if($user->perms('view_estimated_effort') && $proj->id>0 && $proj->prefs['use_effort_tracking']){
+ $headings[]='Estimated Effort';
+ }
+ $headings[]='Description';
+ //if($user->perms('view_current_effort_done') && $proj->id>0 && $proj->prefs['use_effort_tracking']){ $headings[]='Done Effort'; }
+
+ #fputcsv($output, $headings);
+ my_fputcsv($output, $headings); # fixes 'SYLK' FS#2123 Excel problem
+ foreach ($tasks as $task) {
+ $row = array(
+ $task['task_id'],
+ $task['category_name'],
+ $task['task_type'],
+ $fs->severities[ $task['task_severity'] ],
+ $task['item_summary'],
+ $task['status_name'],
+ $task['percent_complete'],
+ $task['date_opened'],
+ $task['date_closed'],
+ $task['due_date'],
+ $task['supertask_id']
+ );
+ if( $user->perms('view_estimated_effort') && $proj->id>0 && $proj->prefs['use_effort_tracking']){
+ $row[]=$task['estimated_effort'];
+ }
+ $row[]=$task['detailed_desc'];
+ //if( $user->perms('view_current_effort_done') && $proj->id>0 && $proj->prefs['use_effort_tracking']){ $row=$task['effort']; }
+
+ my_fputcsv($output, $row); # fputcsv() is buggy
+ }
+ fclose($output);
+ exit();
+}
+
+// Javascript replacement
+if (Get::val('toggleadvanced')) {
+ $advanced_search = intval(!Req::val('advancedsearch'));
+ Flyspray::setCookie('advancedsearch', $advanced_search, time()+60*60*24*30);
+ $_COOKIE['advancedsearch'] = $advanced_search;
+}
+
+/**
+ * Update check
+ */
+if(Get::has('hideupdatemsg')) {
+ unset($_SESSION['latest_version']);
+} else if ($conf['general']['update_check']
+ && $user->perms('is_admin')
+ && $fs->prefs['last_update_check'] < time()-60*60*24*3) {
+ if (!isset($_SESSION['latest_version'])) {
+ $latest = Flyspray::remote_request('http://www.flyspray.org/version.txt', GET_CONTENTS);
+ # if for some silly reason we get an empty response, we use the actual version
+ $_SESSION['latest_version'] = empty($latest) ? $fs->version : $latest ;
+ $db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?', array(time(), 'last_update_check'));
+ }
+}
+if (isset($_SESSION['latest_version']) && version_compare($fs->version, $_SESSION['latest_version'] , '<') ) {
+ $page->assign('updatemsg', true);
+}
+
+
+$page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title'] . ': ' . L('tasklist'));
+$page->pushTpl('index.tpl');
+
+?>
diff --git a/scripts/langdiff.php b/scripts/langdiff.php
new file mode 100644
index 0000000..f004b54
--- /dev/null
+++ b/scripts/langdiff.php
@@ -0,0 +1,192 @@
+<?php
+
+if(!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+# let also project managers allow translation of flyspray
+if(!$user->perms('manage_project')) {
+ Flyspray::show_error(28);
+}
+
+ob_start();
+
+?>
+<style type="text/css">
+pre { margin : 0; }
+table{border-collapse:collapse;}
+.progress_bar_container{height:20px;}
+.progress_bar_container span:first-child{display:inline-block;margin-top:2px;z-index:101;color:#000;}
+.overview{margin-left:auto;margin-right:auto;}
+.overview td, .overview th{border:none;padding:0;}
+a.button{padding:2px 10px 2px 10px;margin:2px;}
+table th{text-align:center;}
+table th, table td {
+ vertical-align:middle;
+ border: 1px solid #ccc;
+ padding: 2px;
+}
+tr:hover td, tr:hover th { background : #e0e0e0; }
+</style>
+<?php
+require_once dirname(dirname(__FILE__)) . '/includes/fix.inc.php';
+/*
+* Usage: Open this file like ?do=langdiff?lang=de in your browser.
+* "de" represents your language code.
+*/
+$lang = isset($_GET['lang']) ? $_GET['lang'] : 'en';
+if( preg_match('/[^a-zA-Z_]/', $lang)) {
+ die('Invalid language name.');
+}
+
+# reload en.php if flyspray did it before!
+require('lang/en.php');
+// while the en.php and $lang.php both defines $language, the english one should be keept
+$orig_language = $language;
+
+$translationfile = 'lang/'."$lang.php";
+if ($lang != 'en' && file_exists($translationfile)) {
+ # reload that file if flyspray did it before!
+ include($translationfile);
+ if( isset($_GET['sort']) && $_GET['sort']=='key'){
+ ksort($orig_language);
+ }elseif( isset($_GET['sort']) && $_GET['sort']=='en'){
+ asort($orig_language);
+ }elseif( isset($_GET['sort']) && $_GET['sort']==$_GET['lang']){
+ # todo
+ }else{
+ # show as it is in file en.php
+ }
+
+ echo '<h1>Diff report for language ',$lang,'</h1>',"\n";
+ echo '<h2>The following translation keys are missing in the translation:</h2>';
+ echo '<table>';
+ $i = 0;
+ foreach ($orig_language as $key => $val) {
+ if (!isset($translation[$key])) {
+ echo '<tr><th>',$key,'</th><td>'.htmlspecialchars($val).'</td></tr>',"\n";
+ $i++;
+ }
+
+ }
+ echo '</table>';
+ if ( $i > 0 ){
+ echo '<p>',$i,' out of ',sizeof($language),' keys to translate.</p>';
+ }
+ echo '<h2>The following translation keys should be deleted from the translation:</h2>';
+ echo '<table cellspacing="0">';
+ $i = 0;
+ foreach ($translation as $key => $val) {
+ if ( !isset($orig_language[$key])) {
+ echo '<tr class="line',($i%2),'"><th>',$key,'</th><td><pre>\'',$val,'\'</pre></td></tr>',"\n";
+ $i++;
+ }
+ }
+ echo '</table>';
+ if ( $i > 0 ){
+ echo '<p>'.$i.' entries can be removed from this translation.</p>';
+ } else{
+ echo '<p><i class="fa fa-check fa-2x"></i> None</p>';
+ }
+ echo '<h2><a name="compare"></a>Direct comparision between english and '.htmlspecialchars($lang).'</h2>';
+ echo '<table>
+ <colgroup></colgroup>
+ <thead><tr>
+ <th><a href="?do=langdiff&lang='.htmlspecialchars($lang).'&amp;sort=key#compare" title="sort by translation key">translation key</th>
+ <th><a href="?do=langdiff&lang='.htmlspecialchars($lang).'&amp;sort=en#compare" title="sort by english">en</a></th>
+ <th>'.htmlspecialchars($lang).'</th>
+ </tr>
+ </thead>
+ <tbody>';
+ $i = 0;
+ foreach ($orig_language as $key => $val) {
+ if (!isset($translation[$key])) {
+ echo '<tr><th>',$key,'</th><td>'.htmlspecialchars($val).'</td><td></td></tr>'."\n";
+ }else{
+ echo '
+ <tr>
+ <th>',$key,'</th><td>'.htmlspecialchars($val).'</td>
+ <td>'.htmlspecialchars($translation[$key]).'</td>
+ </tr>'."\n";
+ }
+ $i++;
+ }
+ echo '</tbody></table>';
+} else {
+ # TODO show all existing translations overview and selection
+ # readdir
+ $english=$language;
+ $max=count($english);
+ $langfiles=array();
+ $workfiles=array();
+ if ($handle = opendir('lang')) {
+ $languages=array();
+ while (false !== ($file = readdir($handle))) {
+ if ($file != "."
+ && $file != ".."
+ && $file!='.langdiff.php'
+ && $file!='.langedit.php'
+ && !(substr($file,-4)=='.bak')
+ && !(substr($file,-5)=='.safe') ) {
+ # if a .$lang.php.work file but no $lang.php exists yet
+ if( substr($file,-5)=='.work'){
+ if(!is_file('lang/'.substr($file,1,-5)) ){
+ $workfiles[]=$file;
+ }
+ } else{
+ $langfiles[]=$file;
+ }
+ }
+ }
+ asort($langfiles);
+ asort($workfiles);
+ echo '<table class="overview">
+ <thead><tr><th>'.L('file').'</th><th>'.L('progress').'</th><th> </th></tr></thead>
+ <tbody>';
+ foreach($langfiles as $lang){
+ unset($translation);
+ require('lang/'.$lang); # file $language variable
+ $i=0; $empty=0;
+ foreach ($orig_language as $key => $val) {
+ if (!isset($translation[$key])) {
+ $i++;
+ }else{
+ if($val==''){
+ $empty++;
+ }
+ }
+ }
+ $progress=floor(($max-$i)*100/$max*10)/10;
+ if($lang!='en.php'){
+ echo '
+<tr>
+<td><a href="?do=langdiff&lang='.substr($lang,0,-4).'">'.$lang.'</a></td>
+<td><a href="?do=langdiff&lang='.substr($lang,0,-4).'" class="progress_bar_container">
+<span class="progress">'.$progress.' %</span>
+<span style="width:'.$progress.'%" class="progress_bar"></span></a>
+</td>
+<td><a class="button" href="?do=langedit&lang='.substr($lang,0,-4).'">'.L('translate').' '.substr($lang,0,-4).'</a></td>
+</tr>';
+ }else{
+ echo '<tr><td>en.php</td><td>is reference and fallback</td><td><a class="button" href="?do=langedit&lang='.substr($lang,0,-4).'">Translate '.substr($lang,0,-4).'</a></td></tr>';
+ }
+ }
+ foreach($workfiles as $workfile){
+ echo '<tr>
+ <td><a href="?do=langdiff&lang='.substr($workfile,1,-9).'">'.$workfile.'</a></td>
+ <td></td>
+ <td><a class="button" href="?do=langedit&lang='.substr($workfile,1,-9).'">'.L('translate').' '.substr($workfile,1,-9).'</a></td>
+ </tr>';
+ }
+ closedir($handle);
+ echo '</tbody></table>';
+ }
+}
+
+$content = ob_get_contents();
+ob_end_clean();
+
+$page->uses('content');
+$page->pushTpl('admin.translation.tpl');
+
+?>
diff --git a/scripts/langedit.php b/scripts/langedit.php
new file mode 100644
index 0000000..20ba4a8
--- /dev/null
+++ b/scripts/langedit.php
@@ -0,0 +1,329 @@
+
+<?php
+/* langedit.php
+ *
+ * Translation tool for the Flyspray Bug Tracker System
+ * http://flyspray.org
+ *
+ * Author
+ * Lars-Erik Hoffsten
+ * larserik@softpoint.nu
+ *
+ * 2006-06-05 Version 1.0
+ * Initial version
+ * 2006-06-05 Version 1.1
+ * Using UTF-8 character encoding
+ * Handles all kinds of characters like line feed etc that need special escaping
+ * Hides backup files with leading '.' in filename
+ * Creates a work file for better safety
+ * New languages are easily created just by typing the new language code on the URL
+ * 2006-06-07 Version 1.2
+ * Moved to the setup directory so that it wouldn't be left behind in the
+ * installation to be used by some one unauthorized
+ * mb_strlen() replaced by strlen(utf_decode()) because mb_* functions are not standard
+ * 2006-06-12 Version 1.3
+ * Writes correct array name for english
+ *
+ * 2015-02-09
+ * use flyspray theme, add button targeting translation overview for better workflow
+ * 2015-03-02
+ * integration into flyspray user interface
+ *
+ * Usage: http://.../flyspray/?do=langedit&lang=sv
+ * "sv" represents your language code.
+ *
+ * !!!
+ * Note that this script rewrites the language file completely when saving.
+ * Anything else than the $translation array will be lost including any file comments.
+ * !!!
+ */
+
+if(!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+# let also project managers allow translation of flyspray
+if(!$user->perms('manage_project')) {
+ Flyspray::show_error(28);
+}
+
+// Make it possible to reload page after updating language
+// Don't want to send form data again if user reloads the page
+ob_start();
+?>
+<script language="javascript">
+// Indicate which texts are changed, called from input and textarea onchange
+function set(id){
+ var checkbox = document.getElementById('id_checkbox_' + id);
+ if(checkbox)
+ checkbox.checked = true;
+ var hidden = document.getElementById('id_hidden_' + id);
+ if(hidden)
+ hidden.disabled = false;
+ var conf = document.getElementById('id_confirm');
+ if(conf)
+ conf.disabled = true;
+}
+</script>
+<?php
+
+// Set current directory to where the language files are
+chdir('lang');
+
+$lang = isset($_GET['lang']) ? $_GET['lang']:false;
+$fail = '';
+if(!$lang || !preg_match('/^[a-zA-Z0-9_]+$/', $lang)){
+ $fail .= "Language code not supplied correctly<br>\n";
+}
+if(!file_exists('en.php')) {
+ $fail .= "The english language file <code>en.php</code> is missing. Make sure this script is run from the same directory as the language files <code>.../flyspray/lang/</code><br>\n";
+}
+if($fail) {
+ die($fail."<b>Usage:</b> <a href='?do=langedit&lang='>&lt;lang code&gt;</a> where &lt;lang code&gt; should be replaced by your language, e.g. <b>de</b> for German.");
+}
+// Read english language file in array $language (assumed to be UTF-8 encoded)
+require('en.php');
+if(!is_array(@$language)){
+ die("Invalid language file for english");
+}
+$count = count($language);
+
+// Read the translation file in array $translation (assumed to be UTF-8 encoded)
+$working_copy = false;
+if(!file_exists($lang.'.php') && !file_exists('.'.$lang.'.php.work')) {
+ echo '<h3>A new language file will be created: <code>'.$lang.'.php</code></h2>';
+} else {
+ if($lang != 'en') {
+ if(file_exists('.'.$lang.'.php.work')) {
+ $working_copy = true;
+ include_once('.'.$lang.'.php.work'); // Read the translation array (work in progress)
+ } else{
+ include($lang.'.php'); // Read the original translation array - maybe again, no _once here!
+ }
+ } else if(file_exists('.en.php.work')){
+ $working_copy = true;
+ $tmp = $language;
+ include_once('.en.php.work'); // Read the language array (work in progress)
+ $translation = $language; // Edit the english language file
+ $language = $tmp;
+ } else{
+ $translation = $language; // Edit the english language file
+ }
+
+ if(!is_array(@$translation)){
+ echo "<b>Warning: </b>the translation file does not contain the \$translation array, a new file will be created: <code>$lang.php</code>\n";
+ }
+}
+
+$limit = 30;
+$begin = isset($_GET['begin']) ? (int)($_GET['begin'] / $limit) * $limit : 0;
+
+// Was show missing pressed?
+$show_empty = (!isset($_POST['search']) && isset($_REQUEST['empty'])); // Either POST or URL
+// Any text in search box?
+if(!$show_empty && isset($_POST['search_for'])) {
+ $search = trim($_POST['search_for']);
+} else if(!$show_empty && isset($_GET['search_for'])) {
+ $search = trim(urldecode($_GET['search_for']));
+} else {
+ $search = "";
+}
+// Path to this file
+$self = "{$_SERVER['SCRIPT_NAME']}?do=langedit&lang=$lang";
+
+if(isset($_POST['confirm'])) {
+ // Make a backup
+ unlink(".$lang.php.bak");
+ rename("$lang.php", ".$lang.php.bak");
+ rename(".$lang.php.work", "$lang.php");
+ // Reload page, so that form data won't get posted again on refresh
+ header("location: $self&begin=$begin" . ($search? "&search_for=".urlencode($search): "") . ($show_empty? "&empty=": ""));
+ exit;
+} else if(isset($_POST['submit']) && isset($_POST['L'])) {
+ // Save button was pressed
+ update_language($lang, $_POST['L'], @$_POST['E']);
+ // Reload page, so that form data won't get posted again on refresh
+ header("location: $self&begin=$begin" . ($search? "&search_for=".urlencode($search): "") . ($show_empty? "&empty=": ""));
+ exit;
+}
+
+// One form for all buttons and inputs
+echo '<a class="button" href="?do=langdiff">Overview</a>';
+echo "<form action=\"$self&do=langedit&begin=$begin". ($show_empty? "&empty=": "") . "\" method=\"post\">\n";
+echo "<table cellspacing=0 cellpadding=1>\n<tr><td colspan=3>";
+// Make page links
+for($p = 0; $p < $count; $p += $limit){
+ if($p){
+ echo " | ";
+ }
+ $bgn = $p+1;
+ $end = min($p+$limit, $count);
+ if($p != $begin || $search || $show_empty) {
+ echo "<a href=\"$self&begin=$bgn\">$bgn&hellip;$end</a>\n"; // Show all links when searching or display all missing strings
+ } else {
+ echo "<b>$bgn&hellip;$end</b>\n";
+ }
+}
+?>
+</td><td>
+<input type="submit" name="submit" value="Save changes" title="Saves changes to a work file">
+<input type="submit" name="confirm" id="id_confirm" value="Confirm all changes"<?php echo !$working_copy ? ' disabled="disabled"': ''; ?> title="Confirm all changes and replace the original language file">
+<br>
+<?php
+if($working_copy) {
+ echo "Your changes are stored in <code>.$lang.php.work</code> until you press 'Confirm all changes'<br>";
+}
+// Search
+echo '<input type="text" name="search_for" value="'.Filters::noXSS($search).'"><input type="submit" name="search" value="Search">';
+// List empty
+if($lang != 'en') {
+ echo '<input type="submit" name="empty" value="Show missing" title="Show all texts that have no translation">';
+}
+?>
+</td></tr>
+<tr><th colspan=2>Key</th><th>English</th><th>Translation:<?php echo $lang; ?></th></tr>
+<?php
+$i = 0; // Counter to find offset
+$j = 0; // Counter for background shading
+foreach ($language as $key => $val){
+ $trans = @$translation[$key];
+ if((!$search && !$show_empty && $i >= $begin) ||
+ ($search && (stristr($key, $search) || stristr($val, $search) || stristr($trans, $search))) ||
+ ($show_empty && !$trans)){
+ $bg = ($j++ & 1)? '#fff': '#eed';
+ // Key
+ echo '<tr style="background-color:'.$bg.'" valign="top"><td align="right">'.($i+1).'</td><td><b>'.$key.'</b></td>';
+ // English (underline leading and trailing spaces)
+ $space = '<b style="color:#c00;" title="Remember to include a space in the translation!">_</b>';
+ echo '<td>'. (preg_match("/^[ \t]/",$val)? $space: "") . nl2br(htmlentities($val)). (preg_match("/[ \t]$/",$val)? $space: "") ."</td>\n";
+ echo '<td align="right"><nobr>';
+ echo '<input type="checkbox" disabled="disabled" id="id_checkbox_'.$key.'">';
+ echo '<input type="hidden" disabled="disabled" id="id_hidden_'.$key.'" name="E['.$key.']">';
+ // Count lines in both english and translation
+ $lines = 1 + max(preg_match_all("/\n/", $val, $matches), preg_match_all("/\n/", $trans, $matches));
+ // Javascript call on some input events
+ $onchange = 'onchange="set(\''.$key.'\');" onkeypress="set(\''.$key.'\');"';
+ // \ is displayed as \\ in edit fields to allow \n as line feed
+ $trans = str_replace("\\", "\\\\", $trans);
+ if($lines > 1 || strlen(utf8_decode($val)) > 60 || strlen(utf8_decode($trans)) > 60){
+ // Format long texts for <textarea>, remove spaces after each new line
+ $trans = preg_replace("/\n[ \t]+|\\n/", "\n", htmlentities($trans, ENT_NOQUOTES, "UTF-8"));
+ echo '<textarea cols=79 rows='.max(4,$lines).' name="L['.$key.']" '.$onchange.'>'.$trans.'</textarea>';
+ } else{
+ // Format short texts for <input type=text>
+ $trans = str_replace(array("\n", "\""), array("\\n", "&quot;"), $trans);
+ echo '<input class="edit" type="text" name="L['.$key.']" value="'.$trans.'" size=80 '.$onchange.'>';
+ }
+ echo "</nobr></td></tr>\n";
+
+ if(--$limit == 0 && !$search && !$show_empty){
+ break;
+ }
+ }
+ $i++;
+}
+?>
+</table>
+<hr>
+<table width="100%">
+<tr>
+<td>The language files are UTF-8 encoded, avoid manual editing if You are not sure that your editor supports UTF-8<br>
+Syntax for <b>\</b> is <b>\\</b> and for line feed type <b>\\n</b> in single line edit fields</td>
+<td style="text-align: right;"><i>langedit by <a href="mailto:larserik@softpoint.nu">larserik@softpoint.nu</a></i></td>
+</tr>
+</table>
+<?php
+
+$content = ob_get_contents();
+ob_end_clean();
+
+$page->uses('content');
+$page->pushTpl('admin.translation.tpl');
+
+// Parse string for \n and \\ to be replaced by <lf> and \
+function parseNL($str) {
+ $pos = 0;
+ while(($pos = strpos($str, "\\", $pos)) !== false){
+ switch(substr($str, $pos, 2)){
+ case "\\n":
+ $str = substr_replace($str, "\n", $pos, 2);
+ break;
+ case "\\\\":
+ $str = substr_replace($str, "\\", $pos, 2);
+ break;
+ }
+ $pos++;
+ }
+ return $str;
+}
+
+function update_language($lang, &$strings, $edit) {
+ global $language, $translation;
+
+ if(!is_array($edit)) {
+ return;
+ }
+ // Form data contains UTF-8 encoded text
+ foreach($edit as $key => $dummy){
+ if(@$strings[$key]) {
+ $translation[$key] = parseNL($strings[$key]);
+ } else {
+ unset($translation[$key]);
+ }
+ }
+ // Make a backup just in case!
+ if(!file_exists(".$lang.php.safe")){
+ // Make one safe backup that will NOT be changed by this script
+ copy("$lang.php", ".$lang.php.safe");
+ }
+ if(file_exists(".$lang.php.work")){
+ // Then make ordinary backups
+ copy(".$lang.php.work", ".$lang.php.bak");
+ }
+ // Write the translation array to file with UNIX style line endings
+ $file = fopen(".$lang.php.work", "w");
+ // Write the UTF-8 BOM, Byte Order Marker
+ //fprintf($file, chr(0xef).chr(0xbb).chr(0xbf));
+ // Header
+ fprintf($file, "<?php\n//\n"
+ ."// This file is auto generated with langedit.php\n"
+ ."// Characters are UTF-8 encoded\n"
+ ."// \n"
+ ."// Be careful when editing this file manually, some text editors\n"
+ ."// may convert text to UCS-2 or similar (16-bit) which is NOT\n"
+ ."// readable by the PHP parser\n"
+ ."// \n"
+ ."// Furthermore, nothing else than the language array is saved\n"
+ ."// when using the langedit.php editor!\n//\n");
+ if($lang == 'en') {
+ fprintf($file, "\$language = array(\n");
+ } else {
+ fprintf($file, "\$translation = array(\n");
+ }
+
+ // The following characters will be escaped in multiline strings
+ // in the following order:
+ // \ => \\
+ // " => \"
+ // $ => \$
+ // <lf> => \n
+ // <cr> are removed if any
+ $pattern = array("\\", "\"", "\$", "\n", "\r");
+ $replace = array("\\\\", "\\\"", "\\\$", "\\n", "");
+ // Write the array to the file, ordered as the english language file
+ foreach($language as $key => $val){
+ $trans = @$translation[$key];
+ if(!$trans) {
+ continue;
+ }
+ if(strstr($trans, "\n")) { // Use double quotes for multiline
+ fprintf($file, "%-26s=> \"%s\",\n", "'$key'", str_replace($pattern, $replace, $trans));
+ } else { // Use single quotes for single lines, only \ and ' needs escaping
+ fprintf($file, "%-26s=> '%s',\n", "'$key'", str_replace(array("\\","'"), array("\\\\", "\\'"), $trans));
+ }
+ }
+ fprintf($file, ");\n\n?".">\n"); // PHP end tag currupts some syntax color coders
+ fclose($file);
+}
+
+?>
diff --git a/scripts/lostpw.php b/scripts/lostpw.php
new file mode 100644
index 0000000..ce2a78d
--- /dev/null
+++ b/scripts/lostpw.php
@@ -0,0 +1,32 @@
+<?php
+
+ /*********************************************************\
+ | Deal with lost passwords |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~ |
+ \*********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$page->setTitle($fs->prefs['page_title'] . L('lostpw'));
+
+if (!Req::has('magic_url') && $user->isAnon()) {
+ // Step One: user requests magic url
+ $page->pushTpl('lostpw.step1.tpl');
+}
+elseif (Req::has('magic_url') && $user->isAnon()) {
+ # Step Two: user enters new password
+ # First as link from email (GET), form could be repeated as POST
+ # when user misrepeats the new password. so GET and POST possible here!
+ $check_magic = $db->query('SELECT * FROM {users} WHERE magic_url = ?',
+ array(Req::val('magic_url')));
+
+ if (!$db->countRows($check_magic)) {
+ Flyspray::show_error(12);
+ }
+ $page->pushTpl('lostpw.step2.tpl');
+} else {
+ Flyspray::redirect($baseurl);
+}
+?>
diff --git a/scripts/myprofile.php b/scripts/myprofile.php
new file mode 100644
index 0000000..bd9bd22
--- /dev/null
+++ b/scripts/myprofile.php
@@ -0,0 +1,41 @@
+<?php
+
+ /*********************************************************\
+ | User Profile Edition |
+ | ~~~~~~~~~~~~~~~~~~~~ |
+ \*********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if ($user->isAnon()) {
+ Flyspray::show_error(13);
+}
+
+# maybe add some checks for output if a task or project or user changed permissions
+# for example the user is moved from developer to basic
+# or a task is changed to private modus
+# or a task is closed now
+# maybe add 'AND t.is_closed<>1' if we want only show votes of active tasks, that are taken for the votes limit.
+# How can a user unvote such now unvisible tasks to get back under his voting limit for the project?
+$votes=$db->query('
+ SELECT v.*, t.project_id, t.item_summary, t.task_type, t.is_closed, p.project_title
+ FROM {votes} v
+ JOIN {tasks} t ON t.task_id=v.task_id
+ LEFT JOIN {projects} p ON p.project_id=t.project_id
+ WHERE user_id = ?
+ ORDER BY t.project_id, t.task_id',
+ $user->id
+);
+$votes=$db->fetchAllArray($votes);
+
+$page->assign('votes', $votes);
+$page->assign('groups', Flyspray::listGroups());
+$page->assign('project_groups', Flyspray::listGroups($proj->id));
+$page->assign('theuser', $user);
+
+$page->setTitle($fs->prefs['page_title'] . L('editmydetails'));
+$page->pushTpl('myprofile.tpl');
+
+?>
diff --git a/scripts/newmultitasks.php b/scripts/newmultitasks.php
new file mode 100644
index 0000000..741ea81
--- /dev/null
+++ b/scripts/newmultitasks.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+* Multiple Tasks Creation
+*/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$user->can_open_task($proj) && !$user->perms('add_multiple_tasks')) {
+ Flyspray::show_error(15);
+}
+
+$page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title'] . ': ' . L('newtask'));
+
+$page->assign('old_assigned', '');
+$page->pushTpl('newmultitasks.tpl');
+
+?> \ No newline at end of file
diff --git a/scripts/newtask.php b/scripts/newtask.php
new file mode 100644
index 0000000..8cd3dc2
--- /dev/null
+++ b/scripts/newtask.php
@@ -0,0 +1,53 @@
+<?php
+
+ /********************************************************\
+ | Task Creation |
+ | ~~~~~~~~~~~~~ |
+ \********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$user->can_open_task($proj)) {
+ Flyspray::show_error(15);
+}
+
+$page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title'] . ': ' . L('newtask'));
+
+$result = $db->query('
+ SELECT u.user_id, u.user_name, u.real_name, g.group_id, g.group_name, g.project_id
+ FROM {users} u
+ JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ JOIN {groups} g ON g.group_id = uig.group_id
+ WHERE (g.show_as_assignees = 1 OR g.is_admin = 1)
+ AND (g.project_id = 0 OR g.project_id = ?) AND u.account_enabled = 1
+ ORDER BY g.project_id ASC, g.group_name ASC, u.user_name ASC', $proj->id);
+
+$userlist = array();
+$userids=array();
+while ($row = $db->fetchRow($result)) {
+ if (!in_array($row['user_id'], $userids)){
+ $userlist[$row['group_id']][] = array(
+ 0 => $row['user_id'],
+ 1 => sprintf('%s (%s)', $row['user_name'], $row['real_name']),
+ 2 => $row['project_id'],
+ 3 => $row['group_name']
+ );
+ $userids[]=$row['user_id'];
+ } else{
+ # user is probably in a global group with assignee permission listed, so no need to show second time in a project group.
+ }
+}
+
+$assignees = array();
+if (is_array(Post::val('rassigned_to'))) {
+ $assignees = Post::val('rassigned_to');
+}
+
+$page->assign('assignees', $assignees);
+$page->assign('userlist', $userlist);
+$page->assign('old_assigned', '');
+$page->pushTpl('newtask.tpl');
+
+?>
diff --git a/scripts/oauth.php b/scripts/oauth.php
new file mode 100644
index 0000000..48dface
--- /dev/null
+++ b/scripts/oauth.php
@@ -0,0 +1,201 @@
+<?php
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$providers = array(
+ 'github' => function() use ($conf) {
+ if (empty($conf['oauth']['github_secret']) ||
+ empty($conf['oauth']['github_id']) ||
+ empty($conf['oauth']['github_redirect'])) {
+
+ throw new Exception('Config error make sure the github_* variables are set.');
+ }
+ return new GithubProvider(array(
+ 'clientId' => $conf['oauth']['github_id'],
+ 'clientSecret' => $conf['oauth']['github_secret'],
+ 'redirectUri' => $conf['oauth']['github_redirect'],
+ 'scopes' => array('user:email')
+ ));
+ },
+ 'google' => function() use ($conf) {
+ if (empty($conf['oauth']['google_secret']) ||
+ empty($conf['oauth']['google_id']) ||
+ empty($conf['oauth']['google_redirect'])) {
+
+ throw new Exception('Config error make sure the google_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Google(array(
+ 'clientId' => $conf['oauth']['google_id'],
+ 'clientSecret' => $conf['oauth']['google_secret'],
+ 'redirectUri' => $conf['oauth']['google_redirect'],
+ 'scopes' => array('email', 'profile')
+ ));
+ },
+ 'facebook' => function() use ($conf) {
+ if (empty($conf['oauth']['facebook_secret']) ||
+ empty($conf['oauth']['facebook_id']) ||
+ empty($conf['oauth']['facebook_redirect'])) {
+
+ throw new Exception('Config error make sure the facebook_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Facebook(array(
+ 'clientId' => $conf['oauth']['facebook_id'],
+ 'clientSecret' => $conf['oauth']['facebook_secret'],
+ 'redirectUri' => $conf['oauth']['facebook_redirect'],
+ ));
+ },
+ 'microsoft' => function() use ($conf) {
+ if (empty($conf['oauth']['microsoft_secret']) ||
+ empty($conf['oauth']['microsoft_id']) ||
+ empty($conf['oauth']['microsoft_redirect'])) {
+
+ throw new Exception('Config error make sure the microsoft_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Microsoft(array(
+ 'clientId' => $conf['oauth']['microsoft_id'],
+ 'clientSecret' => $conf['oauth']['microsoft_secret'],
+ 'redirectUri' => $conf['oauth']['microsoft_redirect'],
+ ));
+ },
+ 'instagram' => function() use ($conf) {
+ if (empty($conf['oauth']['instagram_secret']) ||
+ empty($conf['oauth']['instagram_id']) ||
+ empty($conf['oauth']['instagram_redirect'])) {
+
+ throw new Exception('Config error make sure the instagram_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Instagram(array(
+ 'clientId' => $conf['oauth']['instagram_id'],
+ 'clientSecret' => $conf['oauth']['instagram_secret'],
+ 'redirectUri' => $conf['oauth']['instagram_redirect'],
+ ));
+ },
+ 'eventbrite' => function() use ($conf) {
+ if (empty($conf['oauth']['eventbrite_secret']) ||
+ empty($conf['oauth']['eventbrite_id']) ||
+ empty($conf['oauth']['eventbrite_redirect'])) {
+
+ throw new Exception('Config error make sure the eventbrite_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Eventbrite(array(
+ 'clientId' => $conf['oauth']['eventbrite_id'],
+ 'clientSecret' => $conf['oauth']['eventbrite_secret'],
+ 'redirectUri' => $conf['oauth']['eventbrite_redirect'],
+ ));
+ },
+ 'linkedin' => function() use ($conf) {
+ if (empty($conf['oauth']['linkedin_secret']) ||
+ empty($conf['oauth']['linkedin_id']) ||
+ empty($conf['oauth']['linkedin_redirect'])) {
+
+ throw new Exception('Config error make sure the linkedin_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\LinkedIn(array(
+ 'clientId' => $conf['oauth']['linkedin_id'],
+ 'clientSecret' => $conf['oauth']['linkedin_secret'],
+ 'redirectUri' => $conf['oauth']['linkedin_redirect'],
+ ));
+ },
+ 'vkontakte' => function() use ($conf) {
+ if (empty($conf['oauth']['vkontakte_secret']) ||
+ empty($conf['oauth']['vkontakte_id']) ||
+ empty($conf['oauth']['vkontakte_redirect'])) {
+
+ throw new Exception('Config error make sure the vkontakte_* variables are set.');
+ }
+ return new League\OAuth2\Client\Provider\Vkontakte(array(
+ 'clientId' => $conf['oauth']['vkontakte_id'],
+ 'clientSecret' => $conf['oauth']['vkontakte_secret'],
+ 'redirectUri' => $conf['oauth']['vkontakte_redirect'],
+ ));
+ },
+);
+
+if (! isset($_SESSION['return_to'])) {
+ $_SESSION['return_to'] = base64_decode(Get::val('return_to', ''));
+ $_SESSION['return_to'] = $_SESSION['return_to'] ?: $baseurl;
+}
+
+$provider = isset($_SESSION['oauth_provider']) ? $_SESSION['oauth_provider'] : 'none';
+$provider = strtolower(Get::val('provider', $provider));
+unset($_SESSION['oauth_provider']);
+
+$active_oauths = explode(' ', $fs->prefs['active_oauths']);
+if (!in_array($provider, $active_oauths)) {
+ Flyspray::show_error(26);
+}
+
+$obj = $providers[$provider]();
+
+if ( ! Get::has('code') && ! Post::has('username')) {
+ // get authorization code
+ header('Location: '.$obj->getAuthorizationUrl());
+ exit;
+}
+
+if (isset($_SESSION['oauth_token'])) {
+ $token = unserialize($_SESSION['oauth_token']);
+ unset($_SESSION['oauth_token']);
+} else {
+ // Try to get an access token
+ try {
+ $token = $obj->getAccessToken('authorization_code', array('code' => $_GET['code']));
+ } catch (\League\OAuth2\Client\Exception\IDPException $e) {
+ throw new Exception($e->getMessage());
+ }
+}
+
+$user_details = $obj->getUserDetails($token);
+$uid = $user_details->uid;
+
+if (Post::has('username')) {
+ $username = Post::val('username');
+} else {
+ $username = $user_details->nickname;
+}
+
+// First time logging in
+if (! Flyspray::checkForOauthUser($uid, $provider)) {
+ if (! $user_details->email) {
+ Flyspray::show_error(27);
+ }
+
+ $success = false;
+
+ if ($username) {
+ $group_in = $fs->prefs['anon_group'];
+ $name = $user_details->name ?: $username;
+ $success = Backend::create_user($username, null, $name, '', $user_details->email, 0, 0, $group_in, 1, $uid, $provider);
+ }
+
+ // username taken or not provided, ask for it
+ if (!$success) {
+ $_SESSION['oauth_token'] = serialize($token);
+ $_SESSION['oauth_provider'] = $provider;
+ $page->assign('provider', ucfirst($provider));
+ $page->assign('username', $username);
+ $page->pushTpl('register.oauth.tpl');
+ return;
+ }
+}
+
+if (($user_id = Flyspray::checkLogin($user_details->email, null, 'oauth')) < 1) {
+ Flyspray::show_error(23); // account disabled
+}
+
+$user = new User($user_id);
+
+// Set a couple of cookies
+$passweirded = crypt($user->infos['user_pass'], $conf['general']['cookiesalt']);
+Flyspray::setCookie('flyspray_userid', $user->id, 0,null,null,null,true);
+Flyspray::setCookie('flyspray_passhash', $passweirded, 0,null,null,null,true);
+$_SESSION['SUCCESS'] = L('loginsuccessful');
+$db->query("UPDATE {users} SET last_login = ? WHERE user_id=?", array(time(), $user->id));
+
+$return_to = $_SESSION['return_to'];
+unset($_SESSION['return_to']);
+
+Flyspray::redirect($return_to);
+?>
diff --git a/scripts/pm.php b/scripts/pm.php
new file mode 100644
index 0000000..c9884e8
--- /dev/null
+++ b/scripts/pm.php
@@ -0,0 +1,61 @@
+<?php
+
+ /********************************************************\
+ | Project Managers Toolbox |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~ |
+ | This script is for Project Managers to modify settings |
+ | for their project, including general permissions, |
+ | members, group permissions, and dropdown list items. |
+ \********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$user->perms('manage_project') || !$proj->id) {
+ Flyspray::show_error(16);
+}
+
+switch ($area = Req::val('area', 'prefs')) {
+ case 'pendingreq':
+ $sql = $db->query("SELECT *
+ FROM {admin_requests} ar
+ LEFT JOIN {tasks} t ON ar.task_id = t.task_id
+ LEFT JOIN {users} u ON ar.submitted_by = u.user_id
+ WHERE ar.project_id = ? AND resolved_by = 0
+ ORDER BY ar.time_submitted ASC", array($proj->id));
+
+ $page->assign('pendings', $db->fetchAllArray($sql));
+
+ case 'prefs':
+ case 'groups':
+ $page->assign('globalgroups', Flyspray::listGroups(0)); # global user groups
+ $page->assign('groups', Flyspray::listGroups($proj->id)); # project specific user groups
+ case 'editgroup':
+ // yeah, utterly stupid, is changed in 1.0 already
+ if (Req::val('area') == 'editgroup') {
+ $group_details = Flyspray::getGroupDetails(Req::num('id'));
+ if (!$group_details || $group_details['project_id'] != $proj->id) {
+ Flyspray::show_error(L('groupnotexist'));
+ Flyspray::redirect(createURL('pm', 'groups', $proj->id));
+ }
+ $page->uses('group_details');
+ }
+ case 'tasktype':
+ case 'tag':
+ case 'resolution':
+ case 'os':
+ case 'version':
+ case 'cat':
+ case 'status':
+ case 'newgroup':
+
+ $page->setTitle($fs->prefs['page_title'] . L('pmtoolbox'));
+ $page->pushTpl('pm.menu.tpl');
+ $page->pushTpl('pm.'.$area.'.tpl');
+ break;
+
+ default:
+ Flyspray::show_error(17);
+}
+?>
diff --git a/scripts/register.php b/scripts/register.php
new file mode 100644
index 0000000..34b62d4
--- /dev/null
+++ b/scripts/register.php
@@ -0,0 +1,63 @@
+<?php
+
+ /*********************************************************\
+ | Register a new user (when confirmation codes is used) |
+ | ~~~~~~~~~~~~~~~~~~~ |
+ \*********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$page->setTitle($fs->prefs['page_title'] . L('registernewuser'));
+
+if (!$user->isAnon()) {
+ Flyspray::redirect($baseurl);
+}
+
+if ($user->can_register()) {
+ // 32 is the length of the magic_url
+ if (Req::has('magic_url') && strlen(Req::val('magic_url')) == 32) {
+ // If the user came here from their notification link
+ $sql = $db->query('SELECT * FROM {registrations} WHERE magic_url = ?',
+ array(Get::val('magic_url')));
+
+ if (!$db->countRows($sql)) {
+ Flyspray::show_error(18);
+ }
+
+ $page->pushTpl('register.magic.tpl');
+ } else {
+ if($fs->prefs['captcha_securimage']){
+ $captchaoptions = array(
+ 'input_name' => 'captcha_code',
+ 'show_image_url' => 'securimage.php',
+ 'show_refresh_button' => false,
+ 'show_audio_button' => false,
+ 'disable_flash_fallback' => true
+ );
+ $captcha_securimage_html=Securimage::getCaptchaHtml($captchaoptions);
+ $page->assign('captcha_securimage_html', $captcha_securimage_html);
+ }
+
+ $page->pushTpl('register.no-magic.tpl');
+ }
+} elseif ($user->can_self_register()) {
+ if($fs->prefs['captcha_securimage']){
+ $captchaoptions = array(
+ 'input_name' => 'captcha_code',
+ 'show_image_url' => 'securimage.php',
+ 'show_refresh_button' => false,
+ 'show_audio_button' => false,
+ 'disable_flash_fallback' => true,
+ 'image_attributes' =>array('style'=>'')
+ );
+ $captcha_securimage_html=Securimage::getCaptchaHtml($captchaoptions);
+ $page->assign('captcha_securimage_html', $captcha_securimage_html);
+ }
+
+ $page->pushTpl('common.newuser.tpl');
+} else {
+ Flyspray::show_error(22);
+}
+?>
diff --git a/scripts/reports.php b/scripts/reports.php
new file mode 100644
index 0000000..7870291
--- /dev/null
+++ b/scripts/reports.php
@@ -0,0 +1,122 @@
+<?php
+
+ /********************************************************\
+ | Show various reports on tasks |
+ | ~~~~~~~~~~~~~~~~~~~~~~~~ |
+ \********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$user->perms('view_reports')) {
+ Flyspray::redirect($baseurl);
+}
+
+require_once(BASEDIR . '/includes/events.inc.php');
+$page->setTitle($fs->prefs['page_title'] . L('reports'));
+
+/**********************\
+* Event reports *
+\**********************/
+
+$events = array(1 => L('opened'),
+ 13 => L('reopened'),
+ 2 => L('closed'),
+ 3 => L('edited'),
+ 14 => L('assignmentchanged'),
+ 29 => L('events.useraddedtoassignees'),
+ 4 => L('commentadded'),
+ 5 => L('commentedited'),
+ 6 => L('commentdeleted'),
+ 7 => L('attachmentadded'),
+ 8 => L('attachmentdeleted'),
+ 11 => L('relatedadded'),
+ 12 => L('relateddeleted'),
+ 9 => L('notificationadded'),
+ 10 => L('notificationdeleted'),
+ 17 => L('reminderadded'),
+ 18 => L('reminderdeleted'),
+ 15 => L('addedasrelated'),
+ 16 => L('deletedasrelated'),
+ 19 => L('ownershiptaken'),
+ 20 => L('closerequestmade'),
+ 21 => L('reopenrequestmade'),
+ 22 => L('depadded'),
+ 23 => L('depaddedother'),
+ 24 => L('depremoved'),
+ 25 => L('depremovedother'),
+ 28 => L('pmreqdenied'),
+ 32 => L('subtaskadded'),
+ 33 => L('subtaskremoved'),
+ 34 => L('supertaskadded'),
+ 35 => L('supertaskremoved'),
+ );
+
+// Should events 19, 20, 21, 29 be here instead?
+$user_events = array(30 => L('created'),
+ 31 => L('deleted'));
+
+$page->assign('events', $events);
+$page->assign('user_events', $user_events);
+$page->assign('theuser', $user);
+
+$sort = strtoupper(Req::enum('sort', array('desc', 'asc')));
+
+$where = array();
+$params = array();
+$orderby = '';
+
+switch (Req::val('order')) {
+ case 'type':
+ $orderby = "h.event_type {$sort}, h.event_date {$sort}";
+ break;
+ case 'user':
+ $orderby = "user_id {$sort}, h.event_date {$sort}";
+ break;
+ case 'date': default:
+ $orderby = "h.event_date {$sort}, h.event_type {$sort}";
+}
+
+if( is_array(Req::val('events')) ){
+ foreach (Req::val('events') as $eventtype) {
+ $where[] = 'h.event_type = ?';
+ $params[] = $eventtype;
+ }
+ $where = '(' . implode(' OR ', $where) . ')';
+
+ if ($proj->id) {
+ $where = $where . 'AND (t.project_id = ? OR h.event_type IN(30, 31)) ';
+ $params[] = $proj->id;
+ }
+
+ if ( Req::val('fromdate') || Req::val('todate')) {
+ $where .= ' AND ';
+ $fromdate = Req::val('fromdate');
+ $todate = Req::val('todate');
+
+ if ($fromdate) {
+ $where .= ' h.event_date > ?';
+ $params[] = Flyspray::strtotime($fromdate) + 0;
+ }
+ if ($todate && $fromdate) {
+ $where .= ' AND h.event_date < ?';
+ $params[] = Flyspray::strtotime($todate) + 86400;
+ } else if ($todate) {
+ $where .= ' h.event_date < ?';
+ $params[] = Flyspray::strtotime($todate) + 86400;
+ }
+ }
+
+ $histories = $db->query("SELECT h.*
+ FROM {history} h
+ LEFT JOIN {tasks} t ON h.task_id = t.task_id
+ WHERE $where
+ ORDER BY $orderby", $params, Req::num('event_number', -1));
+ $histories = $db->fetchAllArray($histories);
+}
+
+$page->uses('histories', 'sort');
+
+$page->pushTpl('reports.tpl');
+?>
diff --git a/scripts/roadmap.php b/scripts/roadmap.php
new file mode 100644
index 0000000..709a0a5
--- /dev/null
+++ b/scripts/roadmap.php
@@ -0,0 +1,84 @@
+<?php
+/*********************************************************\
+| Show the roadmap |
+| ~~~~~~~~~~~~~~~~~~~ |
+\*********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if (!$proj->id) {
+ Flyspray::show_error(25);
+}
+
+if ((!$user->isAnon() && !$user->perms('view_roadmap')) || ($user->isAnon() && $proj->prefs['others_viewroadmap'] !=1)) {
+ # better set redirect to false to avoid endless loops
+ Flyspray::show_error(28, false);
+} else{
+
+ if($proj->prefs['use_effort_tracking']){
+ require_once(BASEDIR . '/includes/class.effort.php');
+ }
+
+
+ $page->setTitle($fs->prefs['page_title'] . L('roadmap'));
+
+ // Get milestones
+ $milestones = $db->query('SELECT version_id, version_name
+ FROM {list_version}
+ WHERE (project_id = ? OR project_id=0) AND version_tense = 3
+ ORDER BY list_position ASC',
+ array($proj->id));
+
+ $data = array();
+
+while ($row = $db->fetchRow($milestones)) {
+ // Get all tasks related to a milestone
+ $all_tasks = $db->query('SELECT percent_complete, is_closed
+ FROM {tasks}
+ WHERE closedby_version = ? AND project_id = ?',
+ array($row['version_id'], $proj->id));
+ $all_tasks = $db->fetchAllArray($all_tasks);
+
+ $percent_complete = 0;
+ foreach($all_tasks as $task) {
+ if($task['is_closed']) {
+ $percent_complete += 100;
+ } else {
+ $percent_complete += $task['percent_complete'];
+ }
+ }
+ $percent_complete = round($percent_complete/max(count($all_tasks), 1));
+
+ $tasks = $db->query('SELECT task_id, item_summary, detailed_desc, item_status, task_severity, task_priority, task_type, mark_private, opened_by, content, task_token, t.project_id,estimated_effort
+ FROM {tasks} t
+ LEFT JOIN {cache} ca ON (t.task_id = ca.topic AND ca.type = \'rota\' AND t.last_edited_time <= ca.last_updated)
+ WHERE closedby_version = ? AND t.project_id = ? AND is_closed = 0',
+ array($row['version_id'], $proj->id));
+ $tasks = $db->fetchAllArray($tasks);
+
+ $count = count($tasks);
+ for ($i = 0; $i < $count; $i++) {
+ if (!$user->can_view_task($tasks[$i])) {
+ unset($tasks[$i]);
+ }
+ }
+
+ $data[] = array('id' => $row['version_id'], 'open_tasks' => $tasks, 'percent_complete' => $percent_complete,
+ 'all_tasks' => $all_tasks, 'name' => $row['version_name']);
+} # end while
+
+ if (Get::val('txt')) {
+ $page = new FSTpl;
+ header('Content-Type: text/plain; charset=UTF-8');
+ $page->uses('data', 'page');
+ $page->display('roadmap.text.tpl');
+ exit();
+ } else {
+ $page->uses('data', 'page');
+ $page->pushTpl('roadmap.tpl');
+ }
+
+} # end if allowed roadmap view
+?>
diff --git a/scripts/toplevel.php b/scripts/toplevel.php
new file mode 100644
index 0000000..f370cde
--- /dev/null
+++ b/scripts/toplevel.php
@@ -0,0 +1,103 @@
+<?php
+/***********************************\
+| Top level project overview |
+\***********************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+if ($proj->id && $user->can_select_project($proj->prefs)) {
+ $projects = array(
+ 0 => array(
+ 'project_id' => $proj->id,
+ 'project_title' => $proj->prefs['project_title'],
+ 'project_is_active' => $proj->prefs['project_is_active']
+ )
+ );
+} else {
+ $projects = $fs->projects;
+ # anon users should not see details of a restricted project but anon tasks creation allowed
+ # but in /index.php we filter now by 'can_select_project', not 'can_view_project' anymore.
+ $projects= array_filter($projects, array($user, 'can_select_project'));
+}
+
+if(count($projects)>0){
+
+ $most_wanted = array();
+ $stats = array();
+ $assigned_to_myself = array();
+ $projprefs = array();
+
+ # Most wanted tasks for each project
+ foreach ($projects as $project) {
+ # means 'can view tasks' ..
+ if($user->can_view_project($project['project_id'])){
+ $sql = $db->query('SELECT v.task_id, count(*) AS num_votes
+ FROM {votes} v
+ LEFT JOIN {tasks} t ON v.task_id = t.task_id AND t.project_id = ?
+ WHERE t.is_closed = 0
+ GROUP BY v.task_id
+ ORDER BY num_votes DESC',
+ array($project['project_id']), 5
+ );
+
+ if ($db->countRows($sql)) {
+ $most_wanted[$project['project_id']] = $db->fetchAllArray($sql);
+ }
+ }
+ }
+
+ # Project stats
+ foreach ($projects as $project) {
+ $sql = $db->query('SELECT count(*) FROM {tasks} WHERE project_id = ?', array($project['project_id']));
+ $stats[$project['project_id']]['all'] = $db->fetchOne($sql);
+
+ $sql = $db->query('SELECT count(*) FROM {tasks} WHERE project_id = ? AND is_closed = 0', array($project['project_id']));
+ $stats[$project['project_id']]['open'] = $db->fetchOne($sql);
+
+ $sql = $db->query('SELECT avg(percent_complete) FROM {tasks} WHERE project_id = ? AND is_closed = 0', array($project['project_id']));
+ $stats[$project['project_id']]['average_done'] = round($db->fetchOne($sql), 0);
+
+ if ($proj->id) {
+ $prefs = $proj->prefs;
+ } else {
+ $currentproj = new Project($project['project_id']);
+ $prefs = $currentproj->prefs;
+ }
+
+ $projprefs[$project['project_id']] = $prefs;
+
+ if($user->perms('view_estimated_effort', $project['project_id']) ){
+ if ($prefs['use_effort_tracking']) {
+ $sql = $db->query('
+ SELECT t.task_id, t.estimated_effort
+ FROM {tasks} t
+ WHERE project_id = ? AND is_closed = 0',
+ array($project['project_id'])
+ );
+ $stats[$project['project_id']]['tasks'] = $db->fetchAllArray($sql);
+ }
+ }
+ }
+
+ # Assigned to myself
+ foreach ($projects as $project) {
+ $sql = $db->query('
+ SELECT a.task_id
+ FROM {assigned} a
+ LEFT JOIN {tasks} t ON a.task_id = t.task_id AND t.project_id = ?
+ WHERE t.is_closed = 0 and a.user_id = ?',
+ array($project['project_id'], $user->id), 5
+ );
+ if ($db->countRows($sql)) {
+ $assigned_to_myself[$project['project_id']] = $db->fetchAllArray($sql);
+ }
+ }
+ $page->uses('most_wanted', 'stats', 'projects', 'assigned_to_myself', 'projprefs');
+ $page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title'] . ': ' . L('toplevel'));
+ $page->pushTpl('toplevel.tpl');
+} else{
+ # mmh what we want to show anon users with only the 'create anon task' permission enabled?...
+}
+?>
diff --git a/scripts/user.php b/scripts/user.php
new file mode 100644
index 0000000..9993d9a
--- /dev/null
+++ b/scripts/user.php
@@ -0,0 +1,43 @@
+<?php
+
+ /*********************************************************\
+ | View a user's profile |
+ | ~~~~~~~~~~~~~~~~~~~~ |
+ \*********************************************************/
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$page->assign('groups', Flyspray::listGroups());
+
+if ($proj->id) {
+ $page->assign('project_groups', Flyspray::listGroups($proj->id));
+}
+
+$id = Flyspray::validUserId(Get::val('id', Get::val('uid')));
+if (!$id) {
+ $id = Flyspray::usernameToId(Get::val('user_name'));
+}
+
+$theuser = new User($id);
+if ($theuser->isAnon()) {
+ Flyspray::show_error(19);
+}
+
+// Some possibly interesting information about the user
+$sql = $db->query('SELECT count(*) FROM {comments} WHERE user_id = ?', array($theuser->id));
+$page->assign('comments', $db->fetchOne($sql));
+
+$sql = $db->query('SELECT count(*) FROM {tasks} WHERE opened_by = ?', array($theuser->id));
+$page->assign('tasks', $db->fetchOne($sql));
+
+$sql = $db->query('SELECT count(*) FROM {assigned} WHERE user_id = ?', array($theuser->id));
+$page->assign('assigned', $db->fetchOne($sql));
+
+$page->assign('theuser', $theuser);
+
+$page->setTitle($fs->prefs['page_title'] . L('viewprofile'));
+$page->pushTpl('profile.tpl');
+
+?>
diff --git a/securimage.php b/securimage.php
new file mode 100644
index 0000000..3ac24d0
--- /dev/null
+++ b/securimage.php
@@ -0,0 +1,6 @@
+<?php
+
+require_once 'vendor/dapphp/securimage/securimage.php';
+
+$img = new Securimage();
+$img->show(); // outputs the image and http headers
diff --git a/setup/cleanupaftersetup.php b/setup/cleanupaftersetup.php
new file mode 100644
index 0000000..a62ed7d
--- /dev/null
+++ b/setup/cleanupaftersetup.php
@@ -0,0 +1,7 @@
+<?php
+chdir('..');
+$hide='_'.md5(rand().time()).'_setup';
+rename('setup', $hide); # Hide the setup dir with a random name, not deleting it. Just in case to have the complete software together or something got wrong.
+chmod($hide, 0400); # make it extra unaccessible (directory x bit) just in case the dir lands on search engines index..
+echo '<a href="../">You can now use Flyspray.</a>';
+?>
diff --git a/setup/composerit.php b/setup/composerit.php
new file mode 100644
index 0000000..75c2175
--- /dev/null
+++ b/setup/composerit.php
@@ -0,0 +1,68 @@
+<?php
+@set_time_limit(0);
+ini_set('memory_limit', '64M');
+
+define('IN_FS', 1);
+define('BASEDIR', dirname(__FILE__));
+define('APPLICATION_PATH', dirname(BASEDIR));
+define('OBJECTS_PATH', APPLICATION_PATH . '/includes');
+define('TEMPLATE_FOLDER', BASEDIR . '/templates/');
+
+require_once OBJECTS_PATH.'/i18n.inc.php';
+
+class user{var $infos=array();};
+class project{var $id=0;};
+$user = new user;
+$proj = new project;
+
+load_translations();
+
+# no caching to prevent old pages if user goes back and forth during install
+header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+?>
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Flyspray Install - Third Party Packages needed</title>
+ <link media="screen" href="../themes/CleanFS/theme.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body style="padding:2em;"><img src="../flyspray.png" style="display:block;margin:auto;">
+ <h3>Step 1: Trying to download Composer</h3>
+<?php
+ if (ini_get('safe_mode') == 1) {
+ $composerfile = file_get_contents('https://getcomposer.org/installer');
+ file_put_contents('composerinstaller', $composerfile);
+ echo 'Download done'.'<br>';
+ $argv = array('--disable-tls'); # just for avoiding warnings
+ ?>
+ <h3>Step 2: Trying to load composerinstaller into the running php script</h3>
+ <p>Wait a few seconds until composerinstaller put his output under the button. Once the output looks good, try installing the dependencies using the button.</p>
+ <a href="composerit2.php" class="button" style="padding:1em;font-size:1em">Install dependencies</a>
+ <pre>
+ <?php
+ require 'composerinstaller';
+ # Ok, composerinstaller exits itself, so no more code needed here, but it looks more complete :-)
+ echo '</pre>';
+ } else {
+ $phpexe='php';
+ # TODO: autodetect the matching commandline php on the host matching the php version of the webserver
+ # Any idea? Using $_SERVER['PHP_PEAR_SYSCONF_DIR'] or $_SERVER['PHPRC'] for detecting can help a bit, but weak hints..
+ # This is just a temp hack for installing flyspray on xampp on Windows
+ if (getenv('OS') == 'Windows_NT' && isset($_SERVER['PHPRC']) && strstr($_SERVER['PHPRC'], 'xampp')) {
+ $phpexe=$_SERVER['PHPRC'].'\php.exe';
+ }
+ shell_exec($phpexe.' -r "readfile(\'https://getcomposer.org/installer\');" | '.$phpexe);
+ if (!is_readable('composer.phar')) {
+ die('Composer installer download failed! Please consider downloading vendors directly from Flyspray support website');
+ }
+ echo 'Successfully downloaded Composer.<br /><br />';
+ echo '<a href="composerit2.php" class="button" style="padding:1em;font-size:1em">Try to install dependencies</a>';
+ }
+?>
+</body>
+</html>
diff --git a/setup/composerit.pl b/setup/composerit.pl
new file mode 100755
index 0000000..6363430
--- /dev/null
+++ b/setup/composerit.pl
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+use CGI;
+
+$cgi=new CGI;
+print $cgi->header();
+print '<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Flyspray Install - curl -sS https://getcomposer.org/installer | php</title>
+<link media="screen" href="../themes/CleanFS/theme.css" rel="stylesheet" type="text/css" />
+</head>
+<body style="padding:2em;"><img src="../flyspray.png" style="display:block;margin:auto;">
+';
+print '<h3>Trying to load composer stuff</h3><pre>&gt; curl -sS https://getcomposer.org/installer | php</pre>';
+chdir('../');
+@step1= `curl -sS https://getcomposer.org/installer | php`;
+print '<h3>Step 1 result</h3><br/><pre>';
+foreach (@step1) {
+ print;
+}
+print '</pre>';
+
+print '<a class="button" style="text-align:center;margin:auto;display:block;max-width:300px;font-size:2em;" href="composerit2.pl">Next Step</a>';
diff --git a/setup/composerit2.php b/setup/composerit2.php
new file mode 100644
index 0000000..09aade9
--- /dev/null
+++ b/setup/composerit2.php
@@ -0,0 +1,78 @@
+<?php
+@set_time_limit(0);
+ini_set('memory_limit', '64M');
+
+define('IN_FS', 1);
+define('BASEDIR', dirname(__FILE__));
+define('APPLICATION_PATH', dirname(BASEDIR));
+define('OBJECTS_PATH', APPLICATION_PATH . '/includes');
+define('TEMPLATE_FOLDER', BASEDIR . '/templates/');
+
+require_once OBJECTS_PATH.'/i18n.inc.php';
+class user{var $infos=array();};
+class project{var $id=0;};
+$user = new user;
+$proj = new project;
+load_translations();
+
+# no caching to prevent old pages if user goes back and forth during install
+header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+?>
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Flyspray Install - Third Party Packages needed - Step 3</title>
+ <link media="screen" href="../themes/CleanFS/theme.css" rel="stylesheet" type="text/css" />
+ </head>
+ <body style="padding:2em;"><img src="../flyspray.png" style="display:block;margin:auto;">
+ <?php
+ if (ini_get('safe_mode') == 1) {
+ echo '
+ <h3>PHP safe_mode is enabled. We currently don\'t know how to run the "php composer.phar install" from php web frontend under this circumstances.</h3>
+ <h3>But lets test if we can workaround it with Perl:</h3>
+ <a href="composerit2.pl" class="button">Test using Perl: composerit2.pl</a>';
+ } else {
+ echo '<h3>Step 3: Trying to install dependencies</h3>';
+ # $argv=('install');
+ # chdir('..');
+ # echo '<pre>';
+ # require 'composer.phar';
+ # echo '</pre>';
+
+ # without chdir('..');
+ $phpexe='php';
+ # TODO: autodetect the matching commandline php on the host matching the php version of the webserver
+ # Any idea? Using $_SERVER['PHP_PEAR_SYSCONF_DIR'] or $_SERVER['PHPRC'] for detecting can help a bit, but weak hints..
+ # This is just a temp hack for installing flyspray on xampp on Windows
+ if (getenv('OS') == 'Windows_NT' && isset($_SERVER['PHPRC']) && strstr($_SERVER['PHPRC'], 'xampp')) {
+ $phpexe=$_SERVER['PHPRC'].'\php.exe';
+ }
+ $cmd2 = $phpexe.' composer.phar --working-dir=.. install';
+
+ # with chdir('..');
+ #$cmd2 = 'php composer.phar install';
+
+ echo $cmd2.'<br/><br/>';
+ shell_exec($cmd2);
+ echo '<strong>Done</strong>';
+
+ echo '<h3>Step 4: Checking and cleaning:</h3>';
+ if (is_readable('../vendor/autoload.php')) {
+ echo 'Composer installation ok<br />';
+ } else {
+ echo 'Composer installation failed<br />';
+ }
+ if (is_file('composer.phar')) {
+ unlink('composer.phar');
+ }
+ echo 'Cleanup made<br /><br />';
+ echo '<a href="./index.php" class="button">Go back</a>';
+ }
+ ?>
+ </body>
+</html>
diff --git a/setup/composerit2.pl b/setup/composerit2.pl
new file mode 100755
index 0000000..04bd81b
--- /dev/null
+++ b/setup/composerit2.pl
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+use CGI;
+
+$cgi=new CGI;
+print $cgi->header(
+ -expires => 'Sat, 26 Jul 1997 05:00:00 GMT',
+ -Pragma => 'no-cache',
+ -Cache_Control => join(', ', qw(private no-cache no-store must-revalidate max-age=0 pre-check=0 post-check=0)),
+);
+print '<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Flyspray Install - php composer.phar install</title>
+<link media="screen" href="../themes/CleanFS/theme.css" rel="stylesheet" type="text/css" />
+</head>
+<body style="padding:2em;"><img src="../flyspray.png" style="display:block;margin:auto;">
+';
+
+print '<h3>Trying to install packages</h3>';
+print '<a class="button" style="text-align:center;margin:auto;min-width:300px;" href="index.php">Go to setup page</a>';
+#chdir('..');
+@step2= `export COMPOSER_HOME=. ; php composer.phar --working-dir=.. install 2>&1`;
+print '<pre>&gt; php composer.phar install</pre>';
+print '<pre>';
+foreach (@step2) {
+ print;
+}
+print '</pre>';
diff --git a/setup/composertest.php b/setup/composertest.php
new file mode 100644
index 0000000..b8a5663
--- /dev/null
+++ b/setup/composertest.php
@@ -0,0 +1,68 @@
+<?php
+
+@set_time_limit(0);
+ini_set('memory_limit', '64M');
+
+define('IN_FS', 1);
+define('BASEDIR', dirname(__FILE__));
+define('APPLICATION_PATH', dirname(BASEDIR));
+define('OBJECTS_PATH', APPLICATION_PATH . '/includes');
+define('TEMPLATE_FOLDER', BASEDIR . '/templates/');
+
+require_once OBJECTS_PATH.'/i18n.inc.php';
+class user{var $infos=array();}; class project{var $id=0;};
+$user=new user; $proj=new project;
+load_translations();
+
+# no caching to prevent old pages if user goes back and forth during install
+header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+# Step 1 and 2 of composer install now working also with SAFE_MODE enabled in php5.3.*
+#if(ini_get('safe_mode') == 1){
+# $composerit = 'composerit.pl'; // try it with perl scripts
+#}else{
+ $composerit = 'composerit.php'; // try it with php
+#}
+?>
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8'>
+ <title>Flyspray Install - Third Party Packages needed</title>
+ <link media="screen" href="../themes/CleanFS/theme.css" rel="stylesheet" type="text/css" />
+</head>
+<body style="padding:2em;"><img src="../flyspray.png" style="display:block;margin:auto;">
+ <h2>It seems you try to install a development version of Flyspray.</h2>
+ <h2><?php echo L('needcomposer'); ?></h2>
+ <a href="<?php echo $composerit; ?>" class="button" style="margin:auto;max-width:300px;text-align:center;display:block;font-size:2em;"><?php echo L('installcomposer'); ?></a>
+ <p style="margin-top:50px;">
+ In case the above solution doesn't work for you, use ssh to login to your server, move to the root directory of your unpacked flyspray sources and execute this:
+ </p>
+ <pre>
+ curl -sS https://getcomposer.org/installer | php
+ php composer.phar install
+ </pre>
+
+<div class="error">
+<h4>Shared Hostings</h4>
+<p>If you are on a shared hosting, there are probably different php versions available. The hosting companies name them often like <b>php5.4</b>, <b>php5.5-cli</b> or <b>php-cgi-7.0</b>. Choose the best matching php-version for your Hosting (should ideally match that of what the webserver uses). To see available php versions on the commandline type</p>
+<pre><strong>php</strong> <kbd class="key">tab</kbd> <kbd class="key">tab</kbd></pre>
+<p><kbd>tab</kbd> <kbd>tab</kbd> is autocompletion on bash, so it shows all executable that start with <strong>php</strong>.</p>
+<p>Lets say the webserver uses PHP 5.6 by default, than a <b>php5.6</b> you found on the commandline is a good choice:</p>
+<pre>curl -sS https://getcomposer.org/installer | php5.6
+php5.6 composer.phar install
+</pre>
+</div>
+
+ <p>Or take an official release, which contains all needed external packages bundled.</p>
+ <h2>README.md</h2>
+ <div id="content">
+ <pre>
+ <?php echo file_get_contents('../README.md'); ?>
+ </pre>
+ </div>
+</body>
+</html>
diff --git a/setup/exportdb.php b/setup/exportdb.php
new file mode 100644
index 0000000..fd714a3
--- /dev/null
+++ b/setup/exportdb.php
@@ -0,0 +1,27 @@
+<?php
+
+error_reporting(E_ALL);
+
+die('Enable me by commenting this out by editing '.basename(__FILE__).' at line '.__LINE__);
+
+require_once '../vendor/adodb/adodb-php/adodb.inc.php';
+require_once '../vendor/adodb/adodb-php/adodb-xmlschema03.inc.php';
+
+$conf = @parse_ini_file('../flyspray.conf.php', true) or die('Cannot open config file.');
+
+/* Start by creating a normal ADODB connection. */
+$db = ADONewConnection($conf['database']['dbtype']);
+$db->Connect( $conf['database']['dbhost'], $conf['database']['dbuser'],
+ $conf['database']['dbpass'], $conf['database']['dbname']) or die('Cannot connect to DB.');
+$db->debug= true;
+
+/* Use the database connection to create a new adoSchema object. */
+$schema = new adoSchema($db);
+
+$withdata=false;
+$stripprefix=true;
+$data = $schema->ExtractSchema( $withdata, ' ', $conf['database']['dbprefix'], $stripprefix);
+
+file_put_contents('flyspray-schema.xml', $data);
+
+?>
diff --git a/setup/images/exclamation.png b/setup/images/exclamation.png
new file mode 100644
index 0000000..3e788f5
--- /dev/null
+++ b/setup/images/exclamation.png
Binary files differ
diff --git a/setup/images/title.png b/setup/images/title.png
new file mode 100644
index 0000000..9f615da
--- /dev/null
+++ b/setup/images/title.png
Binary files differ
diff --git a/setup/index.php b/setup/index.php
new file mode 100644
index 0000000..f55e748
--- /dev/null
+++ b/setup/index.php
@@ -0,0 +1,1226 @@
+<?php
+// +----------------------------------------------------------------------
+// | Installer - there is still a lot to clean up, but it works
+// +----------------------------------------------------------------------
+// | Copyright (C) 2005 by Jeffery Fernandez <developer@jefferyfernandez.id.au>
+// | Copyright (C) 2006-2007 by Cristian Rodriguez <judas.iscariote@flyspray.org> and Florian Schmitz <floele@gmail.com>
+// +----------------------------------------------------------------------
+
+@set_time_limit(0);
+ini_set('memory_limit', '64M');
+
+define('IN_FS', 1 );
+define('APPLICATION_NAME', 'Flyspray');
+define('BASEDIR', dirname(__FILE__));
+define('APPLICATION_PATH', dirname(BASEDIR));
+define('OBJECTS_PATH', APPLICATION_PATH . '/includes');
+define('TEMPLATE_FOLDER', BASEDIR . '/templates/');
+
+require_once OBJECTS_PATH.'/fix.inc.php';
+require_once OBJECTS_PATH.'/class.gpc.php';
+require_once OBJECTS_PATH.'/class.flyspray.php';
+require_once OBJECTS_PATH.'/i18n.inc.php';
+require_once OBJECTS_PATH.'/class.tpl.php';
+
+// Load translations
+load_translations();
+
+# must be sure no-cache before any possible redirect, we maybe come back later here after composer install stuff.
+header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
+header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
+header("Cache-Control: post-check=0, pre-check=0", false);
+header("Pragma: no-cache");
+
+if (is_readable(APPLICATION_PATH . '/vendor/autoload.php')){
+ // Use composer autoloader
+ require APPLICATION_PATH . '/vendor/autoload.php';
+} else{
+ Flyspray::redirect('composertest.php');
+ exit;
+}
+
+// no transparent session id improperly configured servers
+ini_set('session.use_trans_sid', 0);
+session_start();
+
+if (is_readable('../flyspray.conf.php') && count(parse_ini_file('../flyspray.conf.php')) > 0){
+ die('<div style="text-align:center;padding:20px;font-family:sans-serif;font-size:16px;">
+Flyspray already installed. Use the <a href="upgrade.php"
+style="
+margin:2em;
+background-color: white;
+border: 1px solid #bbb;
+border-radius: 4px;
+box-shadow: 0 1px 1px #ddd;
+color: #565656;
+cursor: pointer;
+display: inline-block;
+font-family: sans-serif;
+font-size: 100%;
+font-weight: bold;
+line-height: 130%;
+padding: 8px 13px 8px 10px;
+text-decoration: none;
+">Upgrader</a> to upgrade your Flyspray,
+or delete flyspray.conf.php to run setup. You can *not* use the setup on an existing database.</div>');
+}
+
+$conf['general']['syntax_plugin'] = '';
+
+// ---------------------------------------------------------------------
+// Application Web locations
+// ---------------------------------------------------------------------
+define('APPLICATION_SETUP_INDEX', Flyspray::absoluteURI());
+
+class Setup extends Flyspray
+{
+ public $mPhpRequired;
+ public $mSupportedDatabases;
+ public $mAvailableDatabases;
+
+ public $mProceed;
+ public $mPhpVersionStatus;
+ public $mDatabaseStatus;
+ public $xmlStatus;
+ public $mConfigText;
+ public $mHtaccessText;
+ public $mWriteStatus;
+
+ public $mDbConnection;
+ public $mProductName;
+
+ /**
+ * @var string To store the data filter type
+ */
+ public $mDataFilter;
+
+ /**
+ * @var array To store the type of database setup (install or Upgrade).
+ */
+
+ public $mAttachmentsTable;
+ public $mCommentsTable;
+
+ public $mServerSoftware;
+ public $mMinPasswordLength;
+ public $mAdminUsername;
+ public $mAdminPassword;
+ /**
+ * @var object to store the adodb datadict object.
+ */
+ public $mDataDict;
+
+ public $mXmlSchema;
+
+ public function __construct()
+ {
+ // Look for ADOdb
+ $this->mAdodbPath = dirname(__DIR__) . '/vendor/adodb/adodb-php/adodb.inc.php';
+ $this->mProductName = 'Flyspray';
+ $this->mMinPasswordLength = 8;
+
+ // Initialise flag for proceeding to next step.
+ $this->mProceed = false;
+ $this->mPhpRequired = '5.4';
+ $this->xmlStatus = function_exists('xml_parser_create');
+ $this->sapiStatus = (php_sapi_name() != 'cgi');
+
+ // If the database is supported in Flyspray, the function to check in PHP.
+ $this->mSupportedDatabases = array(
+ 'MySQLi' => array(true,'mysqli_connect','mysqli'),
+ 'MySQL' => array(true, 'mysql_connect', 'mysql'),
+ 'Postgres' => array(true, 'pg_connect', 'pgsql'),
+ );
+ $this->mAvailableDatabases = array();
+
+ // Process the page actions
+ $this->processActions();
+ }
+
+ /**
+ * Function to check the permission of the config file
+ * @param void
+ * @return string An html formatted boolean answer
+ */
+ public function checkWriteability($path)
+ {
+ // Get the full path to the file
+ $file = APPLICATION_PATH .'/' . $path;
+
+ // In case it is flyspray.conf.php, the file does not exist
+ // so we can't tell that it is writeable. So we attempt to create an empty one
+ if ($path == 'flyspray.conf.php') {
+ $fp = @fopen($file, 'wb');
+ @fclose($fp);
+ // Let's try at least...
+ #@chmod($file, 0666);
+ @chmod($file, 0644); # looks a bit better than worldwritable
+ }
+ if(is_dir($path)){
+ # for cache and attachement directories x-bit needed
+ @chmod($file, 0755);
+ }
+ $this->mWriteStatus[$path] = $this->IsWriteable($file);
+
+ // Return an html formated writeable/un-writeable string
+ return $this->returnStatus($this->mWriteStatus[$path], $type = 'writeable');
+ }
+
+ /**
+ * Function to check the availability of the Database support
+ * @param void
+ * @return void
+ */
+ public function checkDatabaseSupport()
+ {
+ $status = array();
+
+ foreach ($this->mSupportedDatabases as $which => $database)
+ {
+ // Checking if the database has libraries built into PHP. Returns true/false
+ $this->mAvailableDatabases[$which]['status'] = function_exists($database[1]);
+
+ // If the Application(Flyspray) supports the available database supported in PHP
+ $this->mAvailableDatabases[$which]['supported'] = ($database[0] === $this->mAvailableDatabases[$which]['status'])
+ ? $this->mAvailableDatabases[$which]['status']
+ : false;
+
+ // Just transferring the value for ease of veryfying Database support.
+ $status[] = $this->mAvailableDatabases[$which]['supported'];
+
+ // Generating the output to be displayed
+ $this->mAvailableDatabases[$which]['status_output'] =
+ $this->returnStatus($this->mAvailableDatabases[$which]['status'], $type = 'available');
+ }
+
+ // Check if any one database support exists.
+ // Update the status of database availability
+ $this->mDatabaseStatus = in_array('1', $status);
+ }
+
+
+ /**
+ * CheckPreStatus
+ * we proceed or not ?
+ * @access public
+ * @return bool
+ */
+ public function checkPreStatus()
+ {
+ $this->mProceed = ($this->mDatabaseStatus && $this->mPhpVersionStatus && $this->xmlStatus);
+
+ return $this->mProceed;
+ }
+
+
+ /**
+ * Function to check the version of PHP available compared to the
+ * Applications requirements
+ * @param void
+ * @return string An html formatted boolean answer
+ */
+ public function checkPhpCompatibility()
+ {
+ // Check the PHP version.
+ $this->mPhpVersionStatus = version_compare(PHP_VERSION, $this->mPhpRequired, '>=');
+
+ // Return an html formated Yes/No string
+ return $this->returnStatus($this->mPhpVersionStatus, $type = 'yes');
+ }
+
+ /**
+ * Function to check the posted data from forms.
+ * @param array $expectedFields Array of field names which needs processing
+ * If the array of filed names don't exist in the Posted data, then this function
+ * will accumulate error messages in the $_SESSION[PASS_PHRASE]['page_message'] array.
+ * return boolean/array $data will be returned if successful
+ */
+ public function checkPostedData($expectedFields, $pageHeading)
+ {
+ if(!is_array($expectedFields)){
+ $expectedFields = array();
+ }
+
+ // Grab the posted data and trim it.
+ $data = array_filter($_POST, array(&$this, "trimArgs"));
+
+
+ // Loop through the required values and check data
+ foreach($expectedFields as $key => $value)
+ {
+
+ // If the data is Required and is empty or not set
+ if (!isset($data[$key]) || empty($data[$key]))
+ {
+ if ($expectedFields[$key][2] == true)
+ {
+ // accumulate error messages
+ $_SESSION['page_message'][] = "<strong>{$expectedFields[$key][0]}</strong> is required";
+ }
+ }
+ // Check for variable types
+ elseif (!$this->verifyVariableTypes($expectedFields[$key][1], $data[$key]))
+ {
+ $_SESSION['page_message'][] = "<strong>{$expectedFields[$key][0]}</strong> has to be a {$expectedFields[$key][1]}";
+ }
+ }
+
+ // If there were messages, return false
+ if (isset($_SESSION['page_message']))
+ {
+ $_SESSION['page_heading'] = $pageHeading;
+ return false;
+ }
+ else
+ {
+ return $data;
+ }
+ }
+
+ public function displayAdministration()
+ {
+ // Trim the empty values in the $_POST array
+ $data = array_filter($_POST, array($this, "trimArgs"));
+
+ $templates =
+ array(
+ 'admin_body' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'administration.tpl',
+ 'vars' => array(
+ 'product_name' => $this->mProductName,
+ 'message' => $this->getPageMessage(),
+ 'admin_email' => $this->getParamValue($data, 'admin_email', ''),
+ 'pass_phrase' => $this->getParamValue($data, 'pass_phrase', ''),
+ 'admin_username' => $this->getParamValue($data, 'admin_username', ''),
+ 'admin_realname' => $this->getParamValue($data, 'admin_realname', ''),
+ 'admin_xmpp' => $this->getParamValue($data, 'admin_xmpp', ''),
+ 'admin_password' => $this->getParamValue($data, 'admin_password', substr(md5(mt_rand()), 0, $this->mMinPasswordLength)),
+ 'db_type' => $this->getParamValue($data, 'db_type', ''),
+ 'db_hostname' => $this->getParamValue($data, 'db_hostname', ''),
+ 'db_username' => $this->getParamValue($data, 'db_username', ''),
+ 'db_password' => $this->getParamValue($data, 'db_password', ''),
+ 'db_name' => $this->getParamValue($data, 'db_name', ''),
+ 'db_prefix' => $this->getParamValue($data, 'db_prefix', ''),
+ 'daemonise' => $this->getReminderDaemonSelection($this->getParamValue($data, 'reminder_daemon', '0')),
+ ),
+ ),
+
+ 'structure' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'structure.tpl',
+ 'vars' => array(
+ 'title' => 'Administration setup for',
+ 'headers' => '',
+ 'index' => APPLICATION_SETUP_INDEX,
+ 'version' => $this->version,
+ ),
+ 'block' => array('body' => 'admin_body')
+ )
+ );
+
+ // Output the final template.
+ $this->outputPage($templates);
+ }
+
+
+ public function displayCompletion()
+ {
+ // Trim the empty values in the $_POST array
+ $data = array_filter($_POST, array($this, "trimArgs"));
+
+ $templates =
+ array(
+ 'complete_body' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'complete_install.tpl',
+ 'vars' => array(
+ 'product_name' => $this->mProductName,
+ 'message' => $this->getPageMessage(),
+ 'config_writeable' => $this->mWriteStatus['flyspray.conf.php'],
+ 'config_text' => $this->mConfigText,
+ 'admin_username' => $this->mAdminUsername,
+ 'admin_password' => $this->mAdminPassword,
+ 'site_index' => dirname($_SERVER['REQUEST_URI']) . '/../',
+ 'complete_action' => 'index.php',
+ 'daemonise' => true,
+ ),
+ ),
+
+ 'structure' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'structure.tpl',
+ 'vars' => array(
+ 'title' => 'Setup confirmation for',
+ 'headers' => '',
+ 'index' => APPLICATION_SETUP_INDEX,
+ 'version' => $this->version,
+ ),
+ 'block' => array('body' => 'complete_body')
+ )
+ );
+
+ // Output the final template.
+ $this->outputPage($templates);
+ }
+
+ public function displayDatabaseSetup()
+ {
+
+ // Trim the empty values in the $_POST array
+ $data = array_filter($_POST, array($this, "trimArgs"));
+ $this->checkDatabaseSupport();
+
+ // Make sure that the user can't choose a DB which is not supported
+ foreach ($this->mSupportedDatabases as $db => $arr) {
+ if (!$this->mAvailableDatabases[$db]['supported']) {
+ unset($this->mSupportedDatabases[$db]);
+ }
+ }
+
+ $templates =
+ array(
+ 'database_body' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'database.tpl',
+ 'vars' => array(
+ 'product_name' => $this->mProductName,
+ 'message' => $this->getPageMessage(),
+ 'databases' => $this->mSupportedDatabases,
+ 'db_type' => $this->getParamValue($data, 'db_type', ''),
+ 'db_hostname' => $this->getParamValue($data, 'db_hostname', 'localhost'),
+ 'db_username' => $this->getParamValue($data, 'db_username', ''),
+ 'db_password' => $this->getParamValue($data, 'db_password', ''),
+ 'db_name' => $this->getParamValue($data, 'db_name', ''),
+ 'db_prefix' => $this->getParamValue($data, 'db_prefix', 'flyspray_'),
+ 'version' => $this->version,
+ ),
+ ),
+ 'structure' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'structure.tpl',
+ 'vars' => array(
+ 'title' => 'Database setup for',
+ 'headers' => '',
+ 'index' => APPLICATION_SETUP_INDEX,
+ 'version' => $this->version,
+ ),
+ 'block' => array('body' => 'database_body')
+ )
+ );
+
+ // Output the final template.
+ $this->outputPage($templates);
+ }
+
+
+ public function displayPreInstall()
+ {
+ // Check the Database support on the server.
+ $this->checkDatabaseSupport();
+
+ $templates =
+ array(
+ 'index_body' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'pre_install.tpl',
+ 'vars' => array(
+ 'product_name' => $this->mProductName,
+ 'required_php' => $this->mPhpRequired,
+ 'php_output' => $this->checkPhpCompatibility(),
+ 'database_output' => $this->getDatabaseOutput(),
+ 'config_output' => $this->checkWriteability('flyspray.conf.php'),
+ 'cache_output' => $this->checkWriteability('cache'),
+ 'att_output' => $this->checkWriteability('attachments'),
+ 'ava_output' => $this->checkWriteability('avatars'),
+ 'config_status' => $this->mWriteStatus['flyspray.conf.php'],
+ 'xmlStatus' => $this->xmlStatus,
+ 'sapiStatus' => $this->sapiStatus,
+ 'php_settings' => $this->getPhpSettings(),
+ 'status' => $this->checkPreStatus(),
+ 'message' => $this->getPageMessage(),
+ ),
+ ),
+
+ 'structure' => array(
+ 'path' => TEMPLATE_FOLDER,
+ 'template' => 'structure.tpl',
+ 'vars' => array(
+ 'title' => 'Pre-Installation Check for',
+ 'headers' => '',
+ 'index' => APPLICATION_SETUP_INDEX,
+ 'version' => $this->version,
+ ),
+ 'block' => array('body' => 'index_body')
+ )
+ );
+
+ // Output the final template.
+ $this->outputPage($templates);
+ }
+
+ public function getDatabaseOutput()
+ {
+ $output = '';
+ // Loop through the supported databases array
+ foreach ($this->mSupportedDatabases as $which => $database)
+ {
+ $output .= "
+ <tr>
+ <td> - $which support</td>
+ <td align=\"left\"><strong>{$this->mAvailableDatabases[$which]['status_output']}</strong></td>
+ <td align=\"center\"><strong>". $this->returnStatus($this->mAvailableDatabases[$which]['supported'], $type = 'support') . "</strong></td>
+ </tr>";
+
+ }
+ // Return the html formatted results
+ return $output;
+ }
+
+
+ /**
+ * Function to get the php ini config values
+ * @param string $option The ini setting name to check the status for
+ * @return string The status of the setting either "On" or "OFF"
+ */
+ public function getIniSetting($option)
+ {
+ return (ini_get($option) == '1' ? L('on') : L('off'));
+ }
+
+ /**
+ * Function to get the error messages and generate an error output for the template
+ * @string $heading The value for the Error message heading
+ * The error message is stored in the $_SESSION Global array
+ * $_SESSION[PASS_PHRASE]['page_message']. If there is no value in
+ * this array, then there will be no error message outputed.
+ * @return string $message The message which needs outputting
+ */
+ public function getPageMessage()
+ {
+ // If there is an error
+ if (isset($_SESSION['page_message']) || isset($_SESSION['page_heading']))
+ {
+ $message = '';
+ if (isset($_SESSION['page_heading'])) {
+ $message = '<h1 class="error">' . $_SESSION['page_heading'] . '</h1>';
+ }
+
+ if (isset($_SESSION['page_message'])) {
+ // Get an html formated list
+ $message .= '<div class="box"><div class="shade">' . $this->outputHtmlList($_SESSION['page_message'],'ul') . '</div></div>';
+ }
+
+
+ // Destroy the session value
+ unset($_SESSION['page_heading']);
+ unset($_SESSION['page_message']);
+
+ return $message;
+ }
+ else
+ {
+ return '';
+ }
+ }
+
+
+ /**
+ * Utility function to return a value from a named array or a specified default
+ * @param array &$arr The array to get the values from
+ * @param string $name The name of the key to check the value for
+ * @param string $default The default value if the value is not set with the array
+ * @return string $value The value to be returned
+ */
+ public function getParamValue(&$arr, $name, $default=null )
+ {
+ $value = isset($arr[$name]) ? $arr[$name] : $default;
+ return $value;
+ }
+
+ /**
+ * Function to get a listing of recommended and actual settings
+ * for php.
+ * @param void
+ * @return string $output HTML formatted string.
+ */
+ public function getPhpSettings()
+ {
+ // Array of the setting name, php ini name and the recommended value
+ $test_settings =
+ array(
+ //array ('Safe Mode','safe_mode', L('off')), # removed since PHP5.4
+ array ('File Uploads','file_uploads', L('on')),
+ //array ('Magic Quotes GPC','magic_quotes_gpc', L('off')), # removed since PHP5.4
+ //array ('Register Globals','register_globals', L('off')), # removed since PHP5.4
+ //array ('Output Buffering','output_buffering', L('off')),
+ );
+
+ if (substr(php_sapi_name(), 0, 3) == 'cgi') {
+ $test_settings[] = array('CGI fix pathinfo','cgi.fix_pathinfo', L('on'));
+ }
+
+ $output = '';
+
+ foreach ($test_settings as $recommended)
+ {
+ $actual_setting = $this->getIniSetting($recommended[1]);
+
+ $result = ($actual_setting == $recommended[2] )
+ ? '<span class="green"><strong>' . $recommended[2] . '</strong></span>'
+ : '<span class="red"><strong>' . $actual_setting . '</strong></span>';
+
+ $output .=
+ "
+ <tr>
+ <td>{$recommended[0]}</td><td align=\"center\"><strong>{$recommended[2]}</strong></td><td align=\"center\">{$result}</td>
+ </tr>
+ ";
+ }
+ return $output;
+ }
+
+ public function getReminderDaemonSelection($value)
+ {
+ $selection = '<input type="radio" id="schedyes" name="reminder_daemon" value="1"'.($value==1 ? ' checked="checked"':'').' /> <label for="schedyes">'.L('enable').'</label>';
+ $selection .= '<input type="radio" id="schedno" name="reminder_daemon" value="0"'.($value==0 ? ' checked="checked"':'').' /> <label for="schedno">'.L('disable').'</label>';
+ return $selection;
+ }
+
+
+ /**
+ * Function to check if a particular folder/file is writeable.
+ * @param string $fileSystem Path to check
+ * $return boolean true/false
+ */
+ public function isWriteable($fileSystem)
+ {
+ // Clear the cache
+ clearstatcache();
+
+ // Return the status of the permission
+ return is_writable($fileSystem);
+ }
+
+ /**
+ * Function to Output an Ordered/Un-ordered list from an array. Default list type is un-ordered.
+ * @param array() $list_array An array list of data to be made into a list.
+ * @return string $list An HTML list
+ */
+ public function outputHtmlList($list_array = array(), $list_type = 'ul')
+ {
+ $list = "<$list_type>";
+ foreach ($list_array as $list_item)
+ {
+ $list .= '<li>' . $list_item .'</li>';
+ }
+ $list .= "</$list_type>";
+
+ return $list;
+ }
+
+
+ /**
+ * Function to act on all the actions during Flyspray Setup
+ * The Post variables are extracted for deciding which function to call.
+ */
+ public function processActions()
+ {
+ $action = 'index';
+ $what = '';
+
+ extract($_POST);
+
+ switch($action)
+ {
+ case 'database':
+ $this->displayDatabaseSetup();
+ break;
+
+ case 'administration':
+ // Prepare the required data
+ $required_data =
+ array(
+ 'db_hostname' => array('Database hostname', 'string', true),
+ 'db_type' => array('Database type', 'string', true),
+ 'db_username' => array('Database username', 'string', true),
+ 'db_password' => array('Database password', 'string', false),
+ 'db_name' => array('Database name', 'string', true),
+ 'db_prefix' => array('Table prefix', 'string', false),
+ );
+ if ($data = $this->checkPostedData($required_data, $message = 'Configuration Error'))
+ {
+ // Process the database checks and install tables
+ if ($this->processDatabaseSetup($data))
+ {
+ // Proceed to Administration part
+ $this->displayAdministration();
+ }
+ else
+ {
+ $_POST['action'] = 'database';
+ $this->displayDatabaseSetup();
+ }
+ }
+ else
+ {
+ $_POST['action'] = 'database';
+ $this->displayDatabaseSetup();
+ }
+ break;
+
+ case 'complete':
+ // Prepare the required data
+ $required_data = array(
+ 'db_hostname' => array('Database hostname', 'string', true),
+ 'db_type' => array('Database type', 'string', true),
+ 'db_username' => array('Database username', 'string', true),
+ 'db_password' => array('Database password', 'string', false),
+ 'db_name' => array('Database name', 'string', true),
+ 'db_prefix' => array('Table prefix', 'string', false),
+ 'admin_username' => array('Administrator\'s username', 'string', true),
+ 'admin_realname' => array('Administrator\'s realname', 'string', false),
+ 'admin_password' => array("Administrator's Password must be minimum {$this->mMinPasswordLength} characters long and", 'password', true),
+ 'admin_email' => array('Administrator\'s email address', 'email address', true),
+ 'admin_xmpp' => array('Administrator\'s jabber/xmpp address', 'xmpp address', false),
+ 'syntax_plugin' => array('Syntax', 'option', true),
+ 'reminder_daemon' => array('Reminder Daemon', 'option', false),
+ );
+ if ($data = $this->checkPostedData($required_data, $message = 'Missing config values')) {
+ // Set a page heading in case of errors.
+ $_SESSION['page_heading'] = 'Administration Processing';
+
+ if ($this->processAdminConfig($data)) {
+ $this->displayCompletion($data);
+ } else {
+ $_POST['action'] = 'administration';
+ $this->displayAdministration();
+ }
+ } else {
+ $_POST['action'] = 'administration';
+ $this->displayAdministration();
+ }
+ break;
+
+ default:
+ $this->displayPreInstall();
+ break;
+ }
+ }
+
+
+
+ public function processAdminConfig($data)
+ {
+ // Extract the variables to local namespace
+ extract($data);
+
+ if(!isset($db_password)) {
+ $db_password = '';
+ }
+ if(!isset($admin_xmpp)) {
+ $admin_xmpp = '';
+ }
+ if(!isset($admin_realname)) {
+ $admin_realname = '';
+ }
+
+ if(!isset($syntax_plugin)) {
+ $syntax_plugin = '';
+ }
+
+ $config_intro =
+ "; <?php die( 'Do not access this page directly.' ); ?>
+
+ ; This is the Flysplay configuration file. It contains the basic settings
+ ; needed for Flyspray to operate. All other preferences are stored in the
+ ; database itself and are managed directly within the Flyspray admin interface.
+ ; You should consider putting this file somewhere that isn't accessible using
+ ; a web browser, and editing header.php to point to wherever you put this file.\n";
+ $config_intro = str_replace("\t", "", $config_intro);
+
+ // Create a random cookie salt
+ $cookiesalt = md5(uniqid(mt_rand(), true));
+
+ // check to see if to enable the Reminder Daemon.
+ $daemonise = ( (isset($data['reminder_daemon'])) && ($data['reminder_daemon'] == 1) )
+ ? 1
+ : 0;
+ $db_prefix = (isset($data['db_prefix']) ? $data['db_prefix'] : '');
+
+ $config = array();
+ $config[] = "[database]";
+ $config[] = "dbtype = \"$db_type\" ; Type of database (\"mysql\", \"mysqli\" or \"pgsql\" are currently supported)";
+ $config[] = "dbhost = \"$db_hostname\" ; Name or IP of your database server";
+ $config[] = "dbname = \"$db_name\" ; The name of the database";
+ $config[] = "dbuser = \"$db_username\" ; The user to access the database";
+ $config[] = "dbpass = \"$db_password\" ; The password to go with that username above";
+ $config[] = "dbprefix = \"$db_prefix\" ; The prefix to the {$this->mProductName} tables";
+ $config[] = "\n";
+ $config[] = '[general]';
+ $config[] = "cookiesalt = \"$cookiesalt\" ; Randomisation value for cookie encoding";
+ $config[] = 'output_buffering = "on" ; Available options: "on" or "gzip"';
+ $config[] = 'passwdcrypt = "" ; Available options: "" which chooses best default (coming FS1.0: using crypt/password_hash() with blowfish), "crypt" (auto salted md5), "md5", "sha1" Note: md5 and sha1 are considered insecure for hashing passwords, avoid if possible.';
+ $config[] = "dot_path = \"\" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)";
+ $config[] = "dot_format = \"png\" ; \"png\" or \"svg\"";
+ $config[] = "reminder_daemon = \"$daemonise\" ; Boolean. 0 = off, 1 = on (cron job), 2 = on (PHP).";
+ $config[] = "doku_url = \"http://en.wikipedia.org/wiki/\" ; URL to your external wiki for [[dokulinks]] in FS";
+ $config[] = 'syntax_plugin = "'.$syntax_plugin.'" ; dokuwiki, none, or html';
+ $config[] = "update_check = \"1\" ; Boolean. 0=off, 1=on";
+ $config[] = "\n";
+ $config[] = "[attachments]";
+ $config[] = "zip = \"application/zip\" ; MIME-type for ZIP files";
+ $config[] = "\n";
+ $config[] = "[oauth]";
+ $config[] = "; These are only needed if you plan to use them. You can turn them on in the admin panel.";
+ $config[] = "\n";
+ $config[] = 'github_secret = ""';
+ $config[] = 'github_id = ""';
+ $config[] = 'github_redirect = "YOURDOMAIN/index.php?do=oauth&provider=github"';
+ $config[] = 'google_secret = ""';
+ $config[] = 'google_id = ""';
+ $config[] = 'google_redirect = "YOURDOMAIN/index.php?do=oauth&provider=google"';
+ $config[] = 'facebook_secret = ""';
+ $config[] = 'facebook_id = ""';
+ $config[] = 'facebook_redirect = "YOURDOMAIN/index.php?do=oauth&provider=facebook"';
+ $config[] = 'microsoft_secret = ""';
+ $config[] = 'microsoft_id = ""';
+ $config[] = 'microsoft_redirect = "YOURDOMAIN/index.php"';
+
+ $config_text = $config_intro . implode( "\n", $config );
+
+ if (is_writable('../flyspray.conf.php') && ($fp = fopen('../flyspray.conf.php', "wb")))
+ {
+ fputs($fp, $config_text, strlen($config_text));
+ fclose($fp);
+ $this->mWriteStatus['flyspray.conf.php'] = true;
+ }
+ else
+ {
+ $this->mConfigText = $config_text;
+ $this->mWriteStatus['flyspray.conf.php'] = false;
+ }
+
+
+ // Setting the database for the ADODB connection
+ require_once($this->mAdodbPath);
+
+ # 20160408 peterdd: hack to enable database socket usage with adodb-5.20.3 . For instance on german 1und1 managed linux servers ( e.g. $db_hostname ='localhost:/tmp/mysql5.sock' )
+ if( $db_type=='mysqli' && 'localhost:/'==substr($db_hostname,0,11) ){
+ $dbsocket=substr($db_hostname,10);
+ $db_hostname='localhost';
+ ini_set( 'mysqli.default_socket', $dbsocket );
+ }
+
+ $this->mDbConnection = ADONewConnection(strtolower($db_type));
+ $this->mDbConnection->connect($db_hostname, $db_username, $db_password, $db_name);
+ $this->mDbConnection->setCharSet('utf8');
+
+ // Get the users table name.
+ $users_table = (isset($db_prefix) ? $db_prefix : '') . 'users';
+
+ $sql = "SELECT * FROM $users_table WHERE user_id = '1'";
+
+ // Check if we already have an Admin user.
+ $result = $this->mDbConnection->execute($sql);
+ if ($result)
+ {
+ // If the record exists, we update it.
+ $row = $result->fetchRow();
+ $this->mAdminUsername = $row['user_name'];
+ $this->mAdminPassword = $row['user_pass'];
+ }
+
+ $pwhash= Flyspray::cryptPassword($admin_password);
+ $update_user = "
+ UPDATE
+ $users_table
+ SET
+ user_name = ?,
+ user_pass = ?,
+ email_address = ?,
+ jabber_id = ?,
+ real_name = ?
+ WHERE
+ user_id = '1'";
+
+ $update_params = array($admin_username, $pwhash, $admin_email, $admin_xmpp, $admin_realname);
+
+ $result = $this->mDbConnection->execute($update_user, $update_params);
+
+ if (!$result)
+ {
+ $errorno = $this->mDbConnection->metaError();
+ $_SESSION['page_heading'] = 'Failed to update Admin users details.';
+ $_SESSION['page_message'][] = ucfirst($this->mDbConnection->metaErrorMsg($errorno)) . ': '. $this->mDbConnection->errorMsg($errorno);
+ return false;
+ }
+ else
+ {
+ $this->mAdminUsername = $admin_username;
+ $this->mAdminPassword = $admin_password;
+ }
+
+ return true;
+ }
+
+
+ public function processDatabaseSetup($data)
+ {
+ require_once($this->mAdodbPath);
+
+ // Perform a number of fatality checks, then die gracefully
+ if (!defined('_ADODB_LAYER'))
+ {
+ trigger_error('ADODB Libraries missing or not correct version');
+ }
+
+ # 20160408 peterdd: hack to enable database socket usage with adodb-5.20.3 . For instance on german 1und1 managed linux servers ( e.g. $data['db_hostname'] ='localhost:/tmp/mysql5.sock' )
+ if( strtolower($data['db_type'])=='mysqli' && 'localhost:/'==substr($data['db_hostname'],0,11) ){
+ $dbsocket=substr($data['db_hostname'],10);
+ $data['db_hostname']='localhost';
+ ini_set( 'mysqli.default_socket', $dbsocket );
+ }
+
+ // Setting the database type for the ADODB connection
+ $this->mDbConnection = ADONewConnection(strtolower($data['db_type']));
+ if (!$this->mDbConnection->connect(array_get($data, 'db_hostname'), array_get($data, 'db_username'), array_get($data, 'db_password'), array_get($data, 'db_name')))
+ {
+ $_SESSION['page_heading'] = 'Database Processing';
+ switch($error_number = $this->mDbConnection->metaError())
+ {
+ case '-1':
+ // We are using the unknown error code(-1) because ADOdb library may not have the error defined.
+ // It could be totally some weird error.
+ $_SESSION['page_message'][] = $this->mDbConnection->errorMsg();
+ return false;
+ break;
+
+ case '-24':
+ // Could not connect to database with the hostname provided
+ $_SESSION['page_message'][] = ucfirst($this->mDbConnection->metaErrorMsg($error_number)) . ': ' . ucfirst($this->mDbConnection->ErrorMsg($error_number));
+ $_SESSION['page_message'][] = 'Usually the database host name is "localhost". In some occassions, it maybe an internal ip-address or another host name to your webserver.';
+ $_SESSION['page_message'][] = 'Double check with your hosting provider or System Administrator.';
+ return false;
+ break;
+
+ case '-25':
+ // Database does not exist, try to create one
+ $this->mDbConnection = ADONewConnection(strtolower($data['db_type']));
+ $this->mDbConnection->connect(array_get($data, 'db_hostname'), array_get($data, 'db_username'), array_get($data, 'db_password'));
+ $dict = NewDataDictionary($this->mDbConnection);
+
+ # if possible set correct default character set for mysql.
+ # MySQL below 5.5.3 only supports 1,2,3 byte chars of utf8. But some language's chars or emojis(argh) are defined as 4byte chars
+ $mysqldbcharset='DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci'; # default for mysql for compat
+ if( $data['db_type']=='mysqli' || $data['db_type']=='mysql' ) {
+ $dbinfo=$this->mDbConnection->serverInfo(); # provides 'description' and 'version'
+ if( version_compare($dbinfo['version'], '5.5.3') >=0 ){
+ $mysqldbcharset='DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
+ $this->mDbConnection->setCharSet('utf8mb4');
+ }else{
+ $mysqldbcharset='DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci';
+ $this->mDbConnection->setCharSet('utf8');
+ $_SESSION['page_message'][]='Your MySQL server '.$dbinfo['version'].' < 5.5.3, so database has limited utf8 support (no unicode emojis for instance). Upgrading your MySQL server to 5.5.3 or newer is suggested.';
+ }
+ }else{
+ # postgresql
+ $this->mDbConnection->setCharSet('utf8');
+ }
+
+ $sqlarray = $dict->createDatabase(array_get($data, 'db_name'), array('mysql'=>$mysqldbcharset) );
+
+ if (!$dict->executeSQLArray($sqlarray)) {
+ $_SESSION['page_message'][] = ucfirst($this->mDbConnection->metaErrorMsg($error_number)) . ': ' . ucfirst($this->mDbConnection->ErrorMsg($error_number));
+ $_SESSION['page_message'][] = 'Your database does not exist and could not be created. Either create the database yourself, choose an existing database or
+ use a database user with sufficient permissions to create a database.';
+ return false;
+ } else {
+ $this->mDbConnection->selectDB(array_get($data, 'db_name'));
+ unset($_SESSION['page_heading']);
+ break;
+ }
+
+ case '-26':
+ // Username passwords don't match for the hostname provided
+ $_SESSION['page_message'][] = ucfirst($this->mDbConnection->metaErrorMsg($error_number)) . ': ' . ucfirst($this->mDbConnection->ErrorMsg($error_number));
+ $_SESSION['page_message'][] = "Apparently you haven't set up the right permissions for the database hostname provided.";
+ $_SESSION['page_message'][] = 'Double check the provided credentials or contact your System Administrator for further assistance.';
+ return false;
+ break;
+
+ default:
+ $_SESSION['page_message'][] = "Please verify your username/password/database details (error=$error_number)" . $this->mDbConnection->MetaErrorMsg($error_number);
+ return false;
+ break;
+ }
+ }
+ // Check that table prefix is OK, some DBs don't like it
+ $prefix = array_get($data, 'db_prefix');
+ if (strlen($prefix) > 0 && is_numeric($prefix[0])) {
+ $_SESSION['page_heading'] = 'Database Processing';
+ $_SESSION['page_message'][] = 'The table prefix may not start with a number.';
+ return false;
+ }
+
+ // Setting the Fetch mode of the database connection.
+ $this->mDbConnection->setFetchMode(ADODB_FETCH_BOTH);
+
+ if( $data['db_type']=='mysqli') {
+ $dbinfo=$this->mDbConnection->serverInfo(); # provides 'description' and 'version'
+ if( version_compare($dbinfo['version'], '5.5.3') >=0 ){
+ $this->mDbConnection->setCharSet('utf8mb4');
+ }else{
+ $this->mDbConnection->setCharSet('utf8');
+ }
+ }else{
+ $this->mDbConnection->setCharSet('utf8');
+ }
+
+ //creating the datadict object for further operations
+ $this->mDataDict = NewDataDictionary($this->mDbConnection);
+
+ include_once dirname($this->mAdodbPath) . '/adodb-xmlschema03.inc.php';
+
+ $this->mXmlSchema = new adoSchema($this->mDbConnection);
+
+ // Populate the database with the new tables and return the result (boolean)
+ if (!$this->populateDb($data))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Function to populate the database with the sql constructs which is in an sql file
+ * @param array $data The actual database configuration values
+ * @return boolean
+ */
+
+ public function populateDb($data)
+ {
+ // Check available upgrade scripts, use the script of very latest version
+ $folders = glob_compat(BASEDIR . '/upgrade/[0-9]*');
+ usort($folders, 'version_compare'); // start with lowest version
+ $folders = array_reverse($folders); // start with highest version
+ $sql_file = APPLICATION_PATH . '/setup/upgrade/' . reset($folders) . '/flyspray-install.xml';
+
+ $upgradeInfo = APPLICATION_PATH . '/setup/upgrade/' . reset($folders) . '/upgrade.info';
+ $upgradeInfo = parse_ini_file($upgradeInfo, true);
+
+ // Check if the install/upgrade file exists
+ if (!is_readable($sql_file)) {
+
+ $_SESSION['page_message'][] = 'SQL file required for importing structure and data is missing.';
+ return false;
+ }
+
+ // Extract the variables to local namespace
+ extract($data);
+ if (!isset($db_prefix)) {
+ $db_prefix = '';
+ }
+
+ if(is_numeric($db_prefix)) {
+ $_SESSION['page_message'][] = 'database prefix cannot be numeric only';
+ return false;
+ }
+
+ // Set the prefix for database objects ( before parsing)
+ $this->mXmlSchema->setPrefix( (isset($db_prefix) ? $db_prefix : ''), false);
+ $this->mXmlSchema->parseSchema($sql_file);
+
+ $this->mXmlSchema->executeSchema();
+
+ // Last but not least global prefs update
+ if (isset($upgradeInfo['fsprefs'])) {
+ $existing = $this->mDbConnection->getCol("SELECT pref_name FROM {$db_prefix}prefs");
+ // Add what is missing
+ foreach ($upgradeInfo['fsprefs'] as $name => $value) {
+ if (!in_array($name, $existing)) {
+ $this->mDbConnection->execute("INSERT INTO {$db_prefix}prefs (pref_name, pref_value) VALUES (?, ?)", array($name, $value));
+ }
+ }
+ // Delete what is too much
+ foreach ($existing as $name) {
+ if (!isset($upgradeInfo['fsprefs'][$name])) {
+ $this->mDbConnection->execute("DELETE FROM {$db_prefix}prefs WHERE pref_name = ?", array($name));
+ }
+ }
+ }
+
+ $this->mDbConnection->execute("UPDATE {$db_prefix}prefs SET pref_value = ? WHERE pref_name = 'fs_ver'", array($this->version));
+
+ if (($error_no = $this->mDbConnection->metaError()))
+ {
+ $_SESSION['page_heading'] = 'Database Processing';
+ switch ($error_no)
+ {
+ case '-5':
+ // If there are tables with the same name
+ $_SESSION['page_message'][] = 'Table ' .$this->mDbConnection->metaErrorMsg($this->mDbConnection->metaError());
+ $_SESSION['page_message'][] = 'There probably are tables in the database which have the same prefix you provided.';
+ $_SESSION['page_message'][] = 'It is advised to change the prefix provided or you can drop the existing tables if you don\'t need them. Make a backup if you are not certain.';
+ return false;
+ break;
+
+ case '-1':
+ // We are using the unknown error code(-1) because ADOdb library may not have the error defined.
+ $_SESSION['page_message'][] = $this->mDbConnection->errorMsg();
+ return false;
+ break;
+
+ default:
+ $_SESSION['page_message'][] = $this->mDbConnection->errorMsg() . ': ' . $this->mDbConnection->errorNo();
+ $_SESSION['page_message'][] = 'Unknown error, please notify Developer quoting the error number';
+ return false;
+ break;
+ }
+ }
+
+ return true;
+ }
+
+
+
+
+ /**
+ * Function to return status of boolean results in html format
+ * @param boolean $boolean The status of the result in True/False form
+ * @param string $type The type of html format to return
+ * @return string Depending on the type of format to return
+ */
+ public static function returnStatus($boolean, $type = 'yes')
+ {
+ // Do a switch on the type of status
+ switch($type)
+ {
+ case 'yes':
+ return ($boolean)
+ ? '<span class="green">'.L('yes').'</span>'
+ : '<span class="red">'.L('no').'</span>';
+ break;
+
+ case 'available':
+ return ($boolean)
+ ? '<span class="green">'.L('available').'</span>'
+ : '<span class="red">'.L('missing').'</span>';
+ break;
+
+ case 'writeable':
+ return ($boolean)
+ ? '<span class="green">'.L('writeable').'</span>'
+ : '<span class="red">'.L('unwriteable').'</span>';
+ break;
+
+ case 'on':
+ return ($boolean)
+ ? '<span class="green">'.L('on').'</span>'
+ : '<span class="red">'.L('off').'</span>';
+ break;
+ case 'support':
+ return ($boolean)
+ ? '<span class="green">'.L('supported').'</span>'
+ : '<span class="red">'.L('x').'</span>';
+ break;
+ default:
+ return ($boolean)
+ ? '<span class="green">'.L('true').'</span>'
+ : '<span class="red">'.L('false').'</span>';
+ break;
+ }
+ }
+
+ /**
+ * To verify if a string was empty or not.
+ *
+ * Usually used to validate user input. If the user has inputted empty data
+ * or just blank spaces, we need to trim of such empty data and see if
+ * anything else is left after trimming. If there is data remaining, then
+ * the return value will be greater than 0 else it will be 0 (zero) which
+ * equates to a true/false scenario
+ *
+ * @param string $arg The data to be checked
+ *
+ * @return The result of the check.
+ */
+ public function trimArgs($arg)
+ {
+ return strlen(trim($arg));
+ }
+
+ public function verifyVariableTypes($type, $value)
+ {
+ $message = '';
+ switch($type)
+ {
+ case 'string':
+ return is_string($value);
+ break;
+
+ case 'number':
+ return is_numeric($value);
+ break;
+
+ case 'xmpp address':
+ case 'email address':
+ return filter_var($value, FILTER_VALIDATE_EMAIL);
+ break;
+
+ case 'boolean':
+ return (bool) $value;
+ break;
+
+ case 'password':
+ return (strlen($value) >= $this->mMinPasswordLength);
+ break;
+
+ case 'folder':
+ return is_dir($value);
+ break;
+
+ default:
+ return true;
+ break;
+ }
+ }
+
+ /**
+ * Function to output the templates
+ * @param array $templates The collection of templates with their associated variables
+ *
+ */
+ public function outputPage($templates = array())
+ {
+ if (sizeof($templates) == 0)
+ {
+ trigger_error("Templates not configured properly", E_USER_ERROR);
+ }
+
+ // Define a set of common variables which plugin to the structure template.
+ $page = new Tpl;
+ $body = '';
+
+ // Loop through the templates array to dynamically create objects and assign variables to them.
+ /// XXX: this is not a common way to use our template class, but I didn't want to rewrite
+ /// the whole setup only to change the templating engine
+ foreach($templates as $name => $module)
+ {
+ foreach ($module['vars'] as $var_name => $value) {
+ $page->assign($var_name, $value);
+ }
+
+ if ($name == 'structure') {
+ $page->assign('body', $body);
+ $page->display('structure.tpl');
+ } else {
+ $body .= $page->fetch($module['template']);
+ }
+ }
+ }
+}
+
+//start the installer, it handles the rest inside the class
+new Setup();
diff --git a/setup/lang/de.php b/setup/lang/de.php
new file mode 100644
index 0000000..2d76ae0
--- /dev/null
+++ b/setup/lang/de.php
@@ -0,0 +1,22 @@
+<?php
+$translation=array(
+'available' => 'verfügbar',
+'missing' => 'fehlt',
+'needcomposer' => 'Flyspray benötigt einige zusätzliche Programmbibliotheken, die mit Composer installiert werden können.',
+'no' => 'nein',
+'installcomposer' => 'Starte Composer',
+'performupgrade' => 'Aktualisierung durchführen',
+'precautions' => 'Vorsichtsmaßnahmen',
+'precautionbackup' => 'Erstelle eine Sicherung deiner Datenbank und aller zu Flyspray gehörenden Dateien vor der Aktualisierung',
+'preconditionchecks' => 'Voraussetzungen',
+'proceedtodbsetup' => 'weiter zu den Datenbankeinstellungen',
+'recommended' => 'empfohlen',
+'supported' => 'unterstützt',
+'upgrade' => 'Aktualisierung',
+'upgradepossible' => 'Eine Aktualisierung ist möglich.',
+'versioncompare' => 'Deine derzeitige Flysprayversion ist %s und es kann auf Version %s aktualisiert werden.',
+'writeable' => 'schreibbar',
+'writeaccessconf' => 'Um Flyspray zu aktualisieren muß Lese- und Schreibrecht für flyspray.conf.php existieren.',
+'yes' => 'ja',
+);
+?>
diff --git a/setup/lang/en.php b/setup/lang/en.php
new file mode 100644
index 0000000..e848e9b
--- /dev/null
+++ b/setup/lang/en.php
@@ -0,0 +1,101 @@
+<?php
+$language=array(
+'needcomposer' => 'You need some required libraries installed by Composer.',
+'installcomposer' => 'Run Composer',
+'performupgrade' => 'Perform Upgrade',
+'precautions' => 'Precautions',
+'precautionbackup' => 'Create a backup of your database and all Flyspray related files before performing the upgrade.',
+'preconditionchecks' => 'Precondition checks',
+'upgrade' => 'Upgrade',
+'upgradepossible' => 'Apparently, an upgrade is possible.',
+'versioncompare' => 'Your current version is %s and the version we can upgrade to is %s.',
+'writeaccessconf' => 'In order to upgrade Flyspray correctly it needs to be able to access and write flyspray.conf.php.',
+'adminemail' => 'Admin Email Address',
+'adminxmpp' => 'Admin Jabber Address',
+'adminusername' => 'Admin Username',
+'adminrealname' => 'Admin Realname',
+'adminpassword' => 'Admin Password',
+'slogan' => 'The bug Killer!',
+'progress' => 'Progress',
+'documents' => 'Docs',
+'preinstallcheck' => 'Pre-installation Check',
+'databasesetup' => 'Database Setup',
+'administration' => 'Administration',
+'installflyspray' => 'Install Flyspray',
+'libcheck' => 'PHP and Supported Libraries',
+'libchecktext' => 'To make setup possible, you must have a correct PHP version installed and <strong>at least one</strong> supported database.',
+'recsettings' => 'Recommended Settings',
+'recsettingstext1' => 'These settings are recommended for PHP in order to ensure full compatibility with Flyspray.',
+'recsettingstext2' => 'However, Flyspray will still operate if your settings do not quite match the recommended shown here.',
+'dirandfileperms' => 'Directory and File Permissions',
+'dirandfilepermstext'=> 'In order for Flyspray to function correctly it needs to be able to access or write to certain files or directories. If you see "Unwriteable" you need to change the permissions on the file or directory to allow Flyspray to write to it.',
+'proceedtodbsetup' => 'Proceed to Database Setup',
+'proceedtodbsetuptext'=>'All configurations seems to be in place. You may proceed to the Database Setup page.',
+'library' => 'library',
+'status' => 'status',
+'database' => 'database',
+'recommended' => 'Recommended',
+'actual' => 'Actual',
+'yes' => 'Yes',
+'no' => 'No',
+'explainwhatandwhyheader' => 'Formatting of task descriptions and comments has changed',
+'explainwhatandwhycontent' => 'Previously those installations of Flyspray that didn\'t use dokuwiki formatting engine stored data as plain text. '
+ . 'We now use HTML as the default and can try to add paragraph and line break tags to already existing data entries, so your data retains it\'s '
+ . 'structure. But if your existing data already contains manually added HTML tags something probably goes wrong and you have some corrupted '
+ . 'entries in your database that must be manually fixed. If unsure, answer "No", unless you can examine the situation before proceeding. '
+ . 'If you are fluent in programming with PHP, see also at the end of setup/upgrade.php, look at what it does and possibly modify according to '
+ . 'your needs. ',
+'databaseconfiguration'=>'Database Configuration for ',
+'proceedtoadmin'=>'Proceed to Administration setup',
+'databasehostname'=>'database hostname',
+'databasehostnamehint'=>'Enter the <strong>database hostname</strong> of the server Flyspray is to be installed on. This is usually "localhost" or an IP.',
+'databasetype'=>'database type',
+'databasetypehint'=>'Choose the <strong>database type</strong>. If you have both the choice between MySQL driver and MySQLi driver, use MySQLi. The old MySQL driver (mysql_*) is deprecated in PHP since a long time. If you have MariaDB as MySQL replacement, select MySQLi.',
+'databasename'=>'database name',
+'databasenamehint'=>'Insert a name of an existing database or a new name. If the database not exists, Flyspray tries to create that database for you. Use simple names like "flyspray".',
+'databaseusername'=>'database username',
+'databaseusernamehint'=>'Enter the <strong>database username and password</strong>.
+Flyspray requires that you have a database setup with a username and password to install the database schema.
+If you are not sure about these details, please consult with your administrator or web hosting provider.
+(Local xampp or wampp servers default installs could work with "root" and an empty password field)',
+'databasepassword'=>'database password',
+'databasepasswordhint'=>'',
+'tableprefix'=>'table prefix',
+'tableprefixhint'=>'Optional table prefix to avoid collisions with existing tables. "flyspray_" or "fs_" are good choices.',
+'next'=>'Next',
+'showpassword' => 'Show Password',
+'lgpllicense' => 'LGPL License',
+'installationguide' => 'Install Guide',
+'developermanual' => "Developer's Manual",
+'supported' => 'Supported',
+'inphp' => 'in PHP',
+'available' => 'Available',
+'missing' => 'Missing',
+'writeable' => 'Writeable',
+'unwriteable' => 'Un-writeable',
+'on' => 'ON',
+'off' => 'OFF',
+'x' => 'X',
+'true' => 'True',
+'false' => 'False',
+'directive' => 'Directive',
+'enable' => 'Enable',
+'disable' => 'Disable',
+'administrationsetup' => 'Administration setup',
+'setupapplicationvalue' => 'Setup all the Application values',
+'adminsetuptip1' => 'The Database schema has been populated. Please follow the instructions to complete the Admin configuration.',
+'adminsetuptip2' => '1) Admin <strong>Email, Username, Password</strong> are values for the Administrator of your Flyspray Installation. You can change these values through the administration section of Flyspray.',
+'adminsetuptip3' => 'Choosing a Syntax is a setting that cannot be simply changed and is set at install for the whole Flyspray installation. Choose Text/Dokuwiki if you are unsure which you choose.',
+'syntax' => 'Syntax',
+'syntaxtext' => 'If you are unsure choose Text/Dokuwiki. The switch from dokuwiki to HTML is easy. But a switch from HTML back to dokuwiki or another text format like markdown is nearly impossible without some information loss like deep nested HTML content or formatting.',
+'scheduletitle' => 'You can setup a crontab entry that calls scheduler.php in a time interval. This setting can be switched on/off everytime in Flyspray admin section.',
+'enablescheduling' => 'Enable scheduling',
+'proceedtofinalsetup' => 'Proceed to final Setup',
+'proceedtofinalsetuptext' => 'Proceed to complete Flyspray setup.',
+'installstatus' => 'Install status',
+'congratulations' => 'Congratulations! Flyspray is now installed and ready to use.',
+'removesetupdirectory' => 'Please remove the setup directory now.',
+'viewsite' => 'View Site',
+'proceedtoindex' => 'Proceed to Flyspray index',
+);
+?>
diff --git a/setup/lang/es.php b/setup/lang/es.php
new file mode 100644
index 0000000..2fb4874
--- /dev/null
+++ b/setup/lang/es.php
@@ -0,0 +1,100 @@
+<?php
+$language=array(
+'installFSdevelop' => 'Parece que estás instalando una versión de desarrollo de Flyspray.',
+'needcomposer' => 'Es necesario instalar algunas librerías usando Composer.',
+'installcomposer' => 'Ejecutar Composer',
+'performupgrade' => 'Perform Upgrade',
+'precautions' => 'Advertencias',
+'precautionbackup' => 'Antes de actualizar se recomienta realizar una copia de seguridad de ficheros y base de datos de FlySpray.',
+'preconditionchecks' => 'Precondition checks',
+'upgrade' => 'Actualizar',
+'upgradepossible' => 'Parece que hay una actualización disponible',
+'versioncompare' => 'Your current version is %s and the version we can upgrade to is %s.',
+'writeaccessconf' => 'Para actualizar Flyspray el fichero <pre>flyspray.conf.php</pre> debe tener permisos de escritura',
+'adminemail' => 'Email del administrador',
+'adminusername' => 'Usuario administrador',
+'adminpassword' => 'Contraseña del administrador',
+'slogan' => 'The bug Killer!',
+'progress' => 'Progress',
+'documents' => 'Docs',
+'preinstallcheck' => 'Pre-instalacion',
+'databasesetup' => 'Base de Datos',
+'administration' => 'Aplicación',
+'installflyspray' => 'Instalación Flyspray',
+'libcheck' => 'PHP y módulos requeridos',
+'libchecktext' => 'Para poder completar correctamente la instalación de FlySpray, debes tener instalado una versión de PHP y <strong>al menos una</strong> base de datos compatible.',
+'recsettings' => 'Configuración recomendada',
+'recsettingstext1' => 'A continuación se indica la configuración de PHP recomendada para asegurar la compatibilidad de Flyspray.',
+'recsettingstext2' => 'Nota: No obstante, es posible que FlySpray funcione con una configuración diferente a la recomendada.',
+'dirandfileperms' => 'Permisos en carpetas y ficheros',
+'dirandfilepermstext'=> 'Para poder utilizar Flyspray se necesita tener acceso de escritura en determinados ficheros y carpetas.
+ Si se muestra el mensaje "Sin permiso de escritura" será necesario cambiar los permisos de forma que el servidor de Flyspray pueda escribir en ellos',
+'proceedtodbsetup' => 'Ir a Configuración de Base de Datos',
+'proceedtodbsetuptext'=>'Cuando la configuración sea correcta, continua al siguiente paso para configurar la base de datos.',
+'library' => 'Módulo',
+'status' => 'Estado',
+'database' => 'Base de datos',
+'recommended' => 'Recomendada',
+'actual' => 'Actual',
+'yes' => 'Sí',
+'no' => 'No',
+'explainwhatandwhyheader' => 'Formatting of task descriptions and comments has changed',
+'explainwhatandwhycontent' => 'Previously those installations of Flyspray that didn\'t use dokuwiki formatting engine stored data as plain text. '
+ . 'We now use HTML as the default and can try to add paragraph and line break tags to already existing data entries, so your data retains it\'s '
+ . 'structure. But if your existing data already contains manually added HTML tags something probably goes wrong and you have some corrupted '
+ . 'entries in your database that must be manually fixed. If unsure, answer "No", unless you can examine the situation before proceeding. '
+ . 'If you are fluent in programming with PHP, see also at the end of setup/upgrade.php, look at what it does and possibly modify according to '
+ . 'your needs. ',
+'databaseconfiguration'=>'Configuración de la Base de Datos para ',
+'proceedtoadmin'=>'Ir a la Configuración del Administrador',
+'databasehostname'=>'Servidor ',
+'databasehostnamehint'=>'Introduce el <strong>host del servidor </strong> de base de datos donde se instalará la BDD de Flyspray. Normalmente se usa "localhost" o una dirección IP.',
+'databasetype'=>'Tipo ',
+'databasetypehint'=>'Selecciona el <strong>tipo de base de datos</strong>. Si están disponible las opciones MySQL y MySQLi, seleccione esta última. Idem si su base de datos es MariaDB en lugar de MySQL',
+'databasename'=>'Esquema (nombre)',
+'databasenamehint'=>'Introduce el nombre de la base de datos (esquema). Si no existe Flyspray intentará crearlo por ti. Nota: Usa nombres simples sin espacios, ej. "flyspray".',
+'databaseusername'=>'Usuario',
+'databaseusernamehint'=>'Introduce <strong>el usuario y contraseña de la base de datos</strong>.
+El configurador de Flyspray requiere un usuario que tenga permisos para crear el esquema de base de datos.
+Si no estás seguro, por favor, consulta con tu administrador o proveedor de hosting.
+Nota: Los servidores Xampp o Wampp, por defecto, se instalan con un usuario "root" sin password (vacío)',
+'databasepassword'=>'Contraseña',
+'databasepasswordhint'=>'',
+'tableprefix'=>'Prefijo de las tablas',
+'tableprefixhint'=>'[Opcional] puedes indicar un prefijo para evitar la colisión con tablas existentes. Se recomienda "flyspray_" o "fs_"',
+'next'=>'Next',
+'showpassword' => 'Mostrar Password',
+'lgpllicense' => 'Licencia LGPL',
+'installationguide' => 'Guía de instalación',
+'developermanual' => "Manual del desarrollador",
+'supported' => 'Soportado',
+'inphp' => 'en PHP',
+'available' => 'Sí',
+'missing' => '--',
+'writeable' => 'Escribible',
+'unwriteable' => 'Sin permiso de escritura',
+'on' => 'ON',
+'off' => 'OFF',
+'x' => 'X',
+'true' => 'Verdadero',
+'false' => 'Falso',
+'directive' => 'Directiva',
+'enable' => 'Activo',
+'disable' => 'Desactivado',
+'administrationsetup' => 'Configuración de la aplicación',
+'setupapplicationvalue' => '',
+'adminsetuptip1' => 'El esquema de base de datos de Flyspray ha sido creado. Por favor, sigue las instrucciones para completar la configuración de la aplicación.',
+'adminsetuptip2' => '1) Introduce los valores de <strong>Usuario, correo electrónico y contraseña</strong> del usuario administrador de Flyspray. Puedes cambiar estos valores desde en la sección de administración de Flyspray.',
+'adminsetuptip3' => '2) Selecciona el formado de documentación. Importante: este valor es configurado durante la instalación y no puede ser cambiado posteriormente. Selecciona Text/Dokuwiki si no estás seguro de cual elegir.',
+'syntaxtext' => 'Sintaxis<br>Selecciona Text/docuwiki si no estás seguro de cual elegir. Nota: El cambio de dokuwiki a Html es sencillo. Sin embargo el cambio de Html a dokuwiki u otro formato (como markdown) conlleva pérdidas de información, tanto contenido como formato.',
+'scheduletitle' => 'Recuerda añadir una entrada en el crontab que llame al script "scheduler.php" de forma periódica. El planificador puede ser activado/desactivado desde la sección de administración de Flyspray.',
+'enablescheduling' => 'Activar planificador de tareas',
+'proceedtofinalsetup' => 'Finalizar instalación',
+'proceedtofinalsetuptext' => 'Continua para completar la instalación de Flyspray.',
+'installstatus' => 'Estado de la instalación',
+'congratulations' => 'Enhorabuena! Flyspray está instalado y listo para usar.',
+'removesetupdirectory' => 'Por favor, borra el directorio setup antes de seguir.',
+'viewsite' => 'Finalizar y acceder a Flyspray',
+'proceedtoindex' => 'Ir a la página principal de Flyspray',
+);
+?>
diff --git a/setup/lang/fr.php b/setup/lang/fr.php
new file mode 100644
index 0000000..ffd5091
--- /dev/null
+++ b/setup/lang/fr.php
@@ -0,0 +1,14 @@
+<?php
+$language=array(
+'needcomposer' => 'Vous avez besoin de certaines librairies installées par Composer.',
+'installcomposer' => 'Lancer Composer',
+'performupgrade' => 'Procéder à la mise à jour',
+'precautions' => 'Précautions',
+'precautionbackup' => 'Créez une sauvegarde de votre base de données ainsi que de tous les fichiers de Flyspray avant tout mise à jour.',
+'preconditionchecks' => 'Contrôle des préconditions',
+'upgrade' => 'Mise à jour',
+'upgradepossible' => 'Apparemment la mise à jour n\'est pas possible.',
+'versioncompare' => 'Votre version actuelle est %s et la version vers laquelle nous pouvons mettre à jour est %s.',
+'writeaccessconf' => 'Afin de mettre à jour correctement Flyspray, nous avons besoin d\'accéder en lecture et écriture le fichier flyspray.conf.php.',
+);
+?>
diff --git a/setup/lang/it.php b/setup/lang/it.php
new file mode 100644
index 0000000..9e4f3eb
--- /dev/null
+++ b/setup/lang/it.php
@@ -0,0 +1,100 @@
+<?php
+$language=array(
+'needcomposer' => 'Sono richieste alcune librerie installate da Composer.',
+'installcomposer' => 'Esegui Composer',
+'performupgrade' => 'Esegui Aggiornamento',
+'precautions' => 'Attenzione',
+'precautionbackup' => 'Crea un backup del database e di tutti i file relativi a Flyspray prima di eseguire l\'aggiornamento',
+'preconditionchecks' => 'Verifica dei prerequisiti',
+'upgrade' => 'Aggiorna',
+'upgradepossible' => 'Sembra che sia possibile aggiornare.',
+'versioncompare' => 'La tua versione installata è %s e la versione a cui si può aggiornare è %s.',
+'writeaccessconf' => 'Per essere aggiornato correttamente Flyspray deve avere i diritti di lettura e scrittura su flyspray.conf.php.',
+'adminemail' => 'Indirizzo Email di Admin',
+'adminxmpp' => 'Indirizzo Jabber di Admin',
+'adminusername' => 'Username di Admin',
+'adminrealname' => 'Nome Reale di Admin',
+'adminpassword' => 'Password di Admin',
+'slogan' => 'The bug Killer!',
+'progress' => 'Avanzamento',
+'documents' => 'Documentazione',
+'preinstallcheck' => 'Verifiche Preliminari',
+'databasesetup' => 'Impostazione Database',
+'administration' => 'Amministrazione',
+'installflyspray' => 'Installa Flyspray',
+'libcheck' => 'PHP e Librerie Supportate',
+'libchecktext' => 'Per eseguire l\'installazione devi avere la versione corretta di PHP ed <strong>almeno uno</strong> dei database supportati.',
+'recsettings' => 'Impostazioni Consigliate',
+'recsettingstext1' => 'Queste sono le impostazioni di PHP consigliate per la piena compatibilità con Flyspray.',
+'recsettingstext2' => 'Ad ogni modo, Flyspray può funzionare anche se le tue impostazioni non sono totalmente corrispondenti a quelle consigliate.',
+'dirandfileperms' => 'Permessi per File e Directory',
+'dirandfilepermstext'=> 'Per funzionare correttamente Flyspray deve avere accesso, a volte anche in scrittura, ad alcuni file e directory. Se vedi la scritta "Non scrivibile" devi cambiare i permessi sul file o la directory corrispondente per consentirne l\'accesso a Flyspray.',
+'proceedtodbsetup' => 'Prosegui con il Setup del Database',
+'proceedtodbsetuptext'=>'Tutte le configurazioni sembrano corrette. Puoi proseguire con l\'impostazione del database',
+'library' => 'Libreria',
+'status' => 'Stato',
+'database' => 'database',
+'recommended' => 'Consigliato',
+'actual' => 'Attuale',
+'yes' => 'Sì',
+'no' => 'No',
+'explainwhatandwhyheader' => 'La formattazione nella descrizione dei task e nei commenti è cambiata',
+'explainwhatandwhycontent' => 'Precedentemente, le installazioni di Flyspray che non usavano il motore di formattazione di Dokuwiki memorizzavano i dati come testo semplice. '
+ . 'Adesso utilizziamo HTML come default e proviamo ad aggiungere i tag di paragrafo e a capo ai dati preesitenti così i tuoi dati mantengono '
+ . 'la propria struttura. Se i tuoi dati contengono già dei tag HTML inseriti manualmente, c\'è il rischio che qualcosa possa andare storto e risulti in dati corrotti '
+ . 'nel database che andranno poi corretti manualmente. Se non sei sicuro, rispondi "No", a meno che tu non sia in grado di analizzare la situazione prima di procedere. '
+ . 'Se sai programmare in PHP, esamina la parte finale dello script setup/upgrade.php, guarda cosa fa ed eventualmente modificalo in base alle tue necessità.',
+'databaseconfiguration'=>'Configurazione Database per ',
+'proceedtoadmin'=>'Prosegui con il Setup Amministrazione',
+'databasehostname'=>'Hostname',
+'databasehostnamehint'=>'Inserisci l\'<strong>hostname del database</strong> su cui stai installando Flyspray. Di solito è "localhost" o un indirizzo IP.',
+'databasetype'=>'Tipo DB',
+'databasetypehint'=>'Seleziona il <strong>tipo di database</strong>. Se hai installati entrambi i driver MySQL e MySQLi, <strong>usa MySQLi</strong>. Il vecchio driver MySQL (mysql_*) è stato deprecato in PHP da tempo immemore. Se hai MariaDB al posto di MySQL, seleziona comunque MySQLi.',
+'databasename'=>'Nome del DB',
+'databasenamehint'=>'Inserisci il nome di un database esistente o un nuovo nome. Se il database non esiste, Flyspray tenterà di crearlo. È consigliabile un nome semplice tipo "flyspray".',
+'databaseusername'=>'Nome Utente',
+'databaseusernamehint'=>'Inserisci il <strong>nome utente e la password per il database</strong>.
+Flyspray richiede un database impostato con nome utente e password per installarvi lo schema.
+Se non sei sicuro di questi parametri, chiedi all\'amministratore del database o al tuo hosting provider.
+(A volte le installazioni locali di XAMPP o WAMPP sono configurate di default come "root" con password vuota)',
+'databasepassword'=>'Password',
+'databasepasswordhint'=>'',
+'tableprefix'=>'Prefisso per le Tabelle',
+'tableprefixhint'=>'Prefisso opzionale per le tabelle al fine di evitare conflitti con eventuali tabelle già esistenti. È consigliabile un prefisso semplice come "flyspray_" o "fs_" .',
+'next'=>'Prossimo',
+'showpassword' => 'Mostra la Password',
+'lgpllicense' => 'Licenza LGPL',
+'installationguide' => 'Guida all\'Installazione',
+'developermanual' => "Manuale per Sviluppatori",
+'supported' => 'Supportato',
+'inphp' => 'in PHP',
+'available' => 'Disponibile',
+'missing' => 'Mancante',
+'writeable' => 'Scrivibile',
+'unwriteable' => 'Non scrivibile',
+'on' => 'ON',
+'off' => 'OFF',
+'x' => 'X',
+'true' => 'Vero',
+'false' => 'Falso',
+'directive' => 'Direttiva',
+'enable' => 'Abilita',
+'disable' => 'Disabilita',
+'administrationsetup' => 'Impostazione Amministrazione',
+'setupapplicationvalue' => 'Impostazione di tutti i valori dell\'Applicazione',
+'adminsetuptip1' => 'Lo schema del database è stato configurato. Segui le istruzioni per completare le impostazioni di Amministrazione.',
+'adminsetuptip2' => '1) <strong>Email, Nome Utente e Password</strong> di Admin sono per l\'Amministratore della tua installazione di Flyspray. Puoi modificare questi valori dalla sezione di amministrazione di Flyspray.',
+'adminsetuptip3' => 'L\'impostazione di una Sintassi non è semplice da cambiare una volta installato Flyspray perciò viene impostata globalmente all\'atto dell\'installazione. Se non sei sicuro su cosa scegliere, seleziona Text/Dokuwiki.',
+'syntax' => 'Sintassi',
+'syntaxtext' => 'Se non sei sicuro, seleziona Text/Dokuwiki. Il cambio da Dokuwiki a HTML è semplice. Invece, cambiare successivamente da HTML a Dokuwiki o un altro formato come Markdown, è praticamente impossibile senza la perdita di alcune informazioni come HTML fortemente nidifcati o altra formattazione.',
+'scheduletitle' => 'Puoi impostare un crontab che richiamerà scheduler.php a intervalli di tempo predefiniti. Questa impostazione può essere abilitata e disabilitata a piacere dalla sezione di amministrazione di Flyspray.',
+'enablescheduling' => 'Abilita la schedulazione',
+'proceedtofinalsetup' => 'Prosegui con il Setup finale',
+'proceedtofinalsetuptext' => 'Prosegui per completare l\'installazione di Flyspray.',
+'installstatus' => 'Stato dell\'Installazione',
+'congratulations' => 'Congratulazioni! Flyspray è stato installato ed è pronto all\'uso.',
+'removesetupdirectory' => 'Ricordati di eliminare la directory setup ora.',
+'viewsite' => 'Visualizza il Sito',
+'proceedtoindex' => 'Vai alla pagina principale di Flyspray',
+);
+?>
diff --git a/setup/lang/zh_cn.php b/setup/lang/zh_cn.php
new file mode 100644
index 0000000..9a82dab
--- /dev/null
+++ b/setup/lang/zh_cn.php
@@ -0,0 +1,46 @@
+<?php
+$translation=array(
+'needcomposer' => '请先通过 Composer æ¥å®‰è£…一些ä¾èµ–库。',
+'installcomposer' => 'è¿è¡Œ Composer',
+'upgrade' => 'å‡çº§',
+'adminemail' => '管ç†å‘˜é‚®ç®±',
+'adminusername' => '管ç†å‘˜å¸æˆ·',
+'adminpassword' => '管ç†å‘˜å¯†ç ',
+'progress' => '安装进度',
+'documents' => '文档',
+'preinstallcheck' => 'è¿è¡ŒçŽ¯å¢ƒæ£€æŸ¥',
+'databasesetup' => 'æ•°æ®åº“设置',
+'administration' => '管ç†å‘˜è®¾ç½®',
+'install' => '安装',
+'libcheck' => 'PHP和数æ®åº“',
+'libchecktext' => '在开始设置å‰ï¼Œå¿…须安装好åˆé€‚çš„PHP版本,并且<strong>最少</strong>è¦æ”¯æŒä¸€ç§æ•°æ®åº“。',
+'recsettings' => '推è设置项',
+'recsettingstext1' => '为了使Flyspray获得更好的兼容性,建议å‚考此处的 PHP é…置。',
+'recsettingstext2' => '当然,å³ä½¿ä½ çš„PHP环境é…置和此处ä¸åŒï¼ŒFlyspray ä»ç„¶å¯ä»¥æ­£å¸¸å·¥ä½œã€‚',
+'dirandfileperms' => '目录和文件æƒé™',
+'proceedtodbsetup' => 'å‰å¾€æ•°æ®åº“设置',
+'library' => '库',
+'status' => '状æ€',
+'database' => 'æ•°æ®åº“',
+'recommended' => '推è',
+'actual' => '实际',
+'yes' => '是',
+'no' => 'å¦',
+'databaseconfiguration'=> 'æ•°æ®åº“连接设置,版本:',
+'proceedtoadmin'=> 'å‰å¾€ç®¡ç†å‘˜è®¾ç½®',
+'databasehostname'=> 'æ•°æ®åº“主机',
+'databasehostnamehint' => '请输入<strong>æ•°æ®åº“主机å</strong>,通常å¯èƒ½æ˜¯ "localhost" 或者 IP 地å€ã€‚',
+'databasetype' => 'æ•°æ®åº“类型',
+'databasename' => 'æ•°æ®åº“å',
+'databaseusername' => '用户å',
+'databasepassword' => '密ç ',
+'tableprefix' => '表å‰ç¼€',
+'next' => '下一步',
+'showpassword' => '显示密ç ',
+'lgpllicense' => 'LGPLåè®®',
+'installationguide' => '安装手册',
+'developermanual' => 'å¼€å‘者手册',
+'writeable' => 'å¯å†™',
+'unwriteable' => 'ä¸å¯å†™',
+);
+?>
diff --git a/setup/styles/setup.css b/setup/styles/setup.css
new file mode 100644
index 0000000..00a8014
--- /dev/null
+++ b/setup/styles/setup.css
@@ -0,0 +1,177 @@
+body {
+ margin:0; padding:0;
+ font-family: Verdana, sans-serif;
+ font-size:13px;
+}
+a { text-decoration: none; }
+input:required {border: 1px solid #f90;}
+
+#header {
+ /*background-color:#47617B;*/
+ background-color:#5e5e5e; /* matching grayscale brightness of title.png */
+ margin:0;
+ padding:0;
+ border-bottom:4px solid #5f9729;
+}
+#logo {
+ background-image:url(../images/title.png);
+ background-position: 10px 10px;
+ background-repeat: no-repeat;
+ background-attachment:scroll;
+
+ -webkit-filter: grayscale(100%);
+ -moz-filter: grayscale(100%);
+ -o-filter: grayscale(100%);
+ filter: gray; /* IE6-9 */
+ filter: grayscale(100%); /* grayscale until a transparent logo exists that match colors of default theme */
+}
+#logo h1 {
+ padding:0;
+ margin:0;
+ font-size:2em;
+ line-height: 90px;
+ font-weight:normal;
+}
+#logo h1 a {
+ display: block;
+ height: 90px;
+ color:#fff;
+ border: 0;
+ padding: 0;
+ margin: 0 auto;
+ padding-left:260px;
+}
+#content {max-width:800px;margin-left:auto;margin-right:auto;}
+
+#footer {
+ background-color: #333;
+ padding: 1em;
+ margin:0;
+ border-top: 2px solid #5f9729;
+ text-align: center;
+ color:#ccc;
+}
+#footer p, #footer ul{display:inline-block;}
+#footer li {display:inline-block; list-style:none;}
+#footer a{ display:inline-block; padding:0.5em; color:#fff; }
+#footer a:hover {background-color:#000;}
+
+#stepbar{ margin-top:1em;}
+#stepbar div {
+ box-sizing:border-box;
+ height:36px;
+ font-weight: bold;
+ padding: 10px;
+ background-color:#ddd;
+ margin-right:10px;
+ margin-bottom:6px; /* when it wraps on small displays */
+ display:inline-block;
+ position:relative; /* for the :after rightarrow */
+}
+
+#stepbar .step-on { background-color:#5f9729; color:#fff;}
+#stepbar .done { background-color:#333; color:#ccc;}
+
+/* css arrow to right */
+#stepbar div:after{
+ position:absolute;
+ content:"";
+ width:0;
+ height:0;
+ top:0;
+
+ border-top:18px solid transparent;
+ border-bottom:18px solid transparent;
+ border-left:8px solid #ddd;
+ right:-8px;
+}
+
+#stepbar div::before {
+ position: absolute;
+ content: "";
+ height: 0;
+ width: 0;
+ top: 0;
+
+ border-bottom: 18px solid #ddd;
+ border-top: 18px solid #ddd;
+ border-left: 8px solid transparent;
+ left:-8px;
+}
+#stepbar .step-on::after{border-left-color:#5f9729;}
+#stepbar .step-on::before{border-top-color:#5f9729;border-bottom-color:#5f9729;}
+#stepbar .done::after{border-left-color:#333;}
+#stepbar .done::before{border-top-color:#333;border-bottom-color:#333;}
+#stepbar div:first-child::before{display:none;}
+
+.install {
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: .5em;
+ padding: 10px;
+ border: 1px solid #ddd;
+ max-width: 700px;
+ background-color: #f1f1f1;
+}
+
+.formBlock {
+ border: 1px solid #ddd;
+ padding:5px;
+ margin:5px;
+ background: #f1f1f1;
+ color:#000;
+}
+
+.formBlock td {
+ vertical-align:top;
+ padding-top:1.5em;
+ min-width:150px;
+}
+
+.button {
+ display: block;
+ font-size: 3em;
+ margin: 0.5em auto;
+ cursor:pointer;
+}
+
+.error {
+ color : #c00;
+ font-weight : bold;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.red {
+ color:#F00;
+}
+.orange {
+ color:#FFA500;
+}
+.green {
+ color:#078843;
+}
+
+.install h1 {
+ color:#47617B;
+ text-align:center;
+}
+.install h2 {
+ color:#688EB4;
+}
+
+h1.error {
+ background-image: url(../images/exclamation.png);
+ border:none;
+ text-indent: 45px;
+ background-position: 0px 0px;
+ background-repeat: no-repeat;
+ text-align:left;
+}
+
+.box {
+ border: 5px solid #688EB4;
+ padding:0;
+ margin:0;
+ background-color: #F1F3F5;
+}
diff --git a/setup/styles/theme.css b/setup/styles/theme.css
new file mode 100644
index 0000000..a52f3e3
--- /dev/null
+++ b/setup/styles/theme.css
@@ -0,0 +1,994 @@
+@media screen {
+
+html {
+ margin : 0px;
+ padding : 0px;
+}
+
+body {
+ margin : 0px;
+ padding : 0px;
+ background-color : #F5F9FD;
+ color : #000;
+ font-size : 12px;
+ font-family : Helvetica, Verdana, sans-serif;
+}
+
+img {
+ border : none;
+}
+
+a {
+ text-decoration : none;
+}
+
+a:link {
+ color : #47617b;
+ background-color : transparent;
+ font-weight : bold;
+}
+
+a:visited {
+ color : #47617b;
+ background-color : transparent;
+ font-weight : bold;
+}
+
+a:hover {
+ color : #6395c8;
+ background-color : transparent;
+ text-decoration : none;
+}
+
+/* offsite is only used for the link to the Flyspray homepage */
+a.offsite:after {
+ content : "\2197";
+}
+
+h1 {
+ /*text-align : center;*/
+ font-size : 150%;
+}
+
+/* the main title; h1 alone is also used in the popup windows. */
+h1#title {
+ margin : 0px;
+ padding : 1ex 0px;
+ background-color : #47617b;
+ background-image : url("title.png");
+ background-repeat : no-repeat;
+ height : 45px;
+}
+
+/* there is a span element within the h1 element to enable displaying
+ an image only and blanking out the span */
+h1 span {
+ display : none;
+}
+
+h2 { /* Heading for link to task list and details */
+ margin : 0px;
+ padding : 0px;
+ font-size : 150%;
+}
+
+h4 {
+ padding : 0px;
+ margin-bottom : -10px;
+}
+
+p {
+ margin : 1ex 0px;
+ padding : 0.5ex 0.5em;
+}
+
+form {
+ margin : 0px;
+ padding : 0px;
+ display : inline;
+}
+
+form p {
+ margin : 0px;
+ padding : 0px;
+}
+
+td {
+ vertical-align : top;
+}
+
+th {
+ vertical-align : top;
+}
+
+input, textarea, select, button {
+ background-color : #eef7ff;
+ color : #03008f;
+ border : 1px ridge #000000;
+ margin : 2px;
+ font-size : 100%;
+}
+
+textarea {
+ width : 95%;
+}
+
+/* labels are used nearly in every form */
+label {
+ text-align : right;
+ display : block;
+ margin-right : 8px;
+ font-weight : bold;
+ white-space : nowrap;
+/* padding : 3px 0 0 0; */
+}
+
+label.inline {
+ display : inline;
+}
+
+/* div.admin and p.admin is used for areas that only administrators can use
+ p.admin for instance for adding comments
+ table.admin serves the same purpose */
+/* table.login is the framework for the login form at the bottom of a page */
+/* table.userlist is found in the user&groups section */
+div.admin, p.admin, table.admin, table.userlist {
+ border : 1px solid #ccc;
+ margin : 1em 0px;
+ padding : 0px;
+ margin-bottom : 30px;
+ background-color : #e6eef6;
+ color : #000000;
+ font-family : Helvetica, Verdana, sans-serif;
+}
+
+div#loginbox {
+ border : 1px solid #ccc;
+ margin : 0em 0px;
+ padding : 0px;
+ background-color : #e6eef6;
+ height : 22px;
+}
+
+div#loginbox p {
+ padding : 0px;
+ margin : 0px;
+}
+
+div#loginbox em {
+ display : none;
+}
+
+div#loginbox label, div#loginbox input.maintext, div#loginbox input.mainbutton {
+ display : inline;
+ padding : 0 3px 0 3px;
+/* height : 14px; */
+}
+
+div#loginbox a {
+ padding : 0 0 0 13px;
+ text-align : center;
+}
+
+div#loginbox span#links {
+ position : absolute;
+ top : 5px;
+ right : 5px;
+}
+
+div#loginbox span#links a {
+ color : #ffffff;
+ background-color : transparent;
+ font-weight : normal;
+}
+
+/* The paragraph containing the main menu */
+p#menu {
+ border : 1px solid #ccc;
+ margin : 0em 0px;
+ padding : 4px 0 0 0;
+ background-color : #e6eef6;
+ height : 18px;
+ vertical-align : baseline;
+}
+
+p#menu a {
+ height : 18px;
+ padding : 3px 1px 3px 20px;
+ background-repeat : no-repeat;
+ background-position : 1px 1px;
+ font-size : 12px;
+ font-weight : normal;
+ vertical-align : baseline;
+}
+
+p#menu a:hover {
+ color : #6395c8;
+ background-color : transparent;
+ text-decoration : none;
+}
+
+div#anonopen {
+ position : absolute;
+ top : 30px;
+ right : 5px;
+ padding : 0px;
+}
+
+div#anonopen a {
+ color : #ffffff;
+ background-color : transparent;
+ padding : 0px;
+ font-weight : normal;
+}
+
+/* Links in menus */
+
+a#newtasklink {
+ background-image : url(menu/newtask.png);
+}
+
+a#reportslink {
+ background-image : url(menu/reports.png);
+}
+
+a#editmydetailslink {
+ background-image : url(menu/editmydetails.png);
+}
+
+a#lastsearchlink {
+ background-image : url(menu/search.png);
+}
+
+a#logoutlink {
+ background-image : url(menu/logout.png);
+}
+
+a#optionslink {
+ background-image : url(menu/options.png);
+}
+
+a#projectslink {
+ background-image : url(menu/projectprefs.png);
+}
+
+/* For pending admin requests */
+a#attention {
+ color : #ff0000;
+ background-color : #fff000;
+}
+
+table.userlist td, table.userlist th {
+ border : 1px solid #ccc;
+}
+
+/* p#showtask contains the form to search for a specific task number */
+p#showtask {
+ text-align : right;
+ width : 40%;
+ margin-left : auto;
+ position : relative;
+ top : -2em;
+}
+
+/* The paragraph containing the search form */
+div#search {
+ margin-bottom : 1em;
+ border : 1px solid #000000;
+ background-color : #e6eef6;
+}
+
+div#search p {
+ padding : 0px;
+ margin : 0px;
+}
+
+div#search span#date_d {
+ min-width : 20px;
+ margin : 0px 5px 0px 5px;
+ padding : 0px 5px 0px 5px;
+ display : inline;
+ background-color : #eef7ff;
+ color : #03008f;
+ border : 1px ridge #000000;
+ margin : 2px;
+ font-size : 100%;
+}
+
+div#search select {
+ background-color : #eef7ff;
+ color : #03008f;
+ border : 1px ridge black;
+}
+
+div#search em {
+ float : left;
+ display : block;
+ padding : 0.5ex;
+ height : 2.5em;
+}
+
+div#tasklist {
+ width : 100%;
+ border : 1px solid #000000;
+ color : #000000;
+ background-color : #e6eef6;
+}
+
+/* The table listing tasks on the main page */
+div#tasklist table {
+ width : 100%;
+ border-spacing : 0px;
+ border-collapse : collapse;
+}
+
+div#tasklist table th {
+ text-align : left;
+ padding : 2px 1em 2px 2px;
+ border-bottom : 1px solid #cccccc;
+}
+
+div#tasklist table td {
+ border-bottom : 1px solid #CCC;
+ padding : 2px 1ex 2px 2px;
+}
+
+div#tasklist table td.taskid, div#tasklist table th.taskid {
+ text-align : center;
+ padding : 2px 1ex;
+}
+
+div#tasklist table td.taskdate {
+ text-align : left;
+ white-space : nowrap;
+}
+
+div#tasklist table td.progress {
+ vertical-align : middle;
+ text-align : center;
+}
+
+table#pagenumbers {
+ width : 100%;
+}
+
+table#pagenumbers td#taskrange {
+ padding : 0px 0px 0px 5px;
+ }
+
+table#pagenumbers td#numbers {
+ padding : 0px 5px 0px 0px;
+ text-align : right;
+}
+
+/* table.list is any table holding lists like resolutions, ... */
+table.list th, table.list td {
+ padding-right : 2em;
+}
+
+table.list label, p#showtask label, p.admin label, form#formaddrelatedtask label {
+ display : inline;
+}
+
+/* Area containing all details to a given task */
+div#taskdetails {
+ margin : 2em 0px 1em 0px;
+ background-color : #e6eef6;
+ border : 1px solid #000000;
+}
+
+div#taskdetails h2 {
+ font-size : 100%;
+ padding : 0.3ex 1ex;
+ font-weight : normal;
+}
+
+table.taskdetails td {
+}
+
+/* Task details are listed within a table */
+div#taskdetails table {
+ width : 90%;
+}
+
+div#taskdetails table th {
+ text-align : left;
+ font-weight : bold;
+}
+
+/* div#content is a generic container for anything but the title and
+ the footer paragraph */
+div#content {
+/* border : 2px solid #948d94;*/
+ margin : 5px;
+ padding : 5px;
+/* color : #000000;
+ background-color : #ffffff;
+ z-index : -1;*/
+}
+
+div#content div, div#content fieldset {
+/* -moz-border-radius : 13px; */
+}
+
+div#content div h2 {
+/* -moz-border-radius : 15px; */
+}
+
+/* Powered by Flyspray */
+p#footer {
+ margin : 0em 0em;
+ padding : 5px;
+ background-color : #47617b;
+ border : 3px double white;
+ color : #ffffff;
+ text-align : center;
+ margin-top : 15px;
+ clear : both;
+}
+
+p#footer a:link {
+ color : #ffffff;
+ background-color : transparent;
+}
+
+p#footer a:visited {
+ color : #ffffff;
+ background-color : transparent;
+}
+
+p#footer a:hover {
+ color : #ffffff;
+ background-color : transparent;
+}
+
+/* used solely for several browsers do not support [type="submit"]
+ selectors. These are buttons and buttons only used by the admin */
+input.adminbutton, input.mainbutton, button {
+ background-color : #838ab5;
+ color : #ffffff;
+ background-image : url(button.png);
+ background-repeat : repeat-x;
+ border : 1px ridge #000000;
+ font-weight : bold;
+}
+
+input.adminbutton:hover, input.mainbutton:hover, button:hover {
+ cursor : pointer;
+}
+
+/* div.redirectmessage is used in modify.inc.php when you change things and are redirected
+ to the index or details page */
+div.redirectmessage {
+ border : 1px solid #CCC;
+ background-color : #e6eef6;
+ padding : 1ex;
+ text-align : center;
+}
+
+/* form#registernewuser, form#chgpassword, form#newgroup are used in
+ the popup windows */
+form#chgpassword h1, form#registernewuser h1, form#newgroup h1 {
+ font-size : 110%;
+ margin : 2px;
+ padding : 0px;
+}
+
+/* .buttons used for table cells containing buttons */
+.buttons {
+ text-align : center;
+}
+
+form#registernewuser strong, form#newgroup strong {
+ color : red;
+}
+
+
+
+p#tabs {
+ margin : 0px;
+ padding : 0px;
+}
+
+p#tabs a {
+ background-image : url("tab-notactive.png");
+ background-repeat : no-repeat;
+ background-position : top left;
+ background-color : #cad0d7;
+ border-right : 1px solid #93989D;
+}
+
+p#tabs a.tabactive {
+ background-image : url("tab-active.png");
+ background-repeat : no-repeat;
+ background-position : top left;
+ background-color : #e6eef6;
+ border-right : 1px solid #CCC;
+ padding-bottom : 2px;
+}
+
+div.tabentries {
+ background-color : #e6eef6;
+ margin : 0px 0px 1em 0px;
+ border : 1px solid #CCC;
+ min-height : 5em;
+}
+
+.tabentry {
+ margin : 2px 1em;
+ border-bottom : 1px solid #ccc;
+}
+
+/* This is the division that keeps the buttons to modify comments
+ (delete, edit) */
+div.modifycomment {
+ float: right;
+ width : 20em;
+ text-align : right;
+}
+
+div.modifycomment p {
+ display : inline;
+ margin : 0px;
+ padding : 0px;
+}
+
+/* The tabs containing the links to comments, attachments, ...
+ in details.php */
+p#tabs a {
+ margin : 2px 0px;
+ padding : 0px 1em;
+ white-space : nowrap;
+}
+
+/* separators between the links in the tabs */
+p#tabs small {
+ display : none;
+}
+
+/* p.unregistered holds links to open new task when you're unregistered */
+p.unregistered {
+ margin : 0px;
+ padding : 0px;
+}
+
+/* Some generic classes; severity classes are used for colour
+ indication of severities
+ IE can't do hover on anything but A elements, so there is a
+ script in /js/ie_hover.js that fixes this with
+ javascript. That javascript only checks background-color and
+ color. If you want to change other properties during the hover
+ you'll have to adjust the javascript
+ */
+.severity1 {
+ background-color : #fff5dd;
+ color : #000000;
+ height : 1%;
+}
+
+.severity1:hover {
+ background-color : #ffe9b4;
+ color : #000000;
+ cursor : pointer;
+}
+
+.severity2 {
+ background-color : #ecdbb7;
+ color : #000000;
+ height : 1%;
+}
+
+.severity2:hover {
+ background-color : #efca80;
+ color : #000000;
+ cursor : pointer;
+}
+
+.severity3 {
+ background-color : #ecd0b7;
+ color : #000000;
+ height : 1%;
+}
+
+.severity3:hover {
+ background-color : #edb98a;
+ color : #000000;
+ cursor : pointer;
+}
+
+.severity4 {
+ background-color : #ffd5d1;
+ color : #000000;
+ height : 1%;
+}
+
+.severity4:hover {
+ background-color : #ffb2ac;
+ color : #000000;
+ cursor : pointer;
+}
+
+.severity5 {
+ background-color : #f3a29b;
+ color : #000000;
+ height : 1%;
+}
+
+.severity5:hover {
+ background-color : #f3867e;
+ color : #000000;
+ cursor : pointer;
+}
+
+/* .fineprint is merely used for the details about when a task has
+ been created and changed (in the details pages) */
+div#fineprint {
+ font-size : smaller;
+ border-bottom : 1px solid #cccccc;
+ margin : 0px 0px 10px 5px;
+ padding-bottom : 10px;
+ height : 1%;
+}
+
+table.history
+{
+ width : 100%;
+ margin : 1em 0px 1em 0px;
+ padding : 0px 1em 0px 1em;
+ background-color : #e6eef6;
+ color : black;
+ font-family : Helvetica, Verdana, Sans-Serif;
+}
+
+table.history td {
+ border-bottom : 1px solid #ccc;
+ border-left : 1px solid #ccc;
+ padding-left : 5px;
+}
+
+table.history td.taskid {
+ text-align : center;
+ padding : 2px 1ex;
+}
+
+div#intromessage {
+ margin : 1px;
+ margin-top : -20px;
+ margin-bottom : 10px;
+}
+
+map#formselecttasks {
+ margin : 0em 0px 0em 0px;
+}
+
+a.closedtasklink {
+ text-decoration: line-through;
+}
+
+div#taskfields1 {
+ float: left;
+ width: 49%;
+ border-right: 1px solid #cccccc;
+ margin-bottom: 8px;
+}
+
+div#taskfields2 {
+ float: left;
+ width: 49%;
+ margin-bottom: 8px;
+}
+
+div#taskfields1 table td {
+ width: 50%;
+}
+
+div#taskfields2 table td {
+ width: 50%;
+}
+
+div#taskdetailsfull {
+ clear : both;
+ width : 99%;
+ margin-top : 15px;
+ padding : 15px 0px 15px 8px;
+ border-top : 1px solid #cccccc;
+ border-bottom : 1px solid #cccccc;
+ text-align : left;
+}
+
+div#taskdetailsfull label {
+ text-align : left;
+}
+
+div#deps {
+ padding : 8px;
+ width : 98%;
+ border-bottom : 1px solid #cccccc;
+ min-height : 50px;
+ float : left;
+}
+
+div#taskdeps {
+ float: left;
+ width: 45%;
+ margin-bottom: 8px;
+}
+
+div#taskblocks {
+ float: left;
+ width: 50%;
+}
+
+div#actionbuttons {
+ clear : both;
+ padding : 5px;
+}
+
+div#actionbuttons a {
+ background-image : url(button.png);
+ background-repeat : repeat-x;
+ color : #ffffff;
+ background-color : transparent;
+ border : 1px solid #000000;
+ margin : 0px 3px 0px 3px;
+ /* padding values: top, right, bottom, left */
+ padding : 2px 5px 2px 5px;
+}
+
+div#actionbuttons a#hideclosetask {
+ position : absolute;
+ top : 3px;
+ right : 3px;
+ width : 16px;
+ height : 16px;
+ background-image : url(cancel.png);
+ background-repeat : no-repeat;
+ color : #000000;
+ background-color : transparent;
+ border : none;
+}
+
+div#actionbuttons a#hideclosetask:hover {
+ position : absolute;
+ top : 3px;
+ right : 3px;
+ width : 16px;
+ height : 16px;
+ background-image : url(cancel-over.png);
+ background-repeat : no-repeat;
+ color : #000000;
+ background-color : transparent;
+ border : none;
+}
+
+div#massopsactions {
+ padding : 0px 0px 0px 3px;
+}
+
+div#closeform {
+ visibility : hidden;
+ position : absolute;
+ background-color : #e6eef6;
+ color : #000000;
+ border : 3px ridge #000000;
+ padding : 5px 30px 5px 5px;
+ margin-top : 5px;
+ display : block;
+ width : 300px;
+ height : auto;
+}
+
+div#closeform textarea {
+ width : 100%;
+ height : 100px;
+}
+
+div.denyform {
+ visibility : hidden;
+ position : absolute;
+ right : 12px;
+ background-color : #e6eef6;
+ color : #000000;
+ border : 3px ridge #000000;
+ padding : 5px 30px 5px 5px;
+ margin-top : 5px;
+ display : block;
+ width : 300px;
+ height : auto;
+}
+
+div#denyform textarea {
+ width : 100%;
+ height : 100px;
+}
+
+fieldset.admin {
+ margin-top : 15px;
+ background-color : #e6eef6;
+ color : #000000;
+ border : 1px solid #000000;
+}
+
+fieldset.admin legend {
+ border-bottom : 1px solid #000000;
+ border-right : 1px solid #000000;
+ padding : 2px;
+ font : 120% Helvetica, Verdana, sans-serif;
+ margin : 5px;
+ background-color : #f5f9fd;
+ color : #000000;
+}
+
+div#errorbar {
+ width : 100%;
+ height : 20px;
+ color : yellow;
+ background-color : red;
+ font-weight : bold;
+ position : fixed;
+ bottom : 0px;
+ background-image : url(frown.png);
+ background-repeat : no-repeat;
+ background-position : 5px 0px;
+ padding-left : 30px;
+}
+
+div#successbar {
+ width : 100%;
+ height : 20px;
+ color : #ffffff;
+ background-color : green;
+ font-weight : bold;
+ position : fixed;
+ bottom : 0px;
+ background-image : url(smile.png);
+ background-repeat : no-repeat;
+ background-position : 5px 0px;
+ padding-left : 30px;
+}
+
+div#toolboxmenu {
+ width : auto;
+ float : left;
+ margin-right : 25px;
+ margin-top : 15px;
+}
+
+div#toolboxmenu small {
+ display : none;
+}
+
+div#toolboxmenu a {
+ display : block;
+ border : 1px solid #000000;
+ padding-top : 1em;
+ padding-bottom : 1em;
+ width : 120px;
+ text-align : center;
+}
+
+div#toolboxmenu a:hover {
+ color : #6395c8;
+ background-color : #d7e9ff;
+}
+
+div#toolbox {
+ margin-left : 150px;
+ min-height : 350px;
+}
+
+a.grouptitle {
+ font-size : 16px;
+}
+
+/* The new tabs stuff */
+/* colors */
+#submenu a, div.tab { color: #000000; background-color: #e6eef6; }
+
+#submenu a.active {
+ color : #000000;
+ border-bottom-color : #cdc;
+}
+
+#submenu a:hover {
+ color : #000000;
+}
+
+#submenu a { color: #999;text-decoration: none; }
+#submenu a.active { z-index:5;}
+
+/* margins */
+#submenu, #submenu * { margin: 0; padding: 0; }
+#submenu a, div.tab { border: solid 1px black; }
+#submenu a { margin: auto auto -1px 1ex; padding: 2px 1ex; }
+* html #submenu { margin-bottom: -1em; }
+div.tab { margin: 0; padding: 1ex; padding-bottom: 1cm; margin-right: 1ex; margin-bottom: 10px;}
+
+/* flow */
+#submenu li { display: inline; width: 0; height: 0; }
+#submenu a { display: block; float: left; }
+div.tab h2 { display: none; }
+div.tab { clear: left; }
+* html .tab div.clear { clear: none; height: 14em; }
+
+div#permslink {
+ position : absolute;
+ top : 5px;
+ right : 5px;
+ color : #ffffff;
+ background-color : transparent;
+}
+
+div#permslink a {
+ color : #ffffff;
+ background-color : transparent;
+}
+
+div#permissions {
+ visibility : hidden;
+ position : absolute;
+ top : 25px;
+ right : 5px;
+ padding : 5px;
+ margin-top : none;
+ overflow : auto;
+ z-index : 5;
+}
+
+div#permissions table {
+ color : #000000;
+ background-color : #ffffff;
+ border : 1px dotted #000000;
+}
+
+div#permissions table td {
+ border : 0px;
+}
+
+div#fileupload {
+ margin : 20px 0px 20px 0px;
+}
+
+span#pendingreq {
+ margin : 0px 0px 0px 9px;
+ display: : block;
+ clear : both;
+ font-size : 120%;
+ font-weight : bold;
+ float : right;
+ color : red;
+ background-color : yellow;
+}
+
+/* container for the next/previous links in the task details */
+span#navigation {
+ position : absolute;
+ right : 1em;
+}
+
+span#navigation a#next {
+ padding-right : 20px;
+ background-image : url(next.png);
+ background-repeat : no-repeat;
+ background-position : right;
+}
+
+span#navigation a#prev {
+ padding-left : 20px;
+ background-image : url(prev.png);
+ background-repeat : no-repeat;
+ background-position : left;
+}
+
+
+
+/* End of the @screen section */
+}
+
+@media print {
+p#menu {
+ display : none;
+}
+
+/* End of the @print section */
+}
diff --git a/setup/templates/administration.tpl b/setup/templates/administration.tpl
new file mode 100644
index 0000000..58909b0
--- /dev/null
+++ b/setup/templates/administration.tpl
@@ -0,0 +1,79 @@
+<div>
+<form action="index.php" method="post" name="database_form">
+ <?php echo $message; ?>
+ <h1><?= eL('administrationsetup') ?></h1>
+ <h2><?= eL('setupapplicationvalue') ?></h2>
+ <div class="installBlock">
+<script type="text/javascript">
+function ShowHidePassword(id) {
+ if(document.getElementById(id).type=="text") {
+ document.getElementById(id).type="password";
+ } else {
+ document.getElementById(id).type="text";
+ }
+}
+</script>
+
+ <p><?= L('adminsetuptip1') ?></p>
+ <p><?= L('adminsetuptip2') ?></p>
+ <p><?= L('adminsetuptip3') ?></p>
+
+ <table class="formBlock">
+ <tr>
+ <td style="text-align:right"><?= eL('adminusername') ?></td>
+ <td style="text-align:left"><input class="inputbox" type="text" name="admin_username" value="<?php echo $admin_username; ?>" required="required" size="30" /></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td style="text-align:right"><?= eL('adminrealname') ?></td>
+ <td style="text-align:left"><input class="inputbox" type="text" name="admin_realname" value="<?php echo $admin_realname; ?>" size="30" /></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td style="text-align:right"><?= eL('adminemail') ?></td>
+ <td style="text-align:left"><input class="inputbox" type="text" name="admin_email" value="<?php echo $admin_email; ?>" required="required" size="30" /></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td style="text-align:right"><?= eL('adminxmpp') ?></td>
+ <td style="text-align:left"><input class="inputbox" type="text" name="admin_xmpp" value="<?php echo $admin_xmpp; ?>" size="30" /></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td style="text-align:right"><?= eL('adminpassword') ?></td>
+ <td style="text-align:left"><input class="inputbox" type="password" name="admin_password" id="admin_password" value="<?php echo $admin_password; ?>" required="required" size="30" /></td>
+ <td style="text-align:left"><label for="showpassword"><?= eL('showpassword') ?></label><input type="checkbox" onclick="ShowHidePassword('admin_password')" id="showpassword"></td>
+ </tr>
+ <tr>
+ <td style="text-align:right"><?= eL('syntax') ?></td>
+ <td style="text-align:left">
+ <select name="syntax_plugin">
+ <option value="dokuwiki">Text/Dokuwiki</option>
+ <option value="none">HTML/none</option>
+ <option value="html">HTML/CKEditor</option>
+ </select>
+ </td>
+ <td style="text-align:left"><?= L('syntaxtext') ?></td>
+ </tr>
+ <?php if ($daemonise): ?>
+ <tr>
+ <td style="text-align:right" title="<?= eL('scheduletitle') ?>"><?= eL('enablescheduling') ?></td>
+ <td style="text-align:center"><?php echo $daemonise; ?></td>
+ </tr>
+ <?php endif; ?>
+ </table>
+
+ <input type="hidden" name="db_type" value="<?php echo Filters::noXSS($db_type); ?>" />
+ <input type="hidden" name="db_hostname" value="<?php echo Filters::noXSS($db_hostname); ?>" />
+ <input type="hidden" name="db_username" value="<?php echo Filters::noXSS($db_username); ?>" />
+ <input type="hidden" name="db_password" value="<?php echo Filters::noXSS($db_password); ?>" />
+ <input type="hidden" name="db_name" value="<?php echo Filters::noXSS($db_name); ?>" />
+ <input type="hidden" name="db_prefix" value="<?php echo Filters::noXSS($db_prefix); ?>" />
+
+ <p><?= eL('proceedtofinalsetuptext') ?></p>
+ <input type="hidden" name="action" value="complete" />
+ <button class="button" type="submit" name="next" value="<?= eL('next') ?> >>" ><?= eL('proceedtofinalsetup') ?></button>
+
+ </div>
+</form>
+</div>
diff --git a/setup/templates/complete_install.tpl b/setup/templates/complete_install.tpl
new file mode 100644
index 0000000..2def1e6
--- /dev/null
+++ b/setup/templates/complete_install.tpl
@@ -0,0 +1,50 @@
+<div>
+<form action="<?php echo Filters::noXSS($site_index); ?><?php echo Filters::noXSS($complete_action); ?>" method="post" name="database_form">
+ <h1><?php echo Filters::noXSS(L('installstatus')); ?></h1>
+ <h2><?php echo Filters::noXSS(L('congratulations')); ?></h2>
+ <div class="installBlock">
+ <p class="error"><?php echo Filters::noXSS(L('removesetupdirectory')); ?></p>
+ <?php if(!$config_writeable): ?>
+ <table class="formBlock">
+ <tr>
+ <td>
+ The configuration file is not writeable. You will have to upload the following
+ code manually. Click in the textarea to highlight all of the code. Copy and
+ paste the contents into the flyspray.conf.php file available in the base of
+ <?php echo Filters::noXSS($product_name); ?> installation.
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <textarea class="inputbox" rows="10" cols="38" name="configcode" onclick="javascript:this.form.configcode.focus();this.form.configcode.select();" ><?php echo htmlspecialchars($config_text); ?></textarea>
+ </td>
+ </tr>
+ </table>
+ <h3>flyspray.conf.php NOT writeable</h3>
+ <p>
+ To complete setup, copy and paste the contents of the textarea box into flyspray.conf.php
+ This file resides in the base of your <?php echo Filters::noXSS($product_name); ?> installation.
+ </p>
+ <?php endif; ?>
+ <?php
+ if ($admin_username && $admin_password): ?>
+ <h3>Administration Login Details</h3>
+ <p>
+ <strong>Username: <?php echo Filters::noXSS($admin_username); ?></strong><br />
+ <strong>Password: <?php echo Filters::noXSS($admin_password); ?></strong>
+ </p>
+ <?php endif; ?>
+
+ <?php if ($admin_username && $admin_password): ?>
+ <input type="hidden" name="return_to" value="./" />
+ <input type="hidden" name="do" value="authenticate" />
+ <input type="hidden" name="user_name" value="<?php echo Filters::noXSS($admin_username); ?>" />
+ <input type="hidden" name="password" value="<?php echo Filters::noXSS($admin_password); ?>" />
+ <?php endif; ?>
+ <p><?php echo Filters::noXSS(L('proceedtoindex')); ?></p>
+ <input type="hidden" name="remember_login" value="1" />
+ <button class="button" type="submit" name="next" value="next"><?php echo Filters::noXSS(L('viewsite')); ?></button>
+
+ </div>
+</form>
+</div>
diff --git a/setup/templates/database.tpl b/setup/templates/database.tpl
new file mode 100644
index 0000000..f5e29a8
--- /dev/null
+++ b/setup/templates/database.tpl
@@ -0,0 +1,47 @@
+<div>
+<form action="index.php" method="post" name="database_form">
+ <?php echo $message; ?>
+
+ <h1><?= eL('databasesetup') ?></h1>
+ <h2><?= eL('databaseconfiguration') ?><?php echo Filters::noXSS($version); ?></h2>
+ <div class="installBlock">
+ <table class="formBlock" style="width:auto">
+ <tr>
+ <td><?= eL('databasehostname') ?></td>
+ <td align="left"><input required="required" type="text" name="db_hostname" value="<?php echo Filters::noXSS($db_hostname); ?>" /></td>
+ <td><?= L('databasehostnamehint') ?></td>
+ </tr>
+ <tr>
+ <td><?= eL('databasetype') ?></td>
+ <td><select name="db_type">
+ <?php echo tpl_options(array_combine(array_map(create_function('$x', 'return $x[2];'), $databases), array_keys($databases)), $db_type); ?>
+ </select>
+ </td>
+ <td><?= L('databasetypehint') ?></td>
+ </tr>
+ <tr>
+ <td><?= eL('databasename') ?></td>
+ <td align="left"><input required="required" type="text" name="db_name" value="<?php echo Filters::noXSS($db_name); ?>" /></td>
+ <td><?= L('databasenamehint') ?></td>
+ </tr>
+ <tr>
+ <td><?= eL('databaseusername') ?></td>
+ <td align="left"><input required="required" type="text" name="db_username" value="<?php echo Filters::noXSS($db_username); ?>" /></td>
+ <td rowspan="2"><?= L('databaseusernamehint') ?></td>
+ </tr>
+ <tr>
+ <td><?= eL('databasepassword') ?></td>
+ <td align="left"><input type="password" name="db_password" value="<?php echo Filters::noXSS($db_password); ?>" /></td>
+ </tr>
+ <tr>
+ <td><?= eL('tableprefix') ?></td>
+ <td align="left"><input type="text" maxlength="10" name="db_prefix" value="<?php echo Filters::noXSS($db_prefix); ?>" /></td>
+ <td><?= L('tableprefixhint') ?></td>
+ </tr>
+ </table>
+
+ <input type="hidden" name="action" value="administration" />
+ <button class="button" type="submit" name="next" value="next"><?= eL('proceedtoadmin') ?></button>
+ </div>
+</form>
+</div>
diff --git a/setup/templates/pre_install.tpl b/setup/templates/pre_install.tpl
new file mode 100644
index 0000000..3c65e43
--- /dev/null
+++ b/setup/templates/pre_install.tpl
@@ -0,0 +1,124 @@
+<div>
+ <?php echo Filters::noXSS($message); ?>
+
+ <h1><?php echo Filters::noXSS(L('preinstallcheck')); ?></h1>
+ <h2><?php echo Filters::noXSS(L('libcheck')); ?></h2>
+ <div class="installBlock">
+ <p><?php echo L('libchecktext'); ?></p>
+ <table class="formBlock">
+ <tr>
+ <td class="heading"><?php echo Filters::noXSS(L('library')); ?></td>
+ <td class="heading"><?php echo Filters::noXSS(L('status')); ?></td>
+ <td class="heading">&nbsp;</td>
+ </tr>
+ <tr>
+ <td>PHP <?php echo PHP_VERSION; ?> >= <?php echo Filters::noXSS($required_php); ?></td>
+ <td align="left"><b><?php echo $php_output; ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>XML Extension</td>
+ <td align="left"><b><?php echo Setup::ReturnStatus($xmlStatus); ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>cURL Library</td>
+ <td align="left"><b><?php echo Setup::ReturnStatus(extension_loaded('curl'), 'yes'); ?></b></td>
+ <td>required if you want allow Oauth2 authentications</td>
+ </tr>
+ <tr>
+ <td>GD Library</td>
+ <td align="left"><b><?php echo Setup::ReturnStatus(extension_loaded('gd'), 'yes'); ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>Exif Library</td>
+ <td align="left"><b><?php echo Setup::ReturnStatus(extension_loaded('exif'), 'yes'); ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>SAPI (<?php echo Filters::noXSS(php_sapi_name()); ?>)</td>
+ <td align="left"><b><?php echo Setup::ReturnStatus($sapiStatus, 'support'); ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td class="heading"><?php echo Filters::noXSS(L('database')); ?></td>
+ <td class="heading"><?php echo Filters::noXSS(L('inphp')); ?></td>
+ <td class="heading" style="text-align:center"><?php echo Filters::noXSS($product_name); ?></td>
+ </tr>
+ <?php echo $database_output; ?>
+ </table>
+
+ <?php if (!$sapiStatus): ?>
+ <p><strong>CGI server API is not supported</strong>. Consider upgrading to FastCGI, otherwise you have to add
+ <code>force_baseurl = "http://yourflyspray/"</code> manually to flyspray.conf.php after setup.
+ </p>
+ <?php endif; ?>
+ </div>
+
+ <h2><?php echo Filters::noXSS(L('recsettings')); ?></h2>
+ <div class="installBlock">
+ <p><?php echo Filters::noXSS(L('recsettingstext1')); ?></p>
+ <p><?php echo Filters::noXSS(L('recsettingstext2')); ?></p>
+ <table class="formBlock">
+ <tr>
+ <td class="heading"><?php echo Filters::noXSS(L('directive')); ?></td>
+ <td class="heading"><?php echo Filters::noXSS(L('recommended')); ?></td>
+ <td class="heading"><?php echo Filters::noXSS(L('actual')); ?></td>
+ </tr>
+ <?php echo $php_settings; ?>
+ </table>
+ </div>
+
+ <h2><?php echo Filters::noXSS(L('dirandfileperms')); ?></h2>
+ <div class="installBlock">
+ <p><?php echo Filters::noXSS(L('dirandfilepermstext')); ?></p>
+ <table class="formBlock">
+ <tr>
+ <td valign="top">../flyspray.conf.php</td>
+ <td align="left"><b><?php echo $config_output; ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td valign="top">../cache</td>
+ <td align="left"><b><?php echo $cache_output; ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td valign="top">../attachments</td>
+ <td align="left"><b><?php echo $att_output; ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td valign="top">../avatars</td>
+ <td align="left"><b><?php echo $ava_output; ?></b></td>
+ <td>&nbsp;</td>
+ </tr>
+ </table>
+
+ <?php if (!$config_status): ?>
+ <p>
+ The installer has detected that the <strong>flyspray.conf.php</strong> file is not
+ writeable. Please make it writeable by the web-server user or world writeable to
+ proceed with the setup. Alternatively if you wish to proceed, the installer will
+ make available the contents of the configuration file at the end of the setup. You
+ will then have to manually copy and paste the contents into the configuration file
+ located at <strong><?php echo APPLICATION_PATH . DIRECTORY_SEPARATOR . 'flyspray.conf.php'; ?></strong>.
+ </p>
+ <?php endif; ?>
+
+ <?php if (!$status) { ?>
+ <p>
+ You seem to have problems with the Pre-install configuration. Once you have fixed the
+ problem, please refresh the page to be able to proceed to the next stage of
+ <?php echo Filters::noXSS($product_name); ?> setup.
+ </p>
+ <?php }else { ?>
+ <p><?php echo Filters::noXSS(L('proceedtodbsetuptext')); ?></p>
+ <?php } ?>
+ <form action="index.php" method="post" name="adminForm">
+ <input type="hidden" name="action" value="database" />
+ <button name="next" type="submit" class="button" value="next" <?php echo Filters::noXSS(tpl_disableif(!$status)); ?> ><?php echo Filters::noXSS(L('proceedtodbsetup')); ?></button>
+ </form>
+ </div>
+</div>
diff --git a/setup/templates/structure.tpl b/setup/templates/structure.tpl
new file mode 100644
index 0000000..84a7491
--- /dev/null
+++ b/setup/templates/structure.tpl
@@ -0,0 +1,56 @@
+<!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">
+<head>
+<title><?php echo Filters::noXSS($title . ' ' . $product_name); ?></title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<link rel="stylesheet" href="styles/setup.css" type="text/css" media="screen" />
+<?php echo $headers; ?>
+</head>
+<body>
+<div id="header">
+ <div id="logo">
+ <h1><a href="<?php echo Filters::noXSS($index); ?>" title="Flyspray - <?= eL('slogan') ?>"><?= eL('slogan') ?></a></h1>
+ </div><!-- End of logo -->
+</div><!-- End of header -->
+<div id="content">
+ <div id="stepbar" title="<?= eL('progress') ?>">
+ <!-- <div><?= eL('progress') ?></div> -->
+ <div class="done">3rd party libs</div>
+ <div<?php
+ if(!isset($_POST['action'])){
+ echo ' class="step-on"';
+ } elseif( $_POST['action'] == 'database' || $_POST['action'] == 'administration' || $_POST['action'] == 'complete' ){
+ echo ' class="done"';
+ } ?>><?= eL('preinstallcheck') ?></div>
+ <div<?php
+ if(isset($_POST['action'])){
+ if( $_POST['action'] == 'database' ){
+ echo ' class="step-on"';
+ } elseif( $_POST['action'] == 'administration' || $_POST['action'] == 'complete' ){
+ echo ' class="done"';
+ }
+ }
+ ?>><?= eL('databasesetup') ?></div>
+ <div<?php
+ if(isset($_POST['action'])){
+ if($_POST['action'] == 'administration'){
+ echo ' class="step-on"';
+ } elseif($_POST['action'] == 'complete'){
+ echo ' class="done"';
+ }
+ } ?>><?= eL('administration') ?></div>
+ <div<?php echo (isset($_POST['action']) && ($_POST['action'] == 'complete')) ? ' class="step-on"' : ''; ?>><?php echo Filters::noXSS(L('installflyspray')); ?></div>
+ </div>
+ <?php echo $body; ?>
+</div><!-- End of content -->
+<div id="footer">
+ <ul id="docs">
+ <li><a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" title="<?= eL('lgpllicense') ?>" target="_blank"><?= eL('lgpllicense') ?></a></li>
+ <li><a href="https://www.flyspray.org/manual/" title="<?= eL('installationguide') ?>" target="_blank"><?= eL('installationguide') ?></a></li>
+ </ul>
+ <p>Flyspray <?php echo Filters::noXSS($version); ?><br />
+ Copyright 2004-<?php echo Filters::noXSS(date('Y')); ?> &copy; The Flyspray team. All rights reserved.
+ </p>
+</div><!-- End of footer -->
+</body>
+</html>
diff --git a/setup/templates/upgrade.tpl b/setup/templates/upgrade.tpl
new file mode 100644
index 0000000..68e1619
--- /dev/null
+++ b/setup/templates/upgrade.tpl
@@ -0,0 +1,76 @@
+<!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">
+<head>
+<title><?php echo Filters::noXSS($title); ?> Flyspray</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<link rel="stylesheet" href="styles/setup.css" type="text/css" media="screen" />
+</head>
+<body>
+<div id="header">
+ <div id="logo">
+ <h1><a href="<?php echo Filters::noXSS($index); ?>" title="Flyspray - The bug Killer!"><?php echo L('upgrade'); ?></a></h1>
+ </div><!-- End of logo -->
+</div><!-- End of header -->
+<div id="content">
+<form action="upgrade.php" method="post" onsubmit="document.getElementById('upgradebutton').disabled = true;return true;" >
+ <input type="hidden" name="upgrade" value="1" />
+ <div class="install">
+ <h2><?php echo L('preconditionchecks'); ?></h2>
+ <p><?php echo sprintf(L('versioncompare'), $installed_version, $short_version); ?></p>
+ <p><?php echo L('writeaccessconf'); ?></p>
+ <div class="installBlock">
+ <table class="formBlock">
+ <tr>
+ <td valign="top">../<?php echo Filters::noXSS(basename(CONFIG_PATH)); ?></td>
+ <td align="left"><b><?php if ($checks['config_writable']): ?><span class="green">writeable</span><?php else: ?><span class="red">not writeable</span><?php endif; ?></b></td>
+ </tr>
+ <tr>
+ <td valign="top">Database connection</td>
+ <td align="left"><b><?php if ($checks['db_connect']): ?><span class="green">OK</span><?php else: ?><span class="red">Failed</span><?php endif; ?></b></td>
+ </tr>
+ </table>
+ </div>
+<?php if (!$upgrade_possible): ?>
+ <p style="clear:both">Apparently, an upgrade is not possible. <?php echo Filters::noXSS($todo); ?></p>
+ <p style="text-align:center"><a class="button" href="../">Back to Home</a></p>
+<?php else: ?>
+ <p><?php echo L('upgradepossible'); ?></p>
+ <h2><?php echo L('precautions'); ?></h2>
+ <p><?php echo L('precautionbackup'); ?></p>
+
+ <?php if (isset($upgrade_options)): ?>
+ <h2>Upgrade options</h2>
+ <p><?php echo $upgrade_options; ?></p>
+ <?php endif; ?>
+
+ <?php if ($ask_for_conversion): ?>
+ <h2><?php echo L('explainwhatandwhyheader'); ?></h2>
+ <p><?php echo L('explainwhatandwhycontent'); ?></p>
+ <p><input name="yes_please_do_convert" type="checkbox"/><?php echo eL('yes'); ?>
+ <input name="no_please_do_not_convert" type="checkbox"/><?php echo eL('no'); ?></p>
+ <?php endif; ?>
+
+ <h2><?php echo L('performupgrade'); ?></h2>
+ <p><input name="upgrade" id="upgradebutton" class="button" value="<?php echo eL('performupgrade'); ?>" type="submit" /></p>
+ <?php if (isset($done)): ?>
+ <div class="green"><?php echo join('<br />',$upgradelog); ?></div>
+ <p>If all went fine:</p>
+ <ol>
+ <li>Delete setup directory or restrict access to this directory by a htaccess rule.</li>
+ <li><a href="../" class="button" style="padding:4px;background-color:#fff;border-radius:3px;border:1px solid #000;display:inline-block">Back to Overview</a></li>
+ </ol>
+ <?php else: ?>
+ <p>Info: This may take a while depending on the upgrade type and your database size: from a few seconds up to doing a coffee break.</p>
+ <p>We do not get upgrade progress feedback while the upgrading process is running, but if it takes longer it can be a sign of problem.</p>
+ <?php endif; ?>
+<?php endif; ?>
+ </div><!-- End of install -->
+</form>
+</div><!-- End of content -->
+<div id="footer">
+ <p>Flyspray <?php echo Filters::noXSS($fs->version); ?><br />
+ Copyright 2004-<?php echo Filters::noXSS(date('Y')); ?> &copy; The Flyspray team. All rights reserved.
+ </p>
+</div><!-- End of footer -->
+</body>
+</html>
diff --git a/setup/upgrade.php b/setup/upgrade.php
new file mode 100644
index 0000000..46d108b
--- /dev/null
+++ b/setup/upgrade.php
@@ -0,0 +1,643 @@
+<?php
+// +----------------------------------------------------------------------
+// | PHP Source
+// +----------------------------------------------------------------------
+// | Copyright (C) 2006 by Cristian Rodriguez R <judas.iscariote@flyspray.org>
+// | Copyright (C) 2007 by Florian Florian Schmitz <floele@flyspray.org>
+// +----------------------------------------------------------------------
+// |
+// | Copyright: See COPYING file that comes with this distribution
+// +----------------------------------------------------------------------
+//
+
+@set_time_limit(0);
+//do it fast damn it
+ini_set('memory_limit', '64M');
+
+// define basic stuff first.
+define('IN_FS', 1);
+define('BASEDIR', dirname(__FILE__));
+define('APPLICATION_PATH', dirname(BASEDIR));
+define('OBJECTS_PATH', APPLICATION_PATH . '/includes');
+require_once OBJECTS_PATH . '/class.flyspray.php';
+define('CONFIG_PATH', Flyspray::get_config_path(APPLICATION_PATH));
+
+define('TEMPLATE_FOLDER', BASEDIR . '/templates/');
+$conf = @parse_ini_file(CONFIG_PATH, true) or die('Cannot open config file at ' . CONFIG_PATH);
+
+$borked = str_replace( 'a', 'b', array( -1 => -1 ) );
+
+if(!isset($borked[-1])) {
+ die("Flyspray cannot run here, sorry :-( \n PHP 4.4.x/5.0.x is buggy on your 64-bit system; you must upgrade to PHP 5.1.x\n" .
+ "or higher. ABORTING. (http://bugs.php.net/bug.php?id=34879 for details)\n");
+}
+
+require_once OBJECTS_PATH . '/fix.inc.php';
+require_once OBJECTS_PATH . '/class.gpc.php';
+require_once OBJECTS_PATH . '/i18n.inc.php';
+
+# fake objects for load_translation()
+class user{var $infos=array();}; class project{var $id=0;};
+$user=new user; $proj=new project;
+load_translations();
+
+// Use composer autoloader
+require dirname(__DIR__) . '/vendor/autoload.php';
+
+// Initialise DB
+require_once dirname(__DIR__) . '/vendor/adodb/adodb-php/adodb.inc.php';
+require_once dirname(__DIR__) . '/vendor/adodb/adodb-php/adodb-xmlschema03.inc.php';
+
+$db = new Database;
+$db->dbOpenFast($conf['database']);
+
+$webdir = dirname(htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'utf-8'));
+$baseurl = rtrim(Flyspray::absoluteURI($webdir),'/\\') . '/' ;
+
+// ---------------------------------------------------------------------
+// Application Web locations
+// ---------------------------------------------------------------------
+$fs = new Flyspray();
+
+define('APPLICATION_SETUP_INDEX', Flyspray::absoluteURI());
+define('UPGRADE_VERSION', Flyspray::base_version($fs->version));
+
+define('DOMAIN_HASH', md5($_SERVER['SERVER_NAME'] . (int) $_SERVER['SERVER_PORT']));
+define('CACHE_DIR', Flyspray::get_tmp_dir() . DIRECTORY_SEPARATOR . DOMAIN_HASH);
+
+// Get installed version
+$sql = $db->query('SELECT pref_value FROM {prefs} WHERE pref_name = ?', array('fs_ver'));
+$installed_version = $db->fetchOne($sql);
+
+$page = new Tpl;
+$page->assign('title', 'Upgrade ');
+$page->assign('short_version', UPGRADE_VERSION);
+
+if (!isset($conf['general']['syntax_plugin']) || !$conf['general']['syntax_plugin'] || $conf['general']['syntax_plugin'] == 'none') {
+ $page->assign('ask_for_conversion', true);
+} else {
+ $page->assign('ask_for_conversion', false);
+}
+
+//cleanup
+//the cache dir
+@rmdirr(sprintf('%s/cache/dokuwiki', APPLICATION_PATH));
+
+// ---------------------------------------------------------------------
+// Now the hard work
+// ---------------------------------------------------------------------
+
+// Find out which upgrades need to be run
+$folders = glob_compat(BASEDIR . '/upgrade/[0-9]*');
+usort($folders, 'version_compare'); // start with lowest version
+
+if (Post::val('upgrade')) {
+ $uplog=array();
+ $uplog[]="Start database transaction";
+ $db->dblink->StartTrans();
+ fix_duplicate_list_entries(true);
+ foreach ($folders as $folder) {
+ if (version_compare($installed_version, $folder, '<=')) {
+ $uplog[]="Start $installed_version to $folder";
+ $uplog[]= execute_upgrade_file($folder, $installed_version);
+ $installed_version = $folder;
+ $uplog[]="End $installed_version to $folder";
+ }
+ }
+
+ # maybe as Filter: $out=html2wiki($input, 'wikistyle'); and $out=wiki2html($input, 'wikistyle') ?
+ # No need for any filter, because dokuwiki format wouldn't be touched anyway. But maybe ask the user
+ # first and explain that html-formatting is now used instead of plain text on installations that didn't
+ # use dokuwiki format. Then, adding paragraph tags and line breaks might enhance readability.
+ // For testing, do not use yet, have to discuss this one with others.
+ if ((!isset($conf['general']['syntax_plugin']) || !$conf['general']['syntax_plugin'] || $conf['general']['syntax_plugin'] == 'none') && Post::val('yes_please_do_convert')) {
+ convert_old_entries('tasks', 'detailed_desc', 'task_id');
+ convert_old_entries('projects', 'intro_message', 'project_id');
+ convert_old_entries('projects', 'default_task', 'project_id');
+ convert_old_entries('comments', 'comment_text', 'comment_id');
+ $page->assign('conversion', true);
+ } else {
+ $page->assign('conversion', false);
+ }
+
+ // we should be done at this point
+ $db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?', array($fs->version, 'fs_ver'));
+
+ // Fix the sequence in tasks table for PostgreSQL.
+ if ($db->dblink->dataProvider == 'postgres') {
+ $rslt = $db->query('SELECT MAX(task_id) FROM {tasks}');
+ $maxid = $db->fetchOne($rslt);
+ // The correct sequence should normally have a name containing at least both the table and column name in this format.
+ $rslt = $db->query('SELECT relname FROM pg_class WHERE NOT relname ~ \'pg_.*\' AND relname LIKE \'%' . $conf['database']['dbprefix'] . 'tasks_task_id%\' AND relkind = \'S\'');
+ if ($db->countRows($rslt) == 1) {
+ $seqname = $db->fetchOne($rslt);
+ $db->query('SELECT setval(?, ?)', array($seqname, $maxid));
+ }
+ }
+ // */
+ $db->dblink->completeTrans();
+ $installed_version = $fs->version;
+ $page->assign('done', true);
+ $page->assign('upgradelog', $uplog);
+}
+
+function execute_upgrade_file($folder, $installed_version)
+{
+ global $db, $page, $conf;
+ // At first the config file
+ $upgrade_path = BASEDIR . '/upgrade/' . $folder;
+ new ConfUpdater(CONFIG_PATH, $upgrade_path);
+
+ $upgrade_info = parse_ini_file($upgrade_path . '/upgrade.info', true);
+ // dev version upgrade?
+ if ($folder == Flyspray::base_version($installed_version)) {
+ $type = 'develupgrade';
+ } else {
+ $type = 'defaultupgrade';
+ }
+
+ // Next a mix of XML schema files and PHP upgrade scripts
+ if (!isset($upgrade_info[$type])) {
+ die('#1 Bad upgrade.info file.');
+ }
+
+ ksort($upgrade_info[$type]);
+ foreach ($upgrade_info[$type] as $file) {
+ if (substr($file, -4) == '.php') {
+ require_once $upgrade_path . '/' . $file;
+ }
+
+ if (substr($file, -4) == '.xml') {
+ $schema = new adoSchema($db->dblink);
+ $xml = file_get_contents($upgrade_path . '/' . $file);
+ // $xml = str_replace('<table name="', '<table name="' . $conf['database']['dbprefix'], $xml);
+ // Set the prefix for database objects ( before parsing)
+ $schema->setPrefix($conf['database']['dbprefix'], false);
+ $schema->parseSchemaString($xml);
+ $schema->executeSchema(null, true);
+ }
+ }
+
+ // Last but not least global prefs update
+ if (isset($upgrade_info['fsprefs'])) {
+ $sql = $db->query('SELECT pref_name FROM {prefs}');
+ $existing = $db->fetchCol($sql);
+ // Add what is missing
+ foreach ($upgrade_info['fsprefs'] as $name => $value) {
+ if (!in_array($name, $existing)) {
+ $db->query('INSERT INTO {prefs} (pref_name, pref_value) VALUES (?, ?)', array($name, $value));
+ }
+ }
+ // Delete what is too much
+ foreach ($existing as $name) {
+ if (!isset($upgrade_info['fsprefs'][$name])) {
+ $db->query('DELETE FROM {prefs} WHERE pref_name = ?', array($name));
+ }
+ }
+ }
+
+ $db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?', array(basename($upgrade_path), 'fs_ver'));
+ #$page->assign('done', true);
+ return "Write ".basename($upgrade_path)." into table {prefs} fs_ver in database";
+}
+
+ /**
+ * Delete a file, or a folder and its contents
+ *
+ * @author Aidan Lister <aidan@php.net>
+ * @version 1.0.3
+ * @link http://aidanlister.com/repos/v/function.rmdirr.php
+ * @param string $dirname Directory to delete
+ * @return bool Returns TRUE on success, FALSE on failure
+ * @license Public Domain.
+ */
+
+ function rmdirr($dirname)
+ {
+ // Sanity check
+ if (!file_exists($dirname)) {
+ return false;
+ }
+
+ // Simple delete for a file
+ if (is_file($dirname) || is_link($dirname)) {
+ return unlink($dirname);
+ }
+
+ // Loop through the folder
+ $dir = dir($dirname);
+ while (false !== $entry = $dir->read()) {
+ // Skip pointers
+ if ($entry == '.' || $entry == '..') {
+ continue;
+ }
+ // Recurse
+ rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
+ }
+ // Clean up
+ $dir->close();
+ return rmdir($dirname);
+ }
+
+
+class ConfUpdater
+{
+ var $old_config = array();
+ var $new_config = array();
+
+ /**
+ * Reads the existing config file and updates it
+ * @param string $location
+ * @access public
+ * @return bool
+ */
+ function ConfUpdater($location, $upgrade_path)
+ {
+ if (!is_writable($location)) {
+ return false;
+ }
+
+ $this->old_config = parse_ini_file($location, true) or die('Aborting: Could not open config file at ' . $location);
+ $this->new_config = parse_ini_file($upgrade_path . '/flyspray.conf.php', true);
+ // Now we overwrite all values of the *default* file if there is one in the existing config
+ array_walk($this->new_config, array($this, '_merge_configs'));
+ // save custom attachment definitions
+ $this->new_config['attachments'] = $this->old_config['attachments'];
+ # first try to keep an existing oauth config on upgrades
+ $this->new_config['oauth'] = $this->old_config['oauth'];
+
+ $this->_write_config($location);
+ }
+
+ /**
+ * Callback function, merges config values
+ * @param array $settings
+ * @access private
+ * @return array
+ */
+ function _merge_configs(&$settings, $group)
+ {
+ foreach ($settings as $key => $value) {
+ if (isset($this->old_config[$group][$key])) {
+ $settings[$key] = $this->old_config[$group][$key];
+ }
+ // Upgrade to MySQLi if possible
+ if ($key == 'dbtype' && strtolower($settings[$key]) == 'mysql' && function_exists('mysqli_connect')) {
+
+ //mysqli is broken on 64bit systems in versions < 5.1 do not use it, tested, does not work.
+ if (php_uname('m') == 'x86_64' && version_compare(phpversion(), '5.1.0', '<')) {
+ continue;
+ }
+
+ $settings[$key] = 'mysqli';
+ }
+ //no matter what, change the randomization key on each upgrade as an extra security improvement.
+ if($key === 'cookiesalt') {
+ $settings[$key] = md5(uniqid(mt_rand(), true));
+ }
+ }
+ }
+
+ /**
+ * Writes the new config file to a given $location
+ * @param string $location
+ * @access private
+ */
+ function _write_config($location)
+ {
+ $new_config = "; <?php die( 'Do not access this page directly.' ); ?>\n\n";
+ foreach ($this->new_config as $group => $settings) {
+ $new_config .= "[{$group}]\n";
+ foreach ($settings as $key => $value) {
+ if (is_array($value)) {
+ foreach ($value as $_key => $_value) {
+ $new_config .= sprintf('%s="%s"', "{$key}[{$_key}]", addslashes($_value)). "\n";
+ }
+ } else {
+ $new_config .= sprintf('%s="%s"', $key, addslashes($value)). "\n";
+ }
+ }
+ $new_config .= "\n";
+ }
+
+ $fp = fopen($location, 'wb');
+ fwrite($fp, $new_config);
+ fclose($fp);
+ }
+}
+
+function postgresql_adodb() {
+ if (class_exists('ReflectionClass')) {
+ require_once dirname(__DIR__) . '/vendor/adodb/adodb-php/adodb-datadict.inc.php';
+ require_once dirname(__DIR__) . '/vendor/adodb/adodb-php/datadict/datadict-postgres.inc.php';
+ $refclass = new ReflectionClass('ADODB2_postgres');
+ $refmethod = $refclass->getMethod('ChangeTableSQL');
+ $implclass = $refmethod->getDeclaringClass();
+ if ($implclass->name === 'ADODB2_postgres') {
+ return true;
+ }
+ return false;
+ } else {
+ // Can't even do the test, hope the user is able to handle the situation him/serself.
+ return true;
+ }
+}
+
+$checks = $todo = array();
+$checks['version_compare'] = version_compare($installed_version, UPGRADE_VERSION) === -1;
+$checks['config_writable'] = is_writable(CONFIG_PATH);
+$checks['db_connect'] = (bool) $db->dblink;
+$checks['installed_version'] = version_compare($installed_version, '0.9.6') === 1;
+$todo['config_writable'] = 'Please make sure that the file at ' . CONFIG_PATH . ' is writable.';
+$todo['db_connect'] = 'Connection to the database could not be established. Check your config.';
+$todo['version_compare'] = 'No newer version than yours can be installed with this upgrader.';
+$todo['installed_version'] = 'An upgrade from Flyspray versions lower than 0.9.6 is not possible.
+ You will have to upgrade manually to at least 0.9.6, the scripts which do that are included in all Flyspray releases <= 0.9.8.';
+
+if ($conf['database']['dbtype'] == 'pgsql') {
+ $checks['postgresql_adodb'] = (bool) postgresql_adodb();
+ $todo['postgresql_adodb'] = 'You have a version of ADOdb that does not contain overridden version of method ChangeTableSQL for PostgreSQL. '
+ . 'Please copy setup/upgrade/1.0/datadict-postgres.inc.php to '
+ . 'vendor/adodb/adodb-php/datadict/ before proceeding with the upgrade process.';
+}
+
+$upgrade_possible = true;
+foreach ($checks as $check => $result) {
+ if ($result !== true) {
+ $upgrade_possible = false;
+ $page->assign('todo', $todo[$check]);
+ break;
+ }
+}
+
+if (isset($upgrade_info['options'])) {
+ // piece of HTML which adds user input, quick and dirty*/
+ $page->assign('upgrade_options', implode('', $upgrade_info['options']));
+}
+
+$page->assign('index', APPLICATION_SETUP_INDEX);
+$page->uses('checks', 'fs', 'upgrade_possible');
+$page->assign('installed_version', $installed_version);
+
+$page->display('upgrade.tpl');
+
+// Functions for checking and fixing possible duplicate entries
+// in database for those tables that now have a unique index.
+
+function fix_duplicate_list_entries($doit=true) {
+ global $db,$uplog;
+
+ // Categories need a bit more thinking. A real life example from
+ // my own database: A big project originally written (horrible!)
+ // in VB6, that I ported to .NET -environment. Categories:
+ // BackOfficer (main category)
+ // -> Reports (subcategory - should be allowed)
+ // BackOfficer.NET (main category)
+ // -> Reports (subcategory - should be allowed)
+ // -> Reports (I added a fake duplicate - should not be allowed)
+
+ $sql = $db->query('SELECT MIN(os_id) id, project_id, os_name
+ FROM {list_os}
+ GROUP BY project_id, os_name
+ HAVING COUNT(*) > 1');
+ $dups = $db->fetchAllArray($sql);
+ if (count($dups) > 0) {
+ if($doit){
+ fix_os_table($dups);
+ } else{
+ $uplog[]='<span class="warning">'.count($dups).' duplicate entries in {list_os}</span>';
+ }
+ }
+
+ $sql = $db->query('SELECT MIN(resolution_id) id, project_id, resolution_name
+ FROM {list_resolution}
+ GROUP BY project_id, resolution_name
+ HAVING COUNT(*) > 1');
+ $dups = $db->fetchAllArray($sql);
+ if (count($dups) > 0) {
+ if($doit){
+ fix_resolution_table($dups);
+ }else{
+ $uplog[]='<span class="warning">'.count($dups).' duplicate entries in {list_resolution}</span>';
+ }
+ }
+
+ $sql = $db->query('SELECT MIN(status_id) id, project_id, status_name
+ FROM {list_status}
+ GROUP BY project_id, status_name
+ HAVING COUNT(*) > 1');
+ $dups = $db->fetchAllArray($sql);
+ if (count($dups) > 0) {
+ if($doit){
+ fix_status_table($dups);
+ }else{
+ $uplog[]='<span class="warning">'.count($dups).' duplicate entries in {list_status}</span>';
+ }
+ }
+ $sql = $db->query('SELECT MIN(tasktype_id) id, project_id, tasktype_name
+ FROM {list_tasktype}
+ GROUP BY project_id, tasktype_name
+ HAVING COUNT(*) > 1');
+ $dups = $db->fetchAllArray($sql);
+ if (count($dups) > 0) {
+ if($doit){
+ fix_tasktype_table($dups);
+ }else{
+ $uplog[]='<span class="warning">'.count($dups).' duplicate entries in {list_tasktype}</span>';
+ }
+ }
+
+ $sql = $db->query('SELECT MIN(version_id) id, project_id, version_name
+ FROM {list_version}
+ GROUP BY project_id, version_name
+ HAVING COUNT(*) > 1');
+ $dups = $db->fetchAllArray($sql);
+ if (count($dups) > 0) {
+ if($doit){
+ fix_version_table($dups);
+ }else{
+ $uplog[]='<span class="warning">'.count($dups).' duplicate entries in {list_version}</span>';
+ }
+ }
+}
+
+function fix_os_table($dups) {
+ global $db;
+
+ foreach ($dups as $dup) {
+ $update_id = $dup['id'];
+
+ $sql = $db->query('SELECT os_id id
+ FROM {list_os}
+ WHERE project_id = ? AND os_name = ?',
+ array($dup['project_id'], $dup['os_name']));
+ $entries = $db->fetchAllArray($sql);
+ foreach ($entries as $entry) {
+ if ($entry['id'] == $update_id) {
+ continue;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET operating_system = ?
+ WHERE operating_system = ?',
+ array($update_id, $entry['id']));
+ $db->query('DELETE FROM {list_os} WHERE os_id = ?', array($entry['id']));
+ }
+ }
+}
+
+function fix_resolution_table($dups) {
+ global $db;
+
+ foreach ($dups as $dup) {
+ $update_id = $dup['id'];
+
+ $sql = $db->query('SELECT resolution_id id
+ FROM {list_resolution}
+ WHERE project_id = ? AND resolution_name = ?',
+ array($dup['project_id'], $dup['resolution_name']));
+ $entries = $db->fetchAllArray($sql);
+ foreach ($entries as $entry) {
+ if ($entry['id'] == $update_id) {
+ continue;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET resolution_reason = ?
+ WHERE resolution_reason = ?',
+ array($update_id, $entry['id']));
+ $db->query('DELETE FROM {list_resolution} WHERE resolution_id = ?', array($entry['id']));
+ }
+ }
+}
+
+function fix_status_table($dups) {
+ global $db;
+
+ foreach ($dups as $dup) {
+ $update_id = $dup['id'];
+
+ $sql = $db->query('SELECT status_id id
+ FROM {list_status}
+ WHERE project_id = ? AND status_name = ?',
+ array($dup['project_id'], $dup['status_name']));
+ $entries = $db->fetchAllArray($sql);
+ foreach ($entries as $entry) {
+ if ($entry['id'] == $update_id) {
+ continue;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET item_status = ?
+ WHERE item_status = ?',
+ array($update_id, $entry['id']));
+ $db->query('DELETE FROM {list_status} WHERE status_id = ?', array($entry['id']));
+ }
+ }
+}
+
+function fix_tasktype_table($dups) {
+ global $db;
+
+ foreach ($dups as $dup) {
+ $update_id = $dup['id'];
+
+ $sql = $db->query('SELECT tasktype_id id
+ FROM {list_tasktype}
+ WHERE project_id = ? AND tasktype_name = ?',
+ array($dup['project_id'], $dup['tasktype_name']));
+ $entries = $db->fetchAllArray($sql);
+ foreach ($entries as $entry) {
+ if ($entry['id'] == $update_id) {
+ continue;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET task_type = ?
+ WHERE task_type = ?',
+ array($update_id, $entry['id']));
+ $db->query('DELETE FROM {list_tasktype} WHERE tasktype_id = ?', array($entry['id']));
+ }
+ }
+}
+
+function fix_version_table($dups) {
+ global $db;
+
+ foreach ($dups as $dup) {
+ $update_id = $dup['id'];
+
+ $sql = $db->query('SELECT version_id id
+ FROM {list_version}
+ WHERE project_id = ? AND version_name = ?',
+ array($dup['project_id'], $dup['version_name']));
+ $entries = $db->fetchAllArray($sql);
+ foreach ($entries as $entry) {
+ if ($entry['id'] == $update_id) {
+ continue;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET product_version = ?
+ WHERE product_version = ?',
+ array($update_id, $entry['id']));
+ $db->query('DELETE FROM {list_version} WHERE version_id = ?', array($entry['id']));
+ }
+ }
+}
+
+// Just a sketch on how database columns could be updated to the new format.
+// Not tested for errors or used anywhere yet.
+
+function convert_old_entries($table, $column, $key) {
+ global $db;
+
+ // Assuming that anything not beginning with < was made with older
+ // versions of flyspray. This will not catch neither those old entries
+ // where the user for some reason really added paragraph tags nor those
+ // made with development version before fixing ckeditors configuration
+ // settings. You can't have everything in a limited time frame, this
+ // should be just good enough.
+ $sql = $db->query("SELECT $key, $column "
+ . "FROM {". $table . "} "
+ . "WHERE $column NOT LIKE '<%'");
+ $entries = $db->fetchAllArray($sql);
+
+ # We should probably better use existing and proven filters for the conversions
+ # maybe this or existing dokuwiki functionality?
+ # $out=html2wiki($input, 'wikistyle'); and $out=wiki2html($input, 'wikistyle')
+
+ foreach ($entries as $entry) {
+ $id = $entry[$key];
+ $data = $entry[$column];
+
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+ $data = htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ } else{
+ $data = htmlspecialchars($data, ENT_QUOTES , 'UTF-8');
+ }
+ // Convert two or more line breaks to paragrahs, Windows/Unix/Linux formats
+ $data = preg_replace('/(\h*\r?\n)+\h*\r?\n/', "</p><p>", $data);
+ // Data coming from Macs has only carriage returns, and couldn't say
+ // \r?\n? in the previous regex, it would also have matched nothing.
+ // Even a short word like "it" has three nothings in it, one before
+ // i, one between i and t and one after t...
+ $data = preg_replace('/(\h*\r)+\h*\r/', "</p><p>", $data);
+ // Remaining single line breaks
+ $data = preg_replace('/\h*\r?\n/', "<br/>", $data);
+ $data = preg_replace('/\h*\r/', "<br/>", $data);
+ // Remove final extra break, if the data to converted ended with a line break
+ $data = preg_replace('#<br/>$#', '', $data);
+ // Remove final extra paragraph tags, if the data to converted ended with
+ // more than one line breaks
+ $data = preg_replace('#</p><p>$#', '', $data);
+ // Enclose the whole in paragraph tags, so it looks
+ // the same as what ckeditor produces.
+ $data = '<p>' . $data . '</p>';
+
+ $db->query("UPDATE {". $table . "} "
+ . "SET $column = ?"
+ . "WHERE $key = ?",
+ array($data, $id));
+ }
+}
diff --git a/setup/upgrade/0.9.9.2/flyspray-install.xml b/setup/upgrade/0.9.9.2/flyspray-install.xml
new file mode 100644
index 0000000..eac0638
--- /dev/null
+++ b/setup/upgrade/0.9.9.2/flyspray-install.xml
@@ -0,0 +1,1049 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality.', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project.', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '0.9.9');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id project category tasktype severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org/support. You can customise this message by clicking the **Manage Project** link in the menu above...', 1, 'id category tasktype severity summary status progress', 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index');</query>
+ <query>INSERT INTO tasks VALUES (1, 1, 1, 1130024797, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0');</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Mr Super User', 'super@example.com', 'super@example.com', 0, 1, 1, '', '', 25, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/0.9.9.2/flyspray.conf.php b/setup/upgrade/0.9.9.2/flyspray.conf.php
new file mode 100644
index 0000000..86ccc51
--- /dev/null
+++ b/setup/upgrade/0.9.9.2/flyspray.conf.php
@@ -0,0 +1,33 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "md5" ; Available options: "crypt", "md5", "sha1"
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for Flyspray's syntax (use any non-existing plugin name for deafult syntax)
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+dot_public = "http://public.research.att.com/~north/cgi-bin/webdot/webdot.cgi" ; URL to a public dot server
+dot_path = "" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)
+dot_format = "png" ; "png" or "svg"
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.2/lowercase_emails.php b/setup/upgrade/0.9.9.2/lowercase_emails.php
new file mode 100644
index 0000000..24fd28e
--- /dev/null
+++ b/setup/upgrade/0.9.9.2/lowercase_emails.php
@@ -0,0 +1,6 @@
+<?php
+
+$db->query('UPDATE {users} SET email_address = LOWER(email_address), jabber_id = LOWER(jabber_id)');
+
+?>
+
diff --git a/setup/upgrade/0.9.9.2/update_users.xml b/setup/upgrade/0.9.9.2/update_users.xml
new file mode 100644
index 0000000..591a1bb
--- /dev/null
+++ b/setup/upgrade/0.9.9.2/update_users.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+</schema> \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.2/upgrade.info b/setup/upgrade/0.9.9.2/upgrade.info
new file mode 100644
index 0000000..4ea91dd
--- /dev/null
+++ b/setup/upgrade/0.9.9.2/upgrade.info
@@ -0,0 +1,34 @@
+[defaultupgrade]
+1="update_users.xml"
+2="lowercase_emails.php"
+
+
+[develupgrade]
+1="update_users.xml"
+
+[fsprefs]
+fs_ver="0.9.7" ; doesn't matter which version
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+spam_proof="1"
+default_project="1"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id project tasktype severity summary status progress"
+smtp_server=""
+smtp_user=""
+smtp_pass=""
+lock_for="5" \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.4/flyspray-install.xml b/setup/upgrade/0.9.9.4/flyspray-install.xml
new file mode 100644
index 0000000..ae2de1d
--- /dev/null
+++ b/setup/upgrade/0.9.9.4/flyspray-install.xml
@@ -0,0 +1,1055 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255" />
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality.', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts.', 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);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project.', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '0.9.9');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id project category tasktype severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org/support. You can customise this message by clicking the **Manage Project** link in the menu above...', 1, 'id category tasktype severity summary status progress', 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index');</query>
+ <query>INSERT INTO tasks VALUES (1, 1, 1, 1130024797, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0');</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Mr Super User', 'super@example.com', 'super@example.com', 0, 1, 1, '', '', 25, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/0.9.9.4/flyspray.conf.php b/setup/upgrade/0.9.9.4/flyspray.conf.php
new file mode 100644
index 0000000..86ccc51
--- /dev/null
+++ b/setup/upgrade/0.9.9.4/flyspray.conf.php
@@ -0,0 +1,33 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "md5" ; Available options: "crypt", "md5", "sha1"
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for Flyspray's syntax (use any non-existing plugin name for deafult syntax)
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+dot_public = "http://public.research.att.com/~north/cgi-bin/webdot/webdot.cgi" ; URL to a public dot server
+dot_path = "" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)
+dot_format = "png" ; "png" or "svg"
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.4/upgrade.info b/setup/upgrade/0.9.9.4/upgrade.info
new file mode 100644
index 0000000..3acfddb
--- /dev/null
+++ b/setup/upgrade/0.9.9.4/upgrade.info
@@ -0,0 +1,32 @@
+[defaultupgrade]
+1="upgrade.xml"
+
+[develupgrade]
+1="upgrade.xml"
+
+[fsprefs]
+fs_ver="0.9.7" ; doesn't matter which version
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+spam_proof="1"
+default_project="1"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id project tasktype severity summary status progress"
+smtp_server=""
+smtp_user=""
+smtp_pass=""
+lock_for="5" \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.4/upgrade.xml b/setup/upgrade/0.9.9.4/upgrade.xml
new file mode 100644
index 0000000..5953d91
--- /dev/null
+++ b/setup/upgrade/0.9.9.4/upgrade.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255" />
+ </table>
+</schema>
diff --git a/setup/upgrade/0.9.9.5/flyspray-install.xml b/setup/upgrade/0.9.9.5/flyspray-install.xml
new file mode 100644
index 0000000..11dc5ff
--- /dev/null
+++ b/setup/upgrade/0.9.9.5/flyspray-install.xml
@@ -0,0 +1,1058 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255" />
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="I" size="11">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_votes">
+ <col>task_id</col>
+ </index>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality.', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts.', 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);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project.', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '0.9.9');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id project category tasktype severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org/support. You can customise this message by clicking the **Manage Project** link in the menu above...', 1, 'id category tasktype severity summary status progress', 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index');</query>
+ <query>INSERT INTO tasks VALUES (1, 1, 1, 1130024797, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0');</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Mr Super User', 'super@example.com', 'super@example.com', 0, 1, 1, '', '', 25, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/0.9.9.5/flyspray.conf.php b/setup/upgrade/0.9.9.5/flyspray.conf.php
new file mode 100644
index 0000000..86ccc51
--- /dev/null
+++ b/setup/upgrade/0.9.9.5/flyspray.conf.php
@@ -0,0 +1,33 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "md5" ; Available options: "crypt", "md5", "sha1"
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for Flyspray's syntax (use any non-existing plugin name for deafult syntax)
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+dot_public = "http://public.research.att.com/~north/cgi-bin/webdot/webdot.cgi" ; URL to a public dot server
+dot_path = "" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)
+dot_format = "png" ; "png" or "svg"
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.5/upgrade.info b/setup/upgrade/0.9.9.5/upgrade.info
new file mode 100644
index 0000000..68f3208
--- /dev/null
+++ b/setup/upgrade/0.9.9.5/upgrade.info
@@ -0,0 +1,34 @@
+[defaultupgrade]
+1="upgrade.xml"
+
+[develupgrade]
+1="upgrade.xml"
+
+[fsprefs]
+fs_ver="0.9.7" ; doesn't matter which version
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+spam_proof="1"
+default_project="1"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id project tasktype severity summary status progress"
+smtp_server=""
+smtp_user=""
+smtp_pass=""
+lock_for="5"
+email_ssl="0"
+email_tls="0" \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.5/upgrade.xml b/setup/upgrade/0.9.9.5/upgrade.xml
new file mode 100644
index 0000000..f11b3c8
--- /dev/null
+++ b/setup/upgrade/0.9.9.5/upgrade.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="I" size="11">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+</schema> \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.7/flyspray-install.xml b/setup/upgrade/0.9.9.7/flyspray-install.xml
new file mode 100644
index 0000000..10c79d1
--- /dev/null
+++ b/setup/upgrade/0.9.9.7/flyspray-install.xml
@@ -0,0 +1,1059 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255" />
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="I" size="11">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_votes">
+ <col>task_id</col>
+ </index>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality.', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts.', 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);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project.', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '0.9.9');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'logo', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id project category tasktype severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org/support. You can customise this message by clicking the **Manage Project** link in the menu above...', 1, 'id category tasktype severity summary status progress', 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index');</query>
+ <query>INSERT INTO tasks VALUES (1, 1, 1, 1130024797, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0');</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Mr Super User', 'super@example.com', 'super@example.com', 0, 1, 1, '', '', 25, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/0.9.9.7/flyspray.conf.php b/setup/upgrade/0.9.9.7/flyspray.conf.php
new file mode 100644
index 0000000..3b018e3
--- /dev/null
+++ b/setup/upgrade/0.9.9.7/flyspray.conf.php
@@ -0,0 +1,30 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "md5" ; Available options: "crypt", "md5", "sha1"
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for Flyspray's syntax (use any non-existing plugin name for deafult syntax)
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files \ No newline at end of file
diff --git a/setup/upgrade/0.9.9.7/upgrade.info b/setup/upgrade/0.9.9.7/upgrade.info
new file mode 100644
index 0000000..324d70e
--- /dev/null
+++ b/setup/upgrade/0.9.9.7/upgrade.info
@@ -0,0 +1,35 @@
+[defaultupgrade]
+;1="upgrade.xml"
+
+[develupgrade]
+;1="upgrade.xml"
+
+[fsprefs]
+fs_ver="0.9.7" ; doesn't matter which version
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+spam_proof="1"
+default_project="1"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id project tasktype severity summary status progress"
+smtp_server=""
+smtp_user=""
+smtp_pass=""
+lock_for="5"
+email_ssl="0"
+email_tls="0"
+default_timezone="0" \ No newline at end of file
diff --git a/setup/upgrade/0.9.9/add_data.php b/setup/upgrade/0.9.9/add_data.php
new file mode 100644
index 0000000..8c32d38
--- /dev/null
+++ b/setup/upgrade/0.9.9/add_data.php
@@ -0,0 +1,34 @@
+<?php
+ /**********************************************************\
+ | This script adds/deletes data what can't be added to |
+ | the XML schema files. |
+ \***********************************************************/
+
+// New status list, make sure data is only inserted if we have an empty table
+$sql = $db->query('SELECT count(*) FROM {list_status}');
+if ($db->fetchOne($sql) < 1) {
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)");
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)");
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)");
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)");
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)");
+ $db->query("INSERT INTO {list_status} (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)");
+}
+
+if (Post::val('replace_resolution')) {
+ $db->query('UPDATE {list_resolution} SET resolution_name = ? WHERE resolution_id = ?', array('Duplicate (the real one)', 6));
+}
+
+$db->query("DELETE FROM {list_status} WHERE status_id = 7");
+$db->query("DELETE FROM {notifications} WHERE user_id = 0 OR task_id = 0");
+
+$db->query("UPDATE {tasks} SET closure_comment='' WHERE closure_comment='0'");
+$db->query("UPDATE {groups} SET add_to_assignees = '1' WHERE assign_others_to_self =1 ");
+$db->query("UPDATE {groups} SET add_votes = 1 WHERE group_id = 2 OR group_id = 3 OR group_id = 6");
+$db->query("UPDATE {groups} SET edit_assignments = '1' WHERE group_id = 2");
+$db->query("UPDATE {history} SET event_type = 3 WHERE event_type = 0");
+$db->query("UPDATE {history} SET event_type = 11 WHERE event_type = 15");
+$db->query("UPDATE {history} SET event_type = 12 WHERE event_type = 16");
+$db->query("UPDATE {history} SET field_changed = 'project_id' WHERE field_changed = 'attached_to_project'");
+
+?>
diff --git a/setup/upgrade/0.9.9/add_duplicates.php b/setup/upgrade/0.9.9/add_duplicates.php
new file mode 100644
index 0000000..4a00a8a
--- /dev/null
+++ b/setup/upgrade/0.9.9/add_duplicates.php
@@ -0,0 +1,41 @@
+<?php
+ /**********************************************************\
+ | This script enters the relations of duplicate tasks into |
+ | the databse. |
+ \***********************************************************/
+
+
+$check_sql = $db->query('SELECT task_id, closure_comment, resolution_reason FROM {tasks}');
+
+while ($row = $db->fetchRow($check_sql))
+{
+ if ($row['resolution_reason'] == 6) {
+ preg_match("/\b(?:FS#|bug )(\d+)\b/", $row['closure_comment'], $dupe_of);
+ if (count($dupe_of)) {
+ $existing = $db->query('SELECT * FROM {related} WHERE this_task = ? AND related_task = ? AND is_duplicate = 1',
+ array($row['task_id'], $dupe_of[1]));
+
+ if ($db->countRows($existing) == 0) {
+ $db->query('INSERT INTO {related} (this_task, related_task, is_duplicate) VALUES(?,?,1)',
+ array($row['task_id'], $dupe_of[1]));
+ echo $row['task_id'] . ' is a duplicate of ' . $dupe_of[1] . '.<br />';
+ }
+ }
+ }
+}
+
+$check_sql = $db->query('SELECT this_task, related_task FROM {related} WHERE is_duplicate = 0');
+$deleted = array();
+
+while ($row = $db->fetchRow($check_sql))
+{
+ $existing = $db->query('SELECT related_id FROM {related} WHERE this_task = ? AND related_task = ? AND is_duplicate = 0',
+ array($row['related_task'], $row['this_task']));
+
+ if ($db->countRows($existing) == 1 && !isset($deleted[$row['related_task'].'-'.$row['this_task']])) {
+ $deleted[$row['this_task'].'-'.$row['related_task']] = true;
+ $db->query('DELETE FROM {related} WHERE related_id = ?', array($db->fetchOne($existing)));
+ }
+}
+
+?>
diff --git a/setup/upgrade/0.9.9/add_searches.php b/setup/upgrade/0.9.9/add_searches.php
new file mode 100644
index 0000000..6e5031c
--- /dev/null
+++ b/setup/upgrade/0.9.9/add_searches.php
@@ -0,0 +1,22 @@
+<?php
+ /**********************************************************\
+ | This script addes some default saved searches for every |
+ | user. |
+ \***********************************************************/
+
+$check_sql = $db->query('SELECT user_id FROM {users}');
+
+while ($row = $db->fetchRow($check_sql))
+{
+ $db->query('DELETE FROM {searches} WHERE (name = ? OR name = ? OR name = ?) AND user_id = ?', array('Tasks I watch', 'Tasks assigned to me', 'Tasks I opened', $row['user_id']));
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time) VALUES (?, \'Tasks I watch\', \'a:16:{s:6:"string";N;s:4:"type";a:1:{i:0;s:0:"";}s:3:"sev";a:1:{i:0;s:0:"";}s:3:"due";a:1:{i:0;s:0:"";}s:3:"dev";N;s:3:"cat";a:1:{i:0;s:0:"";}s:6:"status";a:1:{i:0;s:4:"open";}s:5:"order";N;s:4:"sort";N;s:7:"percent";a:1:{i:0;s:0:"";}s:6:"opened";N;s:18:"search_in_comments";N;s:14:"search_for_all";N;s:8:"reported";a:1:{i:0;s:0:"";}s:12:"only_primary";N;s:12:"only_watched";s:1:"1";}\', ' . time() . ')',
+ array($row['user_id']));
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time) VALUES (?, \'Tasks assigned to me\', \'a:16:{s:6:"string";N;s:4:"type";a:1:{i:0;s:0:"";}s:3:"sev";a:1:{i:0;s:0:"";}s:3:"due";a:1:{i:0;s:0:"";}s:3:"dev";s:' . strlen($row['user_id']) . ':"' . $row['user_id'] .'";s:3:"cat";a:1:{i:0;s:0:"";}s:6:"status";a:1:{i:0;s:4:"open";}s:5:"order";N;s:4:"sort";N;s:7:"percent";a:1:{i:0;s:0:"";}s:6:"opened";N;s:18:"search_in_comments";N;s:14:"search_for_all";N;s:8:"reported";a:1:{i:0;s:0:"";}s:12:"only_primary";N;s:12:"only_watched";N;}\', ' . time() . ')',
+ array($row['user_id']));
+ $db->query('INSERT INTO {searches} (user_id, name, search_string, time) VALUES (?, \'Tasks I opened\', \'a:16:{s:6:"string";N;s:4:"type";a:1:{i:0;s:0:"";}s:3:"sev";a:1:{i:0;s:0:"";}s:3:"due";a:1:{i:0;s:0:"";}s:3:"dev";N;s:3:"cat";a:1:{i:0;s:0:"";}s:6:"status";a:1:{i:0;s:4:"open";}s:5:"order";N;s:4:"sort";N;s:7:"percent";a:1:{i:0;s:0:"";}s:6:"opened";s:' . strlen($row['user_id']) . ':"' . $row['user_id'] .'";s:18:"search_in_comments";N;s:14:"search_for_all";N;s:8:"reported";a:1:{i:0;s:0:"";}s:12:"only_primary";N;s:12:"only_watched";N;}\', ' . time() . ')',
+ array($row['user_id']));
+}
+
+
+?>
+
diff --git a/setup/upgrade/0.9.9/clean_unique.php b/setup/upgrade/0.9.9/clean_unique.php
new file mode 100644
index 0000000..652630b
--- /dev/null
+++ b/setup/upgrade/0.9.9/clean_unique.php
@@ -0,0 +1,67 @@
+<?php
+ /**********************************************************\
+ | This script removes duplicate db entries |
+ \**********************************************************/
+
+// Users
+
+$users = $db->query('SELECT * FROM {users} ORDER BY user_id ASC');
+
+while ($row = $db->fetchRow($users))
+{
+ if (!isset($deleted[$row['user_name']])) {
+ $deleted[$row['user_name']] = $row['user_id'];
+ }
+
+ $db->query('DELETE FROM {users} WHERE user_name = ? AND user_id != ?',
+ array($row['user_name'], $deleted[$row['user_name']]));
+}
+
+
+$users = $db->query('SELECT * FROM {registrations} ORDER BY reg_id ASC');
+
+while ($row = $db->fetchRow($users))
+{
+ if (!isset($deleted[$row['user_name']])) {
+ $deleted[$row['user_name']] = $row['reg_id'];
+ }
+
+ $db->query('DELETE FROM {registrations} WHERE user_name = ? AND reg_id != ?',
+ array($row['user_name'], $deleted[$row['user_name']]));
+}
+
+// Users in groups
+
+$sql = $db->query('SELECT * FROM {users_in_groups} ORDER BY record_id');
+while ($row = $db->fetchRow($sql))
+{
+ $db->query('DELETE FROM {users_in_groups} WHERE user_id = ? AND group_id = ? AND record_id <> ?',
+ array($row['user_id'], $row['group_id'], $row['record_id']));
+}
+
+// Group names
+
+$sql = $db->query('SELECT * FROM {groups} ORDER BY group_id ASC');
+while ($row = $db->fetchRow($sql))
+{
+ $col = 'belongs_to_project';
+ if (!isset($row[$col])) {
+ $col = 'project_id';
+ }
+
+ $db->query('DELETE FROM {groups} WHERE group_name = ? AND '.$col.' = ? AND group_id <> ?',
+ array($row['group_name'], $row[$col], $row['group_id']));
+}
+
+// Out of range value adjusted for column..
+$sql = $db->query('SELECT * FROM {tasks}');
+while ($row = $db->fetchRow($sql))
+{
+ $db->query('UPDATE {tasks} SET date_closed = ?, last_edited_time = ? WHERE task_id = ?',
+ array(intval($row['date_closed']), intval($row['last_edited_time']), $row['task_id']));
+ if (isset($row['due_date'])) {
+ $db->query('UPDATE {tasks} SET due_date = ? WHERE task_id = ?',
+ array(intval($row['due_date']), $row['task_id']));
+ }
+}
+?>
diff --git a/setup/upgrade/0.9.9/convert_categories.php b/setup/upgrade/0.9.9/convert_categories.php
new file mode 100644
index 0000000..99789ad
--- /dev/null
+++ b/setup/upgrade/0.9.9/convert_categories.php
@@ -0,0 +1,43 @@
+<?php
+ /**********************************************************\
+ | This script converts the categories table to the new |
+ | format. |
+ \***********************************************************/
+
+function rebuild_tree($parent, $left, $pr) {
+ global $db;
+ // the right value of this node is the left value + 1
+ $right = $left+1;
+
+ // get all children of this node
+ $result = $db->query('SELECT category_id FROM {list_category} WHERE parent_id = ? AND project_id = ?', array($parent, $pr));
+
+ while ($row = $db->fetchRow($result)) {
+ // recursive execution of this function for each
+ // child of this node
+ // $right is the current right value, which is
+ // incremented by the rebuild_tree function
+ $right = rebuild_tree($row['category_id'], $right, $pr);
+ }
+
+ // we've got the left value, and now that we've processed
+ // the children of this node we also know the right value
+ $db->query('UPDATE {list_category} SET lft= ?, rgt= ? WHERE category_id = ?', array($left, $right, $parent));
+ $sql = $db->query('SELECT * FROM {list_category} WHERE category_id = ? OR project_id=? AND parent_id=-1', array($parent, $pr));
+ if (!$db->countRows($sql)) {
+ $db->query('INSERT INTO {list_category} (project_id, lft, rgt, category_name, parent_id) VALUES(?,?,?,?,-1)',
+ array($pr,$left,$right,'root'));
+ }
+ // return the right value of this node + 1
+ return $right+1;
+}
+
+$projects = $db->query('SELECT project_id FROM {projects}');
+
+// Global project
+rebuild_tree(0, 1, 0);
+while ($pr = $db->fetchRow($projects)) {
+ rebuild_tree(0, 1, $pr['project_id']);
+}
+
+?>
diff --git a/setup/upgrade/0.9.9/convert_private.php b/setup/upgrade/0.9.9/convert_private.php
new file mode 100644
index 0000000..b871d39
--- /dev/null
+++ b/setup/upgrade/0.9.9/convert_private.php
@@ -0,0 +1,26 @@
+<?php
+ /**********************************************************\
+ | This script converts the private/public history entries |
+ \***********************************************************/
+
+
+$check_sql = $db->query('SELECT * FROM {history} WHERE event_type = 26 OR event_type = 27');
+
+while ($row = $db->fetchRow($check_sql))
+{
+ $db->query('DELETE FROM {history} WHERE history_id = ?', array($row['history_id']));
+ if ($row['event_type'] == 26) {
+ $row['old_value'] = 0;
+ $row['new_value'] = 1;
+ }
+ if ($row['event_type'] == 27) {
+ $row['old_value'] = 1;
+ $row['new_value'] = 0;
+ }
+ $db->query("INSERT INTO {history} (task_id, user_id, event_date, event_type, field_changed, old_value, new_value)
+ VALUES(?, ?, ?, 0, 'mark_private', ?, ?)",
+ array($row['task_id'], $row['user_id'], $row['event_date'], $row['old_value'], $row['new_value']));
+}
+
+
+?>
diff --git a/setup/upgrade/0.9.9/flyspray-begin.xml b/setup/upgrade/0.9.9/flyspray-begin.xml
new file mode 100644
index 0000000..aac979a
--- /dev/null
+++ b/setup/upgrade/0.9.9/flyspray-begin.xml
@@ -0,0 +1,748 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="deny_reason" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_or_group" type="C" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="belongs_to_project" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="new_value" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="supertask_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="C" size="20"/>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_logo" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="inline_images" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="notify_email_when" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="notify_jabber_when" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="C" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="C" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="attached_to_project" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="C" size="12">
+ <NOTNULL/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assigned_to" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="C" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="last_search" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+</schema>
diff --git a/setup/upgrade/0.9.9/flyspray-final.xml b/setup/upgrade/0.9.9/flyspray-final.xml
new file mode 100644
index 0000000..5db6a96
--- /dev/null
+++ b/setup/upgrade/0.9.9/flyspray-final.xml
@@ -0,0 +1,976 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value="en"/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+</schema>
diff --git a/setup/upgrade/0.9.9/flyspray-install.xml b/setup/upgrade/0.9.9/flyspray-install.xml
new file mode 100644
index 0000000..7496241
--- /dev/null
+++ b/setup/upgrade/0.9.9/flyspray-install.xml
@@ -0,0 +1,1044 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="admin_requests">
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="assigned">
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="task_id_assigned">
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_desc" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ </table>
+ <table name="comments">
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="C" size="250">
+ <DEFAULT value="0"/>
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ </table>
+ <table name="registrations">
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="40"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality.', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project.', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '0.9.9');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id project category tasktype severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org/support. You can customise this message by clicking the **Manage Project** link in the menu above...', 1, 'id category tasktype severity summary status progress', 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index');</query>
+ <query>INSERT INTO tasks VALUES (1, 1, 1, 1130024797, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0');</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Mr Super User', 'super@example.com', 'super@example.com', 0, 1, 1, '', '', 25, 0, 0, 0);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/0.9.9/flyspray.conf.php b/setup/upgrade/0.9.9/flyspray.conf.php
new file mode 100644
index 0000000..86ccc51
--- /dev/null
+++ b/setup/upgrade/0.9.9/flyspray.conf.php
@@ -0,0 +1,33 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "md5" ; Available options: "crypt", "md5", "sha1"
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for Flyspray's syntax (use any non-existing plugin name for deafult syntax)
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+dot_public = "http://public.research.att.com/~north/cgi-bin/webdot/webdot.cgi" ; URL to a public dot server
+dot_path = "" ; Path to the dot executable (for graphs either dot_public or dot_path must be set)
+dot_format = "png" ; "png" or "svg"
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files \ No newline at end of file
diff --git a/setup/upgrade/0.9.9/rename_columns.php b/setup/upgrade/0.9.9/rename_columns.php
new file mode 100644
index 0000000..07a949a
--- /dev/null
+++ b/setup/upgrade/0.9.9/rename_columns.php
@@ -0,0 +1,14 @@
+<?php
+ /**********************************************************\
+ | This script renames columns, adodb seems to have prob here|
+ \**********************************************************/
+
+$dict = NewDataDictionary($db->dblink);
+
+$sqlarray = $dict->RenameColumnSQL($conf['database']['dbprefix'] . 'tasks', 'attached_to_project', 'project_id', 'TYPE INT(3) NOTNULL DEFAULT 0');
+$dict->ExecuteSQLArray($sqlarray);
+
+$sqlarray = $dict->RenameColumnSQL($conf['database']['dbprefix'] . 'groups', 'belongs_to_project', 'project_id', ' TYPE INT(3) NOTNULL DEFAULT 0');
+$dict->ExecuteSQLArray($sqlarray);
+
+?> \ No newline at end of file
diff --git a/setup/upgrade/0.9.9/upgrade.info b/setup/upgrade/0.9.9/upgrade.info
new file mode 100644
index 0000000..e5ea7e5
--- /dev/null
+++ b/setup/upgrade/0.9.9/upgrade.info
@@ -0,0 +1,44 @@
+[defaultupgrade]
+0="clean_unique.php"
+1="flyspray-begin.xml"
+2="upgrade_assignments.php"
+3="convert_categories.php"
+4="convert_private.php"
+5="add_duplicates.php"
+6="add_searches.php"
+7="rename_columns.php"
+8="flyspray-final.xml"
+9="add_data.php"
+
+[develupgrade]
+1="flyspray-final.xml"
+
+[options]
+
+1="<div><label><input name='replace_resolution' checked='checked' type='checkbox'/> Edit resolution list (strongly recommended for 0.9.8 to 0.9.9)</label></div>"
+
+[fsprefs]
+fs_ver="0.9.7" ; doesn't matter which version
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+spam_proof="1"
+default_project="1"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id project tasktype severity summary status progress"
+smtp_server=""
+smtp_user=""
+smtp_pass="" \ No newline at end of file
diff --git a/setup/upgrade/0.9.9/upgrade_assignments.php b/setup/upgrade/0.9.9/upgrade_assignments.php
new file mode 100644
index 0000000..a9aa53c
--- /dev/null
+++ b/setup/upgrade/0.9.9/upgrade_assignments.php
@@ -0,0 +1,31 @@
+<?php
+ /**********************************************************\
+ | This script moves data from {dbprefix)tasks.assigned_to |
+ | to {dbprefix}assigned. This is to implement multiple |
+ | assignees per task as described in FS#329. It only needs |
+ | to be run once to do the conversion. |
+ \***********************************************************/
+
+$check_sql = $db->query("SELECT task_id, assigned_to
+ FROM {tasks}
+ WHERE assigned_to > '0'");
+
+while ($row = $db->fetchRow($check_sql))
+{
+ $check = $db->query('SELECT assigned_id FROM {assigned} WHERE task_id = ? AND user_id = ?',
+ array($row['task_id'], $row['assigned_to']));
+ if ($db->fetchOne($check)) {
+ continue;
+ }
+
+ $db->query('INSERT INTO {assigned}
+ (task_id, user_id)
+ VALUES (?,?)',
+ array($row['task_id'], $row['assigned_to']));
+
+ $db->query('UPDATE {tasks}
+ SET assigned_to = 0
+ WHERE task_id = ?',
+ array($row['task_id']));
+}
+?>
diff --git a/setup/upgrade/1.0/datadict-postgres.inc.php b/setup/upgrade/1.0/datadict-postgres.inc.php
new file mode 100644
index 0000000..ebcb001
--- /dev/null
+++ b/setup/upgrade/1.0/datadict-postgres.inc.php
@@ -0,0 +1,606 @@
+<?php
+
+/**
+ @version v5.20.9-flyspray 21-Dec-2016
+ @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;
+ }
+ // SERIAL is not a true type in PostgreSQL and is only allowed when creating a new table.
+ // See http://www.postgresql.org/docs/9.4/static/datatype-numeric.html, 8.1.4. Serial Types.
+ elseif (preg_match('/^([^ ]+) .*SERIAL/i',$v,$matches)) {
+ list(,$colname,$default) = $matches;
+ $sql[] = 'CREATE SEQUENCE '.$tabname.'_'.$colname.'_seq';
+ $sql[] = $alter.$colname.' INTEGER';
+ $sql[] = 'ALTER SEQUENCE '.$tabname.'_'.$colname.'_seq OWNED BY '.$tabname.'.'.$colname;
+ $sql[] = 'UPDATE '.$tabname.' SET '.$colname.' = nextval(\''.$tabname.'_'.$colname.'_seq\')';
+ $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET DEFAULT nextval(\''.$tabname.'_'.$colname.'_seq\')';
+ $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET NOT NULL';
+ $not_null = false;
+ } 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);
+ }
+
+ // SERIAL is not a true type in PostgreSQL and is only allowed when creating a new table.
+ // See http://www.postgresql.org/docs/9.4/static/datatype-numeric.html, 8.1.4. Serial Types.
+ if (preg_match('/SERIAL/i',$v)) {
+ continue;
+ }
+ // 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;
+ }
+
+ /**
+ "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);
+ }
+
+ $addedcols = array();
+ $modifiedcols = array();
+
+ 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
+ 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)) {
+ $modifiedcols[$k] = $v;
+ }
+ } else {
+ $addedcols[$k] = $v;
+ }
+ }
+ }
+
+
+ $sql = array();
+ $sql = $this->addColumnSQL($tablename, $addedcols);
+
+ // already exists, alter table instead
+ list($lines,$pkey,$idxs) = $this->_genFields($modifiedcols);
+ // genfields can return FALSE at times
+ if ($lines == null) $lines = array();
+
+ $holdflds = 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;
+ }
+ $holdflds[] = $modifiedcols[$id];
+ }
+ }
+ $modifiedcols = $holdflds;
+ $sql += $this->alterColumnSQL($tablename, $modifiedcols);
+
+ if ($dropOldFlds) {
+ $alter = 'ALTER TABLE ' . $this->tableName($tablename);
+ foreach ( $cols as $id => $v )
+ if ( !isset($lines[$id]) )
+ $sql[] = $alter . $this->dropCol . ' ' . $v->name;
+ }
+ return $sql;
+ }
+}
diff --git a/setup/upgrade/1.0/flyspray-install.xml b/setup/upgrade/1.0/flyspray-install.xml
new file mode 100644
index 0000000..ee7102b
--- /dev/null
+++ b/setup/upgrade/1.0/flyspray-install.xml
@@ -0,0 +1,1373 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="user_emails">
+ <descr>multiple addresses for users. subject to change in FS1.1+!</descr>
+ <field name="id" type="I" size="5"></field>
+ <field name="email_address" type="C" size="100"></field>
+ <field name="oauth_uid" type="C" size="255">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_provider" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ </table>
+ <table name="tags">
+ <desc>Do not use this table. pre FS1.0-beta table</desc>
+ <field name="task_id" type="I" size="5"></field>
+ <field name="tag" type="C" size="100"></field>
+ </table>
+ <table name="admin_requests">
+ <descr>Pending requests for admins and PMs to attend to</descr>
+ <field name="request_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="submitted_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="request_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reason_given" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_submitted" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolved_by" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_resolved" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="deny_reason" type="C" size="255" />
+ <index name="resolved_project">
+ <col>resolved_by</col>
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="assigned">
+ <descr>Who is assigned what task</descr>
+ <field name="assigned_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_user">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="attachments">
+ <descr>List the names and locations of files attached to tasks</descr>
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <descr>Table to cache RSS/Atom feeds</descr>
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="I" size="11">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="comments">
+ <descr>task comments</descr>
+ <field name="comment_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_text" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_comments">
+ <col>task_id</col>
+ </index>
+ <index name="user_id_comments">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="dependencies">
+ <descr>Task inter-dependencies</descr>
+ <field name="depend_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dep_task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_deps">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>dep_task_id</col>
+ </index>
+ </table>
+ <table name="effort">
+ <descr>log of time spent on tasks</descr>
+ <field name="effort_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_timestamp" type="I" size="11"/>
+ <field name="end_timestamp" type="I" size="11"/>
+ <field name="effort" type="I" size="15">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_effort">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <descr>User Groups for the Flyspray bug killer</descr>
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_estimated_effort" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_current_effort_done" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="track_effort" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_multiple_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_roadmap" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_groups_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="history">
+ <descr>log of Flyspray activities</descr>
+ <field name="history_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="event_type" type="I" size="2">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="field_changed" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="old_value" type="X" />
+ <field name="new_value" type="X" />
+ <index name="idx_task_id">
+ <col>task_id</col>
+ </index>
+ <index name="idx_event_date">
+ <col>event_date</col>
+ </index>
+ <index name="idx_event_type">
+ <col>event_type</col>
+ </index>
+ </table>
+ <table name="list_category">
+ <descr>hierarchic task categories</descr>
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <descr>Operating system list for the Flyspray bug killer</descr>
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>os_name</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <descr>task close reasons</descr>
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>resolution_name</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <descr>List of possible task statuses</descr>
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>status_name</col>
+ </index>
+ </table>
+ <table name="list_tag">
+ <descr>definition of tags/labels for tasks</descr>
+ <field name="tag_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="tag_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="class" type="C" size="100"/>
+ <index name="tag_name">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>tag_name</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <descr>List of possible task types</descr>
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>tasktype_name</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <descr>list of project versions/milestones/software release versions</descr>
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version_name">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>version_name</col>
+ </index>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="notification_messages">
+ <descr>Notification body and subject</descr>
+ <field name="message_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_subject" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="message_body" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time_created" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="notification_recipients">
+ <descr>Notification recipient list</descr>
+ <field name="recipient_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="message_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_method" type="C" size="1">
+ <NOTNULL/>
+ </field>
+ <field name="notify_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="notifications">
+ <descr>Extra task notification registrations are stored here</descr>
+ <field name="notify_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_notifs">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <descr>global settings of Flyspray</descr>
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="X" />
+ </table>
+ <table name="projects">
+ <descr>project settings</descr>
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="visible_fields" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="others_viewroadmap" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ <field name="disp_intro" type="I" size="1">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="use_effort_tracking" type="I" size="1">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_due_version" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value="Undecided"/>
+ </field>
+ <field name="pages_intro_msg" type="C" size="80">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ <field name="hours_per_manday" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="estimated_effort_format" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="current_effort_done_format" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_order_by" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="id"/>
+ </field>
+ <field name="default_order_by_dir" type="C" size="5">
+ <NOTNULL/>
+ <DEFAULT value="desc"/>
+ </field>
+ <field name="custom_style" type="C" size="32">
+ </field>
+ <field name="freetagging" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="1"/>>
+ </field>
+ </table>
+ <table name="registrations">
+ <descr>table for yet unconfirmed user registrations</descr>
+ <field name="reg_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="reg_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="confirm_code" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="related">
+ <descr>contains loose task relations to another task or task duplicates</descr>
+ <field name="related_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="this_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="related_task" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_duplicate" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="this_task">
+ <UNIQUE/>
+ <col>this_task</col>
+ <col>related_task</col>
+ <col>is_duplicate</col>
+ </index>
+ </table>
+ <table name="reminders">
+ <descr>scheduled task reminders</descr>
+ <field name="reminder_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="to_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="from_user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="how_often" type="I" size="12">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_sent" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="reminder_message" type="X">
+ <NOTNULL/>
+ </field>
+ </table>
+ <table name="searches">
+ <descr>Saves custom searches of users</descr>
+ <field name="id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="name" type="C" size="50">
+ <NOTNULL/>
+ </field>
+ <field name="search_string" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <desc>main table for storing tasks</desc>
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="supertask_id" type="I" size="10">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="list_order" type="I" size="3">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="estimated_effort" type="I" size="3">
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ <index name="task_project_super">
+ <col>project_id</col>
+ <col>supertask_id</col>
+ </index>
+ <index name="task_super">
+ <col>supertask_id</col>
+ <col>task_id</col>
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="task_tag">
+ <desc>join table to add tags/labels to tasks</desc>
+ <field name="task_id" type="I" size="5">
+ <KEY/>
+ </field>
+ <field name="tag_id" type="I" size="5">
+ <KEY/>
+ </field>
+ <index name="task_id_tag">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>tag_id</col>
+ </index>
+ </table>
+ <table name="users">
+ <descr>user accounts of Flyspray</descr>
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="128"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_uid" type="C" size="255">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_provider" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="profile_image" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="hide_my_email" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_online" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_login" type="I" size="11">
+ <descr>last login of a user</descr>
+ <NULL/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="users_in_groups">
+ <descr>Which users are in which groups</descr>
+ <field name="record_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_id_uig">
+ <UNIQUE/>
+ <col>group_id</col>
+ <col>user_id</col>
+ </index>
+ <index name="user_id_uig">
+ <col>user_id</col>
+ </index>
+ </table>
+ <table name="votes">
+ <descr>votes for tasks</descr>
+ <field name="vote_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_votes">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="links">
+ <descr>link a resource (e.g. an URL) with a task - in a more structured way than the task description text area.</descr>
+ <field name="link_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="url" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="added_by" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NONULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_links">
+ <col>task_id</col>
+ </index>
+ </table>
+ <sql>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Admin', 'Members have unlimited access to all functionality', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Developers', 'Global Developers for all projects', 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Reporters', 'Open new tasks / add comments in all projects', 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Basic', 'Members can login, relying upon Project permissions only', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Pending', 'Users who are awaiting approval of their accounts', 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);</query>
+ <query>INSERT INTO groups VALUES (DEFAULT, 'Project Managers', 'Permission to do anything related to the Default Project', 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);</query>
+ <query>INSERT INTO history VALUES (DEFAULT, 1, 1, 1130024797, 1, '', '', '');</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'Backend / Core', 1, 0, 2, 3);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 0, 'root', 0, 0, 1, 2);</query>
+ <query>INSERT INTO list_category VALUES (DEFAULT, 1, 'root', 0, 0, 1, 4);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'All', 1, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Windows', 2, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Linux', 3, 1);</query>
+ <query>INSERT INTO list_os VALUES (DEFAULT, 1, 'Mac OS', 4, 1);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Not a bug', 1, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t fix', 2, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Won''t implement', 3, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Works for me', 4, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Deferred', 5, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Duplicate', 6, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Fixed', 7, 1, 0);</query>
+ <query>INSERT INTO list_resolution VALUES (DEFAULT, 'Implemented', 8, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Bug Report', 1, 1, 0);</query>
+ <query>INSERT INTO list_tasktype VALUES (DEFAULT, 'Feature Request', 2, 1, 0);</query>
+ <query>INSERT INTO list_version VALUES (DEFAULT, 1, 'Development', 1, 1, 2);</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Unconfirmed', 1, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('New', 2, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Assigned', 3, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Researching', 4, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Waiting on Customer', 5, 1, 0)</query>
+ <query>INSERT INTO list_status (status_name, list_position, show_in_list, project_id) VALUES ('Requires testing', 6, 1, 0)</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'fs_ver', '1.0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'logo', 'flyspray_small.png');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'gravatars', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'emailNoHTML', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_port', '5222');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_username', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_password', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_group', '4');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'user_notify', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'admin_email', 'flyspray@example.com');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'lang_code', 'en');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'need_approval', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'spam_proof', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_project', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_entry', 'index');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'dateformat_extended', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'anon_reg', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'global_theme', 'CleanFS');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_columns', 'id category tasktype priority severity summary status progress');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'visible_fields', 'tasktype category severity priority status private assignedto reportedin dueversion duedate progress os votes');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_server', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_user', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'smtp_pass', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'page_title', 'Flyspray::');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'notify_registration', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'jabber_ssl', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'last_update_check', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'intro_message', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'cache_feeds', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'disable_lostpw', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'disable_changepw', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'days_before_alert', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'hide_emails', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'pages_welcome_msg', 'index');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'active_oauths', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'only_oauth_reg', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'enable_avatars', '1');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'max_avatar_size', '50');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_order_by', 'id');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'default_order_by_dir', 'desc');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'url_rewriting', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'max_vote_per_day', '2');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'votes_per_project', '10');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'custom_style', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'general_integration', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'footer_integration', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'repeat_password', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'repeat_emailaddress', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'massops', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'captcha_securimage', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'captcha_recaptcha', '0');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'captcha_recaptcha_sitekey', '');</query>
+ <query>INSERT INTO prefs VALUES (DEFAULT, 'captcha_recaptcha_secret', '');</query>
+ <query>INSERT INTO projects VALUES (DEFAULT, 'Default Project', 'CleanFS', 0, 'Welcome to your first Flyspray project! We hope that Flyspray provides you with many hours of increased productivity. If you have any issues, go to http://flyspray.org . You can customise this message [[?do=pm&amp;project=1|here]]', 1, 'id category tasktype priority severity summary status progress', 'tasktype category severity priority status private assignedto reportedin dueversion duedate progress os votes', 1, 1, 0, '', '', NULL, '0', NULL, NULL, '', 'en', 0, 0, 0, NULL, 'index', 0, 0,'Undecided', '', 0, 0, 0, 'id desc', 'DESC', '', 1 );</query>
+ <query>INSERT INTO tasks VALUES (DEFAULT, 1, 1, 1452600000, 1, 0, 0, 0, ' ', 'Sample Task', 'This isn''t a real task. You should close it and start opening some real tasks.', 2, 1, 1, 1, 0, 1, 1, 2, 0, 0, 0, 0, 0, '', '0', 0, 0, 0);</query>
+ <query>INSERT INTO users VALUES (DEFAULT, 'super', '1b3231655cebb7a1f783eddf27d254ca', 'Superuser', '', '', 0, 1, 1, '', '', '', 25, 0, 0, 0, 0, 'en', '0', '', '', 0, 0, NULL);</query>
+ <query>INSERT INTO users_in_groups VALUES (DEFAULT, 1, 1);</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/1.0/flyspray.conf.php b/setup/upgrade/1.0/flyspray.conf.php
new file mode 100644
index 0000000..2b2e7f4
--- /dev/null
+++ b/setup/upgrade/1.0/flyspray.conf.php
@@ -0,0 +1,48 @@
+; <?php die( 'Do not access this page directly.' ); ?>
+
+; This is the Flysplay configuration file. It contains the basic settings
+; needed for Flyspray to operate. All other preferences are stored in the
+; database itself and are managed directly within the Flyspray admin interface.
+; You should consider putting this file somewhere that isn't accessible using
+; a web browser, and editing header.php to point to wherever you put this file.
+
+
+
+[general]
+cookiesalt = "f1s" ; Randomisation value for cookie encoding
+output_buffering = "on" ; Available options: "off", "on" and "gzip"
+address_rewriting = "0" ; Boolean. 0 = off, 1 = on.
+reminder_daemon = "0" ; Boolean. 0 = off, 1 = on.
+passwdcrypt = "" ; Available options: "" - chooses best default (currently "crypt"), "crypt", "md5", "sha1", "sha512" Note: md5 and sha1 are considered insecure for hashing passwords (statement date: 2016)
+doku_url = "http://en.wikipedia.org/wiki/" ; URL to your external wiki for [[dokulinks]] in FS
+syntax_plugin = "none" ; Plugin name for syntax format for task description and other textarea fields, "none" for the default ckeditor (or any nonexistent plugin folder name), popular alternative: "dokuwiki", see plugins/ directory
+update_check = "1" ; Boolean. 0 = off, 1 = on.
+
+securecookies = false ; Boolean false or true. You can set it only to true if you have a HTTPS Flyspray setup fully working with valid SSL/TLS certificate.
+; If set to true the Flyspray session cookies should be sent only over HTTPS, never HTTP.
+; Check cookie properties within devtools (press F12) of modern (year 2015) webbrowsers.
+
+[database]
+dbtype = "mysql" ; Type of database ("mysql" or "pgsql" are currently supported)
+dbhost = "localhost" ; Name or IP of your database server
+dbname = "DBNAME" ; The name of the database
+dbuser = "DBUSER" ; The user to access the database
+dbpass = "DBPASS" ; The password to go with that username above
+dbprefix = "flyspray_" ; Prefix of the Flyspray tables
+
+[attachments]
+zip = "application/zip" ; MIME-type for ZIP files
+
+[oauth]
+github_secret = ""
+github_id = ""
+github_redirect = "YOURDOMAIN/index.php?do=oauth&provider=github"
+google_secret = ""
+google_id = ""
+google_redirect = "YOURDOMAIN/index.php?do=oauth&provider=google"
+facebook_secret = ""
+facebook_id = ""
+facebook_redirect = "YOURDOMAIN/index.php?do=oauth&provider=facebook"
+microsoft_secret = ""
+microsoft_id = ""
+microsoft_redirect = "YOURDOMAIN/index.php"
diff --git a/setup/upgrade/1.0/upgrade.info b/setup/upgrade/1.0/upgrade.info
new file mode 100644
index 0000000..a1510a5
--- /dev/null
+++ b/setup/upgrade/1.0/upgrade.info
@@ -0,0 +1,70 @@
+[defaultupgrade]
+1="upgrade.xml"
+2="varchartotext.php"
+
+[develupgrade]
+1="upgrade.xml"
+2="varchartotext.php"
+
+[fsprefs]
+fs_ver="1.0"
+logo="flyspray_small.png"
+jabber_server=""
+jabber_port="5222"
+jabber_username=""
+jabber_password=""
+anon_group="0"
+user_notify="1"
+admin_email="flyspray@example.com"
+lang_code="en"
+need_approval="0"
+spam_proof="1"
+default_project="1"
+default_entry="index"
+dateformat=""
+dateformat_extended=""
+anon_reg="1"
+page_title="Flyspray:: "
+notify_registration="0"
+jabber_ssl="0"
+last_update_check="0"
+cache_feeds="1"
+global_theme="CleanFS"
+visible_columns="id supertask project tasktype severity summary status progress"
+visible_fields="supertask tasktype category severity priority status private assignedto reportedin dueversion duedate progress os votes"
+smtp_server=""
+smtp_user=""
+smtp_pass=""
+lock_for="5"
+email_ssl="0"
+email_tls="0"
+gravatars="0"
+emailNoHTML="0"
+default_timezone="0"
+intro_message=""
+disable_lostpw="0"
+disable_changepw="0"
+days_before_alert="0"
+hide_emails="1"
+pages_welcome_msg="index"
+active_oauths=""
+only_oauth_reg="0"
+enable_avatars="1"
+max_avatar_size="50"
+default_order_by="id"
+default_order_by_dir="desc"
+url_rewriting="0"
+max_vote_per_day="2"
+votes_per_project="10"
+custom_style=""
+general_integration=""
+footer_integration=""
+repeat_password="0"
+repeat_emailaddress="0"
+massops="0"
+; Would like to see captchastuff be optional prefs, but
+; current upgrade logic wipes such additional prefs if not listed here too.
+captcha_securimage="0"
+captcha_recaptcha="0"
+captcha_recaptcha_sitekey=""
+captcha_recaptcha_secret=""
diff --git a/setup/upgrade/1.0/upgrade.xml b/setup/upgrade/1.0/upgrade.xml
new file mode 100644
index 0000000..0a48596
--- /dev/null
+++ b/setup/upgrade/1.0/upgrade.xml
@@ -0,0 +1,902 @@
+<?xml version="1.0"?>
+<schema version="0.3">
+ <table name="attachments">
+ <descr>List the names and locations of files attached to tasks</descr>
+ <field name="attachment_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="orig_name" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_name" type="C" size="30">
+ <NOTNULL/>
+ </field>
+ <field name="file_type" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="file_size" type="I" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="added_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_attachments">
+ <col>task_id</col>
+ <col>comment_id</col>
+ </index>
+ </table>
+ <table name="cache">
+ <field name="id" type="I" size="6">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="type" type="C" size="4">
+ <NOTNULL/>
+ </field>
+ <field name="content" type="XL">
+ <NOTNULL/>
+ </field>
+ <field name="topic" type="I" size="11">
+ <NOTNULL/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="max_items" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="cache_type">
+ <UNIQUE/>
+ <col>type</col>
+ <col>topic</col>
+ <col>project_id</col>
+ <col>max_items</col>
+ </index>
+ <index name="cache_type_topic">
+ <col>type</col>
+ <col>topic</col>
+ </index>
+ </table>
+ <table name="users">
+ <field name="user_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="user_name" type="C" size="32">
+ <NOTNULL/>
+ </field>
+ <field name="user_pass" type="C" size="128"/>
+ <field name="real_name" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="jabber_id" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="email_address" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="notify_type" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_own" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="account_enabled" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="dateformat" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="dateformat_extended" type="C" size="30">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="magic_url" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="tasks_perpage" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="register_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="time_zone" type="I" size="6">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="login_attempts" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lock_until" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_uid" type="C" size="255">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_provider" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="profile_image" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="hide_my_email" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_online" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_login" type="I" size="11">
+ <descr>last login of a user</descr>
+ <NULL/>
+ </field>
+ <index name="user_name">
+ <UNIQUE/>
+ <col>user_name</col>
+ </index>
+ </table>
+ <table name="user_emails">
+ <field name="id" type="I" size="5"></field>
+ <field name="email_address" type="C" size="100"></field>
+ <field name="oauth_uid" type="C" size="255">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="oauth_provider" type="C" size="10">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ </table>
+ <table name="effort">
+ <field name="effort_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="user_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="start_timestamp" type="I" size="11"/>
+ <field name="end_timestamp" type="I" size="11"/>
+ <field name="effort" type="I" size="15">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_effort">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="groups">
+ <field name="group_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="group_name" type="C" size="20">
+ <NOTNULL/>
+ </field>
+ <field name="group_desc" type="C" size="150">
+ <NOTNULL/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_admin" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="manage_project" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="open_new_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="modify_all_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_own_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_comments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="create_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="delete_attachments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_history" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="close_other_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="assign_others_to_self" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_to_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_reports" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_votes" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="edit_assignments" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_as_assignees" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_estimated_effort" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_current_effort_done" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="track_effort" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="group_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="add_multiple_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_roadmap" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_own_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="view_groups_tasks" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="group_name">
+ <UNIQUE/>
+ <col>group_name</col>
+ <col>project_id</col>
+ </index>
+ <index name="belongs_to_project">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="prefs">
+ <descr>global settings of Flyspray</descr>
+ <field name="pref_id" type="I" size="1">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="pref_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="pref_value" type="X" />
+ </table>
+ <table name="projects">
+ <field name="project_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_title" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="theme_style" type="C" size="20">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_cat_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="intro_message" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="project_is_active" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="visible_columns" type="C" size="255">
+ <NOTNULL/>
+ </field>
+ <field name="visible_fields" type="C" size="255">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="others_view" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="others_viewroadmap" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_open" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="notify_email" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_jabber" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_reply" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_types" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="feed_img_url" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="feed_description" type="X">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="notify_subject" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="lang_code" type="C" size="10">
+ <NOTNULL/>
+ </field>
+ <field name="comment_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="auto_assign" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_updated" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_task" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="default_entry" type="C" size="8">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ <field name="disp_intro" type="I" size="1">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="use_effort_tracking" type="I" size="1">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_due_version" type="C" size="40">
+ <NOTNULL/>
+ <DEFAULT value="Undecided"/>
+ </field>
+ <field name="pages_intro_msg" type="C" size="80">
+ <NOTNULL/>
+ <DEFAULT value="index"/>
+ </field>
+ <field name="hours_per_manday" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="estimated_effort_format" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="current_effort_done_format" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="default_order_by" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value="id"/>
+ </field>
+ <field name="default_order_by_dir" type="C" size="5">
+ <NOTNULL/>
+ <DEFAULT value="desc"/>
+ </field>
+ <field name="custom_style" type="C" size="32">
+ </field>
+ <field name="freetagging" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ </table>
+ <table name="tasks">
+ <field name="task_id" type="I" size="10">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_type" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_opened" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="opened_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="is_closed" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_closed" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closed_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closure_comment" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_summary" type="C" size="100">
+ <NOTNULL/>
+ </field>
+ <field name="detailed_desc" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="item_status" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="resolution_reason" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="1"/>
+ </field>
+ <field name="product_category" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="product_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="closedby_version" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="operating_system" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_severity" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="task_priority" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_by" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="last_edited_time" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="percent_complete" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="mark_private" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="due_date" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="anon_email" type="C" size="100">
+ <NOTNULL/>
+ <DEFAULT value=""/>
+ </field>
+ <field name="task_token" type="C" size="32">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="supertask_id" type="I" size="10">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="list_order" type="I" size="3">
+ <DEFAULT value="0"/>
+ </field>
+ <field name="estimated_effort" type="I" size="3">
+ <DEFAULT value="0"/>
+ </field>
+ <index name="attached_to_project">
+ <col>project_id</col>
+ </index>
+ <index name="task_severity">
+ <col>task_severity</col>
+ </index>
+ <index name="task_type">
+ <col>task_type</col>
+ </index>
+ <index name="product_category">
+ <col>product_category</col>
+ </index>
+ <index name="item_status">
+ <col>item_status</col>
+ </index>
+ <index name="is_closed">
+ <col>is_closed</col>
+ </index>
+ <index name="closedby_version">
+ <col>closedby_version</col>
+ </index>
+ <index name="due_date">
+ <col>due_date</col>
+ </index>
+ <index name="task_project_super">
+ <col>project_id</col>
+ <col>supertask_id</col>
+ <col>list_order</col>
+ </index>
+ <index name="task_super">
+ <col>supertask_id</col>
+ <col>list_order</col>
+ </index>
+ </table>
+ <table name="links">
+ <field name="link_id" type="I" size="11">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="task_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="comment_id" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="url" type="X">
+ <NOTNULL/>
+ </field>
+ <field name="added_by" type="I" size="11">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="date_added" type="I" size="11">
+ <NONULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="task_id_links">
+ <col>task_id</col>
+ </index>
+ </table>
+ <table name="tags">
+ <field name="task_id" type="I" size="5"></field>
+ <field name="tag" type="C" size="100"></field>
+ </table>
+ <table name="list_category">
+ <field name="category_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="category_owner" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="lft" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <field name="rgt" type="I" size="10">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ <UNSIGNED/>
+ </field>
+ <index name="project_id_cat">
+ <col>project_id</col>
+ </index>
+ </table>
+ <table name="list_os">
+ <field name="os_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="os_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_os">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>os_name</col>
+ </index>
+ </table>
+ <table name="list_resolution">
+ <field name="resolution_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="resolution_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_res">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>resolution_name</col>
+ </index>
+ </table>
+ <table name="list_status">
+ <field name="status_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="status_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_status">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>status_name</col>
+ </index>
+ </table>
+ <table name="list_tag">
+ <field name="tag_id" type="I" size="5">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="5">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="tag_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="class" type="C" size="100"/>
+ <index name="project_id_tag">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>tag_name</col>
+ </index>
+ </table>
+ <table name="list_tasktype">
+ <field name="tasktype_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="tasktype_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_tt">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>tasktype_name</col>
+ </index>
+ </table>
+ <table name="list_version">
+ <field name="version_id" type="I" size="3">
+ <KEY/>
+ <AUTOINCREMENT/>
+ </field>
+ <field name="project_id" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_name" type="C" size="40">
+ <NOTNULL/>
+ </field>
+ <field name="list_position" type="I" size="3">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="show_in_list" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <field name="version_tense" type="I" size="1">
+ <NOTNULL/>
+ <DEFAULT value="0"/>
+ </field>
+ <index name="project_id_version_name">
+ <UNIQUE/>
+ <col>project_id</col>
+ <col>version_name</col>
+ </index>
+ <index name="project_id_version">
+ <col>project_id</col>
+ <col>version_tense</col>
+ </index>
+ </table>
+ <table name="task_tag">
+ <field name="task_id" type="I" size="5">
+ <KEY/>
+ </field>
+ <field name="tag_id" type="I" size="5">
+ <KEY/>
+ </field>
+ <index name="task_id_tag">
+ <UNIQUE/>
+ <col>task_id</col>
+ <col>tag_id</col>
+ </index>
+ </table>
+ <sql>
+ <query>UPDATE projects SET visible_fields = 'tasktype category severity priority status private assignedto reportedin dueversion duedate progress os votes' WHERE visible_fields = ''</query>
+ <query>UPDATE projects SET theme_style = 'CleanFS'</query>
+ <query>UPDATE prefs SET pref_value = 'CleanFS' WHERE pref_name = 'global_theme'</query>
+ </sql>
+</schema>
diff --git a/setup/upgrade/1.0/varchartotext.php b/setup/upgrade/1.0/varchartotext.php
new file mode 100644
index 0000000..3bba5ea
--- /dev/null
+++ b/setup/upgrade/1.0/varchartotext.php
@@ -0,0 +1,17 @@
+<?php
+if ($conf['database']['dbtype'] == 'pgsql') {
+ $db->query('ALTER TABLE {prefs} ALTER COLUMN pref_value TYPE text');
+ $db->query('ALTER TABLE {prefs} ALTER COLUMN pref_value SET DEFAULT \'\'');
+}
+elseif($db->dbtype=='mysqli' || $db->dbtype=='mysql') {
+ $sinfo=$db->dblink->serverInfo();
+ if(isset($sinfo['version']) && version_compare($sinfo['version'], '5.5.3')>=0 ){
+ $db->query('ALTER TABLE {prefs} CHANGE `pref_value` `pref_value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL');
+ }else{
+ $db->query('ALTER TABLE {prefs} CHANGE `pref_value` `pref_value` TEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL');
+ }
+}
+else{
+ $db->query('ALTER TABLE {prefs} CHANGE `pref_value` `pref_value` TEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL');
+}
+?>
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..287a16b
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,19 @@
+<?php
+/*
+function loader($class) {
+ $file = $class . '.php';
+ if (file_exists($file)) {
+ require $file;
+ }
+}
+
+spl_autoload_register('loader');
+*/
+
+define('IN_FS', true); # for passing some checks in Flyspray files
+
+if(is_readable('vendor/autoload.php')){
+ // Use composer autoloader
+ require 'vendor/autoload.php';
+}
+?>
diff --git a/tests/createTestData.php b/tests/createTestData.php
new file mode 100644
index 0000000..fae012b
--- /dev/null
+++ b/tests/createTestData.php
@@ -0,0 +1,702 @@
+<?php
+
+/*
+* For Flyspray development tests only!
+*
+* Script for simulating Flyspray with many testdata like many projets, hundreds of users, thousands of tasks and thousands comments ..
+*
+* TODO:
+# replace some hardcoded IDs by getting the appropriate ids from db instead (autoincrement values of user groups for example)
+# add some supertask_id to some tasks (use flyspray functions/api that should check if legal - no loops of any hop depth, permissions, cross project, existing task)
+# add some related task relations
+# add some dependencies to some tasks (flyspray should check legal - watch for logic deadlocks)
+# add some 'is duplicate of' to some tasks (only when closing)
+# Change: (simulate more daily managing work with tasks)
+# * status of some tasks
+# * progress of some tasks
+# * category of some tasks
+# * reported version of some tasks
+# * milestone version of some tasks
+# close some tasks with resolution status and text
+# assign dev users to tasks
+# add 'notify me' to some tasks
+# add reminders to some tasks
+# add user votes to some tasks
+# add some close requests by some users
+# deactivate and delete some users
+# add some users 'lost password' requests
+# add some failed user login attempts
+# add/change some user avatar gif/png/jpg uploads
+# add some real attachments with uploaded files of different type (generated .docx,.odt,.tex,.ps,.pdf,.txt,.xml,.sql)
+# change some task descriptions:
+# * add some kyrilic, greek, arab, chinese, emoji task description and task summaries
+# * add some code segments for GeShi - some languages are harder to parse than others!
+# tags:
+# * remove some tags from tasks
+
+* Maybe someday parts of it are moved to phpunit testing in future, so can automatic tested by travis-ci.
+*
+*/
+
+# Only use this script after a fresh Flyspray install (1 project id=1 , 1 user with id=1, 1 task with id=1 existing)
+# choose dokuwiki during setup
+#createTestData();
+
+# temp: just wrapped that code in a function so it is not run by phpunit
+function createTestData(){
+ # quick safety
+ exit;
+
+ if (PHP_SAPI !== 'cli') {
+ die('Please call it only from commandline');
+ }
+
+ global $db, $fs, $conf, $proj, $user, $notify;
+
+ # Use this only on a new test installation, code does not work on
+ # an existing one, and never will.
+
+
+ ### Simulation Settings ###
+ # Set conservative data as default, setup bigger values for further performance tests.
+ # maybe setting moved out of this function to make performance graphs with multiple runs..
+
+ $years=10;
+ $timerange=3600*24*365*$years;
+
+ $maxprojects = 3;
+ # ca 100 tasks/sec, 100 comments/sec on an old laptop with Flyspray 1.0-rc7 with mysqli setup in a virtual machine as thumb rule
+ $maxtasks = 1000; # absolute number, e.g. 1000
+ $maxcomments = 1000; # absolute number, e.g. 10000
+ $maxattachments = 500; # only emulated yet, e.g. 500
+ $maxversions = 3; # per project, e.g. 5
+ $maxcorporates = 3; # mmhh
+
+ # spread some user with different permissions
+ $maxcorporateusers = 20; # absolute number, e.g. 20
+ $maxadmins = 2;
+ $maxmanagers = 2;
+ $maxdevelopers = 500; # a bit higher for due they have more rights, can have more relations with tasks
+ $maxindividualusers = 50;
+ $maxviewers = 50;
+
+ ### End of Simulation Settings ###
+
+ $subjects[] = "help me";
+
+ error_reporting(E_ALL);
+
+ define('IN_FS', 1);
+
+ $now=microtime(true); # simple performance times
+ $start=$now;
+ require_once dirname(__FILE__) . '/../includes/fix.inc.php';
+ require_once dirname(__FILE__) . '/../includes/class.flyspray.php';
+ require_once dirname(__FILE__) . '/../includes/constants.inc.php';
+ require_once dirname(__FILE__) . '/../includes/i18n.inc.php';
+ require_once dirname(__FILE__) . '/../includes/class.tpl.php';
+ require_once dirname(__FILE__) . '/../vendor/autoload.php';
+
+ $conf = parse_ini_file('../flyspray.conf.php', true) or die('Cannot open config file.');
+
+ # faster generate new users with very weak, but fast password hash generation, only for this test.
+ $conf['general']['passwdcrypt']='md5';
+
+ error_reporting(E_ALL); # overwrite settings in fix.inc.php
+
+ $db = new Database;
+ $db->dbOpenFast($conf['database']);
+ $RANDOP = 'RAND()';
+ if ($db->dblink->dataProvider == 'postgres') {
+ $RANDOP = 'RANDOM()';
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6)." s database connection\n";
+
+
+ $fs = new Flyspray();
+ $user = new User(1);
+ $proj = new Project(1);
+ $notify = new Notifications;
+ load_translations();
+
+ $last=$now;$now=microtime(true);echo round($now-$last,6)." s Flyspray objects\n";
+
+ for ($i = 1; $i <= $maxadmins; $i++) {
+ $user_name = "admin$i";
+ $real_name = "Admin $i";
+ $password = $user_name;
+ $time_zone = 0; // Assign different one!
+ $email = null; // $user_name . '@example.com';
+
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, 1, 1);
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxadmins." admins created\n";
+
+ for ($i = 1; $i <= $maxmanagers; $i++) {
+ $user_name = "pm$i";
+ $real_name = "Project Manager $i";
+ $password = $user_name;
+ $time_zone = 0; // Assign different one!
+ $email = null; // $user_name . '@example.com';
+
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, 2, 1);
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxmanagers." managers created\n";
+
+ $db->query('UPDATE {projects} SET project_is_active = 0 WHERE project_id = 1');
+ // Show more columns by default, trying to make database or flyspray crash under stress.
+ $db->query("UPDATE {prefs} SET pref_value = 'id project category tasktype severity summary status openedby dateopened progress comments attachments votes' WHERE pref_name = 'visible_columns'");
+
+ // Add 3 different global developer groups with different
+ // view rights first, then assign developers to them at random.
+
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open,view_comments,manage_project,view_tasks, view_groups_tasks, view_own_tasks,open_new_tasks,modify_own_tasks) "
+ . "VALUES('Developer Group 1', 'Developer Group 1', 0, 1, 1, 0, 1, 1, 1, 1, 1)");
+
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open,view_comments,manage_project,view_tasks, view_groups_tasks, view_own_tasks,open_new_tasks,modify_own_tasks) "
+ . "VALUES('Developer Group 2', 'Developer Group 2', 0, 1, 1, 0, 0, 1, 1, 1, 1)");
+
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open,view_comments,manage_project,view_tasks, view_groups_tasks, view_own_tasks,open_new_tasks,modify_own_tasks) "
+ . "VALUES('Developer Group 3', 'Developer Group 3', 0, 1, 1, 0, 0, 0, 1, 1, 1)");
+
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '."3 global dev groups created\n";
+
+
+ // Add also general groups for corporate users, individual users and viewers.
+ // Allow only login. Not so relaxed with them bastards.
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open) "
+ . "VALUES('Corporate Users', 'Corporate Users', 0, 1)");
+
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open) "
+ . "VALUES('Trusted Users', 'Trusted Users', 0, 1)");
+
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,group_open) "
+ . "VALUES('Non-trusted Users', 'Non-trusted Users', 0, 1)");
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '."3 global user groups created\n";
+
+
+ for ($i = 1; $i <= $maxdevelopers; $i++) {
+ $user_name = "dev$i";
+ $real_name = "Developer $i";
+ $password = $user_name;
+ $time_zone = 0;
+ $email = null; // $user_name . '@example.com';
+ $group = rand(7, 9);
+
+ if($i==1){
+ $registered = time() - rand($timerange-3600, $timerange);
+ }else{
+ $registered = $prevuserreg + rand(0, 0.9*2*$timerange/$maxdevelopers); # 0.9 to be sure not in future
+ }
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, $group, 1);
+ # a bit weired, but simple UPDATE {users} SET register_date = ? WHERE user_id = (SELECT MAX(user_id) FROM {users}) doesn't work for mysql
+ $db->query('UPDATE {users} SET register_date = ? WHERE user_id = (SELECT user_id FROM (SELECT * FROM {users}) AS tempusers ORDER BY user_id DESC LIMIT 1)',
+ array($registered) );
+ $prevuserreg=$registered;
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxdevelopers." dev users created\n";
+
+ $tags=array(
+ array('name'=>'blue', 'color'=>'#00c'),
+ array('name'=>'red', 'color'=>'#c00'),
+ array('name'=>'green', 'color'=>'#090'),
+ array('name'=>'rosa', 'color'=>'#f9f'),
+ array('name'=>'lila', 'color'=>'#c0c'),
+ array('name'=>'black', 'color'=>'#000'),
+ array('name'=>'brown', 'color'=>'#c90'),
+ array('name'=>'darkred', 'color'=>'#600'),
+ array('name'=>'darkblue', 'color'=>'#006')
+ );
+
+ // add some projects and some tag definitions
+ $tgcounter=0;
+ $project_id=0;
+ for ($i = 1; $i <= $maxprojects; $i++) {
+ $projname = 'Product '.($i+1);
+
+ $db->query('INSERT INTO {projects}
+ ( project_title, theme_style, intro_message,
+ others_view, anon_open, project_is_active,
+ visible_columns, visible_fields, lang_code,
+ notify_email, notify_jabber, disp_intro)
+ VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)',
+ array($projname, 'CleanFS', "Welcome to $projname", 0, 0,
+ 'id category tasktype severity summary status openedby dateopened progress comments attachments votes',
+ 'supertask tasktype category severity priority status private assignedto reportedin dueversion duedate progress os votes',
+ 'en', '', '', 1)
+ );
+ $project_id=$db->insert_Id();
+ add_project_data($project_id);
+
+ for($t=0; $t<count($tags); $t++){
+ $db->query("INSERT INTO {list_tag} (project_id, tag_name, show_in_list, class) VALUES (?, ?, ?, ?)",
+ array($project_id, $tags[$t]['name'].($i+1), rand(0,1), $tags[$t]['color'])
+ );
+ $tgcounter++;
+ }
+ }
+
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxprojects.' projects created, '.$tgcounter." tags created\n";
+
+ // Assign some developers to project manager or project developer groups
+ for ($i = 1; $i <= $maxprojects; $i++) {
+ $projid = $i + 1;
+ $sql = $db->query('SELECT group_id FROM {groups} WHERE project_id = ? AND manage_project = 1', array($projid));
+ $pmgroup = $db->fetchOne($sql);
+ $sql = $db->query('SELECT group_id FROM {groups} WHERE project_id = ? AND manage_project = 0', array($projid));
+ $pdgroup = $db->fetchOne($sql);
+
+ $pmlimit = intval($maxdevelopers / 100) + rand(-2, 2);
+ $pdlimit = intval($maxdevelopers / 20) + rand(-10, 10);
+ $pmlimit = $pmlimit < 1 ? 1 : $pmlimit;
+ $pdlimit = $pdlimit < 1 ? 1 : $pdlimit;
+
+ $sql = $db->query("SELECT user_id FROM {users_in_groups} WHERE group_id in (7, 8, 9) ORDER BY $RANDOP limit $pmlimit");
+ $pms = $db->fetchCol($sql);
+ $sql = $db->query("SELECT user_id FROM {users_in_groups} WHERE group_id in (8, 9) ORDER BY $RANDOP limit $pdlimit");
+ $pds = $db->fetchCol($sql);
+
+ foreach ($pms as $pm) {
+ $db->query('INSERT INTO {users_in_groups} (user_id, group_id) values (?, ?)', array($pm, $pmgroup));
+ }
+
+ foreach ($pds as $pd) {
+ $check = $db->query('SELECT * FROM {users_in_groups} WHERE user_id = ? AND group_id = ?', array($pd, $pmgroup));
+ if (!$db->countRows($check)) {
+ $db->query('INSERT INTO {users_in_groups} (user_id, group_id) values (?, ?)', array($pd, $pdgroup));
+ }
+ }
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxprojects." projects assigned some developers to project groups\n";
+
+ for ($i = 1; $i <= $maxcorporateusers; $i++) {
+ $user_name = "cu$i";
+ $real_name = "Corporate user $i";
+ $password = $user_name;
+ $time_zone = 0; // Assign different ones!
+ $email = null; // $user_name . '@example.com';
+ $group = 10;
+
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, $group, 1);
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxcorporateusers." corp users created\n";
+
+ // Now, create corporate user groups for some of our projects.
+ // Just %5 change of getting added.
+ for ($i = 1; $i <= $maxcorporates; $i++) {
+ for ($j = 1; $j <= $maxprojects; $j++) {
+ if (rand(1, 20) == 1) {
+ $projid = $j + 1;
+ $db->query("INSERT INTO {groups} "
+ . "(group_name,group_desc,project_id,manage_project,view_tasks, view_groups_tasks, view_own_tasks,open_new_tasks,add_comments,create_attachments,group_open,view_comments) "
+ . "VALUES('Corporate $i', 'Corporate $i Users', $projid, 0, 0, 1, 1, 1, 1, 1,1,1)");
+ $group_id = $db->insert_ID();
+ for ($k = $i; $k <= $maxcorporateusers; $k += $maxcorporates) {
+ $username = "cu$k";
+ $sql = $db->query('SELECT user_id FROM {users} WHERE user_name = ?', array($username));
+ $user_id = $db->fetchOne($sql);
+ $db->query('INSERT INTO {users_in_groups} (user_id, group_id) VALUES (?, ?)', array($user_id, $group_id));
+ }
+ }
+ }
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxcorporates." corporate usergroups created\n";
+
+ // And also those individual users...
+ for ($i = 1; $i <= $maxindividualusers; $i++) {
+ $user_name = "iu$i";
+ $real_name = "Individual user $i";
+ $password = $user_name;
+ $time_zone = 0; // Assign different ones!
+ $email = null; // $user_name . '@example.com';
+ $group = rand(11, 12);
+
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, $group, 1);
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxindividualusers." indi users created\n";
+
+
+ // That's why we need some more global groups with different viewing rights
+ for ($i = 1; $i <= $maxindividualusers; $i++) {
+ $user_name = "basic$i";
+ $real_name = "Basic $i";
+ $password = $user_name;
+ $time_zone = 0; // Assign different ones!
+ $email = null; // $user_name . '@example.com';
+ $group = 4;
+
+ Backend::create_user($user_name, $password, $real_name, '', $email, 0, $time_zone, $group, 1);
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxindividualusers." basic users created\n";
+
+
+ // Must recreate, so rights for new projects get loaded. Otherwise,
+ // even first user in database can't create tasks.
+ $user = new User(1);
+
+ # for generating pseudo task description
+ $vocals=array('e','e','a','i','o','u'); # e most freq vocal in western languages
+ $conso=array('s','sch','st','z','r','l','b','p','g','k','m','n','v','w','d','t','qu');
+ $wordlen=array(3,12);
+ $clauselen=array(3,8); # 3: words per sub clause
+ $commas=array(0,2); # 1: commas per sentence
+ $paralen=array(1,5); # sentences per paragraph
+ $parts=array(1,10); # parts per task description (paragraphs or lists or code..)
+ $codes=array('','php','xml','sql','html');
+
+ echo "Creating $maxtasks tasks: ";
+ for ($i = 1; $i <= $maxtasks; $i++) {
+ $project_id = rand(2, $maxprojects+1); # project id 1 is default project which we exclude here
+ if ($i==1) {
+ $opened = time() - rand($timerange-(30*24*3600), $timerange);
+ } else {
+ $opened = $prevtaskopened + rand(0, 0.9*2*$timerange/$maxtasks); # 0.9 to be sure not in future
+ }
+ // Find someone who is allowed to open a task, do not use global groups
+ $sql = $db->query("SELECT u.user_id
+ FROM {users} u
+ JOIN {users_in_groups} uig ON u.user_id=uig.user_id
+ JOIN {groups} g ON g.group_id = uig.group_id AND g.open_new_tasks = 1 AND (g.project_id = 0 OR g.project_id = ?)
+ WHERE g.group_id NOT IN (1)
+ AND u.register_date < ?
+ ORDER BY $RANDOP LIMIT 1", array($project_id, $opened)
+ );
+ $reporter = $db->fetchOne($sql);
+ $sql = $db->query("SELECT category_id FROM {list_category}
+ WHERE project_id = ?
+ AND category_name <> 'root'
+ ORDER BY $RANDOP LIMIT 1",
+ array($project_id));
+ $category = $db->fetchOne($sql);
+ $args = array();
+
+ $args['project_id'] = $project_id;
+ $args['date_opened'] = $opened;
+ // 'last_edited_time' => time(),
+ $args['opened_by'] = $reporter;
+ $args['product_category'] = $category;
+ $args['task_severity'] = rand(1,5); # 5 fixed severities
+ $args['task_priority'] = rand(1,6); # 6 fixed priorities
+ $args['task_type'] = rand(1,2); # 2 global tasktypes after install
+ // 'product_version'
+ // 'operating_system'
+ // 'estimated_effort'
+ // 'supertask_id' - find existing task of project
+
+ $sql = $db->query("SELECT project_title FROM {projects} WHERE project_id = ?",
+ array($project_id));
+ $projectname = $db->fetchOne($sql);
+ $subject = $subjects[rand(0, count($subjects) - 1)];
+ $subject = sprintf($subject, $projectname);
+
+ $args['item_summary'] = "task $i ($subject)";
+
+ $dparts=rand($parts[0],$parts[1]);
+ $descr='';
+ for($p=0;$p<$dparts;$p++){
+ $para='';
+ $type=rand(0,2); # text, list, code
+ if($type==0){
+ $dsent=rand($paralen[0],$paralen[1]);
+ for($s=0;$s<$dsent;$s++){
+ $sent='';
+ $dcommas=rand($commas[0],$commas[1]);
+ for($c=0;$c<$dcommas;$c++){
+ $clausepart='';
+ $dwords=rand($clauselen[0],$clauselen[1]);
+ for($w=0;$w<$dwords;$w++){
+ $v=rand(0,1);
+ $wd='';
+ $dletters=rand($wordlen[0],$wordlen[1]);
+ for($l=0;$l<$dletters;$l++){
+ $wd.= ($v % 2) ? $vocals[rand(0,count($vocals)-1)] : $conso[rand(0,count($conso)-1)];
+ $v++;
+ }
+ if(rand(0,100)<1){
+ $wd='FS#'.rand(2,$i);
+ }
+ if(rand(0,100)<2){
+ $wd='//'.$wd.'//';
+ }
+ if(rand(0,100)<2){
+ $wd='**'.$wd.'**';
+ }
+ if(rand(0,100)<2){
+ $wd='__'.$wd.'__';
+ }
+ $clausepart.=$wd.' ';
+ }
+ $sent.=$clausepart;
+ $sent.=($c+1 < $dcommas) ? ', ': '.';
+ }
+ $para.=$sent;
+ }
+ }elseif($type==1){
+ # dokuwiki list
+ $para.=" * listitem\n * listitem\n * listitem3";
+ }elseif($type==2) {
+ # dokuwiki code
+ $para.='<code '.$codes[rand(0, count($codes)-1)].'> some signs<<y<>>> """</code>';
+ }
+ $descr.=$para."\n\n";
+ }
+ $args['detailed_desc'] = $descr;
+
+ $ok = Backend::create_task($args);
+ if ($ok === 0) {
+ echo "Failed to create task.\n";
+ } else {
+ list($task_id, $token) = $ok;
+ $db->query('UPDATE {tasks} SET opened_by = ?, date_opened = ? WHERE task_id = ?',
+ array($reporter, $opened, $task_id));
+
+ $limit=rand(0,3);
+ $db->query("INSERT INTO {task_tag} (task_id, tag_id)
+ SELECT $task_id, tag_id FROM {list_tag}
+ WHERE project_id=?
+ ORDER BY $RANDOP
+ LIMIT $limit",
+ array($project_id)
+ );
+ }
+ $prevtaskopened=$opened;
+ if($i%500==0){
+ echo $i.' mem:'.memory_get_usage()."\n";
+ }
+ } # end for maxtasks
+
+ $last=$now;$now=microtime(true); echo round($now-$last,6).': '.$maxtasks." tasks created\n";
+
+ echo "Creating $maxcomments comments: \n";
+ $maxtask=$task_id;
+ for ($i = 1; $i <= $maxcomments; $i++) {
+ $taskid = rand(2, $maxtask);
+ $task = Flyspray::getTaskDetails($taskid);
+ $project_id = $task['project_id'];
+ # XXX only allow comments after task created date and also later as existing comments in that task.
+ $added = time() - rand(1, $timerange);
+
+ // Find someone who is allowed to add comment, do not use global groups
+ $sqltext = "SELECT uig.user_id
+ FROM {users_in_groups} uig
+ JOIN {groups} g ON g.group_id = uig.group_id AND g.add_comments = 1
+ AND (g.project_id = 0 OR g.project_id = ?)
+ WHERE g.group_id NOT IN (1, 2, 7, 8, 9)
+ ORDER BY $RANDOP ";
+ $sql = $db->query($sqltext, array($project_id));
+ $row = $db->fetchRow($sql);
+ $reporter = new User($row['user_id']);
+
+ // User might still not be able to add a comment, if he can not see the task...
+ // Just try again until a suitable one comes out from the query. It will finally.
+ // Try to enhance the query also to return fewer unsuitable ones.
+ while (!$reporter->can_view_task($task)) {
+ $row = $db->fetchRow($sql);
+ $reporter = new User($row['user_id']);
+ }
+
+ $comment = 'Comment.';
+ $comment_id=Backend::add_comment($task, $comment);
+ $db->query('UPDATE {comments} SET user_id = ?, date_added = ? WHERE comment_id = ?',
+ array($reporter->id, $added, $comment_id));
+
+ if($i%500==0){
+ echo $i.' mem:'.memory_get_usage()."\n";
+ }
+ } # end for maxcomments
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxcomments." comments created\n";
+
+
+ // And $maxattachments total, either to task or comment
+ for ($i = 1; $i <= $maxattachments; $i++) {
+ $sql = $db->query("SELECT comment_id, task_id, user_id, date_added FROM {comments} ORDER BY $RANDOP LIMIT 1");
+ list($comment_id, $task_id, $user_id, $date_added) = $db->fetchRow($sql);
+ $fname = "Attachment $i";
+ if (rand(1, 100) == 1) {
+ $comment_id = 0;
+ }
+
+ $origname = getAttachmentDescription() . " $i";
+ $db->query("INSERT INTO {attachments}
+ ( task_id, comment_id, file_name,
+ file_type, file_size, orig_name,
+ added_by, date_added)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ array($task_id, $comment_id, $fname,
+ 'application/octet-stream', 1024,
+ $origname,
+ $user_id, $date_added));
+ }
+ $last=$now;$now=microtime(true);echo round($now-$last,6).': '.$maxattachments." pseudo attachments created\n";
+ echo "\nTestdata filled in ".round($now-$start,1)." s.\n\n";
+ $db->dbClose();
+} // end function createTestData
+
+
+function getAttachmentDescription() {
+ $type = rand(1, 100);
+
+ if ($type > 80 && $type <= 100) {
+ return 'Information that might help solve the problem';
+ }
+ elseif ($type == 79) {
+ return 'Pic of my pet alligator';
+ }
+ elseif ($type == 78) {
+ return 'Pic of my pet rhinoceros';
+ }
+ elseif ($type == 77) {
+ return 'Pic of my pet elephant';
+ }
+ elseif ($type == 76 || $type == 75) {
+ return 'Pic of my pet monkey';
+ }
+ elseif ($type == 74 || $type == 73) {
+ return 'Pic of my undulate';
+ }
+ elseif ($type == 72 || $type == 71) {
+ return 'Pic of my goldfish';
+ }
+ elseif ($type == 70 || $type == 69) {
+ return 'Pic of my pet pig';
+ }
+ elseif ($type == 68 || $type == 67) {
+ return 'Pic of my pet snake';
+ }
+ elseif ($type == 66 || $type == 65) {
+ return 'Pic of my pet rat';
+ }
+ elseif ($type == 64 || $type == 63) {
+ return 'Pic of my pet goat';
+ }
+ elseif ($type == 62 || $type == 61) {
+ return 'Pic of my pet rabbit';
+ }
+ elseif ($type == 60 || $type == 59) {
+ return 'Pic of my pet gerbil';
+ }
+ elseif ($type == 58 || $type == 57) {
+ return 'Pic of my pet hamster';
+ }
+ elseif ($type == 56 || $type == 55) {
+ return 'Pic of my pet chinchilla';
+ }
+ elseif ($type == 54 || $type == 53) {
+ return 'Pic of my pet guinea pig';
+ }
+ elseif ($type == 52 || $type == 51) {
+ return 'Pic of my pet turtle';
+ }
+ elseif ($type == 50 || $type == 49) {
+ return 'Pic of my pet lizard';
+ }
+ elseif ($type == 48 || $type == 47) {
+ return 'Pic of my pet frog';
+ }
+ elseif ($type == 46 || $type == 45) {
+ return 'Pic of my pet tarantula';
+ }
+ elseif ($type == 44 || $type == 43) {
+ return 'Pic of my pet hermit crab';
+ }
+ elseif ($type == 42 || $type == 41) {
+ return 'Pic of my pet parrot';
+ }
+ elseif ($type >= 40 && $type < 25) {
+ return 'Pic of my dog';
+ }
+ else {
+ return 'Pic of my cat';
+ }
+}
+
+function add_project_data($pid = 0) {
+ global $db;
+
+ if(!$pid>0){
+ $sql = $db->query('SELECT project_id FROM {projects} ORDER BY project_id DESC', false, 1);
+ $pid = $db->fetchOne($sql);
+ }
+
+ $cols = array(
+ 'manage_project',
+ 'view_tasks',
+ 'open_new_tasks',
+ 'modify_own_tasks',
+ 'modify_all_tasks',
+ 'view_comments',
+ 'add_comments',
+ 'edit_comments',
+ 'delete_comments',
+ 'show_as_assignees',
+ 'create_attachments',
+ 'delete_attachments',
+ 'view_history',
+ 'add_votes',
+ 'close_own_tasks',
+ 'close_other_tasks',
+ 'assign_to_self',
+ 'edit_own_comments',
+ 'assign_others_to_self',
+ 'add_to_assignees',
+ 'view_reports',
+ 'group_open',
+ 'view_estimated_effort',
+ 'view_current_effort_done',
+ 'track_effort',
+ 'add_multiple_tasks',
+ 'view_roadmap',
+ 'view_own_tasks',
+ 'view_groups_tasks',
+ 'edit_assignments'
+ );
+
+ $args = array_fill(0, count($cols), '1');
+ array_unshift($args, 'Project Managers', 'Permission to do anything related to this project.', intval($pid));
+ $db->query("INSERT INTO {groups}
+ ( group_name, group_desc, project_id,
+ " . join(',', $cols) . ")
+ VALUES ( " . $db->fill_placeholders($cols, 3) . ")", $args);
+
+ // Add 1 project specific developer group too.
+ $args = array_fill(1, count($cols) - 1, '1');
+ array_unshift($args, 'Project Developers', 'Permission to do almost anything but not manage project.', intval($pid), 0);
+ $db->query("INSERT INTO {groups}
+ ( group_name, group_desc, project_id,
+ " . join(',', $cols) . ")
+ VALUES ( " . $db->fill_placeholders($cols, 3) . ")", $args);
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt)
+ VALUES ( ?, ?, 1, 0, 1, 4)", array($pid, 'root'));
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt )
+ VALUES ( ?, ?, 1, 0, 2, 3)", array($pid, 'Backend / Core'));
+
+ $os = 1;
+ $db->query("INSERT INTO {list_os}
+ ( project_id, os_name, list_position, show_in_list )
+ VALUES (?, ?, ?, 1)", array($pid, 'All', $os++));
+
+ $totalversions = rand(3, 10);
+ $present = rand(1, $totalversions);
+ for ($i = 1; $i <= $totalversions; $i++) {
+ $tense = ($i == $present ? 2 : ($i < $present ? 1 : 3));
+ $db->query("INSERT INTO {list_version}
+ ( project_id, version_name, list_position, show_in_list, version_tense )
+ VALUES (?, ?, ?, 1, ?)",
+ array($pid, sprintf('%d.0', $i), $i, $tense)
+ );
+ }
+} # end function add_project_data
+
+?>
diff --git a/tests/flysprayTest.php b/tests/flysprayTest.php
new file mode 100644
index 0000000..cda84db
--- /dev/null
+++ b/tests/flysprayTest.php
@@ -0,0 +1,46 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+class FlysprayTest extends TestCase{
+ #private $pdo;
+ private $db;
+
+ # just taken as first test from github project travis-ci-examples/php
+ protected function setUp(): void
+ {
+ #$this->pdo = new PDO($GLOBALS['db_dsn'], $GLOBALS['db_username'], $GLOBALS['db_password']);
+ #$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ #$this->pdo->query("CREATE TABLE hello (what VARCHAR(50) NOT NULL)");
+ $this->db = new Database;
+ $this->db->dbOpen($GLOBALS['dbhost'], $GLOBALS['dbuser'], $GLOBALS['dbpass'], $GLOBALS['dbname'], $GLOBALS['dbtype'], $GLOBALS['dbprefix']);
+ $this->db->query("CREATE TABLE {projects} (what VARCHAR(50) NOT NULL)");
+ }
+ public function tearDown(): void
+ {
+ #$this->pdo->query("DROP TABLE hello");
+ $this->db->query("DROP TABLE {projects}");
+ }
+
+ public function testHelloWorld(){
+ $helloWorld = 'Hello World';
+ $this->assertEquals('Hello World', $helloWorld);
+ }
+
+ public function testTranslationSyntax(){
+ if ($handle = opendir('lang')) {
+ $languages=array();
+ while (false !== ($file = readdir($handle))) {
+ # exclude temporary files from onsite translations
+ if ($file != "." && $file != ".." && !(substr($file,-4)=='.bak') && !(substr($file,-5)=='.safe') ) {
+ $langfiles[]=$file;
+ }
+ }
+ }
+
+ foreach($langfiles as $lang){
+ $this->assertStringStartsWith('No syntax errors', shell_exec("php -l lang/$lang"));
+ }
+ }
+}
+?>
diff --git a/themes/.htaccess b/themes/.htaccess
new file mode 100644
index 0000000..d1e8b80
--- /dev/null
+++ b/themes/.htaccess
@@ -0,0 +1,4 @@
+<IfModule mod_expires.c>
+ExpiresActive On
+ExpiresDefault "access plus 7 days"
+</IfModule>
diff --git a/themes/CleanFS/README.md b/themes/CleanFS/README.md
new file mode 100644
index 0000000..95dbc2f
--- /dev/null
+++ b/themes/CleanFS/README.md
@@ -0,0 +1,19 @@
+CleanFS
+=======
+
+A proposal for a new [Flyspray](http://flyspray.org/) theme.
+
+You can find more info, and discussion on flyspray's [mailing group](http://groups.google.com/group/flyspray/browse_frm/thread/6bcfdfb6d17f92bf)
+
+Usage
+-----
+
+- In Flyspray 0.9.9.6:
+ - put files in your `/flyspray/themes/` directory
+ - overwrite your `/flyspray/templates/` with the new ones (you might want to backup them first)
+
+Credits
+-------
+
+- [Blueprint CSS](http://blueprintcss.org) - used for reseting CSS and base typography
+- [Iconic](http://somerandomdude.com/work/iconic/) - used for interface icons
diff --git a/themes/CleanFS/_tags.css b/themes/CleanFS/_tags.css
new file mode 100644
index 0000000..a911052
--- /dev/null
+++ b/themes/CleanFS/_tags.css
@@ -0,0 +1,19 @@
+/* template example for customizable tags.css.
+_tags.css will be overwritten by Flyspray upgrades.
+Your customized tags.css will stay untouched during Flyspray upgrades and reflects your tag settings in admin area.
+*/
+
+/* you can overwrite default tag style from theme.css */
+/*.tag {border-width:3px;}*/
+
+/* just a predefined collection of available colors here. So you can map it to tagid's or tag classes */
+.tag.tphone, t1 { background-color:#900;color:#fff;}
+.tag.tfax, t2 { background-color:#ff0;color:#000;}
+.tag.temail, t3 {background-color:#009;color:#fff;}
+
+/* some public icons */
+.tag:before {font-family:fontawesome;}
+.tag.tphone:before {content:'\f098';}
+.tag.tfax:before {content:'\f1ac';}
+.tag.temail:before {content:'\f0e0';}
+.tag.tphone:after, .tag.tphone:after {content:'';} /* hide the title again */
diff --git a/themes/CleanFS/archnavbar.css b/themes/CleanFS/archnavbar.css
new file mode 100644
index 0000000..3f5c280
--- /dev/null
+++ b/themes/CleanFS/archnavbar.css
@@ -0,0 +1,29 @@
+/*
+ * ARCH GLOBAL NAVBAR
+ *
+ * We're forcing all generic selectors with !important
+ * to help prevent other stylesheets from interfering.
+ *
+ */
+
+/* container for the entire bar */
+#archnavbar { min-height: 40px !important; padding: 10px 15px !important; background: #000 !important; }
+
+#archnavbarlogo { float: left !important; margin: 0 !important; padding: 0 !important; height: 40px !important; width: 200px !important; }
+
+/* move the heading/paragraph text offscreen */
+#archnavbarlogo p { margin: 0 !important; padding: 0 !important; text-indent: -9999px !important; }
+#archnavbarlogo h1 { margin: 0 !important; padding: 0 !important; text-indent: -9999px !important; }
+
+/* make the link the same size as the logo */
+#archnavbarlogo a { display: block; height: 40px; width: 230px; }
+
+/* display the list inline, float it to the right and style it */
+#archnavbar ul { list-style: none; margin: 0; padding: 0; font-size:0px; text-align:right; }
+#archnavbar ul li { display:inline-block; font-size: 14px; font-family: sans-serif; padding: 14px 15px 0px;}
+
+/* style the links */
+#archnavbar ul#archnavbarlist li a { color: #999; font-weight: bold; text-decoration: none; }
+#archnavbar ul#archnavbarlist li a:hover { color: white; text-decoration: underline; }
+
+#archnavbar ul#archnavbarlist li a#anb-bugs { color: white; }
diff --git a/themes/CleanFS/asc.png b/themes/CleanFS/asc.png
new file mode 100644
index 0000000..9b798bd
--- /dev/null
+++ b/themes/CleanFS/asc.png
Binary files differ
diff --git a/themes/CleanFS/button_cancel.png b/themes/CleanFS/button_cancel.png
new file mode 100644
index 0000000..297f23c
--- /dev/null
+++ b/themes/CleanFS/button_cancel.png
Binary files differ
diff --git a/themes/CleanFS/calendar.css b/themes/CleanFS/calendar.css
new file mode 100644
index 0000000..b224885
--- /dev/null
+++ b/themes/CleanFS/calendar.css
@@ -0,0 +1,251 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border: 1px solid;
+ border-color: #fff #000 #000 #fff;
+ font-size: 11px;
+ cursor: default;
+ background: Window;
+ color: WindowText;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border: 1px solid;
+ border-color: #fff #000 #000 #fff;
+ font-size: 11px;
+ cursor: default;
+ background: Window;
+ color: WindowText;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border: 1px solid;
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+ background: ButtonFace;
+}
+
+.calendar .nav {
+ background: ButtonFace url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: ActiveCaption;
+ color: CaptionText;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid ButtonShadow;
+ padding: 2px;
+ text-align: center;
+ background: ButtonFace;
+ color: ButtonText;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border: 2px solid;
+ padding: 0px;
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ border-width: 1px;
+ padding: 2px 0px 0px 2px;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid ButtonShadow;
+ background: ButtonFace;
+ color: ButtonText;
+}
+
+.calendar tbody .rowhilite td {
+ background: Highlight;
+ color: HighlightText;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border: 1px solid;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border: 1px solid;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+ padding: 2px 2px 0px 2px;
+ background: ButtonFace;
+ color: ButtonText;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody td.disabled { color: GrayText; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: ButtonFace;
+ padding: 1px;
+ border: 1px solid;
+ border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
+ color: ButtonText;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #e4e0d8;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border: 1px solid;
+ border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight;
+ background: Menu;
+ color: MenuText;
+ font-size: 90%;
+ padding: 1px;
+ z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ padding: 0px;
+ border: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: Highlight;
+ color: HighlightText;
+}
+
+.calendar td.time {
+ border-top: 1px solid ButtonShadow;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: ButtonFace;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: Menu;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/themes/CleanFS/comment.png b/themes/CleanFS/comment.png
new file mode 100644
index 0000000..71d1200
--- /dev/null
+++ b/themes/CleanFS/comment.png
Binary files differ
diff --git a/themes/CleanFS/custom_example.css b/themes/CleanFS/custom_example.css
new file mode 100644
index 0000000..a8f83a4
--- /dev/null
+++ b/themes/CleanFS/custom_example.css
@@ -0,0 +1,60 @@
+/* customization example. Use for color, fontawesome icons, background and border-color settings only, avoid layout changes. */
+
+/*
+body.p1 #title {background:#060;}
+body.p1 #pm-menu {border-color:#090;}
+body.p1 #pm-menu-list a.active{ border-color:#090;}
+body.p1 #pm-menu-list a.active,
+body.p1 #menu-list a.active{ background-color: #090;}
+body.p1 #mysearches{border-color: #090;}
+*/
+
+/* styling the id with the severity color enables you to remove the severity column (see admin and project settings) the view for more overview. */
+tr.severity5, tr.severity5 .task_severity, tr.severity5 .task_id{background: #f96 linear-gradient(#f96, #f85) repeat scroll 0 0;}
+tr.severity4, tr.severity4 .task_severity, tr.severity4 .task_id {background: #fe0 linear-gradient(#fe0, #fc0) repeat scroll 0 0;}
+tr.severity3, tr.severity3 .task_severity, tr.severity3 .task_id {background: #fe9 linear-gradient(#fe9, #fd9) repeat scroll 0 0;}
+tr.severity2 .task_severity, tr.severity2 .task_id {background: #eee linear-gradient(#fff, #eee) repeat scroll 0 0;}
+
+/* highly experimental! may move to default css if finshed */
+#tasklist_table tbody tr td {border-top:none;border-bottom:1px dotted #ccc;vertical-align:middle;}
+#tasklist_table tbody tr td.task_opendby,
+#tasklist_table tbody tr td.task_editedby,
+#tasklist_table tbody tr td.task_assignedto {padding-top:0px; padding-bottom:0px;}
+th.tasktype, th.severity, th.priority {width:20px;max-width:20px;overflow:hidden;}
+
+/* padding done by the ::before pseudoelement.. */
+#tasklist_table .task_tasktype, #tasklist_table .task_severity,#tasklist_table .task_priority {padding-left: 0;padding-right: 0;}
+tr .typ1, tr .typ2, tr .typ3, tr .typ4, tr .typ5,
+tr .sev1, tr .sev2, tr .sev3, tr .sev4, tr .sev5,
+tr .pri1, tr .pri2, tr .pri3, tr .pri4, tr .pri5
+{position:relative;visibility:hidden;border-bottom-width:0 !important;width:20px;max-width:20px;white-space:nowrap;text-overflow:hidden;font-size:16px;}
+
+tr .typ1:before, tr .typ2:before, tr .typ3:before, tr .typ4:before,
+tr .sev1:before, tr .sev2:before, tr .sev3:before, tr .sev4:before, tr .sev5:before,
+tr .pri1:before, tr .pri2:before, tr .pri3:before, tr .pri4:before, tr .pri5:before {
+ border-bottom: #ccc dotted 1px;
+ bottom: 0;
+ box-sizing: border-box;
+ font-family: fontawesome;
+ position: absolute;
+ visibility: visible;
+ width: 100%;
+ line-height:24px;
+}
+.typ1:before {content:"\f188";color:#900;} /*fa-bug*/
+.typ2:before {content:"\f06b";color:#390;} /*fa-gift*/
+.typ3:before {content:"\f19d";color:#000;} /*fa-graduation-cap*/
+.typ4:before {content:"\f111";color:#009;} /*fa-circle*/
+
+tr .sev1:before {content:"";color:#fff;} /**/
+tr .sev2:before {content:"";color:#fff;} /**/
+tr .sev3:before {content:"";color:#cc0;} /**/
+tr .sev4:before {content:"\f06d";color:#c60;} /*fa-fire*/
+/* tr .sev5:before {content:"\f1e2";color:#600;} */ /*fa-bomb*/
+tr .sev5:before {content:"\f0e7";color:#600;} /*fa-bolt*/
+
+tr .pri1:before {content:"\f236";color:#ccc;} /*fa-bed*/
+tr .pri2:before {content:"";color:#ccc;}
+tr .pri3:before {content:"\f04b";color:#f90;font-size:10px;} /*fa-forward*/
+tr .pri4:before {content:"\f04b\f04b";color:#c60;font-size:10px;} /*fa-forward*/
+tr .pri5:before {content:"\f04b\f04b\f04b";color:#900;font-size:10px;} /*fa-forward*/
diff --git a/themes/CleanFS/desc.png b/themes/CleanFS/desc.png
new file mode 100644
index 0000000..1c7b58b
--- /dev/null
+++ b/themes/CleanFS/desc.png
Binary files differ
diff --git a/themes/CleanFS/down.png b/themes/CleanFS/down.png
new file mode 100644
index 0000000..294b222
--- /dev/null
+++ b/themes/CleanFS/down.png
Binary files differ
diff --git a/themes/CleanFS/edit_add.png b/themes/CleanFS/edit_add.png
new file mode 100644
index 0000000..47f4a8c
--- /dev/null
+++ b/themes/CleanFS/edit_add.png
Binary files differ
diff --git a/themes/CleanFS/edit_remove.png b/themes/CleanFS/edit_remove.png
new file mode 100644
index 0000000..cb4f5f3
--- /dev/null
+++ b/themes/CleanFS/edit_remove.png
Binary files differ
diff --git a/themes/CleanFS/font-awesome.css b/themes/CleanFS/font-awesome.css
new file mode 100644
index 0000000..0bd15a4
--- /dev/null
+++ b/themes/CleanFS/font-awesome.css
@@ -0,0 +1,2090 @@
+/*!
+ * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+ font-family: 'FontAwesome';
+ src: url('fonts/fontawesome-webfont.eot?v=4.5.0');
+ src: url('fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),
+ url('fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),
+ url('fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),
+ url('fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),
+ url('fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+.fa {
+ display: inline-block;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+ font-size: 1.33333333em;
+ line-height: 0.75em;
+ vertical-align: -15%;
+}
+.fa-2x {
+ font-size: 2em;
+}
+.fa-3x {
+ font-size: 3em;
+}
+.fa-4x {
+ font-size: 4em;
+}
+.fa-5x {
+ font-size: 5em;
+}
+.fa-fw {
+ width: 1.28571429em;
+ text-align: center;
+}
+.fa-ul {
+ padding-left: 0;
+ margin-left: 2.14285714em;
+ list-style-type: none;
+}
+.fa-ul > li {
+ position: relative;
+}
+.fa-li {
+ position: absolute;
+ left: -2.14285714em;
+ width: 2.14285714em;
+ top: 0.14285714em;
+ text-align: center;
+}
+.fa-li.fa-lg {
+ left: -1.85714286em;
+}
+.fa-border {
+ padding: .2em .25em .15em;
+ border: solid 0.08em #eeeeee;
+ border-radius: .1em;
+}
+.fa-pull-left {
+ float: left;
+}
+.fa-pull-right {
+ float: right;
+}
+.fa.fa-pull-left {
+ margin-right: .3em;
+}
+.fa.fa-pull-right {
+ margin-left: .3em;
+}
+/* Deprecated as of 4.4.0 */
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+.fa.pull-left {
+ margin-right: .3em;
+}
+.fa.pull-right {
+ margin-left: .3em;
+}
+.fa-spin {
+ -webkit-animation: fa-spin 2s infinite linear;
+ animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+ -webkit-animation: fa-spin 1s infinite steps(8);
+ animation: fa-spin 1s infinite steps(8);
+}
+@-webkit-keyframes fa-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@keyframes fa-spin {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+.fa-rotate-90 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+.fa-rotate-180 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+ -webkit-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+.fa-rotate-270 {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+ -webkit-transform: rotate(270deg);
+ -ms-transform: rotate(270deg);
+ transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
+ -webkit-transform: scale(-1, 1);
+ -ms-transform: scale(-1, 1);
+ transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+ filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+ filter: none;
+}
+.fa-stack {
+ position: relative;
+ display: inline-block;
+ width: 2em;
+ height: 2em;
+ line-height: 2em;
+ vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+ position: absolute;
+ left: 0;
+ width: 100%;
+ text-align: center;
+}
+.fa-stack-1x {
+ line-height: inherit;
+}
+.fa-stack-2x {
+ font-size: 2em;
+}
+.fa-inverse {
+ color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+ readers do not read off random characters that represent icons */
+.fa-glass:before {
+ content: "\f000";
+}
+.fa-music:before {
+ content: "\f001";
+}
+.fa-search:before {
+ content: "\f002";
+}
+.fa-envelope-o:before {
+ content: "\f003";
+}
+.fa-heart:before {
+ content: "\f004";
+}
+.fa-star:before {
+ content: "\f005";
+}
+.fa-star-o:before {
+ content: "\f006";
+}
+.fa-user:before {
+ content: "\f007";
+}
+.fa-film:before {
+ content: "\f008";
+}
+.fa-th-large:before {
+ content: "\f009";
+}
+.fa-th:before {
+ content: "\f00a";
+}
+.fa-th-list:before {
+ content: "\f00b";
+}
+.fa-check:before {
+ content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+ content: "\f00d";
+}
+.fa-search-plus:before {
+ content: "\f00e";
+}
+.fa-search-minus:before {
+ content: "\f010";
+}
+.fa-power-off:before {
+ content: "\f011";
+}
+.fa-signal:before {
+ content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+ content: "\f013";
+}
+.fa-trash-o:before {
+ content: "\f014";
+}
+.fa-home:before {
+ content: "\f015";
+}
+.fa-file-o:before {
+ content: "\f016";
+}
+.fa-clock-o:before {
+ content: "\f017";
+}
+.fa-road:before {
+ content: "\f018";
+}
+.fa-download:before {
+ content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+ content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+ content: "\f01b";
+}
+.fa-inbox:before {
+ content: "\f01c";
+}
+.fa-play-circle-o:before {
+ content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+ content: "\f01e";
+}
+.fa-refresh:before {
+ content: "\f021";
+}
+.fa-list-alt:before {
+ content: "\f022";
+}
+.fa-lock:before {
+ content: "\f023";
+}
+.fa-flag:before {
+ content: "\f024";
+}
+.fa-headphones:before {
+ content: "\f025";
+}
+.fa-volume-off:before {
+ content: "\f026";
+}
+.fa-volume-down:before {
+ content: "\f027";
+}
+.fa-volume-up:before {
+ content: "\f028";
+}
+.fa-qrcode:before {
+ content: "\f029";
+}
+.fa-barcode:before {
+ content: "\f02a";
+}
+.fa-tag:before {
+ content: "\f02b";
+}
+.fa-tags:before {
+ content: "\f02c";
+}
+.fa-book:before {
+ content: "\f02d";
+}
+.fa-bookmark:before {
+ content: "\f02e";
+}
+.fa-print:before {
+ content: "\f02f";
+}
+.fa-camera:before {
+ content: "\f030";
+}
+.fa-font:before {
+ content: "\f031";
+}
+.fa-bold:before {
+ content: "\f032";
+}
+.fa-italic:before {
+ content: "\f033";
+}
+.fa-text-height:before {
+ content: "\f034";
+}
+.fa-text-width:before {
+ content: "\f035";
+}
+.fa-align-left:before {
+ content: "\f036";
+}
+.fa-align-center:before {
+ content: "\f037";
+}
+.fa-align-right:before {
+ content: "\f038";
+}
+.fa-align-justify:before {
+ content: "\f039";
+}
+.fa-list:before {
+ content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+ content: "\f03b";
+}
+.fa-indent:before {
+ content: "\f03c";
+}
+.fa-video-camera:before {
+ content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+ content: "\f03e";
+}
+.fa-pencil:before {
+ content: "\f040";
+}
+.fa-map-marker:before {
+ content: "\f041";
+}
+.fa-adjust:before {
+ content: "\f042";
+}
+.fa-tint:before {
+ content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+ content: "\f044";
+}
+.fa-share-square-o:before {
+ content: "\f045";
+}
+.fa-check-square-o:before {
+ content: "\f046";
+}
+.fa-arrows:before {
+ content: "\f047";
+}
+.fa-step-backward:before {
+ content: "\f048";
+}
+.fa-fast-backward:before {
+ content: "\f049";
+}
+.fa-backward:before {
+ content: "\f04a";
+}
+.fa-play:before {
+ content: "\f04b";
+}
+.fa-pause:before {
+ content: "\f04c";
+}
+.fa-stop:before {
+ content: "\f04d";
+}
+.fa-forward:before {
+ content: "\f04e";
+}
+.fa-fast-forward:before {
+ content: "\f050";
+}
+.fa-step-forward:before {
+ content: "\f051";
+}
+.fa-eject:before {
+ content: "\f052";
+}
+.fa-chevron-left:before {
+ content: "\f053";
+}
+.fa-chevron-right:before {
+ content: "\f054";
+}
+.fa-plus-circle:before {
+ content: "\f055";
+}
+.fa-minus-circle:before {
+ content: "\f056";
+}
+.fa-times-circle:before {
+ content: "\f057";
+}
+.fa-check-circle:before {
+ content: "\f058";
+}
+.fa-question-circle:before {
+ content: "\f059";
+}
+.fa-info-circle:before {
+ content: "\f05a";
+}
+.fa-crosshairs:before {
+ content: "\f05b";
+}
+.fa-times-circle-o:before {
+ content: "\f05c";
+}
+.fa-check-circle-o:before {
+ content: "\f05d";
+}
+.fa-ban:before {
+ content: "\f05e";
+}
+.fa-arrow-left:before {
+ content: "\f060";
+}
+.fa-arrow-right:before {
+ content: "\f061";
+}
+.fa-arrow-up:before {
+ content: "\f062";
+}
+.fa-arrow-down:before {
+ content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+ content: "\f064";
+}
+.fa-expand:before {
+ content: "\f065";
+}
+.fa-compress:before {
+ content: "\f066";
+}
+.fa-plus:before {
+ content: "\f067";
+}
+.fa-minus:before {
+ content: "\f068";
+}
+.fa-asterisk:before {
+ content: "\f069";
+}
+.fa-exclamation-circle:before {
+ content: "\f06a";
+}
+.fa-gift:before {
+ content: "\f06b";
+}
+.fa-leaf:before {
+ content: "\f06c";
+}
+.fa-fire:before {
+ content: "\f06d";
+}
+.fa-eye:before {
+ content: "\f06e";
+}
+.fa-eye-slash:before {
+ content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+ content: "\f071";
+}
+.fa-plane:before {
+ content: "\f072";
+}
+.fa-calendar:before {
+ content: "\f073";
+}
+.fa-random:before {
+ content: "\f074";
+}
+.fa-comment:before {
+ content: "\f075";
+}
+.fa-magnet:before {
+ content: "\f076";
+}
+.fa-chevron-up:before {
+ content: "\f077";
+}
+.fa-chevron-down:before {
+ content: "\f078";
+}
+.fa-retweet:before {
+ content: "\f079";
+}
+.fa-shopping-cart:before {
+ content: "\f07a";
+}
+.fa-folder:before {
+ content: "\f07b";
+}
+.fa-folder-open:before {
+ content: "\f07c";
+}
+.fa-arrows-v:before {
+ content: "\f07d";
+}
+.fa-arrows-h:before {
+ content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+ content: "\f080";
+}
+.fa-twitter-square:before {
+ content: "\f081";
+}
+.fa-facebook-square:before {
+ content: "\f082";
+}
+.fa-camera-retro:before {
+ content: "\f083";
+}
+.fa-key:before {
+ content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+ content: "\f085";
+}
+.fa-comments:before {
+ content: "\f086";
+}
+.fa-thumbs-o-up:before {
+ content: "\f087";
+}
+.fa-thumbs-o-down:before {
+ content: "\f088";
+}
+.fa-star-half:before {
+ content: "\f089";
+}
+.fa-heart-o:before {
+ content: "\f08a";
+}
+.fa-sign-out:before {
+ content: "\f08b";
+}
+.fa-linkedin-square:before {
+ content: "\f08c";
+}
+.fa-thumb-tack:before {
+ content: "\f08d";
+}
+.fa-external-link:before {
+ content: "\f08e";
+}
+.fa-sign-in:before {
+ content: "\f090";
+}
+.fa-trophy:before {
+ content: "\f091";
+}
+.fa-github-square:before {
+ content: "\f092";
+}
+.fa-upload:before {
+ content: "\f093";
+}
+.fa-lemon-o:before {
+ content: "\f094";
+}
+.fa-phone:before {
+ content: "\f095";
+}
+.fa-square-o:before {
+ content: "\f096";
+}
+.fa-bookmark-o:before {
+ content: "\f097";
+}
+.fa-phone-square:before {
+ content: "\f098";
+}
+.fa-twitter:before {
+ content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+ content: "\f09a";
+}
+.fa-github:before {
+ content: "\f09b";
+}
+.fa-unlock:before {
+ content: "\f09c";
+}
+.fa-credit-card:before {
+ content: "\f09d";
+}
+.fa-feed:before,
+.fa-rss:before {
+ content: "\f09e";
+}
+.fa-hdd-o:before {
+ content: "\f0a0";
+}
+.fa-bullhorn:before {
+ content: "\f0a1";
+}
+.fa-bell:before {
+ content: "\f0f3";
+}
+.fa-certificate:before {
+ content: "\f0a3";
+}
+.fa-hand-o-right:before {
+ content: "\f0a4";
+}
+.fa-hand-o-left:before {
+ content: "\f0a5";
+}
+.fa-hand-o-up:before {
+ content: "\f0a6";
+}
+.fa-hand-o-down:before {
+ content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+ content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+ content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+ content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+ content: "\f0ab";
+}
+.fa-globe:before {
+ content: "\f0ac";
+}
+.fa-wrench:before {
+ content: "\f0ad";
+}
+.fa-tasks:before {
+ content: "\f0ae";
+}
+.fa-filter:before {
+ content: "\f0b0";
+}
+.fa-briefcase:before {
+ content: "\f0b1";
+}
+.fa-arrows-alt:before {
+ content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+ content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+ content: "\f0c1";
+}
+.fa-cloud:before {
+ content: "\f0c2";
+}
+.fa-flask:before {
+ content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+ content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+ content: "\f0c5";
+}
+.fa-paperclip:before {
+ content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+ content: "\f0c7";
+}
+.fa-square:before {
+ content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+ content: "\f0c9";
+}
+.fa-list-ul:before {
+ content: "\f0ca";
+}
+.fa-list-ol:before {
+ content: "\f0cb";
+}
+.fa-strikethrough:before {
+ content: "\f0cc";
+}
+.fa-underline:before {
+ content: "\f0cd";
+}
+.fa-table:before {
+ content: "\f0ce";
+}
+.fa-magic:before {
+ content: "\f0d0";
+}
+.fa-truck:before {
+ content: "\f0d1";
+}
+.fa-pinterest:before {
+ content: "\f0d2";
+}
+.fa-pinterest-square:before {
+ content: "\f0d3";
+}
+.fa-google-plus-square:before {
+ content: "\f0d4";
+}
+.fa-google-plus:before {
+ content: "\f0d5";
+}
+.fa-money:before {
+ content: "\f0d6";
+}
+.fa-caret-down:before {
+ content: "\f0d7";
+}
+.fa-caret-up:before {
+ content: "\f0d8";
+}
+.fa-caret-left:before {
+ content: "\f0d9";
+}
+.fa-caret-right:before {
+ content: "\f0da";
+}
+.fa-columns:before {
+ content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+ content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+ content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+ content: "\f0de";
+}
+.fa-envelope:before {
+ content: "\f0e0";
+}
+.fa-linkedin:before {
+ content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+ content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+ content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+ content: "\f0e4";
+}
+.fa-comment-o:before {
+ content: "\f0e5";
+}
+.fa-comments-o:before {
+ content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+ content: "\f0e7";
+}
+.fa-sitemap:before {
+ content: "\f0e8";
+}
+.fa-umbrella:before {
+ content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+ content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+ content: "\f0eb";
+}
+.fa-exchange:before {
+ content: "\f0ec";
+}
+.fa-cloud-download:before {
+ content: "\f0ed";
+}
+.fa-cloud-upload:before {
+ content: "\f0ee";
+}
+.fa-user-md:before {
+ content: "\f0f0";
+}
+.fa-stethoscope:before {
+ content: "\f0f1";
+}
+.fa-suitcase:before {
+ content: "\f0f2";
+}
+.fa-bell-o:before {
+ content: "\f0a2";
+}
+.fa-coffee:before {
+ content: "\f0f4";
+}
+.fa-cutlery:before {
+ content: "\f0f5";
+}
+.fa-file-text-o:before {
+ content: "\f0f6";
+}
+.fa-building-o:before {
+ content: "\f0f7";
+}
+.fa-hospital-o:before {
+ content: "\f0f8";
+}
+.fa-ambulance:before {
+ content: "\f0f9";
+}
+.fa-medkit:before {
+ content: "\f0fa";
+}
+.fa-fighter-jet:before {
+ content: "\f0fb";
+}
+.fa-beer:before {
+ content: "\f0fc";
+}
+.fa-h-square:before {
+ content: "\f0fd";
+}
+.fa-plus-square:before {
+ content: "\f0fe";
+}
+.fa-angle-double-left:before {
+ content: "\f100";
+}
+.fa-angle-double-right:before {
+ content: "\f101";
+}
+.fa-angle-double-up:before {
+ content: "\f102";
+}
+.fa-angle-double-down:before {
+ content: "\f103";
+}
+.fa-angle-left:before {
+ content: "\f104";
+}
+.fa-angle-right:before {
+ content: "\f105";
+}
+.fa-angle-up:before {
+ content: "\f106";
+}
+.fa-angle-down:before {
+ content: "\f107";
+}
+.fa-desktop:before {
+ content: "\f108";
+}
+.fa-laptop:before {
+ content: "\f109";
+}
+.fa-tablet:before {
+ content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+ content: "\f10b";
+}
+.fa-circle-o:before {
+ content: "\f10c";
+}
+.fa-quote-left:before {
+ content: "\f10d";
+}
+.fa-quote-right:before {
+ content: "\f10e";
+}
+.fa-spinner:before {
+ content: "\f110";
+}
+.fa-circle:before {
+ content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+ content: "\f112";
+}
+.fa-github-alt:before {
+ content: "\f113";
+}
+.fa-folder-o:before {
+ content: "\f114";
+}
+.fa-folder-open-o:before {
+ content: "\f115";
+}
+.fa-smile-o:before {
+ content: "\f118";
+}
+.fa-frown-o:before {
+ content: "\f119";
+}
+.fa-meh-o:before {
+ content: "\f11a";
+}
+.fa-gamepad:before {
+ content: "\f11b";
+}
+.fa-keyboard-o:before {
+ content: "\f11c";
+}
+.fa-flag-o:before {
+ content: "\f11d";
+}
+.fa-flag-checkered:before {
+ content: "\f11e";
+}
+.fa-terminal:before {
+ content: "\f120";
+}
+.fa-code:before {
+ content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+ content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+ content: "\f123";
+}
+.fa-location-arrow:before {
+ content: "\f124";
+}
+.fa-crop:before {
+ content: "\f125";
+}
+.fa-code-fork:before {
+ content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+ content: "\f127";
+}
+.fa-question:before {
+ content: "\f128";
+}
+.fa-info:before {
+ content: "\f129";
+}
+.fa-exclamation:before {
+ content: "\f12a";
+}
+.fa-superscript:before {
+ content: "\f12b";
+}
+.fa-subscript:before {
+ content: "\f12c";
+}
+.fa-eraser:before {
+ content: "\f12d";
+}
+.fa-puzzle-piece:before {
+ content: "\f12e";
+}
+.fa-microphone:before {
+ content: "\f130";
+}
+.fa-microphone-slash:before {
+ content: "\f131";
+}
+.fa-shield:before {
+ content: "\f132";
+}
+.fa-calendar-o:before {
+ content: "\f133";
+}
+.fa-fire-extinguisher:before {
+ content: "\f134";
+}
+.fa-rocket:before {
+ content: "\f135";
+}
+.fa-maxcdn:before {
+ content: "\f136";
+}
+.fa-chevron-circle-left:before {
+ content: "\f137";
+}
+.fa-chevron-circle-right:before {
+ content: "\f138";
+}
+.fa-chevron-circle-up:before {
+ content: "\f139";
+}
+.fa-chevron-circle-down:before {
+ content: "\f13a";
+}
+.fa-html5:before {
+ content: "\f13b";
+}
+.fa-css3:before {
+ content: "\f13c";
+}
+.fa-anchor:before {
+ content: "\f13d";
+}
+.fa-unlock-alt:before {
+ content: "\f13e";
+}
+.fa-bullseye:before {
+ content: "\f140";
+}
+.fa-ellipsis-h:before {
+ content: "\f141";
+}
+.fa-ellipsis-v:before {
+ content: "\f142";
+}
+.fa-rss-square:before {
+ content: "\f143";
+}
+.fa-play-circle:before {
+ content: "\f144";
+}
+.fa-ticket:before {
+ content: "\f145";
+}
+.fa-minus-square:before {
+ content: "\f146";
+}
+.fa-minus-square-o:before {
+ content: "\f147";
+}
+.fa-level-up:before {
+ content: "\f148";
+}
+.fa-level-down:before {
+ content: "\f149";
+}
+.fa-check-square:before {
+ content: "\f14a";
+}
+.fa-pencil-square:before {
+ content: "\f14b";
+}
+.fa-external-link-square:before {
+ content: "\f14c";
+}
+.fa-share-square:before {
+ content: "\f14d";
+}
+.fa-compass:before {
+ content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+ content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+ content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+ content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+ content: "\f153";
+}
+.fa-gbp:before {
+ content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+ content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+ content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+ content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+ content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+ content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+ content: "\f15a";
+}
+.fa-file:before {
+ content: "\f15b";
+}
+.fa-file-text:before {
+ content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+ content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+ content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+ content: "\f160";
+}
+.fa-sort-amount-desc:before {
+ content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+ content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+ content: "\f163";
+}
+.fa-thumbs-up:before {
+ content: "\f164";
+}
+.fa-thumbs-down:before {
+ content: "\f165";
+}
+.fa-youtube-square:before {
+ content: "\f166";
+}
+.fa-youtube:before {
+ content: "\f167";
+}
+.fa-xing:before {
+ content: "\f168";
+}
+.fa-xing-square:before {
+ content: "\f169";
+}
+.fa-youtube-play:before {
+ content: "\f16a";
+}
+.fa-dropbox:before {
+ content: "\f16b";
+}
+.fa-stack-overflow:before {
+ content: "\f16c";
+}
+.fa-instagram:before {
+ content: "\f16d";
+}
+.fa-flickr:before {
+ content: "\f16e";
+}
+.fa-adn:before {
+ content: "\f170";
+}
+.fa-bitbucket:before {
+ content: "\f171";
+}
+.fa-bitbucket-square:before {
+ content: "\f172";
+}
+.fa-tumblr:before {
+ content: "\f173";
+}
+.fa-tumblr-square:before {
+ content: "\f174";
+}
+.fa-long-arrow-down:before {
+ content: "\f175";
+}
+.fa-long-arrow-up:before {
+ content: "\f176";
+}
+.fa-long-arrow-left:before {
+ content: "\f177";
+}
+.fa-long-arrow-right:before {
+ content: "\f178";
+}
+.fa-apple:before {
+ content: "\f179";
+}
+.fa-windows:before {
+ content: "\f17a";
+}
+.fa-android:before {
+ content: "\f17b";
+}
+.fa-linux:before {
+ content: "\f17c";
+}
+.fa-dribbble:before {
+ content: "\f17d";
+}
+.fa-skype:before {
+ content: "\f17e";
+}
+.fa-foursquare:before {
+ content: "\f180";
+}
+.fa-trello:before {
+ content: "\f181";
+}
+.fa-female:before {
+ content: "\f182";
+}
+.fa-male:before {
+ content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+ content: "\f184";
+}
+.fa-sun-o:before {
+ content: "\f185";
+}
+.fa-moon-o:before {
+ content: "\f186";
+}
+.fa-archive:before {
+ content: "\f187";
+}
+.fa-bug:before {
+ content: "\f188";
+}
+.fa-vk:before {
+ content: "\f189";
+}
+.fa-weibo:before {
+ content: "\f18a";
+}
+.fa-renren:before {
+ content: "\f18b";
+}
+.fa-pagelines:before {
+ content: "\f18c";
+}
+.fa-stack-exchange:before {
+ content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+ content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+ content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+ content: "\f191";
+}
+.fa-dot-circle-o:before {
+ content: "\f192";
+}
+.fa-wheelchair:before {
+ content: "\f193";
+}
+.fa-vimeo-square:before {
+ content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+ content: "\f195";
+}
+.fa-plus-square-o:before {
+ content: "\f196";
+}
+.fa-space-shuttle:before {
+ content: "\f197";
+}
+.fa-slack:before {
+ content: "\f198";
+}
+.fa-envelope-square:before {
+ content: "\f199";
+}
+.fa-wordpress:before {
+ content: "\f19a";
+}
+.fa-openid:before {
+ content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+ content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+ content: "\f19d";
+}
+.fa-yahoo:before {
+ content: "\f19e";
+}
+.fa-google:before {
+ content: "\f1a0";
+}
+.fa-reddit:before {
+ content: "\f1a1";
+}
+.fa-reddit-square:before {
+ content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+ content: "\f1a3";
+}
+.fa-stumbleupon:before {
+ content: "\f1a4";
+}
+.fa-delicious:before {
+ content: "\f1a5";
+}
+.fa-digg:before {
+ content: "\f1a6";
+}
+.fa-pied-piper:before {
+ content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+ content: "\f1a8";
+}
+.fa-drupal:before {
+ content: "\f1a9";
+}
+.fa-joomla:before {
+ content: "\f1aa";
+}
+.fa-language:before {
+ content: "\f1ab";
+}
+.fa-fax:before {
+ content: "\f1ac";
+}
+.fa-building:before {
+ content: "\f1ad";
+}
+.fa-child:before {
+ content: "\f1ae";
+}
+.fa-paw:before {
+ content: "\f1b0";
+}
+.fa-spoon:before {
+ content: "\f1b1";
+}
+.fa-cube:before {
+ content: "\f1b2";
+}
+.fa-cubes:before {
+ content: "\f1b3";
+}
+.fa-behance:before {
+ content: "\f1b4";
+}
+.fa-behance-square:before {
+ content: "\f1b5";
+}
+.fa-steam:before {
+ content: "\f1b6";
+}
+.fa-steam-square:before {
+ content: "\f1b7";
+}
+.fa-recycle:before {
+ content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+ content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+ content: "\f1ba";
+}
+.fa-tree:before {
+ content: "\f1bb";
+}
+.fa-spotify:before {
+ content: "\f1bc";
+}
+.fa-deviantart:before {
+ content: "\f1bd";
+}
+.fa-soundcloud:before {
+ content: "\f1be";
+}
+.fa-database:before {
+ content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+ content: "\f1c1";
+}
+.fa-file-word-o:before {
+ content: "\f1c2";
+}
+.fa-file-excel-o:before {
+ content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+ content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+ content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+ content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+ content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+ content: "\f1c8";
+}
+.fa-file-code-o:before {
+ content: "\f1c9";
+}
+.fa-vine:before {
+ content: "\f1ca";
+}
+.fa-codepen:before {
+ content: "\f1cb";
+}
+.fa-jsfiddle:before {
+ content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+ content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+ content: "\f1ce";
+}
+.fa-ra:before,
+.fa-rebel:before {
+ content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+ content: "\f1d1";
+}
+.fa-git-square:before {
+ content: "\f1d2";
+}
+.fa-git:before {
+ content: "\f1d3";
+}
+.fa-y-combinator-square:before,
+.fa-yc-square:before,
+.fa-hacker-news:before {
+ content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+ content: "\f1d5";
+}
+.fa-qq:before {
+ content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+ content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+ content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+ content: "\f1d9";
+}
+.fa-history:before {
+ content: "\f1da";
+}
+.fa-circle-thin:before {
+ content: "\f1db";
+}
+.fa-header:before {
+ content: "\f1dc";
+}
+.fa-paragraph:before {
+ content: "\f1dd";
+}
+.fa-sliders:before {
+ content: "\f1de";
+}
+.fa-share-alt:before {
+ content: "\f1e0";
+}
+.fa-share-alt-square:before {
+ content: "\f1e1";
+}
+.fa-bomb:before {
+ content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+ content: "\f1e3";
+}
+.fa-tty:before {
+ content: "\f1e4";
+}
+.fa-binoculars:before {
+ content: "\f1e5";
+}
+.fa-plug:before {
+ content: "\f1e6";
+}
+.fa-slideshare:before {
+ content: "\f1e7";
+}
+.fa-twitch:before {
+ content: "\f1e8";
+}
+.fa-yelp:before {
+ content: "\f1e9";
+}
+.fa-newspaper-o:before {
+ content: "\f1ea";
+}
+.fa-wifi:before {
+ content: "\f1eb";
+}
+.fa-calculator:before {
+ content: "\f1ec";
+}
+.fa-paypal:before {
+ content: "\f1ed";
+}
+.fa-google-wallet:before {
+ content: "\f1ee";
+}
+.fa-cc-visa:before {
+ content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+ content: "\f1f1";
+}
+.fa-cc-discover:before {
+ content: "\f1f2";
+}
+.fa-cc-amex:before {
+ content: "\f1f3";
+}
+.fa-cc-paypal:before {
+ content: "\f1f4";
+}
+.fa-cc-stripe:before {
+ content: "\f1f5";
+}
+.fa-bell-slash:before {
+ content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+ content: "\f1f7";
+}
+.fa-trash:before {
+ content: "\f1f8";
+}
+.fa-copyright:before {
+ content: "\f1f9";
+}
+.fa-at:before {
+ content: "\f1fa";
+}
+.fa-eyedropper:before {
+ content: "\f1fb";
+}
+.fa-paint-brush:before {
+ content: "\f1fc";
+}
+.fa-birthday-cake:before {
+ content: "\f1fd";
+}
+.fa-area-chart:before {
+ content: "\f1fe";
+}
+.fa-pie-chart:before {
+ content: "\f200";
+}
+.fa-line-chart:before {
+ content: "\f201";
+}
+.fa-lastfm:before {
+ content: "\f202";
+}
+.fa-lastfm-square:before {
+ content: "\f203";
+}
+.fa-toggle-off:before {
+ content: "\f204";
+}
+.fa-toggle-on:before {
+ content: "\f205";
+}
+.fa-bicycle:before {
+ content: "\f206";
+}
+.fa-bus:before {
+ content: "\f207";
+}
+.fa-ioxhost:before {
+ content: "\f208";
+}
+.fa-angellist:before {
+ content: "\f209";
+}
+.fa-cc:before {
+ content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+ content: "\f20b";
+}
+.fa-meanpath:before {
+ content: "\f20c";
+}
+.fa-buysellads:before {
+ content: "\f20d";
+}
+.fa-connectdevelop:before {
+ content: "\f20e";
+}
+.fa-dashcube:before {
+ content: "\f210";
+}
+.fa-forumbee:before {
+ content: "\f211";
+}
+.fa-leanpub:before {
+ content: "\f212";
+}
+.fa-sellsy:before {
+ content: "\f213";
+}
+.fa-shirtsinbulk:before {
+ content: "\f214";
+}
+.fa-simplybuilt:before {
+ content: "\f215";
+}
+.fa-skyatlas:before {
+ content: "\f216";
+}
+.fa-cart-plus:before {
+ content: "\f217";
+}
+.fa-cart-arrow-down:before {
+ content: "\f218";
+}
+.fa-diamond:before {
+ content: "\f219";
+}
+.fa-ship:before {
+ content: "\f21a";
+}
+.fa-user-secret:before {
+ content: "\f21b";
+}
+.fa-motorcycle:before {
+ content: "\f21c";
+}
+.fa-street-view:before {
+ content: "\f21d";
+}
+.fa-heartbeat:before {
+ content: "\f21e";
+}
+.fa-venus:before {
+ content: "\f221";
+}
+.fa-mars:before {
+ content: "\f222";
+}
+.fa-mercury:before {
+ content: "\f223";
+}
+.fa-intersex:before,
+.fa-transgender:before {
+ content: "\f224";
+}
+.fa-transgender-alt:before {
+ content: "\f225";
+}
+.fa-venus-double:before {
+ content: "\f226";
+}
+.fa-mars-double:before {
+ content: "\f227";
+}
+.fa-venus-mars:before {
+ content: "\f228";
+}
+.fa-mars-stroke:before {
+ content: "\f229";
+}
+.fa-mars-stroke-v:before {
+ content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+ content: "\f22b";
+}
+.fa-neuter:before {
+ content: "\f22c";
+}
+.fa-genderless:before {
+ content: "\f22d";
+}
+.fa-facebook-official:before {
+ content: "\f230";
+}
+.fa-pinterest-p:before {
+ content: "\f231";
+}
+.fa-whatsapp:before {
+ content: "\f232";
+}
+.fa-server:before {
+ content: "\f233";
+}
+.fa-user-plus:before {
+ content: "\f234";
+}
+.fa-user-times:before {
+ content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+ content: "\f236";
+}
+.fa-viacoin:before {
+ content: "\f237";
+}
+.fa-train:before {
+ content: "\f238";
+}
+.fa-subway:before {
+ content: "\f239";
+}
+.fa-medium:before {
+ content: "\f23a";
+}
+.fa-yc:before,
+.fa-y-combinator:before {
+ content: "\f23b";
+}
+.fa-optin-monster:before {
+ content: "\f23c";
+}
+.fa-opencart:before {
+ content: "\f23d";
+}
+.fa-expeditedssl:before {
+ content: "\f23e";
+}
+.fa-battery-4:before,
+.fa-battery-full:before {
+ content: "\f240";
+}
+.fa-battery-3:before,
+.fa-battery-three-quarters:before {
+ content: "\f241";
+}
+.fa-battery-2:before,
+.fa-battery-half:before {
+ content: "\f242";
+}
+.fa-battery-1:before,
+.fa-battery-quarter:before {
+ content: "\f243";
+}
+.fa-battery-0:before,
+.fa-battery-empty:before {
+ content: "\f244";
+}
+.fa-mouse-pointer:before {
+ content: "\f245";
+}
+.fa-i-cursor:before {
+ content: "\f246";
+}
+.fa-object-group:before {
+ content: "\f247";
+}
+.fa-object-ungroup:before {
+ content: "\f248";
+}
+.fa-sticky-note:before {
+ content: "\f249";
+}
+.fa-sticky-note-o:before {
+ content: "\f24a";
+}
+.fa-cc-jcb:before {
+ content: "\f24b";
+}
+.fa-cc-diners-club:before {
+ content: "\f24c";
+}
+.fa-clone:before {
+ content: "\f24d";
+}
+.fa-balance-scale:before {
+ content: "\f24e";
+}
+.fa-hourglass-o:before {
+ content: "\f250";
+}
+.fa-hourglass-1:before,
+.fa-hourglass-start:before {
+ content: "\f251";
+}
+.fa-hourglass-2:before,
+.fa-hourglass-half:before {
+ content: "\f252";
+}
+.fa-hourglass-3:before,
+.fa-hourglass-end:before {
+ content: "\f253";
+}
+.fa-hourglass:before {
+ content: "\f254";
+}
+.fa-hand-grab-o:before,
+.fa-hand-rock-o:before {
+ content: "\f255";
+}
+.fa-hand-stop-o:before,
+.fa-hand-paper-o:before {
+ content: "\f256";
+}
+.fa-hand-scissors-o:before {
+ content: "\f257";
+}
+.fa-hand-lizard-o:before {
+ content: "\f258";
+}
+.fa-hand-spock-o:before {
+ content: "\f259";
+}
+.fa-hand-pointer-o:before {
+ content: "\f25a";
+}
+.fa-hand-peace-o:before {
+ content: "\f25b";
+}
+.fa-trademark:before {
+ content: "\f25c";
+}
+.fa-registered:before {
+ content: "\f25d";
+}
+.fa-creative-commons:before {
+ content: "\f25e";
+}
+.fa-gg:before {
+ content: "\f260";
+}
+.fa-gg-circle:before {
+ content: "\f261";
+}
+.fa-tripadvisor:before {
+ content: "\f262";
+}
+.fa-odnoklassniki:before {
+ content: "\f263";
+}
+.fa-odnoklassniki-square:before {
+ content: "\f264";
+}
+.fa-get-pocket:before {
+ content: "\f265";
+}
+.fa-wikipedia-w:before {
+ content: "\f266";
+}
+.fa-safari:before {
+ content: "\f267";
+}
+.fa-chrome:before {
+ content: "\f268";
+}
+.fa-firefox:before {
+ content: "\f269";
+}
+.fa-opera:before {
+ content: "\f26a";
+}
+.fa-internet-explorer:before {
+ content: "\f26b";
+}
+.fa-tv:before,
+.fa-television:before {
+ content: "\f26c";
+}
+.fa-contao:before {
+ content: "\f26d";
+}
+.fa-500px:before {
+ content: "\f26e";
+}
+.fa-amazon:before {
+ content: "\f270";
+}
+.fa-calendar-plus-o:before {
+ content: "\f271";
+}
+.fa-calendar-minus-o:before {
+ content: "\f272";
+}
+.fa-calendar-times-o:before {
+ content: "\f273";
+}
+.fa-calendar-check-o:before {
+ content: "\f274";
+}
+.fa-industry:before {
+ content: "\f275";
+}
+.fa-map-pin:before {
+ content: "\f276";
+}
+.fa-map-signs:before {
+ content: "\f277";
+}
+.fa-map-o:before {
+ content: "\f278";
+}
+.fa-map:before {
+ content: "\f279";
+}
+.fa-commenting:before {
+ content: "\f27a";
+}
+.fa-commenting-o:before {
+ content: "\f27b";
+}
+.fa-houzz:before {
+ content: "\f27c";
+}
+.fa-vimeo:before {
+ content: "\f27d";
+}
+.fa-black-tie:before {
+ content: "\f27e";
+}
+.fa-fonticons:before {
+ content: "\f280";
+}
+.fa-reddit-alien:before {
+ content: "\f281";
+}
+.fa-edge:before {
+ content: "\f282";
+}
+.fa-credit-card-alt:before {
+ content: "\f283";
+}
+.fa-codiepie:before {
+ content: "\f284";
+}
+.fa-modx:before {
+ content: "\f285";
+}
+.fa-fort-awesome:before {
+ content: "\f286";
+}
+.fa-usb:before {
+ content: "\f287";
+}
+.fa-product-hunt:before {
+ content: "\f288";
+}
+.fa-mixcloud:before {
+ content: "\f289";
+}
+.fa-scribd:before {
+ content: "\f28a";
+}
+.fa-pause-circle:before {
+ content: "\f28b";
+}
+.fa-pause-circle-o:before {
+ content: "\f28c";
+}
+.fa-stop-circle:before {
+ content: "\f28d";
+}
+.fa-stop-circle-o:before {
+ content: "\f28e";
+}
+.fa-shopping-bag:before {
+ content: "\f290";
+}
+.fa-shopping-basket:before {
+ content: "\f291";
+}
+.fa-hashtag:before {
+ content: "\f292";
+}
+.fa-bluetooth:before {
+ content: "\f293";
+}
+.fa-bluetooth-b:before {
+ content: "\f294";
+}
+.fa-percent:before {
+ content: "\f295";
+}
diff --git a/themes/CleanFS/font-awesome.min.css b/themes/CleanFS/font-awesome.min.css
new file mode 100644
index 0000000..acce2ef
--- /dev/null
+++ b/themes/CleanFS/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.5.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}
diff --git a/themes/CleanFS/fonts/FontAwesome.otf b/themes/CleanFS/fonts/FontAwesome.otf
new file mode 100644
index 0000000..3ed7f8b
--- /dev/null
+++ b/themes/CleanFS/fonts/FontAwesome.otf
Binary files differ
diff --git a/themes/CleanFS/fonts/fontawesome-webfont.eot b/themes/CleanFS/fonts/fontawesome-webfont.eot
new file mode 100644
index 0000000..9b6afae
--- /dev/null
+++ b/themes/CleanFS/fonts/fontawesome-webfont.eot
Binary files differ
diff --git a/themes/CleanFS/fonts/fontawesome-webfont.svg b/themes/CleanFS/fonts/fontawesome-webfont.svg
new file mode 100644
index 0000000..d05688e
--- /dev/null
+++ b/themes/CleanFS/fonts/fontawesome-webfont.svg
@@ -0,0 +1,655 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" " horiz-adv-x="448" />
+<glyph unicode="&#x09;" horiz-adv-x="448" />
+<glyph unicode="&#xa0;" horiz-adv-x="448" />
+<glyph unicode="&#xa8;" horiz-adv-x="1792" />
+<glyph unicode="&#xa9;" horiz-adv-x="1792" />
+<glyph unicode="&#xae;" horiz-adv-x="1792" />
+<glyph unicode="&#xb4;" horiz-adv-x="1792" />
+<glyph unicode="&#xc6;" horiz-adv-x="1792" />
+<glyph unicode="&#xd8;" horiz-adv-x="1792" />
+<glyph unicode="&#x2000;" horiz-adv-x="768" />
+<glyph unicode="&#x2001;" horiz-adv-x="1537" />
+<glyph unicode="&#x2002;" horiz-adv-x="768" />
+<glyph unicode="&#x2003;" horiz-adv-x="1537" />
+<glyph unicode="&#x2004;" horiz-adv-x="512" />
+<glyph unicode="&#x2005;" horiz-adv-x="384" />
+<glyph unicode="&#x2006;" horiz-adv-x="256" />
+<glyph unicode="&#x2007;" horiz-adv-x="256" />
+<glyph unicode="&#x2008;" horiz-adv-x="192" />
+<glyph unicode="&#x2009;" horiz-adv-x="307" />
+<glyph unicode="&#x200a;" horiz-adv-x="85" />
+<glyph unicode="&#x202f;" horiz-adv-x="307" />
+<glyph unicode="&#x205f;" horiz-adv-x="384" />
+<glyph unicode="&#x2122;" horiz-adv-x="1792" />
+<glyph unicode="&#x221e;" horiz-adv-x="1792" />
+<glyph unicode="&#x2260;" horiz-adv-x="1792" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xf000;" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
+<glyph unicode="&#xf001;" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf002;" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf003;" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf004;" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
+<glyph unicode="&#xf005;" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf006;" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf007;" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf008;" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf009;" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf00a;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00b;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00c;" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
+<glyph unicode="&#xf00d;" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
+<glyph unicode="&#xf00e;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf010;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
+<glyph unicode="&#xf011;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
+<glyph unicode="&#xf012;" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf013;" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
+<glyph unicode="&#xf014;" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf015;" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
+<glyph unicode="&#xf016;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z " />
+<glyph unicode="&#xf017;" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf018;" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
+<glyph unicode="&#xf019;" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
+<glyph unicode="&#xf01a;" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01b;" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01c;" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
+<glyph unicode="&#xf01d;" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01e;" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
+<glyph unicode="&#xf021;" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf022;" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
+<glyph unicode="&#xf023;" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf024;" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf025;" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
+<glyph unicode="&#xf026;" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf027;" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
+<glyph unicode="&#xf028;" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
+<glyph unicode="&#xf029;" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
+<glyph unicode="&#xf02a;" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
+<glyph unicode="&#xf02b;" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02c;" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02d;" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="&#xf02e;" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf02f;" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
+<glyph unicode="&#xf030;" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf031;" horiz-adv-x="1664" d="M725 977l-170 -450q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452zM0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57 q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5 q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14z" />
+<glyph unicode="&#xf032;" horiz-adv-x="1408" d="M555 15q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5t4.5 -83.5t12 -66.5zM541 761q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142 q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13q0 -50 4 -151t4 -152q0 -27 -0.5 -80t-0.5 -79q0 -46 1 -69zM0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5 t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5 t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12z" />
+<glyph unicode="&#xf033;" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" />
+<glyph unicode="&#xf034;" horiz-adv-x="1792" d="M1744 128q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80zM81 1407l54 -27q12 -5 211 -5q44 0 132 2 t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5 q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44v383z" />
+<glyph unicode="&#xf035;" d="M81 1407l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1t-103 1 t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44v383zM1310 125q12 0 42 -19.5t57.5 -41.5 t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49 t26 49q4 3 36 30t59.5 49t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5z" />
+<glyph unicode="&#xf036;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf037;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf038;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf039;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf03a;" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03b;" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03c;" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03d;" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
+<glyph unicode="&#xf03e;" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf040;" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
+<glyph unicode="&#xf041;" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
+<glyph unicode="&#xf042;" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf043;" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
+<glyph unicode="&#xf044;" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
+<glyph unicode="&#xf045;" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf046;" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
+<glyph unicode="&#xf047;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf048;" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
+<glyph unicode="&#xf049;" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
+<glyph unicode="&#xf04a;" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
+<glyph unicode="&#xf04b;" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
+<glyph unicode="&#xf04c;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04d;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04e;" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf050;" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf051;" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
+<glyph unicode="&#xf052;" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
+<glyph unicode="&#xf053;" horiz-adv-x="1280" d="M1171 1235l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45t19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf054;" horiz-adv-x="1280" d="M1107 659l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45t19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf055;" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf056;" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="&#xf057;" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf058;" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf059;" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05a;" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05b;" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf05c;" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05d;" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05e;" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
+<glyph unicode="&#xf060;" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
+<glyph unicode="&#xf061;" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
+<glyph unicode="&#xf062;" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
+<glyph unicode="&#xf063;" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf064;" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
+<glyph unicode="&#xf065;" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf066;" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
+<glyph unicode="&#xf067;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf068;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf069;" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
+<glyph unicode="&#xf06a;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
+<glyph unicode="&#xf06b;" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf06c;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
+<glyph unicode="&#xf06d;" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
+<glyph unicode="&#xf06e;" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
+<glyph unicode="&#xf070;" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
+<glyph unicode="&#xf071;" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
+<glyph unicode="&#xf072;" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
+<glyph unicode="&#xf073;" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf074;" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf075;" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf076;" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf077;" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" />
+<glyph unicode="&#xf078;" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" />
+<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
+<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1536 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1664 1088v-512q0 -24 -16.5 -42.5t-40.5 -21.5l-1044 -122q13 -60 13 -70q0 -16 -24 -64h920q26 0 45 -19t19 -45 t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 11 8 31.5t16 36t21.5 40t15.5 29.5l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t19.5 -15.5t13 -24.5t8 -26t5.5 -29.5t4.5 -26h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf080;" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" />
+<glyph unicode="&#xf081;" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf082;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-188v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-532q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
+<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
+<glyph unicode="&#xf086;" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
+<glyph unicode="&#xf087;" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf088;" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
+<glyph unicode="&#xf089;" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
+<glyph unicode="&#xf08a;" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
+<glyph unicode="&#xf08b;" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
+<glyph unicode="&#xf08c;" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf08d;" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
+<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf090;" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf092;" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
+<glyph unicode="&#xf094;" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
+<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
+<glyph unicode="&#xf096;" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf097;" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf098;" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf099;" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
+<glyph unicode="&#xf09a;" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" />
+<glyph unicode="&#xf09b;" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
+<glyph unicode="&#xf0a0;" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
+<glyph unicode="&#xf0a1;" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
+<glyph unicode="&#xf0a2;" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM246 128h1300q-266 300 -266 832q0 51 -24 105t-69 103t-121.5 80.5t-169.5 31.5t-169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -532 -266 -832z M1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5 t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" />
+<glyph unicode="&#xf0a3;" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
+<glyph unicode="&#xf0a4;" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf0a5;" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf0a6;" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
+<glyph unicode="&#xf0a7;" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
+<glyph unicode="&#xf0a8;" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0a9;" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0aa;" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ab;" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ac;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
+<glyph unicode="&#xf0ad;" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
+<glyph unicode="&#xf0ae;" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0b0;" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
+<glyph unicode="&#xf0b1;" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0b2;" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
+<glyph unicode="&#xf0c0;" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
+<glyph unicode="&#xf0c1;" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
+<glyph unicode="&#xf0c2;" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
+<glyph unicode="&#xf0c3;" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
+<glyph unicode="&#xf0c4;" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="&#xf0c5;" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
+<glyph unicode="&#xf0c6;" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
+<glyph unicode="&#xf0c7;" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0c8;" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0c9;" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0ca;" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cb;" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cc;" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
+<glyph unicode="&#xf0cd;" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
+<glyph unicode="&#xf0ce;" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
+<glyph unicode="&#xf0d0;" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
+<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf0d4;" d="M917 631q0 26 -6 64h-362v-132h217q-3 -24 -16.5 -50t-37.5 -53t-66.5 -44.5t-96.5 -17.5q-99 0 -169 71t-70 171t70 171t169 71q92 0 153 -59l104 101q-108 100 -257 100q-160 0 -272 -112.5t-112 -271.5t112 -271.5t272 -112.5q165 0 266.5 105t101.5 270zM1262 585 h109v110h-109v110h-110v-110h-110v-110h110v-110h110v110zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0d5;" horiz-adv-x="2304" d="M1437 623q0 -208 -87 -370.5t-248 -254t-369 -91.5q-149 0 -285 58t-234 156t-156 234t-58 285t58 285t156 234t234 156t285 58q286 0 491 -192l-199 -191q-117 113 -292 113q-123 0 -227.5 -62t-165.5 -168.5t-61 -232.5t61 -232.5t165.5 -168.5t227.5 -62 q83 0 152.5 23t114.5 57.5t78.5 78.5t49 83t21.5 74h-416v252h692q12 -63 12 -122zM2304 745v-210h-209v-209h-210v209h-209v210h209v209h210v-209h209z" />
+<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0d9;" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0da;" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0db;" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0dc;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0dd;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0de;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0e0;" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
+<glyph unicode="&#xf0e1;" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
+<glyph unicode="&#xf0e2;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
+<glyph unicode="&#xf0e3;" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
+<glyph unicode="&#xf0e4;" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf0e5;" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf0e6;" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
+<glyph unicode="&#xf0e7;" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
+<glyph unicode="&#xf0e8;" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf0e9;" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0ea;" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0eb;" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
+<glyph unicode="&#xf0ec;" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf0ed;" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0ee;" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0f0;" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f1;" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf0f2;" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
+<glyph unicode="&#xf0f3;" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5 t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" />
+<glyph unicode="&#xf0f4;" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f5;" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f6;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M384 736q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64zM1120 512q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704zM1120 256q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704 q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704z" />
+<glyph unicode="&#xf0f7;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f8;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f9;" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0fa;" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf0fb;" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
+<glyph unicode="&#xf0fc;" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
+<glyph unicode="&#xf0fd;" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0fe;" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf100;" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
+<glyph unicode="&#xf101;" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf102;" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf103;" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf104;" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf105;" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf106;" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf107;" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf108;" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf109;" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf10c;" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf110;" horiz-adv-x="1792" d="M526 142q0 -53 -37.5 -90.5t-90.5 -37.5q-52 0 -90 38t-38 90q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1522 142q0 -52 -38 -90t-90 -38q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM558 1138q0 -66 -47 -113t-113 -47t-113 47t-47 113t47 113t113 47t113 -47t47 -113z M1728 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1088 1344q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1618 1138q0 -93 -66 -158.5t-158 -65.5q-93 0 -158.5 65.5t-65.5 158.5 q0 92 65.5 158t158.5 66q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf111;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
+<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
+<glyph unicode="&#xf114;" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf115;" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
+<glyph unicode="&#xf116;" horiz-adv-x="1792" />
+<glyph unicode="&#xf117;" horiz-adv-x="1792" />
+<glyph unicode="&#xf118;" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf119;" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11a;" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11b;" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
+<glyph unicode="&#xf11c;" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf11d;" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf11e;" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf120;" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
+<glyph unicode="&#xf121;" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
+<glyph unicode="&#xf122;" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
+<glyph unicode="&#xf123;" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
+<glyph unicode="&#xf124;" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
+<glyph unicode="&#xf125;" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf126;" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf127;" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="&#xf128;" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
+<glyph unicode="&#xf129;" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf12a;" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
+<glyph unicode="&#xf12b;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
+<glyph unicode="&#xf12c;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
+<glyph unicode="&#xf12d;" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
+<glyph unicode="&#xf12e;" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
+<glyph unicode="&#xf130;" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
+<glyph unicode="&#xf131;" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
+<glyph unicode="&#xf132;" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf133;" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf134;" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
+<glyph unicode="&#xf135;" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
+<glyph unicode="&#xf136;" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" />
+<glyph unicode="&#xf137;" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf138;" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf139;" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13a;" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13b;" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
+<glyph unicode="&#xf13c;" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
+<glyph unicode="&#xf13d;" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf13e;" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
+<glyph unicode="&#xf140;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf141;" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf142;" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf143;" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf144;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
+<glyph unicode="&#xf145;" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
+<glyph unicode="&#xf146;" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="&#xf147;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf148;" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
+<glyph unicode="&#xf149;" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
+<glyph unicode="&#xf14a;" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14b;" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14c;" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14d;" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14e;" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf150;" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf151;" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf152;" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf153;" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
+<glyph unicode="&#xf154;" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
+<glyph unicode="&#xf155;" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
+<glyph unicode="&#xf156;" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf157;" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+<glyph unicode="&#xf158;" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" />
+<glyph unicode="&#xf159;" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf15a;" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
+<glyph unicode="&#xf15b;" d="M1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472zM896 992q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544z" />
+<glyph unicode="&#xf15c;" d="M1468 1060q14 -14 28 -36h-472v472q22 -14 36 -28zM992 896h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28zM1152 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23z" />
+<glyph unicode="&#xf15d;" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
+<glyph unicode="&#xf15e;" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
+<glyph unicode="&#xf160;" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf161;" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf162;" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
+<glyph unicode="&#xf163;" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
+<glyph unicode="&#xf164;" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
+<glyph unicode="&#xf165;" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
+<glyph unicode="&#xf166;" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf167;" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
+<glyph unicode="&#xf168;" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
+<glyph unicode="&#xf169;" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
+<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
+<glyph unicode="&#xf16c;" d="M1289 -96h-1118v480h-160v-640h1438v640h-160v-480zM347 428l33 157l783 -165l-33 -156zM450 802l67 146l725 -339l-67 -145zM651 1158l102 123l614 -513l-102 -123zM1048 1536l477 -641l-128 -96l-477 641zM330 65v159h800v-159h-800z" />
+<glyph unicode="&#xf16d;" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
+<glyph unicode="&#xf16e;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
+<glyph unicode="&#xf170;" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf171;" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
+<glyph unicode="&#xf172;" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf173;" horiz-adv-x="1024" d="M944 207l80 -237q-23 -35 -111 -66t-177 -32q-104 -2 -190.5 26t-142.5 74t-95 106t-55.5 120t-16.5 118v544h-168v215q72 26 129 69.5t91 90t58 102t34 99t15 88.5q1 5 4.5 8.5t7.5 3.5h244v-424h333v-252h-334v-518q0 -30 6.5 -56t22.5 -52.5t49.5 -41.5t81.5 -14 q78 2 134 29z" />
+<glyph unicode="&#xf174;" d="M1136 75l-62 183q-44 -22 -103 -22q-36 -1 -62 10.5t-38.5 31.5t-17.5 40.5t-5 43.5v398h257v194h-256v326h-188q-8 0 -9 -10q-5 -44 -17.5 -87t-39 -95t-77 -95t-118.5 -68v-165h130v-418q0 -57 21.5 -115t65 -111t121 -85.5t176.5 -30.5q69 1 136.5 25t85.5 50z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf175;" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
+<glyph unicode="&#xf176;" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
+<glyph unicode="&#xf177;" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf178;" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
+<glyph unicode="&#xf179;" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
+<glyph unicode="&#xf17a;" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
+<glyph unicode="&#xf17b;" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
+<glyph unicode="&#xf17c;" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
+<glyph unicode="&#xf17d;" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf17e;" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
+<glyph unicode="&#xf180;" horiz-adv-x="1280" d="M1000 1102l37 194q5 23 -9 40t-35 17h-712q-23 0 -38.5 -17t-15.5 -37v-1101q0 -7 6 -1l291 352q23 26 38 33.5t48 7.5h239q22 0 37 14.5t18 29.5q24 130 37 191q4 21 -11.5 40t-36.5 19h-294q-29 0 -48 19t-19 48v42q0 29 19 47.5t48 18.5h346q18 0 35 13.5t20 29.5z M1227 1324q-15 -73 -53.5 -266.5t-69.5 -350t-35 -173.5q-6 -22 -9 -32.5t-14 -32.5t-24.5 -33t-38.5 -21t-58 -10h-271q-13 0 -22 -10q-8 -9 -426 -494q-22 -25 -58.5 -28.5t-48.5 5.5q-55 22 -55 98v1410q0 55 38 102.5t120 47.5h888q95 0 127 -53t10 -159zM1227 1324 l-158 -790q4 17 35 173.5t69.5 350t53.5 266.5z" />
+<glyph unicode="&#xf181;" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf182;" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf183;" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf184;" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf185;" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
+<glyph unicode="&#xf186;" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
+<glyph unicode="&#xf187;" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf188;" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
+<glyph unicode="&#xf189;" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
+<glyph unicode="&#xf18a;" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
+<glyph unicode="&#xf18b;" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" />
+<glyph unicode="&#xf18c;" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" />
+<glyph unicode="&#xf18d;" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " />
+<glyph unicode="&#xf18e;" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf190;" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf191;" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf192;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf193;" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" />
+<glyph unicode="&#xf194;" d="M1292 898q10 216 -161 222q-231 8 -312 -261q44 19 82 19q85 0 74 -96q-4 -57 -74 -167t-105 -110q-43 0 -82 169q-13 54 -45 255q-30 189 -160 177q-59 -7 -164 -100l-81 -72l-81 -72l52 -67q76 52 87 52q57 0 107 -179q15 -55 45 -164.5t45 -164.5q68 -179 164 -179 q157 0 383 294q220 283 226 444zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf195;" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf196;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf197;" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" />
+<glyph unicode="&#xf198;" horiz-adv-x="1664" d="M1519 760q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72l-55 163l-153 -53q-29 -9 -50 -9 q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102 t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8zM725 498l310 105l-105 315l-310 -107z" />
+<glyph unicode="&#xf199;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM1280 352v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99q-98 -69 -164 -69v0v0q-66 0 -164 69 q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436q0 -40 28 -68t68 -28h832q40 0 68 28t28 68zM1280 925q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13 t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5z" />
+<glyph unicode="&#xf19a;" horiz-adv-x="1792" d="M127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM1415 679q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15t-1.5 -18.5t9 -16.5 t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21 t14.5 -24t14 -23q63 -107 63 -212zM909 573l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1570 1009q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5l235 678q59 169 59 276q0 42 -6 79zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286 t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 -215q173 0 331.5 68t273 182.5t182.5 273t68 331.5t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5t68 -331.5t182.5 -273 t273 -182.5t331.5 -68z" />
+<glyph unicode="&#xf19b;" horiz-adv-x="1792" d="M1086 1536v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360zM1755 954l37 -390l-525 114l147 83q-119 70 -280 99v172q277 -33 481 -157z" />
+<glyph unicode="&#xf19c;" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" />
+<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" />
+<glyph unicode="&#xf19e;" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" />
+<glyph unicode="&#xf1a0;" d="M768 750h725q12 -67 12 -128q0 -217 -91 -387.5t-259.5 -266.5t-386.5 -96q-157 0 -299 60.5t-245 163.5t-163.5 245t-60.5 299t60.5 299t163.5 245t245 163.5t299 60.5q300 0 515 -201l-209 -201q-123 119 -306 119q-129 0 -238.5 -65t-173.5 -176.5t-64 -243.5 t64 -243.5t173.5 -176.5t238.5 -65q87 0 160 24t120 60t82 82t51.5 87t22.5 78h-436v264z" />
+<glyph unicode="&#xf1a1;" horiz-adv-x="1792" d="M1095 369q16 -16 0 -31q-62 -62 -199 -62t-199 62q-16 15 0 31q6 6 15 6t15 -6q48 -49 169 -49q120 0 169 49q6 6 15 6t15 -6zM788 550q0 -37 -26 -63t-63 -26t-63.5 26t-26.5 63q0 38 26.5 64t63.5 26t63 -26.5t26 -63.5zM1183 550q0 -37 -26.5 -63t-63.5 -26t-63 26 t-26 63t26 63.5t63 26.5t63.5 -26t26.5 -64zM1434 670q0 49 -35 84t-85 35t-86 -36q-130 90 -311 96l63 283l200 -45q0 -37 26 -63t63 -26t63.5 26.5t26.5 63.5t-26.5 63.5t-63.5 26.5q-54 0 -80 -50l-221 49q-19 5 -25 -16l-69 -312q-180 -7 -309 -97q-35 37 -87 37 q-50 0 -85 -35t-35 -84q0 -35 18.5 -64t49.5 -44q-6 -27 -6 -56q0 -142 140 -243t337 -101q198 0 338 101t140 243q0 32 -7 57q30 15 48 43.5t18 63.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191 t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf1a2;" d="M939 407q13 -13 0 -26q-53 -53 -171 -53t-171 53q-13 13 0 26q5 6 13 6t13 -6q42 -42 145 -42t145 42q5 6 13 6t13 -6zM676 563q0 -31 -23 -54t-54 -23t-54 23t-23 54q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1014 563q0 -31 -23 -54t-54 -23t-54 23t-23 54 q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1229 666q0 42 -30 72t-73 30q-42 0 -73 -31q-113 78 -267 82l54 243l171 -39q1 -32 23.5 -54t53.5 -22q32 0 54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5q-48 0 -69 -43l-189 42q-17 5 -21 -13l-60 -268q-154 -6 -265 -83 q-30 32 -74 32q-43 0 -73 -30t-30 -72q0 -30 16 -55t42 -38q-5 -25 -5 -48q0 -122 120 -208.5t289 -86.5q170 0 290 86.5t120 208.5q0 25 -6 49q25 13 40.5 37.5t15.5 54.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1a3;" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1a4;" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" />
+<glyph unicode="&#xf1a5;" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="&#xf1a6;" horiz-adv-x="2048" d="M328 1254h204v-983h-532v697h328v286zM328 435v369h-123v-369h123zM614 968v-697h205v697h-205zM614 1254v-204h205v204h-205zM901 968h533v-942h-533v163h328v82h-328v697zM1229 435v369h-123v-369h123zM1516 968h532v-942h-532v163h327v82h-327v697zM1843 435v369h-123 v-369h123z" />
+<glyph unicode="&#xf1a7;" d="M1046 516q0 -64 -38 -109t-91 -45q-43 0 -70 15v277q28 17 70 17q53 0 91 -45.5t38 -109.5zM703 944q0 -64 -38 -109.5t-91 -45.5q-43 0 -70 15v277q28 17 70 17q53 0 91 -45t38 -109zM1265 513q0 134 -88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101 v-636l211 41v206q51 -19 117 -19q125 0 213 95t88 229zM922 940q0 134 -88.5 229t-213.5 95q-74 0 -141 -36h-186v-840l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1a8;" horiz-adv-x="2038" d="M1222 607q75 3 143.5 -20.5t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14 q6 -5 28 -23.5t25.5 -22t19 -18t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24 q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33 q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5 t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5zM1282 842q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43 q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5 t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM822 568l48 12l109 -177l-73 -48zM1323 51q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13 t-54 -9.5t-53.5 -7.5t-32 -4.5l-7 43q21 2 60.5 8.5t72 10t60.5 3.5h14zM866 679l-96 -20l-6 17q10 1 32.5 7t34.5 6q19 0 35 -10zM1061 45h31l10 -83l-41 -12v95zM1950 1535v1v-1zM1950 1535l-1 -5l-2 -2l1 3zM1950 1535l1 1z" />
+<glyph unicode="&#xf1a9;" d="M1167 -50q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16t7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29zM1128 65q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10 q-29 -12 -78 -56q-26 -24 -12 -44q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34zM1483 346q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14 q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106zM1536 506q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44 q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5t19.5 -177.5z" />
+<glyph unicode="&#xf1aa;" d="M1070 463l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5t-60 145.5q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160z M729 1145l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5q-70 15 -115 71t-45 129q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5 t149.5 -87.5zM1536 78q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30l-152 152l-160 160l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5 q76 -11 126.5 -68.5t50.5 -134.5zM1534 1202q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152l-160 -160l-152 152l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126 t135.5 51q85 0 145 -60.5t60 -145.5z" />
+<glyph unicode="&#xf1ab;" d="M654 458q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110q-4 -2 -19.5 -4t-18.5 0q6 4 82 92q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5 q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28 q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5zM449 944q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17zM1147 815l63 -227l-139 42zM39 15l694 232v1032l-694 -233v-1031z M1280 332l102 -31l-181 657l-100 31l-216 -536l102 -31l45 110l211 -65zM777 1294l573 -184v380zM1088 -29l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11 q73 -37 159.5 -61.5t157.5 -24.5q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5zM1536 1050v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5 q20 0 20 -21v-418z" />
+<glyph unicode="&#xf1ac;" horiz-adv-x="1792" d="M288 1152q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h128zM1664 989q58 -34 93 -93t35 -128v-768q0 -106 -75 -181t-181 -75h-864q-66 0 -113 47t-47 113v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48 l152 -152q28 -28 48 -76t20 -88v-163zM928 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 512v128q0 14 -9 23 t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23zM1184 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 256v128q0 14 -9 23t-23 9h-128 q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1536 896v256h-160q-40 0 -68 28t-28 68v160h-640v-512h896z" />
+<glyph unicode="&#xf1ad;" d="M1344 1536q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280zM512 1248v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 992v-64q0 -14 9 -23t23 -9h64q14 0 23 9 t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 736v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 480v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 160v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM384 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 -96v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM896 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 928v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 160v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23z" />
+<glyph unicode="&#xf1ae;" horiz-adv-x="1280" d="M1188 988l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68t28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68zM864 1152q0 -93 -65.5 -158.5 t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf1b0;" horiz-adv-x="1664" d="M780 1064q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5zM438 581q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152q0 80 42 139.5t119 59.5 q76 0 141.5 -55.5t100.5 -134t35 -152.5zM832 608q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146q0 86 56 191.5t139.5 192.5t187.5 146t193 59zM1071 819 q-61 0 -105 39t-63 92.5t-19 113.5q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5zM1503 923q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5q-77 0 -119 59t-42 139q0 74 35 152.5 t100.5 134t141.5 55.5z" />
+<glyph unicode="&#xf1b1;" horiz-adv-x="768" d="M704 1008q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5z" />
+<glyph unicode="&#xf1b2;" horiz-adv-x="1792" d="M896 -93l640 349v636l-640 -233v-752zM832 772l698 254l-698 254l-698 -254zM1664 1024v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73z " />
+<glyph unicode="&#xf1b3;" horiz-adv-x="2304" d="M640 -96l384 192v314l-384 -164v-342zM576 358l404 173l-404 173l-404 -173zM1664 -96l384 192v314l-384 -164v-342zM1600 358l404 173l-404 173l-404 -173zM1152 651l384 165v266l-384 -164v-267zM1088 1030l441 189l-441 189l-441 -189zM2176 512v-416q0 -36 -19 -67 t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70 v-400l434 -186q36 -16 57 -48t21 -70z" />
+<glyph unicode="&#xf1b4;" horiz-adv-x="2048" d="M1848 1197h-511v-124h511v124zM1596 771q-90 0 -146 -52.5t-62 -142.5h408q-18 195 -200 195zM1612 186q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658 q0 -111 57.5 -171.5t166.5 -60.5zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5t45.5 113.5q0 144 -190 144h-260v-294zM0 1282h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204 q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611v1260z" />
+<glyph unicode="&#xf1b5;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM499 1041h-371v-787h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5 t-56.5 60.5t-79 29.5t-97 8.5zM477 723h-176v184h163q119 0 119 -90q0 -94 -106 -94zM486 388h-185v217h189q124 0 124 -113q0 -104 -128 -104zM1136 356q-68 0 -104 38t-36 107h411q1 10 1 30q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216q0 -135 79 -217 t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20zM1126 722q113 0 124 -122h-254q4 56 39 89t91 33zM964 988h319v-77h-319v77z" />
+<glyph unicode="&#xf1b6;" horiz-adv-x="1792" d="M1582 954q0 -101 -71.5 -172.5t-172.5 -71.5t-172.5 71.5t-71.5 172.5t71.5 172.5t172.5 71.5t172.5 -71.5t71.5 -172.5zM812 212q0 104 -73 177t-177 73q-27 0 -54 -6l104 -42q77 -31 109.5 -106.5t1.5 -151.5q-31 -77 -107 -109t-152 -1q-21 8 -62 24.5t-61 24.5 q32 -60 91 -96.5t130 -36.5q104 0 177 73t73 177zM1642 953q0 126 -89.5 215.5t-215.5 89.5q-127 0 -216.5 -89.5t-89.5 -215.5q0 -127 89.5 -216t216.5 -89q126 0 215.5 89t89.5 216zM1792 953q0 -189 -133.5 -322t-321.5 -133l-437 -319q-12 -129 -109 -218t-229 -89 q-121 0 -214 76t-118 192l-230 92v429l389 -157q79 48 173 48q13 0 35 -2l284 407q2 187 135.5 319t320.5 132q188 0 321.5 -133.5t133.5 -321.5z" />
+<glyph unicode="&#xf1b7;" d="M1242 889q0 80 -57 136.5t-137 56.5t-136.5 -57t-56.5 -136q0 -80 56.5 -136.5t136.5 -56.5t137 56.5t57 136.5zM632 301q0 -83 -58 -140.5t-140 -57.5q-56 0 -103 29t-72 77q52 -20 98 -40q60 -24 120 1.5t85 86.5q24 60 -1.5 120t-86.5 84l-82 33q22 5 42 5 q82 0 140 -57.5t58 -140.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v153l172 -69q20 -92 93.5 -152t168.5 -60q104 0 181 70t87 173l345 252q150 0 255.5 105.5t105.5 254.5q0 150 -105.5 255.5t-255.5 105.5 q-148 0 -253 -104.5t-107 -252.5l-225 -322q-9 1 -28 1q-75 0 -137 -37l-297 119v468q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5zM1289 887q0 -100 -71 -170.5t-171 -70.5t-170.5 70.5t-70.5 170.5t70.5 171t170.5 71q101 0 171.5 -70.5t70.5 -171.5z " />
+<glyph unicode="&#xf1b8;" horiz-adv-x="1792" d="M836 367l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5q-11 27 -14.5 55t4 65t12 55t21.5 64t19 53q78 -12 509 -28zM449 953l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188 l-140 86zM1680 436l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164l-230 367l211 362l7 -173q170 -16 283 -5t170 33zM895 1360q-47 -63 -265 -435l-317 187l-19 12l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5 t36 -39.5t32 -35zM1550 1053l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436l313 195zM1407 1279l142 83l-220 -373l-419 20l151 86q-34 89 -75 166t-75.5 123.5t-64.5 80t-47 46.5l-17 13l405 -1 q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190z" />
+<glyph unicode="&#xf1b9;" horiz-adv-x="2048" d="M480 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM516 768h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5zM1888 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM2048 544v-384 q0 -14 -9 -23t-23 -9h-96v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-1024v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h768q98 0 179 -63.5t104 -157.5 l105 -419h28q93 0 158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf1ba;" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" />
+<glyph unicode="&#xf1bb;" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" />
+<glyph unicode="&#xf1bc;" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1bd;" horiz-adv-x="1024" d="M1024 1233l-303 -582l24 -31h279v-415h-507l-44 -30l-142 -273l-30 -30h-301v303l303 583l-24 30h-279v415h507l44 30l142 273l30 30h301v-303z" />
+<glyph unicode="&#xf1be;" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" />
+<glyph unicode="&#xf1c0;" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" />
+<glyph unicode="&#xf1c1;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" />
+<glyph unicode="&#xf1c2;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M233 768v-107h70l164 -661h159l128 485q7 20 10 46q2 16 2 24h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21 q-2 26 -7 46l-99 438h90v107h-300z" />
+<glyph unicode="&#xf1c3;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M429 106v-106h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107 h-290v-107h68l189 -272l-194 -283h-68z" />
+<glyph unicode="&#xf1c4;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M416 106v-106h327v106h-93v167h137q76 0 118 15q67 23 106.5 87t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92zM769 386h-119v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15z" />
+<glyph unicode="&#xf1c5;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M1280 320v-320h-1024v192l192 192l128 -128l384 384zM448 512q-80 0 -136 56t-56 136t56 136t136 56t136 -56t56 -136t-56 -136t-136 -56z" />
+<glyph unicode="&#xf1c6;" d="M640 1152v128h-128v-128h128zM768 1024v128h-128v-128h128zM640 896v128h-128v-128h128zM768 768v128h-128v-128h128zM1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400 v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536h1280zM781 593l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5q0 25 8 52q21 63 120 396v128h128v-128h79 q22 0 39 -13t23 -34zM640 128q53 0 90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45t37.5 -45t90.5 -19z" />
+<glyph unicode="&#xf1c7;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M620 686q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h131l166 167q16 15 35 7zM1037 -3q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5 q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5t23.5 -42.5q18 -15 40 -15zM826 145q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5t20 -44.5q20 -17 44 -17z" />
+<glyph unicode="&#xf1c8;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M768 768q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h384zM1260 766q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9l-265 266v90l265 266q9 9 23 9q4 0 12 -2z" />
+<glyph unicode="&#xf1c9;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M480 768q8 11 21 12.5t24 -6.5l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5l-226 301q-14 19 0 38zM1282 467q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21t6.5 24l182 243 l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5zM662 6q-13 2 -20.5 13t-5.5 24l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5z" />
+<glyph unicode="&#xf1ca;" d="M1497 709v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406 q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14z" />
+<glyph unicode="&#xf1cb;" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" />
+<glyph unicode="&#xf1cc;" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" />
+<glyph unicode="&#xf1cd;" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" />
+<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1760 640q0 -176 -68.5 -336t-184 -275.5t-275.5 -184t-336 -68.5t-336 68.5t-275.5 184t-184 275.5t-68.5 336q0 213 97 398.5t265 305.5t374 151v-228q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5 t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v228q206 -31 374 -151t265 -305.5t97 -398.5z" />
+<glyph unicode="&#xf1d0;" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" />
+<glyph unicode="&#xf1d1;" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf1d2;" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1d3;" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" />
+<glyph unicode="&#xf1d4;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1d5;" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" />
+<glyph unicode="&#xf1d6;" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" />
+<glyph unicode="&#xf1d7;" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" />
+<glyph unicode="&#xf1d8;" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11z" />
+<glyph unicode="&#xf1d9;" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55q-3 39 32 59l1664 960q35 21 68 -2zM1422 26l221 1323l-1434 -827l336 -137 l863 639l-478 -797z" />
+<glyph unicode="&#xf1da;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298zM896 928v-448q0 -14 -9 -23 t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1db;" d="M768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1dc;" horiz-adv-x="1792" d="M1682 -128q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5t45 -15 t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18q-15 10 -45 12t-53 2 t-41 14t-18 45q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22z" />
+<glyph unicode="&#xf1dd;" horiz-adv-x="1280" d="M1278 1347v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179 q-64 117 -64 259q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43z" />
+<glyph unicode="&#xf1de;" d="M352 128v-128h-352v128h352zM704 256q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM864 640v-128h-864v128h864zM224 1152v-128h-224v128h224zM1536 128v-128h-736v128h736zM576 1280q26 0 45 -19t19 -45v-256 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1216 768q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1536 640v-128h-224v128h224zM1536 1152v-128h-864v128h864z" />
+<glyph unicode="&#xf1e0;" d="M1216 512q133 0 226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5t93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5 t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86z" />
+<glyph unicode="&#xf1e1;" d="M1280 341q0 88 -62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5t62.5 -150.5 t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5t150.5 62.5t62.5 150.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1e2;" horiz-adv-x="1792" d="M571 947q-10 25 -34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49t35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49zM1513 1303l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5 t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68zM1521 1359q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23t9 23q10 9 23 9t23 -9l90 -91 q10 -9 10 -22.5t-10 -22.5zM1751 1129q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5t10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23zM1792 1312q0 -14 -9 -23t-23 -9h-96q-14 0 -23 9t-9 23t9 23t23 9h96q14 0 23 -9t9 -23zM1600 1504v-96q0 -14 -9 -23t-23 -9 t-23 9t-9 23v96q0 14 9 23t23 9t23 -9t9 -23zM1751 1449l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5t10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="&#xf1e3;" horiz-adv-x="1792" d="M609 720l287 208l287 -208l-109 -336h-355zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM1515 186q149 203 149 454v3l-102 -89l-240 224l63 323 l134 -12q-150 206 -389 282l53 -124l-287 -159l-287 159l53 124q-239 -76 -389 -282l135 12l62 -323l-240 -224l-102 89v-3q0 -251 149 -454l30 132l326 -40l139 -298l-116 -69q117 -39 240 -39t240 39l-116 69l139 298l326 40z" />
+<glyph unicode="&#xf1e4;" horiz-adv-x="1792" d="M448 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM256 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM832 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM66 768q-28 0 -47 19t-19 46v129h514v-129q0 -27 -19 -46t-46 -19h-383zM1216 224v-192q0 -14 -9 -23t-23 -9h-192 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1600 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23 zM1408 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1016v-13h-514v10q0 104 -382 102q-382 -1 -382 -102v-10h-514v13q0 17 8.5 43t34 64t65.5 75.5t110.5 76t160 67.5t224 47.5t293.5 18.5t293 -18.5t224 -47.5 t160.5 -67.5t110.5 -76t65.5 -75.5t34 -64t8.5 -43zM1792 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 962v-129q0 -27 -19 -46t-46 -19h-384q-27 0 -46 19t-19 46v129h514z" />
+<glyph unicode="&#xf1e5;" horiz-adv-x="1792" d="M704 1216v-768q0 -26 -19 -45t-45 -19v-576q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v512l249 873q7 23 31 23h424zM1024 1216v-704h-256v704h256zM1792 320v-512q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v576q-26 0 -45 19t-19 45v768h424q24 0 31 -23z M736 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23zM1408 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1e6;" horiz-adv-x="1792" d="M1755 1083q37 -37 37 -90t-37 -91l-401 -400l150 -150l-160 -160q-163 -163 -389.5 -186.5t-411.5 100.5l-362 -362h-181v181l362 362q-124 185 -100.5 411.5t186.5 389.5l160 160l150 -150l400 401q38 37 91 37t90 -37t37 -90.5t-37 -90.5l-400 -401l234 -234l401 400 q38 37 91 37t90 -37z" />
+<glyph unicode="&#xf1e7;" horiz-adv-x="1792" d="M873 796q0 -83 -63.5 -142.5t-152.5 -59.5t-152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59t152.5 -59t63.5 -143zM1375 796q0 -83 -63 -142.5t-153 -59.5q-89 0 -152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59q90 0 153 -59t63 -143zM1600 616v667q0 87 -32 123.5 t-111 36.5h-1112q-83 0 -112.5 -34t-29.5 -126v-673q43 -23 88.5 -40t81 -28t81 -18.5t71 -11t70 -4t58.5 -0.5t56.5 2t44.5 2q68 1 95 -27q6 -6 10 -9q26 -25 61 -51q7 91 118 87q5 0 36.5 -1.5t43 -2t45.5 -1t53 1t54.5 4.5t61 8.5t62 13.5t67 19.5t67.5 27t72 34.5z M1763 621q-121 -149 -372 -252q84 -285 -23 -465q-66 -113 -183 -148q-104 -32 -182 15q-86 51 -82 164l-1 326v1q-8 2 -24.5 6t-23.5 5l-1 -338q4 -114 -83 -164q-79 -47 -183 -15q-117 36 -182 150q-105 180 -22 463q-251 103 -372 252q-25 37 -4 63t60 -1q3 -2 11 -7 t11 -8v694q0 72 47 123t114 51h1257q67 0 114 -51t47 -123v-694l21 15q39 27 60 1t-4 -63z" />
+<glyph unicode="&#xf1e8;" horiz-adv-x="1792" d="M896 1102v-434h-145v434h145zM1294 1102v-434h-145v434h145zM1294 342l253 254v795h-1194v-1049h326v-217l217 217h398zM1692 1536v-1013l-434 -434h-326l-217 -217h-217v217h-398v1158l109 289h1483z" />
+<glyph unicode="&#xf1e9;" d="M773 217v-127q-1 -292 -6 -305q-12 -32 -51 -40q-54 -9 -181.5 38t-162.5 89q-13 15 -17 36q-1 12 4 26q4 10 34 47t181 216q1 0 60 70q15 19 39.5 24.5t49.5 -3.5q24 -10 37.5 -29t12.5 -42zM624 468q-3 -55 -52 -70l-120 -39q-275 -88 -292 -88q-35 2 -54 36 q-12 25 -17 75q-8 76 1 166.5t30 124.5t56 32q13 0 202 -77q70 -29 115 -47l84 -34q23 -9 35.5 -30.5t11.5 -48.5zM1450 171q-7 -54 -91.5 -161t-135.5 -127q-37 -14 -63 7q-14 10 -184 287l-47 77q-14 21 -11.5 46t19.5 46q35 43 83 26q1 -1 119 -40q203 -66 242 -79.5 t47 -20.5q28 -22 22 -61zM778 803q5 -102 -54 -122q-58 -17 -114 71l-378 598q-8 35 19 62q41 43 207.5 89.5t224.5 31.5q40 -10 49 -45q3 -18 22 -305.5t24 -379.5zM1440 695q3 -39 -26 -59q-15 -10 -329 -86q-67 -15 -91 -23l1 2q-23 -6 -46 4t-37 32q-30 47 0 87 q1 1 75 102q125 171 150 204t34 39q28 19 65 2q48 -23 123 -133.5t81 -167.5v-3z" />
+<glyph unicode="&#xf1ea;" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" />
+<glyph unicode="&#xf1eb;" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" />
+<glyph unicode="&#xf1ec;" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1ed;" d="M1519 890q18 -84 -4 -204q-87 -444 -565 -444h-44q-25 0 -44 -16.5t-24 -42.5l-4 -19l-55 -346l-2 -15q-5 -26 -24.5 -42.5t-44.5 -16.5h-251q-21 0 -33 15t-9 36q9 56 26.5 168t26.5 168t27 167.5t27 167.5q5 37 43 37h131q133 -2 236 21q175 39 287 144q102 95 155 246 q24 70 35 133q1 6 2.5 7.5t3.5 1t6 -3.5q79 -59 98 -162zM1347 1172q0 -107 -46 -236q-80 -233 -302 -315q-113 -40 -252 -42q0 -1 -90 -1l-90 1q-100 0 -118 -96q-2 -8 -85 -530q-1 -10 -12 -10h-295q-22 0 -36.5 16.5t-11.5 38.5l232 1471q5 29 27.5 48t51.5 19h598 q34 0 97.5 -13t111.5 -32q107 -41 163.5 -123t56.5 -196z" />
+<glyph unicode="&#xf1ee;" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" />
+<glyph unicode="&#xf1f0;" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f1;" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f2;" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" />
+<glyph unicode="&#xf1f3;" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" />
+<glyph unicode="&#xf1f4;" horiz-adv-x="2304" d="M745 630q0 -37 -25.5 -61.5t-62.5 -24.5q-29 0 -46.5 16t-17.5 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM1530 779q0 -42 -22 -57t-66 -15l-32 -1l17 107q2 11 13 11h18q22 0 35 -2t25 -12.5t12 -30.5zM1881 630q0 -36 -25.5 -61t-61.5 -25q-29 0 -47 16 t-18 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM513 801q0 59 -38.5 85.5t-100.5 26.5h-160q-19 0 -21 -19l-65 -408q-1 -6 3 -11t10 -5h76q20 0 22 19l18 110q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM822 489l41 261q1 6 -3 11t-10 5h-76 q-14 0 -17 -33q-27 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q28 0 58 12t48 32q-4 -12 -4 -21q0 -16 13 -16h69q19 0 22 19zM1269 752q0 5 -4 9.5t-9 4.5h-77q-11 0 -18 -10l-106 -156l-44 150q-5 16 -22 16h-75q-5 0 -9 -4.5t-4 -9.5q0 -2 19.5 -59 t42 -123t23.5 -70q-82 -112 -82 -120q0 -13 13 -13h77q11 0 18 10l255 368q2 2 2 7zM1649 801q0 59 -38.5 85.5t-100.5 26.5h-159q-20 0 -22 -19l-65 -408q-1 -6 3 -11t10 -5h82q12 0 16 13l18 116q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM1958 489 l41 261q1 6 -3 11t-10 5h-76q-14 0 -17 -33q-26 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q29 0 59 12t47 32q0 -1 -2 -9t-2 -12q0 -16 13 -16h69q19 0 22 19zM2176 898v1q0 14 -13 14h-74q-11 0 -13 -11l-65 -416l-1 -2q0 -5 4 -9.5t10 -4.5h66 q19 0 21 19zM392 764q-5 -35 -26 -46t-60 -11l-33 -1l17 107q2 11 13 11h19q40 0 58 -11.5t12 -48.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f5;" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f6;" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" />
+<glyph unicode="&#xf1f7;" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" />
+<glyph unicode="&#xf1f8;" horiz-adv-x="1408" d="M512 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM768 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1024 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167 q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1f9;" d="M1150 462v-109q0 -50 -36.5 -89t-94 -60.5t-118 -32.5t-117.5 -11q-205 0 -342.5 139t-137.5 346q0 203 136 339t339 136q34 0 75.5 -4.5t93 -18t92.5 -34t69 -56.5t28 -81v-109q0 -16 -16 -16h-118q-16 0 -16 16v70q0 43 -65.5 67.5t-137.5 24.5q-140 0 -228.5 -91.5 t-88.5 -237.5q0 -151 91.5 -249.5t233.5 -98.5q68 0 138 24t70 66v70q0 7 4.5 11.5t10.5 4.5h119q6 0 11 -4.5t5 -11.5zM768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1fa;" d="M972 761q0 108 -53.5 169t-147.5 61q-63 0 -124 -30.5t-110 -84.5t-79.5 -137t-30.5 -180q0 -112 53.5 -173t150.5 -61q96 0 176 66.5t122.5 166t42.5 203.5zM1536 640q0 -111 -37 -197t-98.5 -135t-131.5 -74.5t-145 -27.5q-6 0 -15.5 -0.5t-16.5 -0.5q-95 0 -142 53 q-28 33 -33 83q-52 -66 -131.5 -110t-173.5 -44q-161 0 -249.5 95.5t-88.5 269.5q0 157 66 290t179 210.5t246 77.5q87 0 155 -35.5t106 -99.5l2 19l11 56q1 6 5.5 12t9.5 6h118q5 0 13 -11q5 -5 3 -16l-120 -614q-5 -24 -5 -48q0 -39 12.5 -52t44.5 -13q28 1 57 5.5t73 24 t77 50t57 89.5t24 137q0 292 -174 466t-466 174q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51q228 0 405 144q11 9 24 8t21 -12l41 -49q8 -12 7 -24q-2 -13 -12 -22q-102 -83 -227.5 -128t-258.5 -45q-156 0 -298 61 t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q344 0 556 -212t212 -556z" />
+<glyph unicode="&#xf1fb;" horiz-adv-x="1792" d="M1698 1442q94 -94 94 -226.5t-94 -225.5l-225 -223l104 -104q10 -10 10 -23t-10 -23l-210 -210q-10 -10 -23 -10t-23 10l-105 105l-603 -603q-37 -37 -90 -37h-203l-256 -128l-64 64l128 256v203q0 53 37 90l603 603l-105 105q-10 10 -10 23t10 23l210 210q10 10 23 10 t23 -10l104 -104l223 225q93 94 225.5 94t226.5 -94zM512 64l576 576l-192 192l-576 -576v-192h192z" />
+<glyph unicode="&#xf1fc;" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" />
+<glyph unicode="&#xf1fd;" horiz-adv-x="1792" d="M1792 128v-384h-1792v384q45 0 85 14t59 27.5t47 37.5q30 27 51.5 38t56.5 11t55.5 -11t52.5 -38q29 -25 47 -38t58 -27t86 -14q45 0 85 14.5t58 27t48 37.5q21 19 32.5 27t31 15t43.5 7q35 0 56.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14t85 14t59 27.5t47 37.5 q30 27 51.5 38t56.5 11q34 0 55.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14zM1792 448v-192q-35 0 -55.5 11t-52.5 38q-29 25 -47 38t-58 27t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-22 -19 -33 -27t-31 -15t-44 -7q-35 0 -56.5 11t-51.5 38q-29 25 -47 38t-58 27 t-86 14q-45 0 -85 -14.5t-58 -27t-48 -37.5q-21 -19 -32.5 -27t-31 -15t-43.5 -7q-35 0 -56.5 11t-51.5 38q-28 24 -47 37.5t-59 27.5t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-30 -27 -51.5 -38t-56.5 -11v192q0 80 56 136t136 56h64v448h256v-448h256v448h256v-448h256v448 h256v-448h64q80 0 136 -56t56 -136zM512 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1024 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51 t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1536 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150z" />
+<glyph unicode="&#xf1fe;" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" />
+<glyph unicode="&#xf200;" horiz-adv-x="1792" d="M768 646l546 -546q-106 -108 -247.5 -168t-298.5 -60q-209 0 -385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103v-762zM955 640h773q0 -157 -60 -298.5t-168 -247.5zM1664 768h-768v768q209 0 385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf201;" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1920 1248v-435q0 -21 -19.5 -29.5t-35.5 7.5l-121 121l-633 -633q-10 -10 -23 -10t-23 10l-233 233l-416 -416l-192 192l585 585q10 10 23 10t23 -10l233 -233l464 464l-121 121q-16 16 -7.5 35.5t29.5 19.5h435q14 0 23 -9 t9 -23z" />
+<glyph unicode="&#xf202;" horiz-adv-x="1792" d="M1292 832q0 -6 10 -41q10 -29 25 -49.5t41 -34t44 -20t55 -16.5q325 -91 325 -332q0 -146 -105.5 -242.5t-254.5 -96.5q-59 0 -111.5 18.5t-91.5 45.5t-77 74.5t-63 87.5t-53.5 103.5t-43.5 103t-39.5 106.5t-35.5 95q-32 81 -61.5 133.5t-73.5 96.5t-104 64t-142 20 q-96 0 -183 -55.5t-138 -144.5t-51 -185q0 -160 106.5 -279.5t263.5 -119.5q177 0 258 95q56 63 83 116l84 -152q-15 -34 -44 -70l1 -1q-131 -152 -388 -152q-147 0 -269.5 79t-190.5 207.5t-68 274.5q0 105 43.5 206t116 176.5t172 121.5t204.5 46q87 0 159 -19t123.5 -50 t95 -80t72.5 -99t58.5 -117t50.5 -124.5t50 -130.5t55 -127q96 -200 233 -200q81 0 138.5 48.5t57.5 128.5q0 42 -19 72t-50.5 46t-72.5 31.5t-84.5 27t-87.5 34t-81 52t-65 82t-39 122.5q-3 16 -3 33q0 110 87.5 192t198.5 78q78 -3 120.5 -14.5t90.5 -53.5h-1 q12 -11 23 -24.5t26 -36t19 -27.5l-129 -99q-26 49 -54 70v1q-23 21 -97 21q-49 0 -84 -33t-35 -83z" />
+<glyph unicode="&#xf203;" d="M1432 484q0 173 -234 239q-35 10 -53 16.5t-38 25t-29 46.5q0 2 -2 8.5t-3 12t-1 7.5q0 36 24.5 59.5t60.5 23.5q54 0 71 -15h-1q20 -15 39 -51l93 71q-39 54 -49 64q-33 29 -67.5 39t-85.5 10q-80 0 -142 -57.5t-62 -137.5q0 -7 2 -23q16 -96 64.5 -140t148.5 -73 q29 -8 49 -15.5t45 -21.5t38.5 -34.5t13.5 -46.5v-5q1 -58 -40.5 -93t-100.5 -35q-97 0 -167 144q-23 47 -51.5 121.5t-48 125.5t-54 110.5t-74 95.5t-103.5 60.5t-147 24.5q-101 0 -192 -56t-144 -148t-50 -192v-1q4 -108 50.5 -199t133.5 -147.5t196 -56.5q186 0 279 110 q20 27 31 51l-60 109q-42 -80 -99 -116t-146 -36q-115 0 -191 87t-76 204q0 105 82 189t186 84q112 0 170 -53.5t104 -172.5q8 -21 25.5 -68.5t28.5 -76.5t31.5 -74.5t38.5 -74t45.5 -62.5t55.5 -53.5t66 -33t80 -13.5q107 0 183 69.5t76 174.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf204;" horiz-adv-x="2048" d="M1152 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1920 640q0 104 -40.5 198.5 t-109.5 163.5t-163.5 109.5t-198.5 40.5h-386q119 -90 188.5 -224t69.5 -288t-69.5 -288t-188.5 -224h386q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM2048 640q0 -130 -51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5 t-136.5 204t-51 248.5t51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5z" />
+<glyph unicode="&#xf205;" horiz-adv-x="2048" d="M0 640q0 130 51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5t-51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5t-136.5 204t-51 248.5zM1408 128q104 0 198.5 40.5t163.5 109.5 t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" />
+<glyph unicode="&#xf206;" horiz-adv-x="2304" d="M762 384h-314q-40 0 -57.5 35t6.5 67l188 251q-65 31 -137 31q-132 0 -226 -94t-94 -226t94 -226t226 -94q115 0 203 72.5t111 183.5zM576 512h186q-18 85 -75 148zM1056 512l288 384h-480l-99 -132q105 -103 126 -252h165zM2176 448q0 132 -94 226t-226 94 q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94t226 94t94 226zM2304 448q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 97 39.5 183.5t109.5 149.5l-65 98l-353 -469 q-18 -26 -51 -26h-197q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q114 0 215 -55l137 183h-224q-26 0 -45 19t-19 45t19 45t45 19h384v-128h435l-85 128h-222q-26 0 -45 19t-19 45t19 45t45 19h256q33 0 53 -28l267 -400 q91 44 192 44q185 0 316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf207;" d="M384 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1362 716l-72 384q-5 23 -22.5 37.5t-40.5 14.5 h-918q-23 0 -40.5 -14.5t-22.5 -37.5l-72 -384q-5 -30 14 -53t49 -23h1062q30 0 49 23t14 53zM1136 1328q0 20 -14 34t-34 14h-640q-20 0 -34 -14t-14 -34t14 -34t34 -14h640q20 0 34 14t14 34zM1536 603v-603h-128v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5v128h-768v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5v128h-128v603q0 112 25 223l103 454q9 78 97.5 137t230 89t312.5 30t312.5 -30t230 -89t97.5 -137l105 -454q23 -102 23 -223z" />
+<glyph unicode="&#xf208;" horiz-adv-x="2048" d="M1463 704q0 -35 -25 -60.5t-61 -25.5h-702q-36 0 -61 25.5t-25 60.5t25 60.5t61 25.5h702q36 0 61 -25.5t25 -60.5zM1677 704q0 86 -23 170h-982q-36 0 -61 25t-25 60q0 36 25 61t61 25h908q-88 143 -235 227t-320 84q-177 0 -327.5 -87.5t-238 -237.5t-87.5 -327 q0 -86 23 -170h982q36 0 61 -25t25 -60q0 -36 -25 -61t-61 -25h-908q88 -143 235.5 -227t320.5 -84q132 0 253 51.5t208 139t139 208t52 253.5zM2048 959q0 -35 -25 -60t-61 -25h-131q17 -85 17 -170q0 -167 -65.5 -319.5t-175.5 -263t-262.5 -176t-319.5 -65.5 q-246 0 -448.5 133t-301.5 350h-189q-36 0 -61 25t-25 61q0 35 25 60t61 25h132q-17 85 -17 170q0 167 65.5 319.5t175.5 263t262.5 176t320.5 65.5q245 0 447.5 -133t301.5 -350h188q36 0 61 -25t25 -61z" />
+<glyph unicode="&#xf209;" horiz-adv-x="1280" d="M953 1158l-114 -328l117 -21q165 451 165 518q0 56 -38 56q-57 0 -130 -225zM654 471l33 -88q37 42 71 67l-33 5.5t-38.5 7t-32.5 8.5zM362 1367q0 -98 159 -521q18 10 49 10q15 0 75 -5l-121 351q-75 220 -123 220q-19 0 -29 -17.5t-10 -37.5zM283 608q0 -36 51.5 -119 t117.5 -153t100 -70q14 0 25.5 13t11.5 27q0 24 -32 102q-13 32 -32 72t-47.5 89t-61.5 81t-62 32q-20 0 -45.5 -27t-25.5 -47zM125 273q0 -41 25 -104q59 -145 183.5 -227t281.5 -82q227 0 382 170q152 169 152 427q0 43 -1 67t-11.5 62t-30.5 56q-56 49 -211.5 75.5 t-270.5 26.5q-37 0 -49 -11q-12 -5 -12 -35q0 -34 21.5 -60t55.5 -40t77.5 -23.5t87.5 -11.5t85 -4t70 0h23q24 0 40 -19q15 -19 19 -55q-28 -28 -96 -54q-61 -22 -93 -46q-64 -46 -108.5 -114t-44.5 -137q0 -31 18.5 -88.5t18.5 -87.5l-3 -12q-4 -12 -4 -14 q-137 10 -146 216q-8 -2 -41 -2q2 -7 2 -21q0 -53 -40.5 -89.5t-94.5 -36.5q-82 0 -166.5 78t-84.5 159q0 34 33 67q52 -64 60 -76q77 -104 133 -104q12 0 26.5 8.5t14.5 20.5q0 34 -87.5 145t-116.5 111q-43 0 -70 -44.5t-27 -90.5zM11 264q0 101 42.5 163t136.5 88 q-28 74 -28 104q0 62 61 123t122 61q29 0 70 -15q-163 462 -163 567q0 80 41 130.5t119 50.5q131 0 325 -581q6 -17 8 -23q6 16 29 79.5t43.5 118.5t54 127.5t64.5 123t70.5 86.5t76.5 36q71 0 112 -49t41 -122q0 -108 -159 -550q61 -15 100.5 -46t58.5 -78t26 -93.5 t7 -110.5q0 -150 -47 -280t-132 -225t-211 -150t-278 -55q-111 0 -223 42q-149 57 -258 191.5t-109 286.5z" />
+<glyph unicode="&#xf20a;" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" />
+<glyph unicode="&#xf20b;" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf20c;" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" />
+<glyph unicode="&#xf20d;" d="M915 450h-294l147 551zM1001 128h311l-324 1024h-440l-324 -1024h311l383 314zM1536 1120v-960q0 -118 -85 -203t-203 -85h-960q-118 0 -203 85t-85 203v960q0 118 85 203t203 85h960q118 0 203 -85t85 -203z" />
+<glyph unicode="&#xf20e;" horiz-adv-x="2048" d="M2048 641q0 -21 -13 -36.5t-33 -19.5l-205 -356q3 -9 3 -18q0 -20 -12.5 -35.5t-32.5 -19.5l-193 -337q3 -8 3 -16q0 -23 -16.5 -40t-40.5 -17q-25 0 -41 18h-400q-17 -20 -43 -20t-43 20h-399q-17 -20 -43 -20q-23 0 -40 16.5t-17 40.5q0 8 4 20l-193 335 q-20 4 -32.5 19.5t-12.5 35.5q0 9 3 18l-206 356q-20 5 -32.5 20.5t-12.5 35.5q0 21 13.5 36.5t33.5 19.5l199 344q0 1 -0.5 3t-0.5 3q0 36 34 51l209 363q-4 10 -4 18q0 24 17 40.5t40 16.5q26 0 44 -21h396q16 21 43 21t43 -21h398q18 21 44 21q23 0 40 -16.5t17 -40.5 q0 -6 -4 -18l207 -358q23 -1 39 -17.5t16 -38.5q0 -13 -7 -27l187 -324q19 -4 31.5 -19.5t12.5 -35.5zM1063 -158h389l-342 354h-143l-342 -354h360q18 16 39 16t39 -16zM112 654q1 -4 1 -13q0 -10 -2 -15l208 -360q2 0 4.5 -1t5.5 -2.5l5 -2.5l188 199v347l-187 194 q-13 -8 -29 -10zM986 1438h-388l190 -200l554 200h-280q-16 -16 -38 -16t-38 16zM1689 226q1 6 5 11l-64 68l-17 -79h76zM1583 226l22 105l-252 266l-296 -307l63 -64h463zM1495 -142l16 28l65 310h-427l333 -343q8 4 13 5zM578 -158h5l342 354h-373v-335l4 -6q14 -5 22 -13 zM552 226h402l64 66l-309 321l-157 -166v-221zM359 226h163v189l-168 -177q4 -8 5 -12zM358 1051q0 -1 0.5 -2t0.5 -2q0 -16 -8 -29l171 -177v269zM552 1121v-311l153 -157l297 314l-223 236zM556 1425l-4 -8v-264l205 74l-191 201q-6 -2 -10 -3zM1447 1438h-16l-621 -224 l213 -225zM1023 946l-297 -315l311 -319l296 307zM688 634l-136 141v-284zM1038 270l-42 -44h85zM1374 618l238 -251l132 624l-3 5l-1 1zM1718 1018q-8 13 -8 29v2l-216 376q-5 1 -13 5l-437 -463l310 -327zM522 1142v223l-163 -282zM522 196h-163l163 -283v283zM1607 196 l-48 -227l130 227h-82zM1729 266l207 361q-2 10 -2 14q0 1 3 16l-171 296l-129 -612l77 -82q5 3 15 7z" />
+<glyph unicode="&#xf210;" d="M0 856q0 131 91.5 226.5t222.5 95.5h742l352 358v-1470q0 -132 -91.5 -227t-222.5 -95h-780q-131 0 -222.5 95t-91.5 227v790zM1232 102l-176 180v425q0 46 -32 79t-78 33h-484q-46 0 -78 -33t-32 -79v-492q0 -46 32.5 -79.5t77.5 -33.5h770z" />
+<glyph unicode="&#xf211;" d="M934 1386q-317 -121 -556 -362.5t-358 -560.5q-20 89 -20 176q0 208 102.5 384.5t278.5 279t384 102.5q82 0 169 -19zM1203 1267q93 -65 164 -155q-389 -113 -674.5 -400.5t-396.5 -676.5q-93 72 -155 162q112 386 395 671t667 399zM470 -67q115 356 379.5 622t619.5 384 q40 -92 54 -195q-292 -120 -516 -345t-343 -518q-103 14 -194 52zM1536 -125q-193 50 -367 115q-135 -84 -290 -107q109 205 274 370.5t369 275.5q-21 -152 -101 -284q65 -175 115 -370z" />
+<glyph unicode="&#xf212;" horiz-adv-x="2048" d="M1893 1144l155 -1272q-131 0 -257 57q-200 91 -393 91q-226 0 -374 -148q-148 148 -374 148q-193 0 -393 -91q-128 -57 -252 -57h-5l155 1272q224 127 482 127q233 0 387 -106q154 106 387 106q258 0 482 -127zM1398 157q129 0 232 -28.5t260 -93.5l-124 1021 q-171 78 -368 78q-224 0 -374 -141q-150 141 -374 141q-197 0 -368 -78l-124 -1021q105 43 165.5 65t148.5 39.5t178 17.5q202 0 374 -108q172 108 374 108zM1438 191l-55 907q-211 -4 -359 -155q-152 155 -374 155q-176 0 -336 -66l-114 -941q124 51 228.5 76t221.5 25 q209 0 374 -102q172 107 374 102z" />
+<glyph unicode="&#xf213;" horiz-adv-x="2048" d="M1500 165v733q0 21 -15 36t-35 15h-93q-20 0 -35 -15t-15 -36v-733q0 -20 15 -35t35 -15h93q20 0 35 15t15 35zM1216 165v531q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-531q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM924 165v429q0 20 -15 35t-35 15h-101 q-20 0 -35 -15t-15 -35v-429q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM632 165v362q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-362q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM2048 311q0 -166 -118 -284t-284 -118h-1244q-166 0 -284 118t-118 284 q0 116 63 214.5t168 148.5q-10 34 -10 73q0 113 80.5 193.5t193.5 80.5q102 0 180 -67q45 183 194 300t338 117q149 0 275 -73.5t199.5 -199.5t73.5 -275q0 -66 -14 -122q135 -33 221 -142.5t86 -247.5z" />
+<glyph unicode="&#xf214;" d="M0 1536h1536v-1392l-776 -338l-760 338v1392zM1436 209v926h-1336v-926l661 -294zM1436 1235v201h-1336v-201h1336zM181 937v-115h-37v115h37zM181 789v-115h-37v115h37zM181 641v-115h-37v115h37zM181 493v-115h-37v115h37zM181 345v-115h-37v115h37zM207 202l15 34 l105 -47l-15 -33zM343 142l15 34l105 -46l-15 -34zM478 82l15 34l105 -46l-15 -34zM614 23l15 33l104 -46l-15 -34zM797 10l105 46l15 -33l-105 -47zM932 70l105 46l15 -34l-105 -46zM1068 130l105 46l15 -34l-105 -46zM1203 189l105 47l15 -34l-105 -46zM259 1389v-36h-114 v36h114zM421 1389v-36h-115v36h115zM583 1389v-36h-115v36h115zM744 1389v-36h-114v36h114zM906 1389v-36h-114v36h114zM1068 1389v-36h-115v36h115zM1230 1389v-36h-115v36h115zM1391 1389v-36h-114v36h114zM181 1049v-79h-37v115h115v-36h-78zM421 1085v-36h-115v36h115z M583 1085v-36h-115v36h115zM744 1085v-36h-114v36h114zM906 1085v-36h-114v36h114zM1068 1085v-36h-115v36h115zM1230 1085v-36h-115v36h115zM1355 970v79h-78v36h115v-115h-37zM1355 822v115h37v-115h-37zM1355 674v115h37v-115h-37zM1355 526v115h37v-115h-37zM1355 378 v115h37v-115h-37zM1355 230v115h37v-115h-37zM760 265q-129 0 -221 91.5t-92 221.5q0 129 92 221t221 92q130 0 221.5 -92t91.5 -221q0 -130 -91.5 -221.5t-221.5 -91.5zM595 646q0 -36 19.5 -56.5t49.5 -25t64 -7t64 -2t49.5 -9t19.5 -30.5q0 -49 -112 -49q-97 0 -123 51 h-3l-31 -63q67 -42 162 -42q29 0 56.5 5t55.5 16t45.5 33t17.5 53q0 46 -27.5 69.5t-67.5 27t-79.5 3t-67 5t-27.5 25.5q0 21 20.5 33t40.5 15t41 3q34 0 70.5 -11t51.5 -34h3l30 58q-3 1 -21 8.5t-22.5 9t-19.5 7t-22 7t-20 4.5t-24 4t-23 1q-29 0 -56.5 -5t-54 -16.5 t-43 -34t-16.5 -53.5z" />
+<glyph unicode="&#xf215;" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" />
+<glyph unicode="&#xf216;" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" />
+<glyph unicode="&#xf217;" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf218;" horiz-adv-x="1664" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf219;" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" />
+<glyph unicode="&#xf21a;" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" />
+<glyph unicode="&#xf21b;" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" />
+<glyph unicode="&#xf21c;" horiz-adv-x="2304" d="M2301 500q12 -103 -22 -198.5t-99 -163.5t-158.5 -106t-196.5 -31q-161 11 -279.5 125t-134.5 274q-12 111 27.5 210.5t118.5 170.5l-71 107q-96 -80 -151 -194t-55 -244q0 -27 -18.5 -46.5t-45.5 -19.5h-256h-69q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5 t-131.5 316.5t131.5 316.5t316.5 131.5q76 0 152 -27l24 45q-123 110 -304 110h-64q-26 0 -45 19t-19 45t19 45t45 19h128q78 0 145 -13.5t116.5 -38.5t71.5 -39.5t51 -36.5h512h115l-85 128h-222q-30 0 -49 22.5t-14 52.5q4 23 23 38t43 15h253q33 0 53 -28l70 -105 l114 114q19 19 46 19h101q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-179l115 -172q131 63 275 36q143 -26 244 -134.5t118 -253.5zM448 128q115 0 203 72.5t111 183.5h-314q-35 0 -55 31q-18 32 -1 63l147 277q-47 13 -91 13q-132 0 -226 -94t-94 -226t94 -226 t226 -94zM1856 128q132 0 226 94t94 226t-94 226t-226 94q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94z" />
+<glyph unicode="&#xf21d;" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf21e;" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" />
+<glyph unicode="&#xf221;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" />
+<glyph unicode="&#xf222;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-382 -383q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5 q203 0 359 -126l382 382h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf223;" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf224;" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf225;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf226;" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" />
+<glyph unicode="&#xf227;" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" />
+<glyph unicode="&#xf228;" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" />
+<glyph unicode="&#xf229;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-213 -214l140 -140q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-140 141l-78 -79q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5 t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5q203 0 359 -126l78 78l-172 172q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l172 -172l213 213h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22a;" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22b;" horiz-adv-x="2048" d="M1901 621q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-132q-24 -217 -187.5 -364.5t-384.5 -147.5q-167 0 -306 87t-212 236t-54 319q15 133 88 245.5 t188 182t249 80.5q155 12 292 -52.5t224 -186t103 -271.5h132v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM576 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5 t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22c;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22d;" horiz-adv-x="1280" d="M1024 576q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1152 576q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123 t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5z" />
+<glyph unicode="&#xf22e;" horiz-adv-x="1792" />
+<glyph unicode="&#xf22f;" horiz-adv-x="1792" />
+<glyph unicode="&#xf230;" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" />
+<glyph unicode="&#xf231;" horiz-adv-x="1280" d="M0 939q0 108 37.5 203.5t103.5 166.5t152 123t185 78t202 26q158 0 294 -66.5t221 -193.5t85 -287q0 -96 -19 -188t-60 -177t-100 -149.5t-145 -103t-189 -38.5q-68 0 -135 32t-96 88q-10 -39 -28 -112.5t-23.5 -95t-20.5 -71t-26 -71t-32 -62.5t-46 -77.5t-62 -86.5 l-14 -5l-9 10q-15 157 -15 188q0 92 21.5 206.5t66.5 287.5t52 203q-32 65 -32 169q0 83 52 156t132 73q61 0 95 -40.5t34 -102.5q0 -66 -44 -191t-44 -187q0 -63 45 -104.5t109 -41.5q55 0 102 25t78.5 68t56 95t38 110.5t20 111t6.5 99.5q0 173 -109.5 269.5t-285.5 96.5 q-200 0 -334 -129.5t-134 -328.5q0 -44 12.5 -85t27 -65t27 -45.5t12.5 -30.5q0 -28 -15 -73t-37 -45q-2 0 -17 3q-51 15 -90.5 56t-61 94.5t-32.5 108t-11 106.5z" />
+<glyph unicode="&#xf232;" d="M985 562q13 0 97.5 -44t89.5 -53q2 -5 2 -15q0 -33 -17 -76q-16 -39 -71 -65.5t-102 -26.5q-57 0 -190 62q-98 45 -170 118t-148 185q-72 107 -71 194v8q3 91 74 158q24 22 52 22q6 0 18 -1.5t19 -1.5q19 0 26.5 -6.5t15.5 -27.5q8 -20 33 -88t25 -75q0 -21 -34.5 -57.5 t-34.5 -46.5q0 -7 5 -15q34 -73 102 -137q56 -53 151 -101q12 -7 22 -7q15 0 54 48.5t52 48.5zM782 32q127 0 243.5 50t200.5 134t134 200.5t50 243.5t-50 243.5t-134 200.5t-200.5 134t-243.5 50t-243.5 -50t-200.5 -134t-134 -200.5t-50 -243.5q0 -203 120 -368l-79 -233 l242 77q158 -104 345 -104zM782 1414q153 0 292.5 -60t240.5 -161t161 -240.5t60 -292.5t-60 -292.5t-161 -240.5t-240.5 -161t-292.5 -60q-195 0 -365 94l-417 -134l136 405q-108 178 -108 389q0 153 60 292.5t161 240.5t240.5 161t292.5 60z" />
+<glyph unicode="&#xf233;" horiz-adv-x="1792" d="M128 128h1024v128h-1024v-128zM128 640h1024v128h-1024v-128zM1696 192q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM128 1152h1024v128h-1024v-128zM1696 704q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1696 1216 q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1792 384v-384h-1792v384h1792zM1792 896v-384h-1792v384h1792zM1792 1408v-384h-1792v384h1792z" />
+<glyph unicode="&#xf234;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1664 512h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5v352h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352zM928 288q0 -52 38 -90t90 -38h256v-238q-68 -50 -171 -50h-874q-121 0 -194 69t-73 190q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q79 -61 154.5 -91.5t164.5 -30.5t164.5 30.5t154.5 91.5q20 17 39 17q132 0 217 -96h-223q-52 0 -90 -38t-38 -90v-192z" />
+<glyph unicode="&#xf235;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1781 320l249 -249q9 -9 9 -23q0 -13 -9 -22l-136 -136q-9 -9 -22 -9q-14 0 -23 9l-249 249l-249 -249q-9 -9 -23 -9q-13 0 -22 9l-136 136 q-9 9 -9 22q0 14 9 23l249 249l-249 249q-9 9 -9 23q0 13 9 22l136 136q9 9 22 9q14 0 23 -9l249 -249l249 249q9 9 23 9q13 0 22 -9l136 -136q9 -9 9 -22q0 -14 -9 -23zM1283 320l-181 -181q-37 -37 -37 -91q0 -53 37 -90l83 -83q-21 -3 -44 -3h-874q-121 0 -194 69 t-73 190q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q154 -122 319 -122t319 122q20 17 39 17q28 0 57 -6q-28 -27 -41 -50t-13 -56q0 -54 37 -91z" />
+<glyph unicode="&#xf236;" horiz-adv-x="2048" d="M256 512h1728q26 0 45 -19t19 -45v-448h-256v256h-1536v-256h-256v1216q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-704zM832 832q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM2048 576v64q0 159 -112.5 271.5t-271.5 112.5h-704 q-26 0 -45 -19t-19 -45v-384h1152z" />
+<glyph unicode="&#xf237;" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" />
+<glyph unicode="&#xf238;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" />
+<glyph unicode="&#xf239;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" />
+<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M597 1115v-1173q0 -25 -12.5 -42.5t-36.5 -17.5q-17 0 -33 8l-465 233q-21 10 -35.5 33.5t-14.5 46.5v1140q0 20 10 34t29 14q14 0 44 -15l511 -256q3 -3 3 -5zM661 1014l534 -866l-534 266v600zM1792 996v-1054q0 -25 -14 -40.5t-38 -15.5t-47 13l-441 220zM1789 1116 q0 -3 -256.5 -419.5t-300.5 -487.5l-390 634l324 527q17 28 52 28q14 0 26 -6l541 -270q4 -2 4 -6z" />
+<glyph unicode="&#xf23b;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
+<glyph unicode="&#xf23c;" horiz-adv-x="2296" d="M478 -139q-8 -16 -27 -34.5t-37 -25.5q-25 -9 -51.5 3.5t-28.5 31.5q-1 22 40 55t68 38q23 4 34 -21.5t2 -46.5zM1819 -139q7 -16 26 -34.5t38 -25.5q25 -9 51.5 3.5t27.5 31.5q2 22 -39.5 55t-68.5 38q-22 4 -33 -21.5t-2 -46.5zM1867 -30q13 -27 56.5 -59.5t77.5 -41.5 q45 -13 82 4.5t37 50.5q0 46 -67.5 100.5t-115.5 59.5q-40 5 -63.5 -37.5t-6.5 -76.5zM428 -30q-13 -27 -56 -59.5t-77 -41.5q-45 -13 -82 4.5t-37 50.5q0 46 67.5 100.5t115.5 59.5q40 5 63 -37.5t6 -76.5zM1158 1094h1q-41 0 -76 -15q27 -8 44 -30.5t17 -49.5 q0 -35 -27 -60t-65 -25q-52 0 -80 43q-5 -23 -5 -42q0 -74 56 -126.5t135 -52.5q80 0 136 52.5t56 126.5t-56 126.5t-136 52.5zM1462 1312q-99 109 -220.5 131.5t-245.5 -44.5q27 60 82.5 96.5t118 39.5t121.5 -17t99.5 -74.5t44.5 -131.5zM2212 73q8 -11 -11 -42 q7 -23 7 -40q1 -56 -44.5 -112.5t-109.5 -91.5t-118 -37q-48 -2 -92 21.5t-66 65.5q-687 -25 -1259 0q-23 -41 -66.5 -65t-92.5 -22q-86 3 -179.5 80.5t-92.5 160.5q2 22 7 40q-19 31 -11 42q6 10 31 1q14 22 41 51q-7 29 2 38q11 10 39 -4q29 20 59 34q0 29 13 37 q23 12 51 -16q35 5 61 -2q18 -4 38 -19v73q-11 0 -18 2q-53 10 -97 44.5t-55 87.5q-9 38 0 81q15 62 93 95q2 17 19 35.5t36 23.5t33 -7.5t19 -30.5h13q46 -5 60 -23q3 -3 5 -7q10 1 30.5 3.5t30.5 3.5q-15 11 -30 17q-23 40 -91 43q0 6 1 10q-62 2 -118.5 18.5t-84.5 47.5 q-32 36 -42.5 92t-2.5 112q16 126 90 179q23 16 52 4.5t32 -40.5q0 -1 1.5 -14t2.5 -21t3 -20t5.5 -19t8.5 -10q27 -14 76 -12q48 46 98 74q-40 4 -162 -14l47 46q61 58 163 111q145 73 282 86q-20 8 -41 15.5t-47 14t-42.5 10.5t-47.5 11t-43 10q595 126 904 -139 q98 -84 158 -222q85 -10 121 9h1q5 3 8.5 10t5.5 19t3 19.5t3 21.5l1 14q3 28 32 40t52 -5q73 -52 91 -178q7 -57 -3.5 -113t-42.5 -91q-28 -32 -83.5 -48.5t-115.5 -18.5v-10q-71 -2 -95 -43q-14 -5 -31 -17q11 -1 32 -3.5t30 -3.5q1 4 5 8q16 18 60 23h13q5 18 19 30t33 8 t36 -23t19 -36q79 -32 93 -95q9 -40 1 -81q-12 -53 -56 -88t-97 -44q-10 -2 -17 -2q0 -49 -1 -73q20 15 38 19q26 7 61 2q28 28 51 16q14 -9 14 -37q33 -16 59 -34q27 13 38 4q10 -10 2 -38q28 -30 41 -51q23 8 31 -1zM1937 1025q0 -29 -9 -54q82 -32 112 -132 q4 37 -9.5 98.5t-41.5 90.5q-20 19 -36 17t-16 -20zM1859 925q35 -42 47.5 -108.5t-0.5 -124.5q67 13 97 45q13 14 18 28q-3 64 -31 114.5t-79 66.5q-15 -15 -52 -21zM1822 921q-30 0 -44 1q42 -115 53 -239q21 0 43 3q16 68 1 135t-53 100zM258 839q30 100 112 132 q-9 25 -9 54q0 18 -16.5 20t-35.5 -17q-28 -29 -41.5 -90.5t-9.5 -98.5zM294 737q29 -31 97 -45q-13 58 -0.5 124.5t47.5 108.5v0q-37 6 -52 21q-51 -16 -78.5 -66t-31.5 -115q9 -17 18 -28zM471 683q14 124 73 235q-19 -4 -55 -18l-45 -19v1q-46 -89 -20 -196q25 -3 47 -3z M1434 644q8 -38 16.5 -108.5t11.5 -89.5q3 -18 9.5 -21.5t23.5 4.5q40 20 62 85.5t23 125.5q-24 2 -146 4zM1152 1285q-116 0 -199 -82.5t-83 -198.5q0 -117 83 -199.5t199 -82.5t199 82.5t83 199.5q0 116 -83 198.5t-199 82.5zM1380 646q-106 2 -211 0v1q-1 -27 2.5 -86 t13.5 -66q29 -14 93.5 -14.5t95.5 10.5q9 3 11 39t-0.5 69.5t-4.5 46.5zM1112 447q8 4 9.5 48t-0.5 88t-4 63v1q-212 -3 -214 -3q-4 -20 -7 -62t0 -83t14 -46q34 -15 101 -16t101 10zM718 636q-16 -59 4.5 -118.5t77.5 -84.5q15 -8 24 -5t12 21q3 16 8 90t10 103 q-69 -2 -136 -6zM591 510q3 -23 -34 -36q132 -141 271.5 -240t305.5 -154q172 49 310.5 146t293.5 250q-33 13 -30 34l3 9v1v-1q-17 2 -50 5.5t-48 4.5q-26 -90 -82 -132q-51 -38 -82 1q-5 6 -9 14q-7 13 -17 62q-2 -5 -5 -9t-7.5 -7t-8 -5.5t-9.5 -4l-10 -2.5t-12 -2 l-12 -1.5t-13.5 -1t-13.5 -0.5q-106 -9 -163 11q-4 -17 -10 -26.5t-21 -15t-23 -7t-36 -3.5q-2 0 -3 -0.5t-3 -0.5h-3q-179 -17 -203 40q-2 -63 -56 -54q-47 8 -91 54q-12 13 -20 26q-17 29 -26 65q-58 -6 -87 -10q1 -2 4 -10zM507 -118q3 14 3 30q-17 71 -51 130t-73 70 q-41 12 -101.5 -14.5t-104.5 -80t-39 -107.5q35 -53 100 -93t119 -42q51 -2 94 28t53 79zM510 53q23 -63 27 -119q195 113 392 174q-98 52 -180.5 120t-179.5 165q-6 -4 -29 -13q0 -2 -1 -5t-1 -4q31 -18 22 -37q-12 -23 -56 -34q-10 -13 -29 -24h-1q-2 -83 1 -150 q19 -34 35 -73zM579 -113q532 -21 1145 0q-254 147 -428 196q-76 -35 -156 -57q-8 -3 -16 0q-65 21 -129 49q-208 -60 -416 -188h-1v-1q1 0 1 1zM1763 -67q4 54 28 120q14 38 33 71l-1 -1q3 77 3 153q-15 8 -30 25q-42 9 -56 33q-9 20 22 38q-2 4 -2 9q-16 4 -28 12 q-204 -190 -383 -284q198 -59 414 -176zM2155 -90q5 54 -39 107.5t-104 80t-102 14.5q-38 -11 -72.5 -70.5t-51.5 -129.5q0 -16 3 -30q10 -49 53 -79t94 -28q54 2 119 42t100 93z" />
+<glyph unicode="&#xf23d;" horiz-adv-x="2304" d="M1524 -25q0 -68 -48 -116t-116 -48t-116.5 48t-48.5 116t48.5 116.5t116.5 48.5t116 -48.5t48 -116.5zM775 -25q0 -68 -48.5 -116t-116.5 -48t-116 48t-48 116t48 116.5t116 48.5t116.5 -48.5t48.5 -116.5zM0 1469q57 -60 110.5 -104.5t121 -82t136 -63t166 -45.5 t200 -31.5t250 -18.5t304 -9.5t372.5 -2.5q139 0 244.5 -5t181 -16.5t124 -27.5t71 -39.5t24 -51.5t-19.5 -64t-56.5 -76.5t-89.5 -91t-116 -104.5t-139 -119q-185 -157 -286 -247q29 51 76.5 109t94 105.5t94.5 98.5t83 91.5t54 80.5t13 70t-45.5 55.5t-116.5 41t-204 23.5 t-304 5q-168 -2 -314 6t-256 23t-204.5 41t-159.5 51.5t-122.5 62.5t-91.5 66.5t-68 71.5t-50.5 69.5t-40 68t-36.5 59.5z" />
+<glyph unicode="&#xf23e;" horiz-adv-x="1792" d="M896 1472q-169 0 -323 -66t-265.5 -177.5t-177.5 -265.5t-66 -323t66 -323t177.5 -265.5t265.5 -177.5t323 -66t323 66t265.5 177.5t177.5 265.5t66 323t-66 323t-177.5 265.5t-265.5 177.5t-323 66zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348 t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM496 704q16 0 16 -16v-480q0 -16 -16 -16h-32q-16 0 -16 16v480q0 16 16 16h32zM896 640q53 0 90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-114q0 -14 -9 -23 t-23 -9h-64q-14 0 -23 9t-9 23v114q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5zM896 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM544 928v-96 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5v-96q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 146 -103 249t-249 103t-249 -103t-103 -249zM1408 192v512q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-512 q0 -26 19 -45t45 -19h896q26 0 45 19t19 45z" />
+<glyph unicode="&#xf240;" horiz-adv-x="2304" d="M1920 1024v-768h-1664v768h1664zM2048 448h128v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288zM2304 832v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113 v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf241;" horiz-adv-x="2304" d="M256 256v768h1280v-768h-1280zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf242;" horiz-adv-x="2304" d="M256 256v768h896v-768h-896zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf243;" horiz-adv-x="2304" d="M256 256v768h512v-768h-512zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf244;" horiz-adv-x="2304" d="M2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23 v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf245;" horiz-adv-x="1280" d="M1133 493q31 -30 14 -69q-17 -40 -59 -40h-382l201 -476q10 -25 0 -49t-34 -35l-177 -75q-25 -10 -49 0t-35 34l-191 452l-312 -312q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v1504q0 42 40 59q12 5 24 5q27 0 45 -19z" />
+<glyph unicode="&#xf246;" horiz-adv-x="1024" d="M832 1408q-320 0 -320 -224v-416h128v-128h-128v-544q0 -224 320 -224h64v-128h-64q-272 0 -384 146q-112 -146 -384 -146h-64v128h64q320 0 320 224v544h-128v128h128v416q0 224 -320 224h-64v128h64q272 0 384 -146q112 146 384 146h64v-128h-64z" />
+<glyph unicode="&#xf247;" horiz-adv-x="2048" d="M2048 1152h-128v-1024h128v-384h-384v128h-1280v-128h-384v384h128v1024h-128v384h384v-128h1280v128h384v-384zM1792 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 -128v128h-128v-128h128zM1664 0v128h128v1024h-128v128h-1280v-128h-128v-1024h128v-128 h1280zM1920 -128v128h-128v-128h128zM1280 896h384v-768h-896v256h-384v768h896v-256zM512 512h640v512h-640v-512zM1536 256v512h-256v-384h-384v-128h640z" />
+<glyph unicode="&#xf248;" horiz-adv-x="2304" d="M2304 768h-128v-640h128v-384h-384v128h-896v-128h-384v384h128v128h-384v-128h-384v384h128v640h-128v384h384v-128h896v128h384v-384h-128v-128h384v128h384v-384zM2048 1024v-128h128v128h-128zM1408 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 256 v128h-128v-128h128zM1536 384h-128v-128h128v128zM384 384h896v128h128v640h-128v128h-896v-128h-128v-640h128v-128zM896 -128v128h-128v-128h128zM2176 -128v128h-128v-128h128zM2048 128v640h-128v128h-384v-384h128v-384h-384v128h-384v-128h128v-128h896v128h128z" />
+<glyph unicode="&#xf249;" d="M1024 288v-416h-928q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68v-928h-416q-40 0 -68 -28t-28 -68zM1152 256h381q-15 -82 -65 -132l-184 -184q-50 -50 -132 -65v381z" />
+<glyph unicode="&#xf24a;" d="M1400 256h-248v-248q29 10 41 22l185 185q12 12 22 41zM1120 384h288v896h-1280v-1280h896v288q0 40 28 68t68 28zM1536 1312v-1024q0 -40 -20 -88t-48 -76l-184 -184q-28 -28 -76 -48t-88 -20h-1024q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf24b;" horiz-adv-x="2304" d="M1951 538q0 -26 -15.5 -44.5t-38.5 -23.5q-8 -2 -18 -2h-153v140h153q10 0 18 -2q23 -5 38.5 -23.5t15.5 -44.5zM1933 751q0 -25 -15 -42t-38 -21q-3 -1 -15 -1h-139v129h139q3 0 8.5 -0.5t6.5 -0.5q23 -4 38 -21.5t15 -42.5zM728 587v308h-228v-308q0 -58 -38 -94.5 t-105 -36.5q-108 0 -229 59v-112q53 -15 121 -23t109 -9l42 -1q328 0 328 217zM1442 403v113q-99 -52 -200 -59q-108 -8 -169 41t-61 142t61 142t169 41q101 -7 200 -58v112q-48 12 -100 19.5t-80 9.5l-28 2q-127 6 -218.5 -14t-140.5 -60t-71 -88t-22 -106t22 -106t71 -88 t140.5 -60t218.5 -14q101 4 208 31zM2176 518q0 54 -43 88.5t-109 39.5v3q57 8 89 41.5t32 79.5q0 55 -41 88t-107 36q-3 0 -12 0.5t-14 0.5h-455v-510h491q74 0 121.5 36.5t47.5 96.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90 t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf24c;" horiz-adv-x="2304" d="M858 295v693q-106 -41 -172 -135.5t-66 -211.5t66 -211.5t172 -134.5zM1362 641q0 117 -66 211.5t-172 135.5v-694q106 41 172 135.5t66 211.5zM1577 641q0 -159 -78.5 -294t-213.5 -213.5t-294 -78.5q-119 0 -227.5 46.5t-187 125t-125 187t-46.5 227.5q0 159 78.5 294 t213.5 213.5t294 78.5t294 -78.5t213.5 -213.5t78.5 -294zM1960 634q0 139 -55.5 261.5t-147.5 205.5t-213.5 131t-252.5 48h-301q-176 0 -323.5 -81t-235 -230t-87.5 -335q0 -171 87 -317.5t236 -231.5t323 -85h301q129 0 251.5 50.5t214.5 135t147.5 202.5t55.5 246z M2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf24d;" horiz-adv-x="1792" d="M1664 -96v1088q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5zM1792 992v-1088q0 -66 -47 -113t-113 -47h-1088q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113 zM1408 1376v-160h-128v160q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h160v-128h-160q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf24e;" horiz-adv-x="2304" d="M1728 1088l-384 -704h768zM448 1088l-384 -704h768zM1269 1280q-14 -40 -45.5 -71.5t-71.5 -45.5v-1291h608q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1344q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h608v1291q-40 14 -71.5 45.5t-45.5 71.5h-491q-14 0 -23 9t-9 23v64 q0 14 9 23t23 9h491q21 57 70 92.5t111 35.5t111 -35.5t70 -92.5h491q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-491zM1088 1264q33 0 56.5 23.5t23.5 56.5t-23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5zM2176 384q0 -73 -46.5 -131t-117.5 -91 t-144.5 -49.5t-139.5 -16.5t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81zM896 384q0 -73 -46.5 -131t-117.5 -91t-144.5 -49.5t-139.5 -16.5 t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81z" />
+<glyph unicode="&#xf250;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-77 -29 -149 -92.5 t-129.5 -152.5t-92.5 -210t-35 -253h1024q0 132 -35 253t-92.5 210t-129.5 152.5t-149 92.5q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
+<glyph unicode="&#xf251;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -66 9 -128h1006q9 61 9 128zM1280 -128q0 130 -34 249.5t-90.5 208t-126.5 152t-146 94.5h-230q-76 -31 -146 -94.5t-126.5 -152t-90.5 -208t-34 -249.5h1024z" />
+<glyph unicode="&#xf252;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -206 85 -384h854q85 178 85 384zM1223 192q-54 141 -145.5 241.5t-194.5 142.5h-230q-103 -42 -194.5 -142.5t-145.5 -241.5h910z" />
+<glyph unicode="&#xf253;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-137 -51 -244 -196 h700q-107 145 -244 196q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
+<glyph unicode="&#xf254;" d="M1504 -64q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472zM130 0q3 55 16 107t30 95t46 87t53.5 76t64.5 69.5t66 60t70.5 55t66.5 47.5t65 43q-43 28 -65 43t-66.5 47.5t-70.5 55t-66 60t-64.5 69.5t-53.5 76t-46 87 t-30 95t-16 107h1276q-3 -55 -16 -107t-30 -95t-46 -87t-53.5 -76t-64.5 -69.5t-66 -60t-70.5 -55t-66.5 -47.5t-65 -43q43 -28 65 -43t66.5 -47.5t70.5 -55t66 -60t64.5 -69.5t53.5 -76t46 -87t30 -95t16 -107h-1276zM1504 1536q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9 h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472z" />
+<glyph unicode="&#xf255;" d="M768 1152q-53 0 -90.5 -37.5t-37.5 -90.5v-128h-32v93q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-429l-32 30v172q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-224q0 -47 35 -82l310 -296q39 -39 39 -102q0 -26 19 -45t45 -19h640q26 0 45 19t19 45v25 q0 41 10 77l108 436q10 36 10 77v246q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-32h-32v125q0 40 -25 72.5t-64 40.5q-14 2 -23 2q-46 0 -79 -33t-33 -79v-128h-32v122q0 51 -32.5 89.5t-82.5 43.5q-5 1 -13 1zM768 1280q84 0 149 -50q57 34 123 34q59 0 111 -27 t86 -76q27 7 59 7q100 0 170 -71.5t70 -171.5v-246q0 -51 -13 -108l-109 -436q-6 -24 -6 -71q0 -80 -56 -136t-136 -56h-640q-84 0 -138 58.5t-54 142.5l-308 296q-76 73 -76 175v224q0 99 70.5 169.5t169.5 70.5q11 0 16 -1q6 95 75.5 160t164.5 65q52 0 98 -21 q72 69 174 69z" />
+<glyph unicode="&#xf256;" horiz-adv-x="1792" d="M880 1408q-46 0 -79 -33t-33 -79v-656h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528v-256l-154 205q-38 51 -102 51q-53 0 -90.5 -37.5t-37.5 -90.5q0 -43 26 -77l384 -512q38 -51 102 -51h688q34 0 61 22t34 56l76 405q5 32 5 59v498q0 46 -33 79t-79 33t-79 -33 t-33 -79v-272h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528h-32v656q0 46 -33 79t-79 33zM880 1536q68 0 125.5 -35.5t88.5 -96.5q19 4 42 4q99 0 169.5 -70.5t70.5 -169.5v-17q105 6 180.5 -64t75.5 -175v-498q0 -40 -8 -83l-76 -404q-14 -79 -76.5 -131t-143.5 -52 h-688q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 106 75 181t181 75q78 0 128 -34v434q0 99 70.5 169.5t169.5 70.5q23 0 42 -4q31 61 88.5 96.5t125.5 35.5z" />
+<glyph unicode="&#xf257;" horiz-adv-x="1792" d="M1073 -128h-177q-163 0 -226 141q-23 49 -23 102v5q-62 30 -98.5 88.5t-36.5 127.5q0 38 5 48h-261q-106 0 -181 75t-75 181t75 181t181 75h113l-44 17q-74 28 -119.5 93.5t-45.5 145.5q0 106 75 181t181 75q46 0 91 -17l628 -239h401q106 0 181 -75t75 -181v-668 q0 -88 -54 -157.5t-140 -90.5l-339 -85q-92 -23 -186 -23zM1024 583l-155 -71l-163 -74q-30 -14 -48 -41.5t-18 -60.5q0 -46 33 -79t79 -33q26 0 46 10l338 154q-49 10 -80.5 50t-31.5 90v55zM1344 272q0 46 -33 79t-79 33q-26 0 -46 -10l-290 -132q-28 -13 -37 -17 t-30.5 -17t-29.5 -23.5t-16 -29t-8 -40.5q0 -50 31.5 -82t81.5 -32q20 0 38 9l352 160q30 14 48 41.5t18 60.5zM1112 1024l-650 248q-24 8 -46 8q-53 0 -90.5 -37.5t-37.5 -90.5q0 -40 22.5 -73t59.5 -47l526 -200v-64h-640q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5 t90.5 -37.5h535l233 106v198q0 63 46 106l111 102h-69zM1073 0q82 0 155 19l339 85q43 11 70 45.5t27 78.5v668q0 53 -37.5 90.5t-90.5 37.5h-308l-136 -126q-36 -33 -36 -82v-296q0 -46 33 -77t79 -31t79 35t33 81v208h32v-208q0 -70 -57 -114q52 -8 86.5 -48.5t34.5 -93.5 q0 -42 -23 -78t-61 -53l-310 -141h91z" />
+<glyph unicode="&#xf258;" horiz-adv-x="2048" d="M1151 1536q61 0 116 -28t91 -77l572 -781q118 -159 118 -359v-355q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v177l-286 143h-546q-80 0 -136 56t-56 136v32q0 119 84.5 203.5t203.5 84.5h420l42 128h-686q-100 0 -173.5 67.5t-81.5 166.5q-65 79 -65 182v32 q0 80 56 136t136 56h959zM1920 -64v355q0 157 -93 284l-573 781q-39 52 -103 52h-959q-26 0 -45 -19t-19 -45q0 -32 1.5 -49.5t9.5 -40.5t25 -43q10 31 35.5 50t56.5 19h832v-32h-832q-26 0 -45 -19t-19 -45q0 -44 3 -58q8 -44 44 -73t81 -29h640h91q40 0 68 -28t28 -68 q0 -15 -5 -30l-64 -192q-10 -29 -35 -47.5t-56 -18.5h-443q-66 0 -113 -47t-47 -113v-32q0 -26 19 -45t45 -19h561q16 0 29 -7l317 -158q24 -13 38.5 -36t14.5 -50v-197q0 -26 19 -45t45 -19h384q26 0 45 19t19 45z" />
+<glyph unicode="&#xf259;" horiz-adv-x="2048" d="M816 1408q-48 0 -79.5 -34t-31.5 -82q0 -14 3 -28l150 -624h-26l-116 482q-9 38 -39.5 62t-69.5 24q-47 0 -79 -34t-32 -81q0 -11 4 -29q3 -13 39 -161t68 -282t32 -138v-227l-307 230q-34 26 -77 26q-52 0 -89.5 -36.5t-37.5 -88.5q0 -67 56 -110l507 -379 q34 -26 76 -26h694q33 0 59 20.5t34 52.5l100 401q8 30 10 88t9 86l116 478q3 12 3 26q0 46 -33 79t-80 33q-38 0 -69 -25.5t-40 -62.5l-99 -408h-26l132 547q3 14 3 28q0 47 -32 80t-80 33q-38 0 -68.5 -24t-39.5 -62l-145 -602h-127l-164 682q-9 38 -39.5 62t-68.5 24z M1461 -256h-694q-85 0 -153 51l-507 380q-50 38 -78.5 94t-28.5 118q0 105 75 179t180 74q25 0 49.5 -5.5t41.5 -11t41 -20.5t35 -23t38.5 -29.5t37.5 -28.5l-123 512q-7 35 -7 59q0 93 60 162t152 79q14 87 80.5 144.5t155.5 57.5q83 0 148 -51.5t85 -132.5l103 -428 l83 348q20 81 85 132.5t148 51.5q87 0 152.5 -54t82.5 -139q93 -10 155 -78t62 -161q0 -30 -7 -57l-116 -477q-5 -22 -5 -67q0 -51 -13 -108l-101 -401q-19 -75 -79.5 -122.5t-137.5 -47.5z" />
+<glyph unicode="&#xf25a;" horiz-adv-x="1792" d="M640 1408q-53 0 -90.5 -37.5t-37.5 -90.5v-512v-384l-151 202q-41 54 -107 54q-52 0 -89 -38t-37 -90q0 -43 26 -77l384 -512q38 -51 102 -51h718q22 0 39.5 13.5t22.5 34.5l92 368q24 96 24 194v217q0 41 -28 71t-68 30t-68 -28t-28 -68h-32v61q0 48 -32 81.5t-80 33.5 q-46 0 -79 -33t-33 -79v-64h-32v90q0 55 -37 94.5t-91 39.5q-53 0 -90.5 -37.5t-37.5 -90.5v-96h-32v570q0 55 -37 94.5t-91 39.5zM640 1536q107 0 181.5 -77.5t74.5 -184.5v-220q22 2 32 2q99 0 173 -69q47 21 99 21q113 0 184 -87q27 7 56 7q94 0 159 -67.5t65 -161.5 v-217q0 -116 -28 -225l-92 -368q-16 -64 -68 -104.5t-118 -40.5h-718q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 105 74.5 180.5t179.5 75.5q71 0 130 -35v547q0 106 75 181t181 75zM768 128v384h-32v-384h32zM1024 128v384h-32v-384h32zM1280 128v384h-32 v-384h32z" />
+<glyph unicode="&#xf25b;" d="M1288 889q60 0 107 -23q141 -63 141 -226v-177q0 -94 -23 -186l-85 -339q-21 -86 -90.5 -140t-157.5 -54h-668q-106 0 -181 75t-75 181v401l-239 628q-17 45 -17 91q0 106 75 181t181 75q80 0 145.5 -45.5t93.5 -119.5l17 -44v113q0 106 75 181t181 75t181 -75t75 -181 v-261q27 5 48 5q69 0 127.5 -36.5t88.5 -98.5zM1072 896q-33 0 -60.5 -18t-41.5 -48l-74 -163l-71 -155h55q50 0 90 -31.5t50 -80.5l154 338q10 20 10 46q0 46 -33 79t-79 33zM1293 761q-22 0 -40.5 -8t-29 -16t-23.5 -29.5t-17 -30.5t-17 -37l-132 -290q-10 -20 -10 -46 q0 -46 33 -79t79 -33q33 0 60.5 18t41.5 48l160 352q9 18 9 38q0 50 -32 81.5t-82 31.5zM128 1120q0 -22 8 -46l248 -650v-69l102 111q43 46 106 46h198l106 233v535q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5v-640h-64l-200 526q-14 37 -47 59.5t-73 22.5 q-53 0 -90.5 -37.5t-37.5 -90.5zM1180 -128q44 0 78.5 27t45.5 70l85 339q19 73 19 155v91l-141 -310q-17 -38 -53 -61t-78 -23q-53 0 -93.5 34.5t-48.5 86.5q-44 -57 -114 -57h-208v32h208q46 0 81 33t35 79t-31 79t-77 33h-296q-49 0 -82 -36l-126 -136v-308 q0 -53 37.5 -90.5t90.5 -37.5h668z" />
+<glyph unicode="&#xf25c;" horiz-adv-x="1973" d="M857 992v-117q0 -13 -9.5 -22t-22.5 -9h-298v-812q0 -13 -9 -22.5t-22 -9.5h-135q-13 0 -22.5 9t-9.5 23v812h-297q-13 0 -22.5 9t-9.5 22v117q0 14 9 23t23 9h793q13 0 22.5 -9.5t9.5 -22.5zM1895 995l77 -961q1 -13 -8 -24q-10 -10 -23 -10h-134q-12 0 -21 8.5 t-10 20.5l-46 588l-189 -425q-8 -19 -29 -19h-120q-20 0 -29 19l-188 427l-45 -590q-1 -12 -10 -20.5t-21 -8.5h-135q-13 0 -23 10q-9 10 -9 24l78 961q1 12 10 20.5t21 8.5h142q20 0 29 -19l220 -520q10 -24 20 -51q3 7 9.5 24.5t10.5 26.5l221 520q9 19 29 19h141 q13 0 22 -8.5t10 -20.5z" />
+<glyph unicode="&#xf25d;" horiz-adv-x="1792" d="M1042 833q0 88 -60 121q-33 18 -117 18h-123v-281h162q66 0 102 37t36 105zM1094 548l205 -373q8 -17 -1 -31q-8 -16 -27 -16h-152q-20 0 -28 17l-194 365h-155v-350q0 -14 -9 -23t-23 -9h-134q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h294q128 0 190 -24q85 -31 134 -109 t49 -180q0 -92 -42.5 -165.5t-115.5 -109.5q6 -10 9 -16zM896 1376q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM1792 640 q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf25e;" horiz-adv-x="1792" d="M605 303q153 0 257 104q14 18 3 36l-45 82q-6 13 -24 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78 q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-148 0 -246 -96.5t-98 -240.5q0 -146 97 -241.5t247 -95.5zM1235 303q153 0 257 104q14 18 4 36l-45 82q-8 14 -25 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5 t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-147 0 -245.5 -96.5t-98.5 -240.5q0 -146 97 -241.5t247 -95.5zM896 1376 q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71z" />
+<glyph unicode="&#xf260;" horiz-adv-x="2048" d="M736 736l384 -384l-384 -384l-672 672l672 672l168 -168l-96 -96l-72 72l-480 -480l480 -480l193 193l-289 287zM1312 1312l672 -672l-672 -672l-168 168l96 96l72 -72l480 480l-480 480l-193 -193l289 -287l-96 -96l-384 384z" />
+<glyph unicode="&#xf261;" horiz-adv-x="1792" d="M717 182l271 271l-279 279l-88 -88l192 -191l-96 -96l-279 279l279 279l40 -40l87 87l-127 128l-454 -454zM1075 190l454 454l-454 454l-271 -271l279 -279l88 88l-192 191l96 96l279 -279l-279 -279l-40 40l-87 -88zM1792 640q0 -182 -71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf262;" horiz-adv-x="2304" d="M651 539q0 -39 -27.5 -66.5t-65.5 -27.5q-39 0 -66.5 27.5t-27.5 66.5q0 38 27.5 65.5t66.5 27.5q38 0 65.5 -27.5t27.5 -65.5zM1805 540q0 -39 -27.5 -66.5t-66.5 -27.5t-66.5 27.5t-27.5 66.5t27.5 66t66.5 27t66.5 -27t27.5 -66zM765 539q0 79 -56.5 136t-136.5 57 t-136.5 -56.5t-56.5 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM1918 540q0 80 -56.5 136.5t-136.5 56.5q-79 0 -136 -56.5t-57 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM850 539q0 -116 -81.5 -197.5t-196.5 -81.5q-116 0 -197.5 82t-81.5 197 t82 196.5t197 81.5t196.5 -81.5t81.5 -196.5zM2004 540q0 -115 -81.5 -196.5t-197.5 -81.5q-115 0 -196.5 81.5t-81.5 196.5t81.5 196.5t196.5 81.5q116 0 197.5 -81.5t81.5 -196.5zM1040 537q0 191 -135.5 326.5t-326.5 135.5q-125 0 -231 -62t-168 -168.5t-62 -231.5 t62 -231.5t168 -168.5t231 -62q191 0 326.5 135.5t135.5 326.5zM1708 1110q-254 111 -556 111q-319 0 -573 -110q117 0 223 -45.5t182.5 -122.5t122 -183t45.5 -223q0 115 43.5 219.5t118 180.5t177.5 123t217 50zM2187 537q0 191 -135 326.5t-326 135.5t-326.5 -135.5 t-135.5 -326.5t135.5 -326.5t326.5 -135.5t326 135.5t135 326.5zM1921 1103h383q-44 -51 -75 -114.5t-40 -114.5q110 -151 110 -337q0 -156 -77 -288t-209 -208.5t-287 -76.5q-133 0 -249 56t-196 155q-47 -56 -129 -179q-11 22 -53.5 82.5t-74.5 97.5 q-80 -99 -196.5 -155.5t-249.5 -56.5q-155 0 -287 76.5t-209 208.5t-77 288q0 186 110 337q-9 51 -40 114.5t-75 114.5h365q149 100 355 156.5t432 56.5q224 0 421 -56t348 -157z" />
+<glyph unicode="&#xf263;" horiz-adv-x="1280" d="M640 629q-188 0 -321 133t-133 320q0 188 133 321t321 133t321 -133t133 -321q0 -187 -133 -320t-321 -133zM640 1306q-92 0 -157.5 -65.5t-65.5 -158.5q0 -92 65.5 -157.5t157.5 -65.5t157.5 65.5t65.5 157.5q0 93 -65.5 158.5t-157.5 65.5zM1163 574q13 -27 15 -49.5 t-4.5 -40.5t-26.5 -38.5t-42.5 -37t-61.5 -41.5q-115 -73 -315 -94l73 -72l267 -267q30 -31 30 -74t-30 -73l-12 -13q-31 -30 -74 -30t-74 30q-67 68 -267 268l-267 -268q-31 -30 -74 -30t-73 30l-12 13q-31 30 -31 73t31 74l267 267l72 72q-203 21 -317 94 q-39 25 -61.5 41.5t-42.5 37t-26.5 38.5t-4.5 40.5t15 49.5q10 20 28 35t42 22t56 -2t65 -35q5 -4 15 -11t43 -24.5t69 -30.5t92 -24t113 -11q91 0 174 25.5t120 50.5l38 25q33 26 65 35t56 2t42 -22t28 -35z" />
+<glyph unicode="&#xf264;" d="M927 956q0 -66 -46.5 -112.5t-112.5 -46.5t-112.5 46.5t-46.5 112.5t46.5 112.5t112.5 46.5t112.5 -46.5t46.5 -112.5zM1141 593q-10 20 -28 32t-47.5 9.5t-60.5 -27.5q-10 -8 -29 -20t-81 -32t-127 -20t-124 18t-86 36l-27 18q-31 25 -60.5 27.5t-47.5 -9.5t-28 -32 q-22 -45 -2 -74.5t87 -73.5q83 -53 226 -67l-51 -52q-142 -142 -191 -190q-22 -22 -22 -52.5t22 -52.5l9 -9q22 -22 52.5 -22t52.5 22l191 191q114 -115 191 -191q22 -22 52.5 -22t52.5 22l9 9q22 22 22 52.5t-22 52.5l-191 190l-52 52q141 14 225 67q67 44 87 73.5t-2 74.5 zM1092 956q0 134 -95 229t-229 95t-229 -95t-95 -229t95 -229t229 -95t229 95t95 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf265;" horiz-adv-x="1720" d="M1565 1408q65 0 110 -45.5t45 -110.5v-519q0 -176 -68 -336t-182.5 -275t-274 -182.5t-334.5 -67.5q-176 0 -335.5 67.5t-274.5 182.5t-183 275t-68 336v519q0 64 46 110t110 46h1409zM861 344q47 0 82 33l404 388q37 35 37 85q0 49 -34.5 83.5t-83.5 34.5q-47 0 -82 -33 l-323 -310l-323 310q-35 33 -81 33q-49 0 -83.5 -34.5t-34.5 -83.5q0 -51 36 -85l405 -388q33 -33 81 -33z" />
+<glyph unicode="&#xf266;" horiz-adv-x="2304" d="M1494 -103l-295 695q-25 -49 -158.5 -305.5t-198.5 -389.5q-1 -1 -27.5 -0.5t-26.5 1.5q-82 193 -255.5 587t-259.5 596q-21 50 -66.5 107.5t-103.5 100.5t-102 43q0 5 -0.5 24t-0.5 27h583v-50q-39 -2 -79.5 -16t-66.5 -43t-10 -64q26 -59 216.5 -499t235.5 -540 q31 61 140 266.5t131 247.5q-19 39 -126 281t-136 295q-38 69 -201 71v50l513 -1v-47q-60 -2 -93.5 -25t-12.5 -69q33 -70 87 -189.5t86 -187.5q110 214 173 363q24 55 -10 79.5t-129 26.5q1 7 1 25v24q64 0 170.5 0.5t180 1t92.5 0.5v-49q-62 -2 -119 -33t-90 -81 l-213 -442q13 -33 127.5 -290t121.5 -274l441 1017q-14 38 -49.5 62.5t-65 31.5t-55.5 8v50l460 -4l1 -2l-1 -44q-139 -4 -201 -145q-526 -1216 -559 -1291h-49z" />
+<glyph unicode="&#xf267;" horiz-adv-x="1792" d="M949 643q0 -26 -16.5 -45t-41.5 -19q-26 0 -45 16.5t-19 41.5q0 26 17 45t42 19t44 -16.5t19 -41.5zM964 585l350 581q-9 -8 -67.5 -62.5t-125.5 -116.5t-136.5 -127t-117 -110.5t-50.5 -51.5l-349 -580q7 7 67 62t126 116.5t136 127t117 111t50 50.5zM1611 640 q0 -201 -104 -371q-3 2 -17 11t-26.5 16.5t-16.5 7.5q-13 0 -13 -13q0 -10 59 -44q-74 -112 -184.5 -190.5t-241.5 -110.5l-16 67q-1 10 -15 10q-5 0 -8 -5.5t-2 -9.5l16 -68q-72 -15 -146 -15q-199 0 -372 105q1 2 13 20.5t21.5 33.5t9.5 19q0 13 -13 13q-6 0 -17 -14.5 t-22.5 -34.5t-13.5 -23q-113 75 -192 187.5t-110 244.5l69 15q10 3 10 15q0 5 -5.5 8t-10.5 2l-68 -15q-14 72 -14 139q0 206 109 379q2 -1 18.5 -12t30 -19t17.5 -8q13 0 13 12q0 6 -12.5 15.5t-32.5 21.5l-20 12q77 112 189 189t244 107l15 -67q2 -10 15 -10q5 0 8 5.5 t2 10.5l-15 66q71 13 134 13q204 0 379 -109q-39 -56 -39 -65q0 -13 12 -13q11 0 48 64q111 -75 187.5 -186t107.5 -241l-56 -12q-10 -2 -10 -16q0 -5 5.5 -8t9.5 -2l57 13q14 -72 14 -140zM1696 640q0 163 -63.5 311t-170.5 255t-255 170.5t-311 63.5t-311 -63.5 t-255 -170.5t-170.5 -255t-63.5 -311t63.5 -311t170.5 -255t255 -170.5t311 -63.5t311 63.5t255 170.5t170.5 255t63.5 311zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191 t191 -286t71 -348z" />
+<glyph unicode="&#xf268;" horiz-adv-x="1792" d="M893 1536q240 2 451 -120q232 -134 352 -372l-742 39q-160 9 -294 -74.5t-185 -229.5l-276 424q128 159 311 245.5t383 87.5zM146 1131l337 -663q72 -143 211 -217t293 -45l-230 -451q-212 33 -385 157.5t-272.5 316t-99.5 411.5q0 267 146 491zM1732 962 q58 -150 59.5 -310.5t-48.5 -306t-153 -272t-246 -209.5q-230 -133 -498 -119l405 623q88 131 82.5 290.5t-106.5 277.5zM896 942q125 0 213.5 -88.5t88.5 -213.5t-88.5 -213.5t-213.5 -88.5t-213.5 88.5t-88.5 213.5t88.5 213.5t213.5 88.5z" />
+<glyph unicode="&#xf269;" horiz-adv-x="1792" d="M903 -256q-283 0 -504.5 150.5t-329.5 398.5q-58 131 -67 301t26 332.5t111 312t179 242.5l-11 -281q11 14 68 15.5t70 -15.5q42 81 160.5 138t234.5 59q-54 -45 -119.5 -148.5t-58.5 -163.5q25 -8 62.5 -13.5t63 -7.5t68 -4t50.5 -3q15 -5 9.5 -45.5t-30.5 -75.5 q-5 -7 -16.5 -18.5t-56.5 -35.5t-101 -34l15 -189l-139 67q-18 -43 -7.5 -81.5t36 -66.5t65.5 -41.5t81 -6.5q51 9 98 34.5t83.5 45t73.5 17.5q61 -4 89.5 -33t19.5 -65q-1 -2 -2.5 -5.5t-8.5 -12.5t-18 -15.5t-31.5 -10.5t-46.5 -1q-60 -95 -144.5 -135.5t-209.5 -29.5 q74 -61 162.5 -82.5t168.5 -6t154.5 52t128 87.5t80.5 104q43 91 39 192.5t-37.5 188.5t-78.5 125q87 -38 137 -79.5t77 -112.5q15 170 -57.5 343t-209.5 284q265 -77 412 -279.5t151 -517.5q2 -127 -40.5 -255t-123.5 -238t-189 -196t-247.5 -135.5t-288.5 -49.5z" />
+<glyph unicode="&#xf26a;" horiz-adv-x="1792" d="M1493 1308q-165 110 -359 110q-155 0 -293 -73t-240 -200q-75 -93 -119.5 -218t-48.5 -266v-42q4 -141 48.5 -266t119.5 -218q102 -127 240 -200t293 -73q194 0 359 110q-121 -108 -274.5 -168t-322.5 -60q-29 0 -43 1q-175 8 -333 82t-272 193t-181 281t-67 339 q0 182 71 348t191 286t286 191t348 71h3q168 -1 320.5 -60.5t273.5 -167.5zM1792 640q0 -192 -77 -362.5t-213 -296.5q-104 -63 -222 -63q-137 0 -255 84q154 56 253.5 233t99.5 405q0 227 -99 404t-253 234q119 83 254 83q119 0 226 -65q135 -125 210.5 -295t75.5 -361z " />
+<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M1792 599q0 -56 -7 -104h-1151q0 -146 109.5 -244.5t257.5 -98.5q99 0 185.5 46.5t136.5 130.5h423q-56 -159 -170.5 -281t-267.5 -188.5t-321 -66.5q-187 0 -356 83q-228 -116 -394 -116q-237 0 -237 263q0 115 45 275q17 60 109 229q199 360 475 606 q-184 -79 -427 -354q63 274 283.5 449.5t501.5 175.5q30 0 45 -1q255 117 433 117q64 0 116 -13t94.5 -40.5t66.5 -76.5t24 -115q0 -116 -75 -286q101 -182 101 -390zM1722 1239q0 83 -53 132t-137 49q-108 0 -254 -70q121 -47 222.5 -131.5t170.5 -195.5q51 135 51 216z M128 2q0 -86 48.5 -132.5t134.5 -46.5q115 0 266 83q-122 72 -213.5 183t-137.5 245q-98 -205 -98 -332zM632 715h728q-5 142 -113 237t-251 95q-144 0 -251.5 -95t-112.5 -237z" />
+<glyph unicode="&#xf26c;" horiz-adv-x="2048" d="M1792 288v960q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1248v-960q0 -66 -47 -113t-113 -47h-736v-128h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23 v64q0 14 9 23t23 9h352v128h-736q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf26d;" horiz-adv-x="1792" d="M138 1408h197q-70 -64 -126 -149q-36 -56 -59 -115t-30 -125.5t-8.5 -120t10.5 -132t21 -126t28 -136.5q4 -19 6 -28q51 -238 81 -329q57 -171 152 -275h-272q-48 0 -82 34t-34 82v1304q0 48 34 82t82 34zM1346 1408h308q48 0 82 -34t34 -82v-1304q0 -48 -34 -82t-82 -34 h-178q212 210 196 565l-469 -101q-2 -45 -12 -82t-31 -72t-59.5 -59.5t-93.5 -36.5q-123 -26 -199 40q-32 27 -53 61t-51.5 129t-64.5 258q-35 163 -45.5 263t-5.5 139t23 77q20 41 62.5 73t102.5 45q45 12 83.5 6.5t67 -17t54 -35t43 -48t34.5 -56.5l468 100 q-68 175 -180 287z" />
+<glyph unicode="&#xf26e;" d="M1401 -11l-6 -6q-113 -114 -259 -175q-154 -64 -317 -64q-165 0 -317 64q-148 63 -259 175q-113 112 -175 258q-42 103 -54 189q-4 28 48 36q51 8 56 -20q1 -1 1 -4q18 -90 46 -159q50 -124 152 -226q98 -98 226 -152q132 -56 276 -56q143 0 276 56q128 55 225 152l6 6 q10 10 25 6q12 -3 33 -22q36 -37 17 -58zM929 604l-66 -66l63 -63q21 -21 -7 -49q-17 -17 -32 -17q-10 0 -19 10l-62 61l-66 -66q-5 -5 -15 -5q-15 0 -31 16l-2 2q-18 15 -18 29q0 7 8 17l66 65l-66 66q-16 16 14 45q18 18 31 18q6 0 13 -5l65 -66l65 65q18 17 48 -13 q27 -27 11 -44zM1400 547q0 -118 -46 -228q-45 -105 -126 -186q-80 -80 -187 -126t-228 -46t-228 46t-187 126q-82 82 -125 186q-15 32 -15 40h-1q-9 27 43 44q50 16 60 -12q37 -99 97 -167h1v339v2q3 136 102 232q105 103 253 103q147 0 251 -103t104 -249 q0 -147 -104.5 -251t-250.5 -104q-58 0 -112 16q-28 11 -13 61q16 51 44 43l14 -3q14 -3 32.5 -6t30.5 -3q104 0 176 71.5t72 174.5q0 101 -72 171q-71 71 -175 71q-107 0 -178 -80q-64 -72 -64 -160v-413q110 -67 242 -67q96 0 185 36.5t156 103.5t103.5 155t36.5 183 q0 198 -141 339q-140 140 -339 140q-200 0 -340 -140q-53 -53 -77 -87l-2 -2q-8 -11 -13 -15.5t-21.5 -9.5t-38.5 3q-21 5 -36.5 16.5t-15.5 26.5v680q0 15 10.5 26.5t27.5 11.5h877q30 0 30 -55t-30 -55h-811v-483h1q40 42 102 84t108 61q109 46 231 46q121 0 228 -46 t187 -126q81 -81 126 -186q46 -112 46 -229zM1369 1128q9 -8 9 -18t-5.5 -18t-16.5 -21q-26 -26 -39 -26q-9 0 -16 7q-106 91 -207 133q-128 56 -276 56q-133 0 -262 -49q-27 -10 -45 37q-9 25 -8 38q3 16 16 20q130 57 299 57q164 0 316 -64q137 -58 235 -152z" />
+<glyph unicode="&#xf270;" horiz-adv-x="1792" d="M1551 60q15 6 26 3t11 -17.5t-15 -33.5q-13 -16 -44 -43.5t-95.5 -68t-141 -74t-188 -58t-229.5 -24.5q-119 0 -238 31t-209 76.5t-172.5 104t-132.5 105t-84 87.5q-8 9 -10 16.5t1 12t8 7t11.5 2t11.5 -4.5q192 -117 300 -166q389 -176 799 -90q190 40 391 135z M1758 175q11 -16 2.5 -69.5t-28.5 -102.5q-34 -83 -85 -124q-17 -14 -26 -9t0 24q21 45 44.5 121.5t6.5 98.5q-5 7 -15.5 11.5t-27 6t-29.5 2.5t-35 0t-31.5 -2t-31 -3t-22.5 -2q-6 -1 -13 -1.5t-11 -1t-8.5 -1t-7 -0.5h-5.5h-4.5t-3 0.5t-2 1.5l-1.5 3q-6 16 47 40t103 30 q46 7 108 1t76 -24zM1364 618q0 -31 13.5 -64t32 -58t37.5 -46t33 -32l13 -11l-227 -224q-40 37 -79 75.5t-58 58.5l-19 20q-11 11 -25 33q-38 -59 -97.5 -102.5t-127.5 -63.5t-140 -23t-137.5 21t-117.5 65.5t-83 113t-31 162.5q0 84 28 154t72 116.5t106.5 83t122.5 57 t130 34.5t119.5 18.5t99.5 6.5v127q0 65 -21 97q-34 53 -121 53q-6 0 -16.5 -1t-40.5 -12t-56 -29.5t-56 -59.5t-48 -96l-294 27q0 60 22 119t67 113t108 95t151.5 65.5t190.5 24.5q100 0 181 -25t129.5 -61.5t81 -83t45 -86t12.5 -73.5v-589zM692 597q0 -86 70 -133 q66 -44 139 -22q84 25 114 123q14 45 14 101v162q-59 -2 -111 -12t-106.5 -33.5t-87 -71t-32.5 -114.5z" />
+<glyph unicode="&#xf271;" horiz-adv-x="1792" d="M1536 1280q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128zM1152 1376v-288q0 -14 9 -23t23 -9 h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 1376v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM1536 -128v1024h-1408v-1024h1408zM896 448h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224 v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224z" />
+<glyph unicode="&#xf272;" horiz-adv-x="1792" d="M1152 416v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23 -9t9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23 t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf273;" horiz-adv-x="1792" d="M1111 151l-46 -46q-9 -9 -22 -9t-23 9l-188 189l-188 -189q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22t9 23l189 188l-189 188q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l188 -188l188 188q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23l-188 -188l188 -188q9 -10 9 -23t-9 -22z M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf274;" horiz-adv-x="1792" d="M1303 572l-512 -512q-10 -9 -23 -9t-23 9l-288 288q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l220 -220l444 444q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf275;" horiz-adv-x="1792" d="M448 1536q26 0 45 -19t19 -45v-891l536 429q17 14 40 14q26 0 45 -19t19 -45v-379l536 429q17 14 40 14q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h384z" />
+<glyph unicode="&#xf276;" horiz-adv-x="1024" d="M512 448q66 0 128 15v-655q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v655q61 -15 128 -15zM512 1536q212 0 362 -150t150 -362t-150 -362t-362 -150t-362 150t-150 362t150 362t362 150zM512 1312q14 0 23 9t9 23t-9 23t-23 9q-146 0 -249 -103t-103 -249 q0 -14 9 -23t23 -9t23 9t9 23q0 119 84.5 203.5t203.5 84.5z" />
+<glyph unicode="&#xf277;" horiz-adv-x="1792" d="M1745 1239q10 -10 10 -23t-10 -23l-141 -141q-28 -28 -68 -28h-1344q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h576v64q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-64h512q40 0 68 -28zM768 320h256v-512q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v512zM1600 768 q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1344q-40 0 -68 28l-141 141q-10 10 -10 23t10 23l141 141q28 28 68 28h512v192h256v-192h576z" />
+<glyph unicode="&#xf278;" horiz-adv-x="2048" d="M2020 1525q28 -20 28 -53v-1408q0 -20 -11 -36t-29 -23l-640 -256q-24 -11 -48 0l-616 246l-616 -246q-10 -5 -24 -5q-19 0 -36 11q-28 20 -28 53v1408q0 20 11 36t29 23l640 256q24 11 48 0l616 -246l616 246q32 13 60 -6zM736 1390v-1270l576 -230v1270zM128 1173 v-1270l544 217v1270zM1920 107v1270l-544 -217v-1270z" />
+<glyph unicode="&#xf279;" horiz-adv-x="1792" d="M512 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472q0 20 17 28l480 256q7 4 15 4zM1760 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472 q0 20 17 28l480 256q7 4 15 4zM640 1536q8 0 14 -3l512 -256q18 -10 18 -29v-1472q0 -13 -9.5 -22.5t-22.5 -9.5q-8 0 -14 3l-512 256q-18 10 -18 29v1472q0 13 9.5 22.5t22.5 9.5z" />
+<glyph unicode="&#xf27a;" horiz-adv-x="1792" d="M640 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 640q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-110 0 -211 18q-173 -173 -435 -229q-52 -10 -86 -13q-12 -1 -22 6t-13 18q-4 15 20 37q5 5 23.5 21.5t25.5 23.5t23.5 25.5t24 31.5t20.5 37 t20 48t14.5 57.5t12.5 72.5q-146 90 -229.5 216.5t-83.5 269.5q0 174 120 321.5t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf27b;" horiz-adv-x="1792" d="M640 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5 t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51 t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 130 71 248.5t191 204.5t286 136.5t348 50.5t348 -50.5t286 -136.5t191 -204.5t71 -248.5z" />
+<glyph unicode="&#xf27c;" horiz-adv-x="1024" d="M512 345l512 295v-591l-512 -296v592zM0 640v-591l512 296zM512 1527v-591l-512 -296v591zM512 936l512 295v-591z" />
+<glyph unicode="&#xf27d;" horiz-adv-x="1792" d="M1709 1018q-10 -236 -332 -651q-333 -431 -562 -431q-142 0 -240 263q-44 160 -132 482q-72 262 -157 262q-18 0 -127 -76l-77 98q24 21 108 96.5t130 115.5q156 138 241 146q95 9 153 -55.5t81 -203.5q44 -287 66 -373q55 -249 120 -249q51 0 154 161q101 161 109 246 q13 139 -109 139q-57 0 -121 -26q120 393 459 382q251 -8 236 -326z" />
+<glyph unicode="&#xf27e;" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
+<glyph unicode="&#xf280;" d="M0 1408h1536v-1536h-1536v1536zM908 1088l-12 -33l75 -83l-31 -114l25 -25l107 57l107 -57l25 25l-31 114l75 83l-12 33h-95l-53 96h-32l-53 -96h-95zM641 925q32 0 44.5 -16t11.5 -63l174 21q0 55 -17.5 92.5t-50.5 56t-69 25.5t-85 7q-133 0 -199 -57.5t-66 -182.5v-72 h-96v-128h76q20 0 20 -8v-382q0 -14 -5 -20t-18 -7l-73 -7v-88h448v86l-149 14q-6 1 -8.5 1.5t-3.5 2.5t-0.5 4t1 7t0.5 10v387h191l38 128h-231q-6 0 -2 6t4 9v80q0 27 1.5 40.5t7.5 28t19.5 20t36.5 5.5zM1248 96v86l-54 9q-7 1 -9.5 2.5t-2.5 3t1 7.5t1 12v520h-275 l-23 -101l83 -22q23 -7 23 -27v-370q0 -14 -6 -18.5t-20 -6.5l-70 -9v-86h352z" />
+<glyph unicode="&#xf281;" horiz-adv-x="1792" d="M1792 690q0 -58 -29.5 -105.5t-79.5 -72.5q12 -46 12 -96q0 -155 -106.5 -287t-290.5 -208.5t-400 -76.5t-399.5 76.5t-290 208.5t-106.5 287q0 47 11 94q-51 25 -82 73.5t-31 106.5q0 82 58 140.5t141 58.5q85 0 145 -63q218 152 515 162l116 521q3 13 15 21t26 5 l369 -81q18 37 54 59.5t79 22.5q62 0 106 -43.5t44 -105.5t-44 -106t-106 -44t-105.5 43.5t-43.5 105.5l-334 74l-104 -472q300 -9 519 -160q58 61 143 61q83 0 141 -58.5t58 -140.5zM418 491q0 -62 43.5 -106t105.5 -44t106 44t44 106t-44 105.5t-106 43.5q-61 0 -105 -44 t-44 -105zM1228 136q11 11 11 26t-11 26q-10 10 -25 10t-26 -10q-41 -42 -121 -62t-160 -20t-160 20t-121 62q-11 10 -26 10t-25 -10q-11 -10 -11 -25.5t11 -26.5q43 -43 118.5 -68t122.5 -29.5t91 -4.5t91 4.5t122.5 29.5t118.5 68zM1225 341q62 0 105.5 44t43.5 106 q0 61 -44 105t-105 44q-62 0 -106 -43.5t-44 -105.5t44 -106t106 -44z" />
+<glyph unicode="&#xf282;" horiz-adv-x="1792" d="M69 741h1q16 126 58.5 241.5t115 217t167.5 176t223.5 117.5t276.5 43q231 0 414 -105.5t294 -303.5q104 -187 104 -442v-188h-1125q1 -111 53.5 -192.5t136.5 -122.5t189.5 -57t213 -3t208 46.5t173.5 84.5v-377q-92 -55 -229.5 -92t-312.5 -38t-316 53 q-189 73 -311.5 249t-124.5 372q-3 242 111 412t325 268q-48 -60 -78 -125.5t-46 -159.5h635q8 77 -8 140t-47 101.5t-70.5 66.5t-80.5 41t-75 20.5t-56 8.5l-22 1q-135 -5 -259.5 -44.5t-223.5 -104.5t-176 -140.5t-138 -163.5z" />
+<glyph unicode="&#xf283;" horiz-adv-x="2304" d="M0 32v608h2304v-608q0 -66 -47 -113t-113 -47h-1984q-66 0 -113 47t-47 113zM640 256v-128h384v128h-384zM256 256v-128h256v128h-256zM2144 1408q66 0 113 -47t47 -113v-224h-2304v224q0 66 47 113t113 47h1984z" />
+<glyph unicode="&#xf284;" horiz-adv-x="1792" d="M1549 857q55 0 85.5 -28.5t30.5 -83.5t-34 -82t-91 -27h-136v-177h-25v398h170zM1710 267l-4 -11l-5 -10q-113 -230 -330.5 -366t-474.5 -136q-182 0 -348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71q244 0 454.5 -124t329.5 -338l2 -4l8 -16 q-30 -15 -136.5 -68.5t-163.5 -84.5q-6 -3 -479 -268q384 -183 799 -366zM896 -234q250 0 462.5 132.5t322.5 357.5l-287 129q-72 -140 -206 -222t-292 -82q-151 0 -280 75t-204 204t-75 280t75 280t204 204t280 75t280 -73.5t204 -204.5l280 143q-116 208 -321 329 t-443 121q-119 0 -232.5 -31.5t-209 -87.5t-176.5 -137t-137 -176.5t-87.5 -209t-31.5 -232.5t31.5 -232.5t87.5 -209t137 -176.5t176.5 -137t209 -87.5t232.5 -31.5z" />
+<glyph unicode="&#xf285;" horiz-adv-x="1792" d="M1427 827l-614 386l92 151h855zM405 562l-184 116v858l1183 -743zM1424 697l147 -95v-858l-532 335zM1387 718l-500 -802h-855l356 571z" />
+<glyph unicode="&#xf286;" horiz-adv-x="1792" d="M640 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1152 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1664 496v-752h-640v320q0 80 -56 136t-136 56t-136 -56t-56 -136v-320h-640v752q0 16 16 16h96 q16 0 16 -16v-112h128v624q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h16v393q-32 19 -32 55q0 26 19 45t45 19t45 -19t19 -45q0 -36 -32 -55v-9h272q16 0 16 -16v-224q0 -16 -16 -16h-272v-128h16q16 0 16 -16v-112h128 v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-624h128v112q0 16 16 16h96q16 0 16 -16z" />
+<glyph unicode="&#xf287;" horiz-adv-x="2304" d="M2288 731q16 -8 16 -27t-16 -27l-320 -192q-8 -5 -16 -5q-9 0 -16 4q-16 10 -16 28v128h-858q37 -58 83 -165q16 -37 24.5 -55t24 -49t27 -47t27 -34t31.5 -26t33 -8h96v96q0 14 9 23t23 9h320q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v96h-96 q-32 0 -61 10t-51 23.5t-45 40.5t-37 46t-33.5 57t-28.5 57.5t-28 60.5q-23 53 -37 81.5t-36 65t-44.5 53.5t-46.5 17h-360q-22 -84 -91 -138t-157 -54q-106 0 -181 75t-75 181t75 181t181 75q88 0 157 -54t91 -138h104q24 0 46.5 17t44.5 53.5t36 65t37 81.5q19 41 28 60.5 t28.5 57.5t33.5 57t37 46t45 40.5t51 23.5t61 10h107q21 57 70 92.5t111 35.5q80 0 136 -56t56 -136t-56 -136t-136 -56q-62 0 -111 35.5t-70 92.5h-107q-17 0 -33 -8t-31.5 -26t-27 -34t-27 -47t-24 -49t-24.5 -55q-46 -107 -83 -165h1114v128q0 18 16 28t32 -1z" />
+<glyph unicode="&#xf288;" horiz-adv-x="1792" d="M1150 774q0 -56 -39.5 -95t-95.5 -39h-253v269h253q56 0 95.5 -39.5t39.5 -95.5zM1329 774q0 130 -91.5 222t-222.5 92h-433v-896h180v269h253q130 0 222 91.5t92 221.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348 t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf289;" horiz-adv-x="2304" d="M1645 438q0 59 -34 106.5t-87 68.5q-7 -45 -23 -92q-7 -24 -27.5 -38t-44.5 -14q-12 0 -24 3q-31 10 -45 38.5t-4 58.5q23 71 23 143q0 123 -61 227.5t-166 165.5t-228 61q-134 0 -247 -73t-167 -194q108 -28 188 -106q22 -23 22 -55t-22 -54t-54 -22t-55 22 q-75 75 -180 75q-106 0 -181 -74.5t-75 -180.5t75 -180.5t181 -74.5h1046q79 0 134.5 55.5t55.5 133.5zM1798 438q0 -142 -100.5 -242t-242.5 -100h-1046q-169 0 -289 119.5t-120 288.5q0 153 100 267t249 136q62 184 221 298t354 114q235 0 408.5 -158.5t196.5 -389.5 q116 -25 192.5 -118.5t76.5 -214.5zM2048 438q0 -175 -97 -319q-23 -33 -64 -33q-24 0 -43 13q-26 17 -32 48.5t12 57.5q71 104 71 233t-71 233q-18 26 -12 57t32 49t57.5 11.5t49.5 -32.5q97 -142 97 -318zM2304 438q0 -244 -134 -443q-23 -34 -64 -34q-23 0 -42 13 q-26 18 -32.5 49t11.5 57q108 164 108 358q0 195 -108 357q-18 26 -11.5 57.5t32.5 48.5q26 18 57 12t49 -33q134 -198 134 -442z" />
+<glyph unicode="&#xf28a;" d="M1500 -13q0 -89 -63 -152.5t-153 -63.5t-153.5 63.5t-63.5 152.5q0 90 63.5 153.5t153.5 63.5t153 -63.5t63 -153.5zM1267 268q-115 -15 -192.5 -102.5t-77.5 -205.5q0 -74 33 -138q-146 -78 -379 -78q-109 0 -201 21t-153.5 54.5t-110.5 76.5t-76 85t-44.5 83 t-23.5 66.5t-6 39.5q0 19 4.5 42.5t18.5 56t36.5 58t64 43.5t94.5 18t94 -17.5t63 -41t35.5 -53t17.5 -49t4 -33.5q0 -34 -23 -81q28 -27 82 -42t93 -17l40 -1q115 0 190 51t75 133q0 26 -9 48.5t-31.5 44.5t-49.5 41t-74 44t-93.5 47.5t-119.5 56.5q-28 13 -43 20 q-116 55 -187 100t-122.5 102t-72 125.5t-20.5 162.5q0 78 20.5 150t66 137.5t112.5 114t166.5 77t221.5 28.5q120 0 220 -26t164.5 -67t109.5 -94t64 -105.5t19 -103.5q0 -46 -15 -82.5t-36.5 -58t-48.5 -36t-49 -19.5t-39 -5h-8h-32t-39 5t-44 14t-41 28t-37 46t-24 70.5 t-10 97.5q-15 16 -59 25.5t-81 10.5l-37 1q-68 0 -117.5 -31t-70.5 -70t-21 -76q0 -24 5 -43t24 -46t53 -51t97 -53.5t150 -58.5q76 -25 138.5 -53.5t109 -55.5t83 -59t60.5 -59.5t41 -62.5t26.5 -62t14.5 -63.5t6 -62t1 -62.5z" />
+<glyph unicode="&#xf28b;" d="M704 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1152 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103 t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf28c;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM864 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192z" />
+<glyph unicode="&#xf28d;" d="M1088 352v576q0 14 -9 23t-23 9h-576q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h576q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="&#xf28e;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h576q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-576z" />
+<glyph unicode="&#xf290;" horiz-adv-x="1792" d="M1757 128l35 -313q3 -28 -16 -50q-19 -21 -48 -21h-1664q-29 0 -48 21q-19 22 -16 50l35 313h1722zM1664 967l86 -775h-1708l86 775q3 24 21 40.5t43 16.5h256v-128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5v128h384v-128q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5v128h256q25 0 43 -16.5t21 -40.5zM1280 1152v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf291;" horiz-adv-x="2048" d="M1920 768q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-15l-115 -662q-8 -46 -44 -76t-82 -30h-1280q-46 0 -82 30t-44 76l-115 662h-15q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5h1792zM485 -32q26 2 43.5 22.5t15.5 46.5l-32 416q-2 26 -22.5 43.5 t-46.5 15.5t-43.5 -22.5t-15.5 -46.5l32 -416q2 -25 20.5 -42t43.5 -17h5zM896 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1280 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1632 27l32 416 q2 26 -15.5 46.5t-43.5 22.5t-46.5 -15.5t-22.5 -43.5l-32 -416q-2 -26 15.5 -46.5t43.5 -22.5h5q25 0 43.5 17t20.5 42zM476 1244l-93 -412h-132l101 441q19 88 89 143.5t160 55.5h167q0 26 19 45t45 19h384q26 0 45 -19t19 -45h167q90 0 160 -55.5t89 -143.5l101 -441 h-132l-93 412q-11 44 -45.5 72t-79.5 28h-167q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45h-167q-45 0 -79.5 -28t-45.5 -72z" />
+<glyph unicode="&#xf292;" horiz-adv-x="1792" d="M991 512l64 256h-254l-64 -256h254zM1759 1016l-56 -224q-7 -24 -31 -24h-327l-64 -256h311q15 0 25 -12q10 -14 6 -28l-56 -224q-5 -24 -31 -24h-327l-81 -328q-7 -24 -31 -24h-224q-16 0 -26 12q-9 12 -6 28l78 312h-254l-81 -328q-7 -24 -31 -24h-225q-15 0 -25 12 q-9 12 -6 28l78 312h-311q-15 0 -25 12q-9 12 -6 28l56 224q7 24 31 24h327l64 256h-311q-15 0 -25 12q-10 14 -6 28l56 224q5 24 31 24h327l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h254l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h311 q15 0 25 -12q9 -12 6 -28z" />
+<glyph unicode="&#xf293;" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" />
+<glyph unicode="&#xf294;" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" />
+<glyph unicode="&#xf295;" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf296;" horiz-adv-x="1792" />
+<glyph unicode="&#xf297;" horiz-adv-x="1792" />
+<glyph unicode="&#xf298;" horiz-adv-x="1792" />
+<glyph unicode="&#xf299;" horiz-adv-x="1792" />
+<glyph unicode="&#xf29a;" horiz-adv-x="1792" />
+<glyph unicode="&#xf29b;" horiz-adv-x="1792" />
+<glyph unicode="&#xf29c;" horiz-adv-x="1792" />
+<glyph unicode="&#xf29d;" horiz-adv-x="1792" />
+<glyph unicode="&#xf29e;" horiz-adv-x="1792" />
+<glyph unicode="&#xf500;" horiz-adv-x="1792" />
+</font>
+</defs></svg> \ No newline at end of file
diff --git a/themes/CleanFS/fonts/fontawesome-webfont.ttf b/themes/CleanFS/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..26dea79
--- /dev/null
+++ b/themes/CleanFS/fonts/fontawesome-webfont.ttf
Binary files differ
diff --git a/themes/CleanFS/fonts/fontawesome-webfont.woff b/themes/CleanFS/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..dc35ce3
--- /dev/null
+++ b/themes/CleanFS/fonts/fontawesome-webfont.woff
Binary files differ
diff --git a/themes/CleanFS/fonts/fontawesome-webfont.woff2 b/themes/CleanFS/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..500e517
--- /dev/null
+++ b/themes/CleanFS/fonts/fontawesome-webfont.woff2
Binary files differ
diff --git a/themes/CleanFS/fonts/octicons/LICENSE.txt b/themes/CleanFS/fonts/octicons/LICENSE.txt
new file mode 100644
index 0000000..69aa0d5
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/LICENSE.txt
@@ -0,0 +1,9 @@
+(c) 2012-2015 GitHub
+
+When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
+
+Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
+Applies to all font files
+
+Code License: MIT (http://choosealicense.com/licenses/mit/)
+Applies to all other files
diff --git a/themes/CleanFS/fonts/octicons/octicons.css b/themes/CleanFS/fonts/octicons/octicons.css
new file mode 100644
index 0000000..9b86765
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.css
@@ -0,0 +1,236 @@
+@font-face {
+ font-family: 'octicons';
+ src: url('octicons.eot?#iefix') format('embedded-opentype'),
+ url('octicons.woff') format('woff'),
+ url('octicons.ttf') format('truetype'),
+ url('octicons.svg#octicons') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+/*
+
+.octicon is optimized for 16px.
+.mega-octicon is optimized for 32px but can be used larger.
+
+*/
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /* ï‚Š */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* ï‚Ž */
+.octicon-alignment-unalign:before { content: '\f08b'} /* ï‚‹ */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /* ï€ */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* ï‚  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* ï‚¡ */
+.octicon-arrow-small-right:before { content: '\f071'} /* ï± */
+.octicon-arrow-small-up:before { content: '\f09f'} /* ï‚Ÿ */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /* ï© */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /* ï» */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /* ïˆ */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /* ï‚‘ */
+.octicon-calendar:before { content: '\f068'} /* ï¨ */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /* ï¶ */
+.octicon-chevron-down:before { content: '\f0a3'} /* ï‚£ */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /* ï¸ */
+.octicon-chevron-up:before { content: '\f0a2'} /* ï‚¢ */
+.octicon-circle-slash:before { content: '\f084'} /* ï‚„ */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /* ï† */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /* ïŸ */
+.octicon-color-mode:before { content: '\f065'} /* ï¥ */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /* ï */
+.octicon-credit-card:before { content: '\f045'} /* ï… */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /* ï½ */
+.octicon-database:before { content: '\f096'} /* ï‚– */
+.octicon-device-camera:before { content: '\f056'} /* ï– */
+.octicon-device-camera-video:before { content: '\f057'} /* ï— */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /* ï */
+.octicon-diff-added:before { content: '\f06b'} /* ï« */
+.octicon-diff-ignored:before { content: '\f099'} /* ï‚™ */
+.octicon-diff-modified:before { content: '\f06d'} /* ï­ */
+.octicon-diff-removed:before { content: '\f06c'} /* ï¬ */
+.octicon-diff-renamed:before { content: '\f06e'} /* ï® */
+.octicon-ellipsis:before { content: '\f09a'} /* ï‚š */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* ïŽ */
+.octicon-file-binary:before { content: '\f094'} /* ï‚” */
+.octicon-file-code:before { content: '\f010'} /* ï€ */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* ï‚° */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /* ï‚ */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /* ïƒ */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* ï¾ */
+.octicon-home:before { content: '\f08d'} /* ï‚ */
+.octicon-horizontal-rule:before { content: '\f070'} /* ï° */
+.octicon-hourglass:before { content: '\f09e'} /* ï‚ž */
+.octicon-hubot:before { content: '\f09d'} /* ï‚ */
+.octicon-inbox:before { content: '\f0cf'} /* ïƒ */
+.octicon-info:before { content: '\f059'} /* ï™ */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /* ï² */
+.octicon-jump-left:before { content: '\f0a5'} /* ï‚¥ */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /* ï³ */
+.octicon-key:before { content: '\f049'} /* ï‰ */
+.octicon-keyboard:before { content: '\f00d'} /* ï€ */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /* ïœ */
+.octicon-link-external:before { content: '\f07f'} /* ï¿ */
+.octicon-list-ordered:before { content: '\f062'} /* ï¢ */
+.octicon-list-unordered:before { content: '\f061'} /* ï¡ */
+.octicon-location:before { content: '\f060'} /* ï  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* ïª */
+.octicon-logo-github:before { content: '\f092'} /* ï‚’ */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /* ï‘ */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /* ï· */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /* ïµ */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /* ï´ */
+.octicon-move-right:before { content: '\f0a9'} /* ï‚© */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /* ï‚€ */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /* ï˜ */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /* ï */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /* ï‚» */
+.octicon-playback-play:before { content: '\f0bf'} /* ï‚¿ */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* ï */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /* ï’ */
+.octicon-primitive-square:before { content: '\f053'} /* ï“ */
+.octicon-pulse:before { content: '\f085'} /* ï‚… */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /* ï£ */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* ï€ */
+.octicon-repo-clone:before { content: '\f04c'} /* ïŒ */
+.octicon-repo-force-push:before { content: '\f04a'} /* ïŠ */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /* ï‡ */
+.octicon-screen-full:before { content: '\f066'} /* ï¦ */
+.octicon-screen-normal:before { content: '\f067'} /* ï§ */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /* ï‚— */
+.octicon-settings:before { content: '\f07c'} /* ï¼ */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /* ï‚ */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /* ïž */
+.octicon-thumbsdown:before { content: '\f0db'} /*  */
+.octicon-thumbsup:before { content: '\f0da'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /* ïƒ */
+.octicon-triangle-down:before { content: '\f05b'} /* ï› */
+.octicon-triangle-left:before { content: '\f044'} /* ï„ */
+.octicon-triangle-right:before { content: '\f05a'} /* ïš */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /* ï¤ */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* ï‚ */
+.octicon-zap:before { content: '\26A1'} /* âš¡ */
diff --git a/themes/CleanFS/fonts/octicons/octicons.eot b/themes/CleanFS/fonts/octicons/octicons.eot
new file mode 100644
index 0000000..659adc4
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.eot
Binary files differ
diff --git a/themes/CleanFS/fonts/octicons/octicons.less b/themes/CleanFS/fonts/octicons/octicons.less
new file mode 100644
index 0000000..301a113
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.less
@@ -0,0 +1,235 @@
+@octicons-font-path: ".";
+@octicons-version: "345f8bad9c5003db196d08f05e7f030fd2a32ff6";
+
+@font-face {
+ font-family: 'octicons';
+ src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')",
+ ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')",
+ ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')",
+ ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')";
+ font-weight: normal;
+ font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /* ï‚Š */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* ï‚Ž */
+.octicon-alignment-unalign:before { content: '\f08b'} /* ï‚‹ */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /* ï€ */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* ï‚  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* ï‚¡ */
+.octicon-arrow-small-right:before { content: '\f071'} /* ï± */
+.octicon-arrow-small-up:before { content: '\f09f'} /* ï‚Ÿ */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /* ï© */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /* ï» */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /* ïˆ */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /* ï‚‘ */
+.octicon-calendar:before { content: '\f068'} /* ï¨ */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /* ï¶ */
+.octicon-chevron-down:before { content: '\f0a3'} /* ï‚£ */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /* ï¸ */
+.octicon-chevron-up:before { content: '\f0a2'} /* ï‚¢ */
+.octicon-circle-slash:before { content: '\f084'} /* ï‚„ */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /* ï† */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /* ïŸ */
+.octicon-color-mode:before { content: '\f065'} /* ï¥ */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /* ï */
+.octicon-credit-card:before { content: '\f045'} /* ï… */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /* ï½ */
+.octicon-database:before { content: '\f096'} /* ï‚– */
+.octicon-device-camera:before { content: '\f056'} /* ï– */
+.octicon-device-camera-video:before { content: '\f057'} /* ï— */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /* ï */
+.octicon-diff-added:before { content: '\f06b'} /* ï« */
+.octicon-diff-ignored:before { content: '\f099'} /* ï‚™ */
+.octicon-diff-modified:before { content: '\f06d'} /* ï­ */
+.octicon-diff-removed:before { content: '\f06c'} /* ï¬ */
+.octicon-diff-renamed:before { content: '\f06e'} /* ï® */
+.octicon-ellipsis:before { content: '\f09a'} /* ï‚š */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* ïŽ */
+.octicon-file-binary:before { content: '\f094'} /* ï‚” */
+.octicon-file-code:before { content: '\f010'} /* ï€ */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* ï‚° */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /* ï‚ */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /* ïƒ */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* ï¾ */
+.octicon-home:before { content: '\f08d'} /* ï‚ */
+.octicon-horizontal-rule:before { content: '\f070'} /* ï° */
+.octicon-hourglass:before { content: '\f09e'} /* ï‚ž */
+.octicon-hubot:before { content: '\f09d'} /* ï‚ */
+.octicon-inbox:before { content: '\f0cf'} /* ïƒ */
+.octicon-info:before { content: '\f059'} /* ï™ */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /* ï² */
+.octicon-jump-left:before { content: '\f0a5'} /* ï‚¥ */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /* ï³ */
+.octicon-key:before { content: '\f049'} /* ï‰ */
+.octicon-keyboard:before { content: '\f00d'} /* ï€ */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /* ïœ */
+.octicon-link-external:before { content: '\f07f'} /* ï¿ */
+.octicon-list-ordered:before { content: '\f062'} /* ï¢ */
+.octicon-list-unordered:before { content: '\f061'} /* ï¡ */
+.octicon-location:before { content: '\f060'} /* ï  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* ïª */
+.octicon-logo-github:before { content: '\f092'} /* ï‚’ */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /* ï‘ */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /* ï· */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /* ïµ */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /* ï´ */
+.octicon-move-right:before { content: '\f0a9'} /* ï‚© */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /* ï‚€ */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /* ï˜ */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /* ï */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /* ï‚» */
+.octicon-playback-play:before { content: '\f0bf'} /* ï‚¿ */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* ï */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /* ï’ */
+.octicon-primitive-square:before { content: '\f053'} /* ï“ */
+.octicon-pulse:before { content: '\f085'} /* ï‚… */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /* ï£ */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* ï€ */
+.octicon-repo-clone:before { content: '\f04c'} /* ïŒ */
+.octicon-repo-force-push:before { content: '\f04a'} /* ïŠ */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /* ï‡ */
+.octicon-screen-full:before { content: '\f066'} /* ï¦ */
+.octicon-screen-normal:before { content: '\f067'} /* ï§ */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /* ï‚— */
+.octicon-settings:before { content: '\f07c'} /* ï¼ */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /* ï‚ */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /* ïž */
+.octicon-thumbsdown:before { content: '\f0db'} /*  */
+.octicon-thumbsup:before { content: '\f0da'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /* ïƒ */
+.octicon-triangle-down:before { content: '\f05b'} /* ï› */
+.octicon-triangle-left:before { content: '\f044'} /* ï„ */
+.octicon-triangle-right:before { content: '\f05a'} /* ïš */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /* ï¤ */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* ï‚ */
+.octicon-zap:before { content: '\26A1'} /* âš¡ */
diff --git a/themes/CleanFS/fonts/octicons/octicons.svg b/themes/CleanFS/fonts/octicons/octicons.svg
new file mode 100644
index 0000000..23faa86
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.svg
@@ -0,0 +1,200 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>
+(c) 2012-2015 GitHub
+
+When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
+
+Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
+Applies to all font files
+
+Code License: MIT (http://choosealicense.com/licenses/mit/)
+Applies to all other files
+</metadata>
+<defs>
+<font id="octicons" horiz-adv-x="1024" >
+<font-face font-family="octicons" font-weight="400" font-stretch="normal" units-per-em="1024" ascent="832" descent="-192" />
+<missing-glyph d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" />
+<glyph glyph-name="alert" unicode="&#xf02d;" d="M1005.854 31.753000000000043l-438.286 767C556.173 818.694 534.967 831 512 831s-44.173-12.306-55.567-32.247l-438.286-767c-11.319-19.809-11.238-44.144 0.213-63.876C29.811-51.85500000000002 50.899-64 73.714-64h876.572c22.814 0 43.903 12.145 55.354 31.877S1017.173 11.94399999999996 1005.854 31.753000000000043zM576 64H448V192h128V64zM576 256H448V512h128V256z" horiz-adv-x="1024" />
+<glyph glyph-name="alignment-align" unicode="&#xf08a;" d="M192 768C85.938 768 0 682.062 0 576s85.938-192 192-192c106.062 0 192 85.938 192 192S298.062 768 192 768zM672 224l160 160H384v-448l160 160 288-288 128 128L672 224z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-aligned-to" unicode="&#xf08e;" d="M384 256l128 128 288-288 160 160v-448H512l160 160L384 256zM192 384C85.938 384 0 469.938 0 576S85.938 768 192 768c106.062 0 192-85.938 192-192S298.062 384 192 384z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-unalign" unicode="&#xf08b;" d="M512 640L384 512 128 768 0 640l256-256L128 256l64-64 384 384L512 640zM640 256l128 128-64 64L320 64l64-64 128 128 256-256 128 128L640 256z" horiz-adv-x="896" />
+<glyph glyph-name="arrow-down" unicode="&#xf03f;" d="M448 384V640H192v-256H0l320-384 320 384H448z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-left" unicode="&#xf040;" d="M384 448V640L0 320l384-320V192h256V448H384z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-right" unicode="&#xf03e;" d="M640 320L256 640v-192H0v-256h256v-192L640 320z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-small-down" unicode="&#xf0a0;" d="M256 384V512H128v-128H0l192-256 192 256H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-left" unicode="&#xf0a1;" d="M256 384V512L0 320l256-192V256h128V384H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-right" unicode="&#xf071;" d="M384 320L128 512v-128H0v-128h128v-128L384 320z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-up" unicode="&#xf09f;" d="M192 512L0 256h128v-128h128V256h128L192 512z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-up" unicode="&#xf03d;" d="M320 640L0 256h192v-256h256V256h192L320 640z" horiz-adv-x="640" />
+<glyph glyph-name="beer" unicode="&#xf069;" d="M896 576c-31 0-192 0-192 0v128c0 71-158 128-352 128s-352-57-352-128v-768c0-71 158-128 352-128s352 57 352 128v128s160 0 192 0 64 30 64 64 0 350 0 384-29 64-64 64z m-704-576h-64v512h64v-512z m192-64h-64v512h64v-512z m192 64h-64v512h64v-512z m-224 640c-124 0-224 29-224 64s100 64 224 64 224-29 224-64-100-64-224-64z m480-448h-128v256h128v-256z" horiz-adv-x="1024" />
+<glyph glyph-name="book" unicode="&#xf007;" d="M768 256h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-55 416c-167 0-209-32-233-56-24 24-66 56-233 56s-247-46-247-78v-586c29 16 119 48 214 56 115 9 234-9 234-32 0-16 8-31 31-32 0 0 0 0 1 0 0 0 0 0 1 0 23 1 31 16 31 32 0 23 119 41 234 32 94-7 185-40 214-56v586c0 32-80 78-247 78z m-265-572c-30 16-103 28-192 28s-170-12-192-27c0 0 0 411 0 443s64 59 192 59 192-27 192-59 0-444 0-444z m448 1c-22 15-103 27-192 27s-162-12-192-28c0 0 0 412 0 444s64 59 192 59 192-27 192-59 0-443 0-443z m-128 283h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m0 128h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-448-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0 256h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z" horiz-adv-x="1024" />
+<glyph glyph-name="bookmark" unicode="&#xf07b;" d="M0 704v-768l192 128 192-128V704H0zM316.25 507.25l-71.875-51.938 27.188-83.406c2.75-8.375-0.688-11.062-7.562-6.594l-72 52.094-72-52.031c-6.844-4.469-10.312-1.781-7.562 6.594l27.219 83.406L67.783 507.25c-6.469 5.125-5 9.219 3.906 9.219l88 0.125 27.125 83.094c2.812 8.812 7.562 8.812 10.375 0l27.188-83.094 87.938-0.125C321.25 516.469 322.688 512.375 316.25 507.25z" horiz-adv-x="384" />
+<glyph glyph-name="briefcase" unicode="&#xf0d3;" d="M896 640H640v66c0 34.2-27.8 62-62 62H446c-34.2 0-62-27.8-62-62v-66H128c-35.3 0-64-28.7-64-64v-512c0-35.3 28.7-64 64-64h768c35.3 0 64 28.7 64 64V576C960 611.3 931.3 640 896 640zM448 688c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16v-48H448V688zM896 320H576v-64H448v64H128V576h64v-192h640V576h64V320z" horiz-adv-x="1024" />
+<glyph glyph-name="broadcast" unicode="&#xf048;" d="M448 640c142 0 256-115 256-256 0-69-28-132-72-178l-16-93c91 56 152 156 152 271 0 177-143 320-320 320s-320-143-320-320c0-115 61-215 152-271l-16 93c-45 46-72 109-72 178 0 142 114 256 256 256z m-64-320c-36 0-64-29-64-64v-128c0-36 30-64 64-64v-256h128v256c34 0 64 28 64 64v128c0 35-28 64-64 64s-64 0-64 0-28 0-64 0z m192 128c0 71-57 128-128 128s-128-57-128-128 57-128 128-128 128 57 128 128z m-128 384c-247 0-448-201-448-448 0-197 128-363 305-423l-12 72c-135 60-229 194-229 351 0 212 172 384 384 384s384-172 384-384c0-157-94-291-229-351l-12-72c177 60 305 225 305 423 0 247-201 448-448 448z" horiz-adv-x="896" />
+<glyph glyph-name="browser" unicode="&#xf0c5;" d="M320 640h64v-64h-64V640zM192 640h64v-64h-64V640zM64 640h64v-64H64V640zM832 0H64V512h768V0zM832 576H448v64h384V576zM896 640c0 35.35-28.65 64-64 64H64c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V640z" horiz-adv-x="896" />
+<glyph glyph-name="bug" unicode="&#xf091;" d="M243.621 675.469C190.747 618.688 205.34 528 205.34 528s53.968-64 160-64c106.031 0 160.031 64 160.031 64s14.375 89.469-37.375 146.312c32.375 18.031 51.438 44.094 43.562 61.812-8.938 19.969-48.375 21.75-88.25 3.969-14.812-6.594-27.438-14.969-37.25-23.875-12.438 2.25-25.625 3.781-40.72 3.781-14.061 0-26.561-1.344-38.344-3.25-9.656 8.75-22.062 16.875-36.531 23.344-39.875 17.719-79.375 15.938-88.25-3.969C194.465 718.781 212.497 693.438 243.621 675.469zM644.746 262.25c-8.25 1.75-16.125 2.75-23.75 3.5 0 2.125 0.375 4.125 0.375 6.312 0 33.594-4.75 65.654-12.438 96.125 16.438-1.406 37.375 2.375 58.562 11.779 39.875 17.781 65 48.375 56.125 68.219-8.875 19.969-48.375 21.75-88.25 3.969-18.625-8.312-33.812-19.469-44-30.906-7.75 18.25-16.5 35.781-26.812 51.719-30.188-25.156-87.312-62.719-167.062-71.062v-321.781c0 0-0.25-32-32.031-32-31.75 0-32 32-32 32V401.781c-79.811 8.344-136.968 45.969-167.093 71.062-9.875-15.312-18.375-32-25.938-49.344-10.281 10.625-24.625 20.844-41.969 28.594-39.875 17.719-79.375 15.938-88.25-3.969-8.906-19.906 16.25-50.438 56.125-68.219 19.844-8.846 39.531-12.812 55.469-12.096-7.656-30.404-12.469-62.344-12.469-95.812 0-2.188 0.375-4.25 0.438-6.5-6.719-0.75-13.688-1.75-20.781-3.25-51.969-10.75-91.781-37.625-88.844-59.812 2.938-22.312 47.5-31.5 99.594-20.688 6.781 1.375 13.438 3.125 19.781 5.062C128.684 146 143.34 108.125 163.622 75.5c-12.031-6.062-24.531-15-36.031-26.625C95.715 17 82.779-21.75 98.715-37.68799999999999c15.938-15.937 54.656-3 86.531 28.812 9.344 9.375 16.844 19.25 22.656 29C251.434-22.5 305.965-48 365.465-48c60.343 0 115.781 26.25 159.531 69.938 5.875-10.312 13.75-20.812 23.625-30.688 31.812-31.875 70.625-44.812 86.562-28.875s3 54.625-28.875 86.5c-12.312 12.375-25.688 21.75-38.438 27.938 20.125 32.5 34.625 70.375 43.688 111.062 7.188-2.25 14.688-4.375 22.562-6.062 52.061-10.812 96.625-1.562 99.625 20.688C736.558 224.625 696.746 251.5 644.746 262.25z" horiz-adv-x="733.886" />
+<glyph glyph-name="calendar" unicode="&#xf068;" d="M704 320h-64v-128h64V320zM576 320h-64v-128h64V320zM704 512h-64v-128h64V512zM832 320h-64v-128h64V320zM576 128h-64v-128h64V128zM768 832h-64v-128h64V832zM256 832h-64v-128h64V832zM832 512h-64v-128h64V512zM576 512h-64v-128h64V512zM320 128h-64v-128h64V128zM192 320h-64v-128h64V320zM320 320h-64v-128h64V320zM832 768v-128H640V768H320v-128H128V768H0v-896h960V768H832zM896-64H64V576h832V-64zM192 128h-64v-128h64V128zM448 512h-64v-128h64V512zM448 128h-64v-128h64V128zM320 512h-64v-128h64V512zM448 320h-64v-128h64V320zM704 128h-64v-128h64V128z" horiz-adv-x="1024" />
+<glyph glyph-name="check" unicode="&#xf03a;" d="M640 640L256 256 128 384 0 256l256-256 512 512L640 640z" horiz-adv-x="768" />
+<glyph glyph-name="checklist" unicode="&#xf076;" d="M760.688 315.78099999999995l-49.812 49.656c-6.438 6.529-16.938 6.594-23.375 0L582.5 260.5 462.375 140.125l-93.031 93.125c-6.531 6.562-17.031 6.562-23.5 0l-49.719-49.688c-6.531-6.562-6.531-17.062 0-23.562l104.781-104.875 17.969-17.875 31.688-31.812c6.562-6.562 17.188-6.562 23.562 0l49.625 49.688L760.625 292.22C767.25 298.688 767.25 309.188 760.688 315.78099999999995zM228.469 251.188L278.156 301c42.469 42.375 116.344 42.438 158.781-0.062l25.312-25.312L576 384V704H0v-704h320l-91.531 92.125C184.688 136.062 184.688 207.375 228.469 251.188zM192 640h320v-64H192V640zM192 512h320v-64H192V512zM128 320H64v64h64V320zM128 448H64v64h64V448zM128 576H64v64h64V576zM192 384h64v-64h-64V384z" horiz-adv-x="765.602" />
+<glyph glyph-name="chevron-down" unicode="&#xf0a3;" d="M512 512L320 320 128 512 0 384l320-320 320 320L512 512z" horiz-adv-x="640" />
+<glyph glyph-name="chevron-left" unicode="&#xf0a4;" d="M448 512L320 640 0 320l320-320 128 128L256 320 448 512z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-right" unicode="&#xf078;" d="M128 640L0 512l192-192L0 128l128-128 320 320L128 640z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-up" unicode="&#xf0a2;" d="M320 576L0 256l128-128 192 192 192-192 128 128L320 576z" horiz-adv-x="640" />
+<glyph glyph-name="circle-slash" unicode="&#xf084;" d="M320 640C143.219 640 0 496.781 0 320c0-176.75 143.219-320 320-320 176.75 0 320 143.25 320 320C640 496.781 496.75 640 320 640zM320 512c27.656 0 53.688-6.094 77.438-16.562L144.562 242.562C134.094 266.312 128 292.34400000000005 128 320 128 426 213.938 512 320 512zM320 128c-28.031 0-54.531 6.375-78.594 17.125l253.906 252.5C505.875 373.812 512 347.719 512 320 512 213.938 426.062 128 320 128z" horiz-adv-x="640" />
+<glyph glyph-name="circuit-board" unicode="&#xf0d6;" d="M320 576c35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64s-64 28.654-64 64C256 547.346 284.654 576 320 576zM960 64c0-106.039-85.961-192-192-192H320l192 192h81.128c22.132-38.258 63.494-64 110.872-64 70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.377 0-88.74-25.742-110.872-64H448L156.044-99.95600000000002C100.845-66.23199999999997 64-5.419999999999959 64 64V576c0 106.039 85.961 192 192 192v-145.128C217.742 600.74 192 559.377 192 512c0-70.692 57.308-128 128-128 47.276 0 88.56 25.633 110.727 63.756l162.416 0.219C615.279 409.731 656.633 384 704 384c70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.388 0-88.758-25.753-110.887-64.025l-162.097-0.219c-11.246 19.54-27.503 35.828-47.016 47.116V768h384c106.039 0 192-85.961 192-192V64zM640 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 92.654 640 128zM640 512c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 476.654 640 512z" horiz-adv-x="1024" />
+<glyph glyph-name="clippy" unicode="&#xf035;" d="M704-64h-640v576h640v-192h64v320c0 35-29 64-64 64h-192c0 71-57 128-128 128s-128-57-128-128h-192c-35 0-64-29-64-64v-704c0-35 29-64 64-64h640c35 0 64 29 64 64v128h-64v-128z m-512 704c29 0 29 0 64 0s64 29 64 64 29 64 64 64 64-29 64-64 32-64 64-64 33 0 64 0 64-29 64-64h-512c0 39 28 64 64 64z m-64-512h128v64h-128v-64z m448 128v128l-256-192 256-192v128h320v128h-320z m-448-256h192v64h-192v-64z m320 448h-320v-64h320v64z m-192-128h-128v-64h128v64z" horiz-adv-x="896" />
+<glyph glyph-name="clock" unicode="&#xf046;" d="M384 256h256l64 64-64 64H512V576l-64 64-64-64V256zM448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.25 0-320 143.75-320 320 0 175.938 144.188 319.5 320 320 175.812-0.5 320-144.062 320-320C768 143.75 624.25 0 448 0z" horiz-adv-x="896" />
+<glyph glyph-name="cloud-download" unicode="&#xf00b;" d="M832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.75 644.125 704 512 704c-132.156 0-245.562-80.25-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426.062 0 320s85.938-192 192-192c20.531 0 39.875 4.25 58.375 10.375C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.875C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.094C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.625 156.938 64.438C830 128.375 830.875 128 832 128c106.062 0 192 85.938 192 192S938.062 512 832 512zM576 320H448v-320H320l192-192 192 192H576V320z" horiz-adv-x="1024" />
+<glyph glyph-name="cloud-upload" unicode="&#xf00c;" d="M512 448L320 256h128v-320h128V256h128L512 448zM832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.812 644.125 704 512 704c-132.156 0-245.562-80.188-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426 0 320c0-106.062 85.938-192 192-192 20.531 0 39.875 4.25 58.375 10.438C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.75C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.156C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.688 157 64.438 1 0 1.875-0.438 3-0.438 106.062 0 192 85.938 192 192C1024 426 938.062 512 832 512z" horiz-adv-x="1024" />
+<glyph glyph-name="code" unicode="&#xf05f;" d="M608 640l-96-96 224-224L512 96l96-96 288 320L608 640zM288 640L0 320l288-320 96 96L160 320l224 224L288 640z" horiz-adv-x="896" />
+<glyph glyph-name="color-mode" unicode="&#xf065;" d="M0 704v-768h768V704H0zM64 0V640h640L64 0z" horiz-adv-x="768" />
+<glyph glyph-name="comment" unicode="&#xf02b;" d="M768 704H128C66 704 0 640 0 576v-384c0-128 128-128 128-128h64v-256l256 256c0 0 258 0 320 0s128 68 128 128V576C896 638 832 704 768 704z" horiz-adv-x="896" />
+<glyph glyph-name="comment-discussion" unicode="&#xf04f;" d="M256 320c0 64 0 192 0 192s-160 0-192 0-64-32-64-64 0-288 0-320 32-64 64-64 64 0 64 0v-192l194 192s162 0 192 0 62 32 62 64 0 64 0 64-128 0-192 0-128 64-128 128z m576 384c-32 0-416 0-448 0s-64-32-64-64 0-288 0-320 32-64 64-64 190 0 190 0l194-192v192s32 0 64 0 64 32 64 64 0 288 0 320-32 64-64 64z" horiz-adv-x="896" />
+<glyph glyph-name="credit-card" unicode="&#xf045;" d="M128 128h128v64h-128v-64z m192 0h128v64h-128v-64z m64 192h-256v-64h256v64z m-128 64h64l128 128h-64l-128-128z m192-128h192v64h-192v-64z m512 384c-32 0-864 0-896 0s-64-32-64-64 0-480 0-512 32-64 64-64 864 0 896 0 64 32 64 64 0 480 0 512-32 64-64 64z m0-256v-288s0-32-32-32h-832c-32 0-32 32-32 32v288h64l128 128h-192v32s0 32 32 32h832c32 0 32-32 32-32v-32h-384l-128-128h512z" horiz-adv-x="1024" />
+<glyph glyph-name="dash" unicode="&#xf0ca;" d="M0 384v-128h512V384H0z" horiz-adv-x="512" />
+<glyph glyph-name="dashboard" unicode="&#xf07d;" d="M416 367.5c-61.562 0-111.5-49.938-111.5-111.5S354.438 144.5 416 144.5 527.5 194.438 527.5 256c0 8.5-1.125 16.75-3 24.688C606.125 375.625 732.5 523.656 800 608c23.125 28.875-2.312 56.188-32 32-85.188-69.375-232.312-194.688-326.906-275.594C433.031 366.281 424.625 367.5 416 367.5zM447.875 576.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S447.875 558.469 447.875 576.125zM639.875 320.125c0-17.656 14.375-32 32-32s32 14.344 32 32-14.375 32-32 32S639.875 337.781 639.875 320.125zM287.875 576.125c-17.656 0-32-14.344-32-32s14.344-32 32-32 32 14.344 32 32S305.531 576.125 287.875 576.125zM223.875 448.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S223.875 430.469 223.875 448.125zM127.875 320.125c0-17.656 14.344-32 32-32s32 14.344 32 32-14.344 32-32 32S127.875 337.781 127.875 320.125zM575.875 544.125c0 17.656-14.375 32-32 32s-32-14.344-32-32 14.375-32 32-32S575.875 526.469 575.875 544.125zM792.875 495.312l-68.75-89.938C731.625 378.188 736 349.625 736 320c0-176.75-143.312-320-320-320S96 143.25 96 320c0 176.688 143.312 320 320 320 65.875 0 127-19.969 177.875-54.094l79.25 60.625C602.375 702.406 513.25 736 416 736 186.25 736 0 549.75 0 320s186.25-416 416-416 416 186.25 416 416C832 382.719 817.75 442 792.875 495.312z" horiz-adv-x="832" />
+<glyph glyph-name="database" unicode="&#xf096;" d="M384-128C171.969-128 0-70.625 0 0c0 38.625 0 80.875 0 128 0 11.125 5.562 21.688 13.562 32C56.375 104.875 205.25 64 384 64s327.625 40.875 370.438 96c8-10.312 13.562-20.875 13.562-32 0-37.062 0-76.375 0-128C768-70.625 596-128 384-128zM384 128C171.969 128 0 185.375 0 256c0 38.656 0 80.844 0 128 0 6.781 2.562 13.375 6 19.906l0 0C7.938 408 10.5 412.031 13.562 416 56.375 360.906 205.25 320 384 320s327.625 40.906 370.438 96c3.062-3.969 5.625-8 7.562-12.094l0 0c3.438-6.531 6-13.125 6-19.906 0-37.062 0-76.344 0-128C768 185.375 596 128 384 128zM384 384C171.969 384 0 441.344 0 512c0 20.219 0 41.594 0 64 0 20.344 0 41.469 0 64C0 710.656 171.969 768 384 768c212 0 384-57.344 384-128 0-19.969 0-41.156 0-64 0-19.594 0-40.25 0-64C768 441.344 596 384 384 384zM384 704c-141.375 0-256-28.594-256-64s114.625-64 256-64 256 28.594 256 64S525.375 704 384 704z" horiz-adv-x="768" />
+<glyph glyph-name="device-camera" unicode="&#xf056;" d="M512 447.999c-70.691 0-127.999-57.308-127.999-127.999S441.309 192.00099999999998 512 192.00099999999998c5.713 0 11.337 0.38 16.852 1.105-46.344 7.058-81.851 47.079-81.851 95.394 0 53.295 43.204 96.499 96.499 96.499 48.314 0 88.336-35.507 95.394-81.851 0.726 5.515 1.105 11.139 1.105 16.852C639.999 390.691 582.691 447.999 512 447.999zM896 576H767.999L640 704H384L255.999 576H128c-35.348 0-64-28.652-64-64v-448c0-35.347 28.652-64 64-64h768c35.347 0 64 28.653 64 64V512C960 547.348 931.347 576 896 576zM416 640h192l64-64H352L416 640zM160.143 64C142.391 64 128 78.39099999999996 128 96.14300000000003V384h64v64h-64v31.857C128 497.609 142.391 512 160.143 512h182.526c-3.98-3.518-7.881-7.174-11.688-10.98-99.974-99.975-99.974-262.064 0-362.039l74.98-74.98H160.143zM512 128.00099999999998c-106.038 0-191.999 85.961-191.999 191.999S405.962 511.999 512 511.999 703.999 426.038 703.999 320 618.038 128.00099999999998 512 128.00099999999998zM832 352L681.327 512H832V352z" horiz-adv-x="1024" />
+<glyph glyph-name="device-camera-video" unicode="&#xf057;" d="M576 640c-35.347 0-64-28.653-64-64s28.653-64 64-64 64 28.653 64 64S611.347 640 576 640zM896 448L768 320v64c0 30.625-21.515 56.21-50.25 62.503C748.958 480.646 768 526.097 768 575.998 768 682.038 682.039 768 576 768c-101.123 0-183.986-78.178-191.45-177.393C350.516 621.306 305.442 640 256 640c-106.038 0-192-85.962-192-192.002C64 341.961 149.962 256 256 256h-64v-128h64v-128c0-35.347 28.653-64 64-64h384c35.347 0 64 28.653 64 64v64l128-128h64V448H896zM256 512c-35.347 0-64-28.653-64-64s28.653-64 64-64v-64c-70.692 0-128 57.308-128 127.999C128 518.692 185.308 576 256 576s128-57.307 128-128h-64C320 483.347 291.347 512 256 512zM576 128H448V256h128V128zM704 237.21299999999997c-33.526 33.547-70.276 70.317-73.373 73.414C624.837 316.418 616.837 320 608 320H416c-17.674 0-32-14.326-32-32v-192c0-8.329 3.183-15.915 8.396-21.607 0.53-0.58 39.123-39.164 74.409-74.393H352c-17.674 0-32 14.326-32 32V352c0 17.674 14.326 32 32 32h320c17.674 0 32-14.326 32-32V237.21299999999997zM576 448c-70.692 0-128 57.308-128 127.999C448 646.692 505.308 704 576 704s128-57.308 128-128.001C704 505.308 646.692 448 576 448zM896 128l-64 64 0.082 128.084L896 384.002V128z" horiz-adv-x="1024" />
+<glyph glyph-name="device-desktop" unicode="&#xf27c;" d="M960 768c-32 0-864 0-896 0s-64-32-64-64 0-544 0-576 32-64 64-64 320 0 320 0-192-64-192-128c0-32 32-64 64-64s480 0 512 0 64 32 64 64c0 64-192 128-192 128s288 0 320 0 64 32 64 64 0 544 0 576-32 64-64 64z m0-640h-896v576h896v-576z m-64 512h-192c-384-64-542-300-576-384v-64h768v448z" horiz-adv-x="1024" />
+<glyph glyph-name="device-mobile" unicode="&#xf038;" d="M576 832H64C28.688 832 0 803.312 0 768v-896c0-35.375 28.688-64 64-64h512c35.375 0 64 28.625 64 64V768C640 803.312 611.375 832 576 832zM288 768h64c17.625 0 32-14.344 32-32s-14.375-32-32-32h-64c-17.656 0-32 14.344-32 32S270.344 768 288 768zM352-128h-64c-17.656 0-32 14.375-32 32s14.344 32 32 32h64c17.625 0 32-14.375 32-32S369.625-128 352-128zM576 0H64V640h512V0z" horiz-adv-x="640" />
+<glyph glyph-name="diff" unicode="&#xf04d;" d="M448 576H320v-128H192v-128h128v-128h128V320h128V448H448V576zM192-64h384V64H192V-64zM640 832H128v-64h480l224-224v-608h64V576L640 832zM0 704v-896h768V512L576 704H0zM704-128H64V640h480l160-160V-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-added" unicode="&#xf06b;" d="M512 512h-128v-128h-128v-128h128v-128h128v128h128v128h-128v128z m320 256c-32 0-736 0-768 0s-64-32-64-64 0-736 0-768 32-64 64-64 736 0 768 0 64 32 64 64 0 736 0 768-32 64-64 64z m-64-736c0-16-17-32-32-32s-558 0-576 0-32 12-32 32c0 16 0 560 0 576s16 32 32 32 561 0 576 0 32-16 32-32 0-560 0-576z" horiz-adv-x="896" />
+<glyph glyph-name="diff-ignored" unicode="&#xf099;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 194v-98h98l286 286v98h-98l-286-286z" horiz-adv-x="896" />
+<glyph glyph-name="diff-modified" unicode="&#xf06d;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 416c-71 0-128-57-128-128s57-128 128-128 128 57 128 128-57 128-128 128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-removed" unicode="&#xf06c;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 224h384v128h-384v-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-renamed" unicode="&#xf06e;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 352h-192v-128h192v-128l256 192-256 192v-128z" horiz-adv-x="896" />
+<glyph glyph-name="ellipsis" unicode="&#xf09a;" d="M640 512c-64 0-448 0-512 0s-128-64-128-128 0-64 0-128 64-128 128-128 448 0 512 0 128 64 128 128 0 64 0 128-64 128-128 128z m-384-256h-128v128h128v-128z m192 0h-128v128h128v-128z m192 0h-128v128h128v-128z" horiz-adv-x="768" />
+<glyph glyph-name="eye" unicode="&#xf04e;" d="M512 704c-192 0-416-128-512-384 96-192 288-320 512-320s416 128 512 320c-96 256-320 384-512 384z m0-640c-192 0-352 128-384 256 32 128 192 256 384 256s352-128 384-256c-32-128-192-256-384-256z m0 448c-20 0-38-4-56-9 33-15 56-48 56-87 0-53-43-96-96-96-39 0-72 23-87 56-5-18-9-36-9-56 0-106 86-192 192-192s192 86 192 192-86 192-192 192z" horiz-adv-x="1024" />
+<glyph glyph-name="file-binary" unicode="&#xf094;" d="M0-128V768h576l192-192v-704H0zM704 512L512 704H64v-768h640V512zM320 320H128V576h192V320zM256 512h-64v-128h64V512zM256 64h64v-64H128v64h64V192h-64v64h128V64zM512 384h64v-64H384v64h64V512h-64v64h128V384zM576 0H384V256h192V0zM512 192h-64v-128h64V192z" horiz-adv-x="768" />
+<glyph glyph-name="file-code" unicode="&#xf010;" d="M288 448L128 288l160-160 64 64-96 96 96 96L288 448zM416 384l96-96-96-96 64-64 160 160L480 448 416 384zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64z" horiz-adv-x="768" />
+<glyph glyph-name="file-directory" unicode="&#xf016;" d="M832 640c-32 0-336 0-352 0s-32 16-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-704 0-704h896s0 544 0 576-32 64-64 64z m-448 0h-320s0 15 0 32 16 32 32 32 241 0 256 0 32-15 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-media" unicode="&#xf012;" d="M576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 576v-512h128c0 70.625 57.344 128 128 128-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128 70.625 0 128-57.375 128-128h128V448L512 576H128z" horiz-adv-x="768" />
+<glyph glyph-name="file-pdf" unicode="&#xf014;" d="M576 768H0v-896h768V576L576 768zM64 704h255.812c-13.188-4.094-27.281-15.031-34.625-42.875-13.25-49.406-7.031-130.75 15.625-209.344C276.688 370.562 178.188 175.125 171.531 163.5c-15.625-4.875-65.344-23.625-107.531-59.812V704zM347.125 396.531c57.625-149.781 95-149.531 135.188-167.594C398.344 216 334.219 206.75 249.781 169.5 246.094 163.062 326.281 315.40599999999995 347.125 396.531zM704-64H65.844 64v0.375c0.781-0.062 1.094-0.375 1.844-0.375 33.812 0 84.75 21 180.562 182.375 38.188 15.438 72.062 26.875 78.469 28.938 58.812 14.875 125 26.625 187.562 33.375C566.875 153.5 639.125 135 680.25 132.375c9.625-0.5 16.062 1.188 23.75 2V-64zM704 246.625c-23.688 14.688-54 25-89.125 25-24.25 0-50.625-1.375-78.688-4.375-26.938 13-92.562 32.719-147.188 190.219 17.094 103.625 12.719 173.562 12.719 173.562 6.781 52.938-23.344 72.844-51.625 72.844 0 0-0.279 0.125-0.344 0.125H512l192-192V246.625z" horiz-adv-x="768" />
+<glyph glyph-name="file-submodule" unicode="&#xf017;" d="M832 320c-32 0-192 0-192 0 0 32-32 64-64 64s-96 0-128 0-64-32-64-64 0-320 0-320h512s0 224 0 256-32 64-64 64z m-256-64h-128s0 17 0 32 15 32 32 32 48 0 64 0 32-15 32-32 0-32 0-32z m256 320c-32 0-336 0-352 0s-32 17-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-640 0-640h320s0 352 0 384 32 64 64 64 224 0 256 0 64-32 64-64h192s0 96 0 128-32 64-64 64z m-448 0h-320s0 16 0 32 16 32 32 32 240 0 256 0 32-17 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-directory" unicode="&#xf0b1;" d="M832 640h-352c-16 0-32 16-32 32s0 0 0 32-32 64-64 64h-320c-32 0-64-32-64-64s0-704 0-704h896s0 544 0 576-32 64-64 64z m-768 32c0 17 16 32 32 32h256c15 0 32-15 32-32s0-32 0-32h-320s0 15 0 32z m384-544v128c-125 0-224-56-256-192 0 209 107 320 256 320 0 49 0 128 0 128l256-192-256-192z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-file" unicode="&#xf0b0;" d="M576 768h-576v-896h768v704l-192 192z m128-832h-640v768h448l192-192v-576z m-320 448c-149 0-256-111-256-320 32 136 131 192 256 192v-128l256 192-256 192s0-79 0-128z" horiz-adv-x="768" />
+<glyph glyph-name="file-text" unicode="&#xf011;" d="M448 576H128v-64h320V576zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 64h512v64H128V64zM128 192h512v64H128V192zM128 320h512v64H128V320z" horiz-adv-x="768" />
+<glyph glyph-name="file-zip" unicode="&#xf013;" d="M320 256v64h-64v-64H320zM320 384v64h-64v-64H320zM320 512v64h-64v-64H320zM192 448h64v64h-64V448zM576 768H0v-896h768V576L576 768zM704-64H64V704h192v-64h64v64h192l192-192V-64zM192 576h64v64h-64V576zM192 320h64v64h-64V320zM192 192l-64-64v-128h256V128l-64 64h-64v64h-64V192zM320 128v-64H192v64H320z" horiz-adv-x="768" />
+<glyph glyph-name="flame" unicode="&#xf0d2;" d="M433 787c50-134 24-207-32-265-61-64-156-112-223-206-89-125-104-400 217-472-135 71-164 277-18 406-38-125 32-205 119-176 85 29 141-32 139-102-1-48-20-89-69-112 209 37 293 210 293 342 0 174-155 198-77 344-93-8-125-69-116-169 6-66-63-111-114-81-41 25-40 73-4 109 77 76 107 251-115 382z" horiz-adv-x="1024" />
+<glyph glyph-name="fold" unicode="&#xf0cc;" d="M896 576H672l-64-64h192L672 384H224L96 512h192l-64 64H0v-63.999L160 352 0 192v-64h224l64 64H96l128 128h448l128-128H608l64-64h224v64L736 352l160 160.001V576zM640 640H512V832H384v-192H256l192-192L640 640zM256 64h128v-192h128V64h128L448 256 256 64z" horiz-adv-x="896" />
+<glyph glyph-name="gear" unicode="&#xf02f;" d="M447.938 482C358.531 482 286 409.469 286 320c0-89.375 72.531-162.062 161.938-162.062 89.438 0 161.438 72.688 161.438 162.062C609.375 409.469 537.375 482 447.938 482zM772.625 226.938l-29.188-70.312 52.062-102.25 6.875-13.5-72.188-72.188L611.75 24.625l-70.312-28.875L505.75-113.5l-4.562-14.5H399.156L355-4.687999999999988l-70.312 29-102.404-51.938-13.5-6.75-72.156 72.125 55.875 118.5-28.969 70.25L14.469 262.125 0 266.812V368.781L123.406 413l28.969 70.188-51.906 102.469-6.844 13.438 72.062 72.062 118.594-55.844 70.219 29.031 35.656 109.188L394.75 768h102l44.188-123.469 70.125-29.031L713.5 667.469l13.625 6.844 72.125-72.062-55.875-118.406L772.25 413.5l109.375-35.656L896 373.25v-101.938L772.625 226.938z" horiz-adv-x="896" />
+<glyph glyph-name="gift" unicode="&#xf042;" d="M448-128h320V192H448V-128zM64-128h320V192H64V-128zM447.75 455.812c31.469 3.5 66.875 7.406 87.375 9.719C619 474.875 694.5 550.406 703.812 634.25c9.312 83.75-51 144.125-134.688 134.719C503.688 761.656 443.844 714 416 653.625 388.156 714 328.312 761.656 262.906 769.031 179.188 778.375 118.781 718 128.188 634.25c9.344-83.844 84.875-159.312 168.656-168.719 20.531-2.312 55.938-6.281 87.406-9.719C383.75 451.594 384 448 384 448h64C448 448 448.25 451.594 447.75 455.812zM555.375 691.312c45.25 5.062 78-27.562 72.875-72.875-5-45.312-45.875-86.156-91.125-91.219-45.375-5.031-78 27.594-72.938 72.906C469.249 645.436 510.125 686.281 555.375 691.312zM294.906 527.219c-45.25 5.062-86.062 45.906-91.125 91.219-5.063 45.313 27.594 77.938 72.812 72.875 45.312-5.031 86.156-45.875 91.222-91.188C372.875 554.812 340.219 522.188 294.906 527.219zM448 448v-192h384V448H448zM0 256h384V448H0V256z" horiz-adv-x="896" />
+<glyph glyph-name="gist" unicode="&#xf00e;" d="M416 448l96-96-96-96 64-64 160 160-160 160-64-64z m-416 320v-832h768v832h-768z m704-768h-640v704h640v-704z m-352 256l-96 96 96 96-64 64-160-160 160-160 64 64z" horiz-adv-x="768" />
+<glyph glyph-name="gist-secret" unicode="&#xf08c;" d="M193 128l128-192h-256l-65 256 257 64-64-128z m448 128l64-128-128-192h256l64 256-256 64z m-84 0h-216l44-102-64-218h256l-64 218 44 102z m84 192h-384l-128-64h640l-128 64z m-64 256l-128-64-128 64-64-192h384l-64 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-branch" unicode="&#xf020;" d="M512 640c-71 0-128-57-128-128 0-47 26-88 64-110v-18c0-64-64-128-128-128-53 0-95-11-128-29v303c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 34-13 64-34 87 19 23 49 41 98 41 128 0 256 128 256 256v18c38 22 64 63 64 110 0 71-57 128-128 128z m-384 64c35 0 64-29 64-64s-29-64-64-64-64 29-64 64 29 64 64 64z m0-768c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m384 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="640" />
+<glyph glyph-name="git-commit" unicode="&#xf01f;" d="M694.875 384C666.375 494.219 567.125 576 448 576c-119.094 0-218.375-81.781-246.906-192H0v-128h201.094C229.625 145.75 328.906 64 448 64c119.125 0 218.375 81.75 246.875 192H896V384H694.875zM448 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128C576 249.375 518.625 192 448 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-compare" unicode="&#xf0ac;" d="M832 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-448 128s-32 0-64 0-64 32-64 64 0 402 0 402c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110 0 0 0-306 0-402s96-192 192-192c64 0 64 0 64 0v-128l192 192-192 192v-128z m-192 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="896" />
+<glyph glyph-name="git-merge" unicode="&#xf023;" d="M640 384c-47.625 0-88.625-26.312-110.625-64.906C523.625 319.5 518 320 512 320c-131.062 0-255.438 99.844-300.812 223.438C238.469 566.906 256 601.281 256 640c0 70.656-57.344 128-128 128S0 710.656 0 640c0-47.219 25.844-88.062 64-110.281V110.25C25.844 88.06200000000001 0 47.25 0 0c0-70.625 57.344-128 128-128s128 57.375 128 128c0 47.25-25.844 88.062-64 110.25V340.531C276.156 251.5 392.375 192 512 192c6.375 0 11.625 0.438 17.375 0.625C551.5 154.188 592.5 128 640 128c70.625 0 128 57.375 128 128C768 326.656 710.625 384 640 384zM128-64c-35.312 0-64 28.625-64 64 0 35.312 28.688 64 64 64 35.406 0 64-28.688 64-64C192-35.375 163.406-64 128-64zM128 576c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S163.406 576 128 576zM640 192c-35.312 0-64 28.625-64 64 0 35.406 28.688 64 64 64 35.375 0 64-28.594 64-64C704 220.625 675.375 192 640 192z" horiz-adv-x="768" />
+<glyph glyph-name="git-pull-request" unicode="&#xf009;" d="M704 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-512 832c-71 0-128-57-128-128 0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110v419c38 22 64 63 64 110 0 71-57 128-128 128z m0-832c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m0 640c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="768" />
+<glyph glyph-name="globe" unicode="&#xf0b6;" d="M512 704c-212.077 0-384-171.923-384-384s171.923-384 384-384c25.953 0 51.303 2.582 75.812 7.49-9.879 4.725-10.957 40.174-1.188 60.385 10.875 22.5 45 79.5 11.25 98.625s-24.375 27.75-45 49.875-12.19 25.451-13.5 31.125c-4.5 19.5 19.875 48.75 21 51.75s1.125 14.25 0.75 17.625S545.75 265.25 542 265.625s-5.625-6-10.875-6.375-28.125 13.875-33 17.625-7.125 12.75-13.875 19.5-7.5 1.5-18 5.625-44.25 16.5-70.125 27-28.125 25.219-28.5 35.625-15.75 25.5-22.961 36.375c-7.209 10.875-8.539 25.875-11.164 22.5s13.5-42.75 10.875-43.875-8.25 10.875-15.75 20.625 7.875 4.5-16.125 51.75 7.5 71.344 9 96 20.25-9 10.5 6.75 0.75 48.75-6.75 60.75S275 602 275 602c1.125 11.625 37.5 31.5 63.75 49.875s42.281 4.125 63.375-2.625 22.5-4.5 15.375 2.25 3 10.125 19.5 7.5 21-22.5 46.125-20.625 2.625-4.875 6-11.25-3.75-5.625-20.25-16.875S469.25 599 498.5 577.625s20.25 14.25 17.25 30S537.125 611 537.125 611c18-12 14.674-0.66 27.799-4.785S613.625 572 613.625 572c-44.625-24.375-16.5-27-9-32.625s-15.375-16.5-15.375-16.5c-9.375 9.375-10.875-0.375-16.875-3.75s-0.375-12-0.375-12c-31.031-4.875-24-37.5-23.625-45.375s-19.875-19.875-25.125-31.125S536.75 395 527 393.5s-19.5 36.75-72 22.5c-15.828-4.297-51-22.5-32.25-59.625s49.875 10.5 60.375 5.25-3-28.875-0.75-29.25 29.625-1.031 31.125-33 41.625-29.25 50.25-30 37.5 23.625 41.625 24.75S626 309.125 662 288.5s54.375-17.625 66.75-26.25 3.75-25.875 15.375-31.5 58.125 1.875 69.75-17.25-48-115.125-66.75-125.625S719.75 53.375 701 38s-45-34.406-69.75-49.125c-21.908-13.027-25.85-36.365-35.609-43.732C767.496-16.67999999999995 896 136.64999999999998 896 320 896 532.077 724.077 704 512 704zM602 343.625c-5.25-1.5-16.125-11.25-42.75 4.5s-45 12.75-47.25 15.375c0 0-2.25 6.375 9.375 7.5 23.871 2.311 54-22.125 60.75-22.5s10.125 6.75 22.125 2.883C616.25 347.52 607.25 345.125 602 343.625zM476.375 665.75c-2.615 1.902 2.166 4.092 5.016 7.875 1.645 2.186 0.425 5.815 2.484 7.875 5.625 5.625 33.375 13.5 27.949-1.875C506.4 664.25 480.5 662.75 476.375 665.75zM543.5 617c-9.375 0.375-31.443 2.707-27.375 6.75 15.844 15.75-6 20.25-19.5 21.375S477.5 653.75 484.25 654.5s33.75-0.375 38.25-4.125 28.875-13.5 30.375-20.625S552.875 616.625 543.5 617zM624.875 619.625c-7.5-6-45.24 21.529-52.5 27.75-31.5 27-48.375 18-54.99 22.5-6.617 4.5-4.26 10.5 5.865 19.5s38.625-3 55.125-4.875 35.625-14.625 36-29.781C614.75 639.564 632.375 625.625 624.875 619.625z" horiz-adv-x="1024" />
+<glyph glyph-name="graph" unicode="&#xf043;" d="M704 576H512v-640h192V576zM960 384H768v-448h192V384zM64-128V0h64v64H64V192h64v64H64V384h64v64H64V576h64v64H64V768h64V832H0v-1024h1024v64H64zM448 256H256v-320h192V256z" horiz-adv-x="1024" />
+<glyph glyph-name="heart" unicode="&#x2665;" d="M384-32c399 314 384 425 384 512s-72 192-192 192-192-128-192-128-72 128-192 128-192-105-192-192-15-198 384-512z" horiz-adv-x="768.199" />
+<glyph glyph-name="history" unicode="&#xf07e;" d="M448 768c-90.938 0-175.312-27.531-245.938-74.062L128 768v-256h256l-88 88c45.438 24.688 96.688 40 152 40 176.75 0 320-143.219 320-320 0-176.75-143.25-320-320-320-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.406C24.312 480.5 0 403.406 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM447.031 1L512 64V256h128l64 64-64 64H512l-64 64L320 320l64-64v-192L447.031 1z" horiz-adv-x="896" />
+<glyph glyph-name="home" unicode="&#xf08d;" d="M192 256l64-384h192V192h128v-320h192l64 384L512 576 192 256zM832 448V704H704l0.312-128.312L512 768 0 256h128l384 384 384-384h128L832 448z" horiz-adv-x="1024" />
+<glyph glyph-name="horizontal-rule" unicode="&#xf070;" d="M63.938 384h128v-128h64V639.938h-64V448h-128V639.938H0V256h63.938V384zM639.875 256V384h-63.938v-128H639.875zM639.875 448V575.938h-63.938V448H639.875zM447.938 448V575.938h128v64h-192V256h64V384h128v64H447.938zM0 0h639.875V128H0V0z" horiz-adv-x="639.875" />
+<glyph glyph-name="hourglass" unicode="&#xf09e;" d="M571 320c118 85 197 240 197 384 0 71-172 128-384 128s-384-57-384-128c0-144 80-299 197-384-118-85-197-240-197-384 0-71 172-128 384-128s384 57 384 128c0 144-80 299-197 384z m-187 448c141 0 256-29 256-64s-115-64-256-64-256 29-256 64 115 64 256 64z m-64-706c-154-7-238-40-253-82 16 114 75 189 141 251 73 68 112 60 112 103v-273z m-105 352c-70 55-122 130-142 215 70-32 183-53 311-53s241 21 311 53c-20-85-72-160-142-215-24 17-70 34-169 34s-145-17-169-34z m233-352v273c0-43 39-35 112-103 66-62 125-138 141-251-14 41-99 75-253 82z" horiz-adv-x="768" />
+<glyph glyph-name="hubot" unicode="&#xf09d;" d="M512 768c-283 0-512-229-512-512 0 0 0-192 0-256s64-128 128-128 704 0 768 0 128 64 128 128 0 256 0 256c0 283-229 512-512 512z m96-768h-192c-18 0-32 14-32 32s14 32 32 32h192c18 0 32-14 32-32s-14-32-32-32z m288 128c0-32-32-64-64-64s-128 0-128 0c0 32-32 64-64 64s-224 0-256 0-64-32-64-64c0 0-96 0-128 0s-64 32-64 64 0 360 0 360c78 129 220 216 384 216s306-87 384-216c0 0 0-328 0-360z m-128 384c-32 0-480 0-512 0s-64-32-64-64 0-96 0-128 32-64 64-64 480 0 512 0 64 32 64 64 0 96 0 128-32 64-64 64z m0-128l-64-64h-128l-64 64-64-64h-128l-64 64v64h64l64-64 64 64h128l64-64 64 64h64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="inbox" unicode="&#xf0cf;" d="M704 640H64L0 256v-256h768V256L704 640zM576 256l-64-128H256l-64 128H79l49 320h512l49-320H576z" horiz-adv-x="768" />
+<glyph glyph-name="info" unicode="&#xf059;" d="M448 448c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64z m0 320c-247 0-448-201-448-448s201-448 448-448 448 201 448 448-201 448-448 448z m0-768c-177 0-320 143-320 320s143 320 320 320 320-143 320-320-143-320-320-320z m64 320c0 32-32 64-64 64s-32 0-64 0-64-32-64-64h64s0-160 0-192 32-64 64-64 32 0 64 0 64 32 64 64h-64s0 160 0 192z" horiz-adv-x="896" />
+<glyph glyph-name="issue-closed" unicode="&#xf028;" d="M704 515.969l-96-96L768 256l256 256-96 96L769.25 449.219 704 515.969zM512 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 88.375 0 168.375-35.844 226.25-93.75l90.562 90.5C747.75 717.875 635.75 768 512 768 264.562 768 64 567.438 64 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448L759.75 119.75C768.688 130.75 684.75 0 512 0zM576 576H448v-320h128V576zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="issue-opened" unicode="&#xf026;" d="M448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 176.75 0 320-143.219 320-320C768 143.25 624.75 0 448 0zM384 64h128V192H384V64zM384 256h128V576H384V256z" horiz-adv-x="896" />
+<glyph glyph-name="issue-reopened" unicode="&#xf027;" d="M639.125 64.75C585.75 24.625 520 0 448 0c-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.469C24.312 480.562 0 403.406 0 320c0-247.438 200.562-448 448-448 107.375 0 204.5 39.312 281.75 102.25L768-64V128H576L639.125 64.75zM384 64h128V192H384V64zM512 576H384v-320h128V576zM896 320c0 247.438-200.562 448-448 448-107.406 0-204.531-39.312-281.656-102.344L128 704v-192h192l-63.156 63.156C310.281 615.312 376 640 448 640c176.75 0 320-143.219 320-320 0-45.562-9.75-88.75-27-128h91v-99.5C871.688 159.438 896 236.5 896 320z" horiz-adv-x="896" />
+<glyph glyph-name="jersey" unicode="&#xf019;" d="M704 832h-192c0-32-33-64-97-64s-95 32-95 64h-192c0-128-2-384-128-384 0 0-1-544-1-576s32-64 64-64 672 0 704 0 64 32 64 64 0 576 0 576c-126 0-128 256-128 384z m-609-960c-16 0-31 10-31 32 0 32 0 480 0 480 119 64 128 192 128 384h64c0-96 32-191 160-192s160 96 160 192h64c0-186 32-276 64-339v-557s-593 0-609 0z m385 576l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z m-352 320l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z" horiz-adv-x="896" />
+<glyph glyph-name="jump-down" unicode="&#xf072;" d="M767.75 640H0.25L384 256.25 767.75 640zM0 128v-128h768V128H0z" horiz-adv-x="768" />
+<glyph glyph-name="jump-left" unicode="&#xf0a5;" d="M256.25 320L640-63.75v767.5L256.25 320zM0-64h128V704H0V-64z" horiz-adv-x="640" />
+<glyph glyph-name="jump-right" unicode="&#xf0a6;" d="M0-63.75L383.75 320 0 703.812V-63.75zM512 704v-768h128V704H512z" horiz-adv-x="640" />
+<glyph glyph-name="jump-up" unicode="&#xf073;" d="M0.188 0h767.5L384 383.75 0.188 0zM0 640v-128h768V640H0z" horiz-adv-x="768" />
+<glyph glyph-name="key" unicode="&#xf049;" d="M640.9 768.1c-141.4 0-256-114.6-256-256 0-19.6 2.2-38.6 6.4-56.9L0 64v-64l64-64h128l64 64v64h64v64h64v64h128l70.8 70.8c18.7-4.3 38.1-6.6 58.1-6.6 141.4 0 256 114.6 256 256S782.2 768.1 640.9 768.1zM384 320L64 0v64l320 320V320zM704 512c-35.3 0-64 28.7-64 64 0 35.3 28.7 64 64 64s64-28.7 64-64C768 540.7 739.3 512 704 512z" horiz-adv-x="896.9" />
+<glyph glyph-name="keyboard" unicode="&#xf00d;" d="M640 256h64V384h-64V256zM768 576h-64v-128h64V576zM640 576h-64v-128h64V576zM512 256h64V384h-64V256zM384 64h320V192H384V64zM768 256h128V576h-64v-192h-64V256zM256 64h64V192h-64V64zM768 64h128V192H768V64zM512 576h-64v-128h64V576zM192 384h-64v-128h64V384zM192 192h-64v-128h64V192zM0 704v-768h1024V704H0zM960 0H64V640h896V0zM384 256h64V384h-64V256zM256 576H128v-128h128V576zM384 576h-64v-128h64V576zM256 256h64V384h-64V256z" horiz-adv-x="1024" />
+<glyph glyph-name="law" unicode="&#xf0d8;" d="M514 640c34 1 61 28 62 62 1 37-29 67-66 66-34-1-61-28-62-62-1-37 29-67 66-66z m464-384h-18l-127 246c18 2 36 9 52 16 24 11 29 43 11 62l-1 1c-11 11-28 15-43 8-14-6-34-13-53-13-56 0-81 64-287 64s-231-64-287-64c-20 0-39 6-53 13-15 6-32 3-43-8l-1-1c-18-19-13-50 11-62 16-8 34-14 52-16l-127-246h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15h-18l-127 245c83 7 127 49 191 49v-486c-35 0-64-29-64-64h-71c-28 0-57-29-57-64h512c0 35-29 64-71 64h-57c0 35-29 64-64 64v486c64 0 108-42 191-49l-127-245h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15z m-658 0h-192l96 180 96-180z m384 0l96 180 96-180h-192z" horiz-adv-x="1024" />
+<glyph glyph-name="light-bulb" unicode="&#xf000;" d="M512 768c-176.731 0-320-143.269-320-320 0-104.69 50.278-197.633 128-256.015V0c0-35.346 28.653-64 64-64 0-35.346 28.653-64 64-64h128c35.347 0 64 28.654 64 64 35.347 0 64 28.654 64 64V191.985C781.722 250.36699999999996 832 343.31 832 448 832 624.731 688.731 768 512 768zM640 32c0-17.673-14.326-32-32-32H416c-17.674 0-32 14.327-32 32v32h256V32zM704 278.693c-33.234-33.03-64-42.389-64-124.041V128h-64V256l128 128v64l-64 64-64-64-64 64-64-64-64 64-64-64v-64l128-128v-128h-64v26.652c0 81.652-30.766 91.011-64 124.041C280.177 323.82 256 383.082 256 448c0 141.385 114.615 256 256 256s256-114.615 256-256C768 383.082 743.823 323.82 704 278.693zM512 256L384 384v64l64-64 64 64 64-64 64 64v-64L512 256z" horiz-adv-x="1024" />
+<glyph glyph-name="link" unicode="&#xf05c;" d="M768 576h-138c48-32 93-89 107-128h30c65 0 128-64 128-128s-65-128-128-128h-192c-63 0-128 64-128 128 0 23 7 45 18 64h-137c-5-21-8-42-8-64 0-128 127-256 255-256s65 0 193 0 256 128 256 256-128 256-256 256z m-481-384h-30c-65 0-128 64-128 128s65 128 128 128h192c63 0 128-64 128-128 0-23-7-45-18-64h137c5 21 8 42 8 64 0 128-127 256-255 256s-65 0-193 0-256-128-256-256 128-256 256-256h138c-48 32-93 89-107 128z" horiz-adv-x="1024" />
+<glyph glyph-name="link-external" unicode="&#xf07f;" d="M640 64H128V574.094L256 576V704H0v-768h768V256H640V64zM384 704l128-128L320 384l128-128 192 192 128-128V704H384z" horiz-adv-x="768" />
+<glyph glyph-name="list-ordered" unicode="&#xf062;" d="M320 256h448v128h-448v-128z m0-256h448v128h-448v-128z m0 640v-128h448v128h-448z m-241-256h78v256h-36l-85-23v-50l43 2v-185z m110-206c0 36-12 78-96 78-33 0-64-6-83-16l1-66c21 10 42 15 67 15s32-11 32-28c0-26-30-58-110-112v-50h192v67l-91-2c49 30 87 66 87 113l1 1z" horiz-adv-x="768" />
+<glyph glyph-name="list-unordered" unicode="&#xf061;" d="M0 256h128v128h-128v-128z m0 256h128v128h-128v-128z m0-512h128v128h-128v-128z m256 256h512v128h-512v-128z m0 256h512v128h-512v-128z m0-512h512v128h-512v-128z" horiz-adv-x="768" />
+<glyph glyph-name="location" unicode="&#xf060;" d="M320 832c-177 0-320-143-320-320s160-416 320-704c160 288 320 527 320 704s-143 320-320 320z m0-448c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z" horiz-adv-x="640" />
+<glyph glyph-name="lock" unicode="&#xf06a;" d="M704 384c-32 0-64 0-64 0s0 64 0 192-128 256-256 256-256-128-256-256 0-192 0-192-32 0-64 0-64-32-64-64 0-416 0-448 32-64 64-64 608 0 640 0 64 32 64 64 0 416 0 448-32 64-64 64z m-192-128h-384v-64h384v-64h-384v-64h384v-64h-384v-64h384v-64h-448v448h448v-64z m0 128h-256s0 128 0 192 64 128 128 128 128-64 128-128 0-192 0-192z" horiz-adv-x="768" />
+<glyph glyph-name="logo-github" unicode="&#xf092;" d="M552.73 499.865H311.557c-6.205 0-11.25-5.045-11.25-11.297v-117.887c0-6.252 5.045-11.272 11.25-11.272h94.109v-146.542c0 0-21.145-7.057-79.496-7.057-68.914 0-165.156 25.244-165.156 236.795 0 211.642 100.197 239.491 194.307 239.491 81.465 0 116.514-14.304 138.869-21.241 7.01-2.203 13.404 4.831 13.404 11.105L534.543 785.87c0 2.912-1.041 6.417-4.262 8.785C521.186 801.048 465.865 832 326.168 832 165.133 832 0 763.513 0 434.243 0 105.02099999999996 189.051 56 348.381 56c131.883 0 212.021 56.314 212.021 56.314 3.268 1.801 3.6 6.395 3.6 8.479V488.568C563.955 494.773 558.887 499.865 552.73 499.865zM1772.381 803.866h-135.695c-6.252 0-11.271-5.044-11.271-11.296v-262.393h-211.619V792.57c0 6.252-5.068 11.296-11.178 11.296h-135.838c-6.111 0-11.084-5.044-11.084-11.296v-710.473c0-6.299 5.021-11.32 11.084-11.32h135.838c6.203 0 11.178 5.068 11.178 11.32V385.933h211.619l-0.475-303.883c0-6.3 5.021-11.272 11.084-11.272h135.885c6.252 0 11.131 5.068 11.131 11.272l0.473 710.521C1783.607 798.822 1778.539 803.866 1772.381 803.866zM714.949 787.763c-48.357 0-87.574-39.572-87.574-88.403 0-48.855 39.217-88.428 87.574-88.428s87.527 39.572 87.527 88.428C802.477 748.19 763.307 787.763 714.949 787.763zM792.861 559.874c0 6.205-5.02 11.344-11.131 11.344H646.32c-6.348 0-11.746-6.394-11.746-12.67 0 0 0-394.654 0-469.867 0-13.735 8.572-17.903 19.703-17.903 0 0 57.688 0 121.959 0 13.311 0 16.814 6.536 16.814 18.188-0.094 25.197-0.094 123.808-0.094 142.942C792.861 250.09500000000003 792.861 559.874 792.861 559.874zM2297.973 570.152h-134.701c-6.158 0-11.084-5.092-11.084-11.344v-348.31c0 0-34.244-25.197-82.934-25.197-48.547 0-61.525 22.024-61.525 69.719 0 47.553 0 303.835 0 303.835 0 6.252-5.068 11.345-11.131 11.345h-136.643c-6.252 0-11.178-5.093-11.178-11.345 0 0 0-185.521 0-326.807 0-141.284 78.766-175.906 186.99-175.906 88.854 0 160.609 49.115 160.609 49.115s3.363-25.766 5.068-28.844c1.422-3.078 5.447-6.158 9.852-6.158h86.58c6.158 0 11.178 5.069 11.178 11.321l0.379 477.278C2309.15 565.0609999999999 2304.129 570.152 2297.973 570.152zM2666.932 586.1610000000001c-76.539 0-128.592-34.148-128.592-34.148V792.57c0 6.252-5.068 11.296-11.131 11.296h-136.264c-6.109 0-11.131-5.044-11.131-11.296l-0.379-710.521c0-6.3 5.068-11.272 11.225-11.272 0 0 94.773 0 94.869 0 4.215 0 7.389 2.179 9.805 5.968 2.369 3.837 5.73 32.775 5.73 32.775s55.557-52.763 161.035-52.763c123.807 0 194.758 62.804 194.758 281.906C2856.859 557.482 2743.471 586.1610000000001 2666.932 586.1610000000001zM2613.791 185.77499999999998c-46.701 1.421-78.34 22.64-78.34 22.64v225.07c0 0 31.307 19.206 69.672 22.593 48.547 4.31 95.438-10.326 95.438-126.13C2700.322 207.94100000000003 2679.199 183.83399999999995 2613.791 185.77499999999998zM1185.125 188.33299999999997c-5.969 0-21.219-2.368-36.85-2.368-49.92 0-66.971 23.256-66.971 53.331 0 30.218 0 199.85 0 199.85h101.926c6.252 0 11.178 5.044 11.178 11.343v109.48c0.094 6.299-4.926 11.344-11.178 11.344h-101.926l-0.143 134.535c0 5.092-2.699 7.625-8.572 7.625H933.861c-5.352 0-8.336-2.391-8.336-7.578v-139.035c0 0-69.576-16.79-74.266-18.188-4.641-1.326-8.051-5.684-8.051-10.822v-87.408c0-6.252 5.068-11.344 11.178-11.344h71.139c0 0 0-91.34 0-210.222 0-156.109 109.553-171.455 183.439-171.455 33.723 0 74.076 10.988 80.848 13.356 4.074 1.421 6.395 5.637 6.395 10.136l0.047 96.101C1196.254 183.312 1190.998 188.428 1185.125 188.33299999999997z" horiz-adv-x="2856.857" />
+<glyph glyph-name="mail" unicode="&#xf03b;" d="M0 640v-640h896V640H0zM768 576L448 312 128 576H768zM64 512l252.031-191.625L64 128V512zM128 64l254 206.25L448 220l65.875 50.125L768 64H128zM832 128L579.625 320.062 832 512V128z" horiz-adv-x="896" />
+<glyph glyph-name="mail-read" unicode="&#xf03c;" d="M576 448H256v-64h320V448zM384 576H256v-64h128V576zM768 603.469V704H627.188L448 832 268.812 704H128v-100.531L0 512v-640h896V512L768 603.469zM192 640h512v-244.812L448 184 192 395.188V640zM64 384l252.031-191.625L64 0V384zM128-64l254 206.25L448 92l65.875 50.125L768-64H128zM832 0L579.625 192.062 832 384V0z" horiz-adv-x="896" />
+<glyph glyph-name="mail-reply" unicode="&#xf051;" d="M384 672l-384-288 384-288v192c111 0 329-61 384-280 0 291-196 451-384 472v192z" horiz-adv-x="768" />
+<glyph glyph-name="mark-github" unicode="&#xf00a;" d="M512 832c-283 0-512-229-512-512 0-226 147-418 350-486 26-5 35 11 35 25 0 12-1 53-1 95-104-23-156 20-173 60-11 27-25 55-57 75-34 22-19 34 4 31 29-3 58-17 79-53 43-75 117-57 149-43 5 33 18 56 33 68-114 13-233 57-233 253 0 56 20 102 53 137-5 13-23 65 5 136 0 0 43 14 141-53 41 11 85 17 128 17 44 0 87-6 128-17 98 66 141 53 141 53 28-71 10-123 5-136 33-36 53-82 53-137 0-197-120-240-234-253 18-16 35-47 35-95 0-68-1-124-1-141 0-14 9-30 35-25 203 68 350 260 350 486 0 283-229 512-512 512z" horiz-adv-x="1024" />
+<glyph glyph-name="markdown" unicode="&#xf0c9;" d="M950.154 640H73.846C33.127 640 0 606.873 0 566.154v-492.308C0 33.125 33.127 0 73.846 0h876.308c40.721 0 73.846 33.125 73.846 73.846V566.154C1024 606.873 990.875 640 950.154 640zM576 128.125L448 128V320l-96-123.077L256 320v-192H128V512h128l96-128 96 128 128 0.125V128.125zM767.091 96.125L608 320h96V512h128v-192h96L767.091 96.125z" horiz-adv-x="1024" />
+<glyph glyph-name="megaphone" unicode="&#xf077;" d="M832 800c-130 0-124-130-704-128C57.344 672 0 557.375 0 416s57.344-256 128-256c22.781 0 43.188-0.5 64.188-0.875L256-128l192-32 64 96-45.125 203.125C709.375 102.875 733.75 32 832 32c106 0 192 172 192 384C1024 628.031 938 800 832 800zM197 349.062c-39.188 1.469-82.188 2.25-127.562 2.625C66 371.406 64 393.094 64 416c0 88.375 28.688 192 64 192 39.031-0.125 75 0.438 109 1.406C209.656 562.438 192 493.688 192 416 192 392.688 194.062 370.562 197 349.062zM261.312 346.062C258.125 368.312 256 391.625 256 416c0 79.5 18.438 149.5 46.906 196.219 155.156 8.312 251.906 28.469 319.031 50.188C593.625 595.531 576 510.344 576 416c0-40 3.875-78 9.5-114.312C513.344 320.375 412.812 337.406 261.312 346.062zM832 128c-12.125 0-23.688 5.062-34.812 12.125-15.25 67.312-83.438 418.344 117.438 494.188C942.125 581.5 960 503.812 960 416 960 257 902.625 128 832 128z" horiz-adv-x="1024" />
+<glyph glyph-name="mention" unicode="&#xf0be;" d="M466.697 732.899C238.66 760.898 31.1 598.735 3.102 370.698c-28-228.038 134.163-435.598 362.2-463.597 71.429-8.756 145.115 0.913 213.325 29.946l-0.016 0.032c24.404 10.357 35.788 38.538 25.431 62.939-10.359 24.403-38.538 35.787-62.94 25.43l-0.001 0.004c-52.472-22.339-109.15-29.799-164.1-23.067-175.413 21.538-300.153 181.2-278.616 356.613 21.538 175.413 181.199 300.154 356.613 278.616 175.412-21.538 300.154-181.199 278.617-356.612-4.309-35.083-21.542-55.725-61.6-55.725-42.5 0-64 45.889-64 81.222V432c0 26.51-21.49 48-48 48-9.699 0-18.72-2.887-26.269-7.833-25.684 20.259-57.437 33.87-94.349 38.402-105.246 12.923-201.045-61.924-213.967-167.17C212.508 238.15200000000004 287.354 142.35400000000004 392.6 129.43200000000002c57.379-7.045 116.216 14.707 157.871 53.13 24.959-28.124 59.866-47.624 100.121-52.567 87.707-10.769 167.537 51.602 178.307 139.309C856.898 497.34 694.734 704.899 466.697 732.899zM511.285 308.30100000000004c-6.462-52.623-54.361-90.047-106.985-83.585-52.623 6.461-90.046 54.36-83.585 106.984 6.461 52.623 54.361 90.046 106.984 83.585C480.322 408.823 517.746 360.924 511.285 308.30100000000004z" horiz-adv-x="832" />
+<glyph glyph-name="microscope" unicode="&#xf089;" d="M617-64c86.312 18.75 151 100 151 192 0 58.438-26.625 110.125-67.875 145.375C702.5 288.625 704 304.125 704 320c0 104.844-49.875 197.875-128 256l64 64v64l64 64L640 832l-64-64h-64L256 512l-128-64v-128l64-64h128l64 128 96 96c55.5-33.406 96-90.438 96-160-106.062 0-192-85.938-192-192H0v-64h192c19.125-14.25 42.062-22.125 64-32v-96H128L0-192h768L640-64H617zM512 128c0 35.375 28.625 64 64 64s64-28.625 64-64c0-35.312-28.625-64-64-64S512 92.68799999999999 512 128z" horiz-adv-x="768" />
+<glyph glyph-name="milestone" unicode="&#xf075;" d="M704 640H0v-256h704l128 128L704 640zM448 448H320V576h128V448zM448 832H320v-128h128V832zM320-192h128V320H320V-192z" horiz-adv-x="832" />
+<glyph glyph-name="mirror" unicode="&#xf024;" d="M320 512L128 320l192-192V256h384v-128l192 192L704 512v-128H320V512zM512 832L0 512v-704l512 256 512-256V512L512 832zM960-64L576 128v64H448v-64L64-64V448l384 256v-256h128V704l384-256V-64z" horiz-adv-x="1024" />
+<glyph glyph-name="mortar-board" unicode="&#xf0d7;" d="M501 244l-245 76s0-96 0-160 115-96 256-96 256 32 256 96 0 160 0 160l-245-76c-7-2-15-2-23 0h1z m18 409c-4 1-9 1-13 0l-489-152c-21-7-21-36 0-43l111-35v-113c-19-11-32-32-32-55 0-12 3-23 9-32-5-9-9-20-9-32v-165c0-35 128-35 128 0v165c0 12-3 23-9 32 5 9 9 20 9 32 0 24-13 44-32 55v93l313-98c4-1 9-1 13 0l489 152c21 7 21 36 0 43l-488 153z m-6-205c-35 0-64 14-64 32s29 32 64 32 64-14 64-32-29-32-64-32z" horiz-adv-x="1024" />
+<glyph glyph-name="move-down" unicode="&#xf0a8;" d="M640 512H448V832H192v-320H0l320-384L640 512zM0-192h640V0H0V-192z" horiz-adv-x="640" />
+<glyph glyph-name="move-left" unicode="&#xf074;" d="M0 0h192V640H0V0zM704 448V640L320 320l384-320V192h320V448H704z" horiz-adv-x="1024" />
+<glyph glyph-name="move-right" unicode="&#xf0a9;" d="M832 640v-640h192V640H832zM320 448H0v-256h320v-192l384 320L320 640V448z" horiz-adv-x="1024" />
+<glyph glyph-name="move-up" unicode="&#xf0a7;" d="M0 128h192v-320h256V128h192L320 512 0 128zM0 832v-192h640V832H0z" horiz-adv-x="640" />
+<glyph glyph-name="mute" unicode="&#xf080;" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM864 416l-64 64-96-96-96 96-63-63.5 95-96.5-96-96 64-64 96 96 96-96 64 64-96 96L864 416z" horiz-adv-x="896" />
+<glyph glyph-name="no-newline" unicode="&#xf09c;" d="M896 512v-128H768V512L576 320l192-192V256h192c0 0 64 0.375 64 64s0 192 0 192H896zM224 544C100.281 544 0 443.719 0 320c0-123.75 100.281-224 224-224s224 100.25 224 224C448 443.719 347.719 544 224 544zM96 320c0 70.656 57.344 128 128 128 18.75 0 36.406-4.219 52.469-11.531L107.531 267.5C100.219 283.625 96 301.25 96 320zM224 192c-18.75 0-36.406 4.25-52.469 11.5l168.938 168.969C347.781 356.406 352 338.75 352 320 352 249.375 294.656 192 224 192z" horiz-adv-x="1024" />
+<glyph glyph-name="octoface" unicode="&#xf008;" d="M940.812 554.312c8.25 20.219 35.375 101.75-8.562 211.906 0 0-67.375 21.312-219.875-82.906C648.5 700.875 579.875 703.5 512 703.5c-67.906 0-136.438-2.625-200.5-20.25C159.031 787.531 91.719 766.219 91.719 766.219 47.812 656 74.938 574.531 83.188 554.312 31.5 498.438 0 427.125 0 339.656 0 10.437999999999988 213.25-64 510.844-64 808.562-64 1024 10.437999999999988 1024 339.656 1024 427.125 992.5 498.438 940.812 554.312zM512-1c-211.406 0-382.781 9.875-382.781 214.688 0 48.938 24.062 94.595 65.344 132.312 68.75 62.969 185.281 29.688 317.438 29.688 132.25 0 248.625 33.281 317.438-29.625 41.312-37.78 65.438-83.312 65.438-132.312C894.875 8.875 723.375-1 512-1zM351.156 319.562c-42.469 0-76.906-51.062-76.906-114.188s34.438-114.312 76.906-114.312c42.375 0 76.812 51.188 76.812 114.312S393.531 319.562 351.156 319.562zM672.875 319.562C630.5 319.562 596 268.5 596 205.375s34.5-114.312 76.875-114.312 76.812 51.188 76.812 114.312C749.75 268.5 715.312 319.562 672.875 319.562z" horiz-adv-x="1024" />
+<glyph glyph-name="organization" unicode="&#xf037;" d="M768 448h-64H576h-64-64-64-64H192h-64C57.344 448 0 390.656 0 320v-64c0-47.25 25.844-88.062 64-110.25V-64h256v-128h256V-64h256V145.75c38.125 22.188 64 62.938 64 110.25v64C896 390.656 838.625 448 768 448zM256 0H128V256H64v64c0 35.312 28.688 64 64 64h81.719c-11-18.875-17.719-40.562-17.719-64v-128c0-47.25 25.844-88.062 64-110.25V0zM576 128V256h-64v-384H384V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64v-128C640 156.625 611.375 128 576 128zM832 256h-64v-256H640v81.75c38.125 22.188 64 62.938 64 110.25V320c0 23.438-6.75 45.125-17.75 64H768c35.375 0 64-28.688 64-64V256zM303.688 514.625C338.875 474.125 390.156 448 448 448c57.875 0 109.125 26.125 144.312 66.625C614.125 475.062 655.688 448 704 448c70.625 0 128 57.344 128 128s-57.375 128-128 128c-25.625 0-49.375-7.688-69.375-20.688C614.875 768.438 539.062 832 448 832S281.094 768.438 261.375 683.312C241.344 696.312 217.594 704 192 704c-70.656 0-128-57.344-128-128s57.344-128 128-128C240.312 448 281.844 475.062 303.688 514.625zM704 640c35.375 0 64-28.594 64-64s-28.625-64-64-64c-35.312 0-64 28.594-64 64S668.688 640 704 640zM448 768c70.625 0 128-57.344 128-128s-57.375-128-128-128c-70.656 0-128 57.344-128 128S377.344 768 448 768zM192 512c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S227.406 512 192 512z" horiz-adv-x="896" />
+<glyph glyph-name="package" unicode="&#xf0c4;" d="M480 768L0 640v-576l480-128 480 128V640L480 768zM63.875 111.06600000000003L63.5 544l384.498-102.533 0.001-432.833L63.875 111.06600000000003zM63.5 608l160.254 42.734L640 539.735v-0.135l-160-42.667L63.5 608zM896.125 111.06600000000003L512.001 8.634000000000015l0.001 432.833L640 475.6v-156l128 34.135V509.733L896.5 544 896.125 111.06600000000003zM768 573.733v0.125L351.734 684.862 480 719.066 896.5 608 768 573.733z" horiz-adv-x="1024" />
+<glyph glyph-name="paintcan" unicode="&#xf0d1;" d="M384 832C171.923 832 0 660.077 0 448v-64c0-35.346 28.654-64 64-64v-320c0-70.692 143.269-128 320-128s320 57.308 320 128V320c35.346 0 64 28.654 64 64v64C768 660.077 596.077 832 384 832zM576 192v-32c0-17.673-14.327-32-32-32s-32 14.327-32 32v32c0 17.673-14.327 32-32 32s-32-14.327-32-32v-160c0-17.673-14.327-32-32-32s-32 14.327-32 32V160c0 17.673-14.327 32-32 32s-32-14.327-32-32v-32c0-35.346-28.654-64-64-64s-64 28.654-64 64v64c-35.346 0-64 28.654-64 64V371.193C186.382 340.108 279.318 320 384 320s197.618 20.108 256 51.193V256C640 220.654 611.346 192 576 192zM384 384c-107.433 0-199.393 26.474-237.372 64 37.979 37.526 129.939 64 237.372 64s199.393-26.474 237.372-64C583.393 410.474 491.433 384 384 384zM384 576c-176.62 0-319.816-57.236-319.996-127.867-0.001 0.001-0.002 0.001-0.003 0.002C64.075 624.804 207.314 768 384 768c176.731 0 320-143.269 320-320C704 518.692 560.731 576 384 576z" horiz-adv-x="768" />
+<glyph glyph-name="pencil" unicode="&#xf058;" d="M704 768L576 640l192-192 128 128L704 768zM0 64l0.688-192.562L192-128l512 512L512 576 0 64zM192-64H64V64h64v-64h64V-64z" horiz-adv-x="896" />
+<glyph glyph-name="person" unicode="&#xf018;" d="M448 640C448 746 362.062 832 256 832S64 746 64 640c0-106.062 85.938-192 192-192S448 533.938 448 640zM256 512c-70.656 0-128 57.344-128 128S185.344 768 256 768c70.625 0 128-57.344 128-128S326.625 512 256 512zM384 448H256 128C57.344 448 0 390.656 0 320v-128c0-70.625 57.344-128 128-128v-256h256V64c70.625 0 128 57.375 128 128V320C512 390.656 454.625 448 384 448zM448 192c0-35.375-28.625-64-64-64V256h-64v-384H192V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64V192z" horiz-adv-x="512" />
+<glyph glyph-name="pin" unicode="&#xf041;" d="M196 128l64-320 64 320c-20-2-43-3-64-3s-44 1-64 3z m254 299c-33 17-62 59-62 85v64c0 22 12 39 23 52 15 13 24 29 24 45 0 53-61 95-175 95s-175-42-175-95c0-16 9-32 24-45 11-13 23-30 23-52v-64c0-26-29-68-62-85-38-19-70-54-70-88 0-74 101-148 260-148s260 73 260 148c0 33-31 68-70 88z" horiz-adv-x="519.657" />
+<glyph glyph-name="playback-fast-forward" unicode="&#xf0bd;" d="M0 64l384 256L0 576V64zM768 320L384 576v-256-256L768 320z" horiz-adv-x="768" />
+<glyph glyph-name="playback-pause" unicode="&#xf0bb;" d="M0 0h192V640H0V0zM320 640v-640h192V640H320z" horiz-adv-x="512" />
+<glyph glyph-name="playback-play" unicode="&#xf0bf;" d="M0 640l512-320L0 0V640z" horiz-adv-x="512" />
+<glyph glyph-name="playback-rewind" unicode="&#xf0bc;" d="M384 320l384-256V576L384 320zM0 320l384-256V320 576L0 320z" horiz-adv-x="768" />
+<glyph glyph-name="plug" unicode="&#xf0d4;" d="M1003.386 627.336l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-45.707-45.707-90.51 90.51 45.707 45.707c24.744 24.744 24.744 64.861 0 89.605l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-47.973-47.973C621.76 802.446 537.237 795.66 482.502 740.926l-24.89-24.89c-109.011-109.011-121.948-277.692-38.854-400.892l-4.138-4.138c-62.392-62.392-62.484-163.493-0.275-225.999 12.41-12.469 12.642-33.327 0.121-45.683-12.509-12.343-32.655-12.292-45.101 0.153l-89.427 89.427c-62.637 62.638-164.63 63.747-227.299 1.141-62.542-62.479-62.562-163.829-0.058-226.332l8.763-8.763c24.744-24.744 64.861-24.744 89.605 0l0.905 0.905c24.744 24.744 24.744 64.861 0 89.605l-8.292 8.292c-12.329 12.329-13.085 32.418-1.098 45.081 12.437 13.138 33.174 13.353 45.882 0.645l89.328-89.328c62.92-62.92 165.504-63.814 228.081-0.553 61.793 62.468 61.65 163.161-0.431 225.451-12.55 12.592-12.777 32.866-0.207 45.437l4.151 4.151c123.2-83.095 291.881-70.158 400.892 38.854l24.89 24.89c54.734 54.735 61.52 139.258 20.362 201.382l47.973 47.973C1028.129 562.475 1028.129 602.593 1003.386 627.336zM889.796 333.632c-37.49-37.49-98.274-37.49-135.765 0L527.757 559.906c-37.49 37.49-37.49 98.274 0 135.765 29.556 29.556 73.585 35.804 109.269 18.759l-41.839-41.839c-24.744-24.744-24.744-64.861 0-89.604l0.905-0.905c24.744-24.744 64.861-24.744 89.605 0l45.707 45.707 90.51-90.51-45.707-45.707c-24.744-24.744-24.744-64.861 0-89.605l0.905-0.905c24.744-24.744 64.861-24.744 89.604 0l41.839 41.839C925.6 407.218 919.351 363.188 889.796 333.632z" horiz-adv-x="1024" />
+<glyph glyph-name="plus" unicode="&#xf05d;" d="M384 384V640H256v-256H0v-128h256v-256h128V256h256V384H384z" horiz-adv-x="640" />
+<glyph glyph-name="podium" unicode="&#xf0af;" d="M320 832c-32 0-64-32-64-64s0-64 0-64h-64l-192-192v-128h192l64-384-128-64v-64h512v64l-128 64 64 384h192v128l-192 192h-256v64s14 0 32 0 32 17 32 32-16 32-32 32 0 0-32 0z m0-832l-53 320h118l-1-320h-64z m-224 512l128 128h32v-64h64v64h224l128-128h-576z" horiz-adv-x="768" />
+<glyph glyph-name="primitive-dot" unicode="&#xf052;" d="M-0.088 320c0 141.5 114.5 256 256 256 141.438 0 256-114.5 256-256s-114.562-256-256-256C114.413 64-0.088 178.5-0.088 320z" horiz-adv-x="511.825" />
+<glyph glyph-name="primitive-square" unicode="&#xf053;" d="M512 64H0V576h512V64z" horiz-adv-x="512" />
+<glyph glyph-name="pulse" unicode="&#xf085;" d="M736 320.062L563.188 486.406 422.406 288 352 729.594 152.438 320.062H0V192h230.406L288 307.188l57.594-345.562L576 288l102.375-96H896V320.062H736z" horiz-adv-x="896" />
+<glyph glyph-name="puzzle" unicode="&#xf0c0;" d="M755.75 256.85c-13.95 9.96-28.52 16.59-43.47 19.92-8.84 1.69-18.06 2.33-27.57 1.81-8.99-0.5-17.56-1.68-25.69-3.52-6.1-1.69-12.22-3.89-18.35-6.59-18.18-8.02-33.89-18.12-46.79-30.33-12.22-12.9-22.32-28.62-30.34-46.79-2.7-6.12-4.9-12.24-6.59-18.34-1.84-8.14-3.03-16.7-3.52-25.69-0.52-9.51 0.12-18.73 1.81-27.57 3.33-14.95 9.96-29.52 19.92-43.47 3.89-5.44 8.08-10.4 12.56-14.88 20.06-20.03 45.83-30.7 75.42-34.11 8.92-1.02 18.12-1.68 26.53-4.48 5.12-1.7 9.16-4.08 12.08-7.02 6.65-6.6 7.63-16.1 2.5-27.24-3.15-6.84-7.7-13.45-12.96-18.84l-2.79-2.86c-3.93-3.92-6.41-6.4-7.05-7.04-3.13-3.16-6.1-6.15-9.06-9.15l-2.96-2.92c-10.52-10.58-21.09-21.12-31.66-31.65-22.76-22.82-45.57-45.58-68.38-68.36-7.5-7.5-15-15-22.5-22.49-3.46-3.45-7.07-6.38-10.78-8.79-1.8-1.22-3.49-2.24-5.18-3.16-19.6-9.89-41.43-5.92-59.24 11.88-5.4 5.4-10.62 10.62-15.85 15.84-30.25 30.25-60.48 60.52-90.77 90.73-8.59 8.57-17.13 17.08-25.68 25.59-6.12 6.09-12.67 11.85-19.56 17.06-5.72 4.33-11.59 7.56-17.46 9.73-21.16 7.32-41.41 2.01-54.67-13.26-3.81-4.8-7-10.47-9.39-16.94-3.43-9.26-4.6-19.47-5.9-29.36-4.9-37.53-25.8-68.43-55.98-82.65-7.48-3.65-15.49-6.29-23.9-7.78-7.95-1.41-15.95-1.71-23.85-1.04-26.61 1.35-49.48 13.09-68.51 32.57-1.68 1.67-2.1 2.09-2.51 2.51-19.48 19.02-31.22 41.9-32.57 68.5-0.68 7.9-0.37 15.9 1.04 23.85 1.49 8.41 4.13 16.43 7.78 23.9 14.22 30.18 45.13 51.07 82.65 55.97 9.89 1.29 20.1 2.47 29.36 5.9 6.94 2.56 12.96 6.05 17.97 10.23 14.54 13.15 19.59 32.63 12.84 52.34-2.78 7.35-6 13.22-10.33 18.94-5.21 6.88-10.97 13.43-17.06 19.55-8.51 8.55-17.03 17.09-25.55 25.63-26.92 26.98-53.84 53.88-80.75 80.78l-10.03 10.03c-5.22 5.22-10.45 10.45-15.26 15.27-18.39 18.4-22.35 40.22-12.46 59.82 0.92 1.69 1.94 3.37 3.08 5.05 2.49 3.84 5.42 7.45 8.87 10.91 7.49 7.5 14.99 15 22.49 22.5 22.77 22.81 45.54 45.62 68.36 68.38 10.53 10.57 21.06 21.14 31.65 31.66l2.92 2.96c2.99 2.97 5.99 5.93 8.98 8.9 0.8 0.81 3.28 3.29 7.2 7.22l2.86 2.79c5.39 5.26 12 9.8 18.84 12.96 11.14 5.13 20.63 4.15 27.24-2.5 2.94-2.92 5.32-6.96 7.02-12.08 2.79-8.41 3.45-17.61 4.48-26.53 3.41-29.59 14.08-55.35 34.11-75.41 4.49-4.48 9.44-8.67 14.88-12.56 13.95-9.96 28.52-16.59 43.47-19.92 8.84-1.69 18.06-2.33 27.57-1.81 8.99 0.5 17.56 1.68 25.69 3.52 6.1 1.69 12.22 3.89 18.35 6.59 18.18 8.02 33.89 18.12 46.79 30.33 12.22 12.9 22.32 28.62 30.34 46.79 2.7 6.12 4.9 12.24 6.59 18.34 1.84 8.14 3.03 16.7 3.52 25.69 0.52 9.51-0.12 18.73-1.81 27.57-3.33 14.95-9.96 29.52-19.92 43.47-3.89 5.44-8.08 10.4-12.56 14.88-20.06 20.03-45.83 30.7-75.42 34.11-8.92 1.02-18.12 1.68-26.53 4.48-5.12 1.7-9.16 4.08-12.08 7.02-6.65 6.6-7.63 16.1-2.5 27.24 3.15 6.84 7.7 13.45 12.96 18.84l2.79 2.86c3.93 3.92 6.41 6.4 7.05 7.04 3.13 3.16 6.1 6.15 9.06 9.15l2.96 2.92c10.52 10.58 21.09 21.12 31.66 31.65 22.76 22.82 45.57 45.58 68.38 68.35 7.5 7.5 15 15 22.5 22.49 3.46 3.45 7.07 6.38 10.78 8.79 1.8 1.22 3.49 2.24 5.18 3.16 19.6 9.89 41.43 5.92 59.24-11.88 5.4-5.4 10.62-10.62 15.85-15.84 30.25-30.25 60.48-60.52 90.77-90.73 8.59-8.57 17.13-17.08 25.68-25.59 6.12-6.09 12.67-11.85 19.56-17.06 5.72-4.33 11.59-7.56 17.46-9.73 21.16-7.32 41.41-2.01 54.67 13.26 3.81 4.8 7 10.47 9.39 16.94 3.43 9.26 4.6 19.47 5.9 29.36 4.9 37.53 25.8 68.43 55.98 82.65 7.48 3.65 15.49 6.28 23.9 7.78 7.95 1.41 15.95 1.71 23.85 1.04 26.61-1.35 49.48-13.09 68.51-32.57 1.68-1.67 2.1-2.09 2.51-2.51 19.48-19.02 31.22-41.9 32.57-68.5 0.68-7.9 0.37-15.9-1.04-23.85-1.49-8.41-4.13-16.43-7.78-23.9-14.22-30.18-45.13-51.07-82.65-55.97-9.89-1.29-20.1-2.47-29.36-5.9-6.94-2.56-12.96-6.05-17.97-10.23-14.54-13.15-19.59-32.63-12.84-52.34 2.78-7.35 6-13.22 10.33-18.94 5.21-6.88 10.97-13.43 17.06-19.55 8.51-8.55 17.03-17.09 25.55-25.63 30.26-30.33 60.54-60.56 90.78-90.81 5.22-5.22 10.45-10.45 15.26-15.27 18.39-18.4 22.35-40.22 12.46-59.82-0.92-1.69-1.94-3.37-3.08-5.05-2.49-3.84-5.42-7.45-8.87-10.91-7.49-7.5-14.99-15-22.49-22.5-22.77-22.81-45.54-45.62-68.36-68.38-10.53-10.57-21.06-21.14-31.65-31.66l-2.92-2.96c-2.99-2.97-5.99-5.93-8.98-8.9-0.8-0.81-3.28-3.29-7.2-7.22l-2.86-2.79c-5.39-5.26-12-9.8-18.84-12.96-11.14-5.13-20.63-4.15-27.24 2.5-2.94 2.92-5.32 6.96-7.02 12.08-2.79 8.41-3.45 17.61-4.48 26.53-3.41 29.59-14.08 55.35-34.11 75.41C766.15 248.76999999999998 761.19 252.97000000000003 755.75 256.85z" horiz-adv-x="1024" />
+<glyph glyph-name="question" unicode="&#xf02c;" d="M448 64h128v128h-128v-128z m64 512c-96 0-192-96-192-192h128c0 32 32 64 64 64s64-32 64-64c0-64-128-64-128-128h128c64 22 128 64 128 160s-96 160-192 160z m0 256c-283 0-512-229-512-512s229-512 512-512 512 229 512 512-229 512-512 512z m0-896c-212 0-384 172-384 384s172 384 384 384 384-172 384-384-172-384-384-384z" horiz-adv-x="1024" />
+<glyph glyph-name="quote" unicode="&#xf063;" d="M0 320v-256h256V320H128c0 0 0 128 128 128V576C256 576 0 576 0 320zM640 448V576c0 0-256 0-256-256v-256h256V320H512C512 320 512 448 640 448z" horiz-adv-x="640" />
+<glyph glyph-name="radio-tower" unicode="&#xf030;" d="M306.838 441.261c15.868 16.306 15.868 42.731 0 59.037-20.521 21.116-30.643 48.417-30.705 76.124 0.062 27.77 10.183 55.039 30.705 76.186 15.868 16.337 15.868 42.764 0 59.069-7.934 8.184-18.272 12.275-28.706 12.275-10.371 0-20.804-4.029-28.738-12.213-36.266-37.297-54.633-86.433-54.57-135.317-0.062-48.792 18.305-97.927 54.57-135.161C265.262 424.955 290.97 424.955 306.838 441.261zM149.093 798.858c-8.121 8.309-18.68 12.463-29.3 12.463-10.558 0-21.179-4.154-29.237-12.463C30.8 737.509 0.751 656.856 0.813 576.422 0.751 496.081 30.8 415.272 90.494 353.985c16.181-16.618 42.356-16.618 58.537 0 16.118 16.587 16.118 43.513 0 60.067-43.7 44.98-65.44 103.456-65.44 162.368s21.74 117.449 65.44 162.368C165.149 755.439 165.149 782.365 149.093 798.858zM513.031 472.153c57.351 0 103.956 46.574 103.956 103.956 0 57.382-46.605 103.955-103.956 103.955-57.381 0-103.956-46.573-103.956-103.955C409.076 518.727 455.65 472.153 513.031 472.153zM933.539 798.233c-16.181 16.618-42.355 16.618-58.475 0-16.181-16.587-16.181-43.513 0-60.068 43.668-44.918 65.409-103.456 65.409-162.368 0-58.85-21.805-117.387-65.473-162.306-16.117-16.618-16.117-43.575 0.062-60.068 8.059-8.309 18.616-12.463 29.237-12.463 10.558 0 21.178 4.154 29.236 12.463 59.726 61.287 89.774 142.096 89.649 222.437C1023.313 656.138 993.264 736.947 933.539 798.233zM513.281 389.127L513.281 389.127c-26.489-0.062-53.04 6.466-77.091 19.429L235.057-127.59000000000003h95.209l54.819 63.973h255.891l53.977-63.973h95.272L589.124 408.431C565.384 395.655 539.395 389.127 513.281 389.127zM512.656 358.483L577.004 128.29999999999995H449.059L512.656 358.483zM385.086 0.3550000000000182l63.974 63.973h127.944l63.974-63.973H385.086zM717.194 710.958c-15.868-16.306-15.868-42.731 0-59.037 20.491-21.116 30.611-48.511 30.674-76.124-0.062-27.77-10.183-55.102-30.674-76.187-15.868-16.336-15.868-42.763 0-59.068 7.871-8.184 18.242-12.213 28.737-12.213 10.309 0 20.741 4.029 28.675 12.213 36.298 37.234 54.665 86.433 54.54 135.255 0.125 48.792-18.181 97.927-54.54 135.161C758.801 727.264 733.062 727.264 717.194 710.958z" horiz-adv-x="1024" />
+<glyph glyph-name="repo" unicode="&#xf001;" d="M320 576h-64v-64h64v64z m0 128h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 736 0 768-32 64-64 64z m0-800c0-16-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 17-32 32 0 96 0 96h640s0-80 0-96z m0 160h-512v576h513l-1-576z m-384 128h-64v-64h64v64z m0 128h-64v-64h64v64z" horiz-adv-x="768" />
+<glyph glyph-name="repo-clone" unicode="&#xf04c;" d="M320 448h-64v-64h64v64z m-128 320h256v64s-352 0-384 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s286 0 320 0 64 32 64 64 0 192 0 192h-576v576z m512-640s0-79 0-96-14-32-32-32-288 0-288 0v64h-192v-64s-80 0-96 0-32 16-32 32 0 96 0 96h640z m-384 448h-64v-64h64v64z m-64-320h64v64h-64v-64z m704 576c-32 0-288 0-320 0s-64-32-64-64 0-352 0-384 32-64 64-64 64 0 64 0v-64l32 32 32-32v64s160 0 192 0 64 32 64 64 0 352 0 384-32 64-64 64z m-256-448s-15 0-32 0-32 15-32 32 0 32 0 32h64v-64z m256 32c0-16-15-32-32-32s-160 0-160 0v64h192s0-16 0-32z m0 96h-256v256h224s32 0 32-32 0-224 0-224z m-640 192h-64v-64h64v64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-force-push" unicode="&#xf04a;" d="M768 768c0 32-32 64-64 64s-608 0-640 0-64-32-64-64 0-768 0-768 0 32 0 0 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 15-32 32 0 96 0 96h256v64h-128v576h512v-576h-128v-64h128s0-80 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768z m-272-320h144l-192 256-192-256h144l-144-192h128v-448h128v448h128l-144 192z" horiz-adv-x="767.896" />
+<glyph glyph-name="repo-forked" unicode="&#xf002;" d="M768 704c0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-106l-192-212-192 212v106c38 22 64 63 64 111 0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-156l256-282v-109c-38-22-64-63-64-111 0-71 57-128 128-128s128 57 128 128c0 47-26 89-64 111v109l256 282v156c38 22 64 63 64 111z m-640 63c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z m256-891c-34 0-62 28-62 62s28 62 62 62 62-28 62-62-28-62-62-62z m256 891c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z" horiz-adv-x="768" />
+<glyph glyph-name="repo-pull" unicode="&#xf006;" d="M1024 512l-192 192v-128h-384v-128h384v-128l192 192z m-320-320h-512v576h512v-128h64s0 96 0 128-32 64-64 64-608 0-640 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 384 0 384h-64v-192z m0-160c0-15-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 16-32 32 0 96 0 96h640s0-81 0-96z m-384 544h-64v-64h64v64z m0 128h-64v-64h64v64z m0-256h-64v-64h64v64z m-64-192h64v64h-64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-push" unicode="&#xf005;" d="M448 512l-192-256h128v-448h128v448h128l-192 256z m-192 0h64v64h-64v-64z m64 192h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 14-32 32 0 96 0 96h256v64h-128v576h513l-1-576h-128v-64h128s0-79 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768-32 64-64 64z" horiz-adv-x="768" />
+<glyph glyph-name="rocket" unicode="&#xf033;" d="M716.737 707.944c-71.926-41.686-148.041-96.13-218.436-166.555-45-45.031-81.213-88.78-110.39-129.778L209.538 378.65 0.047 169.00300000000004l186.818-5.815 131.562 131.562c-46.439-96.224-50.536-160.019-50.536-160.019l58.854-58.792c0 0 65.827 6.255 162.737 53.163L355.107-5.119000000000028l5.88-186.881 209.585 209.521 33.086 179.252c41.403 29.02 85.185 65.046 129.716 109.545 70.425 70.455 124.837 146.541 166.555 218.466-45.97 9.351-88.125 28.488-121.397 61.668C745.257 619.819 725.994 661.975 716.737 707.944zM786.161 745.157c5.004-45 19.952-81.274 44.78-105.98 24.769-24.985 60.98-39.902 106.138-44.844C1003.063 727.677 1023.953 832 1023.953 832S919.63 811.142 786.161 745.157z" horiz-adv-x="1024" />
+<glyph glyph-name="rss" unicode="&#xf034;" d="M128 192C57.344 192 0 134.625 0 64s57.344-128 128-128 128 57.375 128 128S198.656 192 128 192zM128 448c0 0-64-2-64-64s64-64 64-64c141.375 0 256-114.625 256-256 0 0 0-64 64-64s64 64 64 64C512 276 340.031 448 128 448zM128 704c0 0-64 0-64-64s64-64 64-64c282.75 0 512-229.25 512-512 0 0 0-64 64-64s64 64 64 64C768 417.406 481.5 704 128 704z" horiz-adv-x="768" />
+<glyph glyph-name="ruby" unicode="&#xf047;" d="M768 704H256L0 448l512-512 512 512L768 704zM128 448l192 192h384l192-192L512 64 128 448zM704 576H512v-448l320 320L704 576z" horiz-adv-x="1024" />
+<glyph glyph-name="screen-full" unicode="&#xf066;" d="M128 64h639.875V576H128V64zM255.938 448h384v-256h-384V448zM64 639.938h191.938v64H0V448h64V639.938zM64 192H0v-255.938h255.938V0H64V192zM639.938 703.938v-64h191.938V448h64V703.938H639.938zM831.875 0H639.938v-63.938h255.938V192h-64V0z" horiz-adv-x="895.875" />
+<glyph glyph-name="screen-normal" unicode="&#xf067;" d="M127.938 640.062H0v-64h191.938V768h-64V640.062zM0-0.06200000000001182h127.938V-128h64V63.93799999999999H0V-0.06200000000001182zM768.062 640.062V768h-64v-191.938H896v64H768.062zM704.062-128h64V-0.06200000000001182H896v64H704.062V-128zM192.062 128H704V512H192.062V128zM320 384h256v-128H320V384z" horiz-adv-x="896" />
+<glyph glyph-name="search" unicode="&#xf02e;" d="M960 0L710.875 249.125C746.438 307.188 768 374.844 768 448 768 660.031 596 832 384 832 171.969 832 0 660.031 0 448c0-212 171.969-384 384-384 73.156 0 140.812 21.562 198.875 57L832-128c17.5-17.5 46.5-17.375 64 0l64 64C977.5-46.5 977.5-17.5 960 0zM384 192c-141.375 0-256 114.625-256 256s114.625 256 256 256 256-114.625 256-256S525.375 192 384 192z" horiz-adv-x="973.125" />
+<glyph glyph-name="server" unicode="&#xf097;" d="M704 448h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192-128h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 832h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 64h-64v64h64v-64z" horiz-adv-x="768" />
+<glyph glyph-name="settings" unicode="&#xf07c;" d="M64-64h128V128H64V-64zM192 704H64v-320h128V704zM512 704H384v-128h128V704zM0 192h256V320H0V192zM384-64h128V320H384V-64zM320 384h256V512H320V384zM832 704H704v-384h128V704zM640 256v-128h256V256H640zM704-64h128V64H704V-64z" horiz-adv-x="896" />
+<glyph glyph-name="sign-in" unicode="&#xf036;" d="M640 256L640 384 896 384 896 512 640 512 640 640 448 496 448 640 192 768 704 768 704 576 768 576 768 832 64 832 64 0 448-192 448 0 768 0 768 320 704 320 704 64 448 64 448 400z" horiz-adv-x="896" />
+<glyph glyph-name="sign-out" unicode="&#xf032;" d="M640 64H384V640L128 768h512v-192h64V832H0v-832l384-192V0h320V320h-64V64zM1024 448L768 640v-128H512v-128h256v-128L1024 448z" horiz-adv-x="1024" />
+<glyph glyph-name="split" unicode="&#xf0c6;" d="M448 576l-256 256-192-192 311-300c15 81 43 136 133 230l5 6z m128 256l133-133-197-197c-99-99-128-162-128-309v-384h256v384c0 52 19 94 53 128l197 197 133-133v448h-448z" horiz-adv-x="1024" />
+<glyph glyph-name="squirrel" unicode="&#xf0b2;" d="M768 768c-141.385 0-256-83.75-256-186.875C512 457.25 544 387 512 192c0 288-177 405.783-256 405.783 3.266 32.17-30.955 42.217-30.955 42.217s-14-7.124-19.354-21.583c-17.231 20.053-36.154 17.54-36.154 17.54l-8.491-37.081c0 0-117.045-40.876-118.635-206.292C56 371 141.311 353.898 201.887 364.882c57.157-2.956 42.991-50.648 30.193-63.446C178.083 247.438 128 320 64 320s-64-64 0-64 64-64 192-64c-198-77 0-256 0-256h-64c-64 0-64-64-64-64s256 0 384 0c192 0 320 64 320 222.182 0 54.34-27.699 114.629-64 162.228C697.057 349.433 782.453 427.566 832 384s192-64 192 128C1024 653.385 909.385 768 768 768zM160 448c-17.674 0-32 14.327-32 32 0 17.674 14.326 32 32 32 17.673 0 32-14.326 32-32C192 462.327 177.673 448 160 448z" horiz-adv-x="1024" />
+<glyph glyph-name="star" unicode="&#xf02a;" d="M896 448l-313.5 40.781L448 768 313.469 488.781 0 448l230.469-208.875L171-63.93799999999999l277 148.812 277.062-148.812L665.5 239.125 896 448z" horiz-adv-x="896" />
+<glyph glyph-name="steps" unicode="&#xf0c7;" d="M136 768C60.89 768 0 667.71 0 544c0-68.83 17.02-141.84 34-254.54C47.3 201.16999999999996 79.67 128 136 128s94.08 48.79 94.08 137.97c0 30.37-24.97 78.75-26.08 120.03-2.02 74.46 49.93 104.17 49.93 173C253.93 682.71 211.1 768 136 768zM502.97 512c-75.1 0-117.93-85.29-117.93-209 0-68.83 51.95-98.54 49.93-173-1.109-41.28-26.08-89.66-26.08-120.03 0-89.18 37.75-137.97 94.08-137.97s88.7 73.17 102 161.46c16.98 112.7 34 185.71 34 254.54C638.97 411.71 578.08 512 502.97 512z" horiz-adv-x="640" />
+<glyph glyph-name="stop" unicode="&#xf08f;" d="M704 832H320L0 512v-384l320-320h384l320 320V512L704 832zM896 192L640-64H384L128 192V448l256 256h256l256-256V192zM448 256h128V576H448V256zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="sync" unicode="&#xf087;" d="M655.461 358.531c11.875-81.719-13.062-167.781-76.812-230.594-94.188-92.938-239.5-104.375-346.375-34.562l74.875 73L31.96 204.75 70.367-64l84.031 80.5c150.907-111.25 364.938-100.75 502.063 34.562 79.5 78.438 115.75 182.562 111.25 285.312L655.461 358.531zM189.46 511.938c94.156 92.938 239.438 104.438 346.313 34.562l-75-72.969 275.188-38.406L697.586 704l-83.938-80.688C462.711 734.656 248.742 724.031 111.585 588.75 32.085 510.344-4.133 406.219 0.335 303.5l112.25-22.125C100.71 363.125 125.71 449.094 189.46 511.938z" horiz-adv-x="768.051" />
+<glyph glyph-name="tag" unicode="&#xf015;" d="M384 768H128L0 640v-256l512-512 384 384L384 768zM64 416V608l96 96h192l448-448L512-32 64 416zM448 512L256 320l256-256 192 192L448 512zM352 320l96 96 160-160-96-96L352 320zM320 544c0 53-43 96-96 96s-96-43-96-96 43-96 96-96S320 491 320 544zM224 512c-17.656 0-32 14.344-32 32s14.344 32 32 32 32-14.344 32-32S241.656 512 224 512z" horiz-adv-x="896" />
+<glyph glyph-name="telescope" unicode="&#xf088;" d="M76 409c32 8 229 59 229 59-1-6-2-19-2-19 0-71 49-128 128-128s128 59 128 128c0 11-8 22-19 32l49-3s7 2 31 8c-51-14-108 31-126 99s8 135 60 149c-24-6-31-8-31-8l-168-110c-34-9-55-46-46-80 2-9 7-17 12-23-7-12-12-26-15-40-27 1-51 19-59 46-9 34 11 69 45 78l-245-65c-34-9-54-43-45-77s41-54 73-46z m419-153h-128v-64l-320-320h128l192 128v-128h128v128l192-128h128l-320 320v64z m429 448c-18 68-70 110-122 96-69-18-98-28-186-51-51-14-79-80-61-148s74-115 125-102c87 23 117 33 186 51 51 14 76 85 58 154z m-70-90c-17-5-42 17-51 51s-4 66 13 70 42-17 51-51 4-66-13-70z" horiz-adv-x="929.875" />
+<glyph glyph-name="terminal" unicode="&#xf0c8;" d="M831 705H63c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V641C895 676.35 866.35 705 831 705zM127 257l128 128L127 513l64 64 192-192L191 193 127 257zM639 193H383v64h256V193z" horiz-adv-x="896" />
+<glyph glyph-name="three-bars" unicode="&#xf05e;" d="M0 640v-128h768v128h-768z m0-384h768v128h-768v-128z m0-256h768v128h-768v-128z" horiz-adv-x="768" />
+<glyph glyph-name="thumbsdown" unicode="&#xf0db;" d="M833 258c0-38-31-68-68-68h-111c-5 0-109 4-128-8-19-11-21-101-8-139 12-34 22-100-16-150-23-30-57-25-78-17-23 8-14 50-10 86 4 35-14 93-47 123s-29 58-45 86-41 52-62 86-68 41-132 39v353s93 0 114 2c31 3 72 13 112 26 41 13 128 26 140 26 89 3 174-18 208-24 34-7 60-41 54-77-2-11-6-21-12-29 6-2 11-5 16-9 22-12 36-34 36-60 0-21-9-39-24-52 4-1 8-3 12-5 27-9 47-35 47-65 0-27-16-51-40-62 2-1 4-2 6-4 15-9 26-23 31-40 2-6 3-11 3-18 0-1 0-1 0-2z" horiz-adv-x="1024" />
+<glyph glyph-name="thumbsup" unicode="&#xf0da;" d="M833 381c0 38-31 68-68 68h-111c-5 0-109-4-128 8-19 11-21 101-8 139 12 34 22 100-16 150-23 30-57 25-78 17-23-8-14-50-10-86 4-35-14-93-47-123s-29-58-45-86-41-52-62-86-68-41-132-39v-353s93 0 114-2c31-3 72-13 112-26 41-13 128-26 140-26 89-3 174 18 208 24s60 41 54 77c-2 11-6 21-12 29 6 2 11 5 16 9 22 12 36 34 36 60 0 21-9 39-24 52 4 1 8 3 12 5 27 9 47 35 47 65 0 27-16 51-40 62 2 1 4 2 6 4 15 9 26 23 31 40 2 6 3 11 3 18 0 1 0 1 0 2z" horiz-adv-x="1024" />
+<glyph glyph-name="tools" unicode="&#xf031;" d="M286.547 366.984c16.843-16.812 81.716-85.279 81.716-85.279l35.968 37.093-56.373 58.248L456.072 491.98c0 0-48.842 47.623-27.468 28.655 20.438 75.903 1.812 160.589-55.842 220.243C315.608 800.064 234.392 819.47 161.425 799.096l123.653-127.715-32.53-125.309-121.06-33.438L7.898 640.3820000000001c-19.718-75.436-0.969-159.339 56.311-218.556C124.302 359.703 210.83 341.453 286.547 366.984zM698.815 242.769L549.694 95.46100000000001l245.932-254.805c20.062-20.812 46.498-31.188 72.872-31.188 26.25 0 52.624 10.375 72.811 31.188 40.249 41.624 40.249 108.997 0 150.62L698.815 242.769zM1023.681 670.162L867.06 832.001 405.387 354.703l56.373-58.248L185.425 10.839000000000055l-63.154-33.749-89.217-145.559 22.719-23.562 140.839 92.247 32.655 65.312 276.336 285.554 56.404-58.248L1023.681 670.162z" horiz-adv-x="1024" />
+<glyph glyph-name="trashcan" unicode="&#xf0d0;" d="M704 704H448c0 0 0 24.057 0 32 0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 0-32 0-32H128c-35.346 0-64-28.654-64-64v-64c0-35.346 28.654-64 64-64v-576c0-35.346 28.654-64 64-64h448c35.346 0 64 28.654 64 64V512c35.346 0 64 28.654 64 64v64C768 675.346 739.346 704 704 704zM640-32c0-17.673-14.327-32-32-32H224c-17.673 0-32 14.327-32 32V512h64v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480H384v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480h64L512 32c0-17.673 14.327-32 32-32s32 14.327 32 32V512h64V-32zM704 592c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v32c0 8.837 7.163 16 16 16h544c8.837 0 16-7.163 16-16V592z" horiz-adv-x="768" />
+<glyph glyph-name="triangle-down" unicode="&#xf05b;" d="M0 448l383.75-383.75L767.5 448H0z" horiz-adv-x="767.5" />
+<glyph glyph-name="triangle-left" unicode="&#xf044;" d="M0 320.125l383.75-383.75v767.5L0 320.125z" horiz-adv-x="383.75" />
+<glyph glyph-name="triangle-right" unicode="&#xf05a;" d="M0.062 703.75L383.812 320 0.062-63.75V703.75z" horiz-adv-x="383.875" />
+<glyph glyph-name="triangle-up" unicode="&#xf0aa;" d="M383.75 576L0 192.25h767.5L383.75 576z" horiz-adv-x="767.5" />
+<glyph glyph-name="unfold" unicode="&#xf039;" d="M384 448h128V640h128L448 832 256 640h128V448zM576 576v-64h224L672 384H224L96 512h224v64H0v-63.999L160 352 0 192v-64h320v64H96l128 128h448l128-128H576v-64h320v64L736 352l160 160.001V576H576zM512 256H384v-192H256l192-192 192 192H512V256z" horiz-adv-x="896" />
+<glyph glyph-name="unmute" unicode="&#xf0ba;" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM538.51 410.51c-12.496 12.497-32.758 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 24.994-24.993 24.994-65.516 0-90.51-12.496-12.496-12.496-32.758 0-45.255 12.497-12.496 32.759-12.496 45.255 0C588.497 279.47900000000004 588.497 360.523 538.51 410.51zM629.02 501.019c-12.495 12.497-32.758 12.497-45.255 0-12.495-12.496-12.495-32.758 0-45.255 74.981-74.98 74.981-196.548 0-271.528-12.495-12.497-12.495-32.76 0-45.256 12.497-12.496 32.76-12.496 45.255 0C728.994 238.95399999999995 728.994 401.045 629.02 501.019zM719.529 591.529c-12.497 12.497-32.76 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 124.968-124.968 124.968-327.58 0-452.548-12.496-12.497-12.496-32.759 0-45.255 12.495-12.497 32.758-12.497 45.255 0C869.49 198.433 869.49 441.568 719.529 591.529z" horiz-adv-x="896" />
+<glyph glyph-name="versions" unicode="&#xf064;" d="M0 128h128v64H64V448h64v64H0V128zM384 640v-640h512V640H384zM768 128H512V512h256V128zM192 64h128v64h-64V512h64v64H192V64z" horiz-adv-x="896" />
+<glyph glyph-name="x" unicode="&#xf081;" d="M640 512L512 640 320 448 128 640 0 512l192-192L0 128l128-128 192 192 192-192 128 128L448 320 640 512z" horiz-adv-x="640" />
+<glyph glyph-name="zap" unicode="&#x26A1;" d="M640 384H384L576 832 0 256h256L64-192 640 384z" horiz-adv-x="640" />
+</font>
+</defs>
+</svg>
diff --git a/themes/CleanFS/fonts/octicons/octicons.ttf b/themes/CleanFS/fonts/octicons/octicons.ttf
new file mode 100644
index 0000000..921111a
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.ttf
Binary files differ
diff --git a/themes/CleanFS/fonts/octicons/octicons.woff b/themes/CleanFS/fonts/octicons/octicons.woff
new file mode 100644
index 0000000..b49b54c
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/octicons.woff
Binary files differ
diff --git a/themes/CleanFS/fonts/octicons/sprockets-octicons.scss b/themes/CleanFS/fonts/octicons/sprockets-octicons.scss
new file mode 100644
index 0000000..1e8d1ca
--- /dev/null
+++ b/themes/CleanFS/fonts/octicons/sprockets-octicons.scss
@@ -0,0 +1,232 @@
+@font-face {
+ font-family: 'octicons';
+ src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
+ font-url('octicons.woff') format('woff'),
+ font-url('octicons.ttf') format('truetype'),
+ font-url('octicons.svg#octicons') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+ font: normal normal normal 16px/1 octicons;
+ display: inline-block;
+ text-decoration: none;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /* ï‚Š */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /* ï‚Ž */
+.octicon-alignment-unalign:before { content: '\f08b'} /* ï‚‹ */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /* ï€ */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /* ï‚  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /* ï‚¡ */
+.octicon-arrow-small-right:before { content: '\f071'} /* ï± */
+.octicon-arrow-small-up:before { content: '\f09f'} /* ï‚Ÿ */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /* ï© */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /* ï» */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /* ïˆ */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /* ï‚‘ */
+.octicon-calendar:before { content: '\f068'} /* ï¨ */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /* ï¶ */
+.octicon-chevron-down:before { content: '\f0a3'} /* ï‚£ */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /* ï¸ */
+.octicon-chevron-up:before { content: '\f0a2'} /* ï‚¢ */
+.octicon-circle-slash:before { content: '\f084'} /* ï‚„ */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /* ï† */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /* ïŸ */
+.octicon-color-mode:before { content: '\f065'} /* ï¥ */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /* ï */
+.octicon-credit-card:before { content: '\f045'} /* ï… */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /* ï½ */
+.octicon-database:before { content: '\f096'} /* ï‚– */
+.octicon-device-camera:before { content: '\f056'} /* ï– */
+.octicon-device-camera-video:before { content: '\f057'} /* ï— */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /* ï */
+.octicon-diff-added:before { content: '\f06b'} /* ï« */
+.octicon-diff-ignored:before { content: '\f099'} /* ï‚™ */
+.octicon-diff-modified:before { content: '\f06d'} /* ï­ */
+.octicon-diff-removed:before { content: '\f06c'} /* ï¬ */
+.octicon-diff-renamed:before { content: '\f06e'} /* ï® */
+.octicon-ellipsis:before { content: '\f09a'} /* ï‚š */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /* ïŽ */
+.octicon-file-binary:before { content: '\f094'} /* ï‚” */
+.octicon-file-code:before { content: '\f010'} /* ï€ */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /* ï‚° */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /* ï‚ */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /* ïƒ */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /* ï¾ */
+.octicon-home:before { content: '\f08d'} /* ï‚ */
+.octicon-horizontal-rule:before { content: '\f070'} /* ï° */
+.octicon-hourglass:before { content: '\f09e'} /* ï‚ž */
+.octicon-hubot:before { content: '\f09d'} /* ï‚ */
+.octicon-inbox:before { content: '\f0cf'} /* ïƒ */
+.octicon-info:before { content: '\f059'} /* ï™ */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /* ï² */
+.octicon-jump-left:before { content: '\f0a5'} /* ï‚¥ */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /* ï³ */
+.octicon-key:before { content: '\f049'} /* ï‰ */
+.octicon-keyboard:before { content: '\f00d'} /* ï€ */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /* ïœ */
+.octicon-link-external:before { content: '\f07f'} /* ï¿ */
+.octicon-list-ordered:before { content: '\f062'} /* ï¢ */
+.octicon-list-unordered:before { content: '\f061'} /* ï¡ */
+.octicon-location:before { content: '\f060'} /* ï  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /* ïª */
+.octicon-logo-github:before { content: '\f092'} /* ï‚’ */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /* ï‘ */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /* ï· */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /* ïµ */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /* ï´ */
+.octicon-move-right:before { content: '\f0a9'} /* ï‚© */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /* ï‚€ */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /* ï˜ */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /* ï */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /* ï‚» */
+.octicon-playback-play:before { content: '\f0bf'} /* ï‚¿ */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /* ï */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /* ï’ */
+.octicon-primitive-square:before { content: '\f053'} /* ï“ */
+.octicon-pulse:before { content: '\f085'} /* ï‚… */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /* ï£ */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /* ï€ */
+.octicon-repo-clone:before { content: '\f04c'} /* ïŒ */
+.octicon-repo-force-push:before { content: '\f04a'} /* ïŠ */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /* ï‡ */
+.octicon-screen-full:before { content: '\f066'} /* ï¦ */
+.octicon-screen-normal:before { content: '\f067'} /* ï§ */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /* ï‚— */
+.octicon-settings:before { content: '\f07c'} /* ï¼ */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /* ï‚ */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /* ïž */
+.octicon-thumbsdown:before { content: '\f0db'} /*  */
+.octicon-thumbsup:before { content: '\f0da'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /* ïƒ */
+.octicon-triangle-down:before { content: '\f05b'} /* ï› */
+.octicon-triangle-left:before { content: '\f044'} /* ï„ */
+.octicon-triangle-right:before { content: '\f05a'} /* ïš */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /* ï¤ */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /* ï‚ */
+.octicon-zap:before { content: '\26A1'} /* âš¡ */
diff --git a/themes/CleanFS/geshi.css b/themes/CleanFS/geshi.css
new file mode 100644
index 0000000..4bfda12
--- /dev/null
+++ b/themes/CleanFS/geshi.css
@@ -0,0 +1,16 @@
+/* geshi code syntax highlighting colors */
+.code .br0 {color:#6c6;}
+.code .es0 {color:#009;font-weight:700;}
+.code .kw1 {color:#b1b100;}
+.code .kw2 {color:#000;font-weight:700;}
+.code .kw3 {color:#006;}
+.code .kw4 {color:#933;}
+.code .me0 {color:#060;}
+.code .nu0 {color:#c6c;}
+.code .re4 {color:#099;}
+.code .sc0 {color:#0bd;}
+.code .sc1 {color:#db0;}
+.code .sc2 {color:#090;}
+.code .st0 {color:red;}
+.code .co1,.code .co2,.code .coMULTI {color:gray;font-style:italic;}
+.code .kw5,.code .re0,.code .re1,.code .re2 {color:#00f;}
diff --git a/themes/CleanFS/img/black/calendar_alt_fill_16x16.png b/themes/CleanFS/img/black/calendar_alt_fill_16x16.png
new file mode 100644
index 0000000..d39c383
--- /dev/null
+++ b/themes/CleanFS/img/black/calendar_alt_fill_16x16.png
Binary files differ
diff --git a/themes/CleanFS/img/black/comment_stroke_16x14.png b/themes/CleanFS/img/black/comment_stroke_16x14.png
new file mode 100644
index 0000000..e4ee36b
--- /dev/null
+++ b/themes/CleanFS/img/black/comment_stroke_16x14.png
Binary files differ
diff --git a/themes/CleanFS/img/black/loop_alt3_12x9.png b/themes/CleanFS/img/black/loop_alt3_12x9.png
new file mode 100644
index 0000000..f0fe7ce
--- /dev/null
+++ b/themes/CleanFS/img/black/loop_alt3_12x9.png
Binary files differ
diff --git a/themes/CleanFS/img/caret.gif b/themes/CleanFS/img/caret.gif
new file mode 100644
index 0000000..0952569
--- /dev/null
+++ b/themes/CleanFS/img/caret.gif
Binary files differ
diff --git a/themes/CleanFS/img/gray/blocking_13x12.png b/themes/CleanFS/img/gray/blocking_13x12.png
new file mode 100644
index 0000000..d09d45f
--- /dev/null
+++ b/themes/CleanFS/img/gray/blocking_13x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/calendar_alt_stroke_12x12.png b/themes/CleanFS/img/gray/calendar_alt_stroke_12x12.png
new file mode 100644
index 0000000..627d066
--- /dev/null
+++ b/themes/CleanFS/img/gray/calendar_alt_stroke_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/cog_alt_12x12.png b/themes/CleanFS/img/gray/cog_alt_12x12.png
new file mode 100644
index 0000000..ff9d09d
--- /dev/null
+++ b/themes/CleanFS/img/gray/cog_alt_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/comment_stroke_16x14.png b/themes/CleanFS/img/gray/comment_stroke_16x14.png
new file mode 100644
index 0000000..9c603a8
--- /dev/null
+++ b/themes/CleanFS/img/gray/comment_stroke_16x14.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/compass_12x12.png b/themes/CleanFS/img/gray/compass_12x12.png
new file mode 100644
index 0000000..3b9d226
--- /dev/null
+++ b/themes/CleanFS/img/gray/compass_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/dependent_13x12.png b/themes/CleanFS/img/gray/dependent_13x12.png
new file mode 100644
index 0000000..9299fd9
--- /dev/null
+++ b/themes/CleanFS/img/gray/dependent_13x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/document_alt_stroke_9x12.png b/themes/CleanFS/img/gray/document_alt_stroke_9x12.png
new file mode 100644
index 0000000..efe2dab
--- /dev/null
+++ b/themes/CleanFS/img/gray/document_alt_stroke_9x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/folder_stroke_12x12.png b/themes/CleanFS/img/gray/folder_stroke_12x12.png
new file mode 100644
index 0000000..b243180
--- /dev/null
+++ b/themes/CleanFS/img/gray/folder_stroke_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/list_12x11.png b/themes/CleanFS/img/gray/list_12x11.png
new file mode 100644
index 0000000..6bd122d
--- /dev/null
+++ b/themes/CleanFS/img/gray/list_12x11.png
Binary files differ
diff --git a/themes/CleanFS/img/gray/pin_24x24.png b/themes/CleanFS/img/gray/pin_24x24.png
new file mode 100644
index 0000000..d690ce7
--- /dev/null
+++ b/themes/CleanFS/img/gray/pin_24x24.png
Binary files differ
diff --git a/themes/CleanFS/img/green/check_24x20.png b/themes/CleanFS/img/green/check_24x20.png
new file mode 100644
index 0000000..55d4820
--- /dev/null
+++ b/themes/CleanFS/img/green/check_24x20.png
Binary files differ
diff --git a/themes/CleanFS/img/red/x_alt_24x24.png b/themes/CleanFS/img/red/x_alt_24x24.png
new file mode 100644
index 0000000..ecd2fc3
--- /dev/null
+++ b/themes/CleanFS/img/red/x_alt_24x24.png
Binary files differ
diff --git a/themes/CleanFS/img/white/calendar_alt_stroke_12x12.png b/themes/CleanFS/img/white/calendar_alt_stroke_12x12.png
new file mode 100644
index 0000000..b287d04
--- /dev/null
+++ b/themes/CleanFS/img/white/calendar_alt_stroke_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/white/cog_alt_12x12.png b/themes/CleanFS/img/white/cog_alt_12x12.png
new file mode 100644
index 0000000..794fbcf
--- /dev/null
+++ b/themes/CleanFS/img/white/cog_alt_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/white/compass_12x12.png b/themes/CleanFS/img/white/compass_12x12.png
new file mode 100644
index 0000000..19b4fd3
--- /dev/null
+++ b/themes/CleanFS/img/white/compass_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/white/document_alt_stroke_9x12.png b/themes/CleanFS/img/white/document_alt_stroke_9x12.png
new file mode 100644
index 0000000..faf86fc
--- /dev/null
+++ b/themes/CleanFS/img/white/document_alt_stroke_9x12.png
Binary files differ
diff --git a/themes/CleanFS/img/white/folder_stroke_12x12.png b/themes/CleanFS/img/white/folder_stroke_12x12.png
new file mode 100644
index 0000000..5771e57
--- /dev/null
+++ b/themes/CleanFS/img/white/folder_stroke_12x12.png
Binary files differ
diff --git a/themes/CleanFS/img/white/list_12x11.png b/themes/CleanFS/img/white/list_12x11.png
new file mode 100644
index 0000000..75c1fee
--- /dev/null
+++ b/themes/CleanFS/img/white/list_12x11.png
Binary files differ
diff --git a/themes/CleanFS/index.html b/themes/CleanFS/index.html
new file mode 100644
index 0000000..080fe88
--- /dev/null
+++ b/themes/CleanFS/index.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<META HTTP-EQUIV="refresh" CONTENT="2; URL=../../index.php">
+</head>
+<body>
+</body>
+</html> \ No newline at end of file
diff --git a/themes/CleanFS/kaboodleloop.png b/themes/CleanFS/kaboodleloop.png
new file mode 100644
index 0000000..f0fe7ce
--- /dev/null
+++ b/themes/CleanFS/kaboodleloop.png
Binary files differ
diff --git a/themes/CleanFS/left.png b/themes/CleanFS/left.png
new file mode 100644
index 0000000..fbbf1e2
--- /dev/null
+++ b/themes/CleanFS/left.png
Binary files differ
diff --git a/themes/CleanFS/mime/application.png b/themes/CleanFS/mime/application.png
new file mode 100644
index 0000000..b1366b1
--- /dev/null
+++ b/themes/CleanFS/mime/application.png
Binary files differ
diff --git a/themes/CleanFS/mime/application/octet-stream.png b/themes/CleanFS/mime/application/octet-stream.png
new file mode 100644
index 0000000..257b205
--- /dev/null
+++ b/themes/CleanFS/mime/application/octet-stream.png
Binary files differ
diff --git a/themes/CleanFS/mime/application/pdf.png b/themes/CleanFS/mime/application/pdf.png
new file mode 100644
index 0000000..f4863cb
--- /dev/null
+++ b/themes/CleanFS/mime/application/pdf.png
Binary files differ
diff --git a/themes/CleanFS/mime/application/x-gzip.png b/themes/CleanFS/mime/application/x-gzip.png
new file mode 100644
index 0000000..e2b67dc
--- /dev/null
+++ b/themes/CleanFS/mime/application/x-gzip.png
Binary files differ
diff --git a/themes/CleanFS/mime/audio.png b/themes/CleanFS/mime/audio.png
new file mode 100644
index 0000000..f88632d
--- /dev/null
+++ b/themes/CleanFS/mime/audio.png
Binary files differ
diff --git a/themes/CleanFS/mime/image.png b/themes/CleanFS/mime/image.png
new file mode 100644
index 0000000..f1c8572
--- /dev/null
+++ b/themes/CleanFS/mime/image.png
Binary files differ
diff --git a/themes/CleanFS/mime/text.png b/themes/CleanFS/mime/text.png
new file mode 100644
index 0000000..3c3b4b0
--- /dev/null
+++ b/themes/CleanFS/mime/text.png
Binary files differ
diff --git a/themes/CleanFS/mime/text/html.png b/themes/CleanFS/mime/text/html.png
new file mode 100644
index 0000000..f56567f
--- /dev/null
+++ b/themes/CleanFS/mime/text/html.png
Binary files differ
diff --git a/themes/CleanFS/mime/video.png b/themes/CleanFS/mime/video.png
new file mode 100644
index 0000000..d050afa
--- /dev/null
+++ b/themes/CleanFS/mime/video.png
Binary files differ
diff --git a/themes/CleanFS/oldwebkitsiblingfix.css b/themes/CleanFS/oldwebkitsiblingfix.css
new file mode 100644
index 0000000..ae7f541
--- /dev/null
+++ b/themes/CleanFS/oldwebkitsiblingfix.css
@@ -0,0 +1,9 @@
+/*
+used for pure html/css3 switches (with ~ sibling) for older webkit based browsers
+(android ~4.3, safari ~5.1, chrome ?, TODO: exact (webkit)versions for the useragent filter)
+see https://css-tricks.com/webkit-sibling-bug/
+Hack may increase cpuusage due the infinite animation loop, see also https://codepen.io/simeydotme/post/hot-pockets
+So load this file only if really necessary (check useragent) (or find a better pure css workaround)
+*/
+body { -webkit-animation: webkitfix infinite 1s; }
+@-webkit-keyframes webkitfix { from { display: block } to { display: block } }
diff --git a/themes/CleanFS/reset.css b/themes/CleanFS/reset.css
new file mode 100644
index 0000000..7e01561
--- /dev/null
+++ b/themes/CleanFS/reset.css
@@ -0,0 +1,34 @@
+/* reset.css - Resets default browser CSS. */
+html { margin:0;padding:0;border:0;}
+body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, code,
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+ margin: 0;padding: 0;border: 0;font-size: 100%;vertical-align: baseline;
+}
+/* This helps to make newer HTML5 elements behave like DIVs in older browsers */
+article, aside, details, figcaption, figure, dialog,
+footer, header, hgroup, menu, nav, section {
+ display:block;
+}
+body {
+ line-height: 1.5; /* Line-height should always be unitless! */
+ background: #fff;
+/* direction:rtl; */
+}
+table {border-collapse: separate;border-spacing: 0;}
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+ float:none !important; /* float:none prevents the span-x classes from breaking table-cell display */
+}
+table, th, td {vertical-align: middle;}
+/* Remove possible quote marks (") from <q>, <blockquote>. */
+blockquote:before, blockquote:after, q:before, q:after { content: ''; }
+blockquote, q { quotes: "" ""; }
+/* Remember to define your own focus styles! */
+:focus { outline: 0; }
diff --git a/themes/CleanFS/right.png b/themes/CleanFS/right.png
new file mode 100644
index 0000000..a909696
--- /dev/null
+++ b/themes/CleanFS/right.png
Binary files differ
diff --git a/themes/CleanFS/templates/admin.cat.tpl b/themes/CleanFS/templates/admin.cat.tpl
new file mode 100644
index 0000000..02492b4
--- /dev/null
+++ b/themes/CleanFS/templates/admin.cat.tpl
@@ -0,0 +1,4 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('categorylist')); ?></h3>
+ <?php $this->display('common.cat.tpl'); ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.checks.tpl b/themes/CleanFS/templates/admin.checks.tpl
new file mode 100644
index 0000000..37ebc30
--- /dev/null
+++ b/themes/CleanFS/templates/admin.checks.tpl
@@ -0,0 +1,99 @@
+<div id="toolbox">
+<div>PHP version: <?php echo PHP_VERSION; ?></div>
+<?php if(isset($utf8mb4upgradable)) { echo '<div class="error">'.Filters::noXSS($utf8mb4upgradable).'</div>'; } ?>
+<?php if(isset($oldmysqlversion)) { echo '<div class="error">'.Filters::noXSS($oldmysqlversion).'</div>'; } ?>
+<div>ADOdb version: <?php if(isset($adodbversion)) { echo Filters::noXSS($adodbversion); } ?></div>
+<div>HTMLPurifier version: <?php if(isset($htmlpurifierversion)) { echo Filters::noXSS($htmlpurifierversion); } ?></div>
+<div>passwdcrypt: <?php echo Filters::noXSS($passwdcrypt); ?></div>
+<?php if(isset($hashlengths)) { echo '<div>password hash lengths: '.$hashlengths.'</div>'; } ?>
+
+<?php if(isset($registrations)): ?>
+<h4><?= $regcount ?> unfinished registrations</h4>
+<table>
+<thead>
+<tr>
+<th>reg_time</th>
+<th>user_name</th>
+<th>email_address</th>
+</tr>
+</thead>
+<tbody>
+<?php foreach($registrations as $reg): ?>
+<tr>
+<td><?= formatDate($reg['reg_time']) ?></td>
+<td><?= Filters::noXSS($reg['user_name']) ?></td>
+<td><?= Filters::noXSS($reg['email_address']) ?></td>
+</tr>
+<?php endforeach; ?>
+<?php endif; ?>
+</tbody>
+</table>
+
+<?php if(isset($fstables)): ?>
+<style>
+.dbtable{ background-color:#ccc;}
+.dbtable td {border-bottom:1px solid #999;}
+.dbfield{ background-color:#eee;}
+#togglefields { display:none; }
+#togglefields ~ label:after { content:'Hide Fields'; }
+#togglefields:checked ~ label:after { content:'Show Fields'; }
+#togglefields:checked ~ #dbtables .dbfield { display:none; }
+</style>
+<div>
+<div>default_character_set_name: <?=$fsdb['default_character_set_name'] ?></div>
+<div>default_collation_name: <?=$fsdb['default_collation_name'] ?></div>
+</div>
+<input type="checkbox" id="togglefields" name="togglefields" checked="checked" />
+<label for="togglefields" class="button"></label>
+<table id="dbtables">
+<thead>
+<tr class="dbtable">
+<th>tabl_name</th>
+<th>table_type</th>
+<th></th>
+<th>default collation</th>
+<th>comment</th>
+</tr>
+<tr class="dbfield">
+<th>column_name</th>
+<th>data_type</th>
+<th>character_set_name</th>
+<th>collation_name</th>
+<th>comment</th>
+</tr>
+</thead>
+<tbody>
+<?php
+$lasttable='';
+$ti=-1; # $fstables index
+foreach($fsfields as $f):
+ # Show table info row if not yet for that field
+ # This logic fails if there exists a table within $fstables without fields in $fsfields
+ # But for our usecase this should be ok.
+ if ($lasttable != $f['table_name']):
+ $ti++;
+ ?>
+ <tr class="dbtable">
+ <td><?= Filters::noXSS($fstables[$ti]['table_name']) ?></td>
+ <td><?= $fstables[$ti]['table_type'] ?></td>
+ <td></td>
+ <td><?= $fstables[$ti]['table_collation'] ?></td>
+ <td><?= Filters::noXSS($fstables[$ti]['table_comment']) ?></td>
+ </tr>
+ <?php endif; ?>
+<tr class="dbfield">
+<td><?= Filters::noXSS($f['column_name']) ?></td>
+<td><?= $f['column_type'] ?></td>
+<td><?= $f['character_set_name'] ?></td>
+<td><?= $f['collation_name'] ?></td>
+<td><?= Filters::noXSS($f['column_comment']) ?></td>
+</tr>
+<?php
+$lasttable=$f['table_name'];
+endforeach;
+?>
+</tbody>
+</table>
+<?php endif; ?>
+
+</div>
diff --git a/themes/CleanFS/templates/admin.editallusers.tpl b/themes/CleanFS/templates/admin.editallusers.tpl
new file mode 100644
index 0000000..e78edec
--- /dev/null
+++ b/themes/CleanFS/templates/admin.editallusers.tpl
@@ -0,0 +1,4 @@
+<div id="toolbox">
+ <h3><?= eL('admintoolbox') ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?= eL('editallusers') ?></h3>
+ <?php $this->display('common.editallusers.tpl'); ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.editgroup.tpl b/themes/CleanFS/templates/admin.editgroup.tpl
new file mode 100644
index 0000000..6bfbca8
--- /dev/null
+++ b/themes/CleanFS/templates/admin.editgroup.tpl
@@ -0,0 +1,4 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolboxlong')); ?> :: <?php echo Filters::noXSS(L('editgroup')); ?></h3>
+ <?php $this->display('common.editgroup.tpl'); ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.groups.tpl b/themes/CleanFS/templates/admin.groups.tpl
new file mode 100644
index 0000000..0d66177
--- /dev/null
+++ b/themes/CleanFS/templates/admin.groups.tpl
@@ -0,0 +1,147 @@
+<div id="toolbox">
+ <ul id="submenu">
+ <li><a href="#users_tab"><?php echo Filters::noXSS(L('users')); ?></a></li>
+ <li><a href="#groups_tab"><?php echo Filters::noXSS(L('globalgroups')); ?></a></li>
+ </ul>
+ <div id="users_tab" class="tab">
+ <a class="button" href="<?php echo Filters::noXSS(CreateURL('admin', 'newuser', $proj->id)); ?>"><i class="good fa fa-user-plus fa-lg fa-fw"></i><?php echo L('newuser'); ?></a>
+ <a class="button" href="<?php echo Filters::noXSS(CreateURL('admin', 'newuserbulk', $proj->id)); ?>"><i class="good fa fa-user-times fa-lg fa-fw"></i><?php echo L('newuserbulk'); ?></a>
+ <a class="button" href="<?php echo Filters::noXSS(CreateURL('admin', 'editallusers', $proj->id)); ?>"><i class="fa fa-group fa-lg fa-fw"></i><?php echo L('editallusers'); ?></a>
+ <div class="groupedit">
+<!--
+ <form action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+ <label for="selectgroup"><?php echo Filters::noXSS(L('editgroup')); ?></label>
+ <select name="id" id="selectgroup"><?php echo tpl_options(Flyspray::ListGroups()); ?></select>
+ <button type="submit"><?php echo Filters::noXSS(L('edit')); ?></button>
+ <input type="hidden" name="do" value="admin" />
+ <input type="hidden" name="area" value="editgroup" />
+ <input type="hidden" name="project" value="<?php echo $proj->id; ?>" />
+ </form>
+-->
+ <form action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+ <label for="edit_user"><?php echo Filters::noXSS(L('edituser')); ?></label>
+ <?php echo tpl_userselect('user_name', '', 'edit_user'); ?>
+ <button type="submit"><?php echo Filters::noXSS(L('edit')); ?></button>
+ <input type="hidden" name="do" value="admin" />
+ <input type="hidden" name="area" value="users" />
+ <input type="hidden" name="project" value="<?php echo $proj->id; ?>" />
+ </form>
+ </div>
+ </div>
+ <div id="groups_tab" class="tab">
+<div><a class="button" href="<?php echo Filters::noXSS(CreateURL('admin', 'newgroup', $proj->id)); ?>"><i class="fa fa-group fa-lg fa-fw"></i><?php echo Filters::noXSS(L('newgroup')); ?></a></div>
+
+<?php
+$perm_fields = array(
+'group_open',
+'is_admin',
+'manage_project',
+'view_tasks',
+'view_groups_tasks', # TODO: What is the definition of "group's task" and how does it effect project views?
+'view_own_tasks', # TODO: What is the definition of "own task" and how does it effect project views?
+'open_new_tasks',
+'add_multiple_tasks',
+'modify_own_tasks',
+'modify_all_tasks',
+'create_attachments',
+'delete_attachments',
+'assign_to_self',
+'assign_others_to_self',
+'edit_assignments',
+'close_own_tasks',
+'close_other_tasks',
+'view_roadmap',
+'view_history',
+'view_reports',
+'add_votes',
+'view_comments',
+'add_comments',
+'edit_comments',
+'edit_own_comments',
+'delete_comments',
+'view_estimated_effort',
+'view_current_effort_done',
+'track_effort'
+);
+
+$yesno = array(
+ '<td style="color:#ccc" title="'.eL('no').'">-</td>',
+ '<td title="'.eL('yes').'"><i class="good fa fa-check fa-lg"></i></td>'
+);
+
+$perms=array();
+$gmembers='';
+$gnames='';
+$gdesc='';
+$cols='';
+foreach ($groups as $group){
+ $cols.='<col class="group g'.$group['group_id'].($group['group_open']==0?' inactive':'').'"></col>';
+ $gmembers.='<td>'.$group['users'].'</td>';
+ $gnames .='<td><a class="button" title="'.eL('editgroup').'" href="'.( Filters::noXSS(CreateURL('editgroup', $group['group_id'], 'admin'))).'">'
+ .Filters::noXSS($group['group_name'])
+ .'<i class="fa fa-pencil fa-lg fa-fw"></i></a></td>';
+ $gdesc .='<td>'.Filters::noXSS($group['group_desc']).'</td>';
+ foreach ($group as $key => $val) {
+ if (!is_numeric($key) && in_array($key, $perm_fields)) {
+ $perms[$key][]=$val;
+ }
+ }
+}
+?>
+<style>
+.perms {border-collapse:collapse;}
+.perms tbody tr:hover {background-color:#eee;}
+.perms td, .perms th{border:1px solid #999;}
+.perms thead th, .perms thead td {text-align:center;}
+.perms tbody th{text-align:right;}
+.perms tbody td{width:100px;text-align:center;}
+.perms tbody span i:first-child {color: #090;}
+</style>
+<table class="perms">
+<colgroup>
+<col></col>
+<?php echo $cols; ?>
+</colgroup>
+<thead>
+<tr>
+<th><?php echo L('groupmembers'); ?></th>
+<?php echo $gmembers; ?>
+</tr>
+<tr>
+<th><?php echo L('group'); ?></th>
+<?php echo $gnames; ?>
+</tr>
+<tr>
+<th><?php echo L('description'); ?></th>
+<?php echo $gdesc; ?>
+</tr>
+</thead>
+<tbody>
+<?php foreach ($perm_fields as $p): ?>
+<tr>
+ <th><?php echo eL(str_replace('_', '', $p)); ?></th>
+<?php
+require_once('permicons.tpl');
+$i=0;
+# TODO: make it visible that a granted 'view_tasks' overrules 'view_groups_tasks' and 'own_tasks'. (like is_admin)
+foreach($perms[$p] as $val){
+ if ($perms['is_admin'][$i]==1 && $val == 0){
+ if(isset($permicons[$p])){
+ echo '<td title="'.eL('yes').' - Permission granted because of is_admin">( '.$permicons[$p].' )</td>';
+ }else{
+ echo $yesno[1];
+ }
+ } elseif($val==1 && isset($permicons[$p])){
+ echo '<td>'.$permicons[$p].'</td>';
+ } else{
+ echo $yesno[$val];
+ }
+ $i++;
+}
+?>
+</tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+</div>
+</div>
diff --git a/themes/CleanFS/templates/admin.menu.tpl b/themes/CleanFS/templates/admin.menu.tpl
new file mode 100644
index 0000000..789a280
--- /dev/null
+++ b/themes/CleanFS/templates/admin.menu.tpl
@@ -0,0 +1,43 @@
+<?php $activeclass = ' class="active" '; ?>
+
+<div id="toolboxmenu">
+ <a id="globprefslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'prefs') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'prefs')); ?>"><?php echo Filters::noXSS(L('preferences')); ?></a>
+ <a id="globuglink"
+ <?php if(isset($_GET['area']) and in_array($_GET['area'], array('groups','newuser', 'newuserbulk', 'newgroup','editgroup', 'users', 'editallusers'))) echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'groups')); ?>"><?php echo Filters::noXSS(L('usersandgroups')); ?></a>
+ <a id="globttlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'tasktype') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'tasktype')); ?>"><?php echo Filters::noXSS(L('tasktypes') ); ?></a>
+ <a id="globcatlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'cat') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'cat')); ?>"><?php echo Filters::noXSS(L('categories') ); ?></a>
+ <a id="globtglink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'tag') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'tag')); ?>"><i class="fa fa-tag"></i> <?php echo Filters::noXSS(L('tags') ); ?></a>
+ <a id="globstatuslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'status') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'status')); ?>"><?php echo Filters::noXSS(L('taskstatuses') ); ?></a>
+ <a id="globreslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'resolution') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'resolution')); ?>"><?php echo Filters::noXSS(L('resolutions') ); ?></a>
+ <a id="globverlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'version') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'version')); ?>"><?php echo Filters::noXSS(L('versions') ); ?></a>
+ <a id="globoslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'os') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'os')); ?>"><i class="fa fa-linux"></i><i class="fa fa-windows"></i><i class="fa fa-apple"></i> <?php echo Filters::noXSS(L('operatingsystems')); ?></a>
+ <a id="globnewprojlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'newproject') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'newproject')); ?>"><?php echo Filters::noXSS(L('newproject')); ?></a>
+ <a id="userrequestlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'userrequest') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'userrequest')); ?>"><?php echo Filters::noXSS(L('pendingnewuserrequest')); ?></a>
+ <a id="translationslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'translations') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'translations')); ?>"><?php echo Filters::noXSS(L('translations')); ?></a>
+ <a id="checkslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'checks') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'checks')); ?>"><?php echo Filters::noXSS(L('adminchecks')); ?></a>
+</div>
diff --git a/themes/CleanFS/templates/admin.newgroup.tpl b/themes/CleanFS/templates/admin.newgroup.tpl
new file mode 100644
index 0000000..af4a758
--- /dev/null
+++ b/themes/CleanFS/templates/admin.newgroup.tpl
@@ -0,0 +1,7 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolbox')); ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('createnewgroup')); ?></h3>
+
+ <?php
+ $this->display('common.newgroup.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.newproject.tpl b/themes/CleanFS/templates/admin.newproject.tpl
new file mode 100644
index 0000000..db6c36f
--- /dev/null
+++ b/themes/CleanFS/templates/admin.newproject.tpl
@@ -0,0 +1,58 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('createnewproject')); ?></h3>
+ <?php echo tpl_form(CreateURL('admin', 'newproject')); ?>
+ <div>
+ <input type="hidden" name="action" value="admin.newproject" />
+ <input type="hidden" name="area" value="newproject" />
+ </div>
+ <ul class="form_elements">
+ <li>
+ <label for="projecttitle"><?php echo Filters::noXSS(L('projecttitle')); ?></label>
+ <input id="projecttitle" name="project_title" value="<?php echo Filters::noXSS(Req::val('project_title')); ?>" type="text" class="required text" size="40" maxlength="100" />
+ </li>
+ <li>
+ <label for="themestyle"><?php echo Filters::noXSS(L('themestyle')); ?></label>
+ <select id="themestyle" name="theme_style">
+ <?php echo tpl_options(Flyspray::listThemes(), Req::val('theme_style', $proj->prefs['theme_style']), true); ?>
+
+ </select>
+ </li>
+ <li>
+ <label for="langcode"><?php echo Filters::noXSS(L('language')); ?></label>
+ <select id="langcode" name="lang_code">
+ <?php echo tpl_options(Flyspray::listLangs(), Req::val('lang_code', $fs->prefs['lang_code']), true); ?>
+ </select>
+ </li>
+ <li>
+ <label for="intromesg"><?php echo Filters::noXSS(L('intromessage')); ?></label>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('intro_message', 8, 70, array('accesskey' => 'r', 'tabindex' => 8, 'id' => 'intromesg'), Req::val('intro_message', $proj->prefs['intro_message'])); ?>
+ <br />
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <button tabindex="9" type="button" onclick="showPreview('intromesg', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ </li>
+ <li>
+ <label for="othersview"><?php echo Filters::noXSS(L('othersview')); ?></label>
+ <?php echo tpl_checkbox('others_view', Req::val('others_view', 0), 'othersview'); ?>
+ </li>
+ <li>
+ <label for="othersviewroadmap"><?php echo Filters::noXSS(L('othersviewroadmap')); ?></label>
+ <?php echo tpl_checkbox('others_viewroadmap', Req::val('others_viewroadmap', 0), 'othersviewroadmap'); ?>
+ </li>
+ <li>
+ <label for="anonopen"><?php echo Filters::noXSS(L('allowanonopentask')); ?></label>
+ <?php echo tpl_checkbox('anon_open', Req::val('anon_open'), 'anonopen'); ?>
+ </li>
+ <li>
+ <label for="disp_intro"><?php echo Filters::noXSS(L('dispintro')); ?></label>
+ <?php echo tpl_checkbox('disp_intro', Req::val('disp_intro', 0), 'disp_intro'); ?>
+ </li>
+ <li>
+ <td class="buttons" colspan="2"><button type="submit" class="positive"><?php echo Filters::noXSS(L('createthisproject')); ?></button></td>
+ </li>
+ </ul>
+ </form>
+</div>
diff --git a/themes/CleanFS/templates/admin.newuser.tpl b/themes/CleanFS/templates/admin.newuser.tpl
new file mode 100644
index 0000000..825ab82
--- /dev/null
+++ b/themes/CleanFS/templates/admin.newuser.tpl
@@ -0,0 +1,7 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolbox')); ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('newuser')); ?></h3>
+
+ <?php
+ $this->display('common.newuser.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.newuserbulk.tpl b/themes/CleanFS/templates/admin.newuserbulk.tpl
new file mode 100644
index 0000000..189e04f
--- /dev/null
+++ b/themes/CleanFS/templates/admin.newuserbulk.tpl
@@ -0,0 +1,7 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolbox')); ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('newuserbulk')); ?></h3>
+
+ <?php
+ $this->display('common.newuserbulk.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.os.tpl b/themes/CleanFS/templates/admin.os.tpl
new file mode 100644
index 0000000..4bbe0da
--- /dev/null
+++ b/themes/CleanFS/templates/admin.os.tpl
@@ -0,0 +1,9 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('oslist')); ?></h3>
+
+ <?php
+ $this->assign('list_type', 'os');
+ $this->assign('rows', $proj->listOs(true));
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.prefs.tpl b/themes/CleanFS/templates/admin.prefs.tpl
new file mode 100644
index 0000000..97d7e6a
--- /dev/null
+++ b/themes/CleanFS/templates/admin.prefs.tpl
@@ -0,0 +1,529 @@
+<script type="text/javascript">
+function ShowHidePassword(id) {
+ if(document.getElementById(id).type=="text") {
+ document.getElementById(id).type="password";
+ } else {
+ document.getElementById(id).type="text";
+ }
+}
+</script>
+<script>
+ /*
+ * Second argument is always the parent calling to deactivate not needed childs
+ * Next args are all childsto be deactivated
+ */
+ function check_change(inverted)
+ {
+ var i;
+ var parent = arguments[1];
+
+ if(document.getElementById(parent).checked)
+ {
+ for (i = 2; i < arguments.length; i++)
+ {
+ if (inverted) {
+ document.getElementById(arguments[i]).checked = false;
+ document.getElementById(arguments[i]).disabled = true;
+ }
+ else {
+ document.getElementById(arguments[i]).checked = true;
+ document.getElementById(arguments[i]).disabled = false;
+ }
+ }
+ }
+ else
+ {
+ for (i = 2; i < arguments.length; i++)
+ {
+ if (inverted) {
+ document.getElementById(arguments[i]).disabled = false;
+ }
+ else {
+ document.getElementById(arguments[i]).disabled = true;
+ }
+ }
+ }
+ }
+</script>
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolboxlong')); ?> :: <?php echo Filters::noXSS(L('preferences')); ?></h3>
+ <?php echo tpl_form(CreateURL('admin', 'prefs')); ?>
+ <ul id="submenu">
+ <li><a href="#general"><?php echo Filters::noXSS(L('general')); ?></a></li>
+ <li><a href="#lookandfeel"><?php echo Filters::noXSS(L('lookandfeel')); ?></a></li>
+ <li><a href="#userregistration"><?php echo Filters::noXSS(L('userregistration')); ?></a></li>
+ <li><a href="#notifications"><?php echo Filters::noXSS(L('notifications')); ?></a></li>
+ <li><a href="#antispam"><?php echo Filters::noXSS(L('antispam')); ?></a></li>
+ </ul>
+
+ <div id="general" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="pagetitle"><?php echo Filters::noXSS(L('pagetitle')); ?></label>
+ <input id="pagetitle" name="page_title" type="text" class="text" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['page_title']); ?>" />
+ </li>
+
+ <li>
+ <label for="defaultproject"><?php echo Filters::noXSS(L('defaultproject')); ?></label>
+ <select id="defaultproject" name="default_project">
+ <?php echo tpl_options(array_merge(array(0 => L('allprojects')), Flyspray::listProjects()), $fs->prefs['default_project']); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="langcode"><?php echo Filters::noXSS(L('language')); ?></label>
+ <select id="langcode" name="lang_code">
+ <?php echo tpl_options(Flyspray::listLangs(), $fs->prefs['lang_code'], true); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="urlrewriting"><?php echo Filters::noXSS(L('urlrewriting')); ?></label>
+ <select id="urlrewriting" name="url_rewriting">
+ <?php echo tpl_options(array('1' => L('on'), '0' => L('off')), $fs->prefs['url_rewriting'], false); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="emailNoHTML"><?php echo Filters::noXSS(L('emailNoHTML')); ?></label>
+ <?php echo tpl_checkbox('emailNoHTML', $fs->prefs['emailNoHTML'], 'emailNoHTML'); ?>
+ </li>
+
+ <li>
+ <?php
+ // TODO WTF?? Isn't that an old temp fix?
+ if (!array_key_exists('logo', $fs->prefs)) {
+ $fs->prefs['logo'] = '';
+ }
+ ?>
+
+ <label for="logo"><?php echo Filters::noXSS(L('showlogo')); ?></label>
+ <?php if ($fs->prefs['logo']):?>
+ <img src="<?php echo Filters::noXSS($baseurl.'/'.$fs->prefs['logo']); ?>">
+ <?php endif ?>
+ </li>
+
+ <li>
+ <label for="logo_input">&nbsp;</label>
+ <input id="logo_input" name="logo" type="file" accept="image/*" value="<?php echo Filters::noXSS($fs->prefs['logo']); ?>" />
+ </li>
+ <li>
+ <label for="massops"><?php echo Filters::noXSS(L('massopsenable')); ?></label>
+ <?php echo tpl_checkbox('massops', $fs->prefs['massops'], 'massops'); ?>
+ </li>
+ <li>
+ <label for="enable_avatars"><?php echo Filters::noXSS(L('enableavatars')); ?></label>
+ <?php echo tpl_checkbox('enable_avatars', $fs->prefs['enable_avatars'], 'enable_avatars', 1, array('onclick'=>'check_change(false, "enable_avatars", "gravatars", "max_avatar_size")')); ?>
+ </li>
+
+ <li>
+ <label for="gravatars"><?php echo Filters::noXSS(L('showgravatars')); ?></label>
+ <?php echo tpl_checkbox('gravatars', $fs->prefs['gravatars'], 'gravatars'); ?>
+ </li>
+
+ <li>
+ <label for="max_avatar_size"><?php echo Filters::noXSS(L('maxavatarsize')); ?></label>
+ <input id="max_avatar_size" name="max_avatar_size" type="text" class="text" size="3" maxlength="3" value="<?php echo Filters::noXSS($fs->prefs['max_avatar_size']); ?>" />
+ </li>
+
+ <li>
+ <label for="hide_emails"><?php echo Filters::noXSS(L('hideemails')); ?></label>
+ <?php echo tpl_checkbox('hide_emails', $fs->prefs['hide_emails'], 'hide_emails'); ?>
+ </li>
+
+ <li>
+ <label for="dateformat"><?php echo Filters::noXSS(L('dateformat')); ?></label>
+ <select id="dateformat" name="dateformat">
+ <?php echo tpl_date_formats($fs->prefs['dateformat']); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="dateformat_extended"><?php echo Filters::noXSS(L('dateformat_extended')); ?></label>
+ <select id="dateformat_extended" name="dateformat_extended">
+ <?php echo tpl_date_formats($fs->prefs['dateformat_extended'], true); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="cache_feeds"><?php echo Filters::noXSS(L('cache_feeds')); ?></label>
+ <select id="cache_feeds" name="cache_feeds">
+ <?php echo tpl_options(array('0' => L('no_cache'), '1' => L('cache_disk'), '2' => L('cache_db')), $fs->prefs['cache_feeds']); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="disable_lostpw"><?php echo Filters::noXSS(L('disable_lostpw')); ?></label>
+ <?php echo tpl_checkbox('disable_lostpw', $fs->prefs['disable_lostpw'], 'disable_lostpw'); ?>
+
+ </li>
+
+ <li>
+ <label for="disablechangepw"><?php echo Filters::noXSS(L('disable_changepw')); ?></label>
+ <?php echo tpl_checkbox('disable_changepw', $fs->prefs['disable_changepw'], 'disablechangepw'); ?>
+ </li>
+
+ <li>
+ <label for="days_before_alert"><?php echo Filters::noXSS(L('daysbeforealert')); ?></label>
+ <input id="days_before_alert" name="days_before_alert" type="text" class="text" size="3" maxlength="3" value="<?php echo Filters::noXSS($fs->prefs['days_before_alert']); ?>" />
+ </li>
+
+ <li>
+ <label for="max_vote_per_day"><?php echo Filters::noXSS(L('maxvoteperday')); ?></label>
+ <input id="max_vote_per_day" name="max_vote_per_day" type="text" class="text" size="3" maxlength="3" value="<?php echo Filters::noXSS($fs->prefs['max_vote_per_day']); ?>" />
+ </li>
+
+ <li>
+ <label for="votes_per_project"><?php echo Filters::noXSS(L('votesperproject')); ?></label>
+ <input id="votes_per_project" name="votes_per_project" type="text" class="text" size="3" maxlength="3" value="<?php echo Filters::noXSS($fs->prefs['votes_per_project']); ?>" />
+ </li>
+
+ <li>
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('pageswelcomemsg')); ?></label>
+ <?php
+ $pages = array(
+ 'index' => L('tasklist'),
+ 'toplevel' => L('toplevel'),
+ 'reports' => L('reports'));
+ $selectedPages = explode(' ', $fs->prefs['pages_welcome_msg']);
+ echo tpl_double_select('pages_welcome_msg', $pages, $selectedPages, false, false);
+ ?>
+ </li>
+
+ <li>
+ <label class="labeltextarea" for="intromesg"><?php echo Filters::noXSS(L('mainmessage')); ?></label>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <button tabindex="9" type="button" onclick="showPreview('intromesg', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('intro_message', 8, 70, array('accesskey' => 'r', 'tabindex' => 8, 'id' => 'intromesg'), Post::val('intro_message', $fs->prefs['intro_message'])); ?>
+ </li>
+ </ul>
+ </div>
+
+ <div id="userregistration" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="allowusersignups"><?php echo Filters::noXSS(L('anonreg')); ?></label>
+ <?php echo tpl_checkbox('anon_reg', $fs->prefs['anon_reg'], 'allowusersignups'); ?>
+ </li>
+
+ <li>
+ <label for="onlyoauthreg"><?php echo Filters::noXSS(L('onlyoauthreg')); ?></label>
+ <?php echo tpl_checkbox('only_oauth_reg', $fs->prefs['only_oauth_reg'], 'onlyoauthreg', 1, array('onclick'=>'check_change(true, "onlyoauthreg", "needapproval", "spamproof")')); ?>
+ </li>
+
+ <li>
+ <label for="needapproval"><?php echo Filters::noXSS(L('regapprovedbyadmin')); ?></label>
+ <?php echo tpl_checkbox('need_approval', $fs->prefs['need_approval'], 'needapproval', 1, ($fs->prefs['only_oauth_reg']) ? array('disabled' => 'disabled', 'onclick' => 'check_change(true, "needapproval", "spamproof")') : array('onclick' => 'check_change("needapproval", "spamproof")')); ?>
+ </li>
+
+ <li><?php /* TODO rename misleading 'spamproof' pref to something like email_verify */ ?>
+ <label for="spamproof"><?php echo Filters::noXSS(L('spamproof')); ?></label>
+ <?php echo tpl_checkbox('spam_proof', $fs->prefs['spam_proof'], 'spamproof', 1, ($fs->prefs['need_approval'] || $fs->prefs['only_oauth_reg'] ) ? array('disabled' => 'true') : ''); ?>
+ </li>
+
+ <li>
+ <label for="repeat_password"><?php echo Filters::noXSS(L('repeatpassword')); ?></label>
+ <?php echo tpl_checkbox('repeat_password', $fs->prefs['repeat_password'], 'repeat_password'); ?>
+ </li>
+
+ <li>
+ <label for="repeat_emailaddress"><?php echo Filters::noXSS(L('repeatemailaddress')); ?></label>
+ <?php echo tpl_checkbox('repeat_emailaddress', $fs->prefs['repeat_emailaddress'], 'repeat_emailaddress'); ?>
+ </li>
+
+ <li>
+ <label for="notify_registration"><?php echo Filters::noXSS(L('notify_registration')); ?></label>
+ <?php echo tpl_checkbox('notify_registration', $fs->prefs['notify_registration'], 'notify_registration'); ?>
+ </li>
+
+ <li>
+ <label for="defaultglobalgroup"><?php echo Filters::noXSS(L('defaultglobalgroup')); ?></label>
+ <select id="defaultglobalgroup" name="anon_group">
+ <?php echo tpl_options(Flyspray::listGroups(), $fs->prefs['anon_group']); ?>
+ </select>
+ </li>
+
+ <li>
+ <label><?php echo Filters::noXSS(L('activeoauths')); ?></label>
+ <?php
+ $oauths = array('github', 'google', 'facebook', 'microsoft'/*, 'instagram', 'eventbrite', 'linkedin', 'vkontakte'*/); //TODO try the commented out for FS 1.1
+ $selectedOauths = explode(' ', $fs->prefs['active_oauths']);
+ echo tpl_double_select('active_oauths', $oauths, $selectedOauths, true, false);
+ ?>
+ </li>
+
+ </ul>
+ </div>
+
+<div id="antispam" class="tab">
+ <h2><?php echo Filters::noXSS(L('antispam')); ?></h2>
+ <p><?php echo Filters::noXSS(L('antispamprefsinfo')); ?></p>
+
+ <h3>Securimage</h3>
+ <p><?php echo Filters::noXSS(L('securimageprefsinfo')); ?></p>
+ <ul class="form_elements">
+ <li>
+ <label for="captcha_securimage"><?php echo Filters::noXSS(L('securimageenable')); ?></label>
+ <?php echo tpl_checkbox('captcha_securimage', isset($fs->prefs['captcha_securimage'])?$fs->prefs['captcha_securimage']:false, 'captcha_securimage'); ?>
+ </li>
+ </ul>
+
+ <h3>Google reCaptcha</h3>
+ <p><?php echo Filters::noXSS(L('recaptchaprefsinfo')); ?></p>
+ <ul class="form_elements">
+ <li>
+ <label for="captcha_recaptcha"><?php echo Filters::noXSS(L('recaptchaenable')); ?></label>
+ <?php echo tpl_checkbox('captcha_recaptcha', isset($fs->prefs['captcha_recaptcha'])?$fs->prefs['captcha_recaptcha']:false, 'captcha_recaptcha'); ?>
+ </li>
+ <li class="recaptchaconf">
+ <label for="captcha_recaptcha_sitekey">sitekey</label>
+ <input id="captcha_recaptcha_sitekey" class="text" type="text" name="captcha_recaptcha_sitekey" value="<?php echo Filters::noXSS(isset($fs->prefs['captcha_recaptcha_sitekey']) ? $fs->prefs['captcha_recaptcha_sitekey']:''); ?>" />
+ </li>
+ <li class="recaptchaconf">
+ <label for="captcha_recaptcha_secret">secret</label>
+ <input id="captcha_recaptcha_secret" class="text" type="text" name="captcha_recaptcha_secret" value="<?php echo Filters::noXSS(isset($fs->prefs['captcha_recaptcha_secret']) ? $fs->prefs['captcha_recaptcha_secret']:''); ?>" />
+ </li>
+ </ul>
+</div>
+
+ <div id="notifications" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="usernotify"><?php echo Filters::noXSS(L('forcenotify')); ?></label>
+ <select id="usernotify" name="user_notify">
+ <?php echo tpl_options(array(L('neversend'), L('userchoose'), L('email'), L('jabber')), $fs->prefs['user_notify']); ?>
+ </select>
+ </li>
+ </ul>
+
+ <fieldset><legend><?php echo Filters::noXSS(L('emailnotify')); ?></legend>
+ <ul class="form_elements">
+ <li>
+ <label for="adminemail"><?php echo Filters::noXSS(L('fromaddress')); ?></label>
+ <input id="adminemail" name="admin_email" class="text" type="text" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['admin_email']); ?>" />
+ </li>
+
+ <li>
+ <label for="smtpserv"><?php echo Filters::noXSS(L('smtpserver')); ?></label>
+ <input id="smtpserv" name="smtp_server" class="text" type="text" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['smtp_server']); ?>" />
+ <?php if (extension_loaded('openssl')) : ?>
+ <?php echo tpl_checkbox('email_ssl', $fs->prefs['email_ssl'], 'email_ssl'); ?> <label class="inline" for="email_ssl"><?php echo Filters::noXSS(L('ssl')); ?></label>
+ <?php echo tpl_checkbox('email_tls', $fs->prefs['email_tls'], 'email_tls'); ?> <label class="inline" for="email_tls"><?php echo Filters::noXSS(L('tls')); ?></label>
+ <?php endif; ?>
+ </li>
+
+ <li>
+ <label for="smtpuser"><?php echo Filters::noXSS(L('smtpuser')); ?></label>
+ <input id="smtpuser" name="smtp_user" class="text" type="text" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['smtp_user']); ?>" />
+ </li>
+
+ <li>
+ <label for="smtppass"><?php echo Filters::noXSS(L('smtppass')); ?></label>
+ <input id="smtppass" name="smtp_pass" class="text" type="password" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['smtp_pass']); ?>" />
+ </li>
+ <li>
+ <label for="showsmtppass"><?php echo Filters::noXSS(L('showpass')); ?></label>
+ <input id="showsmtppass" name="show_smtp_pass" class="text" type="checkbox" onclick="ShowHidePassword('smtppass')"/>
+ </li>
+ </ul>
+ <?php echo Filters::noXSS(L('testmailsettings')); ?>: <button onclick="testEmail();return false;"><?php echo Filters::noXSS(L('test')); ?></button><div id="emailresult" style="display:inline-block;"></div> <?php echo Filters::noXSS(L('testmailsettingsnotice')); ?>.
+<script>
+function testEmail(){
+ var xmlHttp = new XMLHttpRequest();
+
+ xmlHttp.onreadystatechange = function(){
+ if(xmlHttp.readyState == 4){
+ var target = document.getElementById('emailresult');
+ if(xmlHttp.status == 200){
+ if(xmlHttp.responseText=='ok'){
+ target.style["background-color"]='#66ff00';
+ target.innerHTML = '<i class="fa fa-check fa-2x"></i> '+xmlHttp.responseText;
+ } else{
+ target.innerHTML = '<i class="fa fa-warning fa-2x" style="color:#ff0"></i>' + xmlHttp.responseText;
+ target.style["background-color"]='#ff6600';
+ }
+ } else{
+ target.innerHTML = '<i class="fa fa-warning fa-2x" style="color:#ff0"></i>' + xmlHttp.responseText;
+ target.style["background-color"]='#ff6600';
+ }
+ }
+ }
+ xmlHttp.open("POST", "<?php echo Filters::noXSS($baseurl); ?>js/callbacks/testemail.php", true);
+ xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
+ xmlHttp.send("name=email&csrftoken=<?php echo $_SESSION['csrftoken'] ?>");
+}
+</script>
+ </fieldset>
+
+ <fieldset><legend><?php echo Filters::noXSS(L('jabbernotify')); ?></legend>
+ <ul class="form_elements">
+ <li>
+ <label for="jabberserver"><?php echo Filters::noXSS(L('jabberserver')); ?></label>
+ <input id="jabberserver" class="text" type="text" name="jabber_server" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['jabber_server']); ?>" />
+ <?php if(extension_loaded('openssl')) : ?>
+ <select id="jabber_ssl" name="jabber_ssl">
+ <?php echo tpl_options(array('0' => L('none'), '1' => L('ssl'), '2' => L('tls')), $fs->prefs['jabber_ssl']); ?>
+ </select>
+ <label class="inline" for="jabber_ssl"><?php echo Filters::noXSS(L('ssl')); ?> / <?php echo Filters::noXSS(L('tls')); ?></label>
+ <?php endif; ?>
+ </li>
+
+ <li>
+ <label for="jabberport"><?php echo Filters::noXSS(L('jabberport')); ?></label>
+ <input id="jabberport" class="text" type="text" name="jabber_port" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['jabber_port']); ?>" />
+ </li>
+
+ <li>
+ <label for="jabberusername"><?php echo Filters::noXSS(L('jabberuser')); ?></label>
+ <input id="jabberusername" class="text" type="text" name="jabber_username" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['jabber_username']); ?>" />
+ </li>
+
+ <li>
+ <label for="jabberpassword"><?php echo Filters::noXSS(L('jabberpass')); ?></label>
+ <input id="jabberpassword" name="jabber_password" class="text" type="password" maxlength="100" value="<?php echo Filters::noXSS($fs->prefs['jabber_password']); ?>" />
+ </li>
+
+ <li>
+ <label for="showjabberpass"><?php echo Filters::noXSS(L('showpass')); ?></label>
+ <input id="showjabberpass" name="show_jabber_pass" class="text" type="checkbox" onclick="ShowHidePassword('jabberpassword')"/>
+ </li>
+
+ </ul>
+ </fieldset>
+ </div>
+
+<div id="lookandfeel" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="globaltheme"><?php echo Filters::noXSS(L('globaltheme')); ?></label>
+ <select id="globaltheme" name="global_theme">
+ <?php echo tpl_options(Flyspray::listThemes(), $fs->prefs['global_theme'], true); ?>
+ </select>
+ <label for="customstyle" style="width:auto"><?php echo Filters::noXSS(L('customstyle')); ?></label>
+ <select id="customstyle" name="custom_style">
+ <?php
+ $customs[]=array('', L('no'));
+ $customstyles=glob_compat(BASEDIR ."/themes/".($proj->prefs['theme_style'])."/custom_*.css");
+ foreach ($customstyles as $cs){
+ $customs[]=array($cs,$cs);
+ }
+ echo tpl_options($customs, $proj->prefs['custom_style']);
+ ?>
+ </select>
+ </li>
+ <li>
+ <label for="default_entry"><?php echo Filters::noXSS(L('defaultentry')); ?></label>
+ <select id="default_entry" name="default_entry">
+ <?php echo tpl_options(array('index' => L('tasklist'),'toplevel' => L('toplevel')), Post::val('default_entry', $proj->prefs['default_entry'])); ?>
+ </select>
+ </li>
+
+ <?php // Set the selectable column names
+ // Do NOT use real database column name here and in the next list,
+ // but a term from translation table entries instead, because it's
+ // also used elsewhere to draw a localized version of the name.
+ // Look also at the end of function
+ // tpl_draw_cell in scripts/index.php for further explanation.
+ $columnnames = array(
+ 'id' => L('id'),
+ 'project' => L('project'),
+ 'parent' => L('parent'),
+ 'tasktype' => L('tasktype'),
+ 'category' => L('category'),
+ 'severity' => L('severity'),
+ 'priority' => L('priority'),
+ 'summary' => L('summary'),
+ 'dateopened' => L('dateopened'),
+ 'status' => L('status'),
+ 'openedby' => L('openedby'),
+ 'private' => L('private'),
+ 'assignedto' => L('assignedto'),
+ 'lastedit' => L('lastedit'),
+ 'editedby' => L('editedby'),
+ 'reportedin' => L('reportedin'),
+ 'dueversion' => L('dueversion'),
+ 'duedate' => L('duedate'),
+ 'comments' => L('comments'),
+ 'attachments' => L('attachments'),
+ 'progress' => L('progress'),
+ 'dateclosed' => L('dateclosed'),
+ 'closedby' => L('closedby'),
+ 'os' => L('os'),
+ 'votes' => L('votes'),
+ 'estimatedeffort' => L('estimatedeffort'),
+ 'effort' => L('effort'));
+ $selectedcolumns = explode(' ', Post::val('visible_columns', $fs->prefs['visible_columns']));
+ ?>
+
+ <li>
+ <label for="default_order_by"><?php echo Filters::noXSS(L('defaultorderby')); ?></label>
+ <select id="default_order_by" name="default_order_by">
+ <?php echo tpl_options($columnnames, $proj->prefs['sorting'][0]['field'], false); ?>
+ </select>
+ <select id="default_order_by_dir" name="default_order_by_dir">
+ <?php echo tpl_options(array('asc' => L('ascending'), 'desc' => L('descending')), $proj->prefs['sorting'][0]['dir'], false); ?>
+ </select>
+ </li>
+ <li>
+ <label for="default_order_by2"><?php echo Filters::noXSS(L('defaultorderby2')); ?></label>
+ <select id="default_order_by2" name="default_order_by2">
+ <?php echo tpl_options($columnnames, $proj->prefs['sorting'][1]['field'], false); ?>
+ </select>
+ <select id="default_order_by_dir2" name="default_order_by_dir2">
+ <?php echo tpl_options(array('asc' => L('ascending'), 'desc' => L('descending')), $proj->prefs['sorting'][1]['dir'], false); ?>
+ </select>
+ </li>
+
+ <li>
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('visiblecolumns')); ?></label>
+ <?php echo tpl_double_select('visible_columns', $columnnames, $selectedcolumns, false); ?>
+ </li>
+
+ <li>
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('visiblefields')); ?></label>
+ <?php // Set the selectable field names
+ $fieldnames = array(
+ 'parent' => L('parent'),
+ 'tasktype' => L('tasktype'),
+ 'category' => L('category'),
+ 'severity' => L('severity'),
+ 'priority' => L('priority'),
+ 'status' => L('status'),
+ 'private' => L('private'),
+ 'assignedto' => L('assignedto'),
+ 'reportedin' => L('reportedin'),
+ 'dueversion' => L('dueversion'),
+ 'duedate' => L('duedate'),
+ 'progress' => L('progress'),
+ 'os' => L('os'),
+ 'votes' => L('votes'));
+ $selectedfields = explode(' ', Post::val('visible_fields', $fs->prefs['visible_fields']));
+ echo tpl_double_select('visible_fields', $fieldnames, $selectedfields, false);
+ ?>
+ </li>
+
+ <?php if(isset($fs->prefs['general_integration'])): ?>
+ <li>
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('generalintegration')); ?></label>
+ <?php echo TextFormatter::textarea('general_integration', 8, 70, array('id'=>'general_integration'), Post::val('general_integration', $fs->prefs['general_integration'])); ?>
+ </li>
+ <?php endif; ?>
+
+ <?php if(isset($fs->prefs['footer_integration'])): ?>
+ <li>
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('footerintegration')); ?></label>
+ <?php echo TextFormatter::textarea('footer_integration', 8, 70, array('id'=>'footer_integration'), Post::val('footer_integration', $fs->prefs['footer_integration'])); ?>
+ </li>
+ <?php endif; ?>
+
+ </ul>
+ </div>
+ <div class="tbuttons">
+ <input type="hidden" name="action" value="globaloptions" />
+ <button type="submit" class="positive"><?php echo Filters::noXSS(L('saveoptions')); ?></button>
+ <button type="reset"><?php echo Filters::noXSS(L('resetoptions')); ?></button>
+ </div>
+ </form>
+</div>
diff --git a/themes/CleanFS/templates/admin.resolution.tpl b/themes/CleanFS/templates/admin.resolution.tpl
new file mode 100644
index 0000000..e8b78dc
--- /dev/null
+++ b/themes/CleanFS/templates/admin.resolution.tpl
@@ -0,0 +1,8 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('resolutionlist')); ?></h3>
+ <?php
+ $this->assign('list_type', 'resolution');
+ $this->assign('rows', $proj->listResolutions(true));
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.status.tpl b/themes/CleanFS/templates/admin.status.tpl
new file mode 100644
index 0000000..c38e6a9
--- /dev/null
+++ b/themes/CleanFS/templates/admin.status.tpl
@@ -0,0 +1,9 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('taskstatuses')); ?></h3>
+
+ <?php
+ $this->assign('list_type', 'status');
+ $this->assign('rows', $proj->listTaskStatuses(true));
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.tag.tpl b/themes/CleanFS/templates/admin.tag.tpl
new file mode 100644
index 0000000..2b5afd1
--- /dev/null
+++ b/themes/CleanFS/templates/admin.tag.tpl
@@ -0,0 +1,10 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('tags')); ?></h3>
+ <p>Tag management is in development.</p>
+ <p>Please see <a href="https://bugs.flyspray.org/2012" target="_blank">bugs.flyspray.org/2012</a> for status of <b>Tags</b> feature.</p>
+<?php
+ $this->assign('list_type', 'tag');
+ $this->assign('rows', $proj->listTags(true));
+ $this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/admin.tasktype.tpl b/themes/CleanFS/templates/admin.tasktype.tpl
new file mode 100644
index 0000000..0ceff33
--- /dev/null
+++ b/themes/CleanFS/templates/admin.tasktype.tpl
@@ -0,0 +1,8 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('tasktypes')); ?></h3>
+ <?php
+ $this->assign('list_type', 'tasktype');
+ $this->assign('rows', $proj->listTaskTypes(true));
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.translation.tpl b/themes/CleanFS/templates/admin.translation.tpl
new file mode 100644
index 0000000..f0b575c
--- /dev/null
+++ b/themes/CleanFS/templates/admin.translation.tpl
@@ -0,0 +1 @@
+<?php echo $content; ?>
diff --git a/themes/CleanFS/templates/admin.userrequest.tpl b/themes/CleanFS/templates/admin.userrequest.tpl
new file mode 100644
index 0000000..badc98b
--- /dev/null
+++ b/themes/CleanFS/templates/admin.userrequest.tpl
@@ -0,0 +1,50 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('pendingrequests')); ?></h3>
+
+ <?php if (!count($pendings)): ?>
+ <?php echo Filters::noXSS(L('nopendingreq')); ?>
+
+ <?php else: ?>
+ <table class="requests">
+ <tr>
+ <th><?php echo Filters::noXSS(L('eventdesc')); ?></th>
+ <th><?php echo Filters::noXSS(L('requestedby')); ?></th>
+ <th><?php echo Filters::noXSS(L('daterequested')); ?></th>
+ <th><?php echo Filters::noXSS(L('emailaddress')); ?></th>
+ <th class="pm-buttons"> </th>
+ </tr>
+ <?php foreach ($pendings as $req): ?>
+ <tr>
+ <td>
+ New User Request
+ </td>
+ <td><?php echo tpl_userlink($req['submitted_by']); ?></td>
+ <td><?php echo Filters::noXSS(formatDate($req['time_submitted'], true)); ?></td>
+ <td><?php echo Filters::noXSS($req['reason_given']); ?></td>
+ <td>
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('edituser', $req['submitted_by'])), null, null, null, 'style="display:inline"'); ?>
+ <input type="submit" value="<?php echo Filters::noXSS(L('accept')); ?>">
+ <input type="hidden" name="action" value="approve.user"/>
+ <input type="hidden" name="user_id" value="<?php echo $req['submitted_by']; ?>"/>
+ <input type="hidden" name="account_enabled" value="1"/>
+ </form>
+
+ <button class="submit" onclick="showhidestuff('denyform<?php echo Filters::noXSS($req['request_id']); ?>');"><?php echo Filters::noXSS(L('deny')); ?></button>
+ <div id="denyform<?php echo Filters::noXSS($req['request_id']); ?>" class="denyform">
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('admin','userrequest'))); ?>
+ <div>
+ <input type="hidden" name="action" value="denyuserreq" />
+ <input type="hidden" name="req_id" value="<?php echo Filters::noXSS($req['request_id']); ?>" />
+ <label for="deny_reason<?php echo Filters::noXSS($req['request_id']); ?>" class="inline"><?php echo Filters::noXSS(L('reasonfordeinal')); ?></label><br />
+ <textarea cols="40" rows="5" name="deny_reason" id="deny_reason<?php echo Filters::noXSS($req['request_id']); ?>"></textarea>
+ <br />
+ <button type="submit"><?php echo Filters::noXSS(L('deny')); ?></button>
+ </div>
+ </form>
+ </div>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </table>
+ <?php endif; ?>
+</div>
diff --git a/themes/CleanFS/templates/admin.users.tpl b/themes/CleanFS/templates/admin.users.tpl
new file mode 100644
index 0000000..25e89ad
--- /dev/null
+++ b/themes/CleanFS/templates/admin.users.tpl
@@ -0,0 +1,6 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('admintoolboxlong')); ?> :: <?php echo Filters::noXSS(L('edituser')); ?> : <?php echo Filters::noXSS($theuser->infos['user_name']); ?></h3>
+ <fieldset><legend><?php echo Filters::noXSS(L('edituser')); ?></legend>
+ <?php $this->display('common.profile.tpl'); ?>
+ </fieldset>
+</div>
diff --git a/themes/CleanFS/templates/admin.version.tpl b/themes/CleanFS/templates/admin.version.tpl
new file mode 100644
index 0000000..196fac9
--- /dev/null
+++ b/themes/CleanFS/templates/admin.version.tpl
@@ -0,0 +1,8 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('versionlist')); ?></h3>
+ <?php
+ $this->assign('list_type', 'version');
+ $this->assign('rows', $proj->listVersions(true));
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/common.attachments.tpl b/themes/CleanFS/templates/common.attachments.tpl
new file mode 100644
index 0000000..ee0898b
--- /dev/null
+++ b/themes/CleanFS/templates/common.attachments.tpl
@@ -0,0 +1,50 @@
+ <?php
+ if ($attachments && $user->can_view_task($task_details)): ?>
+ <div class="attachments">
+ <?php foreach ($attachments as $attachment): ?>
+ <?php if (file_exists(BASEDIR . '/attachments/' . $attachment['file_name'])): ?>
+ <a title="<?php echo Filters::noXSS($attachment['orig_name']); ?>" href="?getfile=<?php echo Filters::noXSS($attachment['attachment_id']); ?>" <?php if (substr($attachment['file_type'], 0, 5) == 'image'): ?>rel="lightbox[bug]"<?php endif; ?>>
+ <?php else: ?>
+ <del>
+ <?php endif; ?>
+ <?php
+ // Strip the mimetype to get the icon image name
+ list($main) = explode('/', $attachment['file_type']);
+ $imgdir = BASEDIR . "/themes/".Filters::noXSS($proj->prefs['theme_style'])."/mime/";
+ $imgpath = Filters::noXSS($baseurl)."themes/".Filters::noXSS($proj->prefs['theme_style'])."/mime/";
+ if (file_exists($imgdir.$attachment['file_type'] . '.png')):
+ ?>
+ <img src="<?php echo Filters::noXSS($imgpath); ?><?php echo Filters::noXSS($attachment['file_type']); ?>.png" alt="(<?php echo Filters::noXSS($attachment['file_type']); ?>)" title="<?php echo Filters::noXSS($attachment['file_type']); ?>" />
+ <?php else: ?>
+ <img src="<?php echo Filters::noXSS($imgpath); ?><?php echo Filters::noXSS($main); ?>.png" alt="" title="<?php echo Filters::noXSS($attachment['file_type']); ?>" />
+ <?php endif; ?>
+ &nbsp;&nbsp;
+ <?php if (utf8_strlen($attachment['orig_name']) > 30): ?>
+ <?php echo Filters::noXSS(utf8_substr($attachment['orig_name'], 0, 29)); ?>...
+ <?php else: ?>
+ <?php echo Filters::noXSS($attachment['orig_name']); ?>
+
+ <?php endif; ?>
+ <?php if (file_exists(BASEDIR . '/attachments/' . $attachment['file_name'])): ?>
+ </a>
+ <?php else: ?>
+ </del>
+ <?php endif; ?>
+ <?php if ($attachment['file_size'] < 1000000): ?>
+ (<?php echo Filters::noXSS(round($attachment['file_size']/1024,1)); ?> <?php echo Filters::noXSS(L('KiB')); ?>)
+ <?php else: ?>
+ (<?php echo Filters::noXSS(round($attachment['file_size']/1024/1024,2)); ?> <?php echo Filters::noXSS(L('MiB')); ?>)
+ <?php endif; ?>
+ <?php
+ # showing additional download links for images is a temporary fix, because the lightbox plugin used in <=FS1.0 (a lightbox version from 2008!) catches also right mouse clicks :-(.
+ # So you cannot just choose 'Save as..' context menu option.
+ # When the javascript based features of Flyspray (a big task, planned ~FS1.1) moved complete to (probably) jquery (used also by dokuwiki so prefered) and a jquery based lightbox/fancybox is used, this can be removed.
+ if(file_exists(BASEDIR . '/attachments/' . $attachment['file_name']) && substr($attachment['file_type'], 0, 5) == 'image' ): ?>
+ <a class="fa fa-download" title="Download <?php echo Filters::noXSS($attachment['orig_name']); ?>" href="?getfile=<?php echo Filters::noXSS($attachment['attachment_id']); ?>&amp;dl"></a>
+ <?php endif; ?>
+ <br />
+ <?php endforeach; ?>
+ </div>
+ <?php elseif (count($attachments)): ?>
+ <div class="attachments"><?php echo Filters::noXSS(L('attachnoperms')); ?></div>
+ <?php endif; ?>
diff --git a/themes/CleanFS/templates/common.cat.tpl b/themes/CleanFS/templates/common.cat.tpl
new file mode 100644
index 0000000..fb0e613
--- /dev/null
+++ b/themes/CleanFS/templates/common.cat.tpl
@@ -0,0 +1,157 @@
+<p><?php echo Filters::noXSS(L('listnote')); ?></p>
+<?php if ($do=='pm'): ?>
+<h3><?php echo Filters::noXSS(L('categoriesglobal')); ?></h3>
+<table class="list" id="idtablesys">
+<colgroup>
+ <col class="cname" />
+ <col class="cowner" />
+ <col class="cshow" />
+ <col class="cdelete" />
+ <col class="cusage" />
+</colgroup>
+<thead>
+<tr>
+ <th><?php echo Filters::noXSS(L('name')); ?></th>
+ <th><?php echo Filters::noXSS(L('owner')); ?></th>
+ <th><?php echo Filters::noXSS(L('show')); ?></th>
+ <th>&nbsp;</th>
+ <th><?php echo Filters::noXSS(L('usedintasks')); ?></th>
+</tr>
+</thead>
+<tbody>
+<?php if (isset($sysrows) && count($sysrows)): ?>
+<?php
+$syscountlines=-1;
+foreach ($sysrows as $row):
+$syscountlines++;
+?>
+<tr>
+ <td class="first"><span class="depthmark"><?php echo str_repeat('&rarr;', $row['depth']); ?></span><?php echo Filters::noXSS($row['category_name']); ?></td>
+ <td><?php echo ($row['category_owner']==0)? '': Filters::noXSS($row['category_owner']); ?></td>
+ <td title="<?php echo Filters::noXSS(L('showtip')); ?>"><?php echo $row['show_in_list']; ?></td>
+ <td>&nbsp;</td>
+ <td><?php echo $row['used_in_tasks'] >0 ? $row['used_in_tasks']:''; ?></td>
+</tr>
+<?php endforeach; ?>
+<?php else: ?>
+<tr><td colspan="5"><?php echo Filters::noXSS(L('novalues')); ?></td></tr>
+<?php endif; ?>
+</tbody>
+</table>
+<?php endif; ?>
+<h3><?php echo $do=='pm' ? Filters::noXSS(L('categoriesproject')) : Filters::noXSS(L('categoriesglobal')); ?></h3>
+<?php
+$countlines = -1;
+$categories = $proj->listCategories($proj->id, false, false, false);
+if ( count($categories) ){
+ $root = $categories[0];
+ unset($categories[0]);
+} else{
+ $root=array();
+}
+
+if (count($categories)) : ?>
+<div id="controlBox">
+ <div class="grip"></div>
+ <div class="inner">
+ <a style="display:block;text-align:center;" href="#" onclick="TableControl.up('catTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/up.png" alt="Up" /></a>
+ <a href="#" onclick="TableControl.shallower('catTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/left.png" alt="Left" /></a>
+ <a href="#" onclick="TableControl.deeper('catTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/right.png" alt="Right" /></a>
+ <a style="display:block;text-align:center;" href="#" onclick="TableControl.down('catTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/down.png" alt="Down" /></a>
+</div>
+</div>
+<?php endif; ?>
+<?php echo tpl_form(Filters::noXSS(CreateURL($do, 'cat', $proj->id))); ?>
+ <table class="list" id="catTable">
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('name')); ?></th>
+ <th><?php echo Filters::noXSS(L('owner')); ?></th>
+ <th><?php echo Filters::noXSS(L('show')); ?></th>
+ <th><?php echo Filters::noXSS(L('delete')); ?></th>
+ <th><?php echo Filters::noXSS(L('usedintasks')); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach ($categories as $row):
+ $countlines++;
+ ?>
+ <tr class="depth<?php echo Filters::noXSS($row['depth']); ?>">
+ <td class="first">
+ <input type="hidden" name="lft[<?php echo Filters::noXSS($row['category_id']); ?>]" value="<?php echo Filters::noXSS($row['lft']); ?>" />
+ <input type="hidden" name="rgt[<?php echo Filters::noXSS($row['category_id']); ?>]" value="<?php echo Filters::noXSS($row['rgt']); ?>" />
+ <span class="depthmark"><?php echo str_repeat('&rarr;', intval($row['depth'])); ?></span>
+ <input id="categoryname<?php echo Filters::noXSS($countlines); ?>" class="text" type="text" maxlength="40" name="list_name[<?php echo Filters::noXSS($row['category_id']); ?>]" value="<?php echo Filters::noXSS($row['category_name']); ?>" />
+ </td>
+ <td title="<?php echo Filters::noXSS(L('categoryownertip')); ?>">
+ <?php echo tpl_userselect('category_owner[' . $row['category_id'] . ']' . $countlines, $row['category_owner'], 'categoryowner' . $countlines); ?>
+
+ </td>
+ <td title="<?php echo Filters::noXSS(L('listshowtip')); ?>">
+ <?php echo tpl_checkbox('show_in_list[' . $row['category_id'] . ']', $row['show_in_list'], 'showinlist'.$countlines); ?>
+
+ </td>
+ <td title="<?php echo Filters::noXSS(L('listdeletetip')); ?>">
+ <input id="delete<?php echo Filters::noXSS($row['category_id']); ?>" type="checkbox"
+ <?php if ($row['used_in_tasks']): ?>disabled="disabled"<?php endif; ?>
+ name="delete[<?php echo Filters::noXSS($row['category_id']); ?>]" value="1" />
+ </td>
+ <td><?php echo $row['used_in_tasks'] >0 ? $row['used_in_tasks']:''; ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ <?php if($countlines > -1): ?>
+ <tr>
+ <td colspan="4"></td>
+ <td class="buttons">
+ <input type="hidden" name="action" value="update_category" />
+ <input type="hidden" name="list_type" value="category" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('update')); ?></button>
+ </td>
+ </tr>
+ <?php endif; ?>
+ </table>
+ <?php if (count($categories)): ?>
+ <script type="text/javascript">
+ <?php
+ echo 'TableControl.create("catTable",{
+ controlBox: "controlBox",
+ tree: true,
+ spreadActiveClass: true
+ });';
+ echo 'new Draggable("controlBox",{
+ handle: "grip"
+ });';
+ ?>
+ </script>
+ <?php endif; ?>
+</form>
+<hr />
+<?php echo tpl_form(Filters::noXSS(CreateURL($do, 'cat', $proj->id))); ?>
+ <table class="list">
+ <tr>
+ <td>
+ <input id="listnamenew" class="text" type="text" maxlength="40" name="list_name" autofocus />
+ </td>
+ <td title="<?php echo Filters::noXSS(L('categoryownertip')); ?>">
+ <?php echo tpl_userselect('category_owner', Req::val('category_owner'), 'categoryownernew'); ?>
+
+ </td>
+ <td title="<?php echo Filters::noXSS(L('categoryparenttip')); ?>">
+ <label for="parent_id"><?php echo Filters::noXSS(L('parent')); ?></label>
+ <select id="parent_id" name="parent_id">
+ <option value="<?php echo Filters::noXSS($root['category_id']); ?>"><?php echo Filters::noXSS(L('notsubcategory')); ?></option>
+ <?php echo tpl_options($proj->listCategories($proj->id, false), Req::val('parent_id')); ?>
+ </select>
+ </td>
+ <td class="buttons">
+ <input type="hidden" name="action" value="<?php echo Filters::noXSS($do); ?>.add_category" />
+ <input type="hidden" name="area" value="<?php echo Filters::noXSS(Req::val('area')); ?>" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('addnew')); ?></button>
+ </td>
+ </tr>
+ </table>
+</form>
diff --git a/themes/CleanFS/templates/common.datepicker.tpl b/themes/CleanFS/templates/common.datepicker.tpl
new file mode 100644
index 0000000..48a11e0
--- /dev/null
+++ b/themes/CleanFS/templates/common.datepicker.tpl
@@ -0,0 +1,9 @@
+<?php if ($label): ?>
+<label for="<?php echo Filters::noXSS($name); ?>"><?php echo Filters::noXSS($label); ?></label>
+<?php endif; ?>
+<input id="<?php echo Filters::noXSS($name); ?>" type="text" class="text" size="10" name="<?php echo Filters::noXSS($name); ?>" placeholder=" " value="<?php echo Filters::noXSS($date); ?>" />
+
+<a class="datelink" href="#" id="<?php echo Filters::noXSS($name); ?>dateview">
+ <!--<img src="<?php echo Filters::noXSS($this->get_image('x-office-calendar')); ?>" alt="<?php echo Filters::noXSS(L('selectdate')); ?>" />-->
+</a>
+<script type="text/javascript">Calendar.setup({daFormat: '<?php echo Filters::noJsXSS($dateformat); ?>',inputField: "<?php echo Filters::noXSS($name); ?>", button: "<?php echo Filters::noXSS($name); ?>dateview"});</script>
diff --git a/themes/CleanFS/templates/common.dualselect.tpl b/themes/CleanFS/templates/common.dualselect.tpl
new file mode 100644
index 0000000..f207aa7
--- /dev/null
+++ b/themes/CleanFS/templates/common.dualselect.tpl
@@ -0,0 +1,15 @@
+<div class="double_select">
+ <select class="dualselect_selectable" id="l<?php echo Filters::noXSS($id); ?>" multiple="multiple"
+ ondblclick="dualSelect(this, 'r', '<?php echo Filters::noJsXSS($id); ?>')">%s</select>
+ <div class="dualselect_buttons">
+ <button type="button" onmouseup="dualSelect('l', 'r', '<?php echo Filters::noJsXSS($id); ?>')"><?php echo eL('add'); ?> &#x25b6;</button>
+ <button type="button" onmouseup="dualSelect('r', 'l', '<?php echo Filters::noJsXSS($id); ?>')">&#x25c0; <?php echo eL('remove'); ?></button>
+ </div>
+ <div class="dualselect_selected">
+ <?php if($updown): ?><button type="button" onmouseup="selectMove('<?php echo Filters::noJsXSS($id); ?>', -1)">&#x25b2;</button><br /><?php endif; ?>
+ <select id="r<?php echo Filters::noXSS($id); ?>" multiple="multiple"
+ ondblclick="dualSelect(this, 'l', '<?php echo Filters::noJsXSS($id); ?>')">%s</select>
+ <?php if($updown): ?><button type="button" onmouseup="selectMove('<?php echo Filters::noJsXSS($id); ?>', 1)">&#x25bc;</button><?php endif; ?>
+ <input type="hidden" value="<?php echo Filters::noXSS(join(' ', $selected)); ?>" id="v<?php echo Filters::noXSS($id); ?>" name="<?php echo Filters::noXSS($name); ?>" />
+ </div>
+</div>
diff --git a/themes/CleanFS/templates/common.editallusers.tpl b/themes/CleanFS/templates/common.editallusers.tpl
new file mode 100644
index 0000000..4a49d81
--- /dev/null
+++ b/themes/CleanFS/templates/common.editallusers.tpl
@@ -0,0 +1,137 @@
+<script>
+function toggleCheckbox(id)
+{
+ var el = document.getElementById(id);
+ if (el != null) {
+ if (el.checked) {
+ el.checked = false;
+ } else {
+ el.checked = true;
+ }
+ }
+}
+</script>
+<?php
+ $showstats=(isset($_GET['showfields']) && in_array('stats',$_GET['showfields'])) ? 1 : 0;
+ $showltf= (isset($_GET['showfields']) && in_array('ltf', $_GET['showfields'])) ? 1 : 0;
+ ?>
+<form action="<?php echo Filters::noXSS(createURL($do, 'editallusers'));?>" method="get">
+<input type="hidden" name="do" value="admin" />
+<input type="hidden" name="area" value="editallusers" />
+<div style="background-color:#ff9">Note: Choosing the "statistics" option here can result in a slow SQL query depending on your amount of existing tasks and users! The other options are fast.</div>
+<select name="showfields[]" multiple="multiple" size="3">
+<option value="-">---basic---</option>
+<option value="stats"<?php echo $showstats? ' selected="selected"':'';?>>statistics</option>
+<option value="ltf"<?php echo $showltf? ' selected="selected"':'';?>>language, timezone, dateformat</option>
+</select>
+<button type="submit">Show selected fields</button>
+</form>
+<?php
+if ($do == 'admin'): echo tpl_form(Filters::noXSS(createURL($do, 'editallusers')), null, null, null, 'id="editallusers"');
+ else: echo tpl_form(Filters::noXSS($_SERVER['SCRIPT_NAME']), null, null, null, 'id="editallusers"');
+endif;
+if ($do == 'admin'): ?>
+ <input type="hidden" name="action" value="admin.editallusers" />
+ <input type="hidden" name="do" value="admin" />
+ <input type="hidden" name="area" value="editallusers" />
+<?php endif; ?>
+<style>.bulkedituser td.inactive{color:#999;}</style>
+<table class="bulkedituser">
+ <thead>
+ <tr class="account_header">
+ <th></th>
+ <th><?= eL('realname') ?></th>
+ <th><?= eL('username') ?></th>
+ <th><?= eL('emailaddress') ?></th>
+ <th><?= eL('jabberid') ?></th>
+ <th><?= eL('regdate') ?></th>
+ <th><?= eL('lastlogin') ?></th>
+<?php if($showstats): ?>
+ <th>opened_by</th>
+ <th>closed_by</th>
+ <th>last_edited_by</th>
+ <th>assigned</th>
+ <th>comments</th>
+ <th>votes</th>
+<?php endif; ?>
+<?php if($showltf): ?>
+ <th><?= eL('language') ?></th>
+ <th><?= eL('timezone') ?></th>
+ <th><?= eL('dateformat') ?></th>
+ <th><?= eL('dateformat_extended') ?></th>
+<?php endif; ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+$listopts=null;
+if($showstats){ $listopts['stats']=1; }
+foreach (Flyspray::listUsers($listopts) as $usr): ?>
+<tr class="<?php echo ($usr['account_enabled']) ? 'account_enabled':'account_disabled'; ?>" onclick="toggleCheckbox('<?php echo $usr['user_id']; ?>')">
+ <td><input id="<?php echo $usr['user_id'] ?>" onclick="event.stopPropagation()" type="checkbox" name="checkedUsers[]" value="<?php echo $usr['user_id']; ?>"></td>
+ <td><a href="<?php echo createURL('edituser', $usr['user_id'] ); ?>"><?php echo Filters::noXSS($usr['real_name']); ?></a></td>
+ <td><?php echo $usr['user_name']; ?></td>
+ <td<?= ($usr['notify_type']==0 || $usr['notify_type']==2) ? ' class="inactive"':''; ?>><?php echo Filters::noXSS($usr['email_address']); ?></td>
+ <td<?= ($usr['notify_type']==0 || $usr['notify_type']==1) ? ' class="inactive"':''; ?>><?php echo Filters::noXSS($usr['jabber_id']); ?></td>
+ <td><?php echo formatDate($usr['register_date']); ?></td>
+ <td><?php echo formatDate($usr['last_login']); ?></td>
+<?php if($showstats): ?>
+ <td><?php echo $usr['countopen']>0 ? $usr['countopen']:''; ?></td>
+ <td><?php echo $usr['countclose']>0 ? $usr['countclose']:''; ?></td>
+ <td><?php echo $usr['countlastedit']>0 ? $usr['countlastedit']:''; ?></td>
+ <td><?php echo $usr['countassign']>0 ? $usr['countassign']:''; ?></td>
+ <td><?php echo $usr['countcomments']>0 ? $usr['countcomments']:''; ?></td>
+ <td><?php echo $usr['countvotes']>0 ? $usr['countvotes']:''; ?></td>
+<?php endif; ?>
+<?php if($showltf): ?>
+ <td><?php echo Filters::noXSS($usr['lang_code']); ?></td>
+ <td><?php echo Filters::noXSS($usr['time_zone']); ?></td>
+ <td><?php echo Filters::noXSS($usr['dateformat']); ?></td>
+ <td><?php echo Filters::noXSS($usr['dateformat_extended']); ?></td>
+<?php endif; ?>
+ </tr>
+<?php endforeach; ?>
+ </tbody>
+</table>
+
+<button type="submit" id="buSubmit" name="enable"><?= eL('enableaccounts') ?></button>
+<button type="submit" id="buSubmit" name="disable"><?= eL('disableaccounts') ?></button>
+<button type="submit" id="buSubmit" name="delete"><?= eL('deleteaccounts') ?></button>
+
+<!-- TODO Should still add these to bulk edit, but hasn't been done yet
+<ul class="form_elements">
+<li class="required">
+ <label for="notify_type"><?= eL('notifications') ?></label>
+ <select id="notify_type" name="notify_type">
+ <?php echo tpl_options($fs->getNotificationOptions(), Req::val('notify_type')); ?>
+
+ </select>
+</li>
+<li>
+ <label for="time_zone"><?= eL('timezone') ?></label>
+ <select id="time_zone" name="time_zone">
+ <?php
+ $times = array();
+ for ($i = -12; $i <= 13; $i++) {
+ $times[$i] = L('GMT') . (($i == 0) ? ' ' : (($i > 0) ? '+' . $i : $i));
+ }
+ ?>
+ <?php echo tpl_options($times, Req::val('time_zone', 0)); ?>
+
+ </select>
+</li>
+
+ <?php if (isset($groups)): ?>
+ <li>
+ <label for="groupin"><?= eL('globalgroup') ?></label>
+ <select id="groupin" class="adminlist" name="group_in">
+ <?php echo tpl_options($groups, Req::val('group_in')); ?>
+
+ </select>
+ </li>
+ <?php endif; ?>
+
+ </ul>
+ <p><button type="submit" id="buSubmit"><?= eL('updateaccounts') ?></button></p>
+-->
+</form>
diff --git a/themes/CleanFS/templates/common.editattachments.tpl b/themes/CleanFS/templates/common.editattachments.tpl
new file mode 100644
index 0000000..5cff7d2
--- /dev/null
+++ b/themes/CleanFS/templates/common.editattachments.tpl
@@ -0,0 +1,46 @@
+ <?php if ($attachments): ?>
+ <table class="attachments">
+ <thead><tr><th><?php echo Filters::noXSS(L('file')); ?></th><th<?php echo $user->perms('delete_attachments') ? '':' style="color:#999"'; ?>><?php echo Filters::noXSS(L('size')); ?></th><th><?php echo Filters::noXSS(L('delete')); ?></th></tr></thead>
+ <?php foreach ($attachments as $attachment): ?>
+ <tr>
+ <td>
+ <?php if (file_exists(BASEDIR . '/attachments/' . $attachment['file_name'])): ?>
+ <a href="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?getfile=<?php echo Filters::noXSS($attachment['attachment_id']); ?>" title="<?php echo Filters::noXSS($attachment['file_type']); ?>">
+ <?php else: ?>
+ <del>
+ <?php endif; ?>
+ <?php
+ // Strip the mimetype to get the icon image name
+ list($main) = explode('/', $attachment['file_type']);
+ $imgdir = BASEDIR . "/themes/".Filters::noXSS($proj->prefs['theme_style'])."/mime/";
+ $imgpath = Filters::noXSS($baseurl)."themes/".Filters::noXSS($proj->prefs['theme_style'])."/mime/";
+ if (file_exists($imgdir.$attachment['file_type'] . '.png')):
+ ?>
+ <img src="<?php echo Filters::noXSS($imgpath); ?><?php echo Filters::noXSS($attachment['file_type']); ?>.png" alt="(<?php echo Filters::noXSS($attachment['file_type']); ?>)" title="<?php echo Filters::noXSS($attachment['file_type']); ?>" />
+ <?php else: ?>
+ <img src="<?php echo Filters::noXSS($imgpath); ?><?php echo Filters::noXSS($main); ?>.png" alt="" title="<?php echo Filters::noXSS($attachment['file_type']); ?>" />
+ <?php endif; ?>
+ &nbsp;&nbsp;<?php echo Filters::noXSS($attachment['orig_name']); ?>
+
+ <?php if (file_exists(BASEDIR . '/attachments/' . $attachment['file_name'])): ?>
+ </a>
+ <?php else: ?>
+ </del>
+ <?php endif; ?>
+ </td>
+ <td>
+ <?php if ($attachment['file_size'] < 1000000): ?>
+ <?php echo Filters::noXSS(round($attachment['file_size']/1024,1)); ?> <?php echo Filters::noXSS(L('KiB')); ?>
+
+ <?php else: ?>
+ <?php echo Filters::noXSS(round($attachment['file_size']/1024/1024,2)); ?> <?php echo Filters::noXSS(L('MiB')); ?>
+
+ <?php endif; ?>
+ </td>
+ <td>
+ <input type="checkbox" <?php echo tpl_disableif(!$user->perms('delete_attachments')); ?> name="delete_att[]" value="<?php echo Filters::noXSS($attachment['attachment_id']); ?>" />
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </table>
+ <?php endif; ?>
diff --git a/themes/CleanFS/templates/common.editgroup.tpl b/themes/CleanFS/templates/common.editgroup.tpl
new file mode 100644
index 0000000..28ef9f6
--- /dev/null
+++ b/themes/CleanFS/templates/common.editgroup.tpl
@@ -0,0 +1,233 @@
+ <form action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get" id="groupselect">
+ <div>
+ <label for="selectgroup"><?php echo Filters::noXSS(L('editgroup')); ?></label>
+ <select name="id" id="selectgroup"><?php echo tpl_options(Flyspray::ListGroups($proj->id), Req::num('id')); ?></select>
+ <button type="submit"><?php echo Filters::noXSS(L('edit')); ?></button>
+ <input type="hidden" name="do" value="<?php echo Filters::noXSS(Req::val('do')); ?>" />
+ <input type="hidden" name="area" value="editgroup" />
+ </div>
+ </form>
+
+<?php echo tpl_form(Filters::noXSS(CreateURL('editgroup', Req::num('id'), $do)), null, null, null, 'id="editgroup"'); ?>
+<fieldset class="box"> <legend><?php echo Filters::noXSS(L('editgroup')); ?> <?php echo Filters::noXSS(Req::val('group_name', $group_details['group_name'])); ?></legend>
+<table class="box">
+ <tr>
+ <td>
+ <label for="groupname"><?php echo Filters::noXSS(L('groupname')); ?></label>
+ </td>
+ <td><input id="groupname" class="text" type="text" name="group_name" size="20" maxlength="20" value="<?php echo Filters::noXSS(Req::val('group_name', $group_details['group_name'])); ?>" /></td>
+ </tr>
+ <tr>
+ <td><label for="groupdesc"><?php echo Filters::noXSS(L('description')); ?></label></td>
+ <td><input id="groupdesc" class="text" type="text" name="group_desc" size="50" maxlength="100" value="<?php echo Filters::noXSS(Req::val('group_desc', $group_details['group_desc'])); ?>" /></td>
+ </tr>
+ <?php if ($group_details['group_id'] == 1): ?>
+ <tr>
+ <td colspan="2"><?php echo Filters::noXSS(L('notshownforadmin')); ?></td>
+ </tr>
+ <?php else: ?>
+ <tr>
+ <td><label for="projectmanager"><?php echo Filters::noXSS(L('projectmanager')); ?></label></td>
+ <td><?php echo tpl_checkbox('manage_project', Req::val('manage_project', !Req::val('action') && $group_details['manage_project']), 'projectmanager'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewtasks"><?php echo Filters::noXSS(L('viewtasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_tasks', Req::val('view_tasks', !Req::val('action') && $group_details['view_tasks']), 'viewtasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewowntasks"><?php echo Filters::noXSS(L('viewowntasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_own_tasks', Req::val('view_own_tasks', !Req::val('action') && $group_details['view_own_tasks']), 'viewowntasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewgroupstasks"><?php echo Filters::noXSS(L('viewgroupstasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_groups_tasks', Req::val('view_groups_tasks', !Req::val('action') && $group_details['view_groups_tasks']), 'viewgroupstasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="canopenjobs"><?php echo Filters::noXSS(L('opennewtasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('open_new_tasks', Req::val('open_new_tasks', !Req::val('action') && $group_details['open_new_tasks']), 'canopenjobs'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="canopenmultipletasks"><?php echo Filters::noXSS(L('addmultipletasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('add_multiple_tasks', Req::val('add_multiple_tasks', !Req::val('action') && $group_details['add_multiple_tasks']), 'canopenmultipletasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="modifyowntasks"><?php echo Filters::noXSS(L('modifyowntasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('modify_own_tasks', Req::val('modify_own_tasks', !Req::val('action') && $group_details['modify_own_tasks']), 'modifyowntasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="modifyalltasks"><?php echo Filters::noXSS(L('modifyalltasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('modify_all_tasks', Req::val('modify_all_tasks', !Req::val('action') && $group_details['modify_all_tasks']), 'modifyalltasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewcomments"><?php echo Filters::noXSS(L('viewcomments')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_comments', Req::val('view_comments', !Req::val('action') && $group_details['view_comments']), 'viewcomments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="canaddcomments"><?php echo Filters::noXSS(L('addcomments')); ?></label></td>
+ <td><?php echo tpl_checkbox('add_comments', Req::val('add_comments', !Req::val('action') && $group_details['add_comments']), 'canaddcomments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="editowncomments"><?php echo Filters::noXSS(L('editowncomments')); ?></label></td>
+ <td><?php echo tpl_checkbox('edit_own_comments', Req::val('edit_own_comments', !Req::val('action') && $group_details['edit_own_comments']), 'editowncomments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="editcomments"><?php echo Filters::noXSS(L('editcomments')); ?></label></td>
+ <td><?php echo tpl_checkbox('edit_comments', Req::val('edit_comments', !Req::val('action') && $group_details['edit_comments']), 'editcomments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="deletecomments"><?php echo Filters::noXSS(L('deletecomments')); ?></label></td>
+ <td><?php echo tpl_checkbox('delete_comments', Req::val('delete_comments', !Req::val('action') && $group_details['delete_comments']), 'deletecomments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="createattachments"><?php echo Filters::noXSS(L('createattachments')); ?></label></td>
+ <td><?php echo tpl_checkbox('create_attachments', Req::val('create_attachments', !Req::val('action') && $group_details['create_attachments']), 'createattachments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="deleteattachments"><?php echo Filters::noXSS(L('deleteattachments')); ?></label></td>
+ <td><?php echo tpl_checkbox('delete_attachments', Req::val('delete_attachments', !Req::val('action') && $group_details['delete_attachments']), 'deleteattachments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewhistory"><?php echo Filters::noXSS(L('viewhistory')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_history', Req::val('view_history', !Req::val('action') && $group_details['view_history']), 'viewhistory'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="canviewroadmap"><?php echo Filters::noXSS(L('canviewroadmap')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_roadmap', Req::val('view_roadmap', !Req::val('action') && $group_details['view_roadmap']), 'canviewroadmap'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="closeowntasks"><?php echo Filters::noXSS(L('closeowntasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('close_own_tasks', Req::val('close_own_tasks', !Req::val('action') && $group_details['close_own_tasks']), 'closeowntasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="closeothertasks"><?php echo Filters::noXSS(L('closeothertasks')); ?></label></td>
+ <td><?php echo tpl_checkbox('close_other_tasks', Req::val('close_other_tasks', !Req::val('action') && $group_details['close_other_tasks']), 'closeothertasks'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="assigntoself"><?php echo Filters::noXSS(L('assigntoself')); ?></label></td>
+ <td><?php echo tpl_checkbox('assign_to_self', Req::val('assign_to_self', !Req::val('action') && $group_details['assign_to_self']), 'assigntoself'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="assignotherstoself"><?php echo Filters::noXSS(L('assignotherstoself')); ?></label></td>
+ <td><?php echo tpl_checkbox('assign_others_to_self', Req::val('assign_others_to_self', !Req::val('action') && $group_details['assign_others_to_self']), 'assignotherstoself'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="addtoassignees"><?php echo Filters::noXSS(L('addtoassignees')); ?></label></td>
+ <td><?php echo tpl_checkbox('add_to_assignees', Req::val('add_to_assignees', !Req::val('action') && $group_details['add_to_assignees']), 'addtoassignees'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewreports"><?php echo Filters::noXSS(L('viewreports')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_reports', Req::val('view_reports', !Req::val('action') && $group_details['view_reports']), 'viewreports'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="canvote"><?php echo Filters::noXSS(L('canvote')); ?></label></td>
+ <td><?php echo tpl_checkbox('add_votes', Req::val('add_votes', !Req::val('action') && $group_details['add_votes']), 'canvote'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="editassignments"><?php echo Filters::noXSS(L('editassignments')); ?></label></td>
+ <td><?php echo tpl_checkbox('edit_assignments', Req::val('edit_assignments', !Req::val('action') && $group_details['edit_assignments']), 'editassignments'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="show_as_assignees"><?php echo Filters::noXSS(L('showasassignees')); ?></label></td>
+ <td><?php echo tpl_checkbox('show_as_assignees', Req::val('show_as_assignees', !Req::val('action') && $group_details['show_as_assignees']), 'show_as_assignees'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewestimatedeffort"><?php echo Filters::noXSS(L('viewestimatedeffort')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_estimated_effort', Req::val('view_estimated_effort', !Req::val('action') && $group_details['view_estimated_effort']), 'viewestimatedeffort'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="viewcurrenteffortdone"><?php echo Filters::noXSS(L('viewcurrenteffortdone')); ?></label></td>
+ <td><?php echo tpl_checkbox('view_current_effort_done', Req::val('view_current_effort_done', !Req::val('action') && $group_details['view_current_effort_done']), 'viewactualeffort'); ?></td>
+ </tr>
+ <tr>
+ <td><label for="trackeffort"><?php echo Filters::noXSS(L('trackeffort')); ?></label></td>
+ <td><?php echo tpl_checkbox('track_effort', Req::val('track_effort', !Req::val('action') && $group_details['track_effort']), 'trackeffort'); ?></td>
+ </tr>
+
+ <?php if (!$proj->id): ?>
+ <tr>
+ <td><label for="groupopen"><?php echo Filters::noXSS(L('groupenabled')); ?></label></td>
+ <td><?php echo tpl_checkbox('group_open', Req::val('group_open', !Req::val('action') && $group_details['group_open']), 'groupopen'); ?></td>
+ </tr>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php if ($group_details['group_id'] != '1'): ?>
+ <tr>
+ <td><label><input type="checkbox" name="delete_group" /> <?php echo Filters::noXSS(L('deletegroup')); ?></label></td>
+ <td><select name="move_to">
+ <?php echo tpl_options( array_merge( ($proj->id) ? array(L('nogroup')) : array(), Flyspray::listGroups($proj->id)), null, false, null, $group_details['group_id']); ?>
+ </select>
+ </td>
+ </tr>
+ <?php endif; ?>
+ <tr>
+ <td><label for="add_user"><i class="fa fa-user"></i> <?php echo Filters::noXSS(L('addusergroup')); ?></label></td>
+ <td>
+ <?php echo tpl_userselect('uid', '', 'add_user'); ?>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" class="buttons">
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <input type="hidden" name="action" value="<?php echo Filters::noXSS(Req::val('action', $do.'.editgroup')); ?>" />
+ <input type="hidden" name="area" value="editgroup" />
+ <input type="hidden" name="group_id" value="<?php echo Filters::noXSS($group_details['group_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('updatedetails')); ?></button>
+ </td>
+ </tr>
+ </table>
+</fieldset>
+</form>
+
+<?php echo tpl_form(Filters::noXSS(CreateURL('editgroup', Req::num('id'), $do)), null, null, null, 'id="userlist"'); ?>
+<fieldset>
+ <h3><?php echo Filters::noXSS(L('groupmembers')); ?></h3>
+ <table id="manage_users_in_groups" class="userlist">
+ <thead>
+ <tr>
+ <th>
+ <a href="javascript:ToggleSelected('manage_users_in_groups')">
+ <img title="<?php echo Filters::noXSS(L('toggleselected')); ?>" alt="<?php echo Filters::noXSS(L('toggleselected')); ?>" src="<?php echo Filters::noXSS($this->get_image('kaboodleloop')); ?>" width="16" height="16" />
+ </a>
+ </th>
+ <th><?php echo Filters::noXSS(L('username')); ?></th>
+ <th><?php echo Filters::noXSS(L('realname')); ?></th>
+ <th><?php echo Filters::noXSS(L('accountenabled')); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach(Project::listUsersIn($group_details['group_id']) as $usr): ?>
+ <tr>
+ <td class="ttcolumn"><?php echo tpl_checkbox('users['.$usr['user_id'].']'); ?></td>
+ <td><a href="<?php echo Filters::noXSS(CreateURL('edituser', $usr['user_id'])); ?>"><?php echo Filters::noXSS($usr['user_name']); ?></a></td>
+ <td><?php echo Filters::noXSS($usr['real_name']); ?></td>
+ <?php if ($usr['account_enabled']) : ?>
+ <td class="imgcol"><i class="fa fa-check" style="color:#090" title="<?php echo L('yes'); ?>"></i></td>
+ <?php else: ?>
+ <td class="imgcol"><i class="fa fa-ban" style="color:#900" title="<?php echo L('no'); ?>"></i></td>
+ <?php endif; ?>
+ </tr>
+ <?php
+ $users_in[] = $usr['user_id'];
+ endforeach;
+ ?>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="4">
+ <button type="submit"><?php echo Filters::noXSS(L('moveuserstogroup')); ?></button>
+ <select class="adminlist" name="switch_to_group">
+ <?php if ($proj->id): ?>
+ <option value="0"><?php echo Filters::noXSS(L('nogroup')); ?></option>
+ <?php endif; ?>
+ <?php echo tpl_options(Flyspray::listGroups($proj->id), null, false, null, $group_details['group_id']); ?>
+ </select>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <input type="hidden" name="action" value="movetogroup" />
+ <input type="hidden" name="old_group" value="<?php echo Filters::noXSS($group_details['group_id']); ?>" />
+ </fieldset>
+ </form>
diff --git a/themes/CleanFS/templates/common.editlinks.tpl b/themes/CleanFS/templates/common.editlinks.tpl
new file mode 100644
index 0000000..395d388
--- /dev/null
+++ b/themes/CleanFS/templates/common.editlinks.tpl
@@ -0,0 +1,15 @@
+<?php if($links): ?>
+<table class="links">
+<thead>
+ <tr><th><?php echo Filters::noXSS(L('link')); ?></th><th<?php echo $user->perms('delete_attachments') ? '' : ' style="color:#999"'; ?>><?php echo Filters::noXSS(L('delete')); ?></th></tr>
+</thead>
+<tbody>
+<?php foreach ($links as $link): ?>
+ <tr>
+ <td><a href="<?php echo Filters::noXSS($link['url']); ?>"><?php echo Filters::noXSS($link['url']); ?></a></td>
+ <td><input type="checkbox" <?php echo Filters::noXSS(tpl_disableif(!$user->perms('delete_attachments'))); ?> name="delete_link[]" value="<?php echo $link['link_id']; ?>" /></td>
+ </tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+<?php endif; ?>
diff --git a/themes/CleanFS/templates/common.links.tpl b/themes/CleanFS/templates/common.links.tpl
new file mode 100644
index 0000000..146bf67
--- /dev/null
+++ b/themes/CleanFS/templates/common.links.tpl
@@ -0,0 +1,10 @@
+<?php if ($links && $user->can_view_task($task_details)): ?>
+ <div class="links">
+ <?php echo Filters::noXSS(L('referencelinks')); ?><br>
+ <?php foreach ($links as $link): ?>
+ <a href="<?php echo Filters::noXSS($link['url']); ?>"><?php echo Filters::noXSS($link['url']); ?></a><br>
+ <?php endforeach; ?>
+ </div>
+<?php elseif (count($links)): ?>
+ <div class="links"><?php echo Filters::noXSS(L('linknoperms')); ?></div>
+<?php endif; ?>
diff --git a/themes/CleanFS/templates/common.list.tpl b/themes/CleanFS/templates/common.list.tpl
new file mode 100644
index 0000000..0ae9483
--- /dev/null
+++ b/themes/CleanFS/templates/common.list.tpl
@@ -0,0 +1,226 @@
+<p><?= eL('listnote') ?></p>
+<?php
+$tcols=5;
+if($list_type == 'version') {
+ $tcols++;
+}
+if($list_type == 'tag') {
+ $tcols=$tcols+2;
+}
+?>
+<?php if (count($rows)): ?>
+<div id="controlBox">
+ <div class="grip"></div>
+ <div class="inner">
+ <a style="display:block;" href="#" onclick="TableControl.up('listTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/up.png" alt="Up" /></a>
+ <a style="display:block;" href="#" onclick="TableControl.down('listTable'); return false;"><img src="<?php echo Filters::noXSS($this->themeUrl()); ?>/down.png" alt="Down" /></a>
+ </div>
+</div>
+<?php endif; ?>
+<?php echo tpl_form(Filters::noXSS(createURL($do, $list_type, $proj->id))); ?>
+<table class="list" id="listTable">
+<colgroup>
+ <?php if ($list_type == 'tag'): ?><col class="ctag" /><?php endif; ?>
+ <col class="cname" />
+ <?php if ($list_type == 'tag'): ?><col class="cclasses" /><?php endif; ?>
+ <col class="corder" />
+ <col class="cshow" />
+ <?php if ($list_type == 'version'): ?><col class="ctense" /><?php endif; ?>
+ <col class="cdelete" />
+ <col class="cusage" />
+</colgroup>
+<?php if ($do=='pm'): ?>
+<thead>
+<tr><th colspan="<?= $tcols ?>"><?= eL('systemvalues') ?></th></tr>
+</thead>
+<thead>
+<tr>
+ <?php if ($list_type == 'tag'): ?><th>ID</th><?php endif; ?>
+ <th><?= eL('name') ?></th>
+ <?php if ($list_type == 'tag'): ?><th>CSS Classes</th><?php endif; ?>
+ <th><?= eL('order') ?></th>
+ <th><?= eL('show') ?></th>
+ <?php if ($list_type == 'version'): ?><th><?= eL('tense') ?></th><?php endif; ?>
+ <th>&nbsp;</th>
+ <th><?= eL('usedintasks') ?></th>
+</tr>
+</thead>
+<thead id="globalentries">
+<?php if (isset($sysrows) && count($sysrows)): ?>
+<?php
+$syscountlines=-1;
+foreach ($sysrows as $row):
+$syscountlines++;
+$classtype=''; $class='';
+switch ($list_type){
+ case 'tag':
+ $classtype='tag';
+ $class='t';
+ break;
+ case 'tasktype':
+ $classtype='task_tasktype';
+ $class='typ'.$row[$list_type.'_id'];
+ break;
+ case 'status':
+ $classtype='task_status';
+ $class='sta'.$row[$list_type.'_id'];
+ break;
+ default:
+ $classtype='task_'.$list_type;
+ $class=substr($list_type, 0, 3).$row[$list_type.'_id'];
+}
+?>
+<tr>
+ <?php if ($list_type == 'tag'): ?><td><?php echo tpl_tag($row['tag_id'], $row['tag_id'], $row['class']); ?></td><?php endif; ?>
+ <td<?= ($list_type!='tag') ? ' class="'.$classtype.' '.$class.'"':'' ?>><?= ($list_type=='tag') ? tpl_tag($row['tag_id']) : Filters::noXSS($row[$list_type.'_name']); ?></td>
+ <?php if ($list_type == 'tag'): ?><td><?php echo Filters::noXSS($row['class']); ?></td><?php endif; ?>
+ <td title="<?= eL('ordertip') ?>"><?php echo Filters::noXSS($row['list_position']); ?></td>
+ <td title="<?= eL('showtip') ?>"><?php echo $row['show_in_list']; ?></td>
+ <?php if ($list_type == 'version'): ?><td title="<?= eL('listtensetip') ?>"><?php echo $row[$list_type.'_tense']; ?></td><?php endif; ?>
+ <td>&nbsp;</td>
+ <td><?php echo $row['used_in_tasks'] >0 ? $row['used_in_tasks']:''; ?></td>
+</tr>
+<?php endforeach; ?>
+<?php else: ?>
+<tr><td colspan="<?= $tcols ?>"><?= eL('novalues') ?></td></tr>
+<?php endif; ?>
+</thead>
+<?php endif; ?>
+<thead>
+<tr><th colspan="<?= $tcols ?>"><?= $do=='pm' ? eL('projectvalues') : eL('systemvalues') ?></th></tr>
+</thead>
+<thead>
+<tr>
+ <?php if ($list_type == 'tag'): ?><th>ID</th><?php endif; ?>
+ <th><?= eL('name') ?></th>
+ <?php if ($list_type == 'tag'): ?><th title="CSS Classes or a #rgb or #rrggbb color. For instance #c00 for a red background">CSS Classes or #rgb</th><?php endif; ?>
+ <th><?= eL('order') ?></th>
+ <th><?= eL('show') ?></th>
+ <?php if ($list_type == 'version'): ?><th><?= eL('tense') ?></th><?php endif; ?>
+ <th><?= eL('delete') ?></th>
+ <th><?= eL('usedintasks') ?></th>
+</tr>
+</thead>
+<tbody>
+<?php
+ $countlines = -1;
+ foreach ($rows as $row):
+ $countlines++;
+?>
+<tr<?= ($list_type == 'resolution' && $row[$list_type.'_id'] == RESOLUTION_DUPLICATE ) ? ' class="nodelete" title="fixed duplicate resolution status"':'' ?>>
+ <?php if ($list_type == 'tag'): ?><td><?php echo tpl_tag($row['tag_id'], $row['tag_id'], $row['class']); ?></td><?php endif; ?>
+ <td>
+ <input id="listname<?php echo Filters::noXSS($countlines); ?>" class="text" type="text" maxlength="40" name="list_name[<?php echo Filters::noXSS($row[$list_type.'_id']); ?>]"
+ value="<?php echo Filters::noXSS($row[$list_type.'_name']); ?>" />
+ </td>
+ <?php if ($list_type == 'tag'): ?>
+ <td>
+ <input id="listclass<?php echo Filters::noXSS($countlines); ?>" class="text" type="text" maxlength="40" name="list_class[<?php echo Filters::noXSS($row['tag_id']); ?>]"
+ value="<?php echo Filters::noXSS($row['class']); ?>" />
+ </td>
+ <?php endif; ?>
+ <td title="<?= eL('ordertip') ?>">
+ <input id="listposition<?php echo Filters::noXSS($countlines); ?>" class="text" type="text" maxlength="3" name="list_position[<?php echo Filters::noXSS($row[$list_type.'_id']); ?>]" value="<?php echo Filters::noXSS($row['list_position']); ?>" />
+ </td>
+ <td title="<?= eL('showtip') ?>">
+ <?php echo tpl_checkbox('show_in_list[' . $row[$list_type.'_id'] . ']', $row['show_in_list'], 'showinlist'.$countlines); ?>
+
+ </td>
+ <?php if ($list_type == 'version'): ?>
+ <td title="<?= eL('listtensetip') ?>">
+ <select id="tense<?php echo Filters::noXSS($countlines); ?>" name="<?php echo Filters::noXSS($list_type); ?>_tense[<?php echo Filters::noXSS($row[$list_type.'_id']); ?>]">
+ <?php echo tpl_options(array(1=>L('past'), 2=>L('present'), 3=>L('future')), $row[$list_type.'_tense']); ?>
+ </select>
+ </td>
+ <?php endif; ?>
+
+ <?php if ($row['used_in_tasks'] || ($list_type == 'status' && $row[$list_type.'_id'] < 7) || ($list_type == 'resolution' && $row[$list_type.'_id'] == RESOLUTION_DUPLICATE ) ): ?>
+ <td title="<?= eL('nodeletetip') ?>"></td>
+ <?php else: ?>
+ <td title="<?= eL('deletetip') ?>"><input id="delete<?php echo Filters::noXSS($row[$list_type.'_id']); ?>" type="checkbox" name="delete[<?php echo Filters::noXSS($row[$list_type.'_id']); ?>]" value="1" /></td>
+ <?php endif; ?>
+
+ <td><?php echo $row['used_in_tasks'] >0 ? $row['used_in_tasks']:''; ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ <?php if(count($rows)): ?>
+ <tfoot>
+ <tr>
+ <td colspan="<?= ($tcols-2) ?>"></td>
+ <td colspan="2" class="buttons">
+ <?php if ($list_type == 'version'): ?>
+ <input type="hidden" name="action" value="update_version_list" />
+ <?php else: ?>
+ <input type="hidden" name="action" value="update_list" />
+ <?php endif; ?>
+ <input type="hidden" name="list_type" value="<?php echo Filters::noXSS($list_type); ?>" />
+ <input type="hidden" name="project" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <button type="submit"><?= eL('update') ?></button>
+ </td>
+ </tr>
+ </tfoot>
+ <?php endif; ?>
+ </table>
+ <?php if (count($rows)): ?>
+ <script type="text/javascript">
+ <?php
+ echo 'TableControl.create("listTable",{
+ controlBox: "controlBox",
+ tree: false
+ });';
+ echo 'new Draggable("controlBox",{
+ handle: "grip"
+ });';
+ ?>
+ </script>
+ <?php endif; ?>
+</form>
+<hr />
+<?php echo tpl_form(Filters::noXSS(createURL($do, $list_type, $proj->id))); ?>
+<table class="list">
+<colgroup>
+ <col class="cname" />
+ <col class="corder" />
+ <col class="cshow" />
+ <?php if ($list_type == 'version'): ?><col class="ctense" /><?php endif; ?>
+ <col class="cdelete" />
+</colgroup>
+<tbody>
+<tr>
+ <td>
+ <?php if ($list_type == 'version'): ?>
+ <input type="hidden" name="action" value="<?php echo Filters::noXSS($do); ?>.add_to_version_list" />
+ <?php else: ?>
+ <input type="hidden" name="action" value="<?php echo Filters::noXSS($do); ?>.add_to_list" />
+ <?php endif; ?>
+ <input type="hidden" name="list_type" value="<?php echo Filters::noXSS($list_type); ?>" />
+ <?php if ($proj->id): ?>
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <?php endif; ?>
+ <input type="hidden" name="area" value="<?php echo Filters::noXSS(Req::val('area')); ?>" />
+ <input type="hidden" name="do" value="<?php echo Filters::noXSS($do); ?>" />
+ <input id="listnamenew" placeholder="<?= eL('name') ?>" class="text" type="text" maxlength="40" value="<?php echo Filters::noXSS(Req::val('list_name')); ?>" name="list_name" autofocus />
+ </td>
+ <td>
+ <input id="listpositionnew" placeholder="<?= eL('order') ?>" class="text" type="text" maxlength="3" value="<?php echo Filters::noXSS(Req::val('list_position')); ?>" name="list_position" />
+ </td>
+ <td>
+ <input id="showinlistnew" type="checkbox" name="show_in_list" checked="checked" disabled="disabled" />
+ </td>
+ <?php if ($list_type == 'version'): ?>
+ <td title="<?php echo Filters::noXSS(L('listtensetip')); ?>">
+ <select id="tensenew" name="<?php echo Filters::noXSS($list_type); ?>_tense">
+ <?php echo tpl_options(array(1=>L('past'), 2=>L('present'), 3=>L('future')), 2); ?>
+
+ </select>
+ </td>
+ <?php endif; ?>
+ <td class="buttons">
+ <input type="hidden" name="project" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <button type="submit" class="positive"><?= eL('addnew') ?></button>
+ </td>
+ </tr>
+</tbody>
+</table>
+</form>
diff --git a/themes/CleanFS/templates/common.multiuserselect.tpl b/themes/CleanFS/templates/common.multiuserselect.tpl
new file mode 100644
index 0000000..0692148
--- /dev/null
+++ b/themes/CleanFS/templates/common.multiuserselect.tpl
@@ -0,0 +1,49 @@
+<div class="userSelectWidget">
+<label for="find_user" class="inline"><?php echo Filters::noXSS(L('find')); ?>:</label>
+<input placeholder="<?php echo Filters::noXSS(L('user')); ?>" type="text" class="text" value="<?php echo Filters::noXSS(Post::val('find_user')); ?>" name="find_user" id="find_user" onkeyup="return entercheck(event)" />
+<a class="button" href="javascript:unselectAll()"><?php echo Filters::noXSS(L('noone')); ?></a>
+<br />
+<select size="8" name="rassigned_to[]" id="rassigned_to" multiple="multiple">
+<?php foreach ($userlist as $group => $users): ?>
+ <optgroup <?php echo ($users[0][2]==0 ? 'class="globalgroup" title="'.(Filters::noXSS(L('globalgroup'))).'" ':''); ?>label="<?php echo Filters::noXSS($users[0][3]); ?>">
+ <?php foreach ($users as $info): ?>
+ <option value="<?php echo Filters::noXSS($info[0]); ?>" <?php if (in_array($info[0], $assignees)): ?>selected="selected"<?php endif; ?>><?php echo Filters::noXSS($info[1]); ?></option>
+ <?php endforeach; ?>
+ </optgroup>
+<?php endforeach; ?>
+</select>
+</div>
+<script type="text/javascript">
+resetOption = null;
+
+function entercheck(e){
+ // Find user and select it
+ if ($('find_user').value.length < 1) {
+ if (resetOption != null) {
+ resetOption.selected = false;
+ }
+ } else {
+ var options = $('rassigned_to').options;
+ for (var i = 0; i < options.length; i++) {
+ if (options[i].text.toLowerCase().indexOf($('find_user').value.toLowerCase()) >= 0) {
+ if (resetOption != null) {
+ resetOption.selected = false;
+ }
+ if (options[i].selected == false) {
+ resetOption = options[i];
+ }
+ options[i].selected=false; // focus
+ options[i].selected=true;
+ return true;
+ }
+ }
+ }
+}
+
+function unselectAll(){
+ var options = $('rassigned_to').options;
+ for (var i = 0; i < options.length; i++) {
+ options[i].selected=false;
+ }
+}
+</script>
diff --git a/themes/CleanFS/templates/common.newgroup.tpl b/themes/CleanFS/templates/common.newgroup.tpl
new file mode 100644
index 0000000..09cb737
--- /dev/null
+++ b/themes/CleanFS/templates/common.newgroup.tpl
@@ -0,0 +1,169 @@
+<?php echo tpl_form(Filters::noXSS(CreateUrl($do,'newgroup',$proj->id)),null,null,null,'id="newgroup"'); ?>
+ <ul class="form_elements">
+ <li class="required">
+ <label for="groupname"><?php echo Filters::noXSS(L('groupname')); ?> *</label>
+ <input id="groupname" class="required text" type="text" value="<?php echo Filters::noXSS(Req::val('group_name')); ?>" name="group_name" size="20" maxlength="20" />
+ </li>
+
+ <li>
+ <label for="groupdesc"><?php echo Filters::noXSS(L('description')); ?></label>
+ <input id="groupdesc" class="text" type="text" value="<?php echo Filters::noXSS(Req::val('group_desc')); ?>" name="group_desc" size="50" maxlength="100" />
+ </li>
+
+ <li>
+ <label for="manageproject"><?php echo Filters::noXSS(L('projectmanager')); ?></label>
+ <?php echo tpl_checkbox('manage_project', Req::val('manage_project'), 'manageproject'); ?>
+ </li>
+
+ <li>
+ <label for="viewtasks"><?php echo Filters::noXSS(L('viewtasks')); ?></label>
+ <?php echo tpl_checkbox('view_tasks', Req::val('view_tasks'), 'viewtasks'); ?>
+ </li>
+
+ <li>
+ <label for="viewowntasks"><?php echo Filters::noXSS(L('viewowntasks')); ?></label>
+ <?php echo tpl_checkbox('view_own_tasks', Req::val('view_own_tasks'), 'viewowntasks'); ?>
+ </li>
+
+ <li>
+ <label for="viewgroupstasks"><?php echo Filters::noXSS(L('viewgroupstasks')); ?></label>
+ <?php echo tpl_checkbox('view_groups_tasks', Req::val('view_groups_tasks'), 'viewgroupstasks'); ?>
+ </li>
+
+ <li>
+ <label for="opennewtasks"><?php echo Filters::noXSS(L('opennewtasks')); ?></label>
+ <?php echo tpl_checkbox('open_new_tasks', Req::val('open_new_tasks'), 'opennewtasks'); ?>
+ </li>
+
+ <li>
+ <label for="canopenmultipletasks"><?php echo Filters::noXSS(L('addmultipletasks')); ?></label>
+ <?php echo tpl_checkbox('add_multiple_tasks', Req::val('add_multiple_tasks'), 'canopenmultipletasks'); ?>
+ </li>
+
+ <li>
+ <label for="modifyowntasks"><?php echo Filters::noXSS(L('modifyowntasks')); ?></label>
+ <?php echo tpl_checkbox('modify_own_tasks', Req::val('modify_own_tasks'), 'modifyowntasks'); ?>
+ </li>
+
+ <li>
+ <label for="modifyalltasks"><?php echo Filters::noXSS(L('modifyalltasks')); ?></label>
+ <?php echo tpl_checkbox('modify_all_tasks', Req::val('modify_all_tasks'), 'modifyalltasks'); ?>
+ </li>
+
+ <li>
+ <label for="viewcomments"><?php echo Filters::noXSS(L('viewcomments')); ?></label>
+ <?php echo tpl_checkbox('view_comments', Req::val('view_comments'), 'viewcomments'); ?>
+ </li>
+
+ <li>
+ <label for="addcomments"><?php echo Filters::noXSS(L('addcomments')); ?></label>
+ <?php echo tpl_checkbox('add_comments', Req::val('add_comments'), 'addcomments'); ?>
+ </li>
+
+ <li>
+ <label for="editowncomments"><?php echo Filters::noXSS(L('editowncomments')); ?></label>
+ <?php echo tpl_checkbox('edit_own_comments', Req::val('edit_own_comments'), 'editowncomments'); ?>
+ </li>
+
+ <li>
+ <label for="editcomments"><?php echo Filters::noXSS(L('editcomments')); ?></label>
+ <?php echo tpl_checkbox('edit_comments', Req::val('edit_comments'), 'editcomments'); ?>
+ </li>
+
+ <li>
+ <label for="deletecomments"><?php echo Filters::noXSS(L('deletecomments')); ?></label>
+ <?php echo tpl_checkbox('delete_comments', Req::val('delete_comments'), 'deletecomments'); ?>
+ </li>
+
+ <li>
+ <label for="createattachments"><?php echo Filters::noXSS(L('createattachments')); ?></label>
+ <?php echo tpl_checkbox('create_attachments', Req::val('create_attachments'), 'createattachments'); ?>
+ </li>
+
+ <li>
+ <label for="deleteattachments"><?php echo Filters::noXSS(L('deleteattachments')); ?></label>
+ <?php echo tpl_checkbox('delete_attachments', Req::val('delete_attachments'), 'deleteattachments'); ?>
+ </li>
+
+ <li>
+ <label for="viewhistory"><?php echo Filters::noXSS(L('viewhistory')); ?></label>
+ <?php echo tpl_checkbox('view_history', Req::val('view_history'), 'viewhistory'); ?>
+ </li>
+
+ <li>
+ <label for="canviewroadmap"><?php echo Filters::noXSS(L('canviewroadmap')); ?></label>
+ <?php echo tpl_checkbox('view_roadmap', Req::val('view_roadmap', !Req::val('action')), 'canviewroadmap'); ?>
+ </li>
+
+ <li>
+ <label for="closeowntasks"><?php echo Filters::noXSS(L('closeowntasks')); ?></label>
+ <?php echo tpl_checkbox('close_own_tasks', Req::val('close_own_tasks'), 'closeowntasks'); ?>
+ </li>
+
+ <li>
+ <label for="closeothertasks"><?php echo Filters::noXSS(L('closeothertasks')); ?></label>
+ <?php echo tpl_checkbox('close_other_tasks', Req::val('close_other_tasks'), 'closeothertasks'); ?>
+ </li>
+
+ <li>
+ <label for="assigntoself"><?php echo Filters::noXSS(L('assigntoself')); ?></label>
+ <?php echo tpl_checkbox('assign_to_self', Req::val('assign_to_self'), 'assigntoself'); ?>
+ </li>
+
+ <li>
+ <label for="assignotherstoself"><?php echo Filters::noXSS(L('assignotherstoself')); ?></label>
+ <?php echo tpl_checkbox('assign_others_to_self', Req::val('assign_others_to_self'), 'assignotherstoself'); ?>
+ </li>
+
+ <li>
+ <label for="addtoassignees"><?php echo Filters::noXSS(L('addtoassignees')); ?></label>
+ <?php echo tpl_checkbox('add_to_assignees', Req::val('add_to_assignees'), 'addtoassignees'); ?>
+ </li>
+
+ <li>
+ <label for="viewreports"><?php echo Filters::noXSS(L('viewreports')); ?></label>
+ <?php echo tpl_checkbox('view_reports', Req::val('view_reports'), 'viewreports'); ?>
+ </li>
+
+ <li>
+ <label for="canvote"><?php echo Filters::noXSS(L('canvote')); ?></label>
+ <?php echo tpl_checkbox('add_votes', Req::val('add_votes'), 'canvote'); ?>
+ </li>
+
+ <li>
+ <label for="editassignments"><?php echo Filters::noXSS(L('editassignments')); ?></label>
+ <?php echo tpl_checkbox('edit_assignments', Req::val('edit_assignments'), 'editassignments'); ?>
+ </li>
+
+ <li>
+ <label for="show_as_assignees"><?php echo Filters::noXSS(L('showasassignees')); ?></label>
+ <?php echo tpl_checkbox('show_as_assignees', Req::val('show_as_assignees'), 'show_as_assignees'); ?>
+ </li>
+
+ <li>
+ <label for="viewestimatedeffort"><?php echo Filters::noXSS(L('viewestimatedeffort')); ?></label>
+ <?php echo tpl_checkbox('view_estimated_effort', Req::val('view_estimated_effort'), 'viewestimatedeffort'); ?>
+ </li>
+
+ <li>
+ <label for="viewcurrenteffortdone"><?php echo Filters::noXSS(L('viewcurrenteffortdone')); ?></label>
+ <?php echo tpl_checkbox('view_current_effort_done', Req::val('view_current_effort_done'), 'viewcurrenteffortdone'); ?>
+ </li>
+
+ <li>
+ <label for="trackeffort"><?php echo Filters::noXSS(L('trackeffort')); ?></label>
+ <?php echo tpl_checkbox('track_effort', Req::val('track_effort'), 'trackeffort'); ?>
+ </li>
+
+ <?php if (!$proj->id): ?>
+ <li>
+ <label for="groupopen"><?php echo Filters::noXSS(L('groupenabled')); ?></label>
+ <?php echo tpl_checkbox('group_open', Req::val('group_open'), 'groupopen'); ?>
+ </li>
+ <?php endif; ?>
+ </ul>
+
+ <input type="hidden" name="action" value="<?php if ($proj->id): ?>pm<?php else: ?>admin<?php endif; ?>.newgroup" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS(Req::val('project')); ?>" />
+ <button type="submit" class="positive"><?php echo Filters::noXSS(L('addthisgroup')); ?></button>
+</form>
diff --git a/themes/CleanFS/templates/common.newuser.tpl b/themes/CleanFS/templates/common.newuser.tpl
new file mode 100644
index 0000000..539e307
--- /dev/null
+++ b/themes/CleanFS/templates/common.newuser.tpl
@@ -0,0 +1,120 @@
+<?php
+/* Note: This template file is currently in dual use for one of two guest register configurations
+and also in admin area for admins.
+May change in future because this sharing adds more complexity compared to little gain.
+*/
+if ($do === 'admin'): echo tpl_form(Filters::noXSS(createURL($do, 'newuser')),null,null,null,'id="registernewuser"');
+ else: echo tpl_form(Filters::noXSS(createURL($do, 'newuser')),null,null,null,'id="registernewuser"');
+endif; ?>
+<ul class="form_elements">
+ <li class="required">
+ <?php if ($do === 'admin'): ?>
+ <input type="hidden" name="action" value="admin.newuser" />
+ <input type="hidden" name="do" value="admin" />
+ <input type="hidden" name="area" value="newuser" />
+ <?php else: ?>
+ <input type="hidden" name="action" value="register.newuser" />
+ <?php endif; ?>
+ <label for="username"><?= eL('username') ?></label>
+ <input id="username" name="user_name" value="<?php echo Filters::noXSS(Req::val('user_name')); ?>" required="required" type="text" size="20" maxlength="32" onblur="checkname(this.value);" />
+ <br /><span id="errormessage"></span>
+ </li>
+
+ <?php if (!$fs->prefs['disable_changepw']): ?>
+ <li>
+ <label for="userpass"><?= eL('password') ?></label>
+ <input id="userpass" class="password" name="user_pass" value="<?php echo Filters::noXSS(Req::val('user_pass')); ?>" type="password" size="20" maxlength="100" /> <em><?php echo Filters::noXSS(L('minpwsize')); ?></em>
+ <span class="note"><?= eL('leaveemptyauto') ?></span>
+ </li>
+ <?php if( $do==='register' && $fs->prefs['repeat_password'] ): ?>
+ <li>
+ <label for="userpass2"><?= eL('confirmpass') ?></label>
+ <input id="userpass2" class="password" name="user_pass2" value="<?php echo Filters::noXSS(Req::val('user_pass2')); ?>" type="password" size="20" maxlength="100" /><br />
+ </li>
+ <?php endif; ?>
+ <?php endif; ?>
+
+ <li class="required">
+ <label for="realname"><?= eL('realname') ?></label>
+ <input id="realname" name="real_name" required="required" value="<?php echo Filters::noXSS(Req::val('real_name')); ?>" type="text" size="20" maxlength="100" />
+ </li>
+
+ <li class="required">
+ <label for="emailaddress"><?= eL('emailaddress') ?></label>
+ <input id="emailaddress" name="email_address" required="required" value="<?php echo Filters::noXSS(Req::val('email_address')); ?>" type="text" size="20" maxlength="100" />
+ <!-- <em><?= eL('validemail') ?></em> -->
+ </li>
+
+ <?php if( $do==='register' && $fs->prefs['repeat_emailaddress'] ): ?>
+ <li>
+ <label for="verifyemailaddress"><?= eL('verifyemailaddress') ?></label>
+ <input id="verifyemailaddress" value="<?php echo Filters::noXSS(Req::val('verify_email_address')); ?>" name="verify_email_address" required="required" type="text" size="20" maxlength="100" />
+ </li>
+ <?php endif; ?>
+
+ <?php if (!empty($fs->prefs['jabber_server'])): ?>
+ <li>
+ <label for="jabberid"><?= eL('jabberid') ?></label>
+ <input id="jabberid" name="jabber_id" type="text" value="<?php echo Filters::noXSS(Req::val('jabber_id')); ?>" size="20" maxlength="100" />
+ </li>
+ <?php endif ?>
+
+ <?php if ($fs->prefs['enable_avatars']): ?>
+ <li>
+ <label for="profileimage"><?= eL('profileimage') ?></label>
+ <input id="profileimage" name="profile_image" type="file" value="<?php echo Filters::noXSS(Req::val('profile_image')); ?>"/>
+ </li>
+ <?php endif ?>
+
+ <li>
+ <label for="notify_type"><?= eL('notifications') ?></label>
+ <select id="notify_type" name="notify_type">
+ <?php echo tpl_options($fs->getNotificationOptions(), Req::val('notify_type')); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="time_zone"><?= eL('timezone') ?></label>
+ <select id="time_zone" name="time_zone">
+ <?php
+ $times = array();
+ for ($i = -12; $i <= 13; $i++) {
+ $times[$i] = L('GMT') . (($i == 0) ? ' ' : (($i > 0) ? '+' . $i : $i));
+ }
+ ?>
+ <?php echo tpl_options($times, Req::val('time_zone', 0)); ?>
+ </select>
+ </li>
+
+ <?php if (isset($groups)): ?>
+ <li>
+ <label for="groupin"><?= eL('globalgroup') ?></label>
+ <select id="groupin" class="adminlist" name="group_in">
+ <?php echo tpl_options($groups, Req::val('group_in', $fs->prefs['anon_group'])); ?>
+ </select>
+ </li>
+ <?php endif; ?>
+
+ <?php if($do==='register' && $fs->prefs['captcha_securimage']) : ?>
+ <li class="captchali">
+ <style>
+ #captcha_code{width:100px;}
+ .captchali .securimage label{width:auto;}
+ .captchali .securimage {display:inline-block; width:300px;}
+ </style>
+ <label for="captcha_code"><?= eL('registercaptcha') ?></label>
+ <div class="securimage"><?php echo $captcha_securimage_html; ?></div>
+ </li>
+ <?php endif; ?>
+ </ul>
+ <?php
+ /* only guests need captcha, not admins that currently this template file to add a user too. */
+ if( $do==='register'
+ && isset($fs->prefs['captcha_recaptcha']) && $fs->prefs['captcha_recaptcha']
+ && isset($fs->prefs['captcha_recaptcha_sitekey']) && $fs->prefs['captcha_recaptcha_sitekey']
+ && isset($fs->prefs['captcha_recaptcha_secret']) && $fs->prefs['captcha_recaptcha_secret']): ?>
+ <div class="g-recaptcha" data-sitekey="<?php echo Filters::noXSS($fs->prefs['captcha_recaptcha_sitekey']); ?>"></div>
+ <noscript>Javascript is required for this Google reCAPTCHA.</noscript>
+ <?php endif; ?>
+ <p><button type="submit" id="buSubmit"><?= eL('registeraccount') ?></button></p>
+</form>
diff --git a/themes/CleanFS/templates/common.newuserbulk.tpl b/themes/CleanFS/templates/common.newuserbulk.tpl
new file mode 100644
index 0000000..f9724b6
--- /dev/null
+++ b/themes/CleanFS/templates/common.newuserbulk.tpl
@@ -0,0 +1,78 @@
+<?php if ($do == 'admin'): echo tpl_form(Filters::noXSS(CreateURL($do, 'newuserbulk')),null,null,null,'id="registernewuser"');
+ else: echo tpl_form(Filters::noXSS($_SERVER['SCRIPT_NAME']), null,null,null,'id="registernewuser"');
+endif; ?>
+ <ul class="form_elements">
+ <li class="required">
+ <?php if ($do == 'admin'): ?>
+ <input type="hidden" name="action" value="admin.newuserbulk" />
+ <input type="hidden" name="do" value="admin" />
+ <input type="hidden" name="area" value="newuserbulk" />
+ <?php else: ?>
+ <input type="hidden" name="action" value="register.newuserbulk" />
+ <?php endif; ?>
+ </li>
+
+ <!-- Header -->
+ <li>
+ <b><?php echo Filters::noXSS(L('bulkuserstoadd')); ?>:</b>
+ </li>
+ <table class="bulkuser">
+ <tr>
+ <td><label for="realname"><?php echo Filters::noXSS(L('realname')); ?></label></td>
+ <td><label for="username"><?php echo Filters::noXSS(L('username')); ?></label></td>
+ <td><label for="emailaddress"><?php echo Filters::noXSS(L('emailaddress')); ?></label></td>
+ </tr>
+ <br />
+
+
+<?php for ($i = 0 ; $i < 10 ; $i++) { ?>
+ <!-- User <?php echo Filters::noXSS($i); ?> -->
+ <tr>
+ <td><input id="realname" name="real_name<?php echo Filters::noXSS($i); ?>" class="text" value="<?php echo Filters::noXSS(Req::val('real_name')); ?>" type="text" size="20" maxlength="100" onblur="this.form.elements['user_name<?php echo Filters::noXSS($i); ?>'].value = this.form.elements['real_name<?php echo Filters::noXSS($i); ?>'].value.replace(/ /g,'');"/></td>
+ <td><input id="username" name="user_name<?php echo Filters::noXSS($i); ?>" value="<?php echo Filters::noXSS(Req::val('user_name')); ?>" class="text" type="text" size="20" maxlength="32" onblur="checkname(this.value); "/></td>
+ <td><input id="emailaddress" name="email_address<?php echo Filters::noXSS($i); ?>" class="text" value="<?php echo Filters::noXSS(Req::val('email_address')); ?>" type="text" size="20" maxlength="100" /></td>
+ <tr>
+ <tr></tr>
+<?php } ?>
+ </table>
+
+
+ <br />
+
+ <li>
+ <b><?php echo Filters::noXSS(L('optionsforallusers')); ?>:</b>
+ </li>
+ <li>
+ <label for="notify_type"><?php echo Filters::noXSS(L('notifications')); ?></label>
+ <select id="notify_type" name="notify_type">
+ <?php echo tpl_options($fs->GetNotificationOptions(), Req::val('notify_type')); ?>
+
+ </select>
+ </li>
+
+ <li>
+ <label for="time_zone"><?php echo Filters::noXSS(L('timezone')); ?></label>
+ <select id="time_zone" name="time_zone">
+ <?php
+ $times = array();
+ for ($i = -12; $i <= 13; $i++) {
+ $times[$i] = L('GMT') . (($i == 0) ? ' ' : (($i > 0) ? '+' . $i : $i));
+ }
+ ?>
+ <?php echo tpl_options($times, Req::val('time_zone', 0)); ?>
+
+ </select>
+ </li>
+
+ <?php if (isset($groups)): ?>
+ <li>
+ <label for="groupin"><?php echo Filters::noXSS(L('globalgroup')); ?></label>
+ <select id="groupin" class="adminlist" name="group_in">
+ <?php echo tpl_options($groups, Req::val('group_in', $fs->prefs['anon_group'])); ?>
+ </select>
+ </li>
+ <?php endif; ?>
+
+ </ul>
+ <p><button type="submit" id="buSubmit"><?php echo Filters::noXSS(L('registerbulkaccount')); ?></button></p>
+</form>
diff --git a/themes/CleanFS/templates/common.profile.tpl b/themes/CleanFS/templates/common.profile.tpl
new file mode 100644
index 0000000..97920f4
--- /dev/null
+++ b/themes/CleanFS/templates/common.profile.tpl
@@ -0,0 +1,152 @@
+<?php echo tpl_form( $do=='myprofile' ? Filters::noXSS(createUrl('myprofile')) : Filters::noXSS(createUrl('edituser', $theuser->id))); ?>
+ <ul class="form_elements">
+ <li>
+ <label for="realname"><?= eL('realname') ?></label>
+ <input id="realname" type="text" name="real_name" maxlength="100"
+ value="<?php echo Filters::noXSS(Req::val('real_name', $theuser->infos['real_name'])); ?>" />
+ </li>
+ <li>
+ <label for="emailaddress"><?= eL('emailaddress') ?></label>
+ <input id="emailaddress" type="text" name="email_address" maxlength="100"
+ value="<?php echo Filters::noXSS(Req::val('email_address', $theuser->infos['email_address'])); ?>" />
+ </li>
+ <li>
+ <label for="hide_my_email"><?= eL('hidemyemail') ?></label>
+ <?php echo tpl_checkbox('hide_my_email', Req::val('hide_my_email', !Post::val('action') && $theuser->infos['hide_my_email']), 'hide_my_email', 1, ($fs->prefs['hide_emails'] ) ? array('checked' => 'true', 'disabled' => 'true') : ''); ?>
+ </li>
+ <?php if (!empty($fs->prefs['jabber_server'])):?>
+ <li>
+ <label for="jabberid"><?= eL('jabberid') ?></label>
+ <input id="jabberid" type="text" name="jabber_id" maxlength="100"
+ value="<?php echo Filters::noXSS(Req::val('jabber_id', $theuser->infos['jabber_id'])); ?>" />
+ <input type="hidden" name="old_jabber_id" value="<?php echo Filters::noXSS($theuser->infos['jabber_id']); ?>" />
+ </li>
+ <?php endif ?>
+ <?php if ($fs->prefs['enable_avatars']): ?>
+ <li>
+ <label for="profileimage"><?= eL('profileimage') ?></label>
+ <?php echo tpl_userlinkavatar($theuser->id, $fs->prefs['max_avatar_size'], 'av_comment'); ?>
+ </li>
+ <li>
+ <label for="profileimage_input">&nbsp;</label>
+ <input id="profileimage_input" name="profile_image" type="file" value="<?php echo Filters::noXSS(Req::val('profile_image')); ?>"/>
+ </li>
+ <?php endif ?>
+ <li>
+ <label for="notifytype"><?= eL('notifytype') ?></label>
+ <select id="notifytype" name="notify_type">
+ <?php echo tpl_options($fs->getNotificationOptions(), Req::val('notify_type', $theuser->infos['notify_type'])); ?>
+ </select>
+ </li>
+ <li>
+ <label for="notify_own"><?= eL('notifyown') ?></label>
+ <?php echo tpl_checkbox('notify_own', Req::val('notify_own', !Post::val('action') && $theuser->infos['notify_own']), 'notify_own'); ?>
+ </li>
+ <!--<li>
+ <label for="notify_online"><?= eL('notifyonline') ?></label>
+ <?php echo tpl_checkbox('notify_online', Req::val('notify_online', !Post::val('action') && $theuser->infos['notify_online']), 'notify_online'); ?>
+ </li>-->
+ <li>
+ <label for="dateformat"><?= eL('dateformat') ?></label>
+ <select id="dateformat" name="dateformat">
+ <?php echo tpl_date_formats($theuser->infos['dateformat']); ?>
+ </select>
+ </li>
+ <li>
+ <label for="dateformat_extended"><?= eL('dateformat_extended') ?></label>
+ <select id="dateformat_extended" name="dateformat_extended">
+ <?php echo tpl_date_formats($theuser->infos['dateformat_extended'], true); ?>
+ </select>
+ </li>
+ <li>
+ <label for="tasks_perpage"><?= eL('tasksperpage') ?></label>
+ <select name="tasks_perpage" id="tasks_perpage">
+ <?php echo tpl_options(array(10, 25, 50, 100, 250), Req::val('tasks_perpage', $theuser->infos['tasks_perpage']), true); ?>
+ </select>
+ </li>
+ <li>
+ <label for="time_zone"><?= eL('timezone') ?></label>
+ <select id="time_zone" name="time_zone">
+ <?php
+ $times = array();
+ for ($i = -12; $i <= 13; $i++) {
+ $times[$i] = L('GMT') . (($i == 0) ? ' ' : (($i > 0) ? '+' . $i : $i));
+ }
+ ?>
+ <?php echo tpl_options($times, Req::val('time_zone', $theuser->infos['time_zone'])); ?>
+ </select>
+ </li>
+ <li>
+ <label for="langcode"><?= eL('language') ?></label>
+ <select id="langcode" name="lang_code">
+ <?php
+ #echo tpl_options(array_merge(array('browser', 'project'), Flyspray::listLangs()), Req::val('lang_code', $theuser->infos['lang_code']), true);
+ echo tpl_options( Flyspray::listLangs(), Req::val('lang_code', $theuser->infos['lang_code']), true);
+ ?>
+ </select>
+ </li>
+ <li>
+ <hr />
+ </li>
+ <?php if ($user->perms('is_admin')): ?>
+ <li>
+ <label for="accountenabled"><?= eL('accountenabled') ?></label>
+ <?php echo tpl_checkbox('account_enabled', Req::val('account_enabled', !Post::val('action') && $theuser->infos['account_enabled']), 'accountenabled'); ?>
+ </li>
+ <li>
+ <label for="delete_user"><?= eL('deleteuser') ?></label>
+ <?php echo tpl_checkbox('delete_user', false, 'delete_user'); ?>
+ </li>
+ <?php endif; ?>
+ <li>
+ <label for="groupin"><?= eL('globalgroup') ?></label>
+ <select id="groupin" class="adminlist" name="group_in" <?php echo tpl_disableif(!$user->perms('is_admin')); ?>>
+ <?php echo tpl_options($groups, Req::val('group_in', $theuser->infos['global_group'])); ?>
+ </select>
+ <input type="hidden" name="old_global_id" value="<?php echo Filters::noXSS($theuser->infos['global_group']); ?>" />
+ </li>
+
+ <?php if ($proj->id): ?>
+ <li>
+ <label for="projectgroupin"><?= eL('projectgroup') ?></label>
+ <select id="projectgroupin" class="adminlist" name="project_group_in" <?php echo tpl_disableif(!$user->perms('manage_project')); ?>>
+ <?php echo tpl_options(array_merge($project_groups, array(0 => array('group_name' => L('none'), 0 => 0, 'group_id' => 0, 1 => L('none')))), Req::val('project_group_in', $theuser->perms('project_group'))); ?>
+ </select>
+ <input type="hidden" name="old_group_id" value="<?php echo Filters::noXSS($theuser->perms('project_group')); ?>" />
+ <input type="hidden" name="project_id" value="<?php echo $proj->id; ?>" />
+ </li>
+ <?php endif; ?>
+ <li>
+ <hr />
+ </li>
+
+ <?php if (!$theuser->infos['oauth_uid']): ?>
+ <?php if ($user->perms('is_admin') || $user->id == $theuser->id): ?>
+ <?php if (!$fs->prefs['disable_changepw']): ?>
+ <?php if (!$user->perms('is_admin')): ?>
+ <li>
+ <label for="oldpass"><?= eL('oldpass') ?></label>
+ <input id="oldpass" type="password" name="oldpass" value="<?php echo Filters::noXSS(Req::val('oldpass')); ?>" maxlength="100" />
+ </li>
+ <?php endif; ?>
+ <li>
+ <label for="changepass"><?= eL('changepass') ?></label>
+ <input id="changepass" type="password" name="changepass" value="<?php echo Filters::noXSS(Req::val('changepass')); ?>" maxlength="100" />
+ </li>
+ <?php if ($fs->prefs['repeat_password']): ?>
+ <li>
+ <label for="confirmpass"><?= eL('confirmpass') ?></label>
+ <input id="confirmpass" type="password" name="confirmpass" value="<?php echo Filters::noXSS(Req::val('confirmpass')); ?>" maxlength="100" />
+ </li>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php endif; ?>
+ <li>
+ <input type="hidden" name="action" value="<?php echo Filters::noXSS(Req::val('action', $do . '.edituser')); ?>" />
+ <?php if (Req::val('area') || $do == 'admin'): ?><input type="hidden" name="area" value="users" /><?php endif; ?>
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($theuser->id); ?>" />
+ <button type="submit"><?= eL('updatedetails') ?></button>
+ </li>
+ </ul>
+ </form>
diff --git a/themes/CleanFS/templates/common.userselect.tpl b/themes/CleanFS/templates/common.userselect.tpl
new file mode 100644
index 0000000..dd58eab
--- /dev/null
+++ b/themes/CleanFS/templates/common.userselect.tpl
@@ -0,0 +1,6 @@
+<input class="users text" <?php echo join_attrs($attrs); ?> type="text" name="<?php echo Filters::noXSS($name); ?>" <?php if ($id): ?>id="<?php echo Filters::noXSS($id); ?>"<?php endif; ?> value="<?php echo Filters::noXSS($value); ?>" />
+<span class="autocomplete" id="<?php echo Filters::noXSS($name); ?>_complete"></span>
+<script type="text/javascript">
+ showstuff('<?php echo Filters::noJsXSS($name); ?>_complete');
+ new Ajax.Autocompleter('<?php echo Filters::noJsXSS($id); ?>', '<?php echo Filters::noJsXSS($name); ?>_complete', '<?php echo Filters::noXSS($baseurl); ?>js/callbacks/usersearch.php', {})
+</script>
diff --git a/themes/CleanFS/templates/depends.tpl b/themes/CleanFS/templates/depends.tpl
new file mode 100644
index 0000000..64b2047
--- /dev/null
+++ b/themes/CleanFS/templates/depends.tpl
@@ -0,0 +1,107 @@
+<div style="text-align:center;" class="box">
+ <p><b><?php echo Filters::noXSS(L('pruninglevel')); ?>: </b><?php echo implode(" &nbsp;|&nbsp; \n", $strlist); ?></p>
+ <h2><a href="<?php echo Filters::noXSS(CreateUrl('details', $task_id)); ?>">FS#<?php echo $task_id; ?></a>: <?php echo Filters::noXSS(L('dependencygraph')); ?></h2>
+
+<div id="infovis" style="width:90%;height:50em"></div>
+
+<script type="text/javascript">
+ // init data
+ var json = <?php echo $jasonData; ?>;
+
+ // init ForceDirected
+ var fd = new $jit.ForceDirected({
+ //id of the visualization container
+ injectInto: 'infovis',
+ //Enable zooming and panning
+ //by scrolling and DnD
+ Navigation: {
+ enable: true,
+ //Enable panning events only if we're dragging the empty
+ //canvas (and not a node).
+ zooming: 50 //zoom speed. higher is more sensible
+ },
+ // Change node and edge styles such as
+ // color and width.
+ // These properties are also set per node
+ // with dollar prefixed data-properties in the
+ // JSON structure.
+ Node: {
+ overridable: true,
+ },
+ Edge: {
+ overridable: false,
+ color: '#555555',
+ type: 'arrow',
+ dim: 25,
+ lineWidth: 1
+ },
+ //Native canvas text styling
+ Label: {
+ type: 'HTML', //Native or HTML
+ size: 10,
+ style: 'bold',
+ },
+ //Add Tips
+ Tips: {
+ enable: true,
+ onShow: function(tip, node) {
+ // Count connections
+ var count = 0;
+ node.eachAdjacency(function(adj) {
+ count++;
+ });
+
+ // Display node info in tooltip
+ tip.innerHTML = "<div class=\"popup\" style=\"width:200px\">" + node.name
+ + "<div><b><?php echo Filters::noXSS(L('connectedtasks')); ?></b> " + count + "</div></div>";
+ }
+ },
+ // Add node events
+ Events: {
+ enable: true,
+ type: 'Native'
+ },
+ //Number of iterations for the FD algorithm
+ iterations: 50,
+ //Edge length
+ levelDistance: 130,
+ // Add text to the labels. This method is only triggered
+ // on label creation and only for DOM labels (not native canvas ones).
+ onCreateLabel: function(domElement, node){
+ domElement.innerHTML = node.name;
+ var style = domElement.style;
+ style.fontSize = "1em";
+ style.color = "#ddd";
+ },
+ // Change node styles when DOM labels are placed
+ // or moved.
+ onPlaceLabel: function(domElement, node){
+ var style = domElement.style;
+ var left = parseInt(style.left);
+ var top = parseInt(style.top);
+ var w = domElement.offsetWidth;
+ style.left = (left - w / 2) + 'px';
+ style.top = top + 'px';
+ style.padding = 25 + 'px';
+ style.display = '';
+ }
+ });
+ // load JSON data.
+ fd.loadJSON(json);
+ // compute positions incrementally and animate.
+ fd.computeIncremental({
+ iter: 40,
+ property: 'end',
+ onComplete: function(){
+ fd.animate({
+ modes: ['linear'],
+ transition: $jit.Trans.Elastic.easeOut,
+ duration: 1500
+ });
+ }
+ });
+ // end
+
+</script>
+
+</div> \ No newline at end of file
diff --git a/themes/CleanFS/templates/details.edit.tpl b/themes/CleanFS/templates/details.edit.tpl
new file mode 100644
index 0000000..d56bd56
--- /dev/null
+++ b/themes/CleanFS/templates/details.edit.tpl
@@ -0,0 +1,255 @@
+<?php echo tpl_form(Filters::noXSS(createUrl('details', $task_details['task_id'])),null,null,null,'id="taskeditform"'); ?>
+<!-- Grab fields wanted for this project so we can only show those we want -->
+<?php $fields = explode( ' ', $proj->prefs['visible_fields'] );
+# FIXME The template should respect the ordering of 'visible_fields', aren't they?
+# Maybe define a 'put visible_fields in default ordering'-button in project settings to let them make consistent with other projects and a no-brainer.
+# But let also project managers have the choice to sort to the order they want it.
+
+# FIXME If user wants a task to be moved to other project and a hidden list value (not in visible_fields) would be not legal in the target project:
+# Should we show that dropdown-list even if the field is not in the $fields-array to give the user the chance to resolve the issue?
+# The field list dropdown is not a secret for webtech-people, it is just not visible by css display:none;
+?>
+<style>
+/* can be moved to default theme.css later, when the multiple errors/messages-feature is matured. currently used only here. */
+.errorinput{color:#c00 !important;}
+li.errorinput{background-color:#fc9;}
+.errorinput::before{display:block;content: attr(title);}
+</style>
+<div id="taskdetails">
+ <input type="hidden" name="action" value="details.update" />
+ <input type="hidden" name="edit" value="1" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="edit_start_time" value="<?php echo Filters::noXSS(Req::val('edit_start_time', time())); ?>" />
+ <div id="taskfields">
+ <ul class="form_elements slim">
+ <!-- Status -->
+ <li<?php
+ # show the tasktype if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invalidstatus']) ? ' class="errorinput"' : (in_array('status', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidstatus']) ? '<span class="errorinput" style="display:block;">'.eL('invalidstatus').'</span>' : ''; ?>
+ <label for="status"><?= eL('status') ?></label>
+ <?php echo tpl_select($statusselect); ?>
+ </li>
+ <!-- Progress -->
+ <li<?php echo in_array('progress', $fields) ? '' : ' style="display:none"'; ?>>
+ <label for="percent"<?php echo isset($_SESSION['ERRORS']['invalidprogress']) ? ' class="errorinput" title="'.eL('invalidprogress').'"':''; ?>><?php echo Filters::noXSS(L('percentcomplete')); ?></label>
+ <select id="percent" name="percent_complete" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')) ?>>
+ <?php $arr = array(); for ($i = 0; $i<=100; $i+=10) $arr[$i] = $i.'%'; ?>
+ <?php echo tpl_options($arr, Req::val('percent_complete', $task_details['percent_complete'])); ?>
+ </select>
+ </li>
+ <!-- Task Type -->
+ <li<?php
+ # show the tasktype if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invalidtasktype']) ? ' class="errorinput"' : (in_array('tasktype', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidtasktype']) ? '<span class="errorinput" style="display:block;">'.eL('invalidtasktype').'</span>' : ''; ?>
+ <label for="tasktype"><?= eL('tasktype') ?></label>
+ <?php echo tpl_select($tasktypeselect); ?>
+ </li>
+ <!-- Category -->
+ <li<?php
+ # show the category if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invalidcategory']) ? ' class="errorinput"' : (in_array('category', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidcategory']) ? '<span class="errorinput" style="display:block;">'.eL('invalidcategory').'</span>' : ''; ?>
+ <label for="category"><?= eL('category') ?></label>
+ <?php echo tpl_select($catselect); ?>
+ </li>
+ <!-- Assigned To -->
+ <li<?php echo in_array('assignedto', $fields) ? '' : ' style="display:none"'; ?>>
+ <label><?= eL('assignedto') ?></label>
+ <?php if ($user->perms('edit_assignments')): ?>
+ <input type="hidden" name="old_assigned" value="<?php echo Filters::noXSS($old_assigned); ?>" />
+ <?php $this->display('common.multiuserselect.tpl'); ?>
+ <?php else: ?>
+ <?php if (empty($assigned_users)): ?>
+ <?= eL('noone') ?>
+ <?php else:
+ foreach ($assigned_users as $userid): ?>
+ <?php echo tpl_userlink($userid); ?><br />
+ <?php endforeach;
+ endif;
+ endif; ?>
+ </li>
+ <!-- OS -->
+ <li<?php
+ # show the os if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invalidos']) ? ' class="errorinput"' : (in_array('os', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidos']) ? '<span class="errorinput" style="display:block;">'.eL('invalidos').'</span>' : ''; ?>
+ <label for="os"><?= eL('operatingsystem') ?></label>
+ <?php echo tpl_select($osselect); ?>
+ </li>
+ <!-- Severity -->
+ <li<?php echo in_array('severity', $fields) ? '' : ' style="display:none"'; ?>>
+ <label for="severity"<?php echo isset($_SESSION['ERRORS']['invalidseverity']) ? ' class="errorinput" title="'.eL('invalidseverity').'"':''; ?>><?php echo Filters::noXSS(L('severity')); ?></label>
+ <select id="severity" name="task_severity">
+ <?php echo tpl_options($fs->severities, Req::val('task_severity', $task_details['task_severity'])); ?>
+ </select>
+ </li>
+ <!-- Priority -->
+ <li<?php echo in_array('priority', $fields) ? '' : ' style="display:none"'; ?>>
+ <label for="priority"<?php echo isset($_SESSION['ERRORS']['invalidpriority']) ? ' class="errorinput" title="'.eL('invalidpriority').'"':''; ?>><?php echo Filters::noXSS(L('priority')); ?></label>
+ <select id="priority" name="task_priority" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')) ?>>
+ <?php echo tpl_options($fs->priorities, Req::val('task_priority', $task_details['task_priority'])); ?>
+ </select>
+ </li>
+ <!-- Reported In -->
+ <li<?php
+ # show the reportedversion if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invalidreportedversion']) ? ' class="errorinput"' : (in_array('reportedin', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidreportedversion']) ? '<span class="errorinput" style="display:block;">'.eL('invalidreportedversion').'</span>' : ''; ?>
+ <label for="reportedver"><?= eL('reportedversion') ?></label>
+ <?php echo tpl_select($reportedversionselect); ?>
+ </li>
+ <!-- Due Version -->
+ <li<?php
+ # show the dueversion if invalid when moving tasks - even if not in the visible list.
+ echo isset($_SESSION['ERRORS']['invaliddueversion']) ? ' class="errorinput"' : (in_array('dueversion', $fields) ? '' : ' style="display:none"'); ?>>
+ <?php echo isset($_SESSION['ERRORS']['invaliddueversion']) ? '<span class="errorinput" style="display:block;">'.eL('invaliddueversion').'</span>' : ''; ?>
+ <label for="dueversion"><?= eL('dueinversion') ?></label>
+ <?php echo tpl_select($dueversionselect); ?>
+ </li>
+ <!-- Due Date -->
+ <li<?php echo (in_array('duedate', $fields) && $user->perms('modify_all_tasks')) ? '' : ' style="display:none"'; ?>>
+ <label for="due_date"><?= eL('duedate') ?></label>
+ <?php echo tpl_datepicker('due_date', '', Req::val('due_date', $task_details['due_date'])); ?>
+ </li>
+ <!-- Private -->
+ <?php if ($user->can_change_private($task_details)): ?>
+ <li<?php echo in_array('private', $fields) ? '' : ' style="display:none"'; ?>>
+ <label for="private"><?= eL('private') ?></label>
+ <?php echo tpl_checkbox('mark_private', Req::val('mark_private', $task_details['mark_private']), 'private'); ?>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($proj->prefs['use_effort_tracking'] && $user->perms('view_estimated_effort')): ?>
+ <li>
+ <label for="estimated_effort"><?= eL('estimatedeffort') ?></label>
+ <input id="estimated_effort" name="estimated_effort" class="text" type="text" size="5" maxlength="10" value="<?php echo Filters::noXSS(effort::secondsToEditString($task_details['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format'])); ?>" />
+ <?= eL('hours') ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- If no currently selected project is not there, push it on there so don't have to change things -->
+ <?php
+ $id = Req::val('project_id', $proj->id);
+ $selected = false;
+ foreach ($fs->projects as $value => $label) {
+ if ($label[0] == $id) {
+ $selected = true;
+ break;
+ }
+ }
+
+ if (! $selected) {
+ $title = '---';
+ $foo = array( $id, $title, 'project_id' => $id, 'project_title' => $title);
+ array_unshift( $fs->projects, $foo);
+ }
+
+ ?>
+
+ <!-- If there is only one choice of projects, then don't bother showing it -->
+ <li<?php
+ # show the targetproject selector if invalid when moving tasks
+ echo isset($_SESSION['ERRORS']['invalidtargetproject']) ? ' class="errorinput"' : ''; ?>>
+ <?php echo isset($_SESSION['ERRORS']['invalidtargetproject']) ? '<span class="errorinput" style="display:block;">'.eL('invalidtargetproject').'</span>' : ''; ?>
+ <label for="project_id"><?= eL('attachedtoproject') ?></label>
+ <select name="project_id" id="project_id">
+ <?php echo tpl_options($fs->projects, Req::val('project_id', $proj->id)); ?>
+ </select>
+ </li>
+ </ul>
+ <div id="fineprint">
+ <?= eL('openedby') ?> <?php echo tpl_userlink($task_details['opened_by']); ?>
+ - <span title="<?php echo formatDate($task_details['date_opened'], true); ?>"><?php echo formatDate($task_details['date_opened'], false); ?></span>
+ <?php if ($task_details['last_edited_by']): ?>
+ <br />
+ <?= eL('editedby') ?> <?php echo tpl_userlink($task_details['last_edited_by']); ?>
+ - <span title="<?php echo Filters::noXSS(formatDate($task_details['last_edited_time'], true)); ?>"><?php echo Filters::noXSS(formatDate($task_details['last_edited_time'], false)); ?></span>
+ <?php endif; ?>
+ </div>
+</div>
+<div id="taskdetailsfull">
+ <label for="itemsummary"<?php echo isset($_SESSION['ERRORS']['summaryrequired']) ? ' class="summary errorinput" title="'.eL('summaryrequired').'"':' class="summary"'; ?>>FS#<?php echo Filters::noXSS($task_details['task_id']); ?> <?php echo Filters::noXSS(L('summary')); ?>:
+ <input placeholder="<?= eL('summary') ?>" type="text" name="item_summary" id="itemsummary" maxlength="100" value="<?php echo Filters::noXSS(Req::val('item_summary', $task_details['item_summary'])); ?>" />
+ </label>
+ <?php
+ foreach($tags as $tag): $tagnames[]= Filters::noXSS($tag['tag']); endforeach;
+ isset($tagnames) ? $tagstring=implode(';',$tagnames) : $tagstring='';
+ ?>
+ <label style="display:block;" for="tags" title="<?= eL('tagsinfo') ?>"><?= eL('tags') ?>:
+ <input title="<?= eL('tagsinfo') ?>" placeholder="<?= eL('tags') ?>" type="text" name="tags" id="tags" maxlength="100" value="<?php echo Filters::noXSS(Req::val('tags', $tagstring)); ?>" />
+ </label>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <button tabindex="9" type="button" onclick="showPreview('details', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('detailed_desc', 15, 70, array('id' => 'details'), Req::val('detailed_desc', $task_details['detailed_desc'])); ?>
+ <br />
+ <button type="reset"><?php echo Filters::noXSS(L('reset')); ?></button>
+ <br />
+
+ <div id="addlinkbox">
+ <?php
+ $links = $proj->listTaskLinks($task_details['task_id']);
+ $this->display('common.editlinks.tpl', 'links', $links); ?>
+ <?php if ($user->perms('create_attachments')): ?>
+ <input id="link1" tabindex="8" class="text" type="text" maxlength="100" name="userlink[]" />
+ <script>
+ // hide the fallback input field if javascript is enabled
+ document.getElementById("link1").style.display='none';
+ </script>
+ <button id="addlinkbox_addalink" tabindex="10" type="button" onclick="addLinkField('addlinkbox')"><?php echo Filters::noXSS(L('addalink')); ?></button>
+ <button id="addlinkbox_addanotherlink" tabindex="10" style="display: none" type="button" onclick="addLinkField('addlinkbox')"><?php echo Filters::noXSS(L('addalink')); ?></button>
+ <br />
+ <span style="display: none"><?php /* this span is shown/copied by javascript when adding links */ ?>
+ <input tabindex="8" class="text" type="text" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" class="button fa fa-remove fa-lg" title="<?= eL('remove') ?>" onclick="removeLinkField(this, 'addlinkbox');"></a><br />
+ </span>
+ <?php endif; ?>
+ </div>
+ <div id="uploadfilebox">
+ <?php
+ $attachments = $proj->listTaskAttachments($task_details['task_id']);
+ $this->display('common.editattachments.tpl', 'attachments', $attachments);
+ if ($user->perms('create_attachments')): ?>
+ <input id="file1" tabindex="5" class="file" type="file" size="55" name="usertaskfile[]" />
+ <script>
+ // hide the fallback input field if javascript is enabled
+ document.getElementById("file1").style.display='none';
+ </script>
+ <button id="uploadfilebox_attachafile" tabindex="7" type="button" onclick="addUploadFields()">
+ <?= eL('uploadafile') ?> (<?= eL('max') ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?= eL('MiB') ?>)
+ </button>
+ <button id="uploadfilebox_attachanotherfile" tabindex="7" style="display: none" type="button" onclick="addUploadFields()">
+ <?= eL('attachanotherfile') ?> (<?= eL('max') ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?= eL('MiB') ?>)
+ </button>
+ <br />
+ <span style="display: none"><?php /* this span is shown/copied by javascript when adding files */ ?>
+ <input tabindex="5" class="file" type="file" size="55" name="usertaskfile[]" />
+ <a href="javascript://" tabindex="6" class="button fa fa-remove fa-lg" title="<?= eL('remove') ?>" onclick="removeUploadField(this);"></a><br />
+ </span>
+ <?php endif; ?>
+ </div>
+ <div class="buttons">
+ <?php if ($user->perms('add_comments') && (!$task_details['is_closed'] || $proj->prefs['comment_closed'])): ?>
+ <input type="checkbox" id="s_addcomment" />
+ <label for="s_addcomment" title="<?= eL('addcomment') ?>">
+ <span class="fa-stack">
+ <i class="fa fa-comment-o fa-stack-2x"></i>
+ <i class="fa fa-plus fa-stack-1x"></i>
+ </span>
+ </label>
+ <div id="edit_add_comment">
+ <label for="comment_text"><?php echo Filters::noXSS(L('comment')); ?></label>
+ <textarea accesskey="r" tabindex="8" id="comment_text" name="comment_text" cols="50" rows="2"></textarea>
+ </div>
+ <br />
+ <?php endif; ?>
+ <button type="submit" class="positive" accesskey="s" onclick="return checkok('<?php echo Filters::noJsXSS($baseurl); ?>js/callbacks/checksave.php?time=<?php echo Filters::noXSS(time()); ?>&amp;task_id=<?php echo Filters::noXSS($task_details['task_id']); ?>', '<?php echo Filters::noJsXSS(L('alreadyedited')); ?>', 'taskeditform')"><?php echo Filters::noXSS(L('savedetails')); ?></button>
+ <a class="button" href="<?php echo Filters::noXSS(createUrl('details', $task_details['task_id'])); ?>"><?= eL('canceledit') ?></a>
+ </div>
+</div>
+<div class="clear"></div>
+</div>
+</form>
diff --git a/themes/CleanFS/templates/details.tabs.comment.tpl b/themes/CleanFS/templates/details.tabs.comment.tpl
new file mode 100644
index 0000000..a2ded4f
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.comment.tpl
@@ -0,0 +1,118 @@
+<?php /* preset 'active' class, so browsers with disabled javascript see the comments tab content. */ ?>
+<div id="comments" class="tab active">
+ <?php foreach($comments as $comment): ?>
+ <div class="comment_container" id="<?php echo 'comment' . $comment['comment_id']; ?>">
+ <div class="comment_avatar"><?php echo tpl_userlinkavatar($comment['user_id'], $fs->prefs['max_avatar_size'], 'av_comment'); ?></div>
+ <div class="comment">
+ <div class="comment_header">
+ <div class="comment_header_actions">
+ <?php echo tpl_form(CreateUrl('details', $task_details['task_id'])); ?>
+ <?php
+ $theuser = new User($comment['user_id']);
+ if (!$theuser->isAnon()) {
+ if ($theuser->perms('is_admin')) {
+ $rank = 'Admin';
+ }
+ else if ($theuser->perms('manage_project')) {
+ $rank = 'Project Manager';
+ }
+ else {
+ $rank = '';
+ }
+
+ if (!empty($rank)) {
+ echo '<span class="comment_header_usertype">'.Filters::noXSS($rank).'</span>';
+ }
+ }
+ ?>
+ <?php if ($user->perms('edit_comments') || ($user->perms('edit_own_comments') && $comment['user_id'] == $user->id)): ?>
+ <a href="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?do=editcomment&amp;task_id=<?php echo Filters::noXSS($task_details['task_id']); ?>&amp;id=<?php echo Filters::noXSS($comment['comment_id']); ?>" title="<?php echo Filters::noXSS(L('edit')); ?>"><span class="fa fa-pencil fa-lg" style="margin-right: 10px;"></span></a>
+ <?php endif; ?>
+ <?php if ($user->perms('delete_comments')): ?>
+ <input type="hidden" name="action" value="details.deletecomment"/>
+ <input type="hidden" name="comment_id" value="<?php echo $comment['comment_id']; ?>"/>
+ <?php $confirm = (isset($comment_attachments[$comment['comment_id']])) ? sprintf(L('confirmdeletecomment'), L('attachementswilldeleted')) : sprintf(L('confirmdeletecomment'), '') ?>
+ <button type="submit" class="fakelinkbutton" onclick="return confirm('<?php echo Filters::noJsXSS($confirm); ?>');" title="<?php echo Filters::noXSS(L('delete')); ?>"><span class="fa fa-remove fa-lg"></span></button>
+ <?php endif; ?>
+ </form>
+ </div>
+ <div class="comment_header_infos"><?php echo tpl_userlink($comment['user_id']); ?> <?php echo Filters::noXSS(L('commentedon')); ?> <?php echo Filters::noXSS(formatDate($comment['date_added'], true)); ?></div>
+ </div>
+ <div class="commenttext">
+ <?php echo TextFormatter::render($comment['comment_text'], 'comm', $comment['comment_id'], $comment['content']); ?>
+ <?php if (isset($comment_links[$comment['comment_id']])) {
+ $this->display('common.links.tpl', 'links', $comment_links[$comment['comment_id']]);
+ }?>
+ <?php if (isset($comment_attachments[$comment['comment_id']])) {
+ $this->display('common.attachments.tpl', 'attachments', $comment_attachments[$comment['comment_id']]);
+ }?>
+ </div>
+ </div>
+ </div>
+ <?php endforeach; ?>
+ <?php if ($user->perms('add_comments') && (!$task_details['is_closed'] || $proj->prefs['comment_closed'])): ?>
+ <div class="comment_container">
+ <div class="comment_avatar"><?php echo tpl_userlinkavatar($user->id, $fs->prefs['max_avatar_size'], 'av_comment'); ?></div>
+ <div class="comment">
+ <div class="comment_header">
+ <?php echo Filters::noXSS(L('addcomment')); ?>
+ </div>
+ <div class="commenttext">
+ <?php echo tpl_form(CreateUrl('details', $task_details['task_id']), 'comment', 'post', 'multipart/form-data'); ?>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <?php endif; ?>
+ <input type="hidden" name="action" value="details.addcomment" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS(Req::val('task_id', $task_details['task_id'])); ?>" />
+ <?php if (!$watched): ?>
+ <?php echo tpl_checkbox('notifyme', Req::val('notifyme', !(Req::val('action') == 'details.addcomment')), 'notifyme'); ?> <label class="left" for="notifyme"><?php echo Filters::noXSS(L('notifyme')); ?></label>
+ <?php endif; ?>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <button tabindex="9" type="button" onclick="showPreview('comment_text', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('comment_text', 10, 72, array('accesskey' => 'r', 'tabindex' => 8, 'id' => 'comment_text')); ?>
+ <div id="addlinkbox">
+ <button id="addlinkbox_addalink" tabindex="10" type="button" onclick="addLinkField('addlinkbox')">
+ <?php echo Filters::noXSS(L('addalink')); ?>
+ </button>
+ <button id="addlinkbox_addanotherlink" tabindex="10" style="display: none" type="button" onclick="addLinkField('addlinkbox')">
+ <?php echo Filters::noXSS(L('addanotherlink')); ?>
+ </button>
+ <span style="display: none">
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" onclick="removeLinkField(this, 'addlinkbox');"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ <noscript>
+ <span>
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" onclick="removeLinkField(this, 'addlinkbox');"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ </noscript>
+ </div>
+ <?php if ($user->perms('create_attachments')): ?>
+ <div id="uploadfilebox">
+ <button id="uploadfilebox_attachafile" tabindex="7" type="button" onclick="addUploadFields()">
+ <?php echo Filters::noXSS(L('uploadafile')); ?> (<?php echo Filters::noXSS(L('max')); ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?php echo Filters::noXSS(L('MiB')); ?>)
+ </button>
+ <button id="uploadfilebox_attachanotherfile" tabindex="7" style="display: none" type="button" onclick="addUploadFields()">
+ <?php echo Filters::noXSS(L('attachanotherfile')); ?> (<?php echo Filters::noXSS(L('max')); ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?php echo Filters::noXSS(L('MiB')); ?>)
+ </button>
+ <span style="display: none;"><!-- this span is shown/copied in javascript when adding files -->
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ <a href="javascript://" tabindex="6" onclick="removeUploadField(this);"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ <noscript>
+ <span>
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ <a href="javascript://" tabindex="6" onclick="removeUploadField(this);"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ </noscript>
+ </div>
+ <?php endif; ?>
+ <button class="button positive" accesskey="s" tabindex="9" type="submit"><?php echo Filters::noXSS(L('addcomment')); ?></button>
+ </form>
+ </div>
+ </div>
+ </div>
+ <?php endif; ?>
+</div>
diff --git a/themes/CleanFS/templates/details.tabs.efforttracking.tpl b/themes/CleanFS/templates/details.tabs.efforttracking.tpl
new file mode 100644
index 0000000..7cd6a9b
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.efforttracking.tpl
@@ -0,0 +1,52 @@
+<div id="effort" class="tab">
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('details', $task_details['task_id'])).'#effort'); ?>
+ <?php if ($user->perms('track_effort')) { ?>
+ <input type="hidden" name="action" value="details.efforttracking"/>
+ <button type="submit" name="start_tracking" value="true"><?php echo Filters::noXSS(L('starteffort')); ?></button>
+ <br />
+ <label for="effort_to_add"><?php echo Filters::noXSS(L('manualeffort')); ?></label>
+ <input id="effort_to_add" name="effort_to_add" class="text" type="text" size="5" maxlength="100" value='00:00'/>
+ <button type="submit" name="manual_effort" value="true"><?php echo Filters::noXSS(L('addeffort')); ?></button>
+ <?php } ?>
+ <table class="userlist history">
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('date')); ?></th>
+ <th><?php echo Filters::noXSS(L('user')); ?></th>
+ <th><?php echo Filters::noXSS(L('effort')); ?> (H:M)</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach($effort->details as $details){
+ ?>
+ <tr>
+ <td><?php echo Filters::noXSS(formatDate($details['date_added'], true)); ?></td>
+ <td><?php echo tpl_userlink($details['user_id']); ?></td>
+ <td><?php
+ if($details['effort'] == 0 && $details['end_timestamp']==false)
+ { ?>
+ <?php echo Filters::noXSS(L('trackinginprogress')); ?> (<?php
+
+ echo effort::SecondsToString(time()-$details['start_timestamp'], $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
+
+ ?>)
+ <?php }
+ else
+ {
+ echo effort::SecondsToString($details['effort'], $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
+ } ?>
+ </td>
+ <td>
+ <?php if($user->id == $details['user_id'] & is_null($details['end_timestamp'])){ ?>
+ <button type="submit" name="stop_tracking" value="true"><?php echo Filters::noXSS(L('endeffort')); ?></button>
+ <button type="submit" name="cancel_tracking" value="true"><?php echo Filters::noXSS(L('cleareffort')); ?></button>
+ <?php } ?>
+ </td>
+ </tr>
+ <?php } ?>
+ </tbody>
+ </table>
+ </form>
+</div>
diff --git a/themes/CleanFS/templates/details.tabs.history.callback.tpl b/themes/CleanFS/templates/details.tabs.history.callback.tpl
new file mode 100644
index 0000000..07eefd5
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.history.callback.tpl
@@ -0,0 +1,32 @@
+<?php if ($details && count($histories)): ?>
+<table class="userlist history">
+ <tr>
+ <th><?php echo Filters::noXSS(L('previousvalue')); ?></th>
+ <th><?php echo Filters::noXSS(L('newvalue')); ?></th>
+ </tr>
+ <tr>
+ <td><?php echo $details_previous; ?></td>
+ <td><?php echo $details_new; ?></td>
+ </tr>
+</table>
+<?php else: ?>
+<table class="userlist history">
+ <tr>
+ <th><?php echo Filters::noXSS(L('eventdate')); ?></th>
+ <th><?php echo Filters::noXSS(L('user')); ?></th>
+ <th><?php echo Filters::noXSS(L('event')); ?></th>
+ </tr>
+
+ <?php foreach($histories as $history): ?>
+ <tr>
+ <td><?php echo Filters::noXSS(formatDate($history['event_date'], false)); ?></td>
+ <?php if($fs->prefs['enable_avatars'] == 1) { ?>
+ <td><?php echo tpl_userlinkavatar($history['user_id'], $fs->prefs['max_avatar_size'] / 2, 'left', '0px 5px 0px 0px'); ?> <?php echo tpl_userlink($history['user_id']); ?></td>
+ <?php } else { ?>
+ <td><?php echo tpl_userlink($history['user_id']); ?></td>
+ <?php } ?>
+ <td><?php echo event_description($history); ?></td>
+ </tr>
+ <?php endforeach; ?>
+</table>
+<?php endif; ?>
diff --git a/themes/CleanFS/templates/details.tabs.history.tpl b/themes/CleanFS/templates/details.tabs.history.tpl
new file mode 100644
index 0000000..69d7d79
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.history.tpl
@@ -0,0 +1,3 @@
+<div id="history" class="tab">
+ <h3><?php echo Filters::noXSS(L('loading')); ?></h3>
+</div>
diff --git a/themes/CleanFS/templates/details.tabs.notifs.tpl b/themes/CleanFS/templates/details.tabs.notifs.tpl
new file mode 100644
index 0000000..cebad39
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.notifs.tpl
@@ -0,0 +1,30 @@
+<div id="notify" class="tab">
+ <p><em><?php echo Filters::noXSS(L('theseusersnotify')); ?></em></p>
+ <?php foreach ($notifications as $row): ?>
+ <div>
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('details', $task_details['task_id'])).'#notify',null,null,null,'style="display:inline"'); ?>
+ <input type="hidden" name="action" value="remove_notification" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($row['user_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('remove')); ?></button>
+ <?php echo tpl_userlink($row['user_id']); ?>
+ </form>
+ <!--
+ <a href="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?do=details&amp;action=remove_notification&amp;task_id=<?php echo Filters::noXSS($task_details['task_id']); ?>&amp;ids=<?php echo Filters::noXSS($task_details['task_id']); ?>&amp;user_id=<?php echo Filters::noXSS($row['user_id']); ?>#notify"><?php echo Filters::noXSS(L('remove')); ?></a>
+ -->
+ </div>
+ <?php endforeach; ?>
+
+ <?php if ($user->perms('manage_project')): ?>
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('details', $task_details['task_id'])).'#notify'); ?>
+ <div>
+ <label class="default multisel" for="notif_user_id"><?php echo Filters::noXSS(L('addusertolist')); ?>: </label>
+ <?php echo tpl_userselect('user_name', Req::val('user_name'), 'notif_user_id'); ?>
+ <button type="submit"><?php echo Filters::noXSS(L('add')); ?></button>
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS(Req::num('ids', $task_details['task_id'])); ?>" />
+ <input type="hidden" name="action" value="details.add_notification" />
+ </div>
+ </form>
+ <?php endif; ?>
+</div>
+
diff --git a/themes/CleanFS/templates/details.tabs.related.tpl b/themes/CleanFS/templates/details.tabs.related.tpl
new file mode 100644
index 0000000..543f7d2
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.related.tpl
@@ -0,0 +1,65 @@
+<div id="related" class="tab">
+
+ <div class="related">
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])).'#related');?>
+ <table id="tasks_related" class="userlist">
+ <thead>
+ <tr>
+ <th>
+ <a class="toggle_selected" href="javascript:ToggleSelected('tasks_related')">
+ <!--<img title="<?php echo Filters::noXSS(L('toggleselected')); ?>" alt="<?php echo Filters::noXSS(L('toggleselected')); ?>" src="<?php echo Filters::noXSS($this->get_image('kaboodleloop')); ?>" width="16" height="16" />-->
+ </a>
+ </th>
+ <th><?php echo Filters::noXSS(L('tasksrelated')); ?> (<?php echo Filters::noXSS(count($related)); ?>)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach ($related as $row):
+ ?>
+ <tr>
+ <td class="ttcolumn">
+ <input type="checkbox" name="related_id[]" <?php echo tpl_disableif(!$user->can_edit_task($task_details)); ?> value="<?php echo Filters::noXSS($row['related_id']); ?>" /></td>
+ <td><?php echo tpl_tasklink($row); ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="2">
+ <input type="hidden" name="action" value="remove_related" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('remove')); ?></button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </form>
+ </div>
+
+ <div class="related">
+ <table id="duplicate_tasks" class="userlist">
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('duplicatetasks')); ?> (<?php echo Filters::noXSS(count($duplicates)); ?>)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($duplicates as $row): ?>
+ <tr><td><?php echo tpl_tasklink($row); ?></td></tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+
+ <?php if ($user->can_edit_task($task_details) && !$task_details['is_closed']): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])).'#related',null,null,null,'class="clear" id="formaddrelatedtask"'); ?>
+ <div>
+ <input type="hidden" name="action" value="details.add_related" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <label><?php echo Filters::noXSS(L('addnewrelated')); ?> FS#<input name="related_task" id="related_task_input" type="text" class="text" size="10" maxlength="10" /></label>
+ <button type="submit" onclick="return checkok('<?php echo Filters::noJsXSS($baseurl); ?>js/callbacks/checkrelated.php?related_task=' + $('related_task_input').value + '&amp;project=<?php echo Filters::noXSS($proj->id); ?>', '<?php echo Filters::noJsXSS(L('relatedproject')); ?>', 'formaddrelatedtask')"><?php echo Filters::noXSS(L('add')); ?></button>
+ </div>
+ </form>
+ <?php endif; ?>
+</div>
diff --git a/themes/CleanFS/templates/details.tabs.remind.tpl b/themes/CleanFS/templates/details.tabs.remind.tpl
new file mode 100644
index 0000000..3332304
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.remind.tpl
@@ -0,0 +1,76 @@
+<?php if (!$task_details['is_closed']): ?>
+ <div id="remind" class="tab">
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('details', $task_details['task_id'])).'#remind'); ?>
+ <?php if (count($reminders)): ?>
+ <table id="reminders" class="userlist">
+ <thead>
+ <tr>
+ <th>
+ <a class="toggle_selected" title="<?php echo Filters::noXSS(L('toggleselected')); ?>"
+ href="javascript:ToggleSelected('reminders')"></a>
+ </th>
+ <th><?php echo Filters::noXSS(L('user')); ?></th>
+ <th><?php echo Filters::noXSS(L('startat')); ?></th>
+ <th><?php echo Filters::noXSS(L('frequency')); ?></th>
+ <th><?php echo Filters::noXSS(L('message')); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($reminders as $row): ?>
+ <tr>
+ <td class="ttcolumn">
+ <input type="checkbox" name="reminder_id[]" <?php echo tpl_disableif(!$user->can_edit_task($task_details)); ?> value="<?php echo Filters::noXSS($row['reminder_id']); ?>" />
+ </td>
+ <td><?php echo tpl_userlink($row['user_id']); ?></td>
+ <td><?php echo Filters::noXSS(formatDate($row['start_time'])); ?></td>
+ <?php
+ // Work out the unit of time to display
+ if ($row['how_often'] < 86400) {
+ $how_often = $row['how_often'] / 3600 . ' ' . L('hours');
+ } elseif ($row['how_often'] < 604800) {
+ $how_often = $row['how_often'] / 86400 . ' ' . L('days');
+ } else {
+ $how_often = $row['how_often'] / 604800 . ' ' . L('weeks');
+ }
+ ?>
+ <td><?php echo Filters::noXSS($how_often); ?></td>
+ <td><?php echo TextFormatter::render($row['reminder_message']); ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ <tfoot>
+ <tr><td colspan="5">
+ <input type="hidden" name="action" value="deletereminder" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('remove')); ?></button></td>
+ </tr>
+ </tfoot>
+ </table>
+ <?php endif; ?>
+ </form>
+
+ <fieldset><legend><?php echo Filters::noXSS(L('addreminder')); ?></legend>
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('details', $task_details['task_id'])).'#remind',null,null,null,'id="formaddreminder"'); ?>
+ <div>
+ <input type="hidden" name="action" value="details.addreminder" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <label class="default multisel" for="to_user_id"><?php echo Filters::noXSS(L('remindthisuser')); ?></label>
+ <?php echo tpl_userselect('to_user_id', Req::val('to_user_id'), 'to_user_id'); ?>
+ <br />
+ <label for="timeamount1"><?php echo Filters::noXSS(L('thisoften')); ?></label>
+ <input class="text" type="text" value="<?php echo Filters::noXSS(Req::val('timeamount1')); ?>" id="timeamount1" name="timeamount1" size="3" maxlength="3" />
+ <select class="adminlist" name="timetype1">
+ <?php echo tpl_options(array(3600 => L('hours'), 86400 => L('days'), 604800 => L('weeks')), Req::val('timetype1')); ?>
+ </select>
+ <br />
+ <?php echo tpl_datepicker('timeamount2', L('startat'), Req::val('timeamount2', formatDate(time()))); ?>
+ <br />
+ <textarea class="text" name="reminder_message"
+ rows="10" cols="72"><?php echo Filters::noXSS(Req::val('reminder_message', L('defaultreminder') . "\n\n" . CreateURL('details', $task_details['task_id']))); ?></textarea>
+ <br />
+ <button type="submit"><?php echo Filters::noXSS(L('addreminder')); ?></button>
+ </div>
+ </form>
+ </fieldset>
+</div>
+<?php endif; ?>
diff --git a/themes/CleanFS/templates/details.tabs.tpl b/themes/CleanFS/templates/details.tabs.tpl
new file mode 100644
index 0000000..b1aad8b
--- /dev/null
+++ b/themes/CleanFS/templates/details.tabs.tpl
@@ -0,0 +1,39 @@
+<ul id="submenu">
+ <?php if ($user->perms('view_comments') || $proj->prefs['others_view'] || ($user->isAnon() && $task_details['task_token'] && Get::val('task_token') == $task_details['task_token'])): ?>
+ <li id="commentstab">
+ <a href="#comments"><?php echo Filters::noXSS(L('comments')); ?> (<?php echo count($comments); ?>)</a>
+ </li>
+ <?php endif; ?>
+
+ <li id="relatedtab">
+ <a href="#related"><?php echo Filters::noXSS(L('relatedtasks')); ?> (<?php echo count($related); ?>/<?php echo count($duplicates); ?>)</a>
+ </li>
+
+ <?php if ($user->perms('manage_project')): ?>
+ <li id="notifytab">
+ <a href="#notify"><?php echo Filters::noXSS(L('notifications')); ?> (<?php echo count($notifications); ?>)</a>
+ </li>
+ <?php if (!$task_details['is_closed']): ?>
+ <li id="remindtab">
+ <a href="#remind"><?php echo Filters::noXSS(L('reminders')); ?> (<?php echo count($reminders); ?>)</a>
+ </li>
+ <?php endif; ?>
+ <?php endif; ?>
+
+ <?php if ($user->perms('view_history')): ?>
+ <li id="historytab">
+ <a id="historytaba" onmousedown="getHistory('<?php echo Filters::noXSS($task_details['task_id']); ?>', '<?php echo Filters::noJsXSS($baseurl); ?>', 'history', '<?php echo Filters::noXSS(Get::num('details')); ?>');"
+ href="<?php echo Filters::noXSS(CreateURL('details', $task_details['task_id'], null)); ?>#history"><?php echo Filters::noXSS(L('history')); ?></a>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($proj->prefs['use_effort_tracking']){ ?>
+ <?php if ($user->perms('view_current_effort_done')){ ?>
+ <li id="efforttab">
+ <a href="#effort"><?php echo Filters::noXSS(L('efforttracking')); ?></a>
+ </li>
+
+ <?php
+ }
+ } ?>
+</ul>
diff --git a/themes/CleanFS/templates/details.view.tpl b/themes/CleanFS/templates/details.view.tpl
new file mode 100644
index 0000000..abbcfc1
--- /dev/null
+++ b/themes/CleanFS/templates/details.view.tpl
@@ -0,0 +1,882 @@
+<div id="actionbar">
+<?php if ($task_details['is_closed']): //if task is closed ?>
+ <?php if ($user->can_close_task($task_details)):
+ echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="reopen" />
+ <button><?php echo L('reopenthistask'); ?></button>
+ </form>
+ <?php elseif (!$user->isAnon() && !Flyspray::adminRequestCheck(2, $task_details['task_id'])): ?>
+
+<button class="submit main" onclick="showhidestuff('requestreopen');"><?php echo Filters::noXSS(L('reopenrequest')); ?></button>
+<div id="requestreopen" class="popup hide">
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),'form3',null,null,'id="formclosetask"'); ?>
+ <input type="hidden" name="action" value="requestreopen" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <label for="reason"><?php echo Filters::noXSS(L('reasonforreq')); ?></label>
+ <textarea id="reason" name="reason_given"></textarea><br/>
+ <button type="submit"><?php echo Filters::noXSS(L('submitreq')); ?></button>
+ </form>
+ </div>
+ <?php endif; ?>
+<?php else: //if task is open ?>
+ <?php if ($user->can_close_task($task_details) && !$d_open): ?>
+ <a href="<?php echo Filters::noXSS(createURL('details', $task_details['task_id'], null, array('showclose' => !Req::val('showclose')))); ?>"
+ id="closetask" class="button main" accesskey="y"
+ onclick="showhidestuff('closeform');return false;"> <?php echo Filters::noXSS(L('closetask')); ?></a>
+
+ <div id="closeform" class="<?php if (Req::val('action') != 'details.close' && !Req::val('showclose')): ?>hide <?php endif; ?>popup">
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'id="formclosetask"'); ?>
+ <input type="hidden" name="action" value="details.close"/>
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>"/>
+ <select class="adminlist" name="resolution_reason" onmouseup="event.stopPropagation();">
+ <option value="0"><?php echo Filters::noXSS(L('selectareason')); ?></option>
+ <?php echo tpl_options($proj->listResolutions(), Req::val('resolution_reason')); ?>
+ </select>
+ <button type="submit"><?php echo Filters::noXSS(L('closetask')); ?></button>
+ <br/>
+ <label class="default text" for="closure_comment"><?php echo Filters::noXSS(L('closurecomment')); ?></label>
+ <textarea class="text" id="closure_comment" name="closure_comment" rows="3" cols="25"><?php echo Filters::noXSS(Req::val('closure_comment')); ?></textarea>
+ <?php if($task_details['percent_complete'] != '100'): ?>
+ <label><?php echo tpl_checkbox('mark100', Req::val('mark100', !(Req::val('action') == 'details.close'))); ?>&nbsp;&nbsp;<?php echo Filters::noXSS(L('mark100')); ?></label>
+ <?php endif; ?>
+ </form>
+ </div>
+
+ <?php elseif (!$d_open && !$user->isAnon() && !Flyspray::AdminRequestCheck(1, $task_details['task_id'])): ?>
+ <a href="#close" id="reqclose" class="button main" onclick="showhidestuff('closeform');"><?php echo Filters::noXSS(L('requestclose')); ?></a>
+ <div id="closeform" class="popup hide">
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),'form3',null,null,'id="formclosetask"'); ?>
+ <input type="hidden" name="action" value="requestclose"/>
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>"/>
+ <label for="reason"><?php echo Filters::noXSS(L('reasonforreq')); ?></label>
+ <textarea id="reason" name="reason_given"></textarea><br/>
+ <button type="submit"><?php echo Filters::noXSS(L('submitreq')); ?></button>
+ </form>
+ </div>
+
+ <?php elseif(!$user->isAnon()): ?>
+ <a href="#closedisabled" id="reqclose" class="tooltip button disabled main"><?php echo Filters::noXSS(L('closetask')); ?>
+
+ <span class="custom info">
+ <em><?php echo Filters::noXSS(L('information')); ?></em>
+ <br>
+ <?php echo Filters::noXSS(L('taskclosedisabled')); ?>
+ <br>
+ <?php
+ foreach ($deps as $dependency){
+ echo "FS#".$dependency['task_id']." : ".$dependency['item_summary']."</br>";
+ }
+ ?>
+ </span>
+ </a>
+ <?php endif; ?>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <a id="edittask" class="button" accesskey="e"
+ href="<?php echo Filters::noXSS(createURL('edittask', $task_details['task_id'])); ?>"> <?php echo Filters::noXSS(L('edittask')); ?></a>
+ <?php endif; ?>
+
+ <?php if ($user->can_take_ownership($task_details)): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'style="display:inline"'); ?>
+ <input type="hidden" name="action" value="takeownership" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit" id="own"><?php echo Filters::noXSS(L('assigntome')); ?></button>
+ </form>
+ <?php endif; ?>
+
+ <?php if ($user->can_add_to_assignees($task_details) && !empty($task_details['assigned_to'])): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'style="display:inline"'); ?>
+ <input type="hidden" name="action" value="addtoassignees" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit" id="own_add"><?php echo Filters::noXSS(L('addmetoassignees')); ?></button>
+ </form>
+ <?php endif; ?>
+ <input type="checkbox" id="s_quickactions" />
+ <label class="button main" id="actions" for="s_quickactions"><?php echo Filters::noXSS(L('quickaction')); ?></label>
+ <div id="actionsform">
+ <ul>
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <li>
+ <a accesskey="e" href="<?php echo Filters::noXSS(createURL('edittask', $task_details['task_id'])); ?>"> <?php echo Filters::noXSS(L('edittask')); ?></a>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($user->can_set_task_parent($task_details)): ?>
+ <li><input type="checkbox" id="s_parent" /><label for="s_parent"><?php echo Filters::noXSS(L('setparent')); ?></label>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'id="setparentform"'); ?>
+ <?php echo Filters::noXSS(L('parenttaskid')); ?>
+ <input type="hidden" name="action" value="details.setparent" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input class="text" type="text" value="" id="supertask_id" name="supertask_id" size="5" maxlength="10" />
+ <button type="submit" name="submit"><?php echo Filters::noXSS(L('set')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+ <?php if ($user->can_associate_task($task_details)): ?>
+ <li><input type="checkbox" id="s_associate"/><label for="s_associate"><?php echo Filters::noXSS(L('associatesubtask')); ?></label>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'id="associateform"'); ?>
+ <?php echo Filters::noXSS(L('associatetaskid')); ?>
+ <input type="hidden" name="action" value="details.associatesubtask"/>
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>"/>
+ <input class="text" type="text" value="" id="associate_subtask_id" name="associate_subtask_id" size="5" maxlength="10"/>
+ <button type="submit" name="submit"><?php echo Filters::noXSS(L('set')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+ <li>
+ <a href="<?php echo Filters::noXSS(createURL('depends', $task_details['task_id'])); ?>"><?php echo Filters::noXSS(L('depgraph')); ?></a>
+ </li>
+ <?php if ($user->can_add_task_dependency($task_details)): ?>
+ <li><input type="checkbox" id="s_adddependent"/><label for="s_adddependent"><?php echo Filters::noXSS(L('adddependenttask')); ?></label>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'id="adddepform"'); ?>
+ <input type="hidden" name="action" value="details.newdep" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <label for="dep_task_id"><?php echo Filters::noXSS(L('newdependency')); ?></label>
+ FS# <input class="text" type="text" value="<?php echo Filters::noXSS(Req::val('dep_task_id')); ?>" id="dep_task_id" name="dep_task_id" size="5" maxlength="10" />
+ <button type="submit" name="submit"><?php echo Filters::noXSS(L('add')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($proj->id && $user->perms('open_new_tasks')): ?>
+ <li>
+ <a href="<?php echo Filters::noXSS(createURL('newtask', $proj->id, $task_details['task_id'])); ?>"><?php echo Filters::noXSS(L('addnewsubtask')); ?></a>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($user->can_take_ownership($task_details)): ?>
+ <li><?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="takeownership" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('assigntome')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($user->can_add_to_assignees($task_details) && !empty($task_details['assigned_to'])): ?>
+ <li>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="addtoassignees" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('addmetoassignees')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($user->can_vote($task_details) > 0): ?>
+ <li>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="details.addvote" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('voteforthistask')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+
+ <?php if (!$user->isAnon() && !$watched): ?>
+ <li>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="details.add_notification" />
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($user->id); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('watchthistask')); ?></button>
+ </form>
+ </li>
+ <?php endif; ?>
+
+ <?php if ($user->can_change_private($task_details)): ?>
+ <li>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <?php if ($task_details['mark_private']): ?>
+ <input type="hidden" name="action" value="makepublic"/>
+ <button><?php echo eL('makepublic'); ?></button>
+ <?php elseif (!$task_details['mark_private']): ?>
+ <input type="hidden" name="action" value="makeprivate"/>
+ <button><?php echo eL('privatethistask'); ?></button>
+ <?php endif; ?>
+ </form>
+ </li>
+ <?php endif; ?>
+ </ul>
+ </div>
+<?php endif; ?>
+</div>
+
+<script type="text/javascript">
+function show_hide(elem, flag)
+{
+ elem.style.display = "none";
+ if(flag)
+ elem.nextElementSibling.style.display = "block";
+ else
+ elem.previousElementSibling.style.display = "block";
+}
+function quick_edit(elem, id)
+{
+ var e = document.getElementById(id);
+ var name = e.name;
+ var value = e.value;
+ var text;
+ if(e.selectedIndex != null)
+ text = e.options[e.selectedIndex].text;
+ else
+ text = document.getElementById(id).value; // for due date and estimated effort
+ var xmlHttp = new XMLHttpRequest();
+
+ xmlHttp.onreadystatechange = function(){
+ if(xmlHttp.readyState == 4){
+ var target = elem.previousElementSibling;
+ if(xmlHttp.status == 200){
+ if(target.getElementsByTagName("span").length > 0)//for progress
+ {
+ target.getElementsByTagName("span")[0].innerHTML = text;
+ target.getElementsByClassName("progress_bar")[0].style.width = text;
+ }else{
+ target.innerHTML = text;
+ }
+ target.className='fa fa-check';
+ elem.className='fa fa-check';
+ show_hide(elem, false);
+ }else{
+ // TODO show error message returned from the server and let quickedit form open
+ target.className='fa fa-warning';
+ elem.className='fa fa-warning';
+ }
+ }
+ }
+ xmlHttp.open("POST", "<?php echo Filters::noXSS($baseurl); ?>js/callbacks/quickedit.php", true);
+ xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
+ xmlHttp.send("name=" + name + "&value=" + value + "&task_id=<?php echo Filters::noXSS($task_details['task_id']); ?>&csrftoken=<?php echo $_SESSION['csrftoken'] ?>");
+}
+</script>
+
+<!-- Grab fields wanted for this project so we can only show those we want -->
+<?php $fields = explode( ' ', $proj->prefs['visible_fields'] ); ?>
+
+<div id="taskdetails">
+ <span id="navigation"> <?php if ($prev_id): ?>
+ <?php echo tpl_tasklink($prev_id, L('previoustask'), false, array('id'=>'prev', 'accesskey' => 'p')); ?>
+
+ <?php endif; ?>
+ <?php if ($prev_id): ?> | <?php endif; ?>
+ <?php
+ if(isset($_COOKIE['tasklist_type']) && $_COOKIE['tasklist_type'] == 'project'):
+ $params = $_GET; unset($params['do'], $params['action'], $params['task_id'], $params['switch'], $params['project']);
+ ?>
+ <a href="<?php echo Filters::noXSS(createURL('project', $proj->id, null, array('do' => 'index') + $params)); ?>"><?php echo Filters::noXSS(L('tasklist')); ?></a>
+ <?php endif;
+ if ($next_id): ?>
+ <?php echo tpl_tasklink($next_id, L('nexttask'), false, array('id'=>'next', 'accesskey' => 'n')); ?>
+
+ <?php endif; ?>
+ </span>
+
+ <div id="taskfields">
+<?php if($user->can_edit_task($task_details)) : ?><div id="intromessage" align="center"><?php echo Filters::noXSS(L('clicktoedit')); ?></div><?php endif; ?>
+ <ul class="fieldslist">
+ <!-- Status -->
+ <?php if (in_array('status', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('status')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value">
+ <?php if ($task_details['is_closed']): ?>
+ <?php echo Filters::noXSS(L('closed')); ?>
+
+ <?php else: ?>
+ <?php echo Filters::noXSS($task_details['status_name']); ?>
+
+ <?php if ($reopened): ?>
+ &nbsp; <strong class="reopened"><?php echo Filters::noXSS(L('reopened')); ?></strong>
+ <?php endif; ?>
+ <?php endif; ?>
+ </span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="status" name="item_status">
+ <?php echo tpl_options($proj->listTaskStatuses(), Req::val('item_status', $task_details['item_status'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'status')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a onclick="show_hide(this.parentNode.parentNode, false)" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+
+ </li>
+ <?php endif; ?>
+
+ <!-- Progress -->
+ <?php if (in_array('progress', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('percentcomplete')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value">
+ <div class="progress_bar_container" style="width: 90px">
+ <span><?php echo Filters::noXSS($task_details['percent_complete']); ?>%</span>
+
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($task_details['percent_complete']); ?>%"></div>
+ </div>
+ </span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="percent" name="percent_complete">
+ <?php $arr = array(); for ($i = 0; $i<=100; $i+=10) $arr[$i] = $i.'%'; ?>
+ <?php echo tpl_options($arr, Req::val('percent_complete', $task_details['percent_complete'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'percent')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+
+ </li>
+ <?php endif; ?>
+ <!-- Task Type-->
+ <?php if (in_array('tasktype', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('tasktype')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php echo Filters::noXSS($task_details['tasktype_name']); ?></span>
+ <?php if ($user->can_edit_task($task_details)):?>
+ <span style="display:none;">
+ <div style="float:right">
+ <select id="tasktype" name="task_type">
+ <?php echo tpl_options($proj->listTaskTypes(), Req::val('task_type', $task_details['task_type'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'tasktype')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a></span>
+ </div>
+ </span>
+ <?php endif; ?></li>
+ <?php endif; ?>
+
+ <!-- Category -->
+ <?php if (in_array('category', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('category')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value">
+ <?php foreach ($parent as $cat): ?>
+ <?php echo Filters::noXSS($cat['category_name']); ?> &#8594;
+ <?php endforeach; ?>
+ <?php echo Filters::noXSS($task_details['category_name']); ?>
+
+ </span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="category" name="product_category">
+ <?php echo tpl_options($proj->listCategories(), Req::val('product_category', $task_details['product_category'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'category')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- Assigned To-->
+ <?php if (in_array('assignedto', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('assignedto')); ?></span>
+ <span class="value assignedto">
+ <?php if (empty($assigned_users)): ?>
+ <?php echo Filters::noXSS(L('noone')); ?>
+
+ <?php else: ?>
+ <table class="assignedto">
+ <?php
+ foreach ($assigned_users as $userid):
+ ?>
+ <?php if($fs->prefs['enable_avatars'] == 1) { ?>
+ <tr><td><?php echo tpl_userlinkavatar($userid, $fs->prefs['max_avatar_size'] / 2); ?></td><td><?php echo tpl_userlink($userid); ?></td></tr>
+ <?php } else { ?>
+ <tr>
+ <td class="assignedto_name"><?php echo tpl_userlink($userid); ?></td>
+ </tr>
+ <?php } ?>
+ <?php endforeach;
+ ?>
+ </table>
+ <?php
+ endif; ?>
+ </span>
+ </li>
+ <?php endif; ?>
+
+ <!-- OS -->
+ <?php if (in_array('os', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('operatingsystem')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php echo Filters::noXSS($task_details['os_name']); ?></span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="os" name="operating_system">
+ <?php echo tpl_options($proj->listOs(), Req::val('operating_system', $task_details['operating_system'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'os')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- Severity -->
+ <?php if (in_array('severity', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('severity')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php echo Filters::noXSS($task_details['severity_name']); ?></span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="severity" name="task_severity">
+ <?php echo tpl_options($fs->severities, Req::val('task_severity', $task_details['task_severity'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'severity')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a></div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- Priority -->
+ <?php if (in_array('priority', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('priority')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php echo Filters::noXSS($task_details['priority_name']); ?></span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="priority" name="task_priority">
+ <?php echo tpl_options($fs->priorities, Req::val('task_priority', $task_details['task_priority'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'priority')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- Reported In -->
+ <?php if (in_array('reportedin', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('reportedversion')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php echo Filters::noXSS($task_details['reported_version_name']); ?></span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="reportedver" name="product_version">
+ <?php echo tpl_options($proj->listVersions(false, 2, $task_details['product_version']), Req::val('reportedver', $task_details['product_version'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'reportedver')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- Due -->
+ <?php if (in_array('dueversion', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('dueinversion')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value"><?php if ($task_details['due_in_version_name']): ?>
+ <?php echo Filters::noXSS($task_details['due_in_version_name']); ?>
+
+ <?php else: ?>
+ <?php echo Filters::noXSS(L('undecided')); ?>
+
+ <?php endif; ?>
+ </span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <select id="dueversion" name="closedby_version">
+ <option value="0"><?php echo Filters::noXSS(L('undecided')); ?></option>
+ <?php echo tpl_options($proj->listVersions(false, 3), Req::val('closedby_version', $task_details['closedby_version'])); ?>
+ </select>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'dueversion')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+
+ </li>
+ <?php endif; ?>
+
+ <!-- Due Date -->
+ <?php if (in_array('duedate', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('duedate')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value">
+ <?php echo Filters::noXSS(formatDate($task_details['due_date'], false, L('undecided'))); ?><br><?php
+ $days = floor((strtotime(date('c', $task_details['due_date'])) - strtotime(date("Y-m-d"))) / (60 * 60 * 24));
+ if($task_details['due_date'] > 0)
+ {
+ if($days <$fs->prefs['days_before_alert'] && $days > 0)
+ {
+ echo "<font style='color: red; font-weight: bold'>".$days." ".L('daysleft')."</font>";
+ }
+ elseif($days < 0)
+ {
+ echo "<font style='color: red; font-weight: bold'>".str_replace('-', '', $days)."
+ ".L('dayoverdue')."</font>";
+ }
+ elseif($days == 0)
+ {
+ echo "<font style='color: red; font-weight: bold'>".L('duetoday')."</font>";
+ }
+ else
+ {
+ echo $days." ".L('daysleft');
+ }
+ }
+ ?>
+ </span>
+
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <?php echo tpl_datepicker('due_date', '', Req::val('due_date', $task_details['due_date'])); ?>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'due_date')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a><a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+
+ </li>
+ <?php endif; ?>
+ <?php if($proj->prefs['use_effort_tracking']) {
+ if ($user->perms('view_estimated_effort')) {
+ ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('estimatedeffort')); ?></span>
+ <span <?php if ($user->can_edit_task($task_details)): ?>onclick="show_hide(this, true)"<?php endif;?> class="value">
+ <?php
+ $displayedeffort = effort::secondsToString($task_details['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ if (empty($displayedeffort)) {
+ $displayedeffort = Filters::noXSS(L('undecided'));
+ }
+ echo $displayedeffort;
+ ?>
+ </span>
+ <?php if ($user->can_edit_task($task_details)): ?>
+ <span style="display:none">
+ <div style="float:right">
+ <input type="text" size="15" id="estimatedeffort" name="estimated_effort" value="<?php echo effort::SecondsToEditString($task_details['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']); ?>"/>
+ <br/><a onclick="quick_edit(this.parentNode.parentNode, 'estimatedeffort')" href="javascript:void(0)" class="button"><?php echo Filters::noXSS(L('confirmedit')); ?></a> <a href="javascript:void(0)" onclick="show_hide(this.parentNode.parentNode, false)" class="button"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </div>
+ </span>
+ <?php endif; ?>
+ </li>
+ <?php }
+ if ($user->perms('view_current_effort_done')) {
+ ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('currenteffortdone')); ?></span>
+ <?php
+ $total_effort = 0;
+ foreach($effort->details as $details){
+ $total_effort += $details['effort'];
+ }
+ ?>
+ <span class="value"><?php echo effort::secondsToString($total_effort, $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']); ?> </span>
+ </li>
+ <?php }
+ } ?>
+ <!-- Votes-->
+ <?php if (in_array('votes', $fields)): ?>
+ <li class="votes">
+ <span class="label"><?php echo Filters::noXSS(L('votes')); ?></span>
+ <span class="value">
+ <?php if (count($votes)): ?>
+ <a href="javascript:showhidestuff('showvotes')"><?php echo Filters::noXSS(count($votes)); ?> </a>
+ <div id="showvotes" class="hide">
+ <ul class="reports">
+ <?php foreach ($votes as $vote): ?>
+ <li><?php echo tpl_userlink($vote); ?> (<?php echo Filters::noXSS(formatDate($vote['date_time'])); ?>)</li>
+ <?php endforeach; ?>
+ </ul>
+ </div>
+ <?php endif; ?>
+ <?php if ($user->can_vote($task_details) > 0): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])),null,null,null,'style="display:inline"'); ?>
+ <input type="hidden" name="action" value="details.addvote" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <button class="fakelinkbutton" type="submit" title="<?php echo Filters::noXSS(L('addvote')); ?>">+1</button>
+ </form>
+ <?php elseif ($user->can_vote($task_details) == -2): ?> (<?php echo Filters::noXSS(L('alreadyvotedthistask')); ?>)
+ <?php elseif ($user->can_vote($task_details) == -3): ?> (<?php echo Filters::noXSS(L('alreadyvotedthisday')); ?>)
+ <?php elseif ($user->can_vote($task_details) == -4): ?> (<?php echo Filters::noXSS(L('votelimitreached')); ?>)
+ <?php endif; ?>
+ </span>
+ </li>
+ <?php endif; ?>
+
+ <!-- Private -->
+ <?php if (in_array('private', $fields)): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('private')); ?></span>
+ <span class="value">
+ <?php if ($user->can_change_private($task_details) && $task_details['mark_private']): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="makepublic"/>
+ <button type="submit" class="fakelinkbutton"><?php echo ucfirst(eL('makepublic')); ?></button>
+ </form>
+ <?php elseif ($user->can_change_private($task_details) && !$task_details['mark_private']): ?>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="action" value="makeprivate"/>
+ <button type="submit" class="fakelinkbutton"><?php echo ucfirst(eL('makeprivate')); ?></button>
+ </form>
+ <?php endif; ?>
+ </span>
+ </li>
+ <?php endif; ?>
+
+ <!-- Watching -->
+ <?php if (!$user->isAnon()): ?>
+ <li>
+ <span class="label"><?php echo Filters::noXSS(L('watching')); ?></span>
+ <span class="value">
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="ids" value="<?php echo Filters::noXSS($task_details['task_id']); ?>"/>
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($user->id); ?>"/>
+ <?php if (!$watched): ?>
+ <input type="hidden" name="action" value="details.add_notification"/>
+ <button type="submit" accesskey="w" class="fakelinkbutton"><?php echo ucfirst(eL('watchtask')); ?></button>
+ <?php else: ?>
+ <input type="hidden" name="action" value="remove_notification"/>
+ <button type="submit" accesskey="w" class="fakelinkbutton"><?php echo ucfirst(eL('stopwatching')); ?></button>
+ <?php endif; ?>
+ </form>
+ </span>
+ </li>
+ <?php endif; ?>
+ </ul>
+
+ <div id="fineprint">
+ <?php echo Filters::noXSS(L('attachedtoproject')); ?>: <a
+ href="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?project=<?php echo Filters::noXSS($task_details['project_id']); ?>"><?php echo Filters::noXSS($task_details['project_title']); ?></a>
+ <br/>
+ <?php echo Filters::noXSS(L('openedby')); ?> <?php echo tpl_userlink($task_details['opened_by']); ?>
+
+ <?php if ($task_details['anon_email'] && $user->perms('view_tasks')): ?>
+ (<?php echo Filters::noXSS($task_details['anon_email']); ?>)
+ <?php endif; ?>
+ -
+ <span title="<?php echo Filters::noXSS(formatDate($task_details['date_opened'], true)); ?>"><?php echo Filters::noXSS(formatDate($task_details['date_opened'], false)); ?></span>
+ <?php if ($task_details['last_edited_by']): ?>
+ <br/>
+ <?php echo Filters::noXSS(L('editedby')); ?> <?php echo tpl_userlink($task_details['last_edited_by']); ?>
+
+ -
+ <span title="<?php echo Filters::noXSS(formatDate($task_details['last_edited_time'], true)); ?>"><?php echo Filters::noXSS(formatDate($task_details['last_edited_time'], false)); ?></span>
+ <?php endif; ?>
+ </div>
+
+ </div>
+
+
+<div id="taskdetailsfull">
+ <h2 class="summary severity<?php echo Filters::noXSS($task_details['task_severity']); ?>">
+ FS#<?php echo Filters::noXSS($task_details['task_id']); ?> - <?php echo Filters::noXSS($task_details['item_summary']); ?>
+ </h2>
+ <span class="tags"><?php
+ foreach($tags as $tag):
+ echo tpl_tag($tag['tag_id']);
+ endforeach; ?></span>
+ <div id="taskdetailstext"><?php echo $task_text; ?></div>
+
+ <?php $attachments = $proj->listTaskAttachments($task_details['task_id']);
+ $this->display('common.attachments.tpl', 'attachments', $attachments); ?>
+
+ <?php $links = $proj->listTaskLinks($task_details['task_id']);
+ $this->display('common.links.tpl', 'links', $links); ?>
+</div>
+
+<div id="taskinfo">
+ <?php if(!count($deps)==0): ?>
+ <table id="dependency_table" class="table" width="100%">
+ <caption><?php echo (count($deps)==1) ? eL('taskdependsontask') : eL('taskdependsontasks'); ?></caption>
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('id')); ?></th>
+ <th><?php echo Filters::noXSS(L('project')); ?></th>
+ <th><?php echo Filters::noXSS(L('summary')); ?></th>
+ <th><?php echo Filters::noXSS(L('priority')); ?></th>
+ <th><?php echo Filters::noXSS(L('severity')); ?></th>
+ <th><?php echo Filters::noXSS(L('assignedto')); ?></th>
+ <th><?php echo Filters::noXSS(L('progress')); ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($deps as $dependency): ?>
+ <tr>
+ <td><?php echo $dependency['task_id'] ?></td>
+ <td><?php echo $dependency['project_title'] ?></td>
+ <td><?php echo tpl_tasklink($dependency['task_id']); ?></td>
+ <td><?php echo $fs->priorities[$dependency['task_priority']] ?></td>
+ <td class="severity<?php echo Filters::noXSS($dependency['task_severity']); ?>"><?php echo $fs->
+ severities[$dependency['task_severity']] ?>
+ </td>
+ <td> <?php for ($i=0;$i<count($dependency['assigned_to']);$i++){ if ($i>0) echo ", "; echo $dependency['assigned_to'][$i];} ?></td>
+ <td class="task_progress">
+ <div class="progress_bar_container">
+ <span><?php echo Filters::noXSS($dependency['percent_complete']); ?>%</span>
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($dependency['percent_complete']); ?>%"></div>
+ </div>
+ </td>
+ <td>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id']))); ?>
+ <input type="hidden" name="depend_id" value="<?php echo Filters::noXSS($dependency['depend_id']); ?>" />
+ <input type="hidden" name="return_task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="action" value="removedep" />
+ <button type="submit" title="<?php echo Filters::noXSS(L('remove')); ?>" class="fa fa-unlink fa-lg"></button>
+ </form>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ <?php endif; ?>
+
+ <!-- This task blocks the following tasks: -->
+ <?php if(!count($blocks)==0): ?>
+ <table id="blocking_table" class="table" width="100%">
+ <caption><?php echo (count($blocks)==1) ? eL('taskblock') : eL('taskblocks'); ?></caption>
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('id')); ?></th>
+ <th><?php echo Filters::noXSS(L('project')); ?></th>
+ <th><?php echo Filters::noXSS(L('summary')); ?></th>
+ <th><?php echo Filters::noXSS(L('priority')); ?></th>
+ <th><?php echo Filters::noXSS(L('severity')); ?></th>
+ <th><?php echo Filters::noXSS(L('assignedto')); ?></th>
+ <th><?php echo Filters::noXSS(L('progress')); ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($blocks as $dependency): ?>
+ <tr>
+ <td><?php echo $dependency['task_id'] ?></td>
+ <td><?php echo $dependency['project_title'] ?></td>
+ <td><?php echo tpl_tasklink($dependency['task_id']); ?></td>
+ <td><?php echo $fs->priorities[$dependency['task_priority']] ?></td>
+ <td class="severity<?php echo Filters::noXSS($dependency['task_severity']); ?>"><?php echo $fs->
+ severities[$dependency['task_severity']] ?>
+ </td>
+ <td> <?php for ($i=0;$i<count($dependency['assigned_to']);$i++){ if ($i>0) echo ", "; echo $dependency['assigned_to'][$i];} ?></td>
+ <td class="task_progress">
+ <div class="progress_bar_container">
+ <span><?php echo Filters::noXSS($dependency['percent_complete']); ?>%</span>
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($dependency['percent_complete']); ?>%"></div>
+ </div>
+ </td>
+ <td>
+ <?php echo tpl_form(Filters::noXSS(createURL('details', $dependency['task_id']))); ?>
+ <input type="hidden" name="depend_id" value="<?php echo Filters::noXSS($dependency['depend_id']); ?>" />
+ <input type="hidden" name="return_task_id" value="<?php echo Filters::noXSS($task_details['task_id']); ?>" />
+ <input type="hidden" name="action" value="removedep" />
+ <button type="submit" title="<?php echo Filters::noXSS(L('remove')); ?>" class="fa fa-unlink fa-lg"></button>
+ </form>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ <?php endif; ?>
+
+ <?php
+ if (!$task_details['supertask_id'] == 0) {
+ $supertask = Flyspray::getTaskDetails($task_details['supertask_id'], true);
+ if ($user->can_view_task($supertask)) {
+ echo eL('taskissubtaskof').' '.tpl_tasklink($supertask);
+ }
+ }
+ ?>
+ <?php if(!count($subtasks)==0): ?>
+ <table id="subtask_table" class="table" width="100%">
+ <caption><?php echo (count($subtasks)==1) ? eL('taskhassubtask') : eL('taskhassubtasks'); ?></caption>
+ <thead>
+ <tr>
+ <th><?php echo Filters::noXSS(L('id')); ?></th>
+ <th><?php echo Filters::noXSS(L('project')); ?></th>
+ <th><?php echo Filters::noXSS(L('summary')); ?></th>
+ <th><?php echo Filters::noXSS(L('priority')); ?></th>
+ <th><?php echo Filters::noXSS(L('severity')); ?></th>
+ <th><?php echo Filters::noXSS(L('assignedto')); ?></th>
+ <th><?php echo Filters::noXSS(L('progress')); ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($subtasks as $subtaskOrgin): ?>
+ <?php $subtask = $fs->getTaskDetails($subtaskOrgin['task_id']); ?>
+ <tr id="task<?php echo $subtask['task_id']; ?>" class="severity<?php echo Filters::noXSS($subtask['task_severity']); ?>">
+ <td><?php echo $subtask['task_id'] ?></td>
+ <td><?php echo $subtask['project_title'] ?></td>
+ <td><?php echo tpl_tasklink($subtask['task_id']); ?></td>
+ <td><?php echo $fs->priorities[$subtask['task_priority']] ?></td>
+ <td class="severity<?php echo Filters::noXSS($subtask['task_severity']); ?>"><?php echo $fs->severities[$subtask['task_severity']]
+ ?>
+ </td>
+ <td> <?php for ($i=0;$i<count($subtaskOrgin['assigned_to']);$i++){ if ($i>0) echo ", "; echo $subtaskOrgin['assigned_to'][$i];} ?></td>
+ <td class="task_progress">
+ <div class="progress_bar_container">
+ <span><?php echo Filters::noXSS($subtask['percent_complete']); ?>%</span>
+
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($subtask['percent_complete']); ?>%"></div>
+ </div>
+ </td>
+ <td>
+ <?php
+ echo tpl_form(Filters::noXSS(createURL('details', $task_details['task_id'])));
+ ?>
+ <input type="hidden" name="subtaskid" value="<?php echo Filters::noXSS($subtask['task_id']); ?>" />
+ <input type="hidden" name="action" value="removesubtask" />
+ <button type="submit" title="<?php echo Filters::noXSS(L('remove')); ?>" class="fa fa-unlink fa-lg"></button>
+ </form>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ <?php endif; ?>
+ </div>
+</div>
+
+<?php if ($task_details['is_closed']): ?>
+<div id="taskclosed">
+ <?php echo Filters::noXSS(L('closedby')); ?>&nbsp;&nbsp;<?php echo tpl_userlink($task_details['closed_by']); ?><br/>
+ <?php echo Filters::noXSS(formatDate($task_details['date_closed'], true)); ?><br/>
+ <strong><?php echo Filters::noXSS(L('reasonforclosing')); ?></strong> &nbsp;<?php echo Filters::noXSS($task_details['resolution_name']); ?><br/>
+ <?php if ($task_details['closure_comment']): ?>
+ <strong><?php echo Filters::noXSS(L('closurecomment')); ?></strong>
+ &nbsp;<?php echo wordwrap(TextFormatter::render($task_details['closure_comment']), 40, "\n", true); ?>
+
+ <?php endif; ?>
+</div>
+<?php endif; ?>
+
+<div id="actionbuttons">
+
+ <?php if (count($penreqs)): ?>
+ <div class="pendingreq"><strong><?php echo Filters::noXSS(formatDate($penreqs[0]['time_submitted'])); ?>
+
+ : <?php echo Filters::noXSS(L('request'.$penreqs[0]['request_type'])); ?></strong>
+ <?php if ($penreqs[0]['reason_given']): ?>
+ <?php echo Filters::noXSS(L('reasonforreq')); ?>: <?php echo Filters::noXSS($penreqs[0]['reason_given']); ?>
+
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+</div>
+
+<div class="clear"></div>
+</div>
diff --git a/themes/CleanFS/templates/editcomment.tpl b/themes/CleanFS/templates/editcomment.tpl
new file mode 100644
index 0000000..e27c6eb
--- /dev/null
+++ b/themes/CleanFS/templates/editcomment.tpl
@@ -0,0 +1,83 @@
+<div class="box">
+ <div class="comment_container">
+ <div class="comment_avatar"><?php echo tpl_userlinkavatar($user->id, $fs->prefs['max_avatar_size'], 'av_comment'); ?></div>
+ <div class="comment">
+ <div class="comment_header">
+ <div class="comment_header_actions">
+ <?php
+ $theuser = new User($comment['user_id']);
+ if (!$theuser->isAnon()) {
+ if ($theuser->perms('is_admin')) {
+ $rank = 'Admin';
+ }
+ else if ($theuser->perms('manage_project')) {
+ $rank = 'Project Manager';
+ }
+ else {
+ $rank = '';
+ }
+
+ if (!empty($rank)) {
+ echo '<span class="comment_header_usertype">'.Filters::noXSS($rank).'</span>';
+ }
+ }
+ ?>
+ </div>
+ <div class="comment_header_infos"><?php echo tpl_userlink($comment['user_id']); ?> <?php echo Filters::noXSS(L('commentedon')); ?> <?php echo Filters::noXSS(formatDate($comment['date_added'], true)); ?></div>
+ </div>
+ <div class="commenttext">
+ <?php echo tpl_form(CreateUrl('details', $comment['task_id'], 'multipart/form-data')); ?>
+ <input type="hidden" name="action" value="editcomment" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($comment['task_id']); ?>" />
+ <input type="hidden" name="comment_id" value="<?php echo Filters::noXSS($comment['comment_id']); ?>" />
+ <input type="hidden" name="previous_text" value="<?php echo Filters::noXSS($comment['comment_text']); ?>" />
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <button tabindex="9" type="button" onclick="showPreview('comment_text', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('comment_text', 10, 72, array('id' => 'comment_text'), $comment['comment_text']); ?>
+ <div id="addlinkbox">
+ <?php $links = $proj->listLinks($comment['comment_id'], $comment['task_id']);
+ $this->display('common.editlinks.tpl', 'links', $links); ?>
+ <?php if ($user->perms('create_attachments')): ?>
+ <button id="addlinkbox_addalink" tabindex="10" type="button" onclick="addLinkField('addlinkbox')">
+ <?php echo Filters::noXSS(L('addalink')); ?>
+ </button>
+ <button id="addlinkbox_addanotherlink" tabindex="10" style="display: none" type="button" onclick="addLinkField('addlinkbox')">
+ <?php echo Filters::noXSS(L('addanotherlink')); ?>
+ </button>
+ <span style="display: none">
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" onclick="removeLinkField(this, 'addlinkbox');"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ <noscript>
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ </noscript>
+ <?php endif; ?>
+ </div>
+ <div id="uploadfilebox">
+ <?php $attachments = $proj->listAttachments($comment['comment_id'], $comment['task_id']);
+ $this->display('common.editattachments.tpl', 'attachments', $attachments); ?>
+ <?php if ($user->perms('create_attachments')): ?>
+ <button id="uploadfilebox_attachafile" tabindex="7" type="button" onclick="addUploadFields()">
+ <?php echo Filters::noXSS(L('uploadafile')); ?> (<?php echo Filters::noXSS(L('max')); ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?php echo Filters::noXSS(L('MiB')); ?>)
+ </button>
+ <button id="uploadfilebox_attachanotherfile" tabindex="7" style="display: none" type="button" onclick="addUploadFields()">
+ <?php echo Filters::noXSS(L('attachanotherfile')); ?> (<?php echo Filters::noXSS(L('max')); ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?php echo Filters::noXSS(L('MiB')); ?>)
+ </button>
+ <span style="display: none;"><!-- this span is shown/copied in javascript when adding files -->
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ <a href="javascript://" tabindex="6" onclick="removeUploadField(this);"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ <noscript>
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ </noscript>
+ <?php endif; ?>
+ </div>
+ <button accesskey="s" tabindex="9" type="submit" class="positive"><?php echo Filters::noXSS(L('saveeditedcomment')); ?></button>
+ <a class="button" href="<?php echo Filters::noXSS(CreateUrl('details', $comment['task_id'])); ?>"><?php echo Filters::noXSS(L('canceledit')); ?></a>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/themes/CleanFS/templates/feed.atom.tpl b/themes/CleanFS/templates/feed.atom.tpl
new file mode 100644
index 0000000..e63eb4a
--- /dev/null
+++ b/themes/CleanFS/templates/feed.atom.tpl
@@ -0,0 +1,73 @@
+<?php echo '<?xml version="1.0" ?>'; ?>
+
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title type="text"><?php echo Filters::noXSS($fs->prefs['page_title']); ?></title>
+ <subtitle type="text">
+ <?php echo Filters::noXSS($feed_description); ?>
+
+ </subtitle>
+ <id><?php echo Filters::noXSS($baseurl); ?></id>
+ <?php if($feed_image): ?>
+ <icon><?php echo Filters::noXSS($feed_image); ?></icon>
+ <?php endif; ?>
+ <updated><?php echo Filters::noXSS(date('Y-m-d\TH:i:s\Z',$most_recent)); ?></updated>
+ <link rel="self" type="text/xml" href="feed.php?feed_type=atom"/>
+ <link rel="alternate" type="text/html" hreflang="en" href="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>"/>
+ <?php foreach ($task_details as $row): ?>
+ <entry>
+ <title>FS#<?php echo Filters::noXSS($row['task_id']); ?>: <?php echo Filters::noXSS($row['item_summary']); ?></title>
+ <link href="<?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?>" />
+ <updated><?php echo Filters::noXSS(date('Y-m-d\TH:i:s\Z',intval($row['last_edited_time']))); ?></updated>
+ <published><?php echo Filters::noXSS(date('Y-m-d\TH:i:s\Z',intval($row['date_opened']))); ?></published>
+ <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
+ <div xmlns="http://www.w3.org/1999/xhtml"> <?php
+ $data = $row['detailed_desc'];
+
+ if ($conf['general']['syntax_plugin'] == 'dokuwiki') {
+ $data = TextFormatter::render($data);
+ // Convert most common html- but not xml-entities.
+ $data = preg_replace('/&lsquo;/', '&#8216;', $data);
+ $data = preg_replace('/&rsquo;/', '&#8217;', $data);
+ $data = preg_replace('/&ldquo;/', '&#8220;', $data);
+ $data = preg_replace('/&rdquo;/', '&#8221;', $data);
+ echo $data;
+ }
+ else {
+ if (preg_match('/^</', $data) === 0) {
+ // Assume an old entry. Just can't rely on any tags to be valid.
+ $data = strip_tags($data);
+ $data = preg_replace('/&/', '&amp;', $data);
+ $data = preg_replace('/</', '&lt;', $data);
+ $data = preg_replace('/>/', '&gt;', $data);
+ $data = preg_replace('/"/', '&quot;', $data);
+ $data = '<p>' . nl2br($data) . '</p>';
+ }
+ else {
+ // Assume a new entry. Problem cases when an old entry started with
+ // < are just not handled well. Must draw the line somewhere, even if the
+ // browser will not show it or has an error. Those cases should be quite few.
+ }
+
+ // Chrome complained loudly about this one. Firefox just didn't show anything...
+ // Any more html entities produced by ckeditor that should be turned into
+ // a numeric character reference? Add when found. Or check if we already have
+ // somewhere an existing function to do that.
+ $data = preg_replace('/&nbsp;/', '&#160;', $data);
+
+ // Single case. Old entry that started with <. Can contain &'s too.
+ // Convert to entity, without touching already existing entities.
+ $data = preg_replace('/&(?!([a-z]+|#[0-9]+);)/', '&amp;', $data);
+
+ // Still double quotes there? Convert any not appearing inside tags.
+ // Not sure if ckeditor makes that kind of entries.
+ $data = preg_replace('/"(?=[^>]*(<|$))/', '&quot;', $data);
+
+ // Best alternative, although will strip some odd custom data from old entries.
+ echo TextFormatter::render($data);
+ } ?></div>
+ </content>
+ <author><name><?php echo Filters::noXSS($row['real_name']); ?></name></author>
+ <id><?php echo Filters::noXSS($baseurl); ?>:<?php echo Filters::noXSS($row['task_id']); ?></id>
+ </entry>
+ <?php endforeach; ?>
+</feed>
diff --git a/themes/CleanFS/templates/feed.rss1.tpl b/themes/CleanFS/templates/feed.rss1.tpl
new file mode 100644
index 0000000..4b37832
--- /dev/null
+++ b/themes/CleanFS/templates/feed.rss1.tpl
@@ -0,0 +1,70 @@
+<?php echo '<?xml version="1.0" ?>'; ?>
+
+<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://purl.org/rss/1.0/"
+ xmlns:content="http://purl.org/rss/1.0/modules/content/">
+ <channel rdf:about="<?php echo Filters::noXSS($baseurl); ?>">
+ <title><?php echo Filters::noXSS($fs->prefs['page_title']); ?></title>
+ <link><?php echo Filters::noXSS($baseurl); ?></link>
+ <description><?php echo Filters::noXSS($feed_description); ?></description>
+ <dc:date><?php echo Filters::noXSS(date('Y-m-d\TH:i:s\Z',$most_recent)); ?></dc:date>
+ <items>
+ <rdf:Seq>
+ <?php foreach($task_details as $row): ?>
+ <rdf:li rdf:resource="<?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?>" />
+ <?php endforeach; ?>
+ </rdf:Seq>
+ </items>
+ <?php if($feed_image): ?>
+ <image rdf:resource="<?php echo Filters::noXSS($feed_image); ?>" />
+ <?php endif; ?>
+ </channel>
+ <?php foreach($task_details as $row): ?>
+ <item rdf:about="<?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?>">
+ <title>FS#<?php echo Filters::noXSS($row['task_id']); ?>: <?php echo Filters::noXSS($row['item_summary']); ?></title>
+ <link><?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?></link>
+ <dc:date><?php echo Filters::noXSS(date('Y-m-d\TH:i:s\Z',intval($row['last_edited_time']))); ?></dc:date>
+ <dc:creator><?php echo Filters::noXSS($row['real_name']); ?></dc:creator>
+ <?php
+ $data = $row['detailed_desc'];
+
+ if ($conf['general']['syntax_plugin'] == 'dokuwiki') {
+ $data = TextFormatter::render($data);
+ // Convert most common html- but not xml-entities.
+ $data = preg_replace('/&lsquo;/', '&#8216;', $data);
+ $data = preg_replace('/&rsquo;/', '&#8217;', $data);
+ $data = preg_replace('/&ldquo;/', '&#8220;', $data);
+ $data = preg_replace('/&rdquo;/', '&#8221;', $data);
+ }
+ else {
+ if (preg_match('/^</', $data) === 0) {
+ // Assume an old entry. Just can't rely on any tags to be valid.
+ $data = strip_tags($data);
+ $data = preg_replace('/&/', '&amp;', $data);
+ $data = preg_replace('/</', '&lt;', $data);
+ $data = preg_replace('/>/', '&gt;', $data);
+ $data = preg_replace('/"/', '&quot;', $data);
+ $data = nl2br($data);
+ }
+ else {
+ // Assume a new entry. Problem cases when old entry started with
+ // < are just not handled. Must draw the line somewhere, even if the
+ // browser will not show it or has an error. Those cases should be quite few.
+ }
+
+ // Single case. Old entry that started with <. Can contain &'s too.
+ // Convert to entity, without touching already existing entities.
+ $data = preg_replace('/&(?!([a-z]+|#[0-9]+);)/', '&amp;', $data);
+
+ // Still double quotes there? Convert any not appearing inside tags.
+ // Not sure if ckeditor makes that kind of entries.
+ $data = preg_replace('/"(?=[^>]*(<|$))/', '&quot;', $data);
+ $data = TextFormatter::render($data);
+ }
+ ?>
+ <description><?php echo Filters::noXSS(strip_tags($data)); ?></description>
+ <content:encoded><![CDATA[<?php echo $data; ?>]]></content:encoded>
+ </item>
+ <?php endforeach; ?>
+</rdf:RDF>
diff --git a/themes/CleanFS/templates/feed.rss2.tpl b/themes/CleanFS/templates/feed.rss2.tpl
new file mode 100644
index 0000000..e4e653c
--- /dev/null
+++ b/themes/CleanFS/templates/feed.rss2.tpl
@@ -0,0 +1,65 @@
+<?php echo '<?xml version="1.0" ?>'; ?>
+
+<rss version="2.0">
+ <channel>
+ <title><?php echo Filters::noXSS($fs->prefs['page_title']); ?></title>
+ <lastBuildDate><?php echo Filters::noXSS(date('r',$most_recent)); ?></lastBuildDate>
+ <description><?php echo Filters::noXSS($feed_description); ?></description>
+ <link><?php echo Filters::noXSS($baseurl); ?></link>
+ <?php if($feed_image): ?>
+ <image>
+ <url><?php echo Filters::noXSS($feed_image); ?></url>
+ <link><?php echo Filters::noXSS($baseurl); ?></link>
+ <title>[Logo]</title>
+ </image>
+ <?php endif;
+ foreach($task_details as $row):?>
+ <item>
+ <title>FS#<?php echo Filters::noXSS($row['task_id']); ?>: <?php echo Filters::noXSS($row['item_summary']); ?></title>
+ <author><?php echo Filters::noXSS($row['real_name']); ?></author>
+ <pubDate><?php echo Filters::noXSS(date('r',intval($row['date_opened']))); ?></pubDate>
+ <description><![CDATA[<?php
+ $data = $row['detailed_desc'];
+
+ if ($conf['general']['syntax_plugin'] == 'dokuwiki') {
+ $data = TextFormatter::render($data);
+ // Convert most common html- but not xml-entities.
+ $data = preg_replace('/&lsquo;/', '&#8216;', $data);
+ $data = preg_replace('/&rsquo;/', '&#8217;', $data);
+ $data = preg_replace('/&ldquo;/', '&#8220;', $data);
+ $data = preg_replace('/&rdquo;/', '&#8221;', $data);
+ echo $data;
+ }
+ else {
+ if (preg_match('/^</', $data) === 0) {
+ // Assume an old entry. Just can't rely on any tags to be valid.
+ $data = strip_tags($data);
+ $data = preg_replace('/&/', '&amp;', $data);
+ $data = preg_replace('/</', '&lt;', $data);
+ $data = preg_replace('/>/', '&gt;', $data);
+ $data = preg_replace('/"/', '&quot;', $data);
+ $data = nl2br($data);
+ }
+ else {
+ // Assume a new entry. Problem cases when old entry started with
+ // < are just not handled. Must draw the line somewhere, even if the
+ // browser will not show it or has an error. Those cases should be quite few.
+ }
+
+ // Single case. Old entry that started with <. Can contain &'s too.
+ // Convert to entity, without touching already existing entities.
+ $data = preg_replace('/&(?!([a-z]+|#[0-9]+);)/', '&amp;', $data);
+
+ // Still double quotes there? Convert any not appearing inside tags.
+ // Not sure if ckeditor makes that kind of entries.
+ $data = preg_replace('/"(?=[^>]*(<|$))/', '&quot;', $data);
+ // Best alternative, although will strip some odd custom data from old entries.
+ echo TextFormatter::render($data);
+ }
+ ?>]]></description>
+ <link><?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?></link>
+ <guid><?php echo Filters::noXSS(CreateURL('details', $row['task_id'])); ?></guid>
+ </item>
+ <?php endforeach; ?>
+ </channel>
+</rss>
diff --git a/themes/CleanFS/templates/footer.tpl b/themes/CleanFS/templates/footer.tpl
new file mode 100644
index 0000000..3c2a17c
--- /dev/null
+++ b/themes/CleanFS/templates/footer.tpl
@@ -0,0 +1,10 @@
+<?php $this->display('shortcuts.tpl'); ?>
+</div>
+<?php if(isset($general_integration)): echo $general_integration; endif; ?>
+<div id="footer">
+ <?php if(isset($footer_integration)): echo $footer_integration; endif; ?>
+ <!-- Please don't remove this line - it helps promote Flyspray -->
+ <a href="http://flyspray.org/" class="offsite"><?php echo Filters::noXSS(L('poweredby')); ?><?php if ($user->perms('is_admin')): ?> <?php echo Filters::noXSS($fs->version); ?> <?php endif; ?></a>
+</div>
+</body>
+</html>
diff --git a/themes/CleanFS/templates/header.tpl b/themes/CleanFS/templates/header.tpl
new file mode 100644
index 0000000..ac360b4
--- /dev/null
+++ b/themes/CleanFS/templates/header.tpl
@@ -0,0 +1,131 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="<?= eL('locale') ?>" xml:lang="<?= eL('locale') ?>">
+<head>
+<title><?php echo Filters::noXSS($this->_title); ?></title>
+<meta name="description" content="Flyspray, a Bug Tracking System written in PHP." />
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<?php if ($fs->prefs['url_rewriting']): ?>
+<base href="<?php echo Filters::noXSS($baseurl); ?>" />
+<?php endif; ?>
+<?php if(trim($this->get_image('favicon'))): ?>
+<link rel="icon" type="image/png" href="<?php echo Filters::noXSS($this->get_image('favicon')); ?>" />
+<?php endif; ?>
+<link rel="index" id="indexlink" type="text/html" href="<?php echo Filters::noXSS($baseurl); ?>" />
+<?php foreach ($fs->projects as $project): ?>
+<link rel="section" type="text/html" href="<?php echo Filters::noXSS($baseurl); ?>?project=<?php echo Filters::noXSS($project[0]); ?>" />
+<?php endforeach; ?>
+<link media="screen" href="<?php echo (is_readable(BASEDIR . '/themes/'.$this->_theme.'theme.css')) ? Filters::noXSS($this->themeUrl()) : Filters::noXSS($baseurl).'themes/CleanFS/' ; ?>theme.css" rel="stylesheet" type="text/css" />
+<?php
+# css hack to fix css3only state switches with ~ in older android browser <4.3 TODO: find webkit version when that issue was fixed.
+if(isset($_SERVER['HTTP_USER_AGENT']) && preg_match( '/Android [23]\.\d|Android 4\.[012]/' , $_SERVER['HTTP_USER_AGENT'])):?>
+<link rel="stylesheet" type="text/css" media="screen" href="<?php echo Filters::noXSS($this->themeUrl()); ?>oldwebkitsiblingfix.css'; ?>" />
+<?php endif; ?>
+<link media="print" href="<?php echo Filters::noXSS($this->themeUrl()); ?>theme_print.css" rel="stylesheet" type="text/css" />
+<link href="<?php echo Filters::noXSS($this->themeUrl()); ?>font-awesome.min.css" rel="stylesheet" type="text/css" />
+<?php
+# include an optional, customized css file for tag styling (all projects, loads even for guests)
+if(is_readable(BASEDIR.'/themes/'.$this->_theme.'tags.css')): ?>
+<link href="<?php echo Filters::noXSS($this->themeUrl()); ?>tags.css" rel="stylesheet" type="text/css" />
+<?php endif; ?>
+<?php if($proj->prefs['custom_style'] !=''): ?>
+<link media="screen" href="<?php echo Filters::noXSS($this->themeUrl()).$proj->prefs['custom_style']; ?>" rel="stylesheet" type="text/css" />
+<?php endif; ?>
+<link rel="alternate" type="application/rss+xml" title="Flyspray RSS 1.0 Feed"
+ href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss1&amp;project=<?php echo Filters::noXSS($proj->id); ?>" />
+<link rel="alternate" type="application/rss+xml" title="Flyspray RSS 2.0 Feed"
+ href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss2&amp;project=<?php echo Filters::noXSS($proj->id); ?>" />
+<link rel="alternate" type="application/atom+xml" title="Flyspray Atom 0.3 Feed"
+ href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=atom&amp;project=<?php echo Filters::noXSS($proj->id); ?>" />
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/prototype/prototype.js"></script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/script.aculo.us/scriptaculous.js"></script>
+<?php if ('index' == $do): ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/index.js"></script>
+<?php endif; ?>
+<?php if ('details' == $do && $user->can_view_project($proj->id)): ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/details.js"></script>
+<?php endif; ?>
+<?php if ( $do == 'pm' || $do == 'admin'): ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/tablecontrol.js"></script>
+<?php endif; ?>
+<?php if ( $do == 'depends'): ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/jit/jit.js"></script>
+<?php endif; ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/tabs.js"></script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/functions.js"></script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/jscalendar/calendar_stripped.js"></script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/jscalendar/calendar-setup_stripped.js"> </script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/jscalendar/lang/calendar-<?php echo Filters::noXSS(substr(L('locale'), 0, 2)); ?>.js"></script>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/lightbox/js/lightbox.js"></script>
+<?php if(isset($conf['general']['syntax_plugin']) && $conf['general']['syntax_plugin'] !='dokuwiki'): ?><script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>js/ckeditor/ckeditor.js"></script><?php endif; ?>
+<link rel="stylesheet" href="<?php echo Filters::noXSS($baseurl); ?>js/lightbox/css/lightbox.css" type="text/css" media="screen" />
+<?php foreach(TextFormatter::get_javascript() as $file): ?>
+<script type="text/javascript" src="<?php echo Filters::noXSS($baseurl); ?>plugins/<?php echo Filters::noXSS($file); ?>"></script>
+<?php endforeach; ?>
+<?php if(isset($fs->prefs['captcha_recaptcha']) && $fs->prefs['captcha_recaptcha']
+ && isset($fs->prefs['captcha_recaptcha_sitekey']) && $fs->prefs['captcha_recaptcha_sitekey']!=''
+ && isset($fs->prefs['captcha_recaptcha_secret']) && $fs->prefs['captcha_recaptcha_secret']!=''
+): ?>
+ <?php
+ if (
+ ($do=='register')
+ || ($do=='newtask' && $user->isAnon())
+ ): ?>
+ <script src='https://www.google.com/recaptcha/api.js'></script>
+ <?php endif; ?>
+<?php endif; ?>
+</head>
+<body onload="<?php
+ if (isset($_SESSION['SUCCESS']) || isset($_SESSION['ERROR']) || isset($_SESSION['ERRORS'])):
+ ?>/* window.setTimeout('Effect.Fade(\'successanderrors\', {duration:.3})', 10000); */
+ <?php endif ?>" class="<?php echo (isset($do) ? Filters::noXSS($do) : 'index').' p'.$proj->id; ?>">
+
+<div id="archnavbar" class="anb-forum">
+ <a href="https://www.archlinux32.org">
+ <div id="archnavbarlogo">
+ <?php if($fs->prefs['logo']) { ?><img src="<?php echo Filters::noXSS($baseurl.'/'.$fs->prefs['logo']); ?>" /><?php } ?>
+ </div>
+ </a>
+ <div id="archnavbarmenu">
+ <ul id="archnavbarlist">
+ <li id="anb-home"><a href="http://archlinux32.org/">Home</a></li>
+ <li id="anb-packages"><a href="https://packages.archlinux32.org/">Packages</a></li>
+ <li id="anb-forums"><a href="https://bbs.archlinux32.org/">Forums</a></li>
+ <li id="anb-bugs"><a href="https://bugs.archlinux32.org/">Bugs</a></li>
+ <li id="anb-mailing list"><a href="https://lists.archlinux.org/listinfo/arch-ports">Mailing List</a></li>
+ <li id="anb-download"><a href="https://archlinux32.org/download/">Download</a></li>
+ <li id="anb-arch linux official"><a href="https://www.archlinux.org/">Arch Linux Official</a></li>
+ </ul>
+ </div>
+</div>
+ <?php $this->display('links.tpl'); ?>
+
+ <?php if (isset($_SESSION['SUCCESS']) || isset($_SESSION['ERROR']) || isset($_SESSION['ERRORS'])): ?>
+ <div id="successanderrors" onclick="this.style.display='none'">
+ <?php endif; ?>
+ <?php if(isset($_SESSION['SUCCESS'])): ?><div class="success"><i class="fa fa-check" aria-hidden="true"></i> <?php echo Filters::noXSS($_SESSION['SUCCESS']); ?></div><?php endif; ?>
+ <?php if(isset($_SESSION['ERROR'])): ?><div class="error"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> <?php echo Filters::noXSS($_SESSION['ERROR']); ?></div><?php endif; ?>
+ <?php if(isset($_SESSION['ERRORS'])): ?>
+ <?php
+ foreach(array_keys($_SESSION['ERRORS']) as $e){
+ echo '<div class="error"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> '.eL($e).'</div>';
+ }
+ ?>
+ <?php endif; ?>
+ <?php if(isset($_SESSION['SUCCESS']) || isset($_SESSION['ERROR']) || isset($_SESSION['ERRORS'])): ?>
+ </div>
+ <?php endif;?>
+
+<div id="content">
+ <div class="clear"></div>
+ <?php $show_message = explode(' ', $fs->prefs['pages_welcome_msg']);
+ if ($fs->prefs['intro_message'] && ($proj->id == 0 || $proj->prefs['disp_intro']) && (in_array($do, $show_message)) ):?>
+ <div id="intromessage"><?php echo TextFormatter::render($fs->prefs['intro_message'], 'msg', $proj->id); ?></div>
+ <?php endif; ?>
+ <?php if ($proj->id > 0):
+ $show_message = explode(' ', $proj->prefs['pages_intro_msg']);
+ if ($proj->prefs['intro_message'] && (in_array($do, $show_message))): ?>
+ <div id="intromessage"><?php echo TextFormatter::render($proj->prefs['intro_message'], 'msg', $proj->id, ($proj->prefs['last_updated'] < $proj->prefs['cache_update']) ? $proj->prefs['pm_instructions'] : ''); ?></div>
+ <?php endif; endif; ?>
diff --git a/themes/CleanFS/templates/index.tpl b/themes/CleanFS/templates/index.tpl
new file mode 100644
index 0000000..a918b78
--- /dev/null
+++ b/themes/CleanFS/templates/index.tpl
@@ -0,0 +1,623 @@
+<script type="text/javascript">
+ //Used for dynamically displaying the bulk edit pane, when Checkboxes are >1
+ function BulkEditCheck()
+ {
+ var form = document.getElementById('massops');
+ var count = 0;
+ for(var n=0;n < form.length;n++){
+ if(form[n].name == 'ids[]' && form[n].checked){
+ count++;
+ }
+ }
+
+ if(count == 0)
+ {
+ Effect.Fade('bulk_edit_selectedItems',{ duration: 0.2 });
+ }
+ if(count == 1)
+ {
+ Effect.Appear('bulk_edit_selectedItems',{ duration: 0.2 });
+ }
+ }
+
+ function massSelectBulkEditCheck()
+ {
+ var form = document.getElementById('massops');
+ var check_count = 0, uncheck_count;
+ for(var n=0;n < form.length;n++){
+ if(form[n].name == 'ids[]'){
+ if(form[n].checked)
+ check_count++;
+ else
+ uncheck_count++;
+ }
+ }
+
+ if(check_count == 0)
+ {
+ Effect.Appear('bulk_edit_selectedItems',{ duration: 0.2 });
+ }
+
+ if(uncheck_count == 0)
+ {
+ Effect.Fade('bulk_edit_selectedItems',{ duration: 0.2 });
+ }
+ }
+
+ function ClearAssignments()
+ {
+ document.getElementById('bulk_assignment').options.length = 0;
+ }
+</script>
+
+<?php if(isset($update_error)): ?>
+<div id="updatemsg">
+ <span class="bad"> <?= eL('updatewrong') ?></span>
+ <a href="?hideupdatemsg=yep"><?= eL('hidemessage') ?></a>
+</div>
+<?php endif; ?>
+
+<?php if(isset($updatemsg)): ?>
+<div id="updatemsg">
+ <a href="http://flyspray.org/"><?= eL('updatefs') ?></a> <?= eL('currentversion') ?>
+
+ <span class="bad"><?php echo Filters::noXSS($fs->version); ?></span> <?= eL('latestversion') ?> <span class="good"><?php echo Filters::noXSS($_SESSION['latest_version']); ?></span>.
+ <a href="?hideupdatemsg=yep"><?= eL('hidemessage') ?></a>
+</div>
+<?php endif; ?>
+
+<?php if (!($user->isAnon() && (count($fs->projects) == 0 || ($proj->id >0 && !$user->can_view_project($proj->id)))) ): ?>
+<?php $filter = false; if($proj->id > 0) { $filter = true; $fields = explode( ' ', $proj->prefs['visible_fields'] );} ?>
+<form id="search" action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+ <button id="searchthisproject" type="submit"><?= eL('searchthisproject') ?></button>
+ <input class="text" id="searchtext" name="string" type="text" size="20" placeholder=" "
+ maxlength="100" value="<?php echo Filters::noXSS(Get::val('string')); ?>" accesskey="q"/>
+ <input type="hidden" name="project" value="<?php echo Filters::noXSS(Get::num('project', $proj->id)); ?>"/>
+ <input type="hidden" name="do" value="index"/>
+ <button type="submit" name="export_list" value="1" id="exporttasklist" title="<?= eL('exporttasklist') ?>"><i class="fa fa-download"></i></button>
+<style>
+#sc2,#s_searchstate{display:none;}
+#searchstateactions{color:#999;display:block;cursor:pointer;}
+#s_searchstate:checked ~ #sc2 {display:block;}
+#s_searchstate ~ label::before { content: "\25bc";}
+#s_searchstate:checked ~ label::before { content: "\25b2";}
+</style>
+<input id="s_searchstate" type="checkbox" name="advancedsearch"<?php if(Req::val('advancedsearch')): ?> checked="checked"<?php endif; ?>/>
+<label id="searchstateactions" for="s_searchstate"><?= eL('advanced') ?></label>
+<div id="sc2" class="switchcontent">
+<?php if (!$user->isAnon()): ?>
+<fieldset>
+ <div class="save_search"><label for="save_search" id="lblsaveas"><?= eL('saveas') ?></label>
+ <input class="text" type="text" value="<?php echo Filters::noXSS(Get::val('search_name')); ?>" id="save_search" name="search_name" size="15"/> <button onclick="savesearch('<?php echo Filters::escapeqs($_SERVER['QUERY_STRING']); ?>', '<?php echo Filters::noJsXSS($baseurl); ?>', '<?= eL('saving') ?>', '<?php echo Filters::noJsXSS($_SESSION['csrftoken']); ?>')" type="button"><?= eL('OK') ?></button>
+ </div>
+</fieldset>
+<?php endif; ?>
+<fieldset class="advsearch_misc">
+ <legend><?= eL('miscellaneous') ?></legend>
+ <?php echo tpl_checkbox('search_in_comments', Get::has('search_in_comments'), 'sic'); ?>
+ <label class="left" for="sic"><?= eL('searchcomments') ?></label>
+
+ <?php echo tpl_checkbox('search_in_details', Get::has('search_in_details'), 'search_in_details'); ?>
+ <label class="left" for="search_in_details"><?= eL('searchindetails') ?></label>
+
+ <?php echo tpl_checkbox('search_for_all', Get::has('search_for_all'), 'sfa'); ?>
+ <label class="left" for="sfa"><?= eL('searchforall') ?></label>
+
+ <?php echo tpl_checkbox('only_watched', Get::has('only_watched'), 'only_watched'); ?>
+ <label class="left" for="only_watched"><?= eL('taskswatched') ?></label>
+
+ <?php echo tpl_checkbox('only_primary', Get::has('only_primary'), 'only_primary'); ?>
+ <label class="left" for="only_primary"><?= eL('onlyprimary') ?></label>
+
+ <?php echo tpl_checkbox('only_blocker', Get::has('only_blocker'), 'only_blocker'); ?>
+ <label class="left" for="only_blocker" id="blockerlabel"><?= eL('onlyblocker') ?></label>
+ <span id="blockerornoblocker"><?= eL('blockerornoblocker') ?></span>
+ <style>
+ #blockerornoblocker {display:none;color:#c00;}
+ #only_primary:checked ~ #only_blocker:checked ~ #blockerornoblocker {display:inline;}
+ </style>
+
+ <?php echo tpl_checkbox('has_attachment', Get::has('has_attachment'), 'has_attachment'); ?>
+ <label class="left" for="has_attachment"><?= eL('hasattachment') ?></label>
+
+ <?php echo tpl_checkbox('hide_subtasks', Get::has('hide_subtasks'), 'hide_subtasks'); ?>
+ <label class="left" for="hide_subtasks"><?= eL('hidesubtasks') ?></label>
+ </fieldset>
+
+ <fieldset class="advsearch_task">
+ <legend><?= eL('taskproperties') ?></legend>
+ <!-- Task Type -->
+ <?php if (!$filter || in_array('tasktype', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="type"><?= eL('tasktype') ?></label>
+ <select name="type[]" id="type" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('alltasktypes')) + $proj->listTaskTypes(), Get::val('type', '')); ?>
+ </select>
+ </div>
+
+ <!-- Severity -->
+ <?php if (!$filter || in_array('severity', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="sev"><?= eL('severity') ?></label>
+ <select name="sev[]" id="sev" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('allseverities')) + $fs->severities, Get::val('sev', '')); ?>
+ </select>
+ </div>
+
+ <!-- Priority -->
+ <?php if (!$filter || in_array('priority', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="pri"><?= eL('priority') ?></label>
+ <select name="pri[]" id="pri" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('allpriorities')) + $fs->priorities, Get::val('pri', '')); ?>
+ </select>
+ </div>
+
+ <!-- Due Version -->
+ <?php if (!$filter || in_array('dueversion', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="due"><?= eL('dueversion') ?></label>
+ <select name="due[]" id="due" multiple="multiple" size="8">
+ <?php echo tpl_options(array_merge(array('' => L('dueanyversion'), 0 => L('unassigned')), $proj->listVersions(false)), Get::val('due', '')); ?>
+ </select>
+ </div>
+
+ <!-- Reportedin -->
+ <?php if (!$filter || in_array('reportedin', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="reported"><?= eL('reportedversion') ?></label>
+ <select name="reported[]" id="reported" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('anyversion')) + $proj->listVersions(false), Get::val('reported', '')); ?>
+ </select>
+ </div>
+
+ <!-- Category -->
+ <?php if (!$filter || in_array('category', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="cat"><?= eL('category') ?></label>
+ <select name="cat[]" id="cat" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('allcategories')) + $proj->listCategories(), Get::val('cat', '')); ?>
+ </select>
+ </div>
+
+ <!-- Status -->
+ <?php if (!$filter || in_array('status', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="status"><?= eL('status') ?></label>
+ <select name="status[]" id="status" multiple="multiple" size="8">
+ <?php echo tpl_options(array('' => L('allstatuses')) +
+ array('open' => L('allopentasks')) +
+ array('closed' => L('allclosedtasks')) +
+ $proj->listTaskStatuses(), Get::val('status', 'open')); ?>
+ </select>
+ </div>
+
+ <!-- Progress -->
+ <?php if (!$filter || in_array('progress', $fields)) { ?>
+ <div class="search_select">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <label class="default multisel" for="percent"><?= eL('percentcomplete') ?></label>
+ <!-- legacy: tpl_options()
+ <select name="percent[]" id="percent" multiple="multiple" size="12">
+ <?php $percentages = array(); for ($i = 0; $i <= 100; $i += 10) $percentages[$i] = $i; ?>
+ <?php echo tpl_options(array('' => L('anyprogress')) + $percentages, Get::val('percent', '')); ?>
+ </select>
+ -->
+<?php
+# new: use of tpl_select() which provides much more control
+# maybe move some of the php code from here to scripts/index.php ...
+$selected=Get::val('percent', '');
+$selected = is_array($selected) ? $selected : (array) $selected;
+$percentages = array();
+$percentages[]=array('value'=>'', 'label'=>L('anyprogress') );
+if(in_array('', $selected, true)){
+ $percentages[0]['attr']['selected']='selected';
+}
+for($i = 0; $i <= 100; $i += 10){
+ $opt = array();
+ $opt['value'] = $i;
+ $opt['label'] = $i;
+ # goes to theme.css ..
+ # styling of html select options probably works only in a few browsers (at least firefox), but where it works it can be an added value.
+ $opt['attr']=array('style'=>'background:linear-gradient(90deg,#0c0 0%,#0c0 '.$i.'%, #fff '.$i.'%, #fff 100%)');
+ $opt['attr']=array('class'=>'percent'.$i);
+ if(in_array("$i", $selected)){
+ $opt['attr']['selected']='selected';
+ }
+ $percentages[]=$opt;
+}
+echo tpl_select(
+ array(
+ 'name'=>'percent[]',
+ 'attr'=>array(
+ 'id'=>'percent',
+ 'multiple'=>'multiple',
+ 'size'=>12
+ ),
+ 'options'=>$percentages
+ )
+);
+?>
+ </div>
+ <div class="clear"></div>
+ </fieldset>
+
+ <fieldset class="advsearch_users">
+ <legend><?= eL('users') ?></legend>
+ <label class="default multisel" for="opened"><?= eL('openedby') ?></label>
+ <?php echo tpl_userselect('opened', Get::val('opened'), 'opened', array('placeholder'=>' ')); ?>
+
+ <?php if (!$filter || in_array('assignedto', $fields)) { ?>
+ <label class="default multisel" for="dev"><?= eL('assignedto') ?></label>
+ <?php echo tpl_userselect('dev', Get::val('dev'), 'dev', array('placeholder'=>' ')); } ?>
+ <label class="default multisel" for="closed"><?= eL('closedby') ?></label>
+ <?php echo tpl_userselect('closed', Get::val('closed'), 'closed', array('placeholder'=>' ')); ?>
+ </fieldset>
+
+ <fieldset class="advsearch_dates">
+ <legend><?= eL('dates') ?></legend>
+ <!-- Due Date -->
+ <?php if (!$filter || in_array('duedate', $fields)) { ?>
+ <div class="dateselect">
+ <?php } else { ?>
+ <div style="display:none">
+ <?php } ?>
+ <?php echo tpl_datepicker('duedatefrom', L('selectduedatefrom')); ?>
+ <?php echo tpl_datepicker('duedateto', L('selectduedateto')); ?>
+ </div>
+ <div class="dateselect">
+ <?php echo tpl_datepicker('changedfrom', L('selectsincedatefrom')); ?>
+ <?php echo tpl_datepicker('changedto', L('selectsincedateto')); ?>
+ </div>
+ <div class="dateselect">
+ <?php echo tpl_datepicker('openedfrom', L('selectopenedfrom')); ?>
+ <?php echo tpl_datepicker('openedto', L('selectopenedto')); ?>
+ </div>
+ <div class="dateselect">
+ <?php echo tpl_datepicker('closedfrom', L('selectclosedfrom')); ?>
+ <?php echo tpl_datepicker('closedto', L('selectclosedto')); ?>
+ </div>
+ </fieldset>
+ </div>
+</form>
+<?php endif; ?>
+<?php if (isset($_GET['string']) || $total): ?>
+<div id="tasklist">
+<?php echo tpl_form(Filters::noXSS(createURL('project', $proj->id, null, $_GET)),'massops',null,null,'id="massops"'); ?>
+<div>
+<script type="text/javascript">
+ var cX = 0; var cY = 0; var rX = 0; var rY = 0;
+ function UpdateCursorPosition(e){ cX = e.pageX; cY = e.pageY;}
+ function UpdateCursorPositionDocAll(e){ cX = e.clientX; cY = e.clientY;}
+ if(document.all) { document.onmousemove = UpdateCursorPositionDocAll; }
+ else { document.onmousemove = UpdateCursorPosition; }
+ function AssignPosition(d) {
+ if (self.pageYOffset) {
+ rX = self.pageXOffset;
+ rY = self.pageYOffset;
+ } else if(document.documentElement && document.documentElement.scrollTop) {
+ rX = document.documentElement.scrollLeft;
+ rY = document.documentElement.scrollTop;
+ } else if(document.body) {
+ rX = document.body.scrollLeft;
+ rY = document.body.scrollTop;
+ }
+ if (document.all) {
+ cX += rX;
+ cY += rY;
+ }
+ d.style.left = (cX+10) + "px";
+ d.style.top = (cY+10) + "px";
+ }
+ function Show(elem, id) {
+ if(cY == 0) return;
+ var div = document.getElementById("desc_"+id);
+ AssignPosition(div);
+ div.style.display = "block";
+ }
+ function Hide(elem, id) {
+ document.getElementById("desc_"+id).style.display = "none";
+ }
+</script>
+<table id="tasklist_table">
+<colgroup>
+ <col class="caret" />
+ <?php if (!$user->isAnon() && $proj->id !=0 && $total): ?><col class="toggle" /><?php endif; ?>
+ <?php foreach ($visible as $col): ?>
+ <col class="<?php echo $col; ?>" />
+ <?php endforeach; ?>
+</colgroup>
+<thead>
+<tr>
+ <th class="caret"></th>
+ <?php if (!$user->isAnon() && $proj->id !=0 && $total): ?>
+ <th class="ttcolumn"><a title="<?= eL('toggleselected') ?>" href="javascript:ToggleSelected('massops')" onclick="massSelectBulkEditCheck();"></a></th>
+ <?php
+ endif;
+ foreach ($visible as $col):
+ echo tpl_list_heading($col, "<th%s>%s</th>");
+ endforeach;
+ ?>
+</tr>
+</thead>
+<tbody>
+<?php foreach ($tasks as $task):?>
+<tr id="task<?php echo $task['task_id']; ?>" class="severity<?php echo $task['task_severity']; echo $task['is_closed'] ==1 ? ' closed': '';?>">
+ <td class="caret"></td>
+ <?php if (!$user->isAnon() && $proj->id !=0): ?>
+ <td class="ttcolumn"><input class="ticktask" type="checkbox" name="ids[]" onclick="BulkEditCheck()" value="<?php echo $task['task_id']; ?>"/></td>
+ <?php
+ endif;
+ foreach ($visible as $col):
+ if($col == 'progress'):?>
+ <td class="task_progress"><div class="progress_bar_container"><span><?php echo $task['percent_complete']; ?>%</span><div class="progress_bar" style="width:<?php echo $task['percent_complete']; ?>%"></div></div></td>
+ <?php elseif ($col == 'summary'):
+ echo tpl_draw_cell($task, $col, "<td class='%s' onmouseover=\"Show(this," . $task['task_id'] . ")\" onmouseout=\"Hide(this, " . $task['task_id'] . ")\">%s</td>");
+ else:
+ echo tpl_draw_cell($task, $col);
+ endif;
+ endforeach;
+ ?>
+ <td id="desc_<?php echo $task['task_id']; ?>" class="descbox box">
+ <b><?php echo L('taskdescription'); ?></b>
+ <?php echo $task['detailed_desc'] ? TextFormatter::render($task['detailed_desc'], 'task', $task['task_id'], $task['desccache']) : '<p>'.L('notaskdescription').'</p>'; ?>
+ </td>
+</tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+<table id="pagenumbers">
+<tr>
+<?php if ($total): ?>
+ <td id="taskrange"><?php echo sprintf(L('taskrange'), $offset + 1, ($offset + $perpage > $total ? $total : $offset + $perpage), $total); ?></td>
+ <td id="numbers"><?php echo pagenums($pagenum, $perpage, $total); ?></td>
+<?php else: ?>
+ <td id="taskrange"><strong><?= eL('noresults') ?></strong></td>
+<?php endif; ?>
+</tr>
+</table>
+
+<!-- Bulk editing Tasks -->
+<?php if (!$user->isAnon() && $proj->id !=0 && $total): ?>
+<!-- Grab fields wanted for this project so we only show those specified in the settings -->
+<div id="bulk_edit_selectedItems" style="display:none">
+ <fieldset>
+ <legend><b><?= eL('updateselectedtasks') ?></b></legend>
+ <ul class="form_elements slim">
+ <input type="hidden" name="action" value="task.bulkupdate" />
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($user->id); ?>"/>
+ <!-- Quick Actions -->
+ <li>
+ <label for="bulk_quick_action"><?= eL('quickaction') ?></label>
+ <select name="bulk_quick_action" id="bulk_quick_action">
+ <option value="0"><?= eL('notspecified') ?></option>
+ <option value="bulk_start_watching"><?= eL('watchtasks') ?></option>
+ <option value="bulk_stop_watching"><?= eL('stopwatchingtasks') ?></option>
+ <option value="bulk_take_ownership"><?= eL('assigntaskstome') ?></option>
+ </select>
+ </li>
+ <!-- Status -->
+ <?php if (in_array('status', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+
+ <label for="bulk_status"><?= eL('status') ?></label>
+ <select id="bulk_status" name="bulk_status">
+ <?php $statusList = $proj->listTaskStatuses(); ?>
+ <?php array_unshift($statusList,L('notspecified')); ?>
+ <?php echo tpl_options($statusList); ?>
+
+ </select>
+ </li>
+
+ <!-- Progress -->
+ <?php if (in_array('progress', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="bulk_percent"><?= eL('percentcomplete') ?></label>
+ <select id="bulk_percent" name="bulk_percent_complete">
+ <?php $percentCompleteList = array();$percentCompleteList[0]=L('notspecified'); for ($i = 1; $i<=101; $i+=10) $percentCompleteList[$i-1] =''.($i-1).'%'; ?>
+ <?php echo tpl_options($percentCompleteList); ?>
+
+ </select>
+ </li>
+
+ <!-- Task Type-->
+ <?php if (in_array('tasktype', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $taskTypeList = $proj->listTaskTypes(); ?>
+ <?php array_unshift($taskTypeList,L('notspecified')); ?>
+ <label for="bulk_tasktype"><?= eL('tasktype') ?></label>
+ <select id="bulk_tasktype" name="bulk_task_type">
+ <?php echo tpl_options($taskTypeList); ?>
+ </select>
+
+ </li>
+
+ <!-- Category -->
+ <?php if (in_array('category', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $categoryTypeList = $proj->listCategories(); ?>
+ <?php array_unshift($categoryTypeList,L('notspecified')); ?>
+ <label for="bulk_category"><?php echo Filters::noXSS(L('category')); ?></label>
+ <select id="bulk_category" name="bulk_category">
+ <?php echo tpl_options($categoryTypeList); ?>
+ </select>
+
+ </li>
+
+ <!-- Assigned To-->
+ <li>
+ <?php if ($user->perms('edit_assignments')): ?>
+ <label for="bulk_assignment"><?= eL('assignedto') ?></label>
+ <?php
+ //insert a noone into the list in order to bulk de-assign tasks
+ $noone[0]=array(0,L('noone'));
+ array_unshift($userlist, $noone);
+ ?>
+ <select size="8" style="height: 200px;" name="bulk_assignment[]" id="bulk_assignment" multiple>
+ <?php foreach ($userlist as $group => $users): ?>
+ <optgroup <?php if($group == '0'){ ?> label='<?= eL('pleaseselect') ?> ... ' <?php } else { ?> label='<?php echo Filters::noXSS($group); ?>' <?php } ?> >
+ <?php foreach ($users as $info): ?>
+ <option value="<?php echo Filters::noXSS($info[0]); ?>"><?php echo Filters::noXSS($info[1]); ?></option>
+ <?php endforeach; ?>
+ </optgroup>
+ <?php endforeach; ?>
+ </select>
+ <?php endif; ?>
+ </li>
+
+ <!-- OS -->
+ <?php if (in_array('os', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $osTypeList = $proj->listOs(); ?>
+ <?php array_unshift($osTypeList,L('notspecified')); ?>
+ <label for="bulk_os"><?= eL('operatingsystem') ?></label>
+ <select id="bulk_os" name="bulk_os">
+ <?php echo tpl_options($osTypeList); ?>
+ </select>
+ </li>
+
+ <!-- Severity -->
+ <?php if (in_array('severity', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $severityTypeList = array_reverse($fs->severities); ?>
+ <?php array_unshift($severityTypeList,L('notspecified')); ?>
+ <label for="bulk_severity"><?= eL('severity') ?></label>
+ <select id="bulk_severity" name="bulk_severity">
+ <?php echo tpl_options($severityTypeList); ?>
+ </select>
+ </li>
+
+ <!-- Priority -->
+ <?php if (in_array('priority', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+
+ <?php $priorityTypeList = array_reverse($fs->priorities); ?>
+ <?php array_unshift($priorityTypeList,L('notspecified')); ?>
+ <label for="bulk_priority"><?= eL('priority') ?></label>
+ <select id="bulk_priority" name="bulk_priority">
+ <?php echo tpl_options($priorityTypeList); ?>
+ </select>
+ </li>
+
+ <!-- Reported In -->
+ <?php if (in_array('reportedin', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $reportedVerList = $proj->listVersions(); ?>
+ <?php array_unshift($reportedVerList,L('notspecified')); ?>
+ <label for="bulk_reportedver"><?= eL('reportedversion') ?></label>
+ <select id="bulk_reportedver" name="bulk_reportedver">
+ <?php echo tpl_options($reportedVerList); ?>
+ </select>
+ </li>
+
+ <!-- Due -->
+ <?php if (in_array('dueversion', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $dueInVerList = $proj->listVersions(); ?>
+ <?php array_unshift($dueInVerList,L('undecided')); ?>
+ <?php array_unshift($dueInVerList,L('notspecified')); ?>
+ <label for="bulk_dueversion"><?= eL('dueinversion') ?></label>
+ <select id="bulk_dueversion" name="bulk_due_version">
+ <?php echo tpl_options($dueInVerList); ?>
+ </select>
+ </li>
+
+ <!-- Due Date -->
+ <?php if (in_array('duedate', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="bulk_due_date"><?= eL('duedate') ?></label>
+ <?php echo tpl_datepicker('bulk_due_date'); ?>
+ </li>
+
+ <!-- Projects -->
+ <!-- If there is only one choice of project, then don't bother showing it -->
+ <?php if (count($fs->projects) > 1) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <?php $projectsList = $fs->listProjects(); ?>
+ <?php array_unshift($projectsList,L('notspecified')); ?>
+ <label for="bulk_projects"><?= eL('attachedtoproject') ?></label>
+ <select id="bulk_projects" name="bulk_projects">
+ <?php echo tpl_options($projectsList); ?>
+ </select>
+ </li>
+ </ul>
+ <button type="submit" name="updateselectedtasks" value="true"><?= eL('updateselectedtasks') ?></button>
+ </fieldset>
+ <fieldset>
+ <legend><b><?php echo L('closeselectedtasks'); ?></b></legend>
+ <div>
+ <select class="adminlist" name="resolution_reason" onmouseup="event.stopPropagation();">
+ <option value="0"><?= eL('selectareason') ?></option>
+ <?php echo tpl_options($proj->listResolutions(), Req::val('resolution_reason')); ?>
+ </select>
+ <button type="submit" name="updateselectedtasks" value="false"><?php echo L('closetasks'); ?></button>
+ <br/>
+ <label class="default text" for="closure_comment"><?= eL('closurecomment') ?></label>
+ <textarea class="text" id="closure_comment" name="closure_comment" rows="3"
+ cols="25"><?php echo Filters::noXSS(Req::val('closure_comment')); ?></textarea>
+ <label><?php echo tpl_checkbox('mark100', Req::val('mark100', !(Req::val('action') == 'details.close'))); ?>&nbsp;&nbsp;<?php echo Filters::noXSS(L('mark100')); ?></label>
+ </div>
+ </fieldset>
+
+</div>
+<?php endif; /* !$user->isAnon() && $proj-> !=0 && $total */ ?>
+</div>
+</form>
+</div>
+<?php endif; /* isset($_GET['string'] || $total */ ?>
diff --git a/themes/CleanFS/templates/links.searches.tpl b/themes/CleanFS/templates/links.searches.tpl
new file mode 100644
index 0000000..9347cd4
--- /dev/null
+++ b/themes/CleanFS/templates/links.searches.tpl
@@ -0,0 +1,15 @@
+ <strong id="nosearches" <?php if(count($user->searches)): ?>class="hide"<?php endif; ?>><?php echo Filters::noXSS(L('nosearches')); ?></strong>
+ <?php if(count($user->searches)): ?>
+ <input type="hidden" name="csrftoken" id="deletesearchtoken" value="<?php echo $_SESSION['csrftoken']; ?>">
+ <table id="mysearchestable">
+ <?php foreach ($user->searches as $search): ?>
+ <tr id="rs<?php echo Filters::noXSS($search['id']); ?>" <?php if($search == end($user->searches)): ?>class="last"<?php endif; ?>>
+ <td><a href="<?php echo Filters::noXSS($baseurl); ?>?do=index&amp;<?php echo http_build_query(unserialize($search['search_string']), '', '&amp;'); ?>"><?php echo Filters::noXSS($search['name']); ?></a></td>
+ <td class="searches_delete">
+ <a href="javascript:deletesearch('<?php echo Filters::noXSS($search['id']); ?>','<?php echo Filters::noJsXSS($baseurl); ?>')">
+ <i title="<?php echo Filters::noXSS(L('delete')); ?>" class="fa fa-trash fa-lg"></i></a>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </table>
+ <?php endif; ?>
diff --git a/themes/CleanFS/templates/links.tpl b/themes/CleanFS/templates/links.tpl
new file mode 100644
index 0000000..f793a2b
--- /dev/null
+++ b/themes/CleanFS/templates/links.tpl
@@ -0,0 +1,148 @@
+<input id="menu1" type="checkbox">
+<label id="labelmenu1" for="menu1"></label>
+<div id="menu"><ul id="menu-list"><?php
+if ($user->isAnon()):
+ # 20150211 peterdd: pure css toggle using checked status, no js needed
+ ?><li class="first">
+ <input type="checkbox" id="s_loginbox" />
+ <label for="s_loginbox" id="show_loginbox" accesskey="l"><?php echo Filters::noXSS(L('login')); ?></label>
+ <div id="loginbox" class="popup"><?php $this->display('loginbox.tpl'); ?></div>
+ </li><?php
+else: ?><li>
+ <a id="profilelink" <?php if($do == 'myprofile'): ?> class="active"<?php endif; ?> href="<?php echo Filters::noXSS(CreateURL('myprofile')); ?>" title="<?php echo Filters::noXSS(L('editmydetails')); ?> <?php echo Filters::noXSS($user->infos['real_name']); ?> (<?php echo Filters::noXSS($user->infos['user_name']); ?>)"><i class="fa fa-user fa-lg"></i></a>
+ </li><li>
+ <a id="lastsearchlink" href="#" accesskey="m" onclick="showhidestuff('mysearches');return false;" class="inactive"><?php echo Filters::noXSS(L('mysearch')); ?></a>
+ <div id="mysearches"><?php $this->display('links.searches.tpl'); ?></div>
+ </li><?php
+ if ($user->perms('is_admin')):
+ ?><li>
+ <a id="optionslink"<?php if ($do=='admin'): ?> class="active"<?php endif; ?> href="<?php echo Filters::noXSS(CreateURL('admin', 'prefs')); ?>" title="<?php echo Filters::noXSS(L('admintoolbox')); ?>"><i class="fa fa-gears fa-lg"></i></a>
+ </li><?php
+ endif;
+ ?><li>
+ <a id="logoutlink" href="<?php echo Filters::noXSS(CreateURL('logout', null)); ?>"
+ accesskey="l" title="<?php echo Filters::noXSS(L('logout')); ?>"><i class="fa fa-power-off fa-lg"></i></a>
+ </li><?php
+ if (isset($_SESSION['was_locked'])):
+ ?><li>
+ <span id="locked"><?php echo Filters::noXSS(L('accountwaslocked')); ?></span>
+ </li><?php
+ elseif (isset($_SESSION['login_attempts']) && $_SESSION['login_attempts'] > 0):
+ ?><li>
+ <span id="locked"><?php echo Filters::noXSS(sprintf(L('failedattempts'), $_SESSION['login_attempts'])); ?></span>
+ </li><?php
+ endif;
+ unset($_SESSION['login_attempts'], $_SESSION['was_locked']);
+
+endif; ?>
+</ul>
+</div><div id="pm-menu">
+ <input id="pmmenu" type="checkbox">
+ <label id="labelpmmenu" for="pmmenu"></label>
+ <ul id="pm-menu-list"><?php
+ if ( count($fs->projects) && $user->can_select_project($proj->id) ) {
+ ?><li class="first">
+ <a id="toplevellink"
+ <?php if($do == 'toplevel'): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('toplevel', $proj->id)); ?>"><?php echo Filters::noXSS(L('overview')); ?></a>
+ </li><?php
+ }
+ if( (!$user->isAnon() && $user->perms('view_tasks')) || ($user->isAnon() && $proj->id >0 && $proj->prefs['others_view'])):
+ ?><li>
+ <a id="homelink"
+ <?php if($do == 'index' && !(isset($_GET['dev']) && !$user->isAnon() && $_GET['dev'] == $user->id)): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('tasklist', $proj->id)); ?>"><?php echo Filters::noXSS(L('tasklist')); ?></a>
+ </li><?php
+ endif;
+ if($proj->id && $user->perms('open_new_tasks')):
+ ?><li>
+ <a id="newtasklink" href="<?php echo Filters::noXSS(CreateURL('newtask', $proj->id)); ?>"
+ <?php if($do == 'newtask'): ?> class="active" <?php endif; ?>
+ accesskey="a"><?php echo Filters::noXSS(L('addnewtask')); ?></a>
+ </li><?php
+ if($proj->id && $user->perms('add_multiple_tasks')) :
+ ?><li>
+ <a id="newmultitaskslink" href="<?php echo Filters::noXSS(CreateURL('newmultitasks', $proj->id)); ?>"
+ <?php if($do == 'newmultitasks'): ?> class="active"<?php endif; ?>><?php echo Filters::noXSS(L('addmultipletasks')); ?></a>
+ </li><?php
+ endif;
+ elseif ($proj->id && $user->isAnon() && $proj->prefs['anon_open'] && $proj->prefs['project_is_active']): ?><li>
+ <a id="anonopen"
+ <?php if($do == 'newtask'): ?> class="active" <?php endif; ?>
+ href="?do=newtask&amp;project=<?php echo Filters::noXSS($proj->id); ?>"><?php echo Filters::noXSS(L('opentaskanon')); ?></a>
+ </li><?php
+ endif;
+ if(!$user->isAnon()): ?><li>
+ <a id="mytaskslink"
+ <?php if($do == 'index' && isset($_GET['dev']) && $_GET['dev'] == $user->id): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('mytasks', $proj->id, $user->id, null)); ?>"><?php echo Filters::noXSS(L('myassignedtasks')); ?></a>
+ </li><?php
+ endif;
+ if($user->perms('view_reports')): ?><li>
+ <a id="reportslink"
+ <?php if( $do == 'reports'): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('reports', $proj->id)); ?>"><?php echo Filters::noXSS(L('reports')); ?></a>
+ </li><?php
+ endif;
+ if($proj->id && ($user->perms('view_roadmap') || ($user->isAnon() && $proj->prefs['others_viewroadmap'])) ): ?><li>
+ <a id="roadmaplink"
+ <?php if($do == 'roadmap'): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('roadmap', $proj->id)); ?>"><?php echo Filters::noXSS(L('roadmap')); ?></a>
+ </li><?php
+ endif;
+ if(file_exists(BASEDIR . '/scripts/gantt.php') && $proj->id && $user->perms('view_roadmap')): ?><li>
+ <a id="gantt"
+ <?php if($do == 'gantt'): ?> class="active" <?php endif; ?>
+ href="<?php echo Filters::noXSS(CreateURL('gantt', $proj->id)); ?>" title="Gantt chart"><i class="fa fa-tasks fa-lg"></i></a>
+ </li><?php
+ endif;
+ if ($proj->id && $user->perms('manage_project')): ?><li>
+ <a id="projectslink"<?php if($do=='pm'): ?> class="active"<?php endif; ?> href="<?php echo Filters::noXSS(CreateURL('pm', 'prefs', $proj->id)); ?>"><?php echo Filters::noXSS(L('manageproject')); ?></a>
+ </li><?php
+ endif;
+ if ($proj->id && isset($pm_pendingreq_num) && $pm_pendingreq_num):
+ ?><li>
+ <a class="pendingreq attention"
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'pendingreq', $proj->id)); ?>"><?php echo Filters::noXSS($pm_pendingreq_num); ?> <?php echo Filters::noXSS(L('pendingreq')); ?></a>
+ </li><?php
+ endif;
+ if ($user->perms('is_admin') && isset($admin_pendingreq_num) && $admin_pendingreq_num):
+ ?><li>
+ <a class="pendingreq attention"
+ href="<?php echo Filters::noXSS(CreateURL('admin', 'userrequest')); ?>"><?php echo Filters::noXSS($admin_pendingreq_num); ?> <?php echo Filters::noXSS(L('adminrequestswaiting')); ?></a>
+ </li><?php
+ endif; ?>
+ </ul>
+ <div id="pmcontrol">
+ <div id="projectselector"><?php
+ # $fs->projects is filtered with can_select_project() for the current user/guest in index.php
+ if(count($fs->projects)>0): ?>
+ <form id="projectselectorform" action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+ <select name="project" onchange="document.getElementById('projectselectorform').submit()">
+ <?php echo tpl_options(array_merge(array(0 => L('allprojects')), $fs->projects), $proj->id); ?>
+ </select>
+ <noscript><button type="submit"><?php echo Filters::noXSS(L('switch')); ?></button></noscript>
+ <input type="hidden" name="do" value="<?php echo Filters::noXSS($do); ?>" />
+ <input type="hidden" value="1" name="switch" />
+ <?php $check = array('area', 'id');
+ if ($do == 'reports') {
+ $check = array_merge($check, array('open', 'close', 'edit', 'assign', 'repdate', 'comments', 'attachments',
+ 'related', 'notifications', 'reminders', 'within', 'duein', 'fromdate', 'todate'));
+ }
+ foreach ($check as $key):
+ if (Get::has($key)): ?>
+ <input type="hidden" name="<?php echo Filters::noXSS($key); ?>" value="<?php echo Filters::noXSS(Get::val($key)); ?>" />
+ <?php endif;
+ endforeach; ?>
+ </form>
+ <?php endif; ?></div>
+ <div id="showtask"><?php
+ # $fs->projects is filtered with can_select_project() for the current user/guest in index.php
+ if(count($fs->projects)>0): ?>
+ <form action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+ <noscript><button type="submit"><?php echo Filters::noXSS(L('showtask')); ?> #</button></noscript>
+ <input id="task_id" name="show_task" class="text" type="text" size="10" accesskey="t" placeholder="<?php echo Filters::noXSS(L('showtask')); ?> #" />
+ </form>
+ <?php endif; ?></div>
+ </div>
+</div>
diff --git a/themes/CleanFS/templates/loginbox.tpl b/themes/CleanFS/templates/loginbox.tpl
new file mode 100644
index 0000000..e6df4fb
--- /dev/null
+++ b/themes/CleanFS/templates/loginbox.tpl
@@ -0,0 +1,45 @@
+<form id="login" action="<?php echo Filters::noXSS($baseurl); ?>index.php?do=authenticate" method="post">
+<div id="login_input">
+ <input placeholder="<?php echo Filters::noXSS(L('username')); ?>" class="text" type="text" id="lbl_user_name" name="user_name" size="17" maxlength="32" />
+ <input placeholder="<?php echo Filters::noXSS(L('password')); ?>" class="password" type="password" id="lbl_password" name="password" size="17" maxlength="100" />
+ <label for="lbl_remember"><?php echo Filters::noXSS(L('rememberme')); ?></label>
+ <input type="checkbox" id="lbl_remember" name="remember_login" />
+ <input type="hidden" name="return_to" value="<?php echo Filters::noXSS($_SERVER['REQUEST_URI']); ?>" />
+ <input type="submit" value="<?php echo Filters::noXSS(L('login')); ?>" name="login" id="login_button" />
+</div>
+<div id="login_links">
+ <?php $activeclass = ' class="active" '; ?>
+ <?php if ($user->isAnon() && $fs->prefs['anon_reg'] && !$fs->prefs['only_oauth_reg']): ?>
+ <a id="registerlink"
+ <?php if(isset($_GET['do']) and $_GET['do'] == 'register') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('register','')); ?>"><?php echo Filters::noXSS(L('register')); ?></a>
+ <?php endif; ?>
+ <?php if ($user->isAnon() && !$fs->prefs['disable_lostpw']): ?>
+ <?php if ($user->isAnon() && $fs->prefs['user_notify']): ?>
+ <a id="forgotlink"
+ <?php if(isset($_GET['do']) and $_GET['do'] == 'lostpw') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('lostpw','')); ?>"><?php echo Filters::noXSS(L('lostpassword')); ?></a>
+ <?php else: ?>
+ <a id="forgotlink" href="mailto:<?php echo Filters::noXSS(implode(',', $admin_emails)); ?>?subject=<?php echo Filters::noXSS(rawurlencode(L('lostpwforfs'))); ?>&amp;body=<?php echo Filters::noXSS(rawurlencode(L('lostpwmsg1'))); ?><?php echo Filters::noXSS($baseurl); ?><?php echo Filters::noXSS(rawurlencode(L('lostpwmsg2'))); ?><?php
+ if(isset($_SESSION['failed_login'])):
+ ?><?php echo Filters::noXSS(rawurlencode($_SESSION['failed_login'])); ?><?php
+ else:
+ ?>&lt;<?php echo Filters::noXSS(rawurlencode(L('yourusername'))); ?>&gt;<?php
+ endif;
+ ?><?php echo Filters::noXSS(rawurlencode(L('regards'))); ?>"><?php echo Filters::noXSS(L('lostpassword')); ?></a>
+ <script type="text/javascript">var link = document.getElementById('forgotlink');link.href=link.href.replace(/#/g,"@");</script>
+ <?php endif; ?>
+ <?php endif; ?>
+</div>
+<?php $return = '&return_to=' . base64_encode($_SERVER['REQUEST_URI']);?>
+<div id="login_oauth">
+ <?php
+ if ($fs->prefs['active_oauths']):
+ $providers = explode(' ', $fs->prefs['active_oauths']);
+ foreach($providers as $provider): ?>
+ <a class="btn-<?php echo $provider; ?>" href="index.php?do=oauth&provider=<?php echo $provider . $return; ?>">
+ <i class="fa fa-<?php echo $provider; ?>"></i> <?php echo Filters::noXSS(sprintf(L('signinwith'), ucfirst($provider))); ?>
+ </a>
+ <?php endforeach; endif;?>
+</div>
+</form>
diff --git a/themes/CleanFS/templates/lostpw.step1.tpl b/themes/CleanFS/templates/lostpw.step1.tpl
new file mode 100644
index 0000000..cbc35a2
--- /dev/null
+++ b/themes/CleanFS/templates/lostpw.step1.tpl
@@ -0,0 +1,11 @@
+<h3><?php echo Filters::noXSS(L('lostpw')); ?></h3>
+<div class="box">
+ <p><?php echo Filters::noXSS(L('lostpwexplain')); ?></p>
+ <?php echo tpl_form(Filters::noXSS(createUrl('lostpw'))); ?>
+ <p><b><?php echo Filters::noXSS(L('username')); ?></b>
+ <input type="hidden" name="action" value="lostpw.sendmagic" />
+ <input class="text" type="text" value="<?php echo Filters::noXSS(Req::val('user_name')); ?>" name="user_name" size="20" maxlength="32" />
+ <button type="submit"><?php echo Filters::noXSS(L('sendlink')); ?></button>
+ </p>
+ </form>
+</div>
diff --git a/themes/CleanFS/templates/lostpw.step2.tpl b/themes/CleanFS/templates/lostpw.step2.tpl
new file mode 100644
index 0000000..2495747
--- /dev/null
+++ b/themes/CleanFS/templates/lostpw.step2.tpl
@@ -0,0 +1,23 @@
+<h3><?php echo Filters::noXSS(L('changepass')); ?></h3>
+<div class="box">
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('lostpw'))); ?>
+ <ul class="form_elements wide">
+ <li>
+ <label for="pass1"><?php echo Filters::noXSS(L('changepass')); ?></label>
+ <input class="password" id="pass1" type="password" value="<?php echo Filters::noXSS(Req::val('pass1')); ?>" name="pass1" size="20" />
+ </li>
+<?php if($fs->prefs['repeat_password']): ?>
+ <li>
+ <label for="pass2"><?php echo Filters::noXSS(L('confirmpass')); ?></label>
+ <input class="password" id="pass2" type="password" value="<?php echo Filters::noXSS(Req::val('pass2')); ?>" name="pass2" size="20" />
+ </li>
+ </ul>
+<?php endif;?>
+ <div>
+ <input type="hidden" name="action" value="lostpw.chpass" />
+ <input type="hidden" name="magic_url" value="<?php echo Filters::noXSS(Req::val('magic_url')); ?>" />
+ <button type="submit"><?php echo Filters::noXSS(L('savenewpass')); ?></button>
+ </div>
+ </form>
+</div>
+
diff --git a/themes/CleanFS/templates/myprofile.tpl b/themes/CleanFS/templates/myprofile.tpl
new file mode 100644
index 0000000..5118dcb
--- /dev/null
+++ b/themes/CleanFS/templates/myprofile.tpl
@@ -0,0 +1,40 @@
+<div class="box"><h3><?= eL('editmydetails') ?></h3>
+<ul class="form_elements">
+<li title="<?= eL('usernamenotchangeable') ?>">
+<label><?= eL('username') ?></label>
+<input type="text" style="border:none;background:none;width:auto;" disabled="disabled" value="<?= $user->infos['user_name'] ?>" />
+</li>
+</ul>
+<?php $this->display('common.profile.tpl'); ?>
+</div>
+<div class="box"><h3><?= L('myvotes') ?></h3>
+<?php if(count($votes)>0): ?>
+<table id="myvotes">
+<thead>
+<tr>
+<th><?= eL('project') ?></th>
+<th><?= eL('task') ?></th>
+<th><?= eL('removevote') ?></th>
+</tr>
+</thead>
+<tbody>
+<?php foreach($votes as $vote): ?>
+<tr<?php echo $vote['is_closed'] ? ' class="closed"':''; ?>>
+<td><?php echo $vote['project_title']; ?></td>
+<td><?php echo tpl_tasklink($vote); ?></td>
+<td><?php echo tpl_form(Filters::noXSS(createURL('myprofile', $vote['task_id'])));?>
+<input type="hidden" name="action" value="removevote" />
+<input type="hidden" name="task_id" value="<?php echo $vote['task_id']?>" />
+<button type="submit" title="<?php echo eL('removevote'); ?>"><span class="fa fa-trash"></span></button>
+</form></td>
+<td></td>
+</tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+<?php else:
+ echo eL('novotes');
+endif; ?>
+</div>
+<div class="box"><h3><?php echo eL('permissionsforproject').' '.$proj->prefs['project_title']; ?></h3><?php echo tpl_draw_perms($user->perms); ?></div>
+<div class="clear"></div>
diff --git a/themes/CleanFS/templates/newmultitasks.tpl b/themes/CleanFS/templates/newmultitasks.tpl
new file mode 100644
index 0000000..d8126f5
--- /dev/null
+++ b/themes/CleanFS/templates/newmultitasks.tpl
@@ -0,0 +1,261 @@
+<?php
+ if (!isset($supertask_id)) {
+ $supertask_id = 0;
+ }
+ $field_num = 3;
+?>
+<!-- Grab fields wanted for this project so we can only show those we want -->
+<?php $fields = explode( ' ', $proj->prefs['visible_fields'] ); ?>
+
+<div id="intromessage"><?php echo L('hintforbulkimport'); ?></div>
+<?php echo tpl_form(Filters::noXSS(CreateUrl('newmultitasks', $proj->id, $supertask_id))); ?>
+ <input type="hidden" name="supertask_id" value="<?php echo Filters::noXSS($supertask_id); ?>" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <input type="hidden" name="action" value="newmultitasks.newmultitasks" />
+ <button class="button" accesskey="f" type="button" onClick="Apply()"><?php echo L('applyfirstline'); ?></button>
+ <table class="list">
+ <thead>
+ <tr>
+ <th></th>
+ <?php if (in_array('tasktype', $fields)) { ?><th><?php echo Filters::noXSS(L('tasktype')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('category', $fields)) { ?><th><?php echo Filters::noXSS(L('category')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('status', $fields)) { ?><th><?php echo Filters::noXSS(L('status')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('os', $fields)) { ?><th><?php echo Filters::noXSS(L('operatingsystem')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('severity', $fields)) { ?><th><?php echo Filters::noXSS(L('severity')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('priority', $fields)) { ?><th><?php echo Filters::noXSS(L('priority')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('reportedin', $fields)) { ?><th><?php echo Filters::noXSS(L('reportedversion')); ?></th><?php $field_num++;} ?>
+ <?php if (in_array('dueversion', $fields)) { ?><th><?php echo Filters::noXSS(L('dueinversion')); ?></th><?php $field_num++;} ?>
+ <?php if ($user->perms('modify_all_tasks')): ?><?php if (in_array('assignedto', $fields)) { ?><th><?php echo Filters::noXSS(L('assignedto')); ?></th><?php $field_num++;} ?><?php endif; ?>
+ <th><?php echo Filters::noXSS(L('summary')); ?></th>
+ <th><?php echo Filters::noXSS(L('details')); ?></th>
+ </tr>
+ </thead>
+ <tbody id="table">
+ <tr id="row">
+ <td><button class="button img delete" accesskey="s" type="button" onClick="removeRow(this);return false;"></button></td>
+ <?php if (in_array('tasktype', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select name="task_type[]" id="tasktype">
+ <?php echo tpl_options($proj->listTaskTypes(), Req::val('task_type')); ?>
+
+ </select>
+ </td>
+
+ <!-- Category-->
+ <?php if (in_array('category', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select class="adminlist" name="product_category[]" id="category">
+ <?php echo tpl_options($proj->listCategories(), Req::val('product_category')); ?>
+
+ </select>
+ </td>
+
+ <!-- Status-->
+ <?php if (in_array('status', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select id="status" name="item_status[]" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <?php echo tpl_options($proj->listTaskStatuses(), Req::val('item_status', ($user->perms('modify_all_tasks') ? STATUS_NEW : STATUS_UNCONFIRMED))); ?>
+ </select>
+ </td>
+
+ <!-- OS-->
+ <?php if (in_array('os', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select id="os" name="operating_system[]">
+ <?php echo tpl_options($proj->listOs(), Req::val('operating_system')); ?>
+
+ </select>
+ </td>
+
+ <!-- Severity-->
+ <?php if (in_array('severity', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select id="severity" class="adminlist" name="task_severity[]">
+ <?php echo tpl_options($fs->severities, Req::val('task_severity', 2)); ?>
+
+ </select>
+ </td>
+
+ <!-- Priority-->
+ <?php if (in_array('priority', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select id="priority" name="task_priority[]" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <?php echo tpl_options($fs->priorities, Req::val('task_priority', 4)); ?>
+ </select>
+ </td>
+
+ <!-- Reported Version-->
+ <?php if (in_array('reportedin', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select class="adminlist" name="product_version[]" id="reportedver">
+ <?php echo tpl_options($proj->listVersions(false, 2), Req::val('product_version')); ?>
+
+ </select>
+ </td>
+
+ <!-- Due Version -->
+ <?php if (in_array('dueversion', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } ?>
+ <select id="dueversion" name="closedby_version[]" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <option value="0"><?php echo Filters::noXSS(L('undecided')); ?></option>
+ <?php echo tpl_options($proj->listVersions(false, 3),$proj->prefs['default_due_version'], false); ?>
+ </select>
+ </td>
+
+ <!-- Assigned To -->
+ <?php if ($user->perms('modify_all_tasks')){ ?><?php if (in_array('assignedto', $fields)) { ?>
+ <td>
+ <?php } else { ?>
+ <td style="display:none">
+ <?php } echo tpl_userselect('assigned_to[0]', Req::val('assigned_to[0]'), 'find_user_0'); ?>
+ </td>
+ <?php } ?>
+ <td>
+ <input type="text" class="text" size="30" id="summary" name="item_summary[]" onPaste="pasteMultiLines(this, event);return false"/>
+ </td>
+ <td>
+ <input type="text" class="text" size="20" id="details" name="detailed_desc[]" onkeydown="return TabandCreate(this, event);" onPaste="pasteMultiLines(this, event);return false"/>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="buttons" colspan="<?php echo $field_num; ?>">
+ <button class="button" accesskey="a" type="button" onClick="createRow('','')"><?php echo L('addmorerows'); ?></button>
+ <button class="button positive" accesskey="s" type="submit"><?php echo L('addtasks'); ?></button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <script type="text/javascript">
+
+ var index = 0;
+ function createRow(summary, details)
+ {
+ index++;
+ var table = document.getElementById("table");
+ var rows = table.getElementsByTagName("tr");
+ var clone = rows[0].cloneNode(true);
+
+ var tds = clone.getElementsByTagName("td");
+ var length = tds.length;
+ tds[length-2].getElementsByTagName("input")[0].value = summary;
+ tds[length-1].getElementsByTagName("input")[0].value = details;
+ tds[length-3].getElementsByTagName("input")[0].value = "";
+ tds[length-3].getElementsByTagName("script")[0].innerHTML = "";
+ var res = tds[length-3].innerHTML.replace(/assigned_to\[0\]/g, "assigned_to[" + index + "]");
+ res = res.replace(/find_user_0/g, "find_user_" + index);
+ tds[length-3].innerHTML = res;
+ table.insertBefore(clone, table.lastElementChild);
+ showstuff("assigned_to[" + index + "]_complete");
+ new Ajax.Autocompleter(tds[length-3].getElementsByTagName("input")[0].id, tds[length-3].getElementsByTagName("span")[0].id, "<?php echo Filters::noXSS($baseurl); ?>js/callbacks/usersearch.php", null);
+ }
+ function removeRow(elem)
+ {
+ var table = document.getElementById("table");
+ var rows = table.getElementsByTagName("tr");
+ var length = rows.length;
+ if(length <= 2)
+ return false;
+ for(var i = 0; i < length -1; i++)
+ {
+ if(rows[i] == elem.parentNode.parentNode) {
+ table.deleteRow(i);
+ break;
+ }
+ }
+ }
+ function pasteMultiLines(elem, e)
+ {
+
+ if(e && e.clipboardData && e.clipboardData.getData) {
+ var strs = e.clipboardData.getData("text/plain").split("\n");
+ var table = document.getElementById("table");
+ var rows = table.getElementsByTagName("tr");
+ for(var i = 0; i < rows.length-1; i++)
+ {
+ if(rows[i] == elem.parentNode.parentNode)
+ break;
+ }
+ var index;
+ if(elem.id == "summary")
+ index = 2;
+ else
+ index = 1;
+ var k = 0;
+ for(var j = i; j < rows.length-1 && k < strs.length; j++, k++)
+ {
+ var tds = rows[j].getElementsByTagName("td");
+ var length = tds.length;
+ tds[length-index].getElementsByTagName("input")[0].value = strs[k];
+ }
+ for(; k < strs.length; k++)
+ {
+ if(index == 2)
+ createRow(strs[k], "");
+ else
+ createRow("", strs[k]);
+ }
+ }
+ }
+ function Apply()
+ {
+ var table = document.getElementById("table");
+ var rows = table.getElementsByTagName("tr");
+ var fields = rows[0].getElementsByTagName("td");
+ for(var i = 1; i < rows.length-1; i++)
+ {
+ var tds = rows[i].getElementsByTagName("td");
+ for(var j = 1; j < tds.length; j++)
+ {
+ var input = tds[j].getElementsByTagName("input");
+ var select = tds[j].getElementsByTagName("select");
+ if(input != null && input.length > 0)
+ {
+ input[0].value = fields[j].getElementsByTagName("input")[0].value;
+ }
+ if(select != null && select.length > 0)
+ {
+ select[0].value = fields[j].getElementsByTagName("select")[0].value;
+ }
+ }
+ }
+ }
+ function TabandCreate(elem, e)
+ {
+ if(e.keyCode != 9)
+ return true;
+ var table = document.getElementById("table");
+ var rows = table.getElementsByTagName("tr");
+ var length = rows.length;
+ var parent = elem.parentNode.parentNode;
+ if(parent == rows[length-2])
+ createRow('','');
+ parent.nextElementSibling.getElementsByTagName("input")[1].focus();
+ return false;
+ }
+ </script>
+</form>
diff --git a/themes/CleanFS/templates/newtask.tpl b/themes/CleanFS/templates/newtask.tpl
new file mode 100644
index 0000000..a09e423
--- /dev/null
+++ b/themes/CleanFS/templates/newtask.tpl
@@ -0,0 +1,268 @@
+<!--<h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> :: <?= eL('newtask') ?></h3>-->
+<?php
+ if (!isset($supertask_id)) {
+ $supertask_id = 0;
+ }
+?>
+ <script type="text/javascript">
+ function checkContent()
+ {
+ var instance;
+ for(instance in CKEDITOR.instances){
+ CKEDITOR.instances[instance].updateElement();
+ }
+ var summary = document.getElementById("itemsummary").value;
+ if(summary.trim().length == 0){
+ return true;
+ }
+ var detail = document.getElementById("details").value;
+ var project_id = document.getElementsByName('project_id')[0].value;
+
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.open("POST", "<?php echo Filters::noXSS($baseurl); ?>js/callbacks/searchtask.php", false);
+ xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
+ xmlHttp.send("summary=" + summary + "&detail=" + detail +"&project_id=" + project_id);
+ if(xmlHttp.status === 200) {
+ if(xmlHttp.responseText > 0) {
+ var res = confirm("There is already a similar task, do you still want to create?");
+ return res;
+ }
+ return true;
+ }
+ return false;
+ }
+ </script>
+<?php echo tpl_form(Filters::noXSS(createUrl('newtask', $proj->id, $supertask_id)), 'newtask', 'post', 'multipart/form-data', 'onsubmit="return checkContent()"'); ?>
+ <input type="hidden" name="supertask_id" value="<?php echo Filters::noXSS($supertask_id); ?>" />
+ <div id="actionbar"><div class="clear"></div></div>
+ <?php
+ # Grab fields wanted for this project so we can only show those we want
+ $fields = explode( ' ', $proj->prefs['visible_fields'] );
+ ?>
+ <div id="taskdetails">
+ <div id="taskfields">
+ <ul class="form_elements slim">
+ <!-- Task Type -->
+ <?php if (in_array('tasktype', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="tasktype"><?= eL('tasktype') ?></label>
+ <select name="task_type" id="tasktype">
+ <?php echo tpl_options($proj->listTaskTypes(), Req::val('task_type')); ?>
+ </select>
+ </li>
+
+ <!-- Category -->
+ <?php if (in_array('category', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="category"><?= eL('category') ?></label>
+ <select class="adminlist" name="product_category" id="category">
+ <?php echo tpl_options($proj->listCategories(), Req::val('product_category')); ?>
+ </select>
+ </li>
+
+ <!-- Status -->
+ <?php if (in_array('status', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="status"><?= eL('status') ?></label>
+ <select id="status" name="item_status" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <?php echo tpl_options($proj->listTaskStatuses(), Req::val('item_status', ($user->perms('modify_all_tasks') ? STATUS_NEW : STATUS_UNCONFIRMED))); ?>
+ </select>
+ </li>
+
+ <?php if ($user->perms('modify_all_tasks')): ?>
+ <!-- Assigned To -->
+ <?php if (in_array('assignedto', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label><?= eL('assignedto') ?></label>
+ <?php if ($user->perms('modify_all_tasks')): ?>
+ <?php $this->display('common.multiuserselect.tpl'); ?>
+ <?php endif; ?>
+ </li>
+ <?php endif; ?>
+
+ <!-- os -->
+ <?php if (in_array('os', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="os"><?= eL('operatingsystem') ?></label>
+ <select id="os" name="operating_system">
+ <?php echo tpl_options($proj->listOs(), Req::val('operating_system')); ?>
+ </select>
+ </li>
+
+ <!-- Severity -->
+ <?php if (in_array('severity', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="severity"><?= eL('severity') ?></label>
+ <select onchange="getElementById('edit_summary').className = 'summary severity' + this.value;
+ getElementById('itemsummary').className = 'text severity' + this.value;"
+ id="severity" class="adminlist" name="task_severity">
+ <?php echo tpl_options($fs->severities, Req::val('task_severity', 2)); ?>
+ </select>
+ </li>
+
+ <!-- Priority-->
+ <?php if (in_array('priority', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="priority"><?= eL('priority') ?></label>
+ <select id="priority" name="task_priority" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <?php echo tpl_options($fs->priorities, Req::val('task_priority', 4)); ?>
+ </select>
+ </li>
+
+ <!-- Reported Version-->
+ <?php if (in_array('reportedin', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="reportedver"><?= eL('reportedversion') ?></label>
+ <select class="adminlist" name="product_version" id="reportedver">
+ <?php echo tpl_options($proj->listVersions(false, 2), Req::val('product_version')); ?>
+ </select>
+ </li>
+
+ <!-- Due Version -->
+ <?php if (in_array('dueversion', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="dueversion"><?= eL('dueinversion') ?></label>
+ <select id="dueversion" name="closedby_version" <?php echo tpl_disableif(!$user->perms('modify_all_tasks')); ?>>
+ <option value="0"><?= eL('undecided') ?></option>
+ <?php echo tpl_options($proj->listVersions(false, 3),$proj->prefs['default_due_version'], false); ?>
+ </select>
+ </li>
+
+ <?php if ($user->perms('modify_all_tasks')): ?>
+ <!-- Due Date -->
+ <?php if (in_array('duedate', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="due_date"><?= eL('duedate') ?></label>
+ <?php echo tpl_datepicker('due_date', '', Req::val('due_date')); ?>
+ </li>
+ <?php endif; ?>
+
+ <?php if($proj->prefs['use_effort_tracking']) {
+ if ($user->perms('view_effort')) {
+ ?>
+ <li>
+ <label for="estimatedeffort"><?= eL('estimatedeffort') ?></label>
+ <input id="estimated_effort" name="estimated_effort" class="text" type="text" size="5" maxlength="100" value="0" />
+ <?= eL('hours') ?>
+ </li>
+ <?php }
+ } ?>
+
+ <?php if ($user->perms('manage_project')): ?>
+ <!-- Private -->
+ <?php if (in_array('private', $fields)) { ?>
+ <li>
+ <?php } else { ?>
+ <li style="display:none">
+ <?php } ?>
+ <label for="private"><?= eL('private') ?></label>
+ <?php echo tpl_checkbox('mark_private', Req::val('mark_private', 0), 'private'); ?>
+ </li>
+ <?php endif; ?>
+ </ul>
+ </div>
+
+ <div id="taskdetailsfull">
+ <!--<h3 class="taskdesc"><?= eL('details') ?></h3>-->
+ <label class="severity<?php echo Filters::noXSS(Req::val('task_severity', 2)); ?> summary" id="edit_summary" for="itemsummary"><?php echo Filters::noXSS(L('summary')); ?></label>
+ <input id="itemsummary" required="required" placeholder="<?= eL('summary') ?>" title="<?= eL('tooltipshorttasktitle') ?>" type="text" value="<?php echo Filters::noXSS(Req::val('item_summary')); ?>"
+ name="item_summary" maxlength="100" />
+ <div id="edit_tags">
+ <label for="tags" title="<?= eL('tagsinfo') ?>"><?= eL('tags') ?>:</label>
+ <input id="tags" title="<?= eL('tagsinfo') ?>" class="text" type="text"
+ value="<?php echo Filters::noXSS(Req::val('tags')); ?>" name="tags" maxlength="100" />
+ </div>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <button tabindex="9" type="button" onclick="showPreview('details', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('detailed_desc', 15, 70, array('id' => 'details'), Req::val('detailed_desc', $proj->prefs['default_task'])); ?>
+
+ <p class="buttons">
+ <?php if ($user->isAnon()): ?>
+ <label class="inline" for="anon_email"><?= eL('youremail') ?></label><input type="text" class="text" id="anon_email" name="anon_email" size="30" required="required" value="<?php echo Filters::noXSS(Req::val('anon_email')); ?>" /><br />
+ <?php endif; ?>
+ <?php if (!$user->perms('modify_all_tasks')): ?>
+ <input type="hidden" name="item_status" value="1" />
+ <input type="hidden" name="task_priority" value="2" />
+ <?php endif; ?>
+ <input type="hidden" name="action" value="newtask.newtask" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <?php if (!$user->isAnon()): ?>
+ &nbsp;&nbsp;<input class="text" type="checkbox" id="notifyme" name="notifyme"
+ value="1" checked="checked" />&nbsp;<label class="inline left" for="notifyme"><?= eL('notifyme') ?></label>
+ <?php endif; ?>
+ </p>
+
+ <?php if ($user->perms('create_attachments')): ?>
+ <div id="uploadfilebox">
+ <span style="display: none"><?php // this span is shown/copied in javascript when adding files ?>
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ <a href="javascript://" tabindex="6" onclick="removeUploadField(this, 'uploadfilebox');"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ <noscript>
+ <span>
+ <input tabindex="5" class="file" type="file" size="55" name="userfile[]" />
+ <a href="javascript://" tabindex="6" onclick="removeUploadField(this, 'uploadfilebox');"><?php echo Filters::noXSS(L('remove')); ?></a><br />
+ </span>
+ </noscript>
+ </div>
+ <button id="uploadfilebox_attachafile" tabindex="7" type="button" onclick="addUploadFields('uploadfilebox')">
+ <?= eL('uploadafile') ?> (<?= eL('max') ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?= eL('MiB') ?>)
+ </button>
+ <button id="uploadfilebox_attachanotherfile" tabindex="7" style="display: none" type="button" onclick="addUploadFields('uploadfilebox')">
+ <?= eL('attachanotherfile') ?> (<?= eL('max') ?> <?php echo Filters::noXSS($fs->max_file_size); ?> <?= eL('MiB') ?>)
+ </button>
+
+ <div id="addlinkbox">
+ <span style="display: none">
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" onclick="removeLinkField(this, 'addlinkbox');"><?= eL('remove') ?></a><br />
+ </span>
+ <noscript>
+ <span>
+ <input tabindex="8" class="text" type="text" size="28" maxlength="100" name="userlink[]" />
+ <a href="javascript://" tabindex="9" onclick="removeLinkField(this, 'addlinkbox');"><?= eL('remove') ?></a><br />
+ </span>
+ </noscript>
+</div>
+<button id="addlinkbox_addalink" tabindex="10" type="button" onclick="addLinkField('addlinkbox')"><?= eL('addalink') ?></button>
+<button id="addlinkbox_addanotherlink" tabindex="10" style="display:none" type="button" onclick="addLinkField('addlinkbox')"><?= eL('addanotherlink') ?></button>
+ <?php endif; ?>
+
+<button class="button positive" style="display:block;margin-top:20px" accesskey="s" type="submit"><?= eL('addthistask') ?></button>
+ </div>
+
+ <div class="clear"></div>
+ </div>
+</form>
diff --git a/themes/CleanFS/templates/permicons.tpl b/themes/CleanFS/templates/permicons.tpl
new file mode 100644
index 0000000..7e3d869
--- /dev/null
+++ b/themes/CleanFS/templates/permicons.tpl
@@ -0,0 +1,30 @@
+<?php
+$permicons=array(
+'view_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_groups_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_own_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_roadmap'=>'<span class="fa fa-stack"><i class="fa fa-road fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_history'=>'<span class="fa fa-stack"><i class="fa fa-history fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_reports'=>'<span class="fa fa-stack"><i class="fa fa-line-chart fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_estimated_effort'=>'<span class="fa fa-stack"><i class="fa fa-clock-o fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_current_effort_done'=>'<span class="fa fa-stack"><i class="fa fa-clock-o fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'track_effort'=>'<span class="fa fa-stack"><i class="fa fa-clock-o fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'open_new_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-plus fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'add_multiple_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-plus fa-stack" style="padding-top:8px;padding-top:8px;"></i></span>',
+'modify_own_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-pencil fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'modify_all_tasks'=>'<span class="fa fa-stack"><i class="fa fa-tasks fa-stack-2x"></i><i class="fa fa-pencil fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'close_own_tasks'=>'<i class="fa fa-check fa-lg"></i>',
+'close_other_tasks'=>'<i class="fa fa-check fa-lg"></i>',
+'create_attachments'=>'<span class="fa fa-stack"><i class="fa fa-paperclip fa-flip-horizontal fa-stack-2x"></i><i class="fa fa-plus fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'delete_attachments'=>'<span class="fa fa-stack"><i class="fa fa-paperclip fa-flip-horizontal fa-stack-2x"></i><i class="fa fa-remove fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'assign_to_self'=>'<span class="fa fa-stack"><i class="fa fa-hand-o-right fa-stack-2x"></i></span>',
+'assign_others_to_self'=>'<span class="fa fa-stack"><i class="fa fa-hand-o-right fa-stack-2x"></i></span>',
+'edit_assignments'=>'<span class="fa fa-stack"><i class="fa fa-hand-o-right fa-stack-2x"></i><i class="fa fa-pencil fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'add_votes'=>'<span class="fa fa-stack"><i class="fa fa-star-o fa-stack-2x"></i><i class="fa fa-plus fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'view_comments'=>'<span class="fa fa-stack"><i class="fa fa-comments fa-stack-2x"></i><i class="fa fa-eye fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'add_comments'=>'<span class="fa fa-stack"><i class="fa fa-comments fa-stack-2x"></i><i class="fa fa-plus fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'edit_comments'=>'<span class="fa fa-stack"><i class="fa fa-comments fa-stack-2x"></i><i class="fa fa-pencil fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'edit_own_comments'=>'<span class="fa fa-stack"><i class="fa fa-comments fa-stack-2x"></i><i class="fa fa-pencil fa-stack" style="padding-top:8px;padding-left:12px"></i></span>',
+'delete_comments'=>'<span class="fa fa-stack"><i class="fa fa-comments fa-stack-2x"></i><i class="fa fa-remove fa-stack" style="padding-top:8px;padding-left:12px"></i></span>'
+);
+?>
diff --git a/themes/CleanFS/templates/pm.cat.tpl b/themes/CleanFS/templates/pm.cat.tpl
new file mode 100644
index 0000000..f629cd4
--- /dev/null
+++ b/themes/CleanFS/templates/pm.cat.tpl
@@ -0,0 +1,5 @@
+<?php $this->assign('sysrows', $proj->listCategories(0, false, true, false)); ?>
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('catlisted')); ?></h3>
+ <?php $this->display('common.cat.tpl'); ?>
+</div>
diff --git a/themes/CleanFS/templates/pm.editgroup.tpl b/themes/CleanFS/templates/pm.editgroup.tpl
new file mode 100644
index 0000000..39b00af
--- /dev/null
+++ b/themes/CleanFS/templates/pm.editgroup.tpl
@@ -0,0 +1,4 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('pmtoolbox')); ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('editgroup')); ?></h3>
+ <?php $this->display('common.editgroup.tpl'); ?>
+</div>
diff --git a/themes/CleanFS/templates/pm.groups.tpl b/themes/CleanFS/templates/pm.groups.tpl
new file mode 100644
index 0000000..2b250ef
--- /dev/null
+++ b/themes/CleanFS/templates/pm.groups.tpl
@@ -0,0 +1,138 @@
+<div id="toolbox">
+<h2><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('groupmanage')); ?></h2>
+<?php if ($user->perms('is_admin')): ?><a class="button" href="<?php echo CreateURL('admin', 'newuser', $proj->id); ?>"><i class="fa fa-user-plus fa-lg fa-fw"></i> <?php echo Filters::noXSS(L('newuser')); ?></a><?php endif; ?>
+<a class="button" href="<?php echo Filters::noXSS(CreateURL('pm', 'newgroup', $proj->id)); ?>"><i class="fa fa-group fa-lg fa-fw"></i><?php echo Filters::noXSS(L('newgroup')); ?></a>
+
+<form style="display:inline-block" action="<?php echo Filters::noXSS($baseurl); ?>index.php" method="get">
+<label for="edit_user"><?php echo Filters::noXSS(L('edituser')); ?></label>
+<?php echo tpl_userselect('user_name', '', 'edit_user'); ?>
+<button type="submit"><?php echo Filters::noXSS(L('edit')); ?></button>
+<input type="hidden" name="do" value="user" />
+<input type="hidden" name="project" value="<?php echo $proj->id; ?>" />
+</form>
+<?php
+# 'group_open 'is not relevant for project groups, so lets not add it here.
+$perm_fields = array(
+'is_admin',
+'manage_project',
+'view_tasks',
+'view_groups_tasks', # TODO: What is the definition of "group's task" and how does it effect project views?
+'view_own_tasks', # TODO: What is the definition of "own task" and how does it effect project views?
+'open_new_tasks',
+'add_multiple_tasks',
+'modify_own_tasks',
+'modify_all_tasks',
+'create_attachments',
+'delete_attachments',
+'assign_to_self',
+'assign_others_to_self',
+'edit_assignments',
+'close_own_tasks',
+'close_other_tasks',
+'view_roadmap',
+'view_history',
+'view_reports',
+'add_votes',
+'view_comments',
+'add_comments',
+'edit_comments',
+'edit_own_comments',
+'delete_comments',
+'view_estimated_effort',
+'view_current_effort_done',
+'track_effort'
+);
+
+$yesno = array(
+ '<td style="color:#ccc" title="'.eL('no').'">-</td>',
+ '<td title="'.eL('yes').'"><i class="good fa fa-check fa-lg"></i></td>'
+);
+
+$merge=array_merge($groups,$globalgroups);
+
+$perms=array();
+$gmembers='';
+$gnames='';
+$gdesc='';
+$cols='';
+foreach ($merge as $group){
+ $cols.='<col class="group g'.$group['group_id'].($group['project_id']==0?' globalgroup':'').($group['project_id']==0 && $group['group_open']==0?' inactive':'').'"></col>';
+ $gmembers.='<td>'.$group['users'].'</td>';
+ if($group['project_id']!=0) {
+ $gnames.='<td><a class="button" title="'.eL('editgroup').'" href="'.(CreateURL('editgroup', $group['group_id'], 'pm')).'">'
+ .Filters::noXSS($group['group_name'])
+ .'<i class="fa fa-pencil fa-lg fa-fw"></i></a></td>';
+ } else {
+ $gnames.='<th title="'.eL('globalgroup').'">'.Filters::noXSS($group['group_name']).'</th>';
+ }
+ $gdesc.='<td>'.Filters::noXSS($group['group_desc']).'</td>';
+ foreach ($group as $key => $val) {
+ if (!is_numeric($key) && in_array($key, $perm_fields)) {
+ $perms[$key][]=$val;
+ }
+ }
+}
+?>
+<style>
+.perms {border-collapse:collapse;margin-top:20px;display:block;}
+.perms tbody tr:hover {background-color:#eee;}
+.perms td, .perms th{border:1px solid #999;}
+.perms thead th, .perms thead td {text-align:center;}
+.perms tbody th{text-align:right;}
+.perms tbody td{width:100px;text-align:center;}
+.perms tbody span i:first-child {color: #090;}
+.group.globalgroup {background-color:#ddd;}
+.group.globalgroup.inactive {background-color:#ccc;}
+</style>
+<table class="perms">
+<colgroup>
+<col></col>
+<?php echo $cols; ?>
+</colgroup>
+<thead>
+<tr>
+<th><?php echo L('groupmembers'); ?></th>
+<?php echo $gmembers; ?>
+</tr>
+<tr>
+<th><?php echo L('group'); ?></th>
+<?php echo $gnames; ?>
+</tr>
+<tr>
+<th><?php echo L('description'); ?></th>
+<?php echo $gdesc; ?>
+</tr>
+</thead>
+<tbody>
+<?php foreach ($perm_fields as $p): ?>
+<tr<?php
+# TODO view_own_tasks
+echo ( ($p=='view_tasks' || $p=='view_groups_tasks' || $p=='view_own_tasks') && $proj->prefs['others_view']) ? ' class="everybody"':'';
+echo ($p=='view_roadmap' && $proj->prefs['others_viewroadmap']) ?' class="everybody"':'';
+echo ($p=='open_new_tasks' && $proj->prefs['anon_open']) ? ' class="everybody"':'';
+?>>
+<th<?php echo ($p=='modify_own_tasks') ? ' title="Fields allowed to change: '.implode(', ', $proj->prefs['basic_fields']).'"':''; ?>><?php echo eL(str_replace('_', '', $p)); ?></th>
+<?php
+require_once('permicons.tpl');
+$i=0;
+
+foreach($perms[$p] as $val){
+ if ($perms['is_admin'][$i]==1 && $val == 0){
+ if(isset($permicons[$p])){
+ echo '<td title="'.eL('yes').' - Permission granted because of is_admin">( '.$permicons[$p].' )</td>';
+ }else{
+ echo $yesno[1];
+ }
+ } elseif($val==1 && isset($permicons[$p])){
+ echo '<td>'.$permicons[$p].'</td>';
+ } else{
+ echo $yesno[$val];
+ }
+ $i++;
+}
+?>
+</tr>
+<?php endforeach; ?>
+</tbody>
+</table>
+</div>
diff --git a/themes/CleanFS/templates/pm.menu.tpl b/themes/CleanFS/templates/pm.menu.tpl
new file mode 100644
index 0000000..f239e10
--- /dev/null
+++ b/themes/CleanFS/templates/pm.menu.tpl
@@ -0,0 +1,34 @@
+<?php $activeclass = ' class="active" '; ?>
+
+<div id="toolboxmenu">
+ <a id="projprefslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'prefs') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'prefs', $proj->id)); ?>"><?php echo Filters::noXSS(L('preferences')); ?></a>
+ <a id="projuglink"
+ <?php if(isset($_GET['area']) and ($_GET['area'] == 'groups' || $_GET['area'] == 'newgroup' || $_GET['area'] == 'editgroup') ) echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'groups', $proj->id)); ?>"><?php echo Filters::noXSS(L('usergroups')); ?></a>
+ <a id="projcatlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'cat') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'cat', $proj->id)); ?>"><?php echo Filters::noXSS(L('categories')); ?></a>
+ <a id="projttlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'tasktype') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'tasktype', $proj->id)); ?>"><?php echo Filters::noXSS(L('tasktypes')); ?></a>
+ <a id="projtglink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'tag') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'tag', $proj->id)); ?>"><i class="fa fa-tag"></i> <?php echo Filters::noXSS(L('tags')); ?></a>
+ <a id="projstatuslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'status') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'status', $proj->id)); ?>"><?php echo Filters::noXSS(L('taskstatuses')); ?></a>
+ <a id="projreslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'resolution') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'resolution', $proj->id)); ?>"><?php echo Filters::noXSS(L('resolutions')); ?></a>
+ <a id="projverlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'version') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'version', $proj->id)); ?>"><?php echo Filters::noXSS(L('versions')); ?></a>
+ <a id="projoslink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'os') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'os', $proj->id)); ?>"><i class="fa fa-linux"></i><i class="fa fa-windows"></i><i class="fa fa-apple"></i> <?php echo Filters::noXSS(L('operatingsystems')); ?></a>
+ <a id="projreqlink"
+ <?php if(isset($_GET['area']) and $_GET['area'] == 'pendingreq') echo $activeclass; ?>
+ href="<?php echo Filters::noXSS(CreateURL('pm', 'pendingreq', $proj->id)); ?>"><?php echo Filters::noXSS(L('pendingrequests')); ?></a>
+</div>
diff --git a/themes/CleanFS/templates/pm.newgroup.tpl b/themes/CleanFS/templates/pm.newgroup.tpl
new file mode 100644
index 0000000..dac1c9a
--- /dev/null
+++ b/themes/CleanFS/templates/pm.newgroup.tpl
@@ -0,0 +1,7 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('pmtoolbox')); ?> :: <?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('createnewgroup')); ?></h3>
+
+ <?php
+ $this->display('common.newgroup.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/pm.os.tpl b/themes/CleanFS/templates/pm.os.tpl
new file mode 100644
index 0000000..a2f2dc4
--- /dev/null
+++ b/themes/CleanFS/templates/pm.os.tpl
@@ -0,0 +1,12 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('oslisted')); ?></h3>
+<?php
+$this->assign('list_type', 'os');
+$this->assign('rows', $proj->listOs(true));
+
+$systemwide = new Project(0);
+$this->assign('sysrows', $systemwide->listOs(true));
+
+$this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/pm.pendingreq.tpl b/themes/CleanFS/templates/pm.pendingreq.tpl
new file mode 100644
index 0000000..353dc3b
--- /dev/null
+++ b/themes/CleanFS/templates/pm.pendingreq.tpl
@@ -0,0 +1,79 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('pendingrequests')); ?></h3>
+
+ <?php if (!count($pendings)): ?>
+ <?php echo Filters::noXSS(L('nopendingreq')); ?>
+
+ <?php else: ?>
+ <table class="requests">
+ <tr>
+ <th><?php echo Filters::noXSS(L('eventdesc')); ?></th>
+ <th><?php echo Filters::noXSS(L('requestedby')); ?></th>
+ <th><?php echo Filters::noXSS(L('daterequested')); ?></th>
+ <th><?php echo Filters::noXSS(L('reasongiven')); ?></th>
+ <th class="pm-buttons"> </th>
+ </tr>
+ <?php foreach ($pendings as $req): ?>
+ <tr>
+ <td>
+ <?php if ($req['request_type'] == 1) : ?>
+ <?php echo Filters::noXSS(L('closetask')); ?> -
+ <a href="<?php echo Filters::noXSS(CreateURL('details', $req['task_id'])); ?>">FS#<?php echo Filters::noXSS($req['task_id']); ?> :
+ <?php echo Filters::noXSS($req['item_summary']); ?></a>
+ <?php elseif ($req['request_type'] == 2) : ?>
+ <?php echo Filters::noXSS(L('reopentask')); ?> -
+ <a href="<?php echo Filters::noXSS(CreateURL('details', $req['task_id'])); ?>">FS#<?php echo Filters::noXSS($req['task_id']); ?> :
+ <?php echo Filters::noXSS($req['item_summary']); ?></a>
+ <?php endif; ?>
+ </td>
+ <td><?php echo tpl_userlink($req['user_id']); ?></td>
+ <td><?php echo Filters::noXSS(formatDate($req['time_submitted'], true)); ?></td>
+ <td><?php echo Filters::noXSS($req['reason_given']); ?></td>
+ <td>
+ <?php if ($req['request_type'] == 1) : ?>
+ <a class="button" href="#" onclick="showhidestuff('closeform<?php echo Filters::noXSS($req['request_id']); ?>');"><?php echo Filters::noXSS(L('accept')); ?></a>
+ <div id="closeform<?php echo Filters::noXSS($req['request_id']); ?>" class="denyform">
+ <?php echo tpl_form(Filters::noXSS(CreateURL('pm', 'pendingreq', $proj->id))); ?>
+ <div>
+ <input type="hidden" name="action" value="details.close"/>
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($req['task_id']); ?>"/>
+ <select class="adminlist" name="resolution_reason" onmouseup="event.stopPropagation();">
+ <option value="0"><?php echo Filters::noXSS(L('selectareason')); ?></option>
+ <?php echo tpl_options($proj->listResolutions(), Req::val('resolution_reason')); ?>
+ </select>
+ <button type="submit"><?php echo Filters::noXSS(L('closetask')); ?></button>
+ <br/>
+ <label class="default text" for="closure_comment"><?php echo Filters::noXSS(L('closurecomment')); ?></label>
+ <textarea class="text" id="closure_comment" name="closure_comment" rows="3"
+ cols="25"><?php echo Filters::noXSS(Req::val('closure_comment')); ?></textarea>
+ <label><?php echo tpl_checkbox('mark100', Req::val('mark100', !(Req::val('action') == 'details.close'))); ?>&nbsp;&nbsp;<?php echo Filters::noXSS(L('mark100')); ?></label>
+ </div>
+ </form>
+ </div>
+ <?php elseif ($req['request_type'] == 2) : ?>
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('pm', 'pendingreq', $proj->id)), null, null, null, 'style="display:inline"'); ?>
+ <input type="hidden" name="action" value="reopen" />
+ <input type="hidden" name="task_id" value="<?php echo Filters::noXSS($req['task_id']); ?>">
+ <input type="submit" class="button" value="<?php echo Filters::noXSS(L('accept')); ?>">
+ </form>
+
+ <?php endif; ?>
+ <a href="#" class="button" onclick="showhidestuff('denyform<?php echo Filters::noXSS($req['request_id']); ?>');"><?php echo Filters::noXSS(L('deny')); ?></a>
+ <div id="denyform<?php echo Filters::noXSS($req['request_id']); ?>" class="denyform">
+ <?php echo tpl_form(Filters::noXSS(CreateUrl('pm', 'pendingreq', $proj->id))); ?>
+ <div>
+ <input type="hidden" name="action" value="denypmreq" />
+ <input type="hidden" name="req_id" value="<?php echo Filters::noXSS($req['request_id']); ?>" />
+ <label for="deny_reason<?php echo Filters::noXSS($req['request_id']); ?>" class="inline"><?php echo Filters::noXSS(L('reasonfordeinal')); ?></label><br />
+ <textarea cols="40" rows="5" name="deny_reason" id="deny_reason<?php echo Filters::noXSS($req['request_id']); ?>"></textarea>
+ <br />
+ <button type="submit"><?php echo Filters::noXSS(L('deny')); ?></button>
+ </div>
+ </form>
+ </div>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </table>
+ <?php endif; ?>
+</div>
diff --git a/themes/CleanFS/templates/pm.prefs.tpl b/themes/CleanFS/templates/pm.prefs.tpl
new file mode 100644
index 0000000..6af32fa
--- /dev/null
+++ b/themes/CleanFS/templates/pm.prefs.tpl
@@ -0,0 +1,370 @@
+<div id="toolbox">
+<h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('preferences')); ?></h3>
+<?php echo tpl_form(CreateUrl('pm', 'prefs', $proj->id)); ?>
+ <ul id="submenu">
+ <li><a href="#general"><?php echo Filters::noXSS(L('general')); ?></a></li>
+ <li><a href="#lookandfeel"><?php echo Filters::noXSS(L('lookandfeel')); ?></a></li>
+ <li><a href="#notifications"><?php echo Filters::noXSS(L('notifications')); ?></a></li>
+ <li><a href="#feeds"><?php echo Filters::noXSS(L('feeds')); ?></a></li>
+ <li><a href="#effort"><?php echo Filters::noXSS(L('efforttracking')); ?></a></li>
+ </ul>
+
+ <div id="general" class="tab">
+ <ul class="form_elements wide">
+ <li>
+ <label for="projecttitle"><?php echo Filters::noXSS(L('projecttitle')); ?></label>
+ <input id="projecttitle" name="project_title" class="text" type="text" maxlength="100"
+ value="<?php echo Filters::noXSS(Post::val('project_title', $proj->prefs['project_title'])); ?>" />
+ </li>
+
+ <li>
+ <label for="defaultcatowner"><?php echo Filters::noXSS(L('defaultcatowner')); ?></label>
+ <?php echo tpl_userselect('default_cat_owner', Post::val('default_cat_owner', $proj->prefs['default_cat_owner']), 'defaultcatowner'); ?>
+ </li>
+
+ <li>
+ <label for="langcode"><?php echo Filters::noXSS(L('language')); ?></label>
+ <select id="langcode" name="lang_code">
+ <?php echo tpl_options(Flyspray::listLangs(), Post::val('lang_code', $proj->prefs['lang_code']), true); ?>
+ </select>
+ </li>
+
+ <?php echo tpl_checkbox('disp_intro', Post::val('disp_intro', $proj->prefs['disp_intro']), 'disp_intro'); ?>
+ <label for="disp_intro"><?php echo Filters::noXSS(L('dispintro')); ?></label>
+ <li class="disp_introdep">
+ <label class="labeltextarea" for="intromesg"><?php echo Filters::noXSS(L('intromessage')); ?></label>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview"></div>
+ <button tabindex="9" type="button" onclick="showPreview('intromesg', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('intro_message', 8, 70, array('accesskey' => 'r', 'tabindex' => 8, 'id' => 'intromesg'), Post::val('intro_message', $proj->prefs['intro_message'])); ?>
+
+ <label class="labeltextarea"><?php echo Filters::noXSS(L('pagesintromsg')); ?></label>
+ <?php
+ $pages = array(
+ 'index' => L('tasklist'),
+ 'toplevel' => L('toplevel'),
+ 'newmultitasks' => L('addmultipletasks'),
+ 'details' => L('details'),
+ 'roadmap' => L('roadmap'),
+ 'newtask' => L('newtask'),
+ 'reports' => L('reports'),
+ 'depends' => L('dependencygraph'),
+ 'pm' => L('manageproject'));
+ $selectedPages = explode(' ', $proj->prefs['pages_intro_msg']);
+ echo tpl_double_select('pages_intro_msg', $pages, $selectedPages, false, false);
+ ?>
+ </li>
+
+ <li>
+ <label class="labeltextarea" for="default_task"><?php echo Filters::noXSS(L('defaulttask')); ?></label>
+ <?php if (defined('FLYSPRAY_HAS_PREVIEW')): ?>
+ <div class="hide preview" id="preview_taskdesc"></div>
+ <button tabindex="9" type="button" onclick="showPreview('default_task', '<?php echo Filters::noJsXSS($baseurl); ?>', 'preview_taskdesc')"><?php echo Filters::noXSS(L('preview')); ?></button>
+ <?php endif; ?>
+ <?php echo TextFormatter::textarea('default_task', 8, 70, array('accesskey' => 'r', 'tabindex' => 8, 'id' => 'default_task'), Post::val('default_task', $proj->prefs['default_task'])); ?>
+ </li>
+
+ <li>
+ <label for="isactive"><?php echo Filters::noXSS(L('isactive')); ?></label>
+ <?php echo tpl_checkbox('project_is_active', Post::val('project_is_active', $proj->prefs['project_is_active']), 'isactive'); ?>
+ </li>
+
+ <li>
+ <label><?php echo tpl_checkbox('delete_project', null); ?> <?php echo Filters::noXSS(L('deleteproject')); ?></label>
+ <select name="move_to"><?php echo tpl_options(array_merge(array(0 => L('none')), Flyspray::listProjects()), null, false, null, (string) $proj->id); ?></select>
+ </li>
+
+ <li>
+ <label for="othersviewroadmap"><?php echo Filters::noXSS(L('othersviewroadmap')); ?></label>
+ <?php
+ # note for FS1.0: This setting is currently also used as anon/public permission for: show project name, activity, stats, milestone progress
+ # but not listing tasks per milestone
+ echo tpl_checkbox('others_viewroadmap', Post::val('others_viewroadmap', $proj->prefs['others_viewroadmap']), 'othersviewroadmap'); ?>
+ </li>
+
+ <li>
+ <label for="othersview"><?php echo Filters::noXSS(L('othersview')); ?></label>
+ <?php
+ # note for FS1.0: This setting is current anon/public task view permission for: listing tasks (toplevel, tasklist, roadmap, RSS feed, ..)
+ echo tpl_checkbox('others_view', Post::val('others_view', $proj->prefs['others_view']), 'othersview'); ?>
+ </li>
+
+ <li>
+ <label for="anon_open"><?php echo Filters::noXSS(L('allowanonopentask')); ?></label>
+ <?php echo tpl_checkbox('anon_open', Post::val('anon_open', $proj->prefs['anon_open']), 'anon_open'); ?>
+ </li>
+
+ <li>
+ <label for="comment_closed"><?php echo Filters::noXSS(L('allowclosedcomments')); ?></label>
+ <?php echo tpl_checkbox('comment_closed', Post::val('comment_closed', $proj->prefs['comment_closed']), 'comment_closed'); ?>
+ </li>
+
+ <li>
+ <label for="auto_assign"><?php echo Filters::noXSS(L('autoassign')); ?></label>
+ <?php echo tpl_checkbox('auto_assign', Post::val('auto_assign', $proj->prefs['auto_assign']), 'auto_assign'); ?>
+ </li>
+
+ <li>
+ <label for="defaultdueversion"><?php echo Filters::noXSS(L('defaultdueinversion')); ?></label>
+ <select id="defaultdueversion" name="default_due_version">
+ <option value="0"><?php echo Filters::noXSS(L('undecided')); ?></option>
+ <?php echo tpl_options($proj->listVersions(false, 3), Post::val('default_due_version', $proj->prefs['default_due_version']), true); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="freetagging"><?php echo Filters::noXSS(L('freetagging')); ?></label>
+ <?php echo tpl_checkbox('freetagging', Post::val('freetagging', $proj->prefs['freetagging']), 'freetagging'); ?>
+ </li>
+ </ul>
+ </div>
+
+ <div id="lookandfeel" class="tab">
+ <ul class="form_elements wide">
+ <li>
+ <label for="themestyle"><?php echo Filters::noXSS(L('themestyle')); ?></label>
+ <select id="themestyle" name="theme_style">
+ <?php echo tpl_options(Flyspray::listThemes(), Post::val('theme_style', $proj->prefs['theme_style']), true); ?>
+ </select>
+ <label for="customstyle" style="width:auto"><?php echo Filters::noXSS(L('customstyle')); ?></label>
+ <select id="customstyle" name="custom_style">
+ <?php
+ $customs[]=array('', L('no'));
+ $customstyles=glob_compat(BASEDIR ."/themes/".($proj->prefs['theme_style'])."/custom_*.css");
+ foreach ($customstyles as $cs){
+ $customs[]=array($cs,$cs);
+ }
+ echo tpl_options($customs, $proj->prefs['custom_style']);
+ ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="default_entry"><?php echo Filters::noXSS(L('defaultentry')); ?></label>
+ <select id="default_entry" name="default_entry">
+ <?php echo tpl_options(array('index' => L('tasklist'), 'toplevel' => L('toplevel'), 'roadmap' => L('roadmap')), Post::val('default_entry', $proj->prefs['default_entry'])); ?>
+ </select>
+ </li>
+
+ <?php // Set the selectable column names
+ // Do NOT use real database column name here and in the next list,
+ // but a term from translation table entries instead, because it's
+ // also used elsewhere to draw a localized version of the name.
+ // Look also at the end of function
+ // tpl_draw_cell in scripts/index.php for further explanation.
+ $columnnames = array(
+ 'id' => L('id'),
+ 'project' => L('project'),
+ 'parent' => L('parent'),
+ 'tasktype' => L('tasktype'),
+ 'category' => L('category'),
+ 'severity' => L('severity'),
+ 'priority' => L('priority'),
+ 'summary' => L('summary'),
+ 'dateopened' => L('dateopened'),
+ 'status' => L('status'),
+ 'openedby' => L('openedby'),
+ 'private' => L('private'),
+ 'assignedto' => L('assignedto'),
+ 'lastedit' => L('lastedit'),
+ 'editedby' => L('editedby'),
+ 'reportedin' => L('reportedin'),
+ 'dueversion' => L('dueversion'),
+ 'duedate' => L('duedate'),
+ 'comments' => L('comments'),
+ 'attachments' => L('attachments'),
+ 'progress' => L('progress'),
+ 'dateclosed' => L('dateclosed'),
+ 'closedby' => L('closedby'),
+ 'os' => L('os'),
+ 'votes' => L('votes'),
+ 'estimatedeffort' => L('estimatedeffort'),
+ 'effort' => L('effort'));
+ $selectedcolumns = explode(' ', Post::val('visible_columns', $proj->prefs['visible_columns']));
+ ?>
+
+ <li>
+ <label for="default_order_by"><?php echo Filters::noXSS(L('defaultorderby')); ?></label>
+ <select id="default_order_by" name="default_order_by">
+ <?php echo tpl_options($columnnames, $proj->prefs['sorting'][0]['field'], false); ?>
+ </select>
+ <!-- <label><?php echo Filters::noXSS(L('defaultorderbydirection')); ?></label> -->
+ <select id="default_order_by_dir" name="default_order_by_dir">
+ <?php echo tpl_options(array('asc' => L('ascending'), 'desc' => L('descending')), $proj->prefs['sorting'][0]['dir'], false); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="default_order_by2"><?php echo Filters::noXSS(L('defaultorderby2')); ?></label>
+ <select id="default_order_by2" name="default_order_by2">
+ <?php echo tpl_options($columnnames, $proj->prefs['sorting'][1]['field'], false); ?>
+ </select>
+ <select id="default_order_by_dir2" name="default_order_by_dir2">
+ <?php echo tpl_options(array('asc' => L('ascending'), 'desc' => L('descending')), $proj->prefs['sorting'][1]['dir'], false); ?>
+ </select>
+ </li>
+
+ <li>
+ <label><?php echo Filters::noXSS(L('visiblecolumns')); ?></label>
+ <?php echo tpl_double_select('visible_columns', $columnnames, $selectedcolumns, false); ?>
+ </li>
+
+ <li>
+ <label><?php echo Filters::noXSS(L('visiblefields')); ?></label>
+ <?php // Set the selectable field names
+ $fieldnames = array(
+ 'parent' => L('parent'),
+ 'tasktype' => L('tasktype'),
+ 'category' => L('category'),
+ 'severity' => L('severity'),
+ 'priority' => L('priority'),
+ 'status' => L('status'),
+ 'private' => L('private'),
+ 'assignedto' => L('assignedto'),
+ 'reportedin' => L('reportedin'),
+ 'dueversion' => L('dueversion'),
+ 'duedate' => L('duedate'),
+ 'progress' => L('progress'),
+ 'os' => L('os'),
+ 'votes' => L('votes'));
+ $selectedfields = explode(' ', Post::val('visible_fields', $proj->prefs['visible_fields']));
+ ?>
+ <?php echo tpl_double_select('visible_fields', $fieldnames, $selectedfields, false); ?>
+ </li>
+ </ul>
+ </div>
+
+ <div id="notifications" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="notify_subject"><?php echo Filters::noXSS(L('notifysubject')); ?></label>
+ <input id="notify_subject" class="text" name="notify_subject" type="text" value="<?php echo Filters::noXSS(Post::val('notify_subject', $proj->prefs['notify_subject'])); ?>" />
+ <br /><span class="note"><?php echo Filters::noXSS(L('notifysubjectinfo')); ?></span>
+ </li>
+
+ <li>
+ <label for="emailaddress"><?php echo Filters::noXSS(L('emailaddress')); ?></label>
+ <input id="emailaddress" name="notify_email" class="text" type="text" value="<?php echo Filters::noXSS(Post::val('notify_email', $proj->prefs['notify_email'])); ?>" />
+ </li>
+
+ <?php if (!empty($fs->prefs['jabber_server'])): ?>
+ <li>
+ <label for="jabberid"><?php echo Filters::noXSS(L('jabberid')); ?></label>
+ <input id="jabberid" class="text" name="notify_jabber" type="text" value="<?php echo Filters::noXSS(Post::val('notify_jabber', $proj->prefs['notify_jabber'])); ?>" />
+ </li>
+ <?php endif ?>
+
+ <li>
+ <label for="notify_reply"><?php echo Filters::noXSS(L('replyto')); ?></label>
+ <input id="notify_reply" name="notify_reply" class="text" type="text" value="<?php echo Filters::noXSS(Post::val('notify_reply', $proj->prefs['notify_reply'])); ?>" />
+ </li>
+
+ <li>
+ <label for="notify_types"><?php echo Filters::noXSS(L('notifytypes')); ?></label>
+ <select id="notify_types" size="17" multiple="multiple" name="notify_types[]">
+ <?php echo tpl_options(array(0 => L('none'),
+ NOTIFY_TASK_OPENED => L('taskopened'),
+ NOTIFY_TASK_CHANGED => L('pm.taskchanged'),
+ NOTIFY_TASK_CLOSED => L('taskclosed'),
+ NOTIFY_TASK_REOPENED => L('pm.taskreopened'),
+ NOTIFY_DEP_ADDED => L('pm.depadded'),
+ NOTIFY_DEP_REMOVED => L('pm.depremoved'),
+ NOTIFY_COMMENT_ADDED => L('commentadded'),
+ NOTIFY_ATT_ADDED => L('attachmentadded'),
+ NOTIFY_REL_ADDED => L('relatedadded'),
+ NOTIFY_OWNERSHIP => L('ownershiptaken'),
+ NOTIFY_PM_REQUEST => L('pmrequest'),
+ NOTIFY_PM_DENY_REQUEST => L('pmrequestdenied'),
+ NOTIFY_NEW_ASSIGNEE => L('newassignee'),
+ NOTIFY_REV_DEP => L('revdepadded'),
+ NOTIFY_REV_DEP_REMOVED => L('revdepaddedremoved'),
+ NOTIFY_ADDED_ASSIGNEES => L('assigneeadded')),
+ Post::val('notify_types', Flyspray::int_explode(' ', $proj->prefs['notify_types']))); ?>
+ </select>
+ </li>
+ </ul>
+ </div>
+
+ <div id="feeds" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="feed_description"><?php echo Filters::noXSS(L('feeddescription')); ?></label>
+ <input id="feed_description" class="text" name="feed_description" type="text" value="<?php echo Filters::noXSS(Post::val('feed_description', $proj->prefs['feed_description'])); ?>" />
+ </li>
+
+ <li>
+ <label for="feed_img_url"><?php echo Filters::noXSS(L('feedimgurl')); ?></label>
+ <input id="feed_img_url" class="text" name="feed_img_url" type="text" value="<?php echo Filters::noXSS(Post::val('feed_img_url', $proj->prefs['feed_img_url'])); ?>" />
+ </li>
+ </ul>
+ </div>
+
+ <div id="effort" class="tab">
+ <ul class="form_elements">
+ <li>
+ <label for="useeffort"><?php echo Filters::noXSS(L('useeffort')); ?></label>
+ <?php echo tpl_checkbox('use_effort_tracking', Post::val('use_effort_tracking', $proj->prefs['use_effort_tracking']), 'useeffort'); ?>
+ </li>
+ <li>
+ <label for="hours_per_manday"><?php echo Filters::noXSS(L('hourspermanday')); ?></label>
+ <input id="hours_per_manday" class="text" name="hours_per_manday" type="text"
+ value="<?php
+ $seconds = Post::val('hours_per_manday', $proj->prefs['hours_per_manday']);
+ // Post::val is in HH:mm format, $proj->prefs in seconds.
+ if (!preg_match('/^\d+$/', $seconds)) {
+ $seconds = effort::EditStringToSeconds($seconds, $proj->prefs['hours_per_manday'], effort::FORMAT_HOURS_COLON_MINUTES);
+ }
+
+ echo Filters::noXSS(effort::SecondsToEditString($seconds,$proj->prefs['hours_per_manday'], effort::FORMAT_HOURS_COLON_MINUTES));
+ ?>" />
+ </li>
+ <li>
+ <label for="estimated_effort_format"><?php echo Filters::noXSS(L('estimatedeffortformat')); ?></label>
+ <select id="estimated_effort_format" name="estimated_effort_format">
+ <?php echo tpl_options(array(
+ effort::FORMAT_HOURS_COLON_MINUTES => L('hourplural') . ':' . L('minuteplural'),
+ effort::FORMAT_HOURS_SPACE_MINUTES => L('hourplural') . ' ' . L('minuteplural'),
+ effort::FORMAT_HOURS_PLAIN => L('hourplural'),
+ effort::FORMAT_HOURS_ONE_DECIMAL => L('hourplural') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_MINUTES => L('minuteplural'),
+ effort::FORMAT_DAYS_PLAIN => L('mandays'),
+ effort::FORMAT_DAYS_ONE_DECIMAL => L('mandays') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_DAYS_PLAIN_HOURS_PLAIN => L('mandays') . ' ' . L('hourplural'),
+ effort::FORMAT_DAYS_PLAIN_HOURS_ONE_DECIMAL => L('mandays') . ' ' . L('hourplural') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_DAYS_PLAIN_HOURS_COLON_MINUTES => L('mandays') . ' ' . L('hourplural') . ":" . L('minuteplural'),
+ effort::FORMAT_DAYS_PLAIN_HOURS_SPACE_MINUTES => L('mandays') . ' ' . L('hourplural') . " " . L('minuteplural'),
+ ),
+ Post::val('estimated_effort_format', $proj->prefs['estimated_effort_format'])); ?>
+ </select>
+ </li>
+ <li>
+ <label for="current_effort_done_format"><?php echo Filters::noXSS(L('currenteffortdoneformat')); ?></label>
+ <select id="current_effort_done_format" name="current_effort_done_format">
+ <?php echo tpl_options(array(
+ effort::FORMAT_HOURS_COLON_MINUTES => L('hourplural') . ':' . L('minuteplural'),
+ effort::FORMAT_HOURS_SPACE_MINUTES => L('hourplural') . ' ' . L('minuteplural'),
+ effort::FORMAT_HOURS_PLAIN => L('hourplural'),
+ effort::FORMAT_HOURS_ONE_DECIMAL => L('hourplural') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_MINUTES => L('minuteplural'),
+ effort::FORMAT_DAYS_PLAIN => L('mandays'),
+ effort::FORMAT_DAYS_ONE_DECIMAL => L('mandays') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_DAYS_PLAIN_HOURS_PLAIN => L('mandays') . ' ' . L('hourplural'),
+ effort::FORMAT_DAYS_PLAIN_HOURS_ONE_DECIMAL => L('mandays') . ' ' . L('hourplural') . ' (' . L('onedecimal') . ')',
+ effort::FORMAT_DAYS_PLAIN_HOURS_COLON_MINUTES => L('mandays') . ' ' . L('hourplural') . ":" . L('minuteplural'),
+ effort::FORMAT_DAYS_PLAIN_HOURS_SPACE_MINUTES => L('mandays') . ' ' . L('hourplural') . " " . L('minuteplural'),
+ ),
+ Post::val('current_effort_done_format', $proj->prefs['current_effort_done_format'])); ?>
+ </select>
+ </li>
+ </ul>
+ </div>
+
+ <div class="tbuttons">
+ <input type="hidden" name="action" value="pm.updateproject" />
+ <input type="hidden" name="project_id" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <button type="submit" class="positive"><?php echo Filters::noXSS(L('saveoptions')); ?></button>
+ <button type="reset"><?php echo Filters::noXSS(L('resetoptions')); ?></button>
+ </div>
+</form>
+</div>
diff --git a/themes/CleanFS/templates/pm.resolution.tpl b/themes/CleanFS/templates/pm.resolution.tpl
new file mode 100644
index 0000000..451ef5a
--- /dev/null
+++ b/themes/CleanFS/templates/pm.resolution.tpl
@@ -0,0 +1,12 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('resed')); ?></h3>
+<?php
+$this->assign('list_type', 'resolution');
+$this->assign('rows', $proj->listResolutions(true));
+
+$systemwide = new Project(0);
+$this->assign('sysrows', $systemwide->listResolutions(true));
+
+$this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/pm.status.tpl b/themes/CleanFS/templates/pm.status.tpl
new file mode 100644
index 0000000..9c2263e
--- /dev/null
+++ b/themes/CleanFS/templates/pm.status.tpl
@@ -0,0 +1,12 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('taskstatuses')); ?></h3>
+<?php
+$this->assign('list_type', 'status');
+$this->assign('rows', $proj->listTaskStatuses(true));
+
+$systemwide = new Project(0);
+$this->assign('sysrows', $systemwide->listTaskStatuses(true));
+
+$this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/pm.tag.tpl b/themes/CleanFS/templates/pm.tag.tpl
new file mode 100644
index 0000000..7ec3f2d
--- /dev/null
+++ b/themes/CleanFS/templates/pm.tag.tpl
@@ -0,0 +1,14 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS(L('tags')); ?></h3>
+ <p>Tag management is in development.</p>
+ <p>Please see <a href="https://bugs.flyspray.org/2012" target="_blank">bugs.flyspray.org/2012</a> for status of <b>Tags</b> feature.</p>
+<?php
+$this->assign('list_type', 'tag');
+$this->assign('rows', $proj->listTags(true));
+
+$systemwide = new Project(0);
+$this->assign('sysrows', $systemwide->listTags(true));
+
+$this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/pm.tasktype.tpl b/themes/CleanFS/templates/pm.tasktype.tpl
new file mode 100644
index 0000000..6dcd029
--- /dev/null
+++ b/themes/CleanFS/templates/pm.tasktype.tpl
@@ -0,0 +1,12 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('tasktypeed')); ?></h3>
+ <?php
+ $this->assign('list_type', 'tasktype');
+ $this->assign('rows', $proj->listTaskTypes(true));
+
+ $systemwide = new Project(0);
+ $this->assign('sysrows', $systemwide->listTaskTypes(true));
+
+ $this->display('common.list.tpl');
+ ?>
+</div>
diff --git a/themes/CleanFS/templates/pm.version.tpl b/themes/CleanFS/templates/pm.version.tpl
new file mode 100644
index 0000000..0b56fe6
--- /dev/null
+++ b/themes/CleanFS/templates/pm.version.tpl
@@ -0,0 +1,12 @@
+<div id="toolbox">
+ <h3><?php echo Filters::noXSS($proj->prefs['project_title']); ?> : <?php echo Filters::noXSS(L('verlisted')); ?></h3>
+<?php
+$this->assign('list_type', 'version');
+$this->assign('rows', $proj->listVersions(true));
+
+$systemwide = new Project(0);
+$this->assign('sysrows', $systemwide->listVersions(true));
+
+$this->display('common.list.tpl');
+?>
+</div>
diff --git a/themes/CleanFS/templates/profile.tpl b/themes/CleanFS/templates/profile.tpl
new file mode 100644
index 0000000..2c828ef
--- /dev/null
+++ b/themes/CleanFS/templates/profile.tpl
@@ -0,0 +1,70 @@
+<fieldset class="box"><legend><?php echo Filters::noXSS(L('profile')); ?> <?php echo Filters::noXSS($theuser->infos['real_name']); ?> (<?php echo Filters::noXSS($theuser->infos['user_name']); ?>)</legend>
+<table id="profile">
+ <tr>
+ <th><?php echo Filters::noXSS(L('realname')); ?></th>
+ <td><?php echo Filters::noXSS($theuser->infos['real_name']); ?></td>
+ </tr>
+ <?php if ((!$user->isAnon() && !$fs->prefs['hide_emails'] && !$theuser->infos['hide_my_email']) || $user->perms('is_admin')): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('emailaddress')); ?></th>
+ <td><a href="mailto:<?php echo Filters::noXSS($theuser->infos['email_address']); ?>"><?php echo Filters::noXSS($theuser->infos['email_address']); ?></a></td>
+ </tr>
+ <?php endif; ?>
+ <?php if (!empty($fs->prefs['jabber_server']) && (( !$user->isAnon() && !$fs->prefs['hide_emails'] && !$theuser->infos['hide_my_email']) || $user->perms('is_admin')) ): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('jabberid')); ?></th>
+ <td><?php echo Filters::noXSS($theuser->infos['jabber_id']); ?></td>
+ </tr>
+ <?php endif; ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('globalgroup')); ?></th>
+ <td><?php echo Filters::noXSS($groups[Flyspray::array_find('group_id', $theuser->infos['global_group'], $groups)]['group_name']); ?></td>
+ </tr>
+ <?php if ($proj->id): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('projectgroup')); ?></th>
+ <td>
+ <?php if ($user->perms('manage_project')): ?>
+ <?php echo tpl_form(Filters::noXSS($baseurl).'index.php?do=user&amp;id='.Filters::noXSS($theuser->id)); ?>
+ <select id="projectgroupin" class="adminlist" name="project_group_in">
+ <?php $sel = $theuser->perms('project_group') == '' ? 0 : $theuser->perms('project_group'); ?>
+ <?php echo tpl_options(array_merge($project_groups, array(0 => array('group_name' => L('none'), 0 => 0, 'group_id' => 0, 1 => L('none')))), $sel); ?>
+ </select>
+ <input type="hidden" name="old_group_id" value="<?php echo Filters::noXSS($theuser->perms('project_group')); ?>" />
+ <input type="hidden" name="action" value="admin.edituser" />
+ <input type="hidden" name="user_id" value="<?php echo Filters::noXSS($theuser->id); ?>" />
+ <input type="hidden" name="project_id" value="<?php echo $proj->id; ?>" />
+ <input type="hidden" name="onlypmgroup" value="1" />
+ <button type="submit"><?php echo Filters::noXSS(L('update')); ?></button>
+ </form>
+ <?php else: ?>
+ <?php if ($theuser->perms('project_group')): ?>
+ <?php echo Filters::noXSS($project_groups[Flyspray::array_find('group_id', $theuser->perms('project_group'), $project_groups)]['group_name']); ?>
+ <?php else: ?>
+ <?php echo Filters::noXSS(L('none')); ?>
+ <?php endif; ?>
+ <?php endif; ?>
+ </td>
+ </tr>
+ <?php endif; ?>
+ <tr>
+ <th><a href="<?php echo CreateURL('tasklist', 0, null, array('opened'=>$theuser->id, 'status[]'=>'')); ?>"><?php echo Filters::noXSS(L('tasksopened')); ?></a></th>
+ <td><a href="<?php echo CreateURL('tasklist', 0, null, array('opened'=>$theuser->id, 'status[]'=>'')); ?>"><?php echo Filters::noXSS($tasks); ?></a></td>
+ </tr>
+ <tr>
+ <th><a href="<?php echo CreateURL('tasklist', 0, null, array('dev'=>$theuser->id)); ?>"><?php echo Filters::noXSS(L('assignedto')); ?></a></th>
+ <td><a href="<?php echo CreateURL('tasklist', 0, null, array('dev'=>$theuser->id)); ?>"><?php echo Filters::noXSS($assigned); ?></a></td>
+ </tr>
+ <tr>
+ <th><?php echo Filters::noXSS(L('comments')); ?></th>
+ <td><?php echo Filters::noXSS($comments); ?></td>
+ </tr>
+ <?php if ($theuser->infos['register_date']): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('regdate')); ?></th>
+ <td><?php echo Filters::noXSS(formatDate($theuser->infos['register_date'])); ?></td>
+ </tr>
+ <?php endif; ?>
+</table>
+</fieldset>
+<div><?php if($user->perms('is_admin')): ?><a href="<?php echo CreateURL('edituser', $theuser->id); ?>" class="button"><?php echo L('edituser'); ?></a><?php endif; ?></div>
diff --git a/themes/CleanFS/templates/register.magic.tpl b/themes/CleanFS/templates/register.magic.tpl
new file mode 100644
index 0000000..b93e743
--- /dev/null
+++ b/themes/CleanFS/templates/register.magic.tpl
@@ -0,0 +1,31 @@
+<h3><?php echo Filters::noXSS(L('registernewuser')); ?></h3>
+<div class="box">
+ <!--
+<?php echo tpl_form(Filters::noXSS($baseurl).'index.php','registernewuser',null,null,'id="registernewuser"'); ?>
+ -->
+<?php echo tpl_form(Filters::noXSS(CreateUrl('registernewuser')),null,null,null,'id="registernewuser"'); ?>
+ <p><?php echo Filters::noXSS(L('entercode')); ?></p>
+ <ul class="form_elements wide">
+ <li>
+ <label for="confirmation_code"><?php echo Filters::noXSS(L('confirmationcode')); ?></label>
+ <input id="confirmation_code" class="text" name="confirmation_code" value="<?php echo Filters::noXSS(Req::val('confirmation_code')); ?>" type="text" size="20" maxlength="20" />
+ </li>
+
+ <li>
+ <label for="user_pass"><?php echo Filters::noXSS(L('password')); ?></label>
+ <input id="user_pass" class="password" name="user_pass" value="<?php echo Filters::noXSS(Req::val('user_pass')); ?>" type="password" size="20" maxlength="100" /> <em><?php echo Filters::noXSS(L('minpwsize')); ?></em>
+ </li>
+ <?php if($fs->prefs['repeat_password']): ?>
+ <li>
+ <label for="user_pass2"><?php echo Filters::noXSS(L('confirmpass')); ?></label>
+ <input id="user_pass2" class="password" name="user_pass2" value="<?php echo Filters::noXSS(Req::val('user_pass2')); ?>" type="password" size="20" maxlength="100" />
+ </li>
+ <?php endif;?>
+ </ul>
+ <div>
+ <input type="hidden" name="action" value="register.registeruser" />
+ <input type="hidden" name="magic_url" value="<?php echo Filters::noXSS(Req::val('magic_url')); ?>" />
+ <button type="submit" name="buSubmit"><?php echo Filters::noXSS(L('registeraccount')); ?></button>
+ </div>
+</form>
+</div>
diff --git a/themes/CleanFS/templates/register.no-magic.tpl b/themes/CleanFS/templates/register.no-magic.tpl
new file mode 100644
index 0000000..16fedd2
--- /dev/null
+++ b/themes/CleanFS/templates/register.no-magic.tpl
@@ -0,0 +1,78 @@
+<h3><?= eL('registernewuser') ?></h3>
+<div class="box">
+<?php echo tpl_form(Filters::noXSS(createUrl('register')),null,null,null,'id="registernewuser"'); ?>
+ <ul class="form_elements wide">
+ <li>
+ <label for="username"><?= eL('username') ?></label>
+ <input required="required" value="<?php echo Filters::noXSS(Req::val('user_name')); ?>" id="username" name="user_name" type="text" size="20" maxlength="32" onblur="checkname(this.value);" /> <?= eL('validusername') ?><br /><strong><span id="errormessage"></span></strong>
+ </li>
+
+ <li>
+ <label for="realname"><?= eL('realname') ?></label>
+ <input required="required" value="<?php echo Filters::noXSS(Req::val('real_name')); ?>" id="realname" name="real_name" type="text" size="30" maxlength="100" />
+ </li>
+
+ <li>
+ <label for="emailaddress"><?= eL('emailaddress') ?></label>
+ <input id="emailaddress" value="<?php echo Filters::noXSS(Req::val('email_address')); ?>" name="email_address" required="required" type="text" size="20" maxlength="100" /> <?= eL('validemail') ?>
+ </li>
+ <?php if ($fs->prefs['repeat_emailaddress']): ?>
+ <li>
+ <label for="verifyemailaddress"><?= eL('verifyemailaddress') ?></label>
+ <input id="verifyemailaddress" value="<?php echo Filters::noXSS(Req::val('verify_email_address')); ?>" name="verify_email_address" required="required" type="text" size="20" maxlength="100" />
+ </li>
+ <?php endif ?>
+
+ <?php if (!empty($fs->prefs['jabber_server'])): ?>
+ <li>
+ <label for="jabberid"><?= eL('jabberid') ?></label>
+ <input id="jabberid" value="<?php echo Filters::noXSS(Req::val('jabber_id')); ?>" name="jabber_id" type="text" size="20" maxlength="100" />
+ </li>
+ <?php endif ?>
+
+ <li>
+ <label for="notify_type"><?= eL('notifications') ?></label>
+ <select id="notify_type" name="notify_type">
+ <?php echo tpl_options($fs->getNotificationOptions(), Req::val('notify_type')); ?>
+ </select>
+ </li>
+
+ <li>
+ <label for="time_zone"><?= eL('timezone') ?></label>
+ <select id="time_zone" name="time_zone">
+ <?php
+ $times = array();
+ for ($i = -12; $i <= 13; $i++) {
+ $times[$i] = L('GMT') . (($i == 0) ? ' ' : (($i > 0) ? '+' . $i : $i));
+ }
+ ?>
+ <?php echo tpl_options($times, Req::val('time_zone', 0)); ?>
+ </select>
+ </li>
+ <?php if($fs->prefs['captcha_securimage']) : ?>
+ <li class="captchali">
+ <style>
+ #captcha_code{width:100px;}
+ .captchali .securimage label{width:auto;}
+ .captchali .securimage {display:inline-block; width:300px;}
+ </style>
+ <label for="captcha_code"><?= eL('registercaptcha') ?></label>
+ <div class="securimage"><?php echo $captcha_securimage_html; ?></div>
+ </li>
+ <?php endif; ?>
+ </ul>
+ <div>
+ <input type="hidden" name="action" value="register.sendcode" />
+ <?php if(isset($fs->prefs['captcha_recaptcha']) && $fs->prefs['captcha_recaptcha']
+ && isset($fs->prefs['captcha_recaptcha_sitekey']) && $fs->prefs['captcha_recaptcha_sitekey']
+ && isset($fs->prefs['captcha_recaptcha_secret']) && $fs->prefs['captcha_recaptcha_secret']
+ ): ?>
+ <div class="g-recaptcha" data-sitekey="<?php echo Filters::noXSS($fs->prefs['captcha_recaptcha_sitekey']); ?>"></div>
+ <noscript>Javascript is required for this Google reCAPTCHA.</noscript>
+ <?php endif; ?>
+ <button type="submit" name="buSubmit" id="buSubmit"><?= eL('sendcode') ?></button>
+ </div>
+ <br />
+ <p><?= L('note') ?></p>
+</form>
+</div>
diff --git a/themes/CleanFS/templates/register.oauth.tpl b/themes/CleanFS/templates/register.oauth.tpl
new file mode 100644
index 0000000..0e6ed38
--- /dev/null
+++ b/themes/CleanFS/templates/register.oauth.tpl
@@ -0,0 +1,15 @@
+<h3><?php echo Filters::noXSS(L('register')); ?></h3>
+<div class="box">
+
+<form action="index.php?do=oauth" method="post" id="registernewuser">
+ <ul class="form_elements">
+ <li>
+ <label for="username"><?php echo Filters::noXSS(L('username')); ?></label>
+ <input class="required text" value="<?php echo Filters::noXSS($username); ?>" id="user_name" name="username" type="text" size="20" maxlength="32" /> <?php echo Filters::noXSS(L('validusername')); echo '<span class="warning"> ' . Filters::noXSS(L('usernametaken')) . '</span>'; ?>
+ </li>
+ </ul>
+ <div>
+ <button type="submit" name="buSubmit" id="buSubmit"><?php echo Filters::noXSS(L('register')); ?></button>
+ </div>
+</form>
+</div>
diff --git a/themes/CleanFS/templates/register.ok.tpl b/themes/CleanFS/templates/register.ok.tpl
new file mode 100644
index 0000000..3bc05a8
--- /dev/null
+++ b/themes/CleanFS/templates/register.ok.tpl
@@ -0,0 +1,4 @@
+<div class="redirectmessage">
+ <p><?= eL('loginbelow') ?></p>
+ <p><?= eL('newuserwarning') ?></p>
+</div>
diff --git a/themes/CleanFS/templates/reports.tpl b/themes/CleanFS/templates/reports.tpl
new file mode 100644
index 0000000..f7b0cdf
--- /dev/null
+++ b/themes/CleanFS/templates/reports.tpl
@@ -0,0 +1,120 @@
+<?php
+if(isset($theuser->infos['eventtypes'])){
+ $eventpref=$theuser->infos['eventtypes'];
+}else{
+ $eventpref=array_keys($events);
+ $usereventpref=array_keys($user_events);
+}
+?>
+<h3><?php echo Filters::noXSS(L('eventlog')); ?></h3>
+<div class="box">
+ <form action="<?php echo Filters::noXSS(CreateURL('reports', $proj->id)); ?>" method="get">
+ <table id="event1">
+ <tr>
+ <td><label for="events[]"><?php echo Filters::noXSS(L('events')); ?></label></td>
+ <td>
+ <select name="events[]" class='eventlist<?php echo $histories ? ' hasresult':''; ?>' multiple="multiple" id="events[]" size="<?php echo Filters::noXSS(count($events)+count($user_events)+2); ?>">
+ <optgroup label="<?php echo Filters::noXSS(L('Tasks')); ?>">
+ <?php echo tpl_options($events, Req::val('events', $eventpref)); ?>
+ </optgroup>
+ <optgroup label="<?php echo Filters::noXSS(L('users')); ?>">
+ <?php echo tpl_options($user_events, Req::val('events', $usereventpref)); ?>
+ </optgroup>
+ </select>
+ </td>
+ <td>
+ <div>
+ <label class="inline" for="fromdate"><?php echo Filters::noXSS(L('from')); ?></label>
+ <?php echo tpl_datepicker('fromdate'); ?>
+ <?php echo tpl_datepicker('todate', L('to')); ?>
+ </div>
+ <div>
+ <label for="event_number"><?php echo Filters::noXSS(L('show')); ?></label>
+ <select name="event_number" id="event_number">
+ <?php
+ # set 20 to 25 like in tasks_per_page because we use same settings here too
+ echo tpl_options(array(-1 => L('all'), 10 => 10, 25 => 25, 50 => 50, 100 => 100, 200 => 200),
+ Req::val('event_number', isset($theuser->infos['tasks_perpage']) ? $theuser->infos['tasks_perpage'] : 50)); ?>
+ </select>
+ <?php echo Filters::noXSS(L('events')); ?>
+ </div>
+ </td>
+ </tr>
+ </table>
+
+ <input type="hidden" name="project" value="<?php echo Filters::noXSS($proj->id); ?>" />
+ <input type="hidden" name="do" value="reports" />
+ <button type="submit" name="submit"><?php echo Filters::noXSS(L('show')); ?></button>
+ </form>
+
+ <?php if ($histories): ?>
+ <div id="tasklist">
+ <table id="eventlist_table">
+ <thead>
+ <tr>
+ <th>
+ <a href="<?php echo Filters::noXSS(CreateURL('reports', $proj->id, null, array('sort' => (Req::val('order') == 'type' && $sort == 'DESC') ? 'asc' : 'desc', 'order' => 'type') + $_GET)); ?>">
+ <?php echo Filters::noXSS(L('event')); ?>
+
+ </a>
+ </th>
+ <th>
+ <a href="<?php echo Filters::noXSS(CreateURL('reports', $proj->id, null, array('sort' => (Req::val('order') == 'user' && $sort == 'DESC') ? 'asc' : 'desc', 'order' => 'user') + $_GET)); ?>">
+ <?php echo Filters::noXSS(L('user')); ?>
+
+ </a>
+ </th>
+ <th>
+ <a href="<?php echo Filters::noXSS(CreateURL('reports', $proj->id, null, array('sort' => (Req::val('order') == 'date' && $sort == 'DESC') ? 'asc' : 'desc', 'order' => 'date') + $_GET)); ?>">
+ <?php echo Filters::noXSS(L('eventdate')); ?>
+
+ </a>
+ </th>
+ <th><?php echo Filters::noXSS(L('summary')); ?></th>
+ </tr>
+ </thead>
+ <?php foreach ($histories as $history): ?>
+ <?php if (isset($events[$history['event_type']])): ?>
+ <tr class="severity1"><?php /* just for different colors */ ?>
+ <td><?php echo Filters::noXSS($events[$history['event_type']]); ?></td>
+ <?php else: ?>
+ <tr class="severity2">
+ <td><?php echo Filters::noXSS($user_events[$history['event_type']]); ?></td>
+ <?php endif; ?>
+ <td><?php echo tpl_userlink($history['user_id']); ?></td>
+ <td><?php echo Filters::noXSS(formatDate($history['event_date'], true)); ?></td>
+ <?php if ($history['event_type'] == 30 ||
+ $history['event_type'] == 31):
+ $user_data = unserialize($history['new_value']); ?>
+ <td>
+ <a href="javascript:showhidestuff('h<?php echo Filters::noXSS($history['history_id']); ?>')"><?php echo Filters::noXSS(L('detailedinfo')); ?></a>
+ <div class="hide popup" id="h<?php echo Filters::noXSS($history['history_id']); ?>">
+ <table>
+ <tr>
+ <th><?php echo Filters::noXSS(L('username')); ?></th>
+ <td><?php echo Filters::noXSS($user_data['user_name']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo Filters::noXSS(L('realname')); ?></th>
+ <td><?php echo Filters::noXSS($user_data['real_name']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo Filters::noXSS(L('email')); ?></th>
+ <td><?php echo Filters::noXSS($user_data['email_address']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo Filters::noXSS(L('jabber')); ?></th>
+ <td><?php echo Filters::noXSS($user_data['jabber_id']); ?></td>
+ </tr>
+ </table>
+ </div>
+ </td>
+ <?php else: ?>
+ <td><?php echo tpl_tasklink($history); ?></td>
+ <?php endif; ?>
+ </tr>
+ <?php endforeach; ?>
+ </table>
+ </div>
+ <?php endif; ?>
+</div>
diff --git a/themes/CleanFS/templates/roadmap.text.tpl b/themes/CleanFS/templates/roadmap.text.tpl
new file mode 100644
index 0000000..7d33199
--- /dev/null
+++ b/themes/CleanFS/templates/roadmap.text.tpl
@@ -0,0 +1,51 @@
+=== <?php echo Filters::noXSS($proj->prefs['project_title']); ?> ===
+
+<?php foreach($data as $milestone): ?>
+<?php echo Filters::noXSS(L('roadmapfor')); ?> <?php echo Filters::noXSS($milestone['name']); ?>
+
+
+<?php echo Filters::noXSS($milestone['percent_complete']); ?><?php echo Filters::noXSS(L('of')); ?> <?php echo Filters::noXSS(count($milestone['all_tasks'])); ?> <?php echo Filters::noXSS(L('tasks')); ?> <?php echo Filters::noXSS(L('completed')); ?> <?php
+ if(count($milestone['open_tasks'])):
+ ?><?php echo Filters::noXSS(count($milestone['open_tasks'])); ?> <?php echo Filters::noXSS(L('opentasks')); ?>:<?php
+ endif; ?>
+<?php
+ if ($proj->prefs['use_effort_tracking']) {
+ $total_estimated = 0;
+ $actual_effort = 0;
+
+ foreach($milestone['open_tasks'] as $task) {
+ $total_estimated += $task['estimated_effort'];
+ $effort = new effort($task['task_id'],0);
+ $effort->populateDetails();
+
+ foreach($effort->details as $details) {
+ $actual_effort += $details['effort'];
+ }
+ $effort = null;
+ }
+ // }
+?>
+
+<?php
+ if ($user->perms('view_estimated_effort')) {
+ echo Filters::noXSS(L('opentasks')); ?> - <?php echo Filters::noXSS(L('totalestimatedeffort')); ?>: <?php echo effort::SecondsToString($total_estimated, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+} ?>
+
+<?php
+ if ($user->perms('view_current_effort_done')) {
+ echo Filters::noXSS(L('opentasks')); ?> - <?php echo Filters::noXSS(L('currenteffortdone')); ?>: <?php echo effort::SecondsToString($actual_effort, $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
+} ?>
+<?php } ?>
+
+<?php if(count($milestone['open_tasks'])): ?>
+
+<?php foreach($milestone['open_tasks'] as $task):
+ if(!$user->can_view_task($task)) continue; ?>
+FS#<?php echo Filters::noXSS($task['task_id']); ?> - <?php echo Filters::noXSS($task['item_summary']); ?>
+
+
+<?php endforeach; ?>
+
+<?php endif; ?>
+
+<?php endforeach; ?>
diff --git a/themes/CleanFS/templates/roadmap.tpl b/themes/CleanFS/templates/roadmap.tpl
new file mode 100644
index 0000000..2ec8a48
--- /dev/null
+++ b/themes/CleanFS/templates/roadmap.tpl
@@ -0,0 +1,116 @@
+<script type="text/javascript">
+
+function hideAll(allTasks)
+{
+ for (i = 0; i < allTasks.length; i++) {
+ if (!allTasks[i]) continue;
+ hidestuff('dd'+ allTasks[i]);
+ hidestuff('hide'+ allTasks[i]);
+ showstuff('expand'+ allTasks[i], 'inline');
+ }
+}
+
+function showAll(allTasks)
+{
+ for (i = 0; i < allTasks.length; i++) {
+ if (!allTasks[i]) continue;
+ showstuff('dd'+ allTasks[i]);
+ hidestuff('expand'+ allTasks[i]);
+ showstuff('hide'+ allTasks[i], 'inline');
+ }
+}
+</script>
+
+<?php foreach($data as $milestone): ?>
+
+<script type="text/javascript">
+allTasks<?php echo Filters::noXSS($milestone['id']); ?> = [<?php foreach($milestone['open_tasks'] as $task): echo $task['task_id'] . ','; endforeach; ?>];
+</script>
+
+<div class="box roadmap">
+<h3><?php echo Filters::noXSS(L('roadmapfor')); ?> <?php echo Filters::noXSS($milestone['name']); ?>
+
+ <?php if (count($milestone['open_tasks'])): ?>
+ <small class="DoNotPrint">
+ <a href="javascript:showAll(allTasks<?php echo Filters::noXSS($milestone['id']); ?>)"><?php echo Filters::noXSS(L('expandall')); ?></a> |
+ <a href="javascript:hideAll(allTasks<?php echo Filters::noXSS($milestone['id']); ?>)"><?php echo Filters::noXSS(L('collapseall')); ?></a>
+ </small>
+ <?php endif; ?>
+</h3>
+<div class="progress_bar_container" style="width: 250px;">
+ <span><?php echo Filters::noXSS($milestone['percent_complete']); ?>%</span>
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($milestone['percent_complete']); ?>%"></div>
+</div>
+<p style="margin-top: 5px;"><?php echo Filters::noXSS($milestone['percent_complete']); ?><?php echo Filters::noXSS(L('of')); ?>
+
+ <a href="<?php echo Filters::noXSS($baseurl); ?>index.php?do=index&amp;tasks=&amp;project=<?php echo Filters::noXSS($proj->id); ?>&amp;due=<?php echo Filters::noXSS($milestone['id']); ?>&amp;status[]=">
+ <?php echo Filters::noXSS(count($milestone['all_tasks'])); ?> <?php echo Filters::noXSS(L('tasks')); ?>
+
+ </a> <?php echo Filters::noXSS(L('completed')); ?>
+
+ <?php if(count($milestone['open_tasks'])): ?>
+ <a href="<?php echo Filters::noXSS($baseurl); ?>index.php?do=index&amp;tasks=&amp;project=<?php echo Filters::noXSS($proj->id); ?>&amp;due=<?php echo Filters::noXSS($milestone['id']); ?>"><?php echo Filters::noXSS(count($milestone['open_tasks'])); ?> <?php echo Filters::noXSS(L('opentasks')); ?>:</a>
+ <?php endif; ?>
+ <?php
+ if ($proj->prefs['use_effort_tracking']) {
+ $total_estimated = 0;
+ $actual_effort = 0;
+
+ foreach($milestone['open_tasks'] as $task) {
+ $total_estimated += $task['estimated_effort'];
+ $effort = new effort($task['task_id'],0);
+ $effort->populateDetails();
+
+ foreach($effort->details as $details) {
+ $actual_effort += $details['effort'];
+ }
+ $effort = null;
+ }
+ // }
+ ?>
+ <br />
+ <?php
+ if ($user->perms('view_estimated_effort')) {
+ echo Filters::noXSS(L('opentasks')); ?> - <?php echo Filters::noXSS(L('totalestimatedeffort')); ?>: <?php echo effort::SecondsToString($total_estimated, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ } ?>
+ <br />
+ <?php
+ if ($user->perms('view_current_effort_done')) {
+ echo Filters::noXSS(L('opentasks')); ?> - <?php echo Filters::noXSS(L('currenteffortdone')); ?>: <?php echo effort::SecondsToString($actual_effort, $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
+ } ?>
+ <?php }
+ ?>
+</p>
+
+<?php if(count($milestone['open_tasks'])): ?>
+<dl class="roadmap">
+ <?php foreach($milestone['open_tasks'] as $task): ?>
+ <dt class="severity<?php echo Filters::noXSS($task['task_severity']); ?>">
+ <i class="fa typ<?php echo $task['task_type']; ?>"></i>
+ <?php echo tpl_tasklink($task['task_id']); ?>
+
+ <small class="DoNotPrint">
+ <a id="expand<?php echo Filters::noXSS($task['task_id']); ?>" href="javascript:showstuff('dd<?php echo Filters::noXSS($task['task_id']); ?>');hidestuff('expand<?php echo Filters::noXSS($task['task_id']); ?>');showstuff('hide<?php echo Filters::noXSS($task['task_id']); ?>', 'inline')"><?php echo Filters::noXSS(L('expand')); ?></a>
+ <a class="hide" id="hide<?php echo Filters::noXSS($task['task_id']); ?>" href="javascript:hidestuff('dd<?php echo Filters::noXSS($task['task_id']); ?>');hidestuff('hide<?php echo Filters::noXSS($task['task_id']); ?>');showstuff('expand<?php echo Filters::noXSS($task['task_id']); ?>', 'inline')"><?php echo Filters::noXSS(L('collapse')); ?></a>
+ </small>
+ </dt>
+ <dd id="dd<?php echo Filters::noXSS($task['task_id']); ?>" style="display: none;">
+ <?php echo TextFormatter::render($task['detailed_desc'], 'rota', $task['task_id'], $task['content']); ?>
+
+ <br style="position:absolute;" />
+ </dd>
+ <?php endforeach; ?>
+</dl>
+
+<?php endif; ?>
+</div>
+<?php endforeach; ?>
+
+<?php if (!count($data)): ?>
+<div class="box roadmap">
+<p><em><?php echo Filters::noXSS(L('noroadmap')); ?></em></p>
+</div>
+<?php else: ?>
+<p><a href="<?php echo Filters::noXSS(CreateURL('roadmap', $proj->id, null, array('txt' => 'true'))); ?>">
+<!--<img src="<?php echo Filters::noXSS($this->get_image('mime/text')); ?>" alt="" />--> <?php echo Filters::noXSS(L('textversion')); ?></a></p>
+<?php endif; ?>
diff --git a/themes/CleanFS/templates/shortcuts.tpl b/themes/CleanFS/templates/shortcuts.tpl
new file mode 100644
index 0000000..b6905a4
--- /dev/null
+++ b/themes/CleanFS/templates/shortcuts.tpl
@@ -0,0 +1,31 @@
+<input type="checkbox" id="s_shortcuts" />
+<label for="s_shortcuts" id="shortcutlabel"><i class="fa fa-keyboard-o"></i> <?php echo Filters::noXSS(L('keyboardshortcuts')); ?></label>
+<label for="s_shortcuts" id="shortcutsmodal"></label>
+<div id="shortcuts">
+<label for="s_shortcuts" id="shortcutclose"><i class="fa fa-close fa-2x"></i></label>
+<h3><?php echo Filters::noXSS(L('availablekeybshortcuts')); ?></h3>
+<h4></h4>
+<ul>
+<li><kbd>SHIFT+ALT+l</kbd> <?php echo Filters::noXSS(L('logindialoglogout')); ?></li>
+<li><kbd>SHIFT+ALT+a</kbd> <?php echo Filters::noXSS(L('addnewtask')); ?></li>
+<li><kbd>SHIFT+ALT+m</kbd> <?php echo Filters::noXSS(L('mysearch')); ?></li>
+<li><kbd>SHIFT+ALT+t</kbd> <?php echo Filters::noXSS(L('focustaskidsearch')); ?></li>
+</ul>
+<h4><?php echo Filters::noXSS(L('tasklist')); ?></h4>
+<ul>
+<li><kbd>o</kbd> <?php echo Filters::noXSS(L('openselectedtask')); ?></li>
+<li><kbd>j</kbd> <?php echo Filters::noXSS(L('movecursordown')); ?></li>
+<li><kbd>k</kbd> <?php echo Filters::noXSS(L('movecursorup')); ?></li>
+</ul>
+<h4><?php echo Filters::noXSS(L('taskdetails')); ?></h4>
+<ul>
+<li><kbd>n</kbd> <?php echo Filters::noXSS(L('nexttask')); ?></li>
+<li><kbd>p</kbd> <?php echo Filters::noXSS(L('previoustask')); ?></li>
+<li><kbd>SHIFT+ALT+e</kbd> <kbd>ENTER</kbd> <?php echo Filters::noXSS(L('edittask')); ?></li>
+<li><kbd>SHIFT+ALT+y</kbd> <?php echo Filters::noXSS(L('closetask')); ?></li>
+</ul>
+<h4><?php echo Filters::noXSS(L('taskediting')); ?></h4>
+<ul>
+<li><kbd>SHIFT+ALT+s</kbd> <?php echo Filters::noXSS(L('savetask')); ?></li>
+</ul>
+</div>
diff --git a/themes/CleanFS/templates/toplevel.tpl b/themes/CleanFS/templates/toplevel.tpl
new file mode 100644
index 0000000..78766d6
--- /dev/null
+++ b/themes/CleanFS/templates/toplevel.tpl
@@ -0,0 +1,169 @@
+<?php $project_count = count($projects);
+
+/* If user has no projects, just redirect them to the index page of All Projects */
+if (!$project_count): ?>
+ <meta http-equiv="Refresh" content="0;url=/index.php?project=0&do=index" />
+<?php endif; ?>
+<style type="text/css">
+.activity::after {
+content: "\25B4";
+position: absolute;
+right: -3px;
+bottom:-5px;
+text-align: right;
+color:#c00;
+}
+.activity img{
+padding-right:1px;
+background-color:#c00;
+}
+.activity {
+display: block;
+position: relative;
+width: 160px;
+}
+#s_inactive {display:none;}
+#s_inactive ~ .box {display: none;}
+#s_inactive:checked ~ .box {display: inline-block;}
+</style>
+<?php
+# $projects are now sorted active first, then inactive
+$lastprojectactive=1;
+foreach ($projects as $project): ?>
+ <?php if( count($projects)>1 && $lastprojectactive==1 && $project['project_is_active']==0) : ?>
+ <div style="clear:both;padding-top:20px;border-bottom:1px solid #999;"></div>
+ <input type="checkbox" id="s_inactive" />
+ <label class="button" style="display:block;width:100px;" for="s_inactive"><?php echo Filters::noXSS(L('showinactive')); ?></label>
+ <?php endif; ?>
+ <?php $lastprojectactive=$project['project_is_active']; ?>
+
+<div class="box<?php if ($project_count == 1) echo ' single-project' ?>">
+<h2><a href="<?php echo Filters::noXSS(CreateUrl('project', $project['project_id'])); ?>"><?php echo Filters::noXSS($project['project_title']); ?></a></h2>
+
+<table class="toplevel">
+<?php if($user->can_view_project($project['project_id'])): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('viewtasks')); ?></th>
+ <td>
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('status[]'=>'')); ?>"><?php echo Filters::noXSS(L('All')); ?></a> -
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('status[]'=>'open')); ?>"><?php echo Filters::noXSS(L('open')); ?></a> -
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('openedfrom'=>'-1 week')); ?>"><?php echo Filters::noXSS(L('recentlyopened')); ?></a>
+ <?php if (!$user->isAnon()): ?>
+ <br />
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('dev'=>$user->id, 'devsm'=>'userid')); ?>"><?php echo Filters::noXSS(L('assignedtome')); ?></a> -
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('only_watched'=>1)); ?>"><?php echo Filters::noXSS(L('taskswatched')); ?></a> -
+ <a href="<?php echo CreateURL('tasklist', $project['project_id'], null, array('opened'=>$user->id, 'openedsm'=>'userid')); ?>"><?php echo Filters::noXSS(L('tasksireported')); ?></a>
+ <?php endif; ?>
+ </td>
+
+ <?php if ($project_count == 1 and isset($most_wanted[$project['project_id']])): ?>
+ <td rowspan="4">
+ <strong><?php echo Filters::noXSS(L('mostwanted')); ?></strong>
+ <ul>
+ <?php foreach($most_wanted[$project['project_id']] as $task): ?>
+ <li><?php echo tpl_tasklink($task['task_id']); ?>, <?php echo Filters::noXSS($task['num_votes']); ?> <?php echo ($task['num_votes']==1) ? Filters::noXSS(L('vote')) : Filters::noXSS(L('votes')); ?></li>
+ <?php endforeach; ?>
+ </ul>
+ </td>
+ <?php endif; ?>
+ <?php if ($project_count == 1 and isset($assigned_to_myself[$project['project_id']])): ?>
+ <td rowspan="4">
+ <strong><?php echo Filters::noXSS(L('assignedtome')); ?></strong>
+ <ul>
+ <?php foreach($assigned_to_myself[$project['project_id']] as $task): ?>
+ <li><?php echo tpl_tasklink($task['task_id']); ?></li>
+ <?php endforeach; ?>
+ </ul>
+ </td>
+ <?php endif; ?>
+ </tr>
+<?php endif; ?>
+<?php
+# lets say if someone can view normal tasks of a project, then activity graphs are allowed.
+if($user->can_view_project($project['project_id']) ) : ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('activity')); ?></th>
+ <td><span class="activity" title="red line=today"><img width="160px" height="25px" src="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?line=0066CC&amp;do=activity&amp;project_id=<?php echo Filters::noXSS($project['project_id']); ?>&amp;graph=project"/></span></td>
+ </tr>
+ <?php if (!$user->isAnon()): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('myactivity')); ?></th>
+ <td><span class="activity" title="red line=today"><img width="160px" height="25px" src="<?php echo Filters::noXSS($_SERVER['SCRIPT_NAME']); ?>?line=0066CC&amp;do=activity&amp;user_id=<?php echo Filters::noXSS($user->id); ?>&amp;project_id=<?php echo Filters::noXSS($project['project_id']); ?>&amp;graph=user"/></span></td>
+ </tr>
+ <?php endif; ?>
+<?php endif; ?>
+<?php if($projprefs[$project['project_id']]['others_viewroadmap'] || ($user->perms('view_roadmap', $project['project_id'])) ) : ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('stats')); ?></th>
+ <td><?php echo Filters::noXSS($stats[$project['project_id']]['open']); ?> <?php echo Filters::noXSS(L('opentasks')); ?>, <?php echo Filters::noXSS($stats[$project['project_id']]['all']); ?> <?php echo Filters::noXSS(L('totaltasks')); ?>.</td>
+ </tr>
+ <tr>
+ <th><?php echo Filters::noXSS(L('progress')); ?></th>
+ <td>
+ <?php echo Filters::noXSS($stats[$project['project_id']]['average_done']); ?>% <?php echo Filters::noXSS(L('done')); ?>
+
+ <?php $progressbar_value = $stats[$project['project_id']]['average_done']; ?>
+
+ <div class="progress_bar_container">
+ <span><?php echo Filters::noXSS($stats[$project['project_id']]['average_done']); ?>%</span>
+ <div class="progress_bar" style="width:<?php echo Filters::noXSS($stats[$project['project_id']]['average_done']); ?>%"></div>
+ </div>
+ </td>
+ </tr>
+<?php endif; ?>
+<?php
+ if($projprefs[$project['project_id']]['use_effort_tracking']) :
+ $total_estimated = 0;
+ $actual_effort = 0;
+
+ if(isset($stats[$project['project_id']]['tasks'])) :
+ foreach($stats[$project['project_id']]['tasks'] as $task) {
+ $total_estimated += $task['estimated_effort'];
+ $effort = new effort($task['task_id'],0);
+ $effort->populateDetails();
+
+ foreach($effort->details as $details) {
+ $actual_effort += $details['effort'];
+ }
+ $effort = null;
+ }
+ endif;
+
+ if ($user->perms('view_estimated_effort', $project['project_id'])) : ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('estimatedeffortopen')); ?></th>
+ <td><?php echo effort::SecondsToString($total_estimated, $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']); ?></td>
+ </tr>
+ <?php endif; ?>
+ <?php if ($user->perms('view_current_effort_done', $project['project_id'])) : ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('currenteffortdoneopen')); ?></th>
+ <td><?php echo effort::SecondsToString($actual_effort, $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']); ?></td>
+ </tr>
+ <?php endif; ?>
+ <?php endif; ?>
+<?php if($projprefs[$project['project_id']]['others_view']==1): ?>
+ <tr>
+ <th><?php echo Filters::noXSS(L('feeds')); ?></th>
+ <td>
+ <b><?php echo Filters::noXSS(L('rss')); ?> 1.0</b> <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss1&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('opened')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss1&amp;topic=edit&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('edited')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss1&amp;topic=clo&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('closed')); ?></a>
+ <br />
+ <b><?php echo Filters::noXSS(L('rss')); ?> 2.0</b> <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss2&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('opened')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss2&amp;topic=edit&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('edited')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=rss2&amp;topic=clo&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('closed')); ?></a>
+ <br />
+ <b><?php echo Filters::noXSS(L('atom')); ?></b> <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=atom&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('opened')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=atom&amp;topic=edit&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('edited')); ?></a> -
+ <a href="<?php echo Filters::noXSS($baseurl); ?>feed.php?feed_type=atom&amp;topic=clo&amp;project=<?php echo Filters::noXSS($project['project_id']); ?>"><?php echo Filters::noXSS(L('closed')); ?></a>
+ </td>
+ </tr>
+<?php endif; ?>
+</table>
+</div>
+<?php
+endforeach;
+?>
+
+<div class="clear"></div>
diff --git a/themes/CleanFS/theme.css b/themes/CleanFS/theme.css
new file mode 100644
index 0000000..c4600b4
--- /dev/null
+++ b/themes/CleanFS/theme.css
@@ -0,0 +1,1511 @@
+/* ------------------ IMPORTS -------------------- */
+@import url("../../js/jscalendar/calendar-system.css");
+@import url("geshi.css");
+@import url("archnavbar.css");
+/*@import url("./fonts/octicons/octicons.css");*/
+/*@import url("calendar.css");*/
+/* reset.css - Resets default browser CSS.
+putting it here saves one extra http request.
+*/
+html { margin:0;padding:0;border:0;}
+body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, code,
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+margin: 0;padding: 0;border: 0;font-size: 100%;vertical-align: baseline;
+}
+/* This helps to make newer HTML5 elements behave like DIVs in older browsers */
+article, aside, details, figcaption, figure, dialog,
+footer, header, hgroup, menu, nav, section {
+display:block;
+}
+
+/* Central default color scheme for Flyspray.
+You can overwrite it by grouping the same css rules to your colors later in the file.
+Or better load an extra 'customxxx.css' file in the header.tpl after loading theme.css.
+*/
+.button,
+button,
+input[type=submit],
+input[type=text],
+input[type=password],
+fieldset,
+body.toplevel .box,
+div.box,
+p.box,
+div.roadmap,
+.descbox,
+#toolboxmenu a.active,
+#submenu a.active,
+#tasklist,
+#search,
+#search fieldset,
+#intromessage,
+#toolbox,
+#toolbox div.tab,
+#taskdetails,
+#taskdetails #showvotes,
+#comments, #related, #notify, #remind, #effort, #history,
+#events,
+div#taskclosed,
+#tasklist_table th,
+#controlBox {
+ background-color:#fff;
+}
+div.denyform,
+div.popup, div#mysearches {
+ background-color:#fafafa;
+}
+tr.active,
+body a.button.positive, body button.positive,
+#pm-menu-list a.active, #pm-menu-list a.active:hover,
+#menu-list a.active, #menu-list a.active:hover,
+#s_loginbox:checked ~ #show_loginbox,
+#s_quickactions:checked ~ #actions,
+#menu-list a#show_loginbox:hover {
+ background-color: #6af;
+}
+body a.button.positive, body button.positive {
+ color: #fff;
+}
+
+#disp_intro:not(:checked) ~ .disp_introdep{
+ background-color:#ddd;
+}
+#disp_intro:not(:checked) ~ .disp_introdep select,
+#disp_intro:not(:checked) ~ .disp_introdep textarea{
+ background-color:#eee;
+ color:#666;
+}
+
+/*
+ highlight advanced search fields that have input
+ no pure CSS styling of options of multiselects possible (it was back in time in some web browsers, a shame!)
+*/
+#search #searchtext:not(:placeholder-shown),
+#search #duedatefrom:not(:placeholder-shown), #search #duedateto:not(:placeholder-shown),
+#search #changedfrom:not(:placeholder-shown), #search #changedto:not(:placeholder-shown),
+#search #openedfrom:not(:placeholder-shown), #search #openedto:not(:placeholder-shown),
+#search #closedfrom:not(:placeholder-shown), #search #closedto:not(:placeholder-shown),
+#search input.users:not(:placeholder-shown) {
+ background-color: #ff9;
+}
+
+/* end default Flyspray color scheme */
+
+body {
+line-height: 1.5; /* Line-height should always be unitless! */
+/* direction:rtl; */
+}
+table {border-collapse: separate;border-spacing: 0;}
+caption, th, td {
+text-align: left;
+font-weight: normal;
+float:none !important; /* float:none prevents the span-x classes from breaking table-cell display */
+}
+table, th, td {vertical-align: middle;}
+/* Remove possible quote marks (") from <q>, <blockquote>. */
+blockquote:before, blockquote:after, q:before, q:after { content: ''; }
+blockquote, q { quotes: "" ""; }
+/* Remember to define your own focus styles! */
+:focus { outline: 0; }
+/* end import reset.css */
+
+/* start typography.css */
+/* no colors here. Lets do colors,font-style and text-decoration in extra css files for easy customization */
+body {font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; font-size:12px;}
+h1,h2,h3,h4,h5,h6 { font-weight: normal; }
+h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
+h2 { font-size: 2em; margin-bottom: 0.75em; }
+h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
+h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
+h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
+h6 { font-size: 1em; font-weight: bold; }
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {
+margin: 0;
+}
+p { margin: 0 0 1.5em; }
+/*
+These can be used to pull an image at the start of a paragraph, so
+that the text flows around it (usage: <p><img class="left">Text</p>)
+*/
+/*.left { float: left !important; }*/
+p .left { margin: 1.5em 1.5em 1.5em 0; padding: 0; }
+/*.right { float: right !important; }*/
+p .right { margin: 1.5em 0 1.5em 1.5em; padding: 0; }
+/*
+a:focus, a:hover { color: #09f; }
+a { color: #06c; text-decoration: underline; }
+*/
+blockquote { margin: 1.5em; font-style: italic;}
+strong, dfn { font-weight: bold; }
+em, dfn { font-style: italic; }
+em.u {text-decoration: underline;} /* dokuwiki underline*/
+sup, sub { line-height: 0; }
+/*
+abbr,acronym { border-bottom: 1px dotted #666; }
+*/
+address { margin: 0 0 1.5em; font-style: italic; }
+/*
+del { color:#666; }
+*/
+pre { margin: 1.5em 0; white-space: pre; }
+pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
+/* only matches pre tags if there is a language set too e.g. '<pre class="php code">' */
+pre[class*=" code"]::before {
+ background: #eee none repeat scroll 0 0;
+ content: attr(class);
+ display: block;
+}
+pre.code {
+ background-color: #fff;
+ border: 1px solid #ddd;
+ overflow:auto;
+}
+
+li ul, li ol { margin: 0; }
+ul, ol { margin: 0 1.5em 1.5em 0; padding-left: 2em; }
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+li.level1 {padding-bottom:0.2em;}
+dl { margin: 0 0 1.5em 0; }
+dl dt { font-weight: bold; }
+dd { margin-left: 1.5em;}
+/*
+Because of the need for padding on th and td, the vertical rhythm
+on table cells has to be 27px, instead of the standard 18px or 36px
+of other elements.
+*/
+table { margin-bottom: 1.4em; }
+th { font-weight: bold; }
+th,td,caption { padding: 4px 10px 4px 5px; }
+tfoot { font-style: italic; }
+/*
+caption { background: #eee; }
+*/
+.small { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
+.large { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
+.hide { display: none; }
+/*
+.quiet { color: #666; }
+.loud { color: #000; }
+.highlight { background:#ff0; }
+.added { background:#060; color: #fff; }
+.removed { background:#900; color: #fff; }
+*/
+.first { margin-left:0; }
+.last { margin-right:0; padding-right:0; }
+.top { margin-top:0; padding-top:0; }
+.bottom { margin-bottom:0; padding-bottom:0; }
+
+/* ----------------- VARIABLES --------------------- */
+/*$far_background: #dcdcdc;*/
+/*$far_background: #ededed;*/
+/*$top1: lighten(#292626, 2%);*/
+/*$dominant: #2C6FB2;*/
+/*$dominant: #A82F21;*/
+/*$link: #0066CC;*/
+/*lighten(#F9F2F2, 1%);*/
+/* ----------------- MIXINS --------------------- */
+/* ------------------ HELPER CLASSES -------------------- */
+
+.hide {
+ display: none;
+ visibility: hidden;
+}
+.clear {clear: both;}
+.fade {color: gray; background: pink !important; border: 1px solid blue !important;}
+.search_hit {background: red !important; border: 1px solid green !important;}
+img:-moz-broken {
+ /* show broken images */
+ -moz-force-broken-image-icon: 1;
+ width: 24px;
+ height: 24px;
+ border: solid 2px red;
+}
+div.popup {
+ position: absolute;
+ border: #e1e1e1 1px solid;
+ margin-top: 5px;
+ padding: 5px;
+ -moz-box-shadow: 0px 1px 2px #f9f9f9;
+ -webkit-box-shadow: 0px 1px 2px #f9f9f9;
+ box-shadow: 0px 1px 2px #f9f9f9;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color')";
+ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color');
+}
+
+/* ------------------ STYLE -------------------- */
+body {background: #f9f9f9; /* direction:rtl;*/}
+a {color: #336699;}
+a:hover {color: #6699cc;}
+#container { min-width: 1000px; position: relative; text-align: left; }
+#content { padding: 10px 20px 10px 20px; min-width: 660px; }
+#footer {
+ display: block;
+ margin: 0px 20px 20px 20px;
+ padding-top: 10px;
+ text-align: right;
+}
+#title {
+ background-color: #3c4041; /* fallback/image non-cover color */
+ background-image: -moz-linear-gradient(#3c4041, #242627); /* Firefox 3.6+ */
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#3c4041), to(#242627)); /* Safari 4+, Chrome 1+ */
+ background-image: -webkit-linear-gradient(#3c4041, #242627); /* Safari 5.1+, Chrome 10+ */
+ background-image: linear-gradient(#3c4041, #242627); /* CSS3 CR, IE10+ */
+ /*border-bottom: 1px solid #6cab2e;*/
+ /*border-bottom: 4px solid $dominant;*/
+ margin: 0;
+}
+#title a {color: white; text-decoration: none; display: inline-block; padding: 25px 50px; }
+#title img { float: left; margin-right: 20px; height: 36px;}
+#labelpmmenu,#labelmenu1{display:none; cursor:pointer;}
+#pmmenu,#menu1{display:none;}
+#menu {position: absolute; top:5px; right:5px;}
+#menu-list {display: inline;margin: 0;padding: 0;list-style: none; }
+#menu-list a, #menu-list label{color: #ddd;text-decoration: none;padding: 3px 7px; }
+#menu-list a:hover {
+ background: #000;
+ border-radius: 3px;
+}
+#s_loginbox, #s_quickactions { display: none;}
+#loginbox, #actionsform { display: none;}
+#s_loginbox:checked ~ #loginbox, #s_quickactions:checked ~ #actionsform { display: block; }
+tr.active, #menu-list a.active, #s_loginbox:checked ~ #show_loginbox, #s_quickactions:checked ~ #actions {
+ /*background: $inverse_link; */
+ color: #fff;
+ border-radius: 3px;
+}
+#s_loginbox:checked ~ #show_loginbox{border-radius: 3px 3px 0 0;}
+#menu-list a#lastsearchlink.active {
+ border-radius: 3px 3px 0px 0px;
+}
+#menu-list li {
+ display: block;
+ padding: 0px 2px;
+ float: left;
+ height: 1.4em;
+ border-left: dotted 1px #888888;
+}
+#menu-list li:first-child {border: none; }
+#menu-list #locked { margin-left: 7px; color: red; }
+div#mysearches {
+ border-radius: 4px;
+ border: solid 3px #6af;
+ overflow: auto;
+ min-width:240px;
+ max-height: 25em;
+ display: none;
+ position: absolute;
+ right: 0;
+ top: 1.4em;
+ margin-top: 1px;
+ z-index: 5;
+ padding: 2px 5px 3px;
+ box-sizing:border-box;
+}
+div#mysearches table#mysearchestable {border-collapse: collapse;width: 100%;margin-bottom: 0;}
+div#mysearches table#mysearchestable a {color: #3c4041;font-weight: normal;}
+div#mysearches table#mysearchestable a:hover {background: none; text-decoration: underline;}
+div#mysearches .searches_delete {width: 20px;}
+div#mysearches a {padding: 0 0 0 0.2em;font-weight: bold;}
+div#mysearches table tr {border-bottom: dotted 1px gray;text-align: left;padding: 0.1em 0;}
+div#mysearches table tr.last {border: 0;}
+div#mysearches td {vertical-align: middle;}
+#pmcontrol div {
+ vertical-align:middle;
+ display: -moz-inline-stack; /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+}
+#pmcontrol input[type="text"]{
+ height:auto;
+}
+#showtask #taskid {width: 50px;}
+#projectselector { /*margin-top: 3px;*/ margin-right: 10px; }
+#projectselector button { margin-bottom: 2px;}
+#projectselector option[selected] { font-weight: bold;}
+#pm-menu{background-color:#222; border-bottom: solid 4px #6af;padding: 0px 0px 0px 10px; /*text-align:center;*/ }
+#pm-menu-list {list-style: none;display: inline-block;margin:0;min-height:30px;}
+#pmcontrol{display: inline-block;float: right;margin: 6px 20px 0px 0px;}
+#pm-menu-list li {display: inline-block;}
+#pm-menu-list li a {display: block; line-height: 1.4em; padding: 11px 11px 8px 35px; color: #f9f9f9;
+ text-decoration: none; border-left: dotted 1px #3c4041; border-top-left-radius: 3px; border-top-right-radius: 3px;
+ background-repeat: no-repeat; background-position: 12px 50%;
+}
+#pm-menu-list a:hover { background-color: #000; border-left: 1px solid #000;}
+#pm-menu-list a.active {
+ border-left: none;
+ border-top: 1px solid #6cab2e;
+ border-right: 1px solid #6cab2e;
+}
+#pm-menu-list li:first-child a {border-left: none;}
+#toplevellink {background-image: url("img/gray/folder_stroke_12x12.png");}
+#homelink {background-image: url("img/gray/list_12x11.png");}
+#newtasklink {background-image: url("img/gray/document_alt_stroke_9x12.png"); }
+#newmultitaskslink {background-image: url("img/gray/document_alt_stroke_9x12.png");}
+#mytaskslink {background-image: url("img/gray/calendar_alt_stroke_12x12.png");}
+#reportslink {background-image: url("img/gray/calendar_alt_stroke_12x12.png");}
+#roadmaplink {background-image: url("img/gray/compass_12x12.png");}
+#projectslink {background-image: url("img/gray/cog_alt_12x12.png");}
+.active#toplevellink {background-image: url("img/white/folder_stroke_12x12.png");}
+.active#homelink {background-image: url("img/white/list_12x11.png");}
+.active#newtasklink {background-image: url("img/white/document_alt_stroke_9x12.png");}
+.active#mytaskslink {background-image: url("img/white/calendar_alt_stroke_12x12.png");}
+.active#reportslink {background-image: url("img/white/calendar_alt_stroke_12x12.png");}
+.active#roadmaplink {background-image: url("img/white/compass_12x12.png");}
+.active#projectslink {background-image: url("img/white/cog_alt_12x12.png");}
+/* --- buttons --- */
+#actionbar { /*height: 4em;*/ position: relative;}
+#actionbar a.button, #actionbar button.button { margin-bottom: 0;}
+#actionbar .main {float: right;}
+.button, button, input[type=submit] {
+ display: -moz-inline-stack; /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+ margin: 0.3em 0.3em 0.3em 0;
+ padding: 4px;
+ /* Links */
+ border: 1px solid #bbb;
+ border-radius: 4px;
+ -moz-box-shadow: 0px 1px 1px #ddd;
+ -webkit-box-shadow: 0px 1px 1px #ddd;
+ box-shadow: 0px 1px 1px #ddd;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color')";
+ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color');
+ font-family: "Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+ font-size: 100%;
+ line-height: 130%;
+ text-decoration: none;
+ font-weight: bold;
+ color: #565656;
+ cursor: pointer;
+}
+a.button img, button.button img {
+ margin: 0 3px -3px 0 !important;
+ padding: 0;
+ border: none;
+ width: 16px;
+ height: 16px;
+ float: none;
+}
+a.button:hover, button:hover, .button:hover, input[type=submit] {background-color: #f2f2f2;border: 1px solid #a1a1a1;}
+a.button.disabled{background-color: #f2f2f2; border: 1px solid #a1a1a1;color: #9e9e9e;}
+a.button:active, button.button:active {background-color: #6299c5;border: 1px solid #6299c5;color: #fff;}
+body a.positive, body button.positive {
+ border: solid 1px #5a8f27;
+ border-top: solid 1px #85cb41;
+ border-right: solid 1px #85cb41;
+}
+a.positive:hover, button.positive:hover {
+ background-color: #6af;
+ border: solid 1px #6af;
+}
+a.positive:active, button.positive:active {
+ background-color: #529214;
+ border: 1px solid #529214;
+}
+.button.img.delete {
+ background-image: url('./img/red/x_alt_24x24.png');
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-color:transparent;
+ box-shadow:none;
+ border:none;
+}
+#table tr:first-child td:first-child button.img.delete {display: none;}
+
+body .negative {color: #d12f19;}
+a.negative:hover {background-color: #fbe3e4;border: 1px solid #fbc2c4;color: #d12f19;}
+a.negative:active {background-color: #d12f19;border: 1px solid #d12f19;color: #fff;}
+#intromessage {display: block;}
+#tasklist {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+#tasklist_table {width: 100%;}
+span.pagenums:before{ content:"--";padding-left:10px;padding-right:10px;}
+#search {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ position:relative;
+}
+#search fieldset {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+#search #exporttasklist {
+ position:absolute;
+ right:0;
+ top:0;
+}
+#intromessage {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+body.toplevel .box {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ margin: 10px 0 0 10px;
+ width: 400px;
+ /*@include inline-block;*/
+ /*vertical-align: middle;*/
+ float: left;
+}
+body.toplevel .single-project {
+ width: auto;
+ float: none;
+ display: block;
+ height: auto;
+}
+body.myprofile .box {display:inline-block;vertical-align:top;}
+#editgroup, #userlist{display:inline-block;vertical-align:top;}
+.progress_bar_container {
+ width: 150px;
+ margin:0;
+ border: solid 1px rgba(0,0,0,0.3);
+ height: 15px;
+ position: relative;
+ border-radius: 2px;
+ display: -moz-inline-stack; /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+ vertical-align: sub;
+}
+.progress_bar_container .progress_bar {
+ height: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+ background-color: #6af; /* fallback/image non-cover color */
+ background-image: -moz-linear-gradient(#1061ef, #6af); /* Firefox 3.6+ */
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#1061ef), to(#6af)); /* Safari 4+, Chrome 1+ */
+ background-image: -webkit-linear-gradient(#1061ef, #6af); /* Safari 5.1+, Chrome 10+ */
+ background-image: linear-gradient(#1061ef, #6af); /* CSS3, IE10+ */
+}
+.progress_bar_container span {
+ text-align: center;
+ position: absolute;
+ z-index: 1;
+ width: 100%;
+ font-size: 10px;
+ line-height: 1em;
+ text-shadow: 0 0 6px white;
+ top:2px;
+}
+td.task_progress .progress_bar_container {width: 100%;box-sizing:border-box;}
+a {text-decoration: none;}
+#tasklist_table th img {position: relative;top: 2px;}
+#tasklist_table tr.current_row td.caret {background-image: url(img/caret.gif);background-repeat:no-repeat;background-position: 3px;}
+#tasklist_table td.caret {width: 15px;padding: 0 !important;}
+#tasklist_table .ttcolumn {width: 10px;text-align: center;}
+#tasklist_table .ttcolumn input {margin: 0;}
+#tasklist_table .ttcolumn a, a.toggle_selected {
+ background-image: url(img/black/loop_alt3_12x9.png);
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 16px;
+ height: 16px;
+ display: block;
+}
+#tasklist_table thead tr th {line-height:1;}
+#tasklist_table tbody tr td {border-top:1px solid transparent;border-bottom:1px solid transparent;line-height:1.2;vertical-align:middle;}
+#tasklist_table tbody tr:hover td {border-color:#ddd;}
+#tasklist_table tr.closed, #myvotes tr.closed {background-color:#ddd;}
+tr.severity1 .task_severity {background-color: #fff;}
+tr.severity2 .task_severity {background-color: #fff;}
+tr.severity3 .task_severity {background-color: #f5e7e7;}
+tr.severity4 .task_severity {background-color: #f5dddd;}
+tr.severity5 .task_severity {background-color: #f5d1d1;}
+
+
+td.task_openedby > a, td.task_editedby > a, td.task_assignedto > a {white-space:nowrap;position:relative;line-height:1;display:block;padding:3px;}
+td.task_openedby > a .fa-user, td.task_editedby > a .fa-user {color:#eee;}
+td.task_assignedto > a {margin-right:2px;}
+/* small names over avatars */
+td.task_openedby > a::after, td.task_editedby > a::after, td.task_assignedto > a::after {
+ background-color: rgba(255, 255, 255, 0);
+ bottom: 0;
+ color: rgba(0,0,0,0);
+ content: attr(title);
+ font-size: 8px;
+ left: 0;
+ line-height:1;
+ position: absolute;
+ text-align: center;
+ padding-left:1px;
+ padding-right:1px;
+}
+td.task_openedby > a:hover::after, td.task_editedby > a:hover::after, td.task_assignedto > a:hover::after {color:#000;background-color:rgba(255,255,255,0.9);}
+
+#toolbox {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ margin-left: 120px;
+ min-height: 450px;
+ height: 1%; /* Fix for IE bug */
+ padding: 20px;
+}
+#toolbox div.tab {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+#toolbox h3 {
+ margin-bottom: 1em;
+ padding-bottom: 5px;
+ border-bottom: 1px solid #ddd;
+}
+#toolboxmenu {position: relative;float: left;padding-top: 10px;}
+#toolboxmenu a {
+ display: block;
+ border: 1px solid #d4d4d4;
+ /*border-right: none; */
+ padding: 1em 5px;
+ margin-left: 10px;
+ width: 100px;
+ text-align: center;
+ background: #e5e5e5;
+ color: #3c4041;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+}
+#toolboxmenu a:hover {background-color: #d6d8d9; /*color: $inverse_link;*/ }
+#toolboxmenu a.active {
+ margin-left: 0;
+ font-weight: bold;
+ width: 100px;
+ padding: 1em 10px;
+ color: #3c4041;
+ border: 1px solid #e1e1e1;
+ border-right: none;
+ /*border-left: 1px solid lighten($top1, 30%);*/
+ border-left: 2px solid #6af;
+}
+#controlBox {
+ border: 1px solid gray;
+ padding: 1px;
+ width: auto !important;
+ width: 90px;
+ margin-left: 40px;
+ display: table;
+ position: absolute;
+}
+#controlBox div.grip {
+ background: #ccc;
+ cursor: move;
+ height: 12px;
+}
+#controlBox div.inner {
+ padding: 5px 5px 4px 5px;
+ white-space: nowrap;
+ opacity: .2;
+}
+#controlBox.active div.inner {opacity: 1;}
+div#fineprint {
+ font-size: smaller;
+ margin: 5px 0;
+ padding: 0;
+ color: #555;
+}
+.dokuwiki_toolbar {display:inline;vertical-align:bottom;}
+form #taskfields { width: 335px;}
+form #taskdetailsfull {
+ position: relative;
+ top: -20px;
+ margin-left: 355px;
+}
+
+/* style tables in task comments (dokuwiki) */
+table.inline{border-collapse:collapse;}
+table.inline td {border:1px solid #ccc; padding:4px;}
+
+#shortcutlabel { cursor:pointer;padding-left:1em;}
+#shortcutclose { cursor:pointer;float:right; }
+#shortcuts {
+ display:none;
+ position:fixed;
+ z-index:100;
+ background:#fff;
+ border:1px solid #999;
+ border-radius:10px;
+ padding:10px;
+ box-shadow:0 0 400px #000;
+ top:50%;
+ margin-top:-250px;
+ height:520px;
+ left:50%;
+ width:300px;
+ margin-left:-150px;
+ box-sizing:border-box;
+}
+#shortcutsmodal {
+ background-color: rgba(0, 0, 0, 0.3);
+ display: none;
+ height: 100%;
+ left: 0;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 99;
+ cursor:pointer;
+}
+#s_shortcuts {display:none;}
+#s_shortcuts:checked ~ #shortcuts, #s_shortcuts:checked ~ #shortcutsmodal {display: block;}
+#shortcuts > ul {
+ list-style: outside none none;
+ margin-left: 0;
+ padding-left: 0;
+}
+#shortcuts li{line-height:2em;}
+kbd {background-color: #eee;border: 1px solid #ccc;border-radius: 4px;padding: 2px;}
+
+#taskdetails {
+ margin: 10px 0px 10px 0px;
+ padding: 0px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+#taskdetails #navigation {float: right;padding:6px;}
+#taskdetails h2 {color: #555;display:inline-block;}
+#taskdetailsfull p,
+#taskdetailsfull li,
+.commenttext p,
+.commenttext li {
+max-width:40em;
+}
+.group.inactive{ background-color:#eee;}
+.tag {
+ background: #ccc none repeat scroll 0 0;
+ border-radius: 3px;
+ margin-left: 3px;
+ padding: 3px;
+ font-style:normal;
+ white-space: nowrap;
+ vertical-align:middle;
+}
+.tag:before {font-family:fontawesome;padding-right:3px;}
+.tag:after {content:attr(title);}
+/* style your tags by their tag_id, may be managed by tag list management in admin/project settings */
+/*
+.tag.t1{background-color:#666;}
+.tag.t1:before{content:'\f153';color:#cc0;}
+.tag.t2{background-color:#0c0;}
+.tag.t3{background-color:#09c;}
+*/
+#taskfields {
+ width: 290px;
+ float: left;
+ border-right: 1px solid #eee;
+ padding-top: 5px;
+ background: #f9f9f9;
+ margin: 0;
+}
+#taskfields ul.fieldslist {
+ margin-bottom: 5px;
+ list-style: none;
+ margin-right: 0;
+ padding-left: 0;
+}
+#taskfields ul.fieldslist li {
+ clear: both;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ overflow: auto;
+ width: 100%;
+ border-radius: 4px;
+}
+#taskfields ul.fieldslist li .label {
+ display: block;
+ float: left;
+ width: 45%;
+ text-align: right;
+ color: #888;
+}
+#taskfields ul.fieldslist li .value {display:block;float:right;width:50%;}
+#taskfields ul.fieldslist > li:nth-child(2n-1) {background-color: #f1f1f1;}
+#taskdetails #showvotes {
+ position: absolute;
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ z-index:100;
+}
+.fieldslist .reopened{display:inline-block;font-size:10px;line-height:10px;vertical-align:middle;width:60px;}
+#taskdetailsfull {margin-left: 291px;padding: 2em 3em;}
+#itemsummary, #tags {width:100%;box-sizing:border-box;height:auto;margin-bottom:10px;margin-top:8px;}
+.descbox table, #taskdetailstext table{border-collapse:collapse; border:1px solid #999;}
+.descbox td, .descbox th, #taskdetailstext td, #taskdetailstext th{border:1px solid #999;}
+#taskinfo {
+ margin-left: 291px;
+ margin-top: 15px;
+ border-top: 1px solid #e1e1e1;
+ padding: 8px 5px;
+}
+#comments, #related, #notify, #remind, #effort, #history {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+#comments h4 {margin:1em 0 0.5em 0;}
+.userlist th {background-color: #e0e0e0;}
+.userlist tbody tr:nth-child(2n) td {background-color: #f1f1f1;}
+#related .related {display:inline-block;vertical-align:top;margin-right: 0;}
+#submenu {margin-bottom: 0;height: 25px;}
+#submenu a {
+ border: 1px solid #d4d4d4;
+ background: #e5e5e5;
+ color: #3c4041;
+ display: block;
+ float: left;
+ height: 21px;
+ margin: 4px 0 0 2px;
+ padding: 7px 10px 1px 10px;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+}
+#submenu a:hover {background-color: #d6d8d9;}
+#submenu a.active {
+ height: 26px;
+ margin-top: 0;
+ font-weight: bold;
+ border-bottom: 1px solid white;
+ border-right: bottom;
+ /*border-top: 1px solid lighten($top1, 30%);*/
+ border-top: 2px solid #6af;
+ z-index: 5;
+}
+#submenu li {display: inline;}
+div.tab {margin: 10px 1ex 10px 0;padding: 1ex 1ex 0;}
+.tab{display:none;}
+.tab.active{display:block;}
+* html .tab div.clear {clear: none;height: 14em;}
+div.comment_container {margin: 5px;}
+div.comment_container .comment_avatar { min-width: 50px;display: inline-block;}
+div.comment_container .comment_avatar .av_comment img { width: 50px; height: 50px }
+div.comment_container .comment {
+ position: relative;
+ display: inline-block;
+ margin-left: 10px;
+ border: 1px solid #e1e1e1;
+ vertical-align: top;
+ width: 700px;
+ border-radius: 3px;
+}
+div.comment_container .comment:before {
+ content: ' ';
+ position: absolute;
+ width: 0;
+ height: 0;
+ left: -16px;
+ top: 3px;
+ border: 8px solid;
+ border-color: transparent #e1e1e1 transparent transparent;
+}
+div.comment_container .comment:after {
+ content: ' ';
+ position: absolute;
+ width: 0;
+ height: 0;
+ left: -12px;
+ top: 5px;
+ border: 6px solid;
+ border-color: transparent #f1f1f1 transparent transparent;
+}
+div.comment_container .comment_header {
+ min-height: 30px;
+ background-color: #f1f1f1;
+ line-height: 30px;
+ vertical-align: middle;
+ padding-left: 10px;
+ border-bottom: 1px solid #e1e1e1;
+}
+div.comment_container .comment_header_infos {max-width: 60%;}
+div.comment_container .comment_header_actions {float: right;margin-right: 10px;}
+div.comment_container .comment_header_usertype {
+ border: 1px solid #e1e1e1;
+ padding: 3px;
+ margin-right: 10px;
+ border-radius: 3px;
+}
+div.comment_container .commenttext {clear: both;padding: 10px;}
+.commenttext pre {overflow:auto;}
+div.comment_container .attachments {
+ display: inline-block;
+ width: 45%;
+ margin-top: 10px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ width: 300px;
+ background-color: #ffc;
+ /*
+ background-image: url(img/gray/pin_24x24.png);
+ background-repeat: no-repeat;
+ background-position: 270px 5px;
+ */
+ vertical-align: top;
+}
+div.attachments::after {
+ color: #999;
+ content: "\f0c6"; /*fa-paperclip*/
+ float: right;
+ font-family: FontAwesome;
+ font-size: large;
+}
+div.comment_container .links {
+ display: inline-block;
+ width: 45%;
+ margin-right: 3%;
+ margin-top: 10px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+ width: 300px;
+ background-color: #fefefe;
+ vertical-align: top;
+}
+div.comment_container .attachments img {
+ position: relative;
+ top: 4px;
+}
+div.comment_container #addlinkbox, #taskdetailsfull #addlinkbox {
+ display: inline-block;
+ width: 50%;
+ vertical-align: top;
+}
+div.comment_container #uploadfilebox, #taskdetailsfull #uploadfilebox {
+ display: inline-block;
+ width: 49%;
+ vertical-align: top;
+}
+div.comment_container .commentlink {
+ background-image: url(img/gray/comment_stroke_16x14.png);
+ background-repeat: no-repeat;
+ display: block;
+ float: left;
+ height: 14px;
+ width: 16px;
+ margin: 2px 4px 0 2px;
+}
+div.comment_container .commentlink:hover {background-image: url(img/black/comment_stroke_16x14.png);}
+textarea {width: 99%;}
+#events {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+.eventlist.hasresult {height: 150px;}
+table th {vertical-align: middle;}
+table td {vertical-align: top;}
+div.box, p.box {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+div.roadmap {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+div#successanderrors {
+ border: none;
+ border-radius: 3px;
+ z-index: 50;
+ margin: 30px auto 0 auto;
+ position: absolute;
+ top: 0;
+ width: 30%;
+ min-width:300px;
+ left: 35%;
+}
+div.error, div.success {
+ border-bottom: 1px solid rgba(0,0,0,0.5);
+ padding: 6px;
+}
+div.error {
+ color: #4b1710;
+ background: #ffe6e2;
+}
+div.success {
+ color: #24380d;
+ background: #e8f9d4;
+}
+a#show_loginbox {margin-right: 5px;}
+#loginbox {
+ border-radius: 3px 0 3px 3px;
+ background: #ddd;
+ border: solid 3px #6af;
+ right: 2px;
+ z-index: 200;
+ min-width: 240px;
+ padding-top: 15px;
+ margin-top: 1px;
+ box-shadow: none;
+}
+#login_input {text-align: center;}
+#login_links {text-align: center;}
+#login_oauth {margin-top: 10px;}
+#loginbox #login label {color: #222;}
+#loginbox #login a {color: #222;text-decoration: underline;}
+#loginbox #login a:hover {color: #fff;}
+form#login {position: relative;}
+form#login #lbl_user_name, form#login #lbl_password {
+ display:block;
+ margin: 10px auto;
+ padding: 5px 2px;
+}
+form#login label {width: 50px;color: #ddd;width: 100px;margin: 0 2px 0 20px;}
+form#login #links {position: absolute;top: 30px;left: 20px;}
+form#login #links a {padding: 3px 7px;}
+form#login #links a:hover {
+ background: #494d4e;
+ text-decoration: none;
+ color: #dddddd;
+ border-radius: 10px;
+}
+form#login #links a.active {
+ background: #dddddd;
+ color: #3c4041;
+ border-radius: 10px;
+}
+form#login .remember_me {float: right;}
+span#advancedsearchstate img {vertical-align: middle;}
+fieldset {
+ margin: 10px 0px 10px 0px;
+ padding: 10px;
+ border: solid 1px #e1e1e1;
+ border-radius: 3px;
+}
+/*
+#content > fieldset legend {
+ color: #3c4041;
+ background: #f9f9f9;
+ padding: 2px 9px;
+ border: solid 1px #e1e1e1;
+ border-radius: 10px;
+}
+*/
+#content > h3 {margin-top: 1em;}
+thead th {
+ border-bottom: solid 1px #a1a6a8;
+ background: none;
+}
+div#taskstatus {border-bottom: 1px solid #ccc;padding: 4px;margin-bottom: 5px;}
+div#taskclosed {
+ padding: 5px;
+ margin: 5px 5px 10px;
+ clear: both;
+ width: 20em;
+ border: solid 1px red;
+}
+div#taskdeps {float: left;width: 50%; margin-bottom: 8px;}
+div#subtasks {display: inline;align: right;float: left;width: 50%;margin-bottom: 8px;}
+div#taskblocks { float: left; width: 45%;}
+a.datelink {
+ background-image: url(img/black/calendar_alt_fill_16x16.png);
+ background-repeat: no-repeat;
+ display: -moz-inline-stack;
+ /* optional */
+ display: inline-block;
+ zoom: 1;
+ /* triggers hasLayout for IE */
+ *display: inline;
+ /* target IE7 only */
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+}
+hr { /*color: $box_border;*/ border: none; border-top: 1px solid #e1e1e1;}
+.perms{display:inline-block;vertical-align:top;}
+.bad{color:#900;}
+.good{color:#090;}
+
+fieldset.advsearch_misc input {vertical-align: middle;}
+fieldset.advsearch_misc label {margin-right: 10px; white-space: nowrap;}
+fieldset.advsearch_dates label {margin: 0 5px;}
+.search_select {float: left;position: relative;margin-right: 10px;}
+.search_select .multisel {position: absolute;white-space: nowrap;}
+.search_select select {margin-top: 1.5em;height:158px;}
+.search_select select#percent option {font-size: 10px;height: 12px;}
+fieldset.advsearch_users .multisel {position: absolute;white-space: nowrap;}
+fieldset.advsearch_users input {margin-top: 1.5em;}
+/* --------------------------- FORMS -----------------------*/
+.bulkuser {padding-left: 120px;}
+.account_header{background: #999;border: 1px solid #111;}
+.account_enabled{background: #EAF7D9;border: 1px solid #BBDF8D;}
+.account_enabled:hover {background: #00FF00;}
+.account_disabled{background: #FFD1D1;border: 1px solid #F8ACAC;}
+.account_disabled:hover {background: #FF0000;}
+ul.form_elements {list-style: none;padding: 0;margin: 0 0 0 1em;}
+ul.form_elements li {padding: 2px;margin-bottom: 9px;}
+ul.form_elements label {
+ width: 200px;
+ display: -moz-inline-stack;
+ /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+ vertical-align: top;
+ text-align: end;
+ padding-right: 1ex;
+ padding-top: 2px;
+ color: #555;
+}
+ul.form_elements label.inline {display: inline;vertical-align:middle;}
+ul.form_elements label.labeltextarea {width:auto;display:block;text-align:left;}
+ul.form_elements input {vertical-align: middle;}
+ul.form_elements input[type="text"], ul.form_elements input[type="password"] {width:300px;}
+ul.slim li {margin-bottom:0;padding-bottom:4px;}
+ul.slim input[type="text"], ul.slim input[type="password"] {width:auto;}
+ul.slim label {width:110px;}
+ul.form_elements li.required label {font-weight: bold;}
+ul.form_elements span.note {margin-left:205px;}
+ul.wide label {width:250px;}
+ul.slim {margin:0;}
+ul.slim .userSelectWidget {width:95%;margin-left:auto;margin-right:auto;}
+ul.slim .userSelectWidget input {width:100px;}
+ul.slim .userSelectWidget select {width:100%;}
+.dateselect {clear: both;}
+.dateselect label:first-child {
+ width: 120px;
+ text-align: right;
+ padding-right: 5px;
+ float: left;
+}
+/* fancy dual selects */
+.double_select {position: relative;}
+.double_select .dualselect_selectable {
+ height: 220px;
+ width: 40%;
+ display: -moz-inline-stack; /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+ vertical-align: middle;
+}
+.double_select .dualselect_buttons {
+ width: 16%;
+ display: -moz-inline-stack; /* optional */
+ display: inline-block;
+ zoom: 1; /* triggers hasLayout for IE */
+ *display: inline; /* target IE7 only */
+ vertical-align: middle;
+}
+.double_select .dualselect_buttons button {width: 100%;padding: 3px;}
+.double_select .dualselect_selected {
+ height: 220px;
+ /*min-width: 12em;*/
+ display: -moz-inline-stack;
+ /* optional */
+ display: inline-block;
+ zoom: 1;
+ /* triggers hasLayout for IE */
+ *display: inline;
+ /* target IE7 only */
+ vertical-align: middle;
+ width: 40%;
+}
+.double_select .dualselect_selected select {
+ width: 100%;
+ height: 80%;
+}
+.double_select .dualselect_selected button {
+ height: 16px;
+ line-height:0;
+ width: 100%;
+ margin:0;
+}
+.double_select .c1 select {
+ height: 24em;
+ min-width: 12em;
+ width: auto;
+}
+.double_select td {
+ text-align: center;
+ vertical-align: middle;
+}
+.double_select .c3 button {
+ height: 2em;
+ width: 12em;
+}
+.double_select .c3 select {
+ height: 20em;
+ min-width: 12em;
+ width: auto;
+}
+
+#disp_intro + label{text-align:start;}
+
+/* list management */
+.list .cname {width:200px;}
+.list .cuser {width:100px;}
+.list .corder {width:50px;}
+.list .cshow {width:50px;}
+.list .ctense {width:80px;}
+.list input[id^='listname'], .list input[id^='listposition'] {width:100%;}
+#catTable .first{ white-space:nowrap;min-width:120px;}
+#listTable thead#globalentries td {background-color: #eee;}
+#listpositionnew {width:100%;}
+
+.perms tbody th[title] {text-decoration: underline dotted;}
+.perms .everybody{
+ position:relative;
+ position:-webkit-sticky; /* fix for safari */
+}
+.perms .everybody th:before {content:'Allowed for everybody - project setting overrules this group setting!';color:#900;position:absolute;left:2px;z-index:2;text-shadow:0 0 2px #fff;top:-2px;}
+.perms .everybody > * { background-color: #6c6; }
+
+/* closing task form */
+div#closeform {
+ border-radius: 3px;
+ -moz-box-shadow: 0px 1px 1px #dddddd;
+ -webkit-box-shadow: 0px 1px 1px #dddddd;
+ box-shadow: 0px 1px 1px #dddddd;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color')";
+ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color');
+ padding: 2em;
+ position: absolute;
+ right: 5px;
+ top: 3em;
+ z-index:300;
+}
+#actionsform {
+ border-radius: 3px;
+ -moz-box-shadow: 0px 1px 1px #dddddd;
+ -webkit-box-shadow: 0px 1px 1px #dddddd;
+ box-shadow: 0px 1px 1px #dddddd;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color')";
+ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=90, Color='$color');
+ position: absolute;
+ right: 5px;
+ top: 3em;
+ z-index: 200;
+}
+#actionsform ul {
+ font-family: Arial, Verdana;
+ font-size: 12px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+#actionsform li {
+ border-top: 1px solid #fff;
+ display: block;
+ position: relative;
+ background: #363a3b;
+ color:#fff;
+ padding: 0px;
+}
+#actionsform li a, #actionsform li label, #actionsform li button{
+ display: block;
+ text-decoration: none;
+ color: #fff;
+ white-space: nowrap;
+ padding:5px 15px;
+ background:none;
+ margin:0;
+ border:none;
+ font-weight:normal;
+ font-size:12px;
+ box-shadow:none;
+}
+#actionsform ul li:hover { background: #617F8A; }
+#actionsform li:hover ul {display:none;}
+#actionsform li:hover ul {
+ display: block;
+ position: absolute;
+}
+#actionsform li:hover li {float: none;font-size: 11px;}
+#edit_add_comment{display:none;}
+#setparentform, #associateform, #adddepform {
+ display:none;
+ background: #363a3b;
+ color:#fff;
+ padding-left: 5px;
+}
+#s_parent, #s_associate, #s_adddependent, #s_addcomment {
+ display:none;
+}
+#s_addcomment:checked ~ #edit_add_comment,
+#s_parent:checked ~ #setparentform,
+#s_associate:checked ~ #associateform,
+#s_adddependent:checked ~ #adddepform{
+ display:block;
+}
+#actionsform #setparentform *, #actionsform #associateform *, #actionsform #adddepform * {display: inline-block;}
+#actionsform #setparentform button, #actionsform #associateform button, #actionsform #adddepform button {
+ background-color:#fff;
+ color:#000;
+ margin-right:4px;
+ margin-bottom:4px;
+}
+#actionsform #setparentform input[type=text],
+#actionsform #associateform input[type=text],
+#actionsform #adddepform input[type=text] {
+ width:60px;
+}
+#taskdeps h4 {margin-bottom:0.3em;}
+#taskdeps table {margin:0 0 0.8em -0.2em;}
+#taskdeps td {padding:0.2em 0.4em;}
+#taskdeps img {vertical-align:middle;margin-top:-1px;}
+input[type=text], input[type=password]{
+ color: #000;
+ border: 1px solid #ccc;
+ padding: 2px;
+ vertical-align: middle;
+}
+input[type=text], input[type=password]{
+ height: 19px;
+}
+#table input[type=text] {
+ width: 120px;
+}
+input[type=text], input[type=submit], button {
+ border-radius: 3px;
+}
+#show_loginbox {
+ cursor:pointer;
+ background: #494D4E;
+ border-radius: 3px;
+}
+#login_button {display: block;margin: 6px auto;vertical-align: baseline;}
+div.denyform {
+ visibility:hidden;
+ position:absolute;
+ background-image: none;
+ border: 1px solid #E1E1E1;
+ margin-top:5px;
+ display:block;
+ width:300px;
+ height:auto;
+ padding:5px 30px 5px 5px;
+}
+/*#notify_types {height: 10em;}*/
+#rassigned_to {height: 12em; }
+
+/* Stuff for the autocomplete lists {{{ */
+.autocomplete {
+ background-color:#fff;
+ position: absolute;
+ width: auto !important;
+ box-shadow:0 5px 7px #ccc;
+ padding: 0px;
+ margin:-1px 0 0;
+ text-align:left;
+ display:block;
+}
+.autocomplete ul {
+ list-style-type: none;
+ margin: 0px;
+ padding: 0px;
+}
+.autocomplete ul li {
+ list-style-type: none;
+ display: block;
+ margin: 0;
+ padding:0;
+ height: 25px;
+ white-space:nowrap;
+ vertical-align:middle;
+}
+.autocomplete ul li span.informal {
+ color: #333;
+}
+.autocomplete ul li.selected {
+ background-color: #6af;
+ cursor:pointer;
+ color:HighlightText;
+}
+.autocomplete img, .autocomplete .noavatar{
+ width:25px;
+ height:25px;
+ display:inline-block;
+}
+
+#actionbuttons {
+ margin-left: 300px;
+ margin-top: 15px;
+ min-height: 50px;
+ padding: 8px 5px;
+}
+table.assignedto {border:0px;border-collapse: collapse;}
+table.assignedto tr {height: 30px;}
+table.assignedto td {
+ margin:0px;
+ padding:0px;
+ padding-right: 5px;
+ vertical-align: middle;
+}
+table.assignedto img {padding-top: 2px;width: 25px;height:25px;}
+a.tooltip {outline:none; }
+a.tooltip strong {line-height:30px;}
+a.tooltip:hover {text-decoration:none;}
+a.tooltip span {
+ z-index:10;display:none; padding:14px 20px;
+ margin-top:60px; margin-left:-160px;
+ width:240px; line-height:16px;
+}
+a.tooltip:hover span{
+ display:inline; position:absolute;
+ border:2px solid #FFF; color:#EEE;
+ background:#000;
+}
+.callout {z-index:20;position:absolute;border:0;top:-14px;left:120px;}
+/*CSS3 extras*/
+a.tooltip span{
+ border-radius:2px;
+ -moz-box-shadow: 0px 0px 8px 4px #666;
+ -webkit-box-shadow: 0px 0px 8px 4px #666;
+ box-shadow: 0px 0px 8px 4px #666;
+ opacity: 0.8;
+}
+/* for float box in ticket list */
+.descbox {
+ display:none;
+ border: 1px solid #e1e1e1;
+ border-radius: 3px;
+ background-image: none;
+ margin: 10px 0;
+ padding: 10px;
+ max-height: 144px;
+ width: 400px;
+ position: absolute;
+ z-index: 1000;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ color: #686868;
+ font-weight: normal;
+ text-overflow:ellipsis;
+ overflow:hidden;
+}
+span.warning {
+ color: red;
+ font-weight: bold;
+}
+
+#globoslink .fa, #projoslink .fa{padding-right: 3px;}
+/* typical colorings for task type and permissions */
+.fa-bug{color:#c30;}
+.fa-star{color:#ee0;text-shadow: 0 0 1px rgba(128, 0, 0, 1);}
+.fa-exclamation-triangle{color:#c00;}
+.fa-check{color:#090;}
+.global.fa-check{color:#990;} /* got permission by global settings, not project settings */
+.fa-ban{color:#900;}
+
+button.fakelinkbutton {
+ display: inline-block;
+ background: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+ font: inherit;
+ cursor: pointer;
+ color: #369;
+ box-shadow:none;
+}
+
+button.fakelinkbutton:hover {
+ color: #69c;
+}
+
+button.fakelinkbutton:visited {
+ color: #69c;
+}
+
+@media only screen and (max-width: 480px) and (orientation: portrait) {
+
+th, td, caption {padding: 5px 2px;}
+#menu { position:static;clear:both;background-color:#111;}
+#title {font-size:1.2em;line-height:1.2em;background-color:#111;}
+#title a {padding:0;}
+#title a span{display:none;}
+#title img {float:none;margin:4px;}
+#menu-list {display:block;}
+#menu-list li {float:none;display:block;padding:0;height:auto;border-left:none;text-align:center;}
+#menu-list li > a, #menu-list li > label {display:block;border:1px solid #333;height:24px;}
+#menu-list a:hover {border-radius:unset;}
+#labelpmmenu, #labelmenu1{text-align:center;color:#fff;display:block;position:absolute;top:0;width:15%;height:48px;left:20%;}
+#labelmenu1{left:35%;}
+#labelmenu1:before{content:"\f013";font-family:fontawesome;font-size:30px;} /* cog */
+#labelpmmenu:before{content:"\f0c9";font-family:fontawesome;font-size:30px;} /* bars */
+#menu1:checked ~ #menu{right:0;transition:0.5s;display:block;}
+#pmmenu:checked ~ #pm-menu-list{left:0;transition:0.5s;display:block;}
+#labelpmmenu:hover, #labelmenu1:hover, #menu1:checked ~ #labelmenu1, #pmmenu:checked ~ #labelpmmenu {background-color:rgba(0,0,0,0.3);}
+#pm-menu {padding:0;}
+#menu {z-index:111;padding:0;display:none;width:100%;position:absolute;background-color:#333;top:48px;right:-100%;transition:0.5s;}
+#pm-menu-list {z-index:110;padding:0;display:none;width:100%;position:absolute;background-color:#333;top:48px;left:-100%;transition:0.5s;}
+#pm-menu-list li {display:block;border-top:1px dotted #3c4041;}
+#pm-menu-list a {border:none;border-radius:0;margin:0;}
+#pmcontrol {margin:0;display:block;float:none;padding:10px;}
+#task_id {width:120px;}
+#projectselector {position:absolute;top:0;right:0;margin:0;text-align:center;max-width:40%;}
+#projectselector select{height:40px;padding-right:4px;max-width:100%;}
+#content {padding:0;min-width:unset;}
+#tasklist {padding:0;}
+table.toplevel th, table.toplevel td {display:block;}
+body.toplevel .box {margin:0 0 10px 0;}
+body.toplevel.p0 .box {width:auto;}
+div.box, p.box {padding:0;}
+#event1 td {display:block;}
+body.admin {background-color:#300;} /* experimental: danger zone */
+body.admin h1#title {background-color: #900;background-image: none;} /* danger zone */
+body.admin #pm-menu {background-color:#600;border-bottom-color:#300;} /* danger zone */
+body.reports .box button {display: block;margin-left:auto;margin-right:auto;width:70%;}
+#footer{border-top:none;}
+#toolboxmenu {float:none;text-align:center;}
+#toolboxmenu a {width:31%;display:inline-block;margin-left:0;border-bottom-left-radius: 0;padding:6px 2px;}
+#toolboxmenu a.active {border-top-right-radius:5px;width:31%;padding:6px 2px;float:none;border-color:#6af #6af transparent #6af;}
+#toolbox {margin:0;padding:0;border-radius:0;padding-top:10px;position:relative;}
+#controlBox {position:relative;margin-left:140px;}
+#shortcuts {border-radius:5px;padding:5px;box-shadow:0 0 400px #000;top:50%;margin-top:-150px;height:300px;overflow:auto;}
+h3 {padding-left:6px;}
+p { text-align:justify;padding:2px;}
+#listTable{width:100%;}
+ul.form_elements {margin:0;}
+ul.form_elements span.note {margin-left:0;}
+ul.form_elements li label {text-align:start;}
+ul.form_elements li > input {margin-left:20px;}
+ul.form_elements li > input[type="text"],ul.form_elements li > input[type="password"] {display:block;margin-left:auto;margin-right:auto;width:90%;}
+.double_select {padding-left:0;}
+#toolbox div.tab {padding:0px;}
+#submenu {padding-left:0;margin:0;}
+#submenu ~ div {clear:both;}
+#catTable input[type="text"], #catTable input[type="password"] {width:120px;}
+#taskdetails {margin:0;padding:0;margin-top:40px;}
+#taskfields {float:none; border:none;}
+#taskdetailsfull {margin:0;padding:2px;}
+form #taskdetails {margin:0;}
+form #taskfields {width:auto; margin-top:20px; background-color:#fff;}
+form #taskdetailsfull {margin:0;top:auto;}
+form #taskdetailsfull button.positive {margin-left:auto;margin-right:auto;width:60%;}
+#comments, #related, #notify, #remind, #effort, #history{padding:0;}
+div.comment_container {margin:0;}
+div.comment_container .commenttext{padding:2px;}
+div.comment_container .comment {margin:0;width:auto;}
+}
diff --git a/themes/CleanFS/theme_print.css b/themes/CleanFS/theme_print.css
new file mode 100644
index 0000000..710d8d7
--- /dev/null
+++ b/themes/CleanFS/theme_print.css
@@ -0,0 +1,51 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* print.css */
+body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
+.container {background:none;}
+hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
+code {font:.9em "Courier New", Monaco, Courier, monospace;}
+a img {border:none;}
+p img.top {margin-top:0;}
+blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
+.small {font-size:.9em;}
+.large {font-size:1.1em;}
+.quiet {color:#999;}
+.hide {display:none;}
+a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
+a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
+
+#taskfields, #taskdetailsfull {display:inline-block;}
+
+#navigation,
+#intromessage,
+#pm-menu,
+#menu,
+#actionbar,
+#shortcuts,
+#shortcutlabel,
+#shortcutsmodal,
+#footer,
+.commenttext >form,
+#related button,
+#formaddrelatedtask,
+#remind >fieldset,
+#notify >form,
+#notify button,
+#s_shortcuts,
+#s_quickactions {
+display: none;
+}
diff --git a/themes/CleanFS/typography.css b/themes/CleanFS/typography.css
new file mode 100644
index 0000000..196fa88
--- /dev/null
+++ b/themes/CleanFS/typography.css
@@ -0,0 +1,87 @@
+/* no colors here. Lets do colors,font-style and text-decoration in extra css files for easy customization */
+body {
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+}
+h1,h2,h3,h4,h5,h6 { font-weight: normal; }
+h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
+h2 { font-size: 2em; margin-bottom: 0.75em; }
+h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
+h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
+h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
+h6 { font-size: 1em; font-weight: bold; }
+
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {
+ margin: 0;
+}
+
+p { margin: 0 0 1.5em; }
+/*
+ These can be used to pull an image at the start of a paragraph, so
+ that the text flows around it (usage: <p><img class="left">Text</p>)
+*/
+/*.left { float: left !important; }*/
+p .left { margin: 1.5em 1.5em 1.5em 0; padding: 0; }
+/*.right { float: right !important; }*/
+p .right { margin: 1.5em 0 1.5em 1.5em; padding: 0; }
+
+/*
+a:focus, a:hover { color: #09f; }
+a { color: #06c; text-decoration: underline; }
+*/
+blockquote { margin: 1.5em; font-style: italic;}
+strong, dfn { font-weight: bold; }
+em, dfn { font-style: italic; }
+sup, sub { line-height: 0; }
+
+/*
+abbr,acronym { border-bottom: 1px dotted #666; }
+*/
+address { margin: 0 0 1.5em; font-style: italic; }
+/*
+del { color:#666; }
+*/
+
+pre { margin: 1.5em 0; white-space: pre; }
+pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
+
+li ul, li ol { margin: 0; }
+ul, ol { margin: 0 1.5em 1.5em 0; padding-left: 1.5em; }
+
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+
+dl { margin: 0 0 1.5em 0; }
+dl dt { font-weight: bold; }
+dd { margin-left: 1.5em;}
+
+/*
+ Because of the need for padding on th and td, the vertical rhythm
+ on table cells has to be 27px, instead of the standard 18px or 36px
+ of other elements.
+ */
+table { margin-bottom: 1.4em; }
+th { font-weight: bold; }
+/*
+thead th { background: #c3d9ff; }
+*/
+th,td,caption { padding: 4px 10px 4px 5px; }
+tfoot { font-style: italic; }
+/*
+caption { background: #eee; }
+*/
+.small { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
+.large { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
+.hide { display: none; }
+
+/*
+.quiet { color: #666; }
+.loud { color: #000; }
+.highlight { background:#ff0; }
+.added { background:#060; color: #fff; }
+.removed { background:#900; color: #fff; }
+*/
+
+.first { margin-left:0; }
+.last { margin-right:0; padding-right:0; }
+.top { margin-top:0; padding-top:0; }
+.bottom { margin-bottom:0; padding-bottom:0; }
diff --git a/themes/CleanFS/up.png b/themes/CleanFS/up.png
new file mode 100644
index 0000000..3549df5
--- /dev/null
+++ b/themes/CleanFS/up.png
Binary files differ
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: "ï¼­ï¼³ 明æœ" is a
+ // legitimate font-name
+ // <http://ja.wikipedia.org/wiki/MS_明æœ>. See
+ // the CSS3 spec for more examples:
+ // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
+ // You can see live samples of these on the Internet:
+ // <http://www.google.co.jp/search?q=font-family+ï¼­ï¼³+明æœ|ゴシック>
+ // However, most of these fonts have ASCII equivalents:
+ // for example, 'MS Mincho', and it's considered
+ // professional to use ASCII font names instead of
+ // Unicode font names. Thanks Takeshi Terada for
+ // providing this information.
+ // The following characters, to my knowledge, have not been
+ // used to name font names.
+ // - Single quote. While theoretically you might find a
+ // font name that has a single quote in its name (serving
+ // as an apostrophe, e.g. Dave's Scribble), I haven't
+ // been able to find any actual examples of this.
+ // Internet Explorer's cssText translation (which I
+ // believe is invoked by innerHTML) normalizes any
+ // quoting to single quotes, and fails to escape single
+ // quotes. (Note that this is not IE's behavior for all
+ // CSS properties, just some sort of special casing for
+ // font-family). So a single quote *cannot* be used
+ // safely in the font-family context if there will be an
+ // innerHTML/cssText translation. Note that Firefox 3.x
+ // does this too.
+ // - Double quote. In IE, these get normalized to
+ // single-quotes, no matter what the encoding. (Fun
+ // fact, in IE8, the 'content' CSS property gained
+ // support, where they special cased to preserve encoded
+ // double quotes, but still translate unadorned double
+ // quotes into single quotes.) So, because their
+ // fixpoint behavior is identical to single quotes, they
+ // cannot be allowed either. Firefox 3.x displays
+ // single-quote style behavior.
+ // - Backslashes are reduced by one (so \\ -> \) every
+ // iteration, so they cannot be used safely. This shows
+ // up in IE7, IE8 and FF3
+ // - Semicolons, commas and backticks are handled properly.
+ // - The rest of the ASCII punctuation is handled properly.
+ // We haven't checked what browsers do to unadorned
+ // versions, but this is not important as long as the
+ // browser doesn't /remove/ surrounding quotes (as IE does
+ // for HTML).
+ //
+ // With these results in hand, we conclude that there are
+ // various levels of safety:
+ // - Paranoid: alphanumeric, spaces and dashes(?)
+ // - International: Paranoid + non-ASCII Unicode
+ // - Edgy: Everything except quotes, backslashes
+ // - NoJS: Standards compliance, e.g. sod IE. Note that
+ // with some judicious character escaping (since certain
+ // types of escaping doesn't work) this is theoretically
+ // OK as long as innerHTML/cssText is not called.
+ // We believe that international is a reasonable default
+ // (that we will implement now), and once we do more
+ // extensive research, we may feel comfortable with dropping
+ // it down to edgy.
+
+ // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
+ // str(c)spn assumes that the string was already well formed
+ // Unicode (which of course it is).
+ if (strspn($font, $this->mask) !== strlen($font)) {
+ continue;
+ }
+
+ // Historical:
+ // In the absence of innerHTML/cssText, these ugly
+ // transforms don't pose a security risk (as \\ and \"
+ // might--these escapes are not supported by most browsers).
+ // We could try to be clever and use single-quote wrapping
+ // when there is a double quote present, but I have choosen
+ // not to implement that. (NOTE: you can reduce the amount
+ // of escapes by one depending on what quoting style you use)
+ // $font = str_replace('\\', '\\5C ', $font);
+ // $font = str_replace('"', '\\22 ', $font);
+ // $font = str_replace("'", '\\27 ', $font);
+
+ // font possibly with spaces, requires quoting
+ $final .= "'$font', ";
+ }
+ $final = rtrim($final, ', ');
+ if ($final === '') {
+ return false;
+ }
+ return $final;
+ }
+
+}
+
+// vim: et sw=4 sts=4
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>